/* flow1.jsx — onboarding primitives + Welcome · Verify-intro · Plaid IDV · Success.
   Built on the latest mobile app's atoms (Icon, Btn, Money, Eyebrow, Seam, Logo). */

/* =============================================================
   PRIMITIVES — onboarding-specific, in the app's vocabulary
   ============================================================= */

/* Screen scaffold: a flex column that fills the bezel.
   Body scrolls; Dock pins to the bottom. */
const Screen = ({ children, style }) => (
  <div style={{ flex: 1, minHeight: 0, display: "flex", flexDirection: "column", ...style }}>{children}</div>
);
const Body = ({ children, style }) => {
  const web = usePlatform() === "web";
  return <div className="scroll" style={{ flex: web ? "0 1 auto" : 1, minHeight: 0, padding: "6px 24px 8px", ...style }}>{children}</div>;
};
/* On web the dock is not pinned to the bottom — it flows right under the
   content (left-aligned, auto-width button). On mobile it stays pinned. */
const Dock = ({ children, style }) => {
  const web = usePlatform() === "web";
  return <div className={web ? "web-dock" : undefined} style={{ flex: "none", padding: web ? "20px 24px 28px" : "12px 24px 26px", display: web ? "flex" : "grid", flexDirection: web ? "column" : undefined, alignItems: web ? "flex-start" : undefined, justifyContent: web ? "flex-start" : undefined, gap: web ? 6 : 10, background: web ? "none" : "linear-gradient(to top, var(--bg) 72%, transparent)", ...style }}>{children}</div>;
};
const H = ({ children, size = 32 }) => (
  <div style={{ fontFamily: "var(--f-display)", fontSize: size, fontWeight: 700, letterSpacing: "-0.03em", lineHeight: 1.04, marginTop: 14 }}>{children}</div>
);
/* Line break that collapses to a space on web (wider canvas → one line). */
const Brk = () => (usePlatform() === "web" ? " " : <br />);
const Sub = ({ children, style }) => {
  const web = usePlatform() === "web";
  return (
  <div style={{ fontFamily: "var(--f-display)", fontSize: 14.5, color: "var(--ink-2)", lineHeight: 1.5, marginTop: 12, maxWidth: web ? "none" : "33ch", ...style }}>{children}</div>
  );
};
const Accent = ({ children }) => <span style={{ color: "var(--accent)" }}>{children}</span>;

/* Text field — square, hairline, label above */
const Field = ({ label, value, onChange, placeholder, prefix, autoFocus, inputMode, maxLength, onEnter }) => (
  <label style={{ display: "block" }}>
    {label && <div style={{ marginBottom: 7 }}><Eyebrow>{label}</Eyebrow></div>}
    <div style={{ display: "flex", alignItems: "center", background: "var(--bg-card)", border: "1px solid var(--rule-2)", borderRadius: 12 }}>
      {prefix && <span style={{ paddingLeft: 14, fontFamily: "var(--f-mono)", fontSize: 16, color: "var(--ink-3)" }}>{prefix}</span>}
      <input value={value} onChange={(e) => onChange && onChange(e.target.value)} placeholder={placeholder}
        autoFocus={autoFocus} inputMode={inputMode} maxLength={maxLength}
        onKeyDown={(e) => { if (e.key === "Enter" && onEnter) onEnter(); }}
        style={{ flex: 1, width: "100%", padding: "14px", background: "transparent", border: "none", outline: "none",
          borderRadius: 0, fontFamily: "var(--f-display)", fontSize: 16, color: "var(--ink)" }} />
    </div>
  </label>
);

/* Native-feeling select (square, hairline) */
const Select = ({ label, value, onChange, options, placeholder }) => (
  <label style={{ display: "block" }}>
    {label && <div style={{ marginBottom: 7 }}><Eyebrow>{label}</Eyebrow></div>}
    <div style={{ position: "relative", display: "flex", alignItems: "center", background: "var(--bg-card)", border: "1px solid var(--rule-2)", borderRadius: 12 }}>
      <select value={value || ""} onChange={(e) => onChange(e.target.value)} style={{
        appearance: "none", WebkitAppearance: "none", flex: 1, width: "100%", padding: "14px 40px 14px 14px",
        background: "transparent", border: "none", outline: "none", borderRadius: 0,
        fontFamily: "var(--f-display)", fontSize: 16, color: value ? "var(--ink)" : "var(--ink-3)", cursor: "pointer" }}>
        {placeholder && <option value="" disabled>{placeholder}</option>}
        {options.map((o) => <option key={o} value={o}>{o}</option>)}
      </select>
      <span style={{ position: "absolute", right: 13, pointerEvents: "none", color: "var(--ink-2)", display: "flex" }}>
        <Icon name="down" size={16} color="var(--ink-2)" />
      </span>
    </div>
  </label>
);

