// Opalus — Welcome / sign-in + ECSP onboarding hub (INV-03→11)
const { Button, Input, Card, Badge, ProgressBar, Logo, IconBadge } = window.OpalusDesignSystem_1dce4b;
const OB_ICONS = window.OpalusDesignSystem_1dce4b;

/* ============================================================ WELCOME — sign-in / create-account ============================================================ */
// Login lockout (BRD-1358): 5 failed attempts → 15-minute lockout.
const LOGIN_MAX_ATTEMPTS = 5;
const LOGIN_LOCKOUT_MS = 15 * 60 * 1000;
// Demo correct password for the seed account; "Create account" sets investor.password for new accounts.
const DEMO_PASSWORD = 'password';
// Password policy: ≥12 chars + basic complexity (upper, lower, digit). Returns {ok, rules}.
function checkPassword(pw) {
  const rules = [
    { key: 'len', label: 'At least 12 characters', ok: pw.length >= 12 },
    { key: 'upper', label: 'An uppercase letter', ok: /[A-Z]/.test(pw) },
    { key: 'lower', label: 'A lowercase letter', ok: /[a-z]/.test(pw) },
    { key: 'digit', label: 'A number', ok: /[0-9]/.test(pw) },
  ];
  return { ok: rules.every((r) => r.ok), rules };
}

function Welcome() {
  const { update, go, state } = window.useOpalus();
  const [mode, setMode] = useState('signin');   // signin | create
  const trust = [
    ['Authorised under ECSP', 'Regulation (EU) 2020/1503 · Bank of Lithuania'],
    ['Secure & transparent', 'KYC/AML verified · funds safeguarded in segregated client accounts'],
    ['Invest without borders', 'Curated, institutional-grade European real estate'],
  ];
  return (
    <div className="opx-split">
      <aside className="opx-onb-aside" style={{ background: 'var(--navy-950)', color: '#fff', padding: '56px 56px 48px', display: 'flex', flexDirection: 'column', position: 'relative', overflow: 'hidden' }}>
        <div style={{ position: 'absolute', inset: 0, background: 'radial-gradient(120% 80% at 100% 0%, rgba(167,107,52,0.28), transparent 60%)' }} />
        <div style={{ position: 'relative', zIndex: 1, display: 'flex', flexDirection: 'column', height: '100%' }}>
          {/* alignSelf keeps the img from being flex-stretched to the panel's full width */}
          <Logo variant="white" height={34} base="assets/logo" style={{ alignSelf: 'flex-start', width: 'auto' }} />
          <div style={{ marginTop: 'auto' }}>
            <div style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-micro)', fontWeight: 600, letterSpacing: 'var(--ls-eyebrow)', textTransform: 'uppercase', color: 'var(--bronze-400)' }}>Multi-Jurisdiction Investment Platform</div>
            <h1 style={{ fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 46, lineHeight: 1.08, letterSpacing: 'var(--ls-tight)', margin: '16px 0 14px', color: '#fff' }}>Invest in real estate, without borders.</h1>
            <p style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-body)', lineHeight: 1.6, color: 'rgba(255,255,255,0.72)', maxWidth: 440, margin: 0 }}>A premium, institutional-grade platform offering carefully vetted European property opportunities — open to retail and sophisticated investors alike.</p>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 16, marginTop: 36 }}>
              {trust.map(([t, d]) => (
                <div key={t} style={{ display: 'flex', gap: 13, alignItems: 'flex-start' }}>
                  <span style={{ width: 30, height: 30, borderRadius: 8, background: 'rgba(167,107,52,0.18)', border: '1px solid rgba(228,188,147,0.35)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flex: '0 0 auto' }}>
                    <OB_ICONS.IconlyRegularLightLock style={{ width: 16, height: 16, color: 'var(--bronze-400)' }} />
                  </span>
                  <div>
                    <div style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', fontWeight: 600 }}>{t}</div>
                    <div style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-xs)', color: 'rgba(255,255,255,0.55)' }}>{d}</div>
                  </div>
                </div>
              ))}
            </div>
          </div>
        </div>
      </aside>

      <main style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '48px 28px', background: '#fff' }}>
        <div className="opx-fade" key={mode} style={{ width: '100%', maxWidth: 420 }}>
          <Logo variant="navy" height={34} base="assets/logo" style={{ marginBottom: 28 }} />
          {mode === 'signin'
            ? <SignInForm state={state} update={update} go={go} switchToCreate={() => setMode('create')} />
            : <CreateAccountForm state={state} update={update} go={go} switchToSignIn={() => setMode('signin')} />}
          <div style={{ textAlign: 'center', marginTop: 18, display: 'flex', flexDirection: 'column', gap: 8 }}>
            <span className="opx-link" style={{ fontSize: 13 }} onClick={() => go('calculator')}>Could you bear a loss? Try our free calculator</span>
            {/* Logged-out public browse (BRD §7.5) — all projects visible with marketing-level info */}
            <span className="opx-link" style={{ fontSize: 13 }} onClick={() => go('browse')}>Browse the live projects — no account needed</span>
            {/* Art 8(2) — public register of restricted-person investments */}
            <span className="opx-link" style={{ fontSize: 12, color: 'var(--text-muted)' }} onClick={() => go('register')}>Conflicts-of-interest register (Art 8(2))</span>
          </div>
          <div style={{ marginTop: 18, paddingTop: 18, borderTop: '1px solid var(--border-faint)', display: 'flex', alignItems: 'center', gap: 8, justifyContent: 'center' }}>
            <span style={{ fontFamily: 'var(--font-body)', fontSize: 11, color: 'var(--text-muted)' }}>Protected by 2-factor authentication · EUR · EU/EEA</span>
          </div>
        </div>
      </main>
    </div>
  );
}

/* ---------- Sign in (with 5-attempt / 15-minute lockout) ---------- */
function SignInForm({ state, update, go, switchToCreate }) {
  const lk = state.investor.loginLockout || { fails: 0, until: 0 };
  const [email, setEmail] = useState(state.investor.email);
  const [pw, setPw] = useState('');
  const [error, setError] = useState('');
  const [showPw, setShowPw] = useState(false);
  const [, force] = useState(0);
  const locked = lk.until > Date.now();
  // re-render each second while locked so the countdown advances
  useEffect(() => { if (!locked) return; const id = setInterval(() => force((n) => n + 1), 1000); return () => clearInterval(id); }, [locked]);

  const expected = state.investor.password || DEMO_PASSWORD;
  const signIn = () => {
    if (locked) return;
    if (pw === expected) {
      update((s) => { s.signedIn = true; s.investor.email = email; s.investor.loginLockout = { fails: 0, until: 0 }; });
      go(state.onboarded ? 'dashboard' : 'onboarding');
      return;
    }
    const fails = lk.fails + 1;
    if (fails >= LOGIN_MAX_ATTEMPTS) {
      update((s) => { s.investor.loginLockout = { fails, until: Date.now() + LOGIN_LOCKOUT_MS }; });
      setError('');
    } else {
      update((s) => { s.investor.loginLockout = { fails, until: 0 }; });
      setError(`Incorrect email or password. ${LOGIN_MAX_ATTEMPTS - fails} attempt${LOGIN_MAX_ATTEMPTS - fails === 1 ? '' : 's'} left before your account is temporarily locked.`);
    }
    setPw('');
  };

  if (locked) {
    const mins = Math.floor((lk.until - Date.now()) / 60000);
    const secs = Math.floor(((lk.until - Date.now()) % 60000) / 1000);
    return (
      <>
        <div style={{ width: 52, height: 52, borderRadius: 'var(--radius-md)', background: 'var(--danger-50)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', marginBottom: 16 }}>
          <OB_ICONS.IconlyRegularLightLock style={{ width: 24, height: 24, color: 'var(--danger-600)' }} />
        </div>
        <h2 style={{ fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 26, color: 'var(--ink)', margin: '0 0 6px' }}>Account temporarily locked</h2>
        <p style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', color: 'var(--text-body)', lineHeight: 1.6, margin: '0 0 20px' }}>For your security, sign-in is locked after {LOGIN_MAX_ATTEMPTS} failed attempts. Please try again in 15 minutes, or reset your password.</p>
        <div style={{ background: 'var(--surface-inset)', border: '1px solid var(--border-faint)', borderRadius: 'var(--radius-md)', padding: '16px 18px', marginBottom: 18, textAlign: 'center' }}>
          <div style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-micro)', fontWeight: 600, letterSpacing: 'var(--ls-eyebrow)', textTransform: 'uppercase', color: 'var(--text-muted)' }}>Unlocks in</div>
          <div style={{ fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 32, color: 'var(--ink)', fontVariantNumeric: 'tabular-nums', marginTop: 4 }}>{String(mins).padStart(2, '0')}:{String(secs).padStart(2, '0')}</div>
        </div>
        <Button variant="primary" block onClick={() => go('reset')}>Reset your password</Button>
        <div style={{ textAlign: 'center', marginTop: 14 }}>
          <span className="opx-link" style={{ fontSize: 12.5 }} onClick={() => update((s) => { s.investor.loginLockout = { fails: 0, until: 0 }; })}>Clear lockout (demo)</span>
        </div>
      </>
    );
  }

  return (
    <>
      <h2 style={{ fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 28, color: 'var(--ink)', margin: '0 0 6px' }}>Hi, Welcome Back!</h2>
      <p style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', color: 'var(--text-body)', margin: '0 0 28px' }}>Sign in to access your account and opportunities.</p>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
        <window.Field label="Email Address">
          <Input icon={<OB_ICONS.IconlyRegularLightProfile style={{ width: 20, height: 20, color: 'var(--gray-600)' }} />} placeholder="you@email.com" value={email} onChange={(e) => setEmail(e.target.value)} />
        </window.Field>
        <window.Field label="Password">
          <Input icon={<OB_ICONS.IconlyRegularLightLock style={{ width: 20, height: 20, color: 'var(--gray-600)' }} />} trailing={<span style={{ cursor: 'pointer' }} onClick={() => setShowPw(!showPw)}><OB_ICONS.IconlyRegularLightShow style={{ width: 20, height: 20, color: 'var(--gray-500)' }} /></span>} type={showPw ? 'text' : 'password'} placeholder="••••••••" value={pw} onChange={(e) => setPw(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && signIn()} />
        </window.Field>
        {error && <div style={{ fontFamily: 'var(--font-body)', fontSize: 12.5, color: 'var(--danger-600)', lineHeight: 1.5 }}>{error}</div>}
        <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: -4 }}>
          <span className="opx-link" style={{ fontSize: 13 }} onClick={() => go('reset')}>Forgot password?</span>
        </div>
        <Button variant="primary" block onClick={signIn} trailingIcon={<OB_ICONS.IconlyRegularLightArrowRight style={{ width: 18, height: 18 }} />}>Sign In</Button>
        <div style={{ textAlign: 'center', fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', color: 'var(--text-body)', marginTop: 4 }}>
          New to Opalus? <span className="opx-link" onClick={switchToCreate}>Create an account</span>
        </div>
        <div style={{ fontFamily: 'var(--font-body)', fontSize: 11.5, color: 'var(--text-muted)', textAlign: 'center' }}>Demo password for the seed account: <b>password</b></div>
      </div>
    </>
  );
}

