// FOOTPASS v2 · Onboarding, actes 2 & 3 : compte, code, organisation, Veriff, reveal + entrée invité

// ═══════ ÉCRAN · INVITATION (entrée invité) ═══════
// Mécanique réelle : la structure génère un lien d'invitation ; la personne le
// colle ici pour finaliser son inscription. Coller → vérifier → invitation révélée.
function ObStepInvite({ ctx }) {
  const LINK = 'footpass.app/i/FP24-0871-117-0';
  const [phase, setPhase] = React.useState('paste'); // paste | checking | valid
  const [val, setVal] = React.useState('');
  const paste = () => { if (phase === 'paste' && !val) setVal(LINK); };
  const check = () => {
    if (!val || phase !== 'paste') return;
    setPhase('checking');
    setTimeout(() => setPhase('valid'), 1400);
  };
  const accept = () => {
    ctx.set('role', 'joueur'); ctx.set('roleChosen', true);
    ctx.set('plan', 'pro'); ctx.set('paidBy', 'FC Nantes');
    ctx.set('org', { name: 'FC Nantes', status: 'member' });
    ctx.next();
  };
  if (phase !== 'valid') {
    return (
      <div className="ob-step" data-screen-label="Onboarding · Invitation (coller le lien)">
        <div className="ob-body">
          <div className="ob-kicker accent">INVITATION</div>
          <h1 className="ob-h1">{TX('Rejoindre FOOTPASS®')}</h1>
          <p className="ob-sub">{TX('Collez votre lien d’invitation pour activer votre accès réservé.')}</p>
          <div className="ob-invpaste">
            <div className={`ob-input ob-invpaste-input ${val ? 'done' : ''}`} onClick={paste}>
              {val ? <span className="ob-invpaste-val">{val}</span> : <span className="ob-ph">{TX('Lien d’invitation FOOTPASS®')}</span>}
            </div>
            <button className="ob-invpaste-btn" onClick={paste} disabled={!!val}>{TX('Coller')}</button>
          </div>
          <p className="ob-invpaste-hint">{TX('Lien personnel, valable 72 h.')}</p>
        </div>
        <div className="ob-foot">
          <button className={`ob-cta ${val ? '' : 'disabled'}`} onClick={check}>
            {phase === 'checking' ? <span className="ob-spinner"></span> : TX('Continuer')}
          </button>
          <button className="ob-link" onClick={ctx.back}>{TX('Je n’ai pas reçu d’invitation')}</button>
        </div>
      </div>
    );
  }
  return (
    <div className="ob-step" data-screen-label="Onboarding · Invitation">
      <div className="ob-body">
        {/* Logo + nom de la structure, kicker authentification en dessous */}
        <div className="ob-invhead">
          <div className="ob-crest"><img src="assets/crest_nantes.png" alt="FC Nantes" /></div>
          <div className="ob-invhead-txt">
            <div className="ob-invhead-name">FC NANTES</div>
            <div className="ob-kicker accent">{TX('INVITATION OFFICIELLE')}<br />{TX('ACCÈS RÉSERVÉ')}</div>
          </div>
        </div>
        {/* Concis et scannable : qui invite, dans quel rôle, quand */}
        <div className="ob-inv-facts">
          <div className="ob-inv-row">
            <span className="ob-inv-lbl">{TX('Invité par')}</span>
            <span className="ob-inv-val"><b>Karim Benatia</b><em>{TX('Directeur sportif · FC Nantes')}</em></span>
          </div>
          <div className="ob-inv-row">
            <span className="ob-inv-lbl">{TX('Destinée à')}</span>
            <span className="ob-inv-val"><b>Alexandre Da Costa</b><em>{TX('Invitation personnelle, non transférable')}</em></span>
          </div>
          <div className="ob-inv-row">
            <span className="ob-inv-lbl">{TX('Votre rôle')}</span>
            <span className="ob-inv-val"><b>{TX('Joueur')}</b><em>{TX('FC Nantes · vestiaire pro')}</em></span>
          </div>
          <div className="ob-inv-row">
            <span className="ob-inv-lbl">{TX('Créée')}</span>
            <span className="ob-inv-val"><b>{TX('Hier, 18:42')}</b><em>{TX('expire dans 48 h')}</em></span>
          </div>
        </div>
        <div className="ob-invcode">
          <span className="ob-invcode-lbl">{TX("Code d'invitation")}</span>
          <span className="ob-invcode-val">FP24·0871·117·0</span>
          <span className="ob-invcode-meta">{TX('Émis par le registre FOOTPASS® du FC Nantes · usage unique')}</span>
        </div>
        <div className="ob-banner">
          <FPIcon.Shield s={13} />
          <span><b>{TX('PassPRO pris en charge par le club.')}</b><br />{TX('Aucun frais pour vous.')}</span>
        </div>
      </div>
      <div className="ob-foot">
        <button className="ob-cta" onClick={accept}>{TX("Accepter l'invitation")}</button>
        <button className="ob-link" onClick={ctx.back}>{TX('Pas maintenant')}</button>
      </div>
    </div>
  );
}