/* Selectable choice row — accent border on select */
const Choice = ({ on, onClick, icon, title, sub, multi, light }) => (
  <button className="press" onClick={onClick} style={{
    width: "100%", textAlign: "left", cursor: "pointer", position: "relative", padding: "15px 15px", borderRadius: 12,
    background: on ? "var(--bg-card)" : "var(--bg-2)", border: `1px solid ${on ? "var(--accent)" : "var(--rule)"}`,
    display: "flex", alignItems: "center", gap: 13, color: "var(--ink)" }}>
    {icon && (
      <span style={{ width: 30, flex: "none", display: "flex", alignItems: "center", justifyContent: "center", color: on ? "var(--accent)" : "var(--ink-2)" }}>
        <Icon name={icon} size={24} color={on ? "var(--accent)" : "var(--ink-2)"} />
      </span>
    )}
    <span style={{ flex: 1, minWidth: 0 }}>
      <span style={{ display: "block", fontFamily: "var(--f-display)", fontSize: 14.5, fontWeight: light ? 500 : 600, letterSpacing: "-0.01em", lineHeight: 1.35 }}>{title}</span>
      {sub && <span style={{ display: "block", fontFamily: "var(--f-display)", fontSize: 12.5, color: "var(--ink-2)", marginTop: 3, lineHeight: 1.4 }}>{sub}</span>}
    </span>
    <span style={{ width: 20, height: 20, flex: "none", border: `1.5px solid ${on ? "var(--accent)" : "var(--rule-2)"}`, borderRadius: multi ? 0 : 999,
      background: on ? "var(--accent)" : "transparent", display: "flex", alignItems: "center", justifyContent: "center" }}>
      {on && <Icon name="check" size={13} stroke={2.4} color="var(--accent-ink)" />}
    </span>
  </button>
);

/* Quiet disclosure line */
const Disclosure = ({ children, icon = "shield" }) => (
  <div style={{ display: "flex", gap: 9, alignItems: "flex-start", marginTop: 16 }}>
    <span style={{ color: "var(--ink-3)", marginTop: 1, flex: "none" }}><Icon name={icon} size={14} color="var(--ink-3)" /></span>
    <span style={{ fontFamily: "var(--f-display)", fontSize: 12, color: "var(--ink-3)", lineHeight: 1.5 }}>{children}</span>
  </div>
);