/* ---------- Create account — email + password (≥12 + complexity) + consent capture ---------- */
function CreateAccountForm({ state, update, go, switchToSignIn }) {
  const LEGAL = window.OPALUS_LEGAL_DOCS;
  const [email, setEmail] = useState('');
  const [pw, setPw] = useState('');
  const [confirm, setConfirm] = useState('');
  const [showPw, setShowPw] = useState(false);
  const [acceptTerms, setAcceptTerms] = useState(false);
  const [acceptCookies, setAcceptCookies] = useState(false);
  const policy = checkPassword(pw);
  const match = confirm.length > 0 && pw === confirm;
  const emailOk = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  // Terms+Privacy are bundled into one mandatory acceptance; cookies is a separate consent (GDPR Art 7).
  const canSubmit = emailOk && policy.ok && match && acceptTerms;

  const createAccount = () => {
    if (!canSubmit) return;
    const now = new Date().toISOString();
    update((s) => {
      s.signedIn = true;
      s.investor.email = email;
      s.investor.password = pw;
      s.investor.emailVerified = false;
      s.investor.loginLockout = { fails: 0, until: 0 };
      // Versioned + timestamped consent records (GDPR Art 7). Terms & Privacy accepted together;
      // cookies recorded only if granted.
      s.investor.consents = {
        terms: { version: LEGAL.terms.version, acceptedAt: now },
        privacy: { version: LEGAL.privacy.version, acceptedAt: now },
        ...(acceptCookies ? { cookies: { version: LEGAL.cookies.version, acceptedAt: now } } : {}),
      };
    });
    go('verify');
  };

  return (
    <>
      <h2 style={{ fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 28, color: 'var(--ink)', margin: '0 0 6px' }}>Create your account</h2>
      <p style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', color: 'var(--text-body)', margin: '0 0 24px' }}>Open a free account to start investing. We'll verify your email next.</p>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
        <window.Field label="Email Address">
          <Input icon={<OB_ICONS.IconlyRegularLightProfile style={{ width: 20, height: 20, color: 'var(--gray-600)' }} />} placeholder="you@email.com" value={email} onChange={(e) => setEmail(e.target.value)} />
        </window.Field>
        <window.Field label="Create Password">
          <Input icon={<OB_ICONS.IconlyRegularLightLock style={{ width: 20, height: 20, color: 'var(--gray-600)' }} />} trailing={<span style={{ cursor: 'pointer' }} onClick={() => setShowPw(!showPw)}><OB_ICONS.IconlyRegularLightShow style={{ width: 20, height: 20, color: 'var(--gray-500)' }} /></span>} type={showPw ? 'text' : 'password'} placeholder="At least 12 characters" value={pw} onChange={(e) => setPw(e.target.value)} />
        </window.Field>
        {pw.length > 0 && (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginTop: -6 }}>
            {policy.rules.map((r) => (
              <div key={r.key} style={{ display: 'flex', alignItems: 'center', gap: 8, fontFamily: 'var(--font-body)', fontSize: 12, color: r.ok ? 'var(--success-600)' : 'var(--text-muted)' }}>
                <span style={{ width: 15, height: 15, borderRadius: '50%', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flex: '0 0 auto', background: r.ok ? 'var(--success-50)' : 'var(--surface-inset)', fontSize: 10, fontWeight: 700 }}>{r.ok ? '✓' : ''}</span>
                {r.label}
              </div>
            ))}
          </div>
        )}
        <window.Field label="Confirm Password">
          <Input icon={<OB_ICONS.IconlyRegularLightLock style={{ width: 20, height: 20, color: 'var(--gray-600)' }} />} type={showPw ? 'text' : 'password'} placeholder="Re-enter your password" value={confirm} onChange={(e) => setConfirm(e.target.value)} />
        </window.Field>
        {confirm.length > 0 && !match && <div style={{ fontFamily: 'var(--font-body)', fontSize: 12, color: 'var(--danger-600)', marginTop: -8 }}>Passwords don't match.</div>}

        <div style={{ display: 'flex', flexDirection: 'column', gap: 12, marginTop: 4, paddingTop: 14, borderTop: '1px solid var(--border-faint)' }}>
          <window.AckBox checked={acceptTerms} onChange={setAcceptTerms}>
            I have read and accept the <span className="opx-link">Terms of Use ({LEGAL.terms.version})</span> and the <span className="opx-link">Privacy Policy ({LEGAL.privacy.version})</span>.
          </window.AckBox>
          <window.AckBox checked={acceptCookies} onChange={setAcceptCookies}>
            I accept non-essential cookies per the <span className="opx-link">Cookie Policy ({LEGAL.cookies.version})</span> <span style={{ color: 'var(--text-muted)' }}>(optional)</span>.
          </window.AckBox>
          {/* Art 19(2) scheme disclaimers — required at signup (BRD §8.9) */}
          <div style={{ fontFamily: 'var(--font-body)', fontSize: 11.5, color: 'var(--text-muted)', lineHeight: 1.55 }}>{window.OPALUS_COPY.art19_2}</div>
        </div>

        <Button variant="primary" block disabled={!canSubmit} onClick={createAccount} trailingIcon={<OB_ICONS.IconlyRegularLightArrowRight style={{ width: 18, height: 18 }} />}>Create account</Button>
        <div style={{ textAlign: 'center', fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', color: 'var(--text-body)', marginTop: 4 }}>
          Already have an account? <span className="opx-link" onClick={switchToSignIn}>Sign in</span>
        </div>
      </div>
    </>
  );
}

/* ============================================================ PASSWORD RESET (single-use token, 1h expiry) ============================================================ */
function PasswordResetScreen() {
  const { state, update, go } = window.useOpalus();
  const [phase, setPhase] = useState('email');   // email | sent | set | done
  const [email, setEmail] = useState(state.investor.email);
  const [token] = useState(() => 'RST-' + Math.random().toString(36).slice(2, 8).toUpperCase());
  const [issuedAt] = useState(() => Date.now());
  const [entered, setEntered] = useState('');
  const [pw, setPw] = useState('');
  const [confirm, setConfirm] = useState('');
  const [showPw, setShowPw] = useState(false);
  const [tokenErr, setTokenErr] = useState('');
  const policy = checkPassword(pw);
  const match = confirm.length > 0 && pw === confirm;
  const expiresAt = issuedAt + 60 * 60 * 1000;   // 1-hour expiry

  const verifyToken = () => {
    if (Date.now() > expiresAt) { setTokenErr('This reset link has expired. Links are valid for one hour — request a new one.'); return; }
    if (entered.trim().toUpperCase() !== token) { setTokenErr('That code is not valid. Check the link we emailed you.'); return; }
    setTokenErr(''); setPhase('set');
  };
  const setNewPassword = () => {
    if (!policy.ok || !match) return;
    update((s) => { s.investor.password = pw; s.investor.loginLockout = { fails: 0, until: 0 }; });
    setPhase('done');
  };

  return (
    <AuthShellLite>
      <Card padding="lg" radius="xl" shadow="card" style={{ padding: 40, maxWidth: 460, width: '100%' }}>
        {phase === 'email' && (
          <>
            <div style={{ width: 52, height: 52, borderRadius: 'var(--radius-md)', background: 'var(--cream-100)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', marginBottom: 16 }}><OB_ICONS.IconlyRegularLightLock style={{ width: 24, height: 24, color: 'var(--bronze-600)' }} /></div>
            <h2 style={{ fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 26, color: 'var(--ink)', margin: '0 0 6px' }}>Reset your password</h2>
            <p style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', color: 'var(--text-body)', lineHeight: 1.6, margin: '0 0 22px' }}>Enter your account email. We'll send a single-use link, valid for one hour, to set a new password.</p>
            <window.Field label="Email Address">
              <Input icon={<OB_ICONS.IconlyRegularLightProfile style={{ width: 20, height: 20, color: 'var(--gray-600)' }} />} placeholder="you@email.com" value={email} onChange={(e) => setEmail(e.target.value)} />
            </window.Field>
            <Button variant="primary" block style={{ marginTop: 18 }} disabled={!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)} onClick={() => setPhase('sent')}>Send reset link</Button>
            <div style={{ textAlign: 'center', marginTop: 16 }}><span className="opx-link" style={{ fontSize: 13 }} onClick={() => go('welcome')}>Back to sign in</span></div>
          </>
        )}
        {phase === 'sent' && (
          <>
            <div style={{ width: 52, height: 52, borderRadius: 'var(--radius-md)', background: 'var(--cream-100)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', marginBottom: 16 }}><OB_ICONS.IconlyRegularLightProfile style={{ width: 24, height: 24, color: 'var(--bronze-600)' }} /></div>
            <h2 style={{ fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 26, color: 'var(--ink)', margin: '0 0 6px' }}>Check your inbox</h2>
            <p style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', color: 'var(--text-body)', lineHeight: 1.6, margin: '0 0 18px' }}>If <b style={{ color: 'var(--ink)' }}>{email}</b> matches an account, a single-use reset link is on its way. It can be used once and expires in one hour.</p>
            <window.Note tone="info" title="Demo reset code" style={{ marginBottom: 18 }}>For this prototype, enter the code below as if you'd opened the email link: <b style={{ color: 'var(--ink)' }}>{token}</b></window.Note>
            <window.Field label="Reset code">
              <input className="opx-amount" style={{ fontSize: 16, fontWeight: 600, letterSpacing: '0.08em' }} value={entered} onChange={(e) => { setEntered(e.target.value); setTokenErr(''); }} placeholder="RST-XXXXXX" />
            </window.Field>
            {tokenErr && <div style={{ fontFamily: 'var(--font-body)', fontSize: 12.5, color: 'var(--danger-600)', marginTop: 8 }}>{tokenErr}</div>}
            <Button variant="primary" block style={{ marginTop: 18 }} disabled={!entered.trim()} onClick={verifyToken}>Continue</Button>
            <div style={{ textAlign: 'center', marginTop: 16 }}><span className="opx-link" style={{ fontSize: 13 }} onClick={() => go('welcome')}>Back to sign in</span></div>
          </>
        )}
        {phase === 'set' && (
          <>
            <h2 style={{ fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 26, color: 'var(--ink)', margin: '0 0 6px' }}>Set a new password</h2>
            <p style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', color: 'var(--text-body)', lineHeight: 1.6, margin: '0 0 22px' }}>Choose a strong password. All other sessions will be signed out.</p>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
              <window.Field label="New password">
                <Input icon={<OB_ICONS.IconlyRegularLightLock style={{ width: 20, height: 20, color: 'var(--gray-600)' }} />} trailing={<span style={{ cursor: 'pointer' }} onClick={() => setShowPw(!showPw)}><OB_ICONS.IconlyRegularLightShow style={{ width: 20, height: 20, color: 'var(--gray-500)' }} /></span>} type={showPw ? 'text' : 'password'} placeholder="At least 12 characters" value={pw} onChange={(e) => setPw(e.target.value)} />
              </window.Field>
              {pw.length > 0 && (
                <div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginTop: -6 }}>
                  {policy.rules.map((r) => (
                    <div key={r.key} style={{ display: 'flex', alignItems: 'center', gap: 8, fontFamily: 'var(--font-body)', fontSize: 12, color: r.ok ? 'var(--success-600)' : 'var(--text-muted)' }}>
                      <span style={{ width: 15, height: 15, borderRadius: '50%', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flex: '0 0 auto', background: r.ok ? 'var(--success-50)' : 'var(--surface-inset)', fontSize: 10, fontWeight: 700 }}>{r.ok ? '✓' : ''}</span>
                      {r.label}
                    </div>
                  ))}
                </div>
              )}
              <window.Field label="Confirm new password">
                <Input icon={<OB_ICONS.IconlyRegularLightLock style={{ width: 20, height: 20, color: 'var(--gray-600)' }} />} type={showPw ? 'text' : 'password'} placeholder="Re-enter your password" value={confirm} onChange={(e) => setConfirm(e.target.value)} />
              </window.Field>
              {confirm.length > 0 && !match && <div style={{ fontFamily: 'var(--font-body)', fontSize: 12, color: 'var(--danger-600)', marginTop: -8 }}>Passwords don't match.</div>}
            </div>
            <Button variant="primary" block style={{ marginTop: 20 }} disabled={!policy.ok || !match} onClick={setNewPassword}>Update password</Button>
          </>
        )}
        {phase === 'done' && (
          <>
            <div style={{ width: 56, height: 56, borderRadius: 'var(--radius-md)', background: 'var(--success-50)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', marginBottom: 16 }}><span style={{ color: 'var(--success-600)', fontSize: 26, fontWeight: 700 }}>✓</span></div>
            <h2 style={{ fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 26, color: 'var(--ink)', margin: '0 0 6px' }}>Password updated</h2>
            <p style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', color: 'var(--text-body)', lineHeight: 1.6, margin: '0 0 22px' }}>Your password has been changed and the reset link is now used up. You can sign in with your new password.</p>
            <Button variant="primary" block onClick={() => go('welcome')}>Back to sign in</Button>
          </>
        )}
      </Card>
    </AuthShellLite>
  );
}

// Lightweight auth chrome for the password-reset flow (account.jsx owns the richer AuthShell, but it
// loads after onboarding.jsx, so we keep a self-contained one here).
function AuthShellLite({ children }) {
  return (
    <div style={{ minHeight: '100vh', background: 'var(--surface-page)', display: 'flex', flexDirection: 'column' }}>
      <header style={{ height: 68, background: '#fff', borderBottom: '1px solid var(--border-faint)', display: 'flex', alignItems: 'center', padding: '0 28px' }}>
        <Logo variant="navy" height={30} base="assets/logo" />
        <span style={{ marginLeft: 16, paddingLeft: 16, borderLeft: '1px solid var(--border-subtle)', fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', color: 'var(--text-body)' }}>Account recovery</span>
      </header>
      <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '40px 24px' }}>
        <div className="opx-fade" style={{ width: '100%', maxWidth: 480, display: 'flex', justifyContent: 'center' }}>{children}</div>
      </div>
    </div>
  );
}