// ═══════ ÉCRAN 6 · CODE EMAIL ═══════
function ObStepCode({ ctx }) {
  const CODE = '482910';
  const [digits, setDigits] = React.useState('');
  const [chip, setChip] = React.useState(false);
  const [cool, setCool] = React.useState(0);
  React.useEffect(() => {
    if (cool <= 0) return;
    const t = setTimeout(() => setCool((c) => c - 1), 1000);
    return () => clearTimeout(t);
  }, [cool]);
  const resend = () => { if (cool > 0) return; setCool(30); setChip(false); setTimeout(() => setChip(true), 1400); };
  React.useEffect(() => {
    const t = setTimeout(() => setChip(true), 1000);
    return () => clearTimeout(t);
  }, []);
  React.useEffect(() => {
    if (digits.length === 6) {
      const t = setTimeout(ctx.next, 750);
      return () => clearTimeout(t);
    }
  }, [digits]);
  const press = (k) => {
    if (k === 'del') { setDigits((d) => d.slice(0, -1)); return; }
    setDigits((d) => (d.length >= 6 ? d : d + k));
  };
  const autofill = () => {
    setChip(false);
    let i = 0;
    const iv = setInterval(() => {
      i += 1; setDigits(CODE.slice(0, i));
      if (i >= 6) clearInterval(iv);
    }, 90);
  };
  const full = digits.length === 6;
  return (
    <div className="ob-step" data-screen-label="Onboarding · Code email">
      <div className="ob-body">
        <h1 className="ob-h1">{TX('Vérifiez votre email')}</h1>
        <p className="ob-sub">{TX('Code à 6 chiffres envoyé à ')}<strong>a.dacosta@gmail.com</strong>{TX('. Il expire dans 10 minutes.')}</p>
        <button className="ob-link inline" onClick={ctx.back}>{TX("Modifier l'email")}</button>
        <div className="ob-codes">
          {[0, 1, 2, 3, 4, 5].map((i) => (
            <span key={i} className={`ob-codebox ${full ? 'ok' : digits[i] ? 'filled' : ''}`}>{digits[i] || ''}</span>
          ))}
        </div>
        {chip && !full ? (
          <button className="ob-autofill" onClick={autofill}>
            <span>{TX('Depuis Mail · ')}<b>{CODE}</b></span>
            <span>{TX('Remplir')}</span>
          </button>
        ) : null}
        {full ? <div className="ob-banner"><OBI.Check c="var(--acc-bright)" /> <span><b>{TX('Email vérifié.')}</b></span></div> : null}
      </div>
      <div className="ob-foot">
        <div className="ob-pad">
          {['1', '2', '3', '4', '5', '6', '7', '8', '9', '', '0', 'del'].map((k, i) => (
            k === '' ? <span key={i} className="ob-key blank"></span> : (
              <button key={i} className="ob-key" onClick={() => press(k)}>
                {k === 'del' ? <FPIcon.Back c="currentColor" /> : k}
              </button>
            )
          ))}
        </div>
        <button className="ob-link" disabled={cool > 0} onClick={resend}>
          {cool > 0 ? (window.FP_LANG === 'en' ? `Code resent · new one in ${cool} s` : `Code renvoyé · nouveau dans ${cool} s`) : TX('Renvoyer le code')}
        </button>
      </div>
    </div>
  );
}