const SkipLink = ({ children, onClick }) => (
  <button className="press" onClick={onClick} style={{ display: "block", margin: "0 auto", background: "none", border: "none", padding: 10,
    fontFamily: "var(--f-display)", fontSize: 12, fontWeight: 600, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--ink-3)", cursor: "pointer" }}>{children}</button>
);

/* =============================================================
   01 · WELCOME — a whisper about what we could be
   ============================================================= */
const ProviderBtn = ({ provider, onClick }) => {
  const palette = useTheme();
  const dark = palette === "graphite";
  const apple = provider === "apple";
  // Both providers share one pill style: dark on dark UI, white on light UI.
  const btnBg = dark ? "#131314" : "#ffffff";
  const btnFg = dark ? "#e3e3e3" : "#1f1f1f";
  const btnBorder = dark ? "1px solid #5f6368" : "1px solid var(--rule-2)";
  const appleFg = btnFg;
  const glyph = apple ? (
    <svg width="17" height="20" viewBox="0 0 17 20" style={{ display: "block" }}>
      <path fill={appleFg} d="M13.9 10.5c0-1.9 1.5-2.8 1.6-2.9-.9-1.3-2.2-1.4-2.7-1.5-1.2-.1-2.2.7-2.8.7-.6 0-1.5-.7-2.4-.7-1.2 0-2.4.7-3 1.8-1.3 2.2-.3 5.5 1 7.3.6.9 1.3 1.9 2.3 1.8.9 0 1.3-.6 2.4-.6 1.1 0 1.4.6 2.4.6 1 0 1.6-.9 2.2-1.8.7-1 1-2 1-2-.1 0-1.9-.8-1.9-2.9zM12.1 4.6c.5-.6.8-1.5.7-2.4-.7 0-1.6.5-2.1 1.1-.5.5-.9 1.4-.8 2.3.8.1 1.6-.4 2.2-1z" />
    </svg>
  ) : (
    <svg width="18" height="18" viewBox="0 0 18 18" style={{ display: "block" }}>
      <path fill="#4285F4" d="M17.6 9.2c0-.6-.1-1.2-.2-1.8H9v3.4h4.8a4.1 4.1 0 0 1-1.8 2.7v2.2h2.9c1.7-1.6 2.7-3.9 2.7-6.5z" />
      <path fill="#34A853" d="M9 18c2.4 0 4.5-.8 6-2.2l-2.9-2.2c-.8.5-1.8.9-3.1.9-2.4 0-4.4-1.6-5.1-3.8H.9v2.3A9 9 0 0 0 9 18z" />
      <path fill="#FBBC05" d="M3.9 10.7a5.4 5.4 0 0 1 0-3.4V5H.9a9 9 0 0 0 0 8l3-2.3z" />
      <path fill="#EA4335" d="M9 3.6c1.3 0 2.5.5 3.4 1.3l2.6-2.6A9 9 0 0 0 .9 5l3 2.3C4.6 5.2 6.6 3.6 9 3.6z" />
    </svg>
  );
  return (
    <button className="press" onClick={onClick} style={{
      width: "100%", padding: "15px 18px", borderRadius: 12, cursor: "pointer",
      background: btnBg, color: btnFg, border: btnBorder,
      display: "flex", alignItems: "center", justifyContent: "center", gap: 11,
      fontFamily: "var(--f-display)", fontSize: 15, fontWeight: 600, letterSpacing: "-0.01em" }}>
      {glyph}
      {apple ? "Sign in with Apple" : "Sign in with Google"}
    </button>
  );
};

const Welcome = ({ onNext }) => {
  const [drawerOpen, setDrawerOpen] = useState(false);
  const [drawerIn, setDrawerIn] = useState(false);

  const platform = usePlatform();
  const web = platform === "web";
  const openDrawer = () => {
    if (web) { setDrawerOpen(true); setDrawerIn(true); return; }
    setDrawerOpen(true);
    requestAnimationFrame(() => requestAnimationFrame(() => setDrawerIn(true)));
  };
  const closeDrawer = () => {
    setDrawerIn(false);
    setTimeout(() => setDrawerOpen(false), 300);
  };

  return (
    <Screen style={{ position: "relative", overflow: "hidden" }}>
      <div style={{ flex: 1, minHeight: 0, display: "flex", flexDirection: "column", justifyContent: "center", padding: "0 30px", ...(web ? { alignItems: "center", textAlign: "center" } : {}) }}>
        <div className="yo-enter"><Logo size={52} color="var(--ink)" /></div>
        <div className="yo-enter" data-delay="2" style={{ marginTop: 30 }}>
          <div style={{ fontFamily: "var(--f-display)", fontSize: 38, fontWeight: 700, letterSpacing: "-0.038em", lineHeight: 1.02 }}>
            Your money deserves more. <br /><Accent>Welcome to Yoshi.</Accent>
          </div>
        </div>
        <Seam style={{ width: 96, maxWidth: "100%", opacity: 1, margin: web ? "26px auto 0" : "26px 0 0" }} />
        <div className="yo-enter" data-delay="3" style={{ marginTop: 20, fontFamily: "var(--f-display)", fontSize: 14.5, color: "var(--ink-2)", lineHeight: 1.55, maxWidth: web ? "46ch" : "30ch" }}>
          The only financial account you need, unified behind an agent that's always on your side.
        </div>
        <div className="yo-enter" data-delay="3" style={{ marginTop: 28 }}>
          {web ? (
            <div style={{ maxWidth: 320, margin: "0 auto", display: "grid", gap: 10 }}>
              <ProviderBtn provider="google" onClick={onNext} />
              <ProviderBtn provider="apple" onClick={onNext} />
            </div>
          ) : (
            <Btn onClick={openDrawer} style={{ minWidth: 168 }}>Sign in</Btn>
          )}
        </div>
      </div>

      {web && (
        <div className="yo-enter" data-delay="3" style={{ flex: "none", padding: "10px 30px 26px", fontFamily: "var(--f-display)", fontSize: 11, color: "var(--ink-3)", lineHeight: 1.55, letterSpacing: "0.01em", whiteSpace: "nowrap", textAlign: "center" }}>
          By signing in, you acknowledge that you have read, understood, and agree to Yoshi's{" "}
          <a href="#" onClick={(e) => e.preventDefault()} style={{ color: "var(--ink-2)", textDecoration: "underline", textUnderlineOffset: 2 }}>Terms</a>{" "}and{" "}
          <a href="#" onClick={(e) => e.preventDefault()} style={{ color: "var(--ink-2)", textDecoration: "underline", textUnderlineOffset: 2 }}>Privacy Policy</a>.
        </div>
      )}

      {!web && drawerOpen && (
        <>
          {/* scrim */}
          <div onClick={closeDrawer} style={{
            position: "absolute", inset: 0, zIndex: 50,
            background: "rgba(0,0,0,0.48)",
            opacity: drawerIn ? 1 : 0,
            transition: "opacity 260ms ease"
          }} />
          {/* sheet */}
          <div style={{
            position: "absolute", bottom: 0, left: 0, right: 0, zIndex: 51,
            background: "var(--bg-card)", borderTop: "1px solid var(--rule)",
            borderRadius: "20px 20px 0 0",
            padding: "14px 24px 36px",
            transform: drawerIn ? "translateY(0)" : "translateY(100%)",
            transition: "transform 320ms cubic-bezier(0.16,1,0.30,1)"
          }}>
            <div style={{ width: 36, height: 4, borderRadius: 999, background: "var(--rule-2)", margin: "0 auto 22px" }} />
            <div style={{ display: "grid", gap: 10 }}>
              <ProviderBtn provider="google" onClick={onNext} />
              <ProviderBtn provider="apple" onClick={onNext} />
            </div>
            <div style={{ fontFamily: "var(--f-display)", fontSize: 11, color: "var(--ink-3)", textAlign: "center", lineHeight: 1.55, marginTop: 16, letterSpacing: "0.01em", textWrap: "pretty" }}>
              By signing in, you acknowledge that you have read, understood, and agree to Yoshi's{" "}
              <a href="#" onClick={(e) => e.preventDefault()} style={{ color: "var(--ink-2)", textDecoration: "underline", textUnderlineOffset: 2 }}>Terms</a>{" "}and{" "}
              <a href="#" onClick={(e) => e.preventDefault()} style={{ color: "var(--ink-2)", textDecoration: "underline", textUnderlineOffset: 2 }}>Privacy Policy</a>.
            </div>
          </div>
        </>
      )}
    </Screen>
  );
};

/* =============================================================
   02 · VERIFY-INTRO — "Confirm it's you", teeing up Plaid IDV
   ============================================================= */
const IDV_STEPS = [
  { icon: "phone",  t: "Your number",   s: "A code confirms your phone." },
  { icon: "doc",    t: "Your details",  s: "Confirm what's on file." },
  { icon: "profile", t: "ID & selfie",  s: "A quick liveness check." },
];

const VerifyIntro = ({ onNext }) => (
  <Screen>
    <Body>
      <H>Let's confirm<Brk />your <Accent>details.</Accent></H>
      <Sub>Yoshi opens an insured financial account, so we verify your identity with Plaid.<br />It takes about one minute and is secured end-to-end.</Sub>

      <div style={{ marginTop: 26 }}>
        {IDV_STEPS.map((st, i) => {
          const last = i === IDV_STEPS.length - 1;
          return (
            <div key={st.t} style={{ display: "flex", gap: 15 }}>
              {/* timeline rail: node + connector */}
              <div style={{ display: "flex", flexDirection: "column", alignItems: "center", flex: "none", width: 34 }}>
                <span style={{ width: 34, height: 34, flex: "none", borderRadius: 999, border: "1.5px solid var(--accent)", background: "var(--bg)", display: "grid", placeItems: "center", color: "var(--accent)" }}>
                  <Icon name={st.icon} size={18} color="var(--accent)" />
                </span>
                {!last && <span style={{ flex: 1, width: 1.5, background: "repeating-linear-gradient(var(--accent) 0 4px, transparent 4px 8px)", margin: "4px 0" }} />}
              </div>
              {/* content (plain text, no card) */}
              <div style={{ paddingBottom: last ? 0 : 22, paddingTop: 5 }}>
                <div style={{ fontFamily: "var(--f-display)", fontSize: 18, fontWeight: 700, letterSpacing: "-0.02em", color: "var(--ink)" }}>{st.t}</div>
                <div style={{ fontFamily: "var(--f-display)", fontSize: 12.5, color: "var(--ink-3)", marginTop: 3, lineHeight: 1.45 }}>{st.s}</div>
              </div>
            </div>
          );
        })}
      </div>

      <div style={{ display: "flex", gap: 10, alignItems: "flex-start", padding: "13px 14px", background: "var(--bg-2)", border: "1px solid var(--rule)", borderRadius: 12, marginTop: 14 }}>
        <span style={{ color: "var(--accent)", marginTop: 1, flex: "none" }}><Icon name="shield" size={17} color="var(--accent)" /></span>
        <span style={{ fontFamily: "var(--f-display)", fontSize: 12, color: "var(--ink-2)", lineHeight: 1.5 }}>
          Plaid handles the next few steps. Yoshi keeps a verification token, never your photos or ID.
        </span>
      </div>
    </Body>
    <Dock>
      <Btn full onClick={onNext}>Continue with Plaid <Icon name="arrow" size={15} color="var(--accent-ink)" /></Btn>
    </Dock>
  </Screen>
);

/* =============================================================
   03 · PLAID IDV — a takeover. Plaid's own product surface:
   phone → DOB → SMS code → review on-file details → liveness.
   Styled distinctly from Yoshi so the handoff reads clearly.
   ============================================================= */
const PlaidMark = ({ light }) => (
  <span style={{ display: "inline-flex", alignItems: "center", gap: 7 }}>
    <span style={{ display: "inline-flex", gap: 2.5, alignItems: "flex-end", height: 16 }}>
      <span style={{ width: 3.5, height: 16, background: light ? "#fff" : "#111", borderRadius: 1 }} />
      <span style={{ width: 3.5, height: 11, background: light ? "#fff" : "#111", borderRadius: 1 }} />
      <span style={{ width: 3.5, height: 16, background: light ? "#fff" : "#111", borderRadius: 1 }} />
    </span>
    <span style={{ fontFamily: "var(--f-display)", fontSize: 16, fontWeight: 700, letterSpacing: "-0.01em", color: light ? "#fff" : "#111" }}>Plaid</span>
  </span>
);

/* Plaid-styled controls (neutral near-black, white surface, 12px radius) */
const PField = ({ value, onChange, placeholder, prefix, autoFocus, inputMode, maxLength, big, onEnter }) => (
  <div style={{ display: "flex", alignItems: "center", background: "#fff", border: "1.5px solid #d8dadf", borderRadius: 12 }}>
    {prefix && <span style={{ paddingLeft: 16, fontSize: big ? 22 : 17, fontWeight: 600, color: "#6b6f76" }}>{prefix}</span>}
    <input value={value} onChange={(e) => onChange(e.target.value)} placeholder={placeholder} autoFocus={autoFocus}
      inputMode={inputMode} maxLength={maxLength} onKeyDown={(e) => { if (e.key === "Enter" && onEnter) onEnter(); }}
      style={{ flex: 1, width: "100%", padding: big ? "16px" : "15px 16px", background: "transparent", border: "none", outline: "none",
        borderRadius: 12, fontSize: big ? 22 : 17, fontWeight: 500, color: "#1a1c1f" }} />
  </div>
);
const PBtn = ({ children, onClick, disabled, kind }) => (
  <button className="press" onClick={onClick} disabled={disabled} style={{
    width: "100%", padding: "16px", borderRadius: 12, border: "none", cursor: disabled ? "default" : "pointer",
    background: kind === "ghost" ? "transparent" : (disabled ? "#c9ccd1" : "#111"),
    color: kind === "ghost" ? "#5a5e66" : "#fff", border: kind === "ghost" ? "1.5px solid #d8dadf" : "none",
    fontFamily: "var(--f-display)", fontSize: 16, fontWeight: 600, letterSpacing: "-0.01em",
    display: "flex", alignItems: "center", justifyContent: "center", gap: 8 }}>{children}</button>
);
const PLabel = ({ children }) => (
  <div style={{ fontFamily: "var(--f-display)", fontSize: 12, fontWeight: 700, letterSpacing: "0.06em", textTransform: "uppercase", color: "#8a8e96", marginBottom: 9 }}>{children}</div>
);
const PHead = ({ children }) => (
  <div style={{ fontFamily: "var(--f-display)", fontSize: 26, fontWeight: 700, letterSpacing: "-0.025em", lineHeight: 1.12, color: "#15171a" }}>{children}</div>
);
const PNote = ({ children }) => (
  <div style={{ fontFamily: "var(--f-display)", fontSize: 14, color: "#5a5e66", lineHeight: 1.5, marginTop: 10 }}>{children}</div>
);

const fmtPhone = (v) => {
  const d = v.replace(/\D/g, "").slice(0, 10);
  if (d.length <= 3) return d;
  if (d.length <= 6) return `(${d.slice(0, 3)}) ${d.slice(3)}`;
  return `(${d.slice(0, 3)}) ${d.slice(3, 6)}-${d.slice(6)}`;
};

const PlaidIDV = ({ data, setData, onDone, onCancel, fail, onFail }) => {
  const [step, setStep] = useState(0); // 0 phone 1 dob 2 code 3 review 4 liveness
  const set = (k) => (v) => setData((d) => ({ ...d, [k]: v }));
  const phoneOk = (data.phone || "").replace(/\D/g, "").length === 10;
  const dobOk = !!data.dob;
  const next = () => setStep((s) => s + 1);
  const back = () => { if (step === 0) onCancel(); else setStep((s) => s - 1); };
  const STAGES = 5;

  return (
    <div className="yo-enter" style={{ position: "absolute", inset: 0, zIndex: 400, background: "#f6f7f9", display: "flex", flexDirection: "column" }}>
      {/* Plaid header */}
      <div style={{ flex: "none", paddingTop: 46 }}>
        <div style={{ display: "flex", alignItems: "center", padding: "8px 18px 12px", gap: 10 }}>
          <button className="press" onClick={back} aria-label="Back" style={{ background: "none", border: "none", padding: 4, margin: "0 -4px", color: "#15171a", display: "flex" }}>
            <Icon name={step === 0 ? "close" : "back"} size={22} color="#15171a" />
          </button>
          <div style={{ flex: 1, display: "flex", justifyContent: "center" }}><PlaidMark /></div>
          <span style={{ width: 22 }} />
        </div>
        {/* Plaid progress dots */}
        <div style={{ display: "flex", gap: 5, padding: "0 18px 4px" }}>
          {Array.from({ length: STAGES }).map((_, i) => (
            <div key={i} style={{ flex: 1, height: 3, borderRadius: 999, background: i <= step ? "#111" : "#e1e3e8", transition: "background 300ms ease" }} />
          ))}
        </div>
      </div>

      <div className="scroll" style={{ flex: 1, minHeight: 0, padding: "20px 22px 8px" }} key={step}>
        <div className="yo-enter">
          {step === 0 && (
            <>
              <PHead>What's your mobile number?</PHead>
              <PNote>Yoshi partners with Plaid to verify it's you. We'll text a one-time code.</PNote>
              <div style={{ marginTop: 26 }}>
                <PLabel>Mobile number</PLabel>
                <PField prefix="+1" value={data.phone || ""} onChange={(v) => set("phone")(fmtPhone(v))} placeholder="(415) 555-0142" inputMode="tel" autoFocus big onEnter={() => phoneOk && next()} />
              </div>
            </>
          )}

          {step === 1 && (
            <>
              <PHead>Your date of birth</PHead>
              <PNote>This must match your government ID. We use it only to verify your identity.</PNote>
              <div style={{ marginTop: 26 }}>
                <PLabel>Date of birth</PLabel>
                <PField value={data.dob || ""} onChange={set("dob")} placeholder="MM / DD / YYYY" inputMode="numeric" autoFocus big onEnter={() => dobOk && next()} />
              </div>
            </>
          )}

          {step === 2 && <PlaidCode phone={data.phone} onDone={next} />}

          {step === 3 && (
            <>
              <PHead>Confirm your details</PHead>
              <PNote>Plaid already has these on file from your other linked accounts. Check they're current.</PNote>
              <div style={{ marginTop: 22, background: "#fff", border: "1.5px solid #e1e3e8", borderRadius: 14, overflow: "hidden" }}>
                {[
                  ["Legal name", `${data.first} ${data.last}`],
                  ["Date of birth", data.dobPretty || "Jul 4, 1989"],
                  ["Home address", `${data.street}, ${data.city} ${data.state} ${data.zip}`],
                  ["SSN", "•••–••–4471"],
                ].map(([k, v], i, arr) => (
                  <div key={k} style={{ padding: "14px 16px", borderBottom: i < arr.length - 1 ? "1px solid #eceef1" : "none" }}>
                    <div style={{ fontFamily: "var(--f-display)", fontSize: 11, fontWeight: 700, letterSpacing: "0.05em", textTransform: "uppercase", color: "#9a9ea6" }}>{k}</div>
                    <div style={{ fontFamily: "var(--f-display)", fontSize: 15.5, fontWeight: 500, color: "#1a1c1f", marginTop: 4 }}>{v}</div>
                  </div>
                ))}
              </div>
              <div style={{ display: "flex", gap: 9, alignItems: "flex-start", marginTop: 14 }}>
                <span style={{ color: "#6b6f76", marginTop: 1, flex: "none" }}><Icon name="shield" size={14} color="#6b6f76" /></span>
                <span style={{ fontFamily: "var(--f-display)", fontSize: 12, color: "#6b6f76", lineHeight: 1.5 }}>Encrypted by Plaid. Shared with Yoshi only after you confirm.</span>
              </div>
            </>
          )}

          {step === 4 && <PlaidLiveness onDone={onDone} fail={fail} onFail={onFail} />}
        </div>
      </div>

      {(step === 0 || step === 1 || step === 3) && (
        <div style={{ flex: "none", padding: "12px 22px 26px" }}>
          {step === 0 && <PBtn disabled={!phoneOk} onClick={next}>Send code</PBtn>}
          {step === 1 && <PBtn disabled={!dobOk} onClick={() => { set("dobPretty")("Jul 4, 1989"); next(); }}>Continue</PBtn>}
          {step === 3 && <PBtn onClick={next}>Confirm &amp; continue</PBtn>}
          <div style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: 6, marginTop: 14, color: "#9a9ea6" }}>
            <Icon name="shield" size={13} color="#9a9ea6" />
            <span style={{ fontFamily: "var(--f-display)", fontSize: 11, fontWeight: 600, letterSpacing: "0.02em" }}>Secured by Plaid</span>
          </div>
        </div>
      )}
    </div>
  );
};