/* ============================================================ ONBOARDING HUB (INV-03) ============================================================ */
const KNOWLEDGE_Q = [
  { q: 'What can happen to the money you invest in these projects?', a: ['It is guaranteed by Opalus', 'You may lose some or all of your capital', 'It always grows with the property market'], correct: 1 },
  { q: 'How easily can you sell or exit an investment before the project ends?', a: ['Instantly, at any time', 'It is illiquid — you may not be able to exit early', 'Within 24 hours via the platform'], correct: 1 },
  { q: 'Are the advertised returns guaranteed?', a: ['Yes, they are fixed and guaranteed', 'No — they are targets and may not be achieved', 'Only for sophisticated investors'], correct: 1 },
  { q: 'Why does spreading money across several projects matter?', a: ['It increases guaranteed returns', 'Diversification helps manage the risk of any one project failing', 'It is required by law every month'], correct: 1 },
  { q: 'As a non-sophisticated investor, what protection applies after you commit?', a: ['A 4-day reflection period to withdraw without penalty', 'No protection applies', 'A 30-day money-back guarantee on returns'], correct: 0 },
];

function stepStatus(inv) {
  const soph = inv.category === 'sophisticated';
  const sophReview = inv.sophApplication?.status === 'review';
  return {
    residency: inv.residencyConfirmed ? (inv.countrySupported ? 'Done' : 'Action needed') : 'Not started',
    identity: inv.kyc === 'verified' ? 'Done' : inv.kyc === 'review' ? 'Under review' : inv.kyc === 'rejected' ? 'Action needed' : inv.kyc === 'pending' ? 'In progress' : 'Not started',
    connection: inv.restrictedPerson ? 'Done' : 'Not started',
    category: inv.category ? (sophReview ? 'Under review' : 'Done') : 'Not started',
    knowledge: soph ? 'Done' : inv.knowledgeTest?.passed ? 'Done' : inv.knowledgeTest ? 'In progress' : 'Not started',
    losssim: soph ? 'Done' : inv.lossSim?.acknowledged ? 'Done' : 'Not started',
  };
}

function StatusChip({ status }) {
  const map = {
    'Not started': ['var(--surface-inset)', 'var(--text-muted)'],
    'In progress': ['var(--cream-100)', 'var(--bronze-700)'],
    'Under review': ['var(--info-50)', 'var(--info-600)'],
    'Done': ['var(--success-50)', 'var(--success-600)'],
    'Action needed': ['var(--danger-50)', 'var(--danger-600)'],
  };
  const [bg, fg] = map[status] || map['Not started'];
  return <span style={{ fontFamily: 'var(--font-body)', fontSize: 11.5, fontWeight: 600, padding: '4px 11px', borderRadius: 999, background: bg, color: fg, whiteSpace: 'nowrap' }}>{status}</span>;
}

function Onboarding() {
  const { state, update, go } = window.useOpalus();
  const inv = state.investor;
  // view: hub | residency | identity | category | soph | knowledge | losssim
  const [view, setView] = useState(() => (inv.residencyConfirmed ? 'hub' : 'residency'));
  const toHub = () => { setView('hub'); window.scrollTo(0, 0); };

  return (
    <div style={{ minHeight: '100vh', background: 'var(--surface-page)' }}>
      <header style={{ height: 68, background: '#fff', borderBottom: '1px solid var(--border-faint)', display: 'flex', alignItems: 'center', padding: '0 28px', position: 'sticky', top: 0, zIndex: 5 }}>
        <Logo variant="navy" height={30} base="assets/logo" />
        <span style={{ marginLeft: 16, paddingLeft: 16, borderLeft: '1px solid var(--border-subtle)', fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', color: 'var(--text-body)' }}>Account setup</span>
        <span className="opx-link" style={{ marginLeft: 'auto', fontSize: 13 }} onClick={() => { update((s) => { s.onboarded = true; }); go('dashboard'); }}>Save & exit</span>
      </header>

      <div style={{ maxWidth: 860, margin: '0 auto', padding: '34px 24px 80px' }} className="opx-fade" key={view}>
        {view === 'hub' && <OnbHub inv={inv} setView={setView} update={update} go={go} />}
        {view === 'residency' && <CountryStep inv={inv} update={update} onDone={toHub} />}
        {view === 'identity' && <KycStep kyc={inv.kyc} update={update} onDone={toHub} onBack={toHub} />}
        {view === 'connection' && <ConnectionStep inv={inv} update={update} onDone={toHub} onBack={toHub} />}
        {view === 'category' && <CategoryStep inv={inv} update={update} onContinue={toHub} onApply={() => setView('soph')} onBack={toHub} />}
        {view === 'soph' && <SophApplication update={update} onDone={toHub} onBack={() => setView('category')} />}
        {view === 'knowledge' && <KnowledgeStep update={update} onDone={toHub} onBack={toHub} />}
        {view === 'losssim' && <LossSimStep inv={inv} update={update} onDone={toHub} onBack={toHub} />}
      </div>
    </div>
  );
}

function OnbHub({ inv, setView, update, go }) {
  const st = stepStatus(inv);
  const items = [
    { key: 'residency', name: 'Confirm where you live', view: 'residency', status: st.residency, Icon: OB_ICONS.IconlyRegularLightLocation },
    { key: 'identity', name: 'Verify your identity', view: 'identity', status: st.identity, Icon: OB_ICONS.IconlyRegularOutlineDocument },
    { key: 'connection', name: 'Declare any connection to Opalus', view: 'connection', status: st.connection, Icon: OB_ICONS.IconlyRegularLightProfile },
    { key: 'category', name: 'Confirm your investor category', view: 'category', status: st.category, Icon: OB_ICONS.IconlyRegularLightProfile },
    { key: 'knowledge', name: 'Take the knowledge assessment', view: 'knowledge', status: st.knowledge, Icon: OB_ICONS.IconlyRegularLightGraph },
    { key: 'losssim', name: 'Simulate your capacity to bear losses', view: 'losssim', status: st.losssim, Icon: OB_ICONS.IconlyRegularLightActivity },
  ];
  const next = items.find((i) => i.status !== 'Done' && i.status !== 'Under review');
  const allDone = items.every((i) => i.status === 'Done');
  const done = items.filter((i) => i.status === 'Done').length;

  return (
    <div>
      <window.Eyebrow>Account setup</window.Eyebrow>
      <h1 style={{ fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 30, color: 'var(--ink)', margin: '8px 0 8px', letterSpacing: 'var(--ls-tight)' }}>Set up your account to invest</h1>
      <p style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', color: 'var(--text-body)', margin: '0 0 22px', maxWidth: 560, lineHeight: 1.6 }}>A few one-time steps, required under EU rules. Each protects you as an investor.</p>

      <div style={{ marginBottom: 22 }}>
        <ProgressBar value={Math.round(done / items.length * 100)} label={`${done} of ${items.length} complete`} track="cream" />
      </div>

      <Card padding="none" radius="lg" shadow="sm" style={{ overflow: 'hidden' }}>
        {items.map((it, i) => {
          const isDone = it.status === 'Done';
          return (
            <button key={it.key} type="button" onClick={() => setView(it.view)} style={{ display: 'flex', alignItems: 'center', gap: 16, width: '100%', textAlign: 'left', cursor: 'pointer', border: 'none', background: '#fff', padding: '18px 22px', borderTop: i ? '1px solid var(--border-faint)' : 'none' }}>
              <span style={{ width: 44, height: 44, borderRadius: 'var(--radius-md)', background: isDone ? 'var(--success-50)' : 'var(--cream-100)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flex: '0 0 auto' }}>
                {isDone ? <span style={{ color: 'var(--success-600)', fontSize: 20, fontWeight: 700 }}>✓</span> : <it.Icon style={{ width: 22, height: 22, color: 'var(--bronze-600)' }} />}
              </span>
              <span style={{ flex: 1, fontFamily: 'var(--font-body)', fontSize: 15, fontWeight: 600, color: 'var(--ink)' }}>{it.name}</span>
              <StatusChip status={it.status} />
              <OB_ICONS.IconlyRegularLightArrowRight style={{ width: 18, height: 18, color: 'var(--text-muted)' }} />
            </button>
          );
        })}
      </Card>

      <div style={{ marginTop: 24, display: 'flex', gap: 14, alignItems: 'center', flexWrap: 'wrap' }}>
        {allDone ? (
          <>
            <div style={{ fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 18, color: 'var(--success-600)' }}>You're ready to invest.</div>
            <Button variant="primary" style={{ marginLeft: 'auto' }} onClick={() => { update((s) => { s.onboarded = true; }); go('browse'); }} trailingIcon={<OB_ICONS.IconlyRegularLightArrowRight style={{ width: 18, height: 18 }} />}>Browse projects</Button>
          </>
        ) : next ? (
          <Button variant="primary" onClick={() => setView(next.view)} trailingIcon={<OB_ICONS.IconlyRegularLightArrowRight style={{ width: 18, height: 18 }} />}>Continue: {next.name}</Button>
        ) : (
          <div style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', color: 'var(--text-body)' }}>Some steps are under review. We'll email you when they're complete.</div>
        )}
      </div>
    </div>
  );
}

/* ---------- shared step frame ---------- */
function StepFrame({ eyebrow, title, sub, children, footer }) {
  return (
    <Card padding="lg" radius="xl" shadow="sm" style={{ padding: 36 }}>
      <window.Eyebrow>{eyebrow}</window.Eyebrow>
      <h2 style={{ fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 26, color: 'var(--ink)', margin: '8px 0 8px', letterSpacing: 'var(--ls-tight)' }}>{title}</h2>
      {sub && <p style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', color: 'var(--text-body)', lineHeight: 1.6, margin: '0 0 24px', maxWidth: 620 }}>{sub}</p>}
      {children}
      {footer && <div style={{ display: 'flex', gap: 12, marginTop: 28, alignItems: 'center', flexWrap: 'wrap' }}>{footer}</div>}
    </Card>
  );
}

/* ---------- INV-04 · Country of residence ---------- */
/* ASSUMPTION (confirm Eric Kay): v1 authorised jurisdictions = FR (host) + LT (home) ONLY;
   EU macro OFF; all other countries OFF. Only France and Lithuania are flagged supported below.
   — see Sync_Audit_Report open Q2 */
const COUNTRIES = [
  ['France', true], ['Germany', false], ['Spain', false], ['Italy', false], ['Netherlands', false], ['Belgium', false],
  ['Ireland', false], ['Portugal', false], ['Lithuania', true], ['United Kingdom', false], ['United States', false], ['Switzerland', false],
];
function CountryStep({ inv, update, onDone }) {
  const [query, setQuery] = useState(inv.country || '');
  const [picked, setPicked] = useState(inv.country || '');
  const [fiscal, setFiscal] = useState(inv.fiscalResidence || inv.country || '');
  const supported = COUNTRIES.find((c) => c[0] === picked)?.[1];
  const matches = COUNTRIES.filter((c) => c[0].toLowerCase().includes(query.toLowerCase()));
  const choose = (name) => { setPicked(name); setQuery(name); setFiscal(name); };  // fiscal residence defaults to residence
  return (
    <StepFrame eyebrow="Step 1 · Residency" title="Where do you live?"
      sub="This determines which projects you can invest in. If you move later, we'll need to re-verify your identity."
      footer={picked && supported && <Button variant="primary" onClick={() => { update((s) => { s.investor.country = picked; s.investor.fiscalResidence = fiscal || picked; s.investor.countrySupported = true; s.investor.residencyConfirmed = true; }); onDone(); }} trailingIcon={<OB_ICONS.IconlyRegularLightArrowRight style={{ width: 18, height: 18 }} />}>Continue</Button>}>
      <window.Field label="Country of residence (searchable)">
        <input className="opx-amount" style={{ fontSize: 16, fontWeight: 500 }} value={query} onChange={(e) => { setQuery(e.target.value); setPicked(''); }} placeholder="Start typing…" />
      </window.Field>
      {query && !picked && (
        <div style={{ marginTop: 8, border: '1px solid var(--border-faint)', borderRadius: 'var(--radius-md)', overflow: 'hidden', maxHeight: 200, overflowY: 'auto' }}>
          {matches.map(([name]) => <button key={name} type="button" onClick={() => choose(name)} style={{ display: 'block', width: '100%', textAlign: 'left', cursor: 'pointer', border: 'none', background: '#fff', padding: '11px 16px', fontFamily: 'var(--font-body)', fontSize: 14, color: 'var(--text-body)', borderTop: '1px solid var(--border-faint)' }}>{name}</button>)}
          {!matches.length && <div style={{ padding: '11px 16px', fontFamily: 'var(--font-body)', fontSize: 13, color: 'var(--text-muted)' }}>No match.</div>}
        </div>
      )}
      {/* Art 16 country of fiscal residence — defaults to residence, overridable; snapshot at offer creation */}
      {picked && supported && (
        <div style={{ marginTop: 18 }}>
          <window.Field label="Country of fiscal residence" hint="Where you are tax-resident. Usually the same as where you live — change it if yours differs.">
            <select className="opx-amount" style={{ fontSize: 16, fontWeight: 500 }} value={fiscal} onChange={(e) => setFiscal(e.target.value)}>
              {COUNTRIES.map(([name]) => <option key={name} value={name}>{name}</option>)}
            </select>
          </window.Field>
        </div>
      )}
      {picked && !supported && (
        <div style={{ marginTop: 16 }}>
          <window.Note tone="warn" title={`Opalus is not yet available to residents of ${picked}.`} icon={<span style={{ fontWeight: 700 }}>!</span>}>
            Your account stays active, and we'll email you if this changes. You can browse projects, but investing isn't available in your country yet.
            <div style={{ marginTop: 12 }}><Button variant="outline" size="sm" onClick={() => { update((s) => { s.investor.country = picked; s.investor.countrySupported = false; s.investor.residencyConfirmed = true; }); onDone(); }}>Browse as an observer</Button></div>
          </window.Note>
        </div>
      )}
    </StepFrame>
  );
}

/* ---------- INV-05 · KYC — Sumsub WebSDK-mirroring identity verification ----------
   Mirrors the real Sumsub applicant flow (Sumsub_Flow_Spec §A): intro/consent → applicant data →
   country + document type → ID document capture (front/back, scanning + quality-retry) → selfie +
   liveness (face-roll) → optional proof of address → submit → Under review. Applicant status states
   surfaced: Not started / In progress / Processing / Under review / Verified / Action needed / Rejected.
   Capture is mocked (file pickers / camera placeholders) — no real SDK in a prototype. */
const SUMSUB_DOC_TYPES = [
  { id: 'passport', label: 'Passport', sides: ['photo page'], Icon: OB_ICONS.IconlyRegularOutlineDocument },
  { id: 'id-card', label: 'National ID card', sides: ['front', 'back'], Icon: OB_ICONS.IconlyRegularOutlineDocument },
  { id: 'drivers', label: "Driver's licence", sides: ['front', 'back'], Icon: OB_ICONS.IconlyRegularOutlineDocument },
  { id: 'permit', label: 'Residence permit', sides: ['front', 'back'], Icon: OB_ICONS.IconlyRegularOutlineDocument },
];
// quality-retry reasons surfaced after a (mocked) failed read
const SUMSUB_QUALITY_REASONS = {
  glare: 'Glare detected — move away from direct light and avoid reflections.',
  blur: 'The image is blurry — hold steady and make sure the document is in focus.',
  cropped: 'Part of the document is cut off — fit all four corners inside the frame.',
  expired: 'This document appears to be expired — use a current, in-date document.',
};

// "Powered by Sumsub" + EU data-residency footer, shown on every step
function SumsubBrand() {
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
      <span style={{ fontFamily: 'var(--font-body)', fontSize: 12, fontWeight: 600, color: 'var(--text-muted)' }}>Powered by Sumsub</span>
      <span style={{ width: 4, height: 4, borderRadius: '50%', background: 'var(--border-strong)' }} />
      <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, fontFamily: 'var(--font-body)', fontSize: 12, color: 'var(--text-muted)' }}>
        <OB_ICONS.IconlyRegularLightLock style={{ width: 13, height: 13 }} /> Data processed in the EU (Frankfurt / Dublin)
      </span>
    </div>
  );
}

// dropzone / capture placeholder used for document & PoA capture
function CaptureBox({ captured, label, onUpload, onCamera }) {
  return (
    <div style={{ border: `1.5px dashed ${captured ? 'var(--bronze-500)' : 'var(--border-strong)'}`, borderRadius: 'var(--radius-md)', background: captured ? 'var(--cream-100)' : 'var(--surface-inset)', padding: '22px 18px', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 10, textAlign: 'center' }}>
      {captured ? (
        <>
          <span style={{ width: 44, height: 44, borderRadius: '50%', background: 'var(--success-600)', color: '#fff', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: 22, fontWeight: 700 }}>✓</span>
          <div style={{ fontFamily: 'var(--font-body)', fontSize: 13.5, fontWeight: 600, color: 'var(--ink)' }}>{label} captured</div>
          <button type="button" className="opx-link" style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 12.5 }} onClick={onUpload}>Replace</button>
        </>
      ) : (
        <>
          <OB_ICONS.IconlyRegularOutlineDocument style={{ width: 30, height: 30, color: 'var(--bronze-600)' }} />
          <div style={{ fontFamily: 'var(--font-body)', fontSize: 13.5, fontWeight: 600, color: 'var(--text-strong)' }}>Capture the {label}</div>
          <div style={{ display: 'flex', gap: 10, marginTop: 2, flexWrap: 'wrap', justifyContent: 'center' }}>
            <Button variant="soft" size="sm" leadingIcon={<OB_ICONS.IconlyRegularOutlineUpload style={{ width: 15, height: 15, color: 'var(--bronze-700)' }} />} onClick={onUpload}>Upload file</Button>
            <Button variant="outline" size="sm" onClick={onCamera}>Use camera</Button>
          </div>
        </>
      )}
    </div>
  );
}