// ═══════ ÉCRAN 7 · ORGANISATION ═══════
function ObStepOrg({ ctx }) {
  const [mode, setMode] = React.useState(null); // null | 'join' | 'create'
  const [searched, setSearched] = React.useState(false);
  const [picked, setPicked] = React.useState(null);
  const [role, setRole] = React.useState(ctx.d.role);
  const [sent, setSent] = React.useState(false);
  const OB_ORG_DEF = { agent: 'Agence', juriste: 'Cabinet', recruteur: 'Scouting', arbitre: 'Fédération', entraineur: 'Académie', autre: 'Média' };
  const [orgType, setOrgType] = React.useState(OB_ORG_DEF[ctx.d.role] || 'Club');
  const [created, setCreated] = React.useState(false);
  const RESULTS = [
    { id: 'asm', crest: 'ASM', name: 'AS Monaco', meta: 'Club · Ligue 1 · 142 membres' },
    { id: 'mou', crest: 'MOU', name: 'AS Moulins', meta: 'Club · National 2 · 11 membres' },
  ];
  const send = () => { setSent(true); ctx.set('org', { name: picked.name, status: 'pending' }); };
  const create = () => { setCreated(true); ctx.set('org', { name: 'Riviera Talents', status: 'admin' }); };

  return (
    <div className="ob-step" data-screen-label="Onboarding · Organisation">
      <div className="ob-body">
        <h1 className="ob-h1">{TX('Votre vestiaire')}</h1>
        <p className="ob-sub">{TX('Le football est un sport collectif. Votre Pass prend vie dans une organisation.')}</p>

        {mode === null ? (
          <div className="ob-cards">
            <button className="ob-selcard" onClick={() => setMode('join')}>
              <span className="ob-selcard-icon"><OBI.Search /></span>
              <span className="ob-selcard-main">
                <span className="ob-selcard-title">{TX('Rejoindre une organisation')}</span>
                <div className="ob-selcard-desc">{TX('Club, agence ou cabinet déjà sur FOOTPASS')}</div>
              </span>
              <span className="ob-selcard-arrow"><OBI.Arrow /></span>
            </button>
            <button className="ob-selcard" onClick={() => setMode('create')}>
              <span className="ob-selcard-icon"><FPIcon.Plus /></span>
              <span className="ob-selcard-main">
                <span className="ob-selcard-title">{TX('Créer la mienne')}</span>
                <div className="ob-selcard-desc">{TX('Vous inviterez votre équipe ensuite')}</div>
              </span>
              <span className="ob-selcard-arrow"><OBI.Arrow /></span>
            </button>
          </div>
        ) : null}

        {mode === 'join' && !sent ? (
          <div>
            <div className="ob-fields">
              <ObField label="Rechercher" canned="AS Mo" placeholder="Nom du club, de l'agence…" onDone={() => setSearched(true)} />
            </div>
            {searched ? (
              <div className="ob-results">
                {RESULTS.map((r) => (
                  <button key={r.id} className={`ob-result ${picked && picked.id === r.id ? 'sel' : ''}`} onClick={() => setPicked(r)}>
                    <span className="ob-result-crest">{r.crest}</span>
                    <span>
                      <div className="ob-result-name">{r.name}</div>
                      <div className="ob-result-meta">{TX(r.meta)}</div>
                    </span>
                  </button>
                ))}
              </div>
            ) : null}
            {picked ? (
              <div>
                <div className="ob-subhead">{TX('Votre rôle chez ')}{picked.name}</div>
                <div className="ob-chips">
                  {OB_ROLES.map((r) => (
                    <button key={r.id} className={`ob-chip ${role === r.id ? 'on' : ''}`} onClick={() => setRole(r.id)}>{TX(r.label)}</button>
                  ))}
                </div>
              </div>
            ) : null}
          </div>
        ) : null}

        {mode === 'join' && sent ? (
          <div className="ob-banner">
            <OBI.Check c="var(--acc-bright)" />
            <span><b>{TX('Demande envoyée à ')}{picked.name}.</b>{TX(' Un admin doit valider votre rôle de ')}{TX(OB_ROLE_LABEL[role])}{TX(', on vous notifiera. Vous pouvez continuer.')}</span>
          </div>
        ) : null}

        {mode === 'create' && !created ? (
          <div>
            <div className="ob-fields">
              <ObField label="Nom de l'organisation" canned="Riviera Talents" placeholder="Ex. Riviera Talents" />
            </div>
            <div className="ob-subhead">{TX('Type')}</div>
            <div className="ob-chips">
              {(window.FP_ST_TYPES || [{ t: 'Club' }, { t: 'Agence' }, { t: 'Cabinet' }]).map((x) => (
                <button key={x.t} className={`ob-chip ${orgType === x.t ? 'on' : ''}`} onClick={() => setOrgType(x.t)}>{TX(x.t)}</button>
              ))}
            </div>
          </div>
        ) : null}

        {mode === 'create' && created ? (
          <div className="ob-banner">
            <OBI.Check c="var(--acc-bright)" />
            <span><b>{TX("Riviera Talents créée, vous en êtes l'admin.")}</b> {TX("Invitez votre équipe plus tard, depuis l'app.")}</span>
          </div>
        ) : null}
      </div>

      <div className="ob-foot">
        {mode === 'join' && picked && !sent ? <button className="ob-cta" onClick={send}>{TX('Envoyer la demande')}</button> : null}
        {mode === 'create' && !created ? <button className="ob-cta" onClick={create}>{TX('Créer mon organisation')}</button> : null}
        {sent || created ? <button className="ob-cta" onClick={ctx.next}>{TX('Continuer')}</button> : null}
        {mode !== null && !sent && !created
          ? <button className="ob-link" onClick={() => { setMode(null); setPicked(null); setSearched(false); }}>{TX('Retour aux options')}</button>
          : null}
        {mode === null ? <button className="ob-link" onClick={ctx.next}>{TX('Plus tard, je serai invité')}</button> : null}
      </div>
    </div>
  );
}