/* SMS code — 6 boxes, auto-fills then resolves */
const PlaidCode = ({ phone, onDone }) => {
  const [code, setCode] = useState("");
  const tail = (phone || "").replace(/\D/g, "").slice(-4) || "0142";
  useEffect(() => {
    // auto-arrive the texted code, char by char, then continue
    let i = 0; const seq = "418205";
    const iv = setInterval(() => { i++; setCode(seq.slice(0, i)); if (i >= 6) { clearInterval(iv); setTimeout(onDone, 650); } }, 320);
    return () => clearInterval(iv);
  }, []);
  return (
    <>
      <PHead>Enter the code</PHead>
      <PNote>We texted a 6-digit code to <span style={{ color: "#1a1c1f", fontWeight: 600 }}>+1 ••• ••• {tail}</span>.</PNote>
      <div style={{ display: "flex", gap: 9, marginTop: 28 }}>
        {Array.from({ length: 6 }).map((_, i) => {
          const ch = code[i];
          const active = i === code.length;
          return (
            <div key={i} style={{ flex: 1, aspectRatio: "1 / 1.25", display: "grid", placeItems: "center",
              background: "#fff", border: `1.5px solid ${ch ? "#111" : active ? "#9a9ea6" : "#d8dadf"}`, borderRadius: 12,
              fontFamily: "var(--f-display)", fontSize: 26, fontWeight: 700, color: "#15171a" }}>{ch || ""}</div>
          );
        })}
      </div>
      <div style={{ display: "flex", alignItems: "center", gap: 7, marginTop: 22, color: "#6b6f76" }}>
        <LiveDot size={5} color="#111" />
        <span style={{ fontFamily: "var(--f-display)", fontSize: 13 }}>Waiting for your text…</span>
      </div>
    </>
  );
};