function KycStep({ kyc, update, onDone, onBack }) {
  const inv = window.useOpalus().state.investor;
  // applicant status: maps to Sumsub applicant status / review answer
  // notStarted | inProgress | processing | review | verified | actionNeeded | rejected
  const initialStatus = kyc === 'verified' ? 'verified' : kyc === 'review' ? 'review' : kyc === 'rejected' ? 'actionNeeded' : kyc === 'pending' ? 'inProgress' : 'notStarted';
  const [status, setStatus] = useState(initialStatus);
  // wizard step index when inProgress: 0 intro, 1 applicant, 2 country+type, 3 doc capture, 4 selfie, 5 PoA, 6 submit
  const [step, setStep] = useState(0);

  // collected applicant data (pre-filled from onboarding where known)
  const [legalName, setLegalName] = useState(inv.name || '');
  const [dob, setDob] = useState('1990-05-14');
  const [residence, setResidence] = useState(inv.country || 'France');
  const [taxResidence, setTaxResidence] = useState(inv.fiscalResidence || inv.country || 'France');
  const [issuingCountry, setIssuingCountry] = useState(inv.country || 'France');
  const [docType, setDocType] = useState('id-card');
  const docDef = SUMSUB_DOC_TYPES.find((d) => d.id === docType) || SUMSUB_DOC_TYPES[1];

  // capture state
  const [captured, setCaptured] = useState({});           // { 'front': true, ... }
  const [docPhase, setDocPhase] = useState('capture');    // capture | scanning | quality | done
  const [qualityReason, setQualityReason] = useState(null);
  const [selfiePhase, setSelfiePhase] = useState('intro');// intro | rolling | processing | done
  const [rollPct, setRollPct] = useState(0);
  const [poaCaptured, setPoaCaptured] = useState(false);
  const [actionReason, setActionReason] = useState('Document expired — please upload a current, in-date ID.');

  const finishVerified = () => { update((s) => { s.investor.kyc = 'verified'; }); onDone(); };
  const setKyc = (v) => update((s) => { s.investor.kyc = v; });

  /* ----- terminal status screens ----- */
  if (status === 'verified') {
    return (
      <StepFrame eyebrow="Step 2 · Identity" title="Identity verified"
        footer={<><Button variant="primary" onClick={onDone} trailingIcon={<OB_ICONS.IconlyRegularLightArrowRight style={{ width: 18, height: 18 }} />}>Back to setup</Button><SumsubBrand /></>}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 16, padding: 20, borderRadius: 'var(--radius-lg)', background: 'var(--success-50)', border: '1px solid #a8e3b6' }}>
          <span style={{ width: 50, height: 50, borderRadius: '50%', background: 'var(--success-600)', color: '#fff', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: 24, fontWeight: 700, flex: '0 0 auto' }}>✓</span>
          <div style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', color: 'var(--text-body)' }}>Your identity has been verified. Your KYC badge is now active and you can continue setting up your account.</div>
        </div>
      </StepFrame>
    );
  }
  if (status === 'review') {
    return (
      <StepFrame eyebrow="Step 2 · Identity" title="We're reviewing your details" sub="Your verification is with Sumsub. This usually takes seconds, but can take longer if a manual review is needed — nothing more is required from you."
        footer={<><Button variant="primary" onClick={() => { setStatus('verified'); finishVerified(); }}>Approve now (demo)</Button><Button variant="ghost" onClick={onBack}>Back to setup</Button><SumsubBrand /></>}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 14, padding: '16px 18px', borderRadius: 'var(--radius-md)', background: 'var(--info-50)', border: '1px solid var(--info-400)' }}>
          <span className="opx-spin" style={{ width: 22, height: 22, borderRadius: '50%', border: '3px solid var(--info-400)', borderTopColor: 'var(--info-600)', flex: '0 0 auto' }} />
          <div style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', color: 'var(--text-body)' }}><b style={{ color: 'var(--ink)' }}>Under review.</b> We'll email you the moment it's complete.</div>
        </div>
        <div style={{ marginTop: 14, display: 'flex', gap: 8, flexWrap: 'wrap' }}>
          <Button variant="ghost" size="sm" onClick={() => { setStatus('actionNeeded'); setKyc('rejected'); }}>Action needed (demo)</Button>
          <Button variant="ghost" size="sm" onClick={() => { setStatus('rejected'); setKyc('rejected'); }}>Reject · final (demo)</Button>
        </div>
      </StepFrame>
    );
  }
  if (status === 'actionNeeded') {
    return (
      <StepFrame eyebrow="Step 2 · Identity" title="Action needed to verify your identity" sub="Sumsub couldn't complete your check. Fix the issue below and resubmit — you can retry."
        footer={<><Button variant="primary" onClick={() => { setStatus('inProgress'); setStep(0); setCaptured({}); setDocPhase('capture'); setSelfiePhase('intro'); setRollPct(0); setKyc('pending'); }}>Resubmit verification</Button><Button variant="ghost">Contact support</Button><SumsubBrand /></>}>
        <window.Note tone="danger" title="Reason for resubmission">{actionReason}</window.Note>
      </StepFrame>
    );
  }
  if (status === 'rejected') {
    return (
      <StepFrame eyebrow="Step 2 · Identity" title="We couldn't verify your identity" sub="Your account stays open, but investing isn't available. If you believe this is a mistake, contact support — self-retry isn't available for this outcome."
        footer={<><Button variant="primary">Contact support</Button><Button variant="ghost" onClick={onBack}>Back to setup</Button><SumsubBrand /></>}>
        <window.Note tone="danger" title="Verification unsuccessful">We were unable to confirm your identity from the information and documents provided.</window.Note>
      </StepFrame>
    );
  }

  /* ----- intro / consent (Not started) ----- */
  if (status === 'notStarted') {
    return (
      <StepFrame eyebrow="Step 2 · Identity" title="Verify your identity"
        sub="A quick, secure check that confirms who you are. You'll need a valid government ID and a device camera. It takes about 2 minutes."
        footer={<><Button variant="primary" onClick={() => { setStatus('inProgress'); setStep(0); setKyc('pending'); }} leadingIcon={<OB_ICONS.IconlyRegularLightLogin style={{ width: 18, height: 18 }} />}>Start verification</Button><Button variant="ghost" onClick={onBack}>Back to setup</Button><SumsubBrand /></>}>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 10, marginBottom: 16 }}>
          {[['A valid government ID', 'Passport, national ID, driver’s licence or residence permit', OB_ICONS.IconlyRegularOutlineDocument],
            ['A device camera', 'For a short selfie and a liveness check', OB_ICONS.IconlyRegularLightProfile]].map(([t, d, Icon]) => (
            <div key={t} style={{ display: 'flex', alignItems: 'center', gap: 14, padding: '14px 16px', border: '1px solid var(--border-faint)', borderRadius: 'var(--radius-md)', background: 'var(--surface-inset)' }}>
              <span style={{ width: 40, height: 40, borderRadius: 'var(--radius-md)', background: '#fff', border: '1px solid var(--border-faint)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flex: '0 0 auto' }}><Icon style={{ width: 20, height: 20, color: 'var(--bronze-600)' }} /></span>
              <div style={{ flex: 1 }}>
                <div style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', fontWeight: 600, color: 'var(--text-strong)' }}>{t}</div>
                <div style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-xs)', color: 'var(--text-muted)' }}>{d}</div>
              </div>
            </div>
          ))}
        </div>
        <window.Note tone="cream" title="Consent to identity & biometric processing">By starting, you agree that Sumsub may process your identity document and biometric (facial) data to verify your identity on Opalus's behalf, in the EU. <span className="opx-link">Learn how your data is used</span>.</window.Note>
      </StepFrame>
    );
  }

  /* ----- in-progress wizard ----- */
  const WIZ = ['Consent', 'Your details', 'Document', 'ID capture', 'Selfie', 'Address', 'Submit'];
  const advance = () => setStep((n) => Math.min(WIZ.length - 1, n + 1));
  const back = () => (step === 0 ? onBack() : setStep((n) => n - 1));

  // ID capture sub-flow handlers (mock scan → quality retry → done)
  const captureSide = (side) => { setCaptured((c) => ({ ...c, [side]: true })); };
  const allSidesCaptured = docDef.sides.every((s) => captured[s]);
  const runScan = () => {
    setDocPhase('scanning');
    setTimeout(() => {
      // first scan fails with a quality reason; second scan passes (demo behaviour)
      if (!qualityReason && Math.random() < 0.5) {
        const keys = Object.keys(SUMSUB_QUALITY_REASONS);
        setQualityReason(keys[Math.floor(Math.random() * keys.length)]);
        setDocPhase('quality');
      } else {
        setDocPhase('done');
      }
    }, 1600);
  };
  const retryCapture = () => { setCaptured({}); setQualityReason(null); setDocPhase('capture'); };

  // selfie liveness (face-roll) handler
  const startRoll = () => {
    setSelfiePhase('rolling'); setRollPct(0);
    const id = setInterval(() => setRollPct((p) => {
      if (p >= 100) { clearInterval(id); setSelfiePhase('processing'); setTimeout(() => setSelfiePhase('done'), 1400); return 100; }
      return p + 10;
    }), 180);
  };

  const submit = () => { setStatus('review'); setKyc('review'); };

  return (
    <StepFrame eyebrow={`Step 2 · Identity · ${step + 1} of ${WIZ.length}`} title="Identity verification"
      footer={<SumsubBrand />}>
      <div style={{ marginBottom: 22 }}><window.Stepper steps={WIZ} current={step} /></div>

      {/* 0 · consent */}
      {step === 0 && (
        <>
          <div style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', color: 'var(--text-body)', lineHeight: 1.6, marginBottom: 16 }}>You're about to start identity verification with Sumsub. Your ID and a facial scan are processed securely in the EU to confirm your identity.</div>
          <window.Note tone="cream" title="What we'll check" style={{ marginBottom: 18 }}>Your identity document, a selfie with a liveness check, and that the two match.</window.Note>
          <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
            <Button variant="primary" onClick={advance} trailingIcon={<OB_ICONS.IconlyRegularLightArrowRight style={{ width: 18, height: 18 }} />}>I consent — continue</Button>
            <Button variant="ghost" onClick={back}>Back to setup</Button>
          </div>
        </>
      )}

      {/* 1 · applicant data */}
      {step === 1 && (
        <>
          <div style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', fontWeight: 600, color: 'var(--ink)', marginBottom: 14 }}>Confirm your details</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 16, maxWidth: 480 }}>
            <window.Field label="Legal name (as on your ID)">
              <input className="opx-amount" style={{ fontSize: 16, fontWeight: 500 }} value={legalName} onChange={(e) => setLegalName(e.target.value)} />
            </window.Field>
            <window.Field label="Date of birth">
              <input className="opx-amount" style={{ fontSize: 16, fontWeight: 500 }} type="date" value={dob} onChange={(e) => setDob(e.target.value)} />
            </window.Field>
            <window.Field label="Country of residence">
              <select className="opx-amount" style={{ fontSize: 16, fontWeight: 500 }} value={residence} onChange={(e) => setResidence(e.target.value)}>
                {COUNTRIES.map(([name]) => <option key={name} value={name}>{name}</option>)}
              </select>
            </window.Field>
            <window.Field label="Country of tax residence">
              <select className="opx-amount" style={{ fontSize: 16, fontWeight: 500 }} value={taxResidence} onChange={(e) => setTaxResidence(e.target.value)}>
                {COUNTRIES.map(([name]) => <option key={name} value={name}>{name}</option>)}
              </select>
            </window.Field>
          </div>
          <div style={{ display: 'flex', gap: 12, marginTop: 22 }}>
            <Button variant="primary" disabled={!legalName.trim()} onClick={advance} trailingIcon={<OB_ICONS.IconlyRegularLightArrowRight style={{ width: 18, height: 18 }} />}>Continue</Button>
            <Button variant="ghost" onClick={back}>Back</Button>
          </div>
        </>
      )}

      {/* 2 · country + document type */}
      {step === 2 && (
        <>
          <div style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', fontWeight: 600, color: 'var(--ink)', marginBottom: 14 }}>Which document will you use?</div>
          <div style={{ maxWidth: 480, marginBottom: 18 }}>
            <window.Field label="Issuing country">
              <select className="opx-amount" style={{ fontSize: 16, fontWeight: 500 }} value={issuingCountry} onChange={(e) => setIssuingCountry(e.target.value)}>
                {COUNTRIES.map(([name]) => <option key={name} value={name}>{name}</option>)}
              </select>
            </window.Field>
          </div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            {SUMSUB_DOC_TYPES.map((d) => {
              const on = docType === d.id;
              return (
                <button key={d.id} type="button" onClick={() => { setDocType(d.id); setCaptured({}); setDocPhase('capture'); setQualityReason(null); }} style={{ display: 'flex', alignItems: 'center', gap: 14, textAlign: 'left', cursor: 'pointer', padding: '14px 16px', borderRadius: 'var(--radius-md)', border: `1.5px solid ${on ? 'var(--bronze-500)' : 'var(--border-faint)'}`, background: on ? 'var(--cream-100)' : '#fff' }}>
                  <span style={{ width: 40, height: 40, borderRadius: 'var(--radius-md)', background: 'var(--surface-inset)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flex: '0 0 auto' }}><d.Icon style={{ width: 20, height: 20, color: 'var(--bronze-600)' }} /></span>
                  <div style={{ flex: 1 }}>
                    <div style={{ fontFamily: 'var(--font-body)', fontSize: 14, fontWeight: 600, color: 'var(--ink)' }}>{d.label}</div>
                    <div style={{ fontFamily: 'var(--font-body)', fontSize: 12, color: 'var(--text-muted)' }}>{d.sides.length === 1 ? `Capture the ${d.sides[0]}` : `Capture ${d.sides.join(' + ')}`}</div>
                  </div>
                  <span style={{ width: 18, height: 18, borderRadius: '50%', border: `2px solid ${on ? 'var(--bronze-600)' : 'var(--border-strong)'}`, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flex: '0 0 auto' }}>{on && <span style={{ width: 9, height: 9, borderRadius: '50%', background: 'var(--bronze-600)' }} />}</span>
                </button>
              );
            })}
          </div>
          <div style={{ display: 'flex', gap: 12, marginTop: 22 }}>
            <Button variant="primary" onClick={advance} trailingIcon={<OB_ICONS.IconlyRegularLightArrowRight style={{ width: 18, height: 18 }} />}>Continue</Button>
            <Button variant="ghost" onClick={back}>Back</Button>
          </div>
        </>
      )}

      {/* 3 · ID document capture */}
      {step === 3 && (
        <>
          <div style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', fontWeight: 600, color: 'var(--ink)', marginBottom: 4 }}>Capture your {docDef.label.toLowerCase()}</div>
          <p style={{ fontFamily: 'var(--font-body)', fontSize: 12.5, color: 'var(--text-muted)', margin: '0 0 16px', lineHeight: 1.5 }}>Place the document on a flat surface, in good light, with all corners visible.</p>

          {docPhase === 'capture' && (
            <>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
                {docDef.sides.map((side) => (
                  <CaptureBox key={side} captured={!!captured[side]} label={`${docDef.label} ${side}`} onUpload={() => captureSide(side)} onCamera={() => captureSide(side)} />
                ))}
              </div>
              <div style={{ display: 'flex', gap: 12, marginTop: 22 }}>
                <Button variant="primary" disabled={!allSidesCaptured} onClick={runScan}>Submit document</Button>
                <Button variant="ghost" onClick={back}>Back</Button>
              </div>
            </>
          )}

          {docPhase === 'scanning' && (
            <div style={{ display: 'flex', alignItems: 'center', gap: 14, padding: '20px 18px', borderRadius: 'var(--radius-md)', background: 'var(--info-50)', border: '1px solid var(--info-400)' }}>
              <span className="opx-spin" style={{ width: 24, height: 24, borderRadius: '50%', border: '3px solid var(--info-400)', borderTopColor: 'var(--info-600)', flex: '0 0 auto' }} />
              <div style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', color: 'var(--text-body)' }}><b style={{ color: 'var(--ink)' }}>Reading your document…</b> Checking quality and extracting the details.</div>
            </div>
          )}

          {docPhase === 'quality' && (
            <>
              <window.Note tone="danger" title="We couldn't read your document" icon={<span style={{ fontWeight: 700 }}>!</span>}>{SUMSUB_QUALITY_REASONS[qualityReason]}</window.Note>
              <div style={{ display: 'flex', gap: 12, marginTop: 18 }}>
                <Button variant="primary" onClick={retryCapture}>Retake document</Button>
                <Button variant="ghost" onClick={back}>Back</Button>
              </div>
            </>
          )}

          {docPhase === 'done' && (
            <>
              <div style={{ display: 'flex', alignItems: 'center', gap: 14, padding: '16px 18px', borderRadius: 'var(--radius-md)', background: 'var(--success-50)', border: '1px solid #a8e3b6' }}>
                <span style={{ width: 36, height: 36, borderRadius: '50%', background: 'var(--success-600)', color: '#fff', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: 18, fontWeight: 700, flex: '0 0 auto' }}>✓</span>
                <div style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', color: 'var(--text-body)' }}>Document read successfully.</div>
              </div>
              <div style={{ display: 'flex', gap: 12, marginTop: 22 }}>
                <Button variant="primary" onClick={advance} trailingIcon={<OB_ICONS.IconlyRegularLightArrowRight style={{ width: 18, height: 18 }} />}>Continue</Button>
                <Button variant="ghost" onClick={retryCapture}>Retake</Button>
              </div>
            </>
          )}
        </>
      )}

      {/* 4 · selfie + liveness */}
      {step === 4 && (
        <>
          <div style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', fontWeight: 600, color: 'var(--ink)', marginBottom: 4 }}>Selfie &amp; liveness check</div>
          <p style={{ fontFamily: 'var(--font-body)', fontSize: 12.5, color: 'var(--text-muted)', margin: '0 0 18px', lineHeight: 1.5 }}>We match your face to your document and confirm you're really here.</p>

          <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 16 }}>
            <div style={{ position: 'relative', width: 180, height: 180 }}>
              <div style={{ position: 'absolute', inset: 0, borderRadius: '50%', background: 'var(--surface-inset)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}>
                {selfiePhase === 'done'
                  ? <span style={{ color: 'var(--success-600)', fontSize: 56, fontWeight: 700 }}>✓</span>
                  : <OB_ICONS.IconlyRegularLightProfile style={{ width: 64, height: 64, color: 'var(--bronze-400)' }} />}
              </div>
              <svg viewBox="0 0 180 180" style={{ position: 'absolute', inset: 0, transform: 'rotate(-90deg)' }}>
                <circle cx="90" cy="90" r="84" fill="none" stroke="var(--border-faint)" strokeWidth="6" />
                <circle cx="90" cy="90" r="84" fill="none" stroke={selfiePhase === 'done' ? 'var(--success-600)' : 'var(--bronze-500)'} strokeWidth="6" strokeLinecap="round" strokeDasharray={2 * Math.PI * 84} strokeDashoffset={2 * Math.PI * 84 * (1 - (selfiePhase === 'done' ? 1 : rollPct / 100))} style={{ transition: 'stroke-dashoffset .18s linear' }} />
              </svg>
            </div>
            <div style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', color: 'var(--text-body)', textAlign: 'center', maxWidth: 320 }}>
              {selfiePhase === 'intro' && 'Position your face in the circle, then slowly turn your head until the ring turns green.'}
              {selfiePhase === 'rolling' && 'Keep turning your head — hold steady…'}
              {selfiePhase === 'processing' && 'Checking your selfie…'}
              {selfiePhase === 'done' && 'Liveness confirmed and matched to your document.'}
            </div>
            {selfiePhase === 'processing' && <span className="opx-spin" style={{ width: 22, height: 22, borderRadius: '50%', border: '3px solid var(--border-faint)', borderTopColor: 'var(--bronze-600)' }} />}
          </div>

          <div style={{ display: 'flex', gap: 12, marginTop: 24, justifyContent: 'center', flexWrap: 'wrap' }}>
            {selfiePhase === 'intro' && <Button variant="primary" onClick={startRoll} leadingIcon={<OB_ICONS.IconlyRegularLightLogin style={{ width: 18, height: 18 }} />}>Start liveness check</Button>}
            {selfiePhase === 'done' && <Button variant="primary" onClick={advance} trailingIcon={<OB_ICONS.IconlyRegularLightArrowRight style={{ width: 18, height: 18 }} />}>Continue</Button>}
            <Button variant="ghost" onClick={back}>Back</Button>
          </div>
        </>
      )}

      {/* 5 · proof of address (optional / level-dependent) */}
      {step === 5 && (
        <>
          <div style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', fontWeight: 600, color: 'var(--ink)', marginBottom: 4 }}>Proof of address</div>
          <p style={{ fontFamily: 'var(--font-body)', fontSize: 12.5, color: 'var(--text-muted)', margin: '0 0 16px', lineHeight: 1.5 }}>Required for your verification level. Upload a utility bill or bank statement issued in the last 3 months showing your name and address.</p>
          <CaptureBox captured={poaCaptured} label="proof of address" onUpload={() => setPoaCaptured(true)} onCamera={() => setPoaCaptured(true)} />
          <div style={{ display: 'flex', gap: 12, marginTop: 22 }}>
            <Button variant="primary" disabled={!poaCaptured} onClick={advance} trailingIcon={<OB_ICONS.IconlyRegularLightArrowRight style={{ width: 18, height: 18 }} />}>Continue</Button>
            <Button variant="ghost" onClick={back}>Back</Button>
          </div>
        </>
      )}

      {/* 6 · submit */}
      {step === 6 && (
        <>
          <div style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', fontWeight: 600, color: 'var(--ink)', marginBottom: 14 }}>Review and submit</div>
          <div style={{ border: '1px solid var(--border-faint)', borderRadius: 'var(--radius-md)', overflow: 'hidden', marginBottom: 18 }}>
            {[['Applicant', legalName], ['Date of birth', dob], ['Residence', residence], ['Document', `${docDef.label} · ${issuingCountry}`], ['ID document', 'Captured ✓'], ['Selfie & liveness', 'Confirmed ✓'], ['Proof of address', poaCaptured ? 'Captured ✓' : 'Skipped']].map(([k, v], i) => (
              <div key={k} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, padding: '12px 16px', background: i % 2 ? 'var(--surface-inset)' : '#fff' }}>
                <span style={{ fontFamily: 'var(--font-body)', fontSize: 13, color: 'var(--text-muted)' }}>{k}</span>
                <span style={{ fontFamily: 'var(--font-body)', fontSize: 13, fontWeight: 600, color: 'var(--ink)' }}>{v}</span>
              </div>
            ))}
          </div>
          <div style={{ display: 'flex', gap: 12 }}>
            <Button variant="primary" onClick={submit} trailingIcon={<OB_ICONS.IconlyRegularLightArrowRight style={{ width: 18, height: 18 }} />}>Submit for review</Button>
            <Button variant="ghost" onClick={back}>Back</Button>
          </div>
        </>
      )}
    </StepFrame>
  );
}