// ═══════ ÉCRAN 8 · VÉRIFICATION VERIFF ═══════
// Icône « non visible » · pierre angulaire du discours de confiance : seul le statut est public
const ObEyeOff = ({ s = 13, c = 'currentColor' }) => (
  <svg width={s} height={s} viewBox="0 0 16 16" fill="none" aria-hidden="true">
    <path d="M2.2 8s2.3-4.2 5.8-4.2c1 0 1.9.3 2.7.7M13.4 6.2c.2.3.4.6.4.6s-2.3 4.2-5.8 4.2c-.5 0-1-.1-1.4-.2" stroke={c} strokeWidth="1.3" strokeLinecap="round"></path>
    <path d="M6.6 6.6a2 2 0 002.8 2.8" stroke={c} strokeWidth="1.3" strokeLinecap="round"></path>
    <path d="M2.5 2.5l11 11" stroke={c} strokeWidth="1.3" strokeLinecap="round"></path>
  </svg>
);
function ObStepVerify({ ctx }) {
  const [stage, setStage] = React.useState('intro'); // intro | doc | nfc | selfie | done
  // Données réelles du porteur pour le passeport scanné
  const vSafe = (s) => s.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toUpperCase().replace(/[^A-Z0-9]+/g, '<');
  const vParts = (ctx.d.name || 'Alexandre Da Costa').trim().split(/\s+/);
  const vGiven = vParts[0] || 'ALEXANDRE';
  const vSurname = vParts.slice(1).join(' ') || 'DA COSTA';
  const vMrz = ('P<FRA' + vSafe(vSurname) + '<<' + vSafe(vGiven) + '<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<').slice(0, 30);
  const [pct, setPct] = React.useState(0);
  const start = () => {
    setStage('doc'); setPct(0);
    const iv = setInterval(() => {
      setPct((p) => {
        if (p >= 100) { clearInterval(iv); return 100; }
        return p + 4;
      });
    }, 90);
    setTimeout(() => setStage('nfc'), 2700);
    setTimeout(() => setStage('selfie'), 4900);
    // La vérification se conclut par le portail immersif (accès vérifié + sceau crypto)
    setTimeout(() => { setStage('done'); ctx.next(); }, 7300);
  };
  return (
    <div className="ob-step" data-screen-label="Onboarding · Vérification d'identité">
      {stage === 'intro' ? (
        <div className="ob-body">
          <div className="ob-kicker accent">CERTIFICATION · PASS ID</div>
          <h1 className="ob-h1">{TX('Vérifiez votre identité')}</h1>
          <p className="ob-sub">{TX('Pour protéger les échanges sur FOOTPASS®, ')}<strong>{TX('chaque membre doit obtenir un Pass ID vérifié')}</strong>.</p>
          {/* Pierre angulaire de confiance : FOOTPASS ne montre jamais les documents, seulement le statut */}
          <div className="ob-privacy">
            <span className="ob-privacy-ic"><ObEyeOff s={15} /></span>
            <span className="ob-privacy-txt">{TX('Vos documents ne sont ')}<b>{TX('jamais visibles')}</b>{TX('. Seul votre ')}<b>{TX('Pass ID vérifié')}</b>{TX(' apparaît auprès des autres membres.')}</span>
          </div>
          {/* Parcours en 3 étapes reliées */}
          <div className="ob-vtl">
            {[
              { ic: <FPIcon.Doc s={15} />, title: 'Document d’identité', desc: 'Passeport ou carte d’identité' },
              { ic: <FPIcon.User s={17} />, title: 'Scan du visage', desc: 'Vérification rapide par caméra' },
              { ic: <OBI.Chip />, title: 'Lecture NFC', desc: 'Validation sécurisée du document' },
            ].map((s, i, arr) => (
              <div key={s.title} className="ob-vtl-item">
                <span className="ob-vtl-rail">
                  <i className="ob-vtl-dot">{s.ic}</i>
                  {i < arr.length - 1 ? <i className="ob-vtl-line"></i> : null}
                </span>
                <span className="ob-vtl-main">
                  <span className="ob-vtl-head"><b>{TX(s.title)}</b></span>
                  <span className="ob-vtl-desc">{TX(s.desc)}</span>
                </span>
              </div>
            ))}
          </div>
          {/* Le résultat, concret : un seul parcours · anneau inactif → coche vérifiée */}
          <div className="ob-voutcome">
            <div className="ob-voutcome-top">
              <span className="ob-voutcome-title">{TX('Votre Pass ID')}</span>
            </div>
            <div className="ob-voutcome-rings">
              <i className="ob-vring from"></i>
              <span className="ob-vline"></span>
              <i className="ob-vring to"><OBI.Check s={12} c="currentColor" /></i>
            </div>
            <div className="ob-voutcome-labels">
              <span className="ob-vlabel">{TX("Inactif aujourd'hui")}</span>
              <span className="ob-vlabel on">{TX('Vérifié')}</span>
            </div>
          </div>
          {/* Trois garanties · comble le vide tout en rassurant */}
          <div className="ob-vchips">
            <span className="ob-vchip"><ObEyeOff s={12} /> {TX('Privé')}</span>
            <span className="ob-vchip"><OBI.Lock s={12} /> {TX('Sécurisé')}</span>
            <span className="ob-vchip"><OBI.Check s={12} /> {TX('Une seule fois')}</span>
          </div>
        </div>
      ) : null}

      {stage === 'doc' || stage === 'nfc' || stage === 'selfie' ? (
        <div className="ob-body" style={{ display: 'flex', flexDirection: 'column' }}>
          <div className="ob-scanzone">
            {stage === 'doc' ? (
              <div className="ob-scan">
                <span className="ob-scan-corner tl"></span><span className="ob-scan-corner tr"></span>
                <span className="ob-scan-corner bl"></span><span className="ob-scan-corner br"></span>
                {/* Vrai passeport dans le cadre : photo, identité du porteur, MRZ */}
                <span className="ob-scan-pass">
                  <span className="ob-pass-band">PASSEPORT · PASSPORT</span>
                  <span className="ob-pass-row">
                    <img src={LUCAS_PHOTO} alt="" />
                    <span className="ob-pass-txt">
                      <i>Nom · Surname</i>
                      <b>{vSurname.toUpperCase()}</b>
                      <i>Prénom · Given name</i>
                      <b>{vGiven}</b>
                    </span>
                  </span>
                  <span className="ob-pass-mrz">{vMrz}</span>
                </span>
                <span className="sweep"></span>
              </div>
            ) : null}
            {stage === 'nfc' ? (
              <div className="ob-scan nfc">
                <span className="ob-nfc-glow"></span>
                <span className="ob-nfc-ring r1"></span>
                <span className="ob-nfc-ring r2"></span>
                <span className="ob-nfc-orbit"></span>
                <span className="ob-nfc-chip"><OBI.Chip s={26} /></span>
              </div>
            ) : null}
            {stage === 'selfie' ? (
              <div className="ob-scan selfie">
                {/* Flux caméra réel : visage cadré dans l'ovale de guidage */}
                <img className="ob-selfie-feed" src={LUCAS_PHOTO} alt="" />
                <span className="ob-selfie-oval"></span>
                <span className="sweep"></span>
              </div>
            ) : null}
            <div className="ob-scan-label">{stage === 'doc' ? TX('Scan du passeport…') : stage === 'nfc' ? TX('Approchez le document du téléphone…') : TX('Regardez l’objectif…')}</div>
            <div className="ob-scan-pct">{stage === 'doc' ? `${Math.min(pct, 100)} %` : stage === 'nfc' ? TX('LECTURE NFC · PUCE AUTHENTIFIÉE') : TX('ANALYSE EN COURS')}</div>
            {/* Journal de sécurité en direct · les contrôles cryptographiques défilent */}
            <div className="ob-seclog" key={stage}>
              {(stage === 'doc'
                ? ['MRZ détectée · ICAO 9303', 'Clés de contrôle… valides', 'Hologramme · authentique']
                : stage === 'nfc'
                  ? ['Canal sécurisé BAC… établi', 'Certificat AA · puce authentique', 'Signature SOD… vérifiée']
                  : ['Détection de vivacité… OK', 'Visage ↔ document · correspondance', 'Score biométrique · 99,2 %']
              ).map((line) => (
                <span key={line}><OBI.Check s={10} c="currentColor" /> {TX(line)}</span>
              ))}
            </div>
          </div>
          <div className="ob-note" style={{ paddingBottom: 8 }}><OBI.Lock /> {TX('Contrôle sécurisé. Vos documents restent privés.')}</div>
        </div>
      ) : null}

      {/* L'état 'done' n'affiche rien ici : le portail immersif (rendu par OnboardingFlow) prend le relais */}

      <div className="ob-foot">
        {stage === 'intro' ? (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 11 }}>
            <button className="ob-cta" onClick={start}>{TX('Créer mon Pass ID vérifié')}</button>
            <div className="ob-note"><OBI.Lock /> {TX('Contrôle chiffré · conforme RGPD & nLPD. Vos documents sont supprimés après vérification.')}</div>
          </div>
        ) : null}
      </div>
    </div>
  );
}