/* Liveness — scan ID, then selfie. A schematic camera sequence that resolves.
   With `fail`, the first match comes back inconclusive; the user gets one
   retry, and if it's still no match we hand off to manual review (`onFail`). */
const PlaidLiveness = ({ onDone, fail, onFail }) => {
  const [phase, setPhase] = useState(0); // 0 id, 1 selfie, 2 done, 3 no-match
  const [attempt, setAttempt] = useState(0);
  const timers = useRef([]);
  const clearTimers = () => { timers.current.forEach(clearTimeout); timers.current = []; };
  const run = () => {
    clearTimers();
    setPhase(0);
    timers.current.push(setTimeout(() => setPhase(1), 2100));
    timers.current.push(setTimeout(() => setPhase(fail ? 3 : 2), 4200));
    if (!fail) timers.current.push(setTimeout(() => onDone && onDone(), 5300));
  };
  useEffect(() => { run(); return clearTimers; }, []);
  // a second inconclusive attempt routes to a human
  useEffect(() => {
    if (phase === 3 && attempt >= 1) {
      const t = setTimeout(() => onFail && onFail(), 2500);
      return () => clearTimeout(t);
    }
  }, [phase, attempt]);
  const retry = () => { setAttempt((a) => a + 1); run(); };

  const isSelfie = phase === 1;
  const done = phase === 2;
  const noMatch = phase === 3;
  if (noMatch) {
    const firstTry = attempt === 0;
    return (
      <>
        <PHead>{firstTry ? "That didn't scan cleanly" : "We still couldn't confirm a match"}</PHead>
        <PNote>{firstTry
          ? "Your selfie didn't clearly match your ID. Let's try once more. Good lighting and holding steady help."
          : "We tried twice and still couldn't get a clear match. We'll hand this to a specialist to check by hand and return you to Yoshi."}</PNote>
        <div style={{ marginTop: 30, display: "flex", justifyContent: "center" }}>
          <div className="yo-enter" style={{ width: 96, height: 96, borderRadius: 999, background: "#fff", border: "2px solid #d2865f", display: "grid", placeItems: "center" }}>
            <Icon name={firstTry ? "profile" : "close"} size={firstTry ? 44 : 46} stroke={2} color="#c0443a" />
          </div>
        </div>
        <div style={{ display: "flex", justifyContent: "center", gap: 8, marginTop: 30 }}>
          {["Reading ID", "Selfie", "No match"].map((l, i) => {
            const bad = i === 2;
            return (
              <div key={l} style={{ display: "flex", alignItems: "center", gap: 6, fontFamily: "var(--f-display)", fontSize: 11.5, fontWeight: 600, color: bad ? "#c0443a" : "#15171a" }}>
                <span style={{ width: 16, height: 16, borderRadius: 999, border: `1.5px solid ${bad ? "#c0443a" : "#1e8e5a"}`, background: bad ? "#c0443a" : "#1e8e5a", display: "grid", placeItems: "center" }}>
                  <Icon name={bad ? "close" : "check"} size={9} stroke={2.6} color="#fff" />
                </span>{l}
              </div>
            );
          })}
        </div>
        {firstTry ? (
          <div style={{ marginTop: 30 }}>
            <PBtn onClick={retry}>Try again</PBtn>
            <div style={{ textAlign: "center", marginTop: 12, fontFamily: "var(--f-display)", fontSize: 12, color: "#8a8e96" }}>Attempt 1 of 2</div>
          </div>
        ) : (
          <div style={{ marginTop: 30, display: "flex", alignItems: "center", justifyContent: "center", gap: 8 }}>
            <LiveDot size={6} />
            <span style={{ fontFamily: "var(--f-display)", fontSize: 13, fontWeight: 600, color: "#8a6d3b" }}>Sending to a specialist…</span>
          </div>
        )}
      </>
    );
  }
  return (
    <>
      <PHead>{done ? "Identity confirmed" : isSelfie ? "Now a quick selfie" : "Scan your ID"}</PHead>
      <PNote>{done ? "You're verified. Returning you to Yoshi." : isSelfie ? "Look at the camera and hold still." : "Position the front of your ID inside the frame."}</PNote>

      <div style={{ marginTop: 30, display: "flex", justifyContent: "center" }}>
        {!done ? (
          <div style={{ position: "relative", width: isSelfie ? 168 : 248, height: isSelfie ? 168 : 156, transition: "all 300ms ease" }}>
            {/* camera viewport */}
            <div style={{ position: "absolute", inset: 0, background: "#0e0f11", borderRadius: isSelfie ? "999px" : 16, overflow: "hidden",
              border: "2px solid #2a2c30" }}>
              {/* subject */}
              <div style={{ position: "absolute", inset: 0, display: "grid", placeItems: "center" }}>
                <Icon name={isSelfie ? "profile" : "card"} size={isSelfie ? 84 : 96} stroke={1.2} color="rgba(255,255,255,0.45)" />
              </div>
              {/* scan line */}
              <div className="plaid-scan" style={{ position: "absolute", left: 0, right: 0, height: 2, background: "#5ad1a0", boxShadow: "0 0 12px 2px rgba(90,209,160,0.7)" }} />
            </div>
            {/* corner brackets */}
            {[[0, 0], [1, 0], [0, 1], [1, 1]].map(([rx, ry], i) => (
              <span key={i} style={{ position: "absolute", width: 22, height: 22, [rx ? "right" : "left"]: -2, [ry ? "bottom" : "top"]: -2,
                borderTop: ry ? "none" : "3px solid #5ad1a0", borderBottom: ry ? "3px solid #5ad1a0" : "none",
                borderLeft: rx ? "none" : "3px solid #5ad1a0", borderRight: rx ? "3px solid #5ad1a0" : "none",
                borderTopLeftRadius: !rx && !ry ? 8 : 0, borderTopRightRadius: rx && !ry ? 8 : 0,
                borderBottomLeftRadius: !rx && ry ? 8 : 0, borderBottomRightRadius: rx && ry ? 8 : 0 }} />
            ))}
          </div>
        ) : (
          <div className="yo-enter" style={{ width: 96, height: 96, borderRadius: 999, background: "#1e8e5a", display: "grid", placeItems: "center" }}>
            <Icon name="check" size={52} stroke={2} color="#fff" />
          </div>
        )}
      </div>

      <div style={{ display: "flex", justifyContent: "center", gap: 8, marginTop: 30 }}>
        {["Reading ID", "Selfie", "Matched"].map((l, i) => (
          <div key={l} style={{ display: "flex", alignItems: "center", gap: 6, fontFamily: "var(--f-display)", fontSize: 11.5, fontWeight: 600,
            color: phase >= i ? "#15171a" : "#b6b9bf" }}>
            <span style={{ width: 16, height: 16, borderRadius: 999, border: `1.5px solid ${phase > i ? "#1e8e5a" : phase === i ? "#111" : "#cdd0d5"}`,
              background: phase > i ? "#1e8e5a" : "transparent", display: "grid", placeItems: "center" }}>
              {phase > i && <Icon name="check" size={9} stroke={2.6} color="#fff" />}
            </span>{l}
          </div>
        ))}
      </div>
    </>
  );
};