/* ---------- Art 8(2) · Connection to Opalus (restricted-person self-flagging) ----------
   ECSP Art 8(2): a person connected to the platform (shareholder ≥20% / manager / director /
   employee / a person they control) may invest, but their participation must be published on the
   public register. We capture the connection, the relationship, and an explicit public-disclosure
   consent, and store it on the investor profile (investor.restrictedPerson). */
const RP_RELATIONSHIPS = [
  ['shareholder', 'Shareholder holding 20% or more of Opalus’s capital or voting rights'],
  ['manager', 'Manager or member of Opalus’s management body'],
  ['director', 'Director of Opalus'],
  ['employee', 'Employee of Opalus'],
  ['controlled', 'A person or entity controlled by one of the above'],
];
function ConnectionStep({ inv, update, onDone, onBack }) {
  const existing = inv.restrictedPerson;
  const [connected, setConnected] = useState(existing ? (existing.connected ? 'yes' : 'no') : null); // null | 'no' | 'yes'
  const [relationship, setRelationship] = useState(existing?.relationship || '');
  const [disclosureConsent, setDisclosureConsent] = useState(!!existing?.disclosureConsent);
  const canSave = connected === 'no' || (connected === 'yes' && !!relationship && disclosureConsent);

  const save = () => {
    if (!canSave) return;
    const declaredAt = new Date().toISOString();
    update((s) => {
      s.investor.restrictedPerson = connected === 'yes'
        ? { connected: true, relationship, disclosureConsent: true, declaredAt }
        : { connected: false, relationship: null, disclosureConsent: false, declaredAt };
    });
    onDone();
  };

  return (
    <StepFrame eyebrow="Step 3 · Connection to Opalus" title="Are you connected to Opalus?"
      sub="EU rules require us to ask whether you are connected to Opalus before you invest. Connected persons may still invest on the same terms as anyone else — we simply have to disclose their participation on our public register."
      footer={<><Button variant="primary" disabled={!canSave} onClick={save} trailingIcon={<OB_ICONS.IconlyRegularLightArrowRight style={{ width: 18, height: 18 }} />}>Save & continue</Button><Button variant="ghost" onClick={onBack}>Back to setup</Button></>}>
      <div style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', fontWeight: 600, color: 'var(--ink)', marginBottom: 12 }}>Are you a shareholder (20% or more), manager, director or employee of Opalus, or a person controlled by any of them?</div>
      <div className="opx-grid-2" style={{ gap: 12 }}>
        {[['no', 'No — I have no connection to Opalus'], ['yes', 'Yes — I am connected to Opalus']].map(([id, label]) => (
          <button key={id} type="button" onClick={() => { setConnected(id); if (id === 'no') { setRelationship(''); setDisclosureConsent(false); } }} style={{ cursor: 'pointer', textAlign: 'left', padding: '16px 18px', borderRadius: 'var(--radius-md)', border: `1.5px solid ${connected === id ? 'var(--bronze-500)' : 'var(--border-subtle)'}`, background: connected === id ? 'var(--cream-100)' : '#fff', fontFamily: 'var(--font-body)', fontSize: 14, fontWeight: 600, color: connected === id ? 'var(--bronze-700)' : 'var(--text-body)' }}>{label}</button>
        ))}
      </div>

      {connected === 'yes' && (
        <div style={{ marginTop: 22, display: 'flex', flexDirection: 'column', gap: 16 }}>
          <div>
            <div style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', fontWeight: 600, color: 'var(--ink)', marginBottom: 10 }}>What is your relationship to Opalus?</div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
              {RP_RELATIONSHIPS.map(([id, label]) => {
                const on = relationship === id;
                return (
                  <button key={id} type="button" onClick={() => setRelationship(id)} style={{ display: 'flex', alignItems: 'center', gap: 12, textAlign: 'left', cursor: 'pointer', padding: '13px 16px', borderRadius: 'var(--radius-md)', border: `1.5px solid ${on ? 'var(--bronze-500)' : 'var(--border-faint)'}`, background: on ? 'var(--cream-100)' : '#fff' }}>
                    <span style={{ width: 18, height: 18, borderRadius: '50%', border: `2px solid ${on ? 'var(--bronze-600)' : 'var(--border-strong)'}`, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flex: '0 0 auto' }}>{on && <span style={{ width: 9, height: 9, borderRadius: '50%', background: 'var(--bronze-600)' }} />}</span>
                    <span style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', color: 'var(--text-body)', lineHeight: 1.45 }}>{label}</span>
                  </button>
                );
              })}
            </div>
          </div>
          <window.Note tone="cream" title="Public-register disclosure (Art 8(2))">{window.OPALUS_COPY.art8_2}</window.Note>
          <window.AckBox checked={disclosureConsent} onChange={setDisclosureConsent}>I consent to my participation in projects being disclosed on Opalus’s public register, as required for persons connected to the platform.</window.AckBox>
        </div>
      )}
    </StepFrame>
  );
}