// ═══════ ÉCRAN 9 · REVEAL ═══════
function ObStepReveal({ ctx }) {
  const [stage, setStage] = React.useState('gen'); // gen | card
  const [msg, setMsg] = React.useState('Chiffrement des clés…');
  React.useEffect(() => {
    const msgs = ['Chiffrement des clés…', 'Autorisation sécurisée…', 'Émission du Pass…'];
    let i = 0;
    const iv = setInterval(() => { i += 1; if (i < msgs.length) setMsg(msgs[i]); }, 600);
    const t = setTimeout(() => setStage('card'), 1900);
    return () => { clearInterval(iv); clearTimeout(t); };
  }, []);
  const d = ctx.d;
  const firstName = (d.name || '').trim().split(/\s+/)[0] || null;
  const orgName = d.org ? d.org.name : null;
  const roleLabel = d.roleCustom || OB_ROLE_LABEL[d.role];
  // Partage · la victoire se publie : chaque partage embarque le lien d'invitation personnel
  const INVITE = 'footpass.app/i/' + (firstName || 'PASS').slice(0, 4).toUpperCase() + '-2480';
  const [share, setShare] = React.useState(false);
  const [shared, setShared] = React.useState(null);
  const [wallet, setWallet] = React.useState(false);
  const doShare = (network) => {
    if (shared) return;
    setShared(network === 'copy' ? TX('Lien copié · à coller où vous voulez') : (window.FP_LANG === 'en' ? `Draft ready in ${network} · your invitation link is included` : `Brouillon prêt dans ${network} · votre lien d'invitation est inclus`));
    try { if (network === 'copy' && navigator.clipboard) navigator.clipboard.writeText('https://' + INVITE); } catch (e) { /* ignore */ }
    setTimeout(() => { setShare(false); setShared(null); }, 1700);
  };
  // Confettis or/vert · une seule salve, à l'apparition de la carte
  const confetti = Array.from({ length: 22 }).map((_, i) => {
    const a = (i / 22) * Math.PI * 2;
    const r = 80 + (i % 5) * 26;
    return (
      <i key={i} style={{
        '--dx': `${Math.round(Math.cos(a) * r)}px`,
        '--dy': `${Math.round(Math.sin(a) * r * 0.8 - 40)}px`,
        animationDelay: `${(i % 11) * 26}ms`,
      }}></i>
    );
  });
  return (
    <div className="ob-step" data-screen-label="Onboarding · Pass ID actif">
      {stage === 'gen' ? (
        <div className="ob-gen">
          <div className="ob-genring"></div>
          <div className="ob-genlabel">{TX(msg)}</div>
        </div>
      ) : (
        <div className="ob-body ob-reveal-zone">
          <div className="ob-confetti" aria-hidden="true">{confetti}</div>
          <div>
            <h1 className="ob-h1">{firstName ? (window.FP_LANG === 'en' ? `Welcome, ${firstName}` : `Bienvenue, ${firstName}`) : TX('Bienvenue')}</h1>
          </div>
          <div className="ob-cardwrap">
            <div>
              <div className="ob-kicker accent" style={{ marginBottom: 12 }}>{TX('VOTRE PASS ID')}</div>
              <div className="ob-reveal-card">
                <ObPassCard name={d.name} role={roleLabel} org={orgName} photo={d.passPhoto || d.photo} verified gold shimmer />
              </div>
              {/* Invitation discrète à publier la victoire */}
              <button className="ob-share-cta" onClick={() => setShare(true)}>
                <svg width="13" height="14" viewBox="0 0 13 14" fill="none" aria-hidden="true">
                  <path d="M6.5 1.2v8M3.2 4L6.5 1.2 9.8 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"></path>
                  <path d="M1.8 7.5v4a1.3 1.3 0 001.3 1.3h6.8a1.3 1.3 0 001.3-1.3v-4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"></path>
                </svg>
                {TX('Annoncer ma certification')}
              </button>
              {/* Rituel de possession (audit onboarding P2-3) · le passeport qu'on garde sur soi */}
              <button className="ob-share-cta" disabled={wallet} onClick={() => setWallet(true)} style={wallet ? { opacity: 0.75 } : null}>
                {wallet ? <OBI.Check s={12} c="currentColor" /> : (
                  <svg width="13" height="13" viewBox="0 0 14 14" fill="none" aria-hidden="true">
                    <rect x="1.2" y="2.4" width="11.6" height="9.2" rx="1.6" stroke="currentColor" strokeWidth="1.4"></rect>
                    <path d="M1.2 5.4h11.6" stroke="currentColor" strokeWidth="1.4"></path>
                  </svg>
                )}
                {wallet ? TX('Dans Apple Wallet') : TX('Ajouter à Apple Wallet')}
              </button>
            </div>
          </div>
        </div>
      )}
      {stage === 'card' ? (
        <div className="ob-foot">
          <button className="ob-cta" onClick={ctx.finish}>{TX('Entrer dans FOOTPASS')}</button>
        </div>
      ) : null}
      {share ? (
        <div className="ob-sheetwrap">
          <div className="ob-scrim" onClick={() => setShare(false)}></div>
          <div className="ob-sheet">
            <span className="ob-grab"></span>
            <div className="ob-sheet-title">{TX('Annoncez votre certification')}</div>
            {/* La vraie carte Pass ID · numéro et MRZ confidentiels masqués */}
            <div className="ob-share-card">
              <ObPassCard name={d.name} role={roleLabel} org={orgName} photo={d.passPhoto || d.photo} verified gold redact />
              <div className="ob-share-card-cap"><OBI.Check s={10} c="currentColor" /> {TX('PASS ID CERTIFIÉ ·')} {INVITE}</div>
            </div>
            <div className="ob-share-pills">
              {['Instagram', 'X', 'LinkedIn', 'WhatsApp'].map((n) => (
                <button key={n} onClick={() => doShare(n)}>{n}</button>
              ))}
              <button className="wide" onClick={() => doShare('copy')}>{TX("Copier mon lien d'invitation")}</button>
            </div>
            {shared
              ? <div className="ob-share-done"><OBI.Check s={11} c="currentColor" /> {shared}</div>
              : <div className="ob-share-note">{TX('Chaque inscription via votre lien renforce votre réseau vérifié.')}</div>}
          </div>
        </div>
      ) : null}
    </div>
  );
}

Object.assign(window, {
  ObStepInvite, ObStepCode, ObStepOrg, ObStepVerify, ObStepReveal,
});