/* =============================================================
   04 · SUCCESS — back in the Yoshi flow. Approval-style reveal.
   ============================================================= */
const Success = ({ onNext }) => {
  useEffect(() => { const t = setTimeout(onNext, 2400); return () => clearTimeout(t); }, []);
  return (
    <Screen>
      <div style={{ flex: 1, display: "flex", flexDirection: "column", justifyContent: "center", alignItems: "center", textAlign: "center", padding: "0 30px" }}>
        <span className="yo-pop-in" style={{ width: 92, height: 92, marginBottom: 22, background: "var(--accent)", display: "grid", placeItems: "center", borderRadius: 999 }}>
          <Icon name="check" size={50} stroke={2.2} color="var(--accent-ink)" />
        </span>
        <div className="yo-enter" data-delay="1" style={{ fontFamily: "var(--f-display)", fontSize: 32, fontWeight: 700, letterSpacing: "-0.03em", lineHeight: 1.05 }}>
          You're <Accent>verified.</Accent>
        </div>
        <Seam draw style={{ width: 80, margin: "18px 0" }} />
        <div className="yo-enter" data-delay="2" style={{ fontFamily: "var(--f-display)", fontSize: 14.5, color: "var(--ink-2)", lineHeight: 1.5, maxWidth: "26ch" }}>
          Identity confirmed. Your account is opening, FDIC-insured to applicable limits.
        </div>
      </div>
    </Screen>
  );
};