/* ---------- INV-06 · Categorisation ---------- */
function CategoryStep({ inv, update, onContinue, onApply, onBack }) {
  const [scrolledEnd, setScrolledEnd] = useState(false);
  const onScroll = (e) => { const el = e.target; if (el.scrollTop + el.clientHeight >= el.scrollHeight - 8) setScrolledEnd(true); };
  const sophCriteria = [
    'Income ≥ €60,000 / year, or a financial portfolio over €100,000',
    'At least 1 year in a professional financial-sector role, or 12 months in an executive position',
    '10+ significant capital-market transactions per quarter over the last 4 quarters',
  ];
  return (
    <StepFrame eyebrow="Step 4 · Investor category" title="Your investor category"
      sub="EU rules distinguish two categories of crowdfunding investor. Your category determines which protections apply to you.">
      <div className="opx-grid-2" style={{ gap: 16, alignItems: 'stretch' }}>
        <div style={{ border: '1.5px solid var(--bronze-500)', background: 'var(--cream-100)', borderRadius: 'var(--radius-lg)', padding: 22, display: 'flex', flexDirection: 'column', gap: 10 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <span style={{ fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 'var(--fs-lg)', color: 'var(--ink)' }}>Non-sophisticated investor</span>
            <Badge tone="bronze" size="sm">Recommended</Badge>
          </div>
          <p style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-xs)', color: 'var(--text-body)', lineHeight: 1.6, margin: 0 }}>The standard category. You keep the full set of protections: a knowledge assessment, a loss-capacity simulation, warnings for larger investments, and a 4-day reflection period on every investment.</p>
        </div>
        <div style={{ border: '1.5px solid var(--border-subtle)', background: '#fff', borderRadius: 'var(--radius-lg)', padding: 22, display: 'flex', flexDirection: 'column', gap: 10 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <span style={{ fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 'var(--fs-lg)', color: 'var(--ink)' }}>Sophisticated investor</span>
            <Badge tone="navy" size="sm">On request</Badge>
          </div>
          <p style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-xs)', color: 'var(--text-body)', lineHeight: 1.6, margin: 0 }}>For experienced investors who meet specific EU criteria. Fewer protections, fewer steps.</p>
          <div style={{ fontFamily: 'var(--font-body)', fontSize: 12, fontWeight: 600, color: 'var(--ink)', marginTop: 4 }}>Who qualifies as sophisticated</div>
          <ul style={{ margin: 0, padding: 0, listStyle: 'none', display: 'flex', flexDirection: 'column', gap: 5 }}>
            {sophCriteria.map((c) => <li key={c} style={{ fontFamily: 'var(--font-body)', fontSize: 11.5, color: 'var(--text-muted)', display: 'flex', gap: 7, lineHeight: 1.45 }}><span style={{ color: 'var(--bronze-500)' }}>•</span>{c}</li>)}
          </ul>
        </div>
      </div>

      {/* consequence statement — scroll-to-end required */}
      <div style={{ marginTop: 18, border: '1px solid #e7c98a', borderRadius: 'var(--radius-md)', overflow: 'hidden' }}>
        <div style={{ background: 'var(--warn-50)', padding: '10px 16px', fontFamily: 'var(--font-body)', fontSize: 12, fontWeight: 600, color: 'var(--warn-600)', display: 'flex', justifyContent: 'space-between' }}>
          <span>If you apply for sophisticated status</span>{scrolledEnd && <span style={{ color: 'var(--success-600)' }}>✓ Read</span>}
        </div>
        <div onScroll={onScroll} style={{ maxHeight: 92, overflowY: 'auto', padding: '14px 16px', background: '#fff', fontFamily: 'var(--font-body)', fontSize: 13, lineHeight: 1.6, color: 'var(--text-body)' }}>
          Sophisticated investors give up protections: no appropriateness assessment, no loss-bearing simulation, no warnings on larger investments, and <b style={{ color: 'var(--ink)' }}>no 4-day reflection period</b> — investments settle immediately and cannot be withdrawn.
          <div style={{ height: 28 }} />
          <span style={{ color: 'var(--text-muted)', fontSize: 12 }}>Scroll to the end to enable the sophisticated application.</span>
        </div>
      </div>

      <div style={{ display: 'flex', gap: 12, marginTop: 24, flexWrap: 'wrap' }}>
        <Button variant="primary" onClick={() => { update((s) => { s.investor.category = 'non-sophisticated'; }); onContinue(); }}>Continue as non-sophisticated</Button>
        <Button variant="outline" disabled={!scrolledEnd} onClick={onApply}>Apply for sophisticated status</Button>
        <Button variant="ghost" onClick={onBack}>Back to setup</Button>
      </div>
    </StepFrame>
  );
}

/* ---------- INV-07/08 · Sophisticated application ---------- */
const SOPH_CRITERIA = [
  'Annual gross income ≥ €60,000, or a financial instrument portfolio over €100,000',
  'At least one year in a professional position in the financial sector',
  'An average of 10 significant transactions per quarter over the last 4 quarters',
  'Hold or have held an executive position for at least 12 months',
];
function SophApplication({ update, onDone, onBack }) {
  const [step, setStep] = useState(0);
  const [entity, setEntity] = useState('individual');
  const [crit, setCrit] = useState([]);
  const [statementAck, setStatementAck] = useState(false);
  const [veracity, setVeracity] = useState(false);
  const [submitted, setSubmitted] = useState(false);
  // Sophisticated status is valid for 24 months from the application date (ECSP / BRD §5.2).
  const appliedAt = useRef(new Date().toISOString()).current;
  const validUntil = window.opalusAddMonths(appliedAt, 24);
  const minCrit = entity === 'individual' ? 2 : 1;
  const enough = crit.length >= minCrit;
  const toggle = (i) => setCrit((c) => c.includes(i) ? c.filter((x) => x !== i) : [...c, i]);

  if (submitted) {
    return (
      <StepFrame eyebrow="Sophisticated status" title="Application submitted"
        footer={<Button variant="primary" onClick={onDone} trailingIcon={<OB_ICONS.IconlyRegularLightArrowRight style={{ width: 18, height: 18 }} />}>Back to setup</Button>}>
        <window.Note tone="info" title="Under review">Compliance will review it, usually within 2 business days. Until it's approved, you can continue as a non-sophisticated investor.</window.Note>
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap', marginTop: 16, padding: '14px 16px', background: 'var(--surface-inset)', border: '1px solid var(--border-faint)', borderRadius: 'var(--radius-md)' }}>
          <div>
            <div style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-micro)', fontWeight: 600, letterSpacing: 'var(--ls-eyebrow)', textTransform: 'uppercase', color: 'var(--text-muted)' }}>If approved, valid until</div>
            <div style={{ fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 18, color: 'var(--ink)', marginTop: 2 }}>{window.opalusFmtDate(validUntil)}</div>
          </div>
          <span style={{ fontFamily: 'var(--font-body)', fontSize: 12, color: 'var(--text-muted)' }}>24 months from {window.opalusFmtDate(appliedAt)}</span>
        </div>
      </StepFrame>
    );
  }

  const steps = ['Applicant', 'Criteria', 'Consequences', 'Confirm'];
  return (
    <StepFrame eyebrow={`Sophisticated application · ${step + 1} of 4`} title="Apply for sophisticated status">
      <div style={{ marginBottom: 22 }}><window.Stepper steps={steps} current={step} /></div>

      {step === 0 && (
        <>
          <div style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', fontWeight: 600, color: 'var(--ink)', marginBottom: 12 }}>Are you applying as an individual or on behalf of a legal entity?</div>
          <div className="opx-grid-2" style={{ gap: 12 }}>
            {[['individual', 'Individual'], ['entity', 'Legal entity']].map(([id, label]) => (
              <button key={id} type="button" onClick={() => setEntity(id)} style={{ cursor: 'pointer', textAlign: 'left', padding: '16px 18px', borderRadius: 'var(--radius-md)', border: `1.5px solid ${entity === id ? 'var(--bronze-500)' : 'var(--border-subtle)'}`, background: entity === id ? 'var(--cream-100)' : '#fff', fontFamily: 'var(--font-body)', fontSize: 14, fontWeight: 600, color: entity === id ? 'var(--bronze-700)' : 'var(--text-body)' }}>{label}</button>
            ))}
          </div>
        </>
      )}

      {step === 1 && (
        <>
          <div style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', fontWeight: 600, color: 'var(--ink)', marginBottom: 6 }}>Which criteria do you meet?</div>
          <p style={{ fontFamily: 'var(--font-body)', fontSize: 12.5, color: 'var(--text-muted)', margin: '0 0 14px', lineHeight: 1.5 }}>Individuals must meet at least 2; legal entities at least 1. Attach evidence for each criterion you claim — for example a tax return, portfolio statement, employment letter, registry extract, or financial statements.</p>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            {SOPH_CRITERIA.map((c, i) => (
              <div key={i} style={{ border: `1px solid ${crit.includes(i) ? 'var(--bronze-400)' : 'var(--border-faint)'}`, borderRadius: 'var(--radius-md)', padding: '12px 14px', background: crit.includes(i) ? 'var(--cream-100)' : '#fff' }}>
                <window.AckBox checked={crit.includes(i)} onChange={() => toggle(i)}>{c}</window.AckBox>
                {crit.includes(i) && <button type="button" className="opx-link" style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 12, marginTop: 8, marginLeft: 31, padding: 0, display: 'inline-flex', alignItems: 'center', gap: 6 }}><OB_ICONS.IconlyRegularOutlineUpload style={{ width: 14, height: 14 }} /> Attach evidence</button>}
              </div>
            ))}
          </div>
          {!enough && <div style={{ marginTop: 12, fontFamily: 'var(--font-body)', fontSize: 12, color: 'var(--warn-600)' }}>Select and evidence at least {minCrit} criteria to continue.</div>}
        </>
      )}

      {step === 2 && (
        <>
          <div style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', fontWeight: 600, color: 'var(--ink)', marginBottom: 12 }}>Statement of consequences</div>
          <div style={{ background: 'var(--warn-50)', border: '1px solid #e7c98a', borderRadius: 'var(--radius-md)', padding: '16px 18px', fontFamily: 'var(--font-body)', fontSize: 13, lineHeight: 1.6, color: 'var(--text-strong)', marginBottom: 16 }}>
            I understand that as a sophisticated investor I will not receive the appropriateness assessment, the loss-bearing simulation, the warnings applicable to larger investments, or the 4-calendar-day reflection period, and that my investments will settle immediately and cannot be withdrawn.
          </div>
          <window.AckBox checked={statementAck} onChange={setStatementAck}>I have read and understand this statement of consequences.</window.AckBox>
        </>
      )}

      {step === 3 && (
        <window.AckBox checked={veracity} onChange={setVeracity}>I confirm that the information and evidence provided are true, accurate and complete, and that I remain responsible for their truthfulness.</window.AckBox>
      )}

      <div style={{ display: 'flex', gap: 12, marginTop: 28, flexWrap: 'wrap' }}>
        {step < 3
          ? <Button variant="primary" disabled={step === 1 && !enough || step === 2 && !statementAck} onClick={() => setStep(step + 1)} trailingIcon={<OB_ICONS.IconlyRegularLightArrowRight style={{ width: 18, height: 18 }} />}>Continue</Button>
          : <Button variant="primary" disabled={!veracity} onClick={() => { update((s) => { s.investor.category = 'non-sophisticated'; s.investor.sophApplication = { status: 'review', appliedAt, validUntil }; }); setSubmitted(true); }}>Submit application</Button>}
        <Button variant="ghost" onClick={() => step === 0 ? onBack() : setStep(step - 1)}>Back</Button>
      </div>
    </StepFrame>
  );
}