/* =============================================================
   Loader — just the mark, centered, its arms slowly growing in.
   No words. Used for genuine pending moments.
   ============================================================= */
const Loader = () => {
  const mark = `url(${(typeof window !== "undefined" && window.__resources && window.__resources.logoSharp) || "assets/logo-mark-sharp.png"})`;
  return (
  <div style={{ flex: 1, minHeight: 0, display: "flex", alignItems: "center", justifyContent: "center" }}>
    <div className="yo-grow" style={{ width: 92, height: 104, background: "var(--ink)", animationDuration: "4.6s",
      WebkitMaskImage: mark, maskImage: mark,
      WebkitMaskSize: "contain", maskSize: "contain", WebkitMaskRepeat: "no-repeat", maskRepeat: "no-repeat",
      WebkitMaskPosition: "center", maskPosition: "center" }} />
  </div>
  );
};

/* Account-provisioning beat after Plaid — the growing Yoshi mark, with one
   quiet status line ticking through the behind-the-scenes checks. With
   `review` set, the compliance check flags and we route to a human (`onReview`)
   instead of opening the account. */
const PROVISION_STEPS = ["Confirming your identity", "Running compliance checks", "Opening your account"];
const REVIEW_AT = 1; // the compliance-checks step is where automated KYC flags surface
const Provisioning = ({ onDone, review, onReview }) => {
  const provMark = `url(${(typeof window !== "undefined" && window.__resources && window.__resources.logoSharp) || "assets/logo-mark-sharp.png"})`;
  const [n, setN] = useState(0);
  const flagged = !!review;
  useEffect(() => {
    const stop = flagged ? REVIEW_AT : PROVISION_STEPS.length - 1;
    const ts = [];
    for (let i = 0; i <= stop; i++) ts.push(setTimeout(() => setN(i), 700 + i * 1150));
    ts.push(setTimeout(() => { flagged ? (onReview && onReview(review)) : (onDone && onDone()); }, 700 + (stop + 1) * 1150 + 700));
    return () => ts.forEach(clearTimeout);
  }, []);
  const atFlag = flagged && n >= REVIEW_AT;
  const caption = atFlag ? "This one needs a closer look…" : PROVISION_STEPS[Math.min(n, PROVISION_STEPS.length - 1)];
  const dotColor = atFlag ? "#b07d1c" : "var(--accent)";
  return (
    <Screen>
      <div style={{ flex: 1, minHeight: 0, display: "flex", flexDirection: "column", justifyContent: "center", alignItems: "center", textAlign: "center", padding: "0 30px" }}>
        {/* the mark, growing in — the signature pending beat */}
        <div style={{ width: 100, height: 112, display: "grid", placeItems: "center" }}>
          <div className="yo-grow" style={{ width: 88, height: 100, background: "var(--accent)",
            WebkitMaskImage: provMark, maskImage: provMark, WebkitMaskSize: "contain", maskSize: "contain",
            WebkitMaskRepeat: "no-repeat", maskRepeat: "no-repeat", WebkitMaskPosition: "center", maskPosition: "center" }} />
        </div>
        {/* one quiet status line */}
        <div style={{ display: "flex", alignItems: "center", gap: 9, marginTop: 30, minHeight: 20 }}>
          {atFlag
            ? <Icon name="clock" size={15} color={dotColor} />
            : <LiveDot size={6} color={dotColor} />}
          <span style={{ fontFamily: "var(--f-display)", fontSize: 13.5, fontWeight: 600, letterSpacing: "-0.005em", color: atFlag ? dotColor : "var(--ink-2)", transition: "color 240ms ease" }}>{caption}</span>
        </div>
      </div>
    </Screen>
  );
};

Object.assign(window, {
  Screen, Body, Dock, H, Sub, Accent, Field, Select, Choice, Disclosure, SkipLink,
  Welcome, VerifyIntro, PlaidIDV, PlaidLiveness, Success, Loader, Provisioning,
});