/* ---------- INV-09/10 · Knowledge assessment (one question at a time) ---------- */
function KnowledgeStep({ update, onDone, onBack }) {
  const total = KNOWLEDGE_Q.length;
  const [n, setN] = useState(0);
  const [answers, setAnswers] = useState({});
  const [phase, setPhase] = useState('quiz'); // quiz | result | warning
  const [warnAck, setWarnAck] = useState(false);
  const score = KNOWLEDGE_Q.reduce((acc, q, i) => acc + (answers[i] === q.correct ? 1 : 0), 0);
  const passed = score >= 4;
  const cur = KNOWLEDGE_Q[n];
  const answered = answers[n] !== undefined;

  const submit = () => { setPhase(passed ? 'result' : 'warning'); };

  if (phase === 'result') {
    return (
      <StepFrame eyebrow="Step 5 · Knowledge assessment" title="Assessment complete" sub="Your result is saved. You'll be asked to refresh it in 24 months."
        footer={<Button variant="primary" onClick={() => { update((s) => { s.investor.knowledgeTest = { passed: true, score, dueDate: '12 Apr 2028' }; }); onDone(); }} trailingIcon={<OB_ICONS.IconlyRegularLightArrowRight style={{ width: 18, height: 18 }} />}>Continue</Button>}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 16, padding: 20, borderRadius: 'var(--radius-lg)', background: 'var(--success-50)', border: '1px solid #a8e3b6' }}>
          <span style={{ width: 54, height: 54, borderRadius: '50%', background: 'var(--success-600)', color: '#fff', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontFamily: 'var(--font-display)', fontWeight: 700, fontSize: 20 }}>{score}/{total}</span>
          <div style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', color: 'var(--text-body)' }}>Knowledge requirement satisfied under ECSP Article 21.</div>
        </div>
      </StepFrame>
    );
  }
  if (phase === 'warning') {
    return (
      <StepFrame eyebrow="Step 5 · Knowledge assessment" title="A warning before you continue" sub={`You answered ${score} of ${total} correctly.`}>
        <div style={{ background: 'var(--danger-50)', border: '1px solid #f0b4b6', borderRadius: 'var(--radius-lg)', padding: '18px 20px' }}>
          <div style={{ fontFamily: 'var(--font-body)', fontSize: 14, lineHeight: 1.6, color: 'var(--text-strong)' }}>Based on your answers, the crowdfunding services provided through this platform may not be appropriate for you. <b style={{ color: 'var(--danger-600)' }}>You risk losing the entirety of the money invested.</b></div>
        </div>
        <div style={{ marginTop: 18 }}><window.AckBox checked={warnAck} onChange={setWarnAck}>I have read and understood this warning.</window.AckBox></div>
        <div style={{ display: 'flex', gap: 12, marginTop: 24, flexWrap: 'wrap' }}>
          <Button variant="primary" disabled={!warnAck} onClick={() => { update((s) => { s.investor.knowledgeTest = { passed: true, score, warned: true, dueDate: '12 Apr 2028' }; }); onDone(); }} trailingIcon={<OB_ICONS.IconlyRegularLightArrowRight style={{ width: 18, height: 18 }} />}>I understand and wish to continue</Button>
          <Button variant="outline" onClick={() => { setAnswers({}); setN(0); setPhase('quiz'); setWarnAck(false); }}>Retake the test</Button>
        </div>
      </StepFrame>
    );
  }
  return (
    <StepFrame eyebrow="Step 5 · Knowledge assessment" title="Knowledge assessment"
      sub="A short assessment of your understanding of crowdfunding investments, required for non-sophisticated investors. There's no time limit, you can save and come back, and you can retake it.">
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
        <span style={{ fontFamily: 'var(--font-body)', fontSize: 12.5, fontWeight: 600, color: 'var(--text-muted)' }}>Question {n + 1} of {total}</span>
        <span className="opx-link" style={{ fontSize: 12.5 }} onClick={onBack}>Save and continue later</span>
      </div>
      <div style={{ height: 6, borderRadius: 999, background: 'var(--gray-100)', overflow: 'hidden', marginBottom: 22 }}><div style={{ height: '100%', width: ((n + 1) / total * 100) + '%', background: 'var(--bronze-500)', borderRadius: 999, transition: 'width .2s' }} /></div>
      <div style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-body)', fontWeight: 600, color: 'var(--ink)', marginBottom: 14 }}>{cur.q}</div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
        {cur.a.map((opt, oi) => {
          const sel = answers[n] === oi;
          return (
            <button type="button" key={oi} onClick={() => setAnswers((a) => ({ ...a, [n]: oi }))} style={{ textAlign: 'left', cursor: 'pointer', display: 'flex', gap: 11, alignItems: 'center', padding: '13px 16px', borderRadius: 'var(--radius-md)', border: `1.5px solid ${sel ? 'var(--bronze-500)' : 'var(--border-faint)'}`, background: sel ? 'var(--cream-100)' : '#fff' }}>
              <span style={{ width: 20, height: 20, borderRadius: '50%', border: `2px solid ${sel ? 'var(--bronze-600)' : 'var(--border-strong)'}`, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flex: '0 0 auto' }}>{sel && <span style={{ width: 10, height: 10, borderRadius: '50%', background: 'var(--bronze-600)' }} />}</span>
              <span style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', color: 'var(--text-body)' }}>{opt}</span>
            </button>
          );
        })}
      </div>
      <div style={{ display: 'flex', gap: 12, marginTop: 26 }}>
        {n > 0 && <Button variant="ghost" onClick={() => setN(n - 1)}>Previous</Button>}
        {n < total - 1
          ? <Button variant="primary" disabled={!answered} onClick={() => setN(n + 1)}>Next</Button>
          : <Button variant="primary" disabled={Object.keys(answers).length < total} onClick={submit}>Submit answers</Button>}
      </div>
    </StepFrame>
  );
}

/* ---------- INV-11 · Loss-bearing simulation ---------- */
function LossSimStep({ inv, update, onDone, onBack }) {
  const [netWorth, setNetWorth] = useState(inv.netWorth || 150000);
  const [submitted, setSubmitted] = useState(false);
  const [ack, setAck] = useState(false);
  const capacity = Math.round(netWorth * 0.10);

  if (submitted) {
    return (
      <StepFrame eyebrow="Step 6 · Ability to bear losses" title="Your simulation is saved"
        footer={<><Button variant="primary" disabled={!ack} onClick={() => { update((s) => { s.investor.netWorth = netWorth; s.investor.lossSim = { capacity, planned: 0, acknowledged: true, date: '11 Jun 2026', dueDate: '11 Jun 2027' }; }); onDone(); }} trailingIcon={<OB_ICONS.IconlyRegularLightArrowRight style={{ width: 18, height: 18 }} />}>I understand — continue</Button><Button variant="ghost" onClick={() => setSubmitted(false)}>Adjust</Button></>}>
        <div style={{ background: 'var(--navy-950)', borderRadius: 'var(--radius-lg)', padding: 28, color: '#fff', marginBottom: 16 }}>
          <div style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-micro)', fontWeight: 600, letterSpacing: 'var(--ls-eyebrow)', textTransform: 'uppercase', color: 'var(--bronze-400)' }}>Your simulated ability to bear losses</div>
          <div style={{ fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 44, margin: '6px 0 0', letterSpacing: 'var(--ls-tight)' }}>{window.eur(capacity)}</div>
        </div>
        <p style={{ fontFamily: 'var(--font-body)', fontSize: 13, color: 'var(--text-body)', lineHeight: 1.6, margin: '0 0 12px' }}>If a single investment exceeds 10% of this amount, we'll show you an additional warning before you proceed. EU rules also advise against investing more than 10% of your net worth in crowdfunding overall.</p>
        <div style={{ fontFamily: 'var(--font-body)', fontSize: 12.5, color: 'var(--text-muted)', marginBottom: 16 }}>Unlike the public tool, your result is saved to your account and refreshed annually. Valid until 11 Jun 2027 — you can redo the simulation anytime, for example if your finances change.</div>
        <window.AckBox checked={ack} onChange={setAck}>I understand these investments put my capital at risk, and I am financially able to bear the loss of the amounts I choose to invest.</window.AckBox>
      </StepFrame>
    );
  }
  return (
    <StepFrame eyebrow="Step 6 · Ability to bear losses" title="Simulate your capacity for loss"
      sub="Under ECSP Article 21, we help you picture what a loss would mean before you invest. Tell us your net worth — excluding your annual income and the home you live in."
      footer={<><Button variant="primary" onClick={() => setSubmitted(true)} trailingIcon={<OB_ICONS.IconlyRegularLightArrowRight style={{ width: 18, height: 18 }} />}>See my result</Button><Button variant="ghost" onClick={onBack}>Back to setup</Button></>}>
      <window.Field label="Investable net worth (€)" hint="Excludes annual income and your principal residence.">
        <input className="opx-amount" style={{ fontSize: 22 }} type="number" value={netWorth} min={0} step={5000} onChange={(e) => setNetWorth(Math.max(0, +e.target.value || 0))} />
      </window.Field>
    </StepFrame>
  );
}

/* ---------- Public pre-registration loss-bearing calculator (logged-out, anonymous) ----------
   D-H1 / BP-048 / DM loss_bearing_simulations.source='public_tool' (no save-to-profile).
   Reuses the loss-sim inputs; output = 10% of net worth. Live exposure is gated behind a
   Bank of Lithuania pre-clearance flag (see PUBLIC_CALC_BOL_CLEARED below). */
/* ASSUMPTION (confirm Lithuanian counsel): the public loss calculator is BUILT but its live
   exposure is gated behind a "BoL pre-clearance" flag, defaulting to NOT-yet-cleared.
   — see Sync_Audit_Report open Q4 */
const PUBLIC_CALC_BOL_CLEARED = false;

function PublicCalculator() {
  const { go } = window.useOpalus();
  const [netWorth, setNetWorth] = useState(150000);
  const [commitments, setCommitments] = useState(0);
  const [permanent, setPermanent] = useState(true);
  const [seen, setSeen] = useState(false);
  const investable = Math.max(0, netWorth - commitments);
  const capacity = Math.round(investable * 0.10);

  return (
    <div style={{ minHeight: '100vh', background: 'var(--surface-page)' }}>
      <header style={{ height: 68, background: '#fff', borderBottom: '1px solid var(--border-faint)', display: 'flex', alignItems: 'center', padding: '0 28px', position: 'sticky', top: 0, zIndex: 5 }}>
        <Logo variant="navy" height={30} base="assets/logo" />
        <span style={{ marginLeft: 16, paddingLeft: 16, borderLeft: '1px solid var(--border-subtle)', fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', color: 'var(--text-body)' }}>Loss-bearing calculator</span>
        <span className="opx-link" style={{ marginLeft: 'auto', fontSize: 13 }} onClick={() => go('welcome')}>Back to sign in</span>
      </header>

      <div style={{ maxWidth: 720, margin: '0 auto', padding: '34px 24px 80px' }} className="opx-fade">
        <window.Eyebrow>Free tool · No account needed</window.Eyebrow>
        <h1 style={{ fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 30, color: 'var(--ink)', margin: '8px 0 8px', letterSpacing: 'var(--ls-tight)' }}>Could you bear a loss?</h1>
        <p style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', color: 'var(--text-body)', margin: '0 0 22px', maxWidth: 560, lineHeight: 1.6 }}>Before you ever invest in crowdfunding, picture what a loss would mean. This anonymous tool estimates a sensible ceiling — 10% of your net worth. Nothing you enter is saved.</p>

        {!PUBLIC_CALC_BOL_CLEARED ? (
          <window.Note tone="info" title="This tool is being prepared for public release">
            Our public loss-bearing calculator is awaiting pre-clearance from the Bank of Lithuania before it goes live. In the meantime, you can complete the full simulation once you <span className="opx-link" onClick={() => go('welcome')}>create an account</span>.
          </window.Note>
        ) : (
          <Card padding="lg" radius="xl" shadow="sm" style={{ padding: 32 }}>
            <window.Field label="Investable net worth (€)" hint="Exclude your annual income, the home you live in, and your pension.">
              <input className="opx-amount" style={{ fontSize: 22 }} type="number" value={netWorth} min={0} step={5000} onChange={(e) => setNetWorth(Math.max(0, +e.target.value || 0))} />
            </window.Field>
            <div style={{ marginTop: 16 }}>
              <window.Field label="Liquid assets committed elsewhere (€)" hint="Money you already owe or have set aside for other commitments.">
                <input className="opx-amount" style={{ fontSize: 22 }} type="number" value={commitments} min={0} step={1000} onChange={(e) => setCommitments(Math.max(0, +e.target.value || 0))} />
              </window.Field>
            </div>
            <div style={{ marginTop: 16 }}>
              <window.AckBox checked={permanent} onChange={setPermanent}>My income is reasonably permanent (stable employment or recurring income).</window.AckBox>
            </div>

            {seen && (
              <div style={{ background: 'var(--navy-950)', borderRadius: 'var(--radius-lg)', padding: 28, color: '#fff', margin: '20px 0 0' }}>
                <div style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-micro)', fontWeight: 600, letterSpacing: 'var(--ls-eyebrow)', textTransform: 'uppercase', color: 'var(--bronze-400)' }}>Your estimated capacity to bear losses</div>
                <div style={{ fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 44, margin: '6px 0 0', letterSpacing: 'var(--ls-tight)' }}>{window.eur(capacity)}</div>
                <p style={{ fontFamily: 'var(--font-body)', fontSize: 13, color: 'rgba(255,255,255,0.72)', lineHeight: 1.6, margin: '12px 0 0' }}>This is an anonymous estimate and is not saved. When you create an account, you'll complete the full simulation, which is recorded and refreshed annually.</p>
              </div>
            )}

            <div style={{ display: 'flex', gap: 12, marginTop: 22, flexWrap: 'wrap' }}>
              <Button variant="primary" onClick={() => setSeen(true)} trailingIcon={<OB_ICONS.IconlyRegularLightArrowRight style={{ width: 18, height: 18 }} />}>See my estimate</Button>
              {seen && <Button variant="outline" onClick={() => go('welcome')}>Create an account to invest</Button>}
            </div>
          </Card>
        )}
      </div>
    </div>
  );
}

/* ============================================================ PUBLIC REGISTER — Art 8(2) restricted-person investments ============================================================
   Public-facing (logged-out) register required by ECSP Art 8(2) 2nd subparagraph: persons connected
   to Opalus may invest only if their participation is disclosed on the website, identifying the
   specific projects, with identical treatment and no preferential information. Opalus itself never
   participates in any offer (Art 8(1)). Updated as positions change. */
function PublicRegisterScreen() {
  const { go } = window.useOpalus();
  const rows = [
    { project: 'Le Marais Heritage Apartments', role: 'Platform manager', since: 'Mar 2026', status: 'Open position' },
    { project: 'Canal Saint-Martin Lofts', role: 'Employee', since: 'May 2026', status: 'Open position' },
  ];
  return (
    <div style={{ minHeight: '100vh', background: 'var(--surface-page)' }}>
      <header style={{ height: 68, background: '#fff', borderBottom: '1px solid var(--border-faint)', display: 'flex', alignItems: 'center', padding: '0 28px', position: 'sticky', top: 0, zIndex: 5 }}>
        <Logo variant="navy" height={30} base="assets/logo" />
        <span style={{ marginLeft: 16, paddingLeft: 16, borderLeft: '1px solid var(--border-subtle)', fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', color: 'var(--text-body)' }}>Conflicts-of-interest register</span>
        <span className="opx-link" style={{ marginLeft: 'auto', fontSize: 13 }} onClick={() => go('welcome')}>Back to sign in</span>
      </header>

      <div style={{ maxWidth: 760, margin: '0 auto', padding: '34px 24px 80px' }} className="opx-fade">
        <window.Eyebrow>Public disclosure · No account needed</window.Eyebrow>
        <h1 style={{ fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 30, color: 'var(--ink)', margin: '8px 0 8px', letterSpacing: 'var(--ls-tight)' }}>Restricted-person investments</h1>
        <p style={{ fontFamily: 'var(--font-body)', fontSize: 'var(--fs-sm)', color: 'var(--text-body)', margin: '0 0 22px', maxWidth: 620, lineHeight: 1.6 }}>
          Under Article 8(2) of Regulation (EU) 2020/1503, Opalus shareholders holding 20% or more, managers, employees, and persons linked to them by control cannot be project owners on this platform. They may invest only if their participation is disclosed here, identifying the specific projects — on identical terms to every other investor and with no preferential information. Opalus itself never participates in any offer (Article 8(1)).
        </p>

        <Card padding="lg" radius="lg" shadow="sm" style={{ padding: 0, overflow: 'hidden' }}>
          <div style={{ display: 'grid', gridTemplateColumns: '1.6fr 1.1fr 0.7fr 0.9fr', gap: 14, padding: '13px 22px', background: 'var(--surface-inset)', borderBottom: '1px solid var(--border-faint)', fontFamily: 'var(--font-body)', fontSize: 11, letterSpacing: '0.05em', textTransform: 'uppercase', color: 'var(--text-muted)', fontWeight: 600 }}>
            <span>Project</span><span>Connection to Opalus</span><span>Since</span><span>Status</span>
          </div>
          {rows.map((r) => (
            <div key={r.project} style={{ display: 'grid', gridTemplateColumns: '1.6fr 1.1fr 0.7fr 0.9fr', gap: 14, padding: '15px 22px', alignItems: 'center', borderBottom: '1px solid var(--border-faint)' }}>
              <span style={{ fontFamily: 'var(--font-body)', fontSize: 13.5, fontWeight: 600, color: 'var(--text-strong)' }}>{r.project}</span>
              <span style={{ fontFamily: 'var(--font-body)', fontSize: 13, color: 'var(--text-body)' }}>{r.role}</span>
              <span style={{ fontFamily: 'var(--font-body)', fontSize: 12.5, color: 'var(--text-muted)' }}>{r.since}</span>
              <span style={{ fontFamily: 'var(--font-body)', fontSize: 12, fontWeight: 600, color: 'var(--bronze-700)', background: 'var(--cream-100)', padding: '4px 11px', borderRadius: 'var(--radius-pill)', width: 'fit-content' }}>{r.status}</span>
            </div>
          ))}
        </Card>

        <p style={{ fontFamily: 'var(--font-body)', fontSize: 12, color: 'var(--text-muted)', margin: '16px 0 0', lineHeight: 1.6 }}>
          This register is updated as positions change. Connected persons receive no early access, no separate fee schedule and no privileged information. Last updated 11 June 2026.
        </p>
      </div>
    </div>
  );
}

Object.assign(window, { Welcome, Onboarding, PublicCalculator, PasswordResetScreen, PublicRegisterScreen });
