// FOOTPASS v2 · Onboarding : composants partagés, compte, rôle, plan, paiement
// Tarifs et positionnement alignés sur footpass.app (PassSTART / PassPRO, CHF, essai 10 jours)

// ── Mini-icônes propres à l'onboarding (formes simples uniquement) ──
const OBI = {
  Bubble: ({ s = 13, c = 'currentColor' }) => (
    <svg width={s} height={s} viewBox="0 0 14 14" fill="none">
      <path d="M7 1.7c3 0 5.3 2 5.3 4.7S10 11.1 7 11.1c-.55 0-1.08-.07-1.58-.2L2.7 12.3l.55-2.2C2.3 9.2 1.7 8 1.7 6.4 1.7 3.7 4 1.7 7 1.7z" stroke={c} strokeWidth="1.4" strokeLinejoin="round"></path>
    </svg>
  ),
  Check: ({ s = 13, c = 'currentColor' }) => (
    <svg width={s} height={s} viewBox="0 0 14 14" fill="none">
      <path d="M2.5 7.5L5.5 10.5L11.5 3.5" stroke={c} strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"></path>
    </svg>
  ),
  Lock: ({ s = 12, c = 'currentColor' }) => (
    <svg width={s} height={s} viewBox="0 0 14 14" fill="none">
      <rect x="2.2" y="6" width="9.6" height="6.5" rx="1.8" stroke={c} strokeWidth="1.4"></rect>
      <path d="M4.5 6V4.4a2.5 2.5 0 015 0V6" stroke={c} strokeWidth="1.4"></path>
    </svg>
  ),
  Search: ({ s = 16, c = 'currentColor' }) => (
    <svg width={s} height={s} viewBox="0 0 16 16" fill="none">
      <circle cx="7" cy="7" r="4.6" stroke={c} strokeWidth="1.6"></circle>
      <path d="M10.6 10.6L14 14" stroke={c} strokeWidth="1.6" strokeLinecap="round"></path>
    </svg>
  ),
  Case: ({ s = 17, c = 'currentColor' }) => (
    <svg width={s} height={s} viewBox="0 0 18 18" fill="none">
      <rect x="1.8" y="5" width="14.4" height="10.2" rx="2.4" stroke={c} strokeWidth="1.5"></rect>
      <path d="M6 5V3.8A1.8 1.8 0 017.8 2h2.4A1.8 1.8 0 0112 3.8V5M1.8 9h14.4" stroke={c} strokeWidth="1.5"></path>
    </svg>
  ),
  Board: ({ s = 17, c = 'currentColor' }) => (
    <svg width={s} height={s} viewBox="0 0 18 18" fill="none">
      <rect x="3" y="2.6" width="12" height="13.6" rx="2.2" stroke={c} strokeWidth="1.5"></rect>
      <path d="M6.4 7h5.2M6.4 10h5.2M6.4 13h3" stroke={c} strokeWidth="1.5" strokeLinecap="round"></path>
    </svg>
  ),
  Building: ({ s = 17, c = 'currentColor' }) => (
    <svg width={s} height={s} viewBox="0 0 18 18" fill="none">
      <rect x="3.2" y="4.6" width="11.6" height="10.8" rx="1.8" stroke={c} strokeWidth="1.5"></rect>
      <path d="M3.2 4.6L9 1.8l5.8 2.8" stroke={c} strokeWidth="1.5" strokeLinejoin="round"></path>
      <path d="M6.4 8h1.6M10 8h1.6M6.4 11h1.6M10 11h1.6" stroke={c} strokeWidth="1.5" strokeLinecap="round"></path>
    </svg>
  ),
  CardRef: ({ s = 16, c = 'currentColor' }) => (
    <svg width={s} height={s} viewBox="0 0 16 16" fill="none">
      <rect x="4.6" y="1.6" width="8" height="11.6" rx="1.6" transform="rotate(8 8 8)" stroke={c} strokeWidth="1.5"></rect>
    </svg>
  ),
  Arrow: ({ s = 12, c = 'currentColor' }) => (
    <svg width={s} height={s} viewBox="0 0 12 12" fill="none">
      <path d="M2 6h8M6.5 2.5L10 6l-3.5 9.5" stroke={c} strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" style={{ display: 'none' }}></path>
      <path d="M2 6h8M6.5 2.5L10 6l-3.5 3.5" stroke={c} strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"></path>
    </svg>
  ),
  Chip: ({ s = 16, c = 'currentColor' }) => (
    <svg width={s} height={s} viewBox="0 0 16 16" fill="none">
      <rect x="3" y="3" width="10" height="10" rx="2" stroke={c} strokeWidth="1.4"></rect>
      <path d="M6 1v2M10 1v2M6 13v2M10 13v2M1 6h2M1 10h2M13 6h2M13 10h2" stroke={c} strokeWidth="1.4" strokeLinecap="round"></path>
    </svg>
  ),
};

// ── Rôles : toute la communauté du football, pas seulement le pro ──
const OB_ROLES = [
  { id: 'joueur', label: 'Joueur', desc: 'Pro, amateur ou en formation', rec: 'start', icon: 'User' },
  { id: 'agent', label: 'Agent', desc: 'Représentation de joueurs', rec: 'pro', icon: 'Case' },
  { id: 'entraineur', label: 'Entraîneur', desc: 'Coach, éducateur, formateur', rec: 'start', icon: 'Board' },
  { id: 'recruteur', label: 'Recruteur', desc: 'Détection de talents', rec: 'pro', icon: 'Search' },
  { id: 'staff', label: 'Dirigeant & staff', desc: 'Club, académie, association', rec: 'start', icon: 'Building' },
  { id: 'arbitre', label: 'Arbitre', desc: 'Officiel de match', rec: 'start', icon: 'CardRef' },
  { id: 'juriste', label: 'Juriste', desc: 'Droit du sport, contrats', rec: 'pro', icon: 'Doc' },
  { id: 'autre', label: 'Autre rôle', desc: 'Médias, bénévoles, famille…', rec: 'start', icon: 'Globe' },
];
const OB_ROLE_LABEL = Object.fromEntries(OB_ROLES.map((r) => [r.id, r.label]));
// Le Pass ID est en anglais (langue des documents de voyage) · la carte ne doit JAMAIS
// afficher de français : on traduit par id ET par libellé, insensible à la casse/accents.
const OB_ROLE_EN = {
  joueur: 'Player', agent: 'Agent', entraineur: 'Coach', recruteur: 'Scout',
  staff: 'Club staff', arbitre: 'Referee', juriste: 'Lawyer', autre: 'Member',
  // libellés FR → EN (au cas où le libellé est passé à la place de l'id)
  'joueur ': 'Player', 'entraineur ': 'Coach', 'dirigeant & staff': 'Club staff',
  'autre role': 'Member',
};
// Normalise (minuscules, sans accents) puis traduit ; fallback propre si inconnu.
const obRoleEn = (r) => {
  if (!r) return 'Pending';
  const key = r.toString().trim().toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '');
  return OB_ROLE_EN[key] || r;
};

function ObRoleIcon({ icon }) {
  if (icon === 'User') return <FPIcon.User s={18} />;
  if (icon === 'Doc') return <FPIcon.Doc s={14} />;
  if (icon === 'Globe') return <FPIcon.Globe s={16} />;
  const C = OBI[icon];
  return <C />;
}

// ── Plans : tarifs réels footpass.app/tarifs ──
const OB_PLANS = [
  { id: 'start', name: 'PassSTART', m: '9,99', y: '99,99', ym: '8,33', save: '−17 %',
    tag: 'Pour les acteurs du football amateur',
    feats: ['Pass ID', 'Messagerie sécurisée', 'Coffre-fort', 'Support standard', 'iOS et Android'] },
  { id: 'pro', name: 'PassPRO', m: '29,99', y: '239,99', ym: '19,99', save: '−33 %',
    tag: 'Pour les acteurs du football professionnel',
    feats: ['Tout PassSTART', 'Lié à une structure : 19,99 CHF/mois', 'Autorisations · représentation & délégations', 'Signature & historique certifié', 'Salons d’appel', 'Accès Desktop', 'Support prioritaire'] },
];
const OB_PLAN_BY_ID = Object.fromEntries(OB_PLANS.map((p) => [p.id, p]));

function obFrDate(plusDays) {
  // Audit 360 P0-4 · une seule source de temps : le calendrier canonique du prototype (18 juin 2026)
  const C = window.FP_CAL;
  const d = C && C.todayD ? new Date(2026, 5, C.todayD) : new Date();
  d.setDate(d.getDate() + plusDays);
  return d.toLocaleDateString(window.FP_LANG === 'en' ? 'en-US' : 'fr-FR', { day: 'numeric', month: 'long' });
}

// ── Carte Pass ID · le passeport numérique du football ──
// Codes visuels universels du passeport (norme ICAO) : libellés bilingues FR/EN,
// zone portrait, symbole biométrique, sceau holographique, bande MRZ lisible machine.
const obMrzSafe = (s) => s.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toUpperCase().replace(/[^A-Z0-9]+/g, '<');

// ── Logique interne du N° Pass ───────────────────────────────────
// 12 chiffres structurés : AA FF RRRR SSS C
//   AA   · année d'émission (24 = 2024)
//   FF   · fédération de rattachement (80 = registre Europe/UEFA)
//   RRRR · registre de l'organisation (1196 = AS Monaco)
//   SSS  · séquence du membre dans ce registre
//   C    · clé de Luhn sur les 11 chiffres : tout numéro altéré à la main
//          casse la clé et se détecte hors-ligne, sans appel serveur.
// Les codes d'invitation suivent le même schéma (registre + séquence + clé),
// émis et révoqués par le registre du club, à usage unique.
const obLuhn = (payload) => {
  let sum = 0;
  const ds = payload.split('').reverse();
  for (let i = 0; i < ds.length; i++) {
    let d = +ds[i];
    if (i % 2 === 0) { d *= 2; if (d > 9) d -= 9; }
    sum += d;
  }
  return String((10 - (sum % 10)) % 10);
};
const obPassNumber = ({ year = '24', fed = '80', org = '1196', seq = '004' } = {}) => {
  const payload = year + fed + org + seq;
  return (payload + obLuhn(payload)).replace(/(\d{4})(\d{4})(\d{4})/, '$1 $2 $3');
};
// Clé ICAO 9303 (pondération 7-3-1, A=10…Z=35, '<'=0) · celle des vrais passeports.
const obIcaoCheck = (s) => {
  const w = [7, 3, 1];
  let sum = 0;
  for (let i = 0; i < s.length; i++) {
    const c = s[i];
    sum += (c === '<' ? 0 : c >= '0' && c <= '9' ? +c : c.charCodeAt(0) - 55) * w[i % 3];
  }
  return String(sum % 10);
};
// Puce de contact dorée · comme sur une carte bancaire / un passeport biométrique
const ObIcaoChip = () => (
  <svg className="ob-card-icao" width="21" height="15" viewBox="0 0 22 16" aria-label="Puce biométrique">
    <defs>
      <linearGradient id="obchipg" x1="0" y1="0" x2="1" y2="1">
        <stop offset="0" stopColor="#E8C96A"></stop>
        <stop offset="1" stopColor="#A8853A"></stop>
      </linearGradient>
    </defs>
    <rect x="0.5" y="0.5" width="21" height="15" rx="3" fill="url(#obchipg)" stroke="rgba(0,0,0,0.4)"></rect>
    <path d="M7.5 0.5v3.7a2.6 2.6 0 002.6 2.6h1.8a2.6 2.6 0 012.6 2.6v6.1M14.5 0.5v3.7M7.5 15.5v-6.1M0.5 5.2h7M14.5 5.2h7M0.5 10.8h7.2M14.3 10.8h7.2" stroke="rgba(55,38,8,0.5)" strokeWidth="1" fill="none"></path>
  </svg>
);

const ObClubCrest = ({ org }) => {
  const ini = (org || '').trim().split(/\s+/).map((w) => (w === w.toUpperCase() && w.length <= 3 ? w : w[0])).join('').slice(0, 3).toUpperCase();
  return (
    <svg width="11" height="13" viewBox="0 0 12 14" aria-hidden="true">
      <path d="M6 0.9L11 2.8v4c0 3.3-2.1 5.4-5 6.5-2.9-1.1-5-3.2-5-6.5v-4L6 0.9z" fill="rgba(212,175,55,0.12)" stroke="currentColor" strokeWidth="1"></path>
      <text x="6" y="8.7" textAnchor="middle" fontSize="4" fontWeight="800" fill="currentColor" fontFamily="Inter, sans-serif">{ini}</text>
    </svg>
  );
};

// Fond guilloché · entrelacs fins type billet de banque, quasi impossibles à reproduire
const ObGuilloche = () => (
  <svg className="ob-card-guilloche" viewBox="0 0 320 220" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
    <g fill="none" stroke="currentColor" strokeWidth="0.5">
      {Array.from({ length: 14 }).map((_, i) => (
        <ellipse key={i} cx="160" cy="110" rx="152" ry="44" transform={`rotate(${(i * 180) / 14} 160 110)`}></ellipse>
      ))}
    </g>
  </svg>
);

function ObPassCard({ name, role, org, orgMissing, agent, agentLabel = 'Agent', number = obPassNumber(), verified = false, shimmer = false, float = false, gold = false, photo, initials, clubLabel = 'Club', tier, tierKind, redact = false }) {
  const parts = (name || '').trim().split(/\s+/).filter(Boolean);
  const given = obMrzSafe(parts[0] || 'VOTRE');
  const surname = obMrzSafe(parts.length > 1 ? parts.slice(1).join(' ') : 'NOM');
  const pad = '<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<';
  // Nom affiché : prénom inchangé, nom de famille en MAJUSCULES (convention pièce d'identité)
  const _np = (name || '').trim().split(/\s+/).filter(Boolean);
  const displayName = _np.length > 1 ? _np[0] + ' ' + _np.slice(1).join(' ').toLocaleUpperCase('fr') : name;
  // Partage public : numéro Pass et MRZ (zone lisible machine) sont confidentiels · on les masque
  const publicNumber = number.slice(0, 4) + ' •••• ••••';
  const mrzMask = (s) => (redact ? s.replace(/[A-Z0-9]/g, '•') : s);
  // MRZ conforme à l'esprit ICAO 9303 : document + clé, nationalité FBL,
  // naissance + clé, sexe, expiration + clé, puis clé composite globale.
  const digits = number.replace(/\D/g, '');
  const doc = ('FP' + digits).slice(0, 9);
  const dob = '970512';
  const exp = '290611';
  const mrz1 = ('P<FBL' + surname + '<<' + given + pad).slice(0, 34);
  const core = doc + obIcaoCheck(doc) + 'FBL' + (verified ? dob + obIcaoCheck(dob) + 'M' + exp + obIcaoCheck(exp) : '<<<<<<<<<<<<<<<');
  const mrz2 = ((verified ? core + obIcaoCheck(core) : core) + pad).slice(0, 34);
  return (
    <div className={`ob-card ${verified ? 'verified' : ''} ${shimmer ? 'shimmer' : ''} ${float ? 'float' : ''} ${gold ? 'gold' : ''}`}>
      <div className="shine"></div>
      <ObGuilloche />
      <div className="ob-card-emblem" aria-hidden="true"></div>
      <div className="ob-card-head">
        <div className="ob-card-doc">
          <div className="ob-card-doctitle main">Pass ID</div>
          <div className="ob-card-doctitle">Football digital passport</div>
        </div>
        <div className="ob-card-headright">
          <img className="ob-card-logo" src="assets/logotype_white.png" alt="FOOTPASS®" />
          {tier ? <span className={`ob-card-tier ${tierKind === 'pro' ? 'pro' : ''}`}>{tier}</span> : null}
        </div>
      </div>
      <div className="ob-card-body">
        <div className={`ob-card-photo ${verified ? 'on' : ''}`}>
          {verified ? (
            photo
              ? <img src={photo} alt={name || ''} />
              : initials
                ? <span className="ob-card-mono">{initials}</span>
                : <img src={LUCAS_PHOTO} alt={name || ''} />
          ) : (
            <svg viewBox="0 0 48 60" aria-hidden="true">
              <circle cx="24" cy="21" r="10" fill="rgba(255,255,255,0.15)"></circle>
              <rect x="5" y="37" width="38" height="28" rx="14" fill="rgba(255,255,255,0.15)"></rect>
            </svg>
          )}
        </div>
        <div className="ob-card-fields">
          <div className="ob-card-lbl">Name</div>
          <div className={`ob-card-name ${name ? '' : 'ph'}`}>{displayName || 'YOUR NAME'}</div>
          <div className="ob-card-lbl">Role</div>
          <div className="ob-card-val">{obRoleEn(role)}</div>
          <div className="ob-card-lbl">{clubLabel}</div>
          <div className={`ob-card-val ${org ? '' : (orgMissing ? 'miss' : 'empty')}`}>{org || orgMissing || '-'}</div>
          <div className="ob-card-lbl">{agentLabel}</div>
          <div className={`ob-card-val ${agent ? '' : 'empty'}`}>{agent || '-'}</div>
        </div>
      </div>
      <div className="ob-card-numrow">
        <div>
          <div className="ob-card-lbl">Pass No</div>
          <div className="ob-card-num">{verified ? (redact ? publicNumber : number) : '•••• •••• ••••'}</div>
        </div>
        {verified
          ? <span className="ob-card-badge"><OBI.Check s={9} c="currentColor" /> VERIFIED</span>
          : <span className="ob-card-badge off">INACTIVE</span>}
      </div>
      {verified && (photo || !initials) ? <img className="ob-card-ghost" src={photo || LUCAS_PHOTO} alt="" aria-hidden="true" /> : null}
      <div className="ob-card-mrz" aria-hidden="true">
        <span>{mrzMask(mrz1)}</span>
        <span>{mrzMask(mrz2)}</span>
      </div>
    </div>
  );
}

// ── Champ à remplissage simulé (tap : le texte se tape tout seul) ──
function ObField({ label, canned, mask, placeholder, hint, strength, onDone, onStart }) {
  const [n, setN] = React.useState(0);
  const [st, setSt] = React.useState('idle');
  const [show, setShow] = React.useState(false);
  const start = () => {
    if (st !== 'idle') return;
    setSt('typing');
    if (onStart) onStart();
    let i = 0;
    const iv = setInterval(() => {
      i += 1; setN(i);
      if (i >= canned.length) { clearInterval(iv); setSt('done'); onDone && onDone(); }
    }, 38);
  };
  const shown = mask && !show ? '•'.repeat(n) : canned.slice(0, n);
  const ratio = n / canned.length;
  return (
    <div className="ob-fieldwrap">
      {label ? <div className="ob-fieldlabel">{TX(label)}</div> : null}
      <div className={`ob-input ${st}`} onClick={start}>
        {n === 0 ? <span className="ob-ph">{TX(placeholder)}</span> : <span>{shown}</span>}
        {st === 'typing' ? <span className="ob-caret"></span> : null}
        {st === 'done' && !mask ? <span className="ob-field-ok"><OBI.Check s={12} c="var(--acc-bright, #00C49A)" /></span> : null}
        {mask && n > 0 ? (
          <button type="button" className="ob-eye" aria-label={show ? 'Masquer le mot de passe' : 'Afficher le mot de passe'}
            onClick={(e) => { e.stopPropagation(); setShow((s) => !s); }}>
            <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
              <path d="M1.5 8s2.4-4.2 6.5-4.2S14.5 8 14.5 8s-2.4 4.2-6.5 4.2S1.5 8 1.5 8z" stroke="currentColor" strokeWidth="1.3"></path>
              <circle cx="8" cy="8" r="1.9" stroke="currentColor" strokeWidth="1.3"></circle>
              {show ? <path d="M3 13L13 3" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round"></path> : null}
            </svg>
          </button>
        ) : null}
      </div>
      {strength && n > 0 ? (
        <div className="ob-strength">
          {[0.25, 0.5, 0.75, 1].map((t) => <i key={t} className={ratio >= t ? 'on' : ''}></i>)}
          {st === 'done' ? <span>{TX('Solide')}</span> : null}
        </div>
      ) : null}
      {hint ? <div className="ob-fieldhint">{hint}</div> : null}
    </div>
  );
}

// ═══════ ÉCRAN · WELCOME ═══════
function ObStepWelcome({ ctx }) {
  const [toast, setToast] = React.useState(false);
  const login = () => { setToast(true); setTimeout(() => setToast(false), 2200); };
  return (
    <div className="ob-step" data-screen-label="Onboarding · Bienvenue">
      <div className="ob-wel-brand"><img src={ctx.dark === false ? 'assets/logotype_black.png' : 'assets/logotype_white.png'} alt="FOOTPASS®" /></div>
      <div className="ob-wel-cardzone">
        <img className="ob-wel-watermark" src={ctx.dark === false ? 'assets/logomark_black.png' : 'assets/logomark_white.png'} alt="" aria-hidden="true" />
        <div className="ob-cardwrap" style={{ width: '100%' }}>
          <ObPassCard name="Lucas Ferreira" role="Joueur" org="AS Monaco" agent="Atlas Sports" verified gold shimmer float />
        </div>
      </div>
      <div className="ob-wel-text">
        <h1 className="ob-h1">{TX('Parlez uniquement avec des profils vérifiés')}</h1>
      </div>
      <div className="ob-foot">
        <div className="ob-trialnote">{TX("10 jours offerts · 0 CHF aujourd'hui · sans engagement")}</div>
        <button className="ob-cta" onClick={() => ctx.choose('self')}>{TX('Créer mon Pass ID')}</button>
        <button className="ob-cta ghost" onClick={() => ctx.choose('invite')}>{TX("J'ai un lien d'invitation")}</button>
        {toast
          ? <span className="ob-toast">{TX("La connexion n'est pas couverte par ce prototype.")}</span>
          : <button className="ob-link" onClick={login}>{TX("J'ai déjà un compte")}</button>}
      </div>
    </div>
  );
}

// ═══════ ÉCRAN · COMPTE (en premier) ═══════
function ObStepAccount({ ctx }) {
  const [done, setDone] = React.useState({ name: false, email: false, pwd: false });
  const [kb, setKb] = React.useState(false);
  const ok = (k) => setDone((p) => {
    const n = { ...p, [k]: true };
    // Dernier champ rempli : le clavier se range, le CTA réapparaît
    if (n.name && n.email && n.pwd) setTimeout(() => setKb(false), 450);
    return n;
  });
  const all = done.name && done.email && done.pwd;
  return (
    <div className="ob-step" data-screen-label="Onboarding · Création du compte">
      <div className="ob-body">
        <h1 className="ob-h1">{TX('Créez votre compte')}</h1>
        <div className="ob-fields">
          <ObField label="Prénom et nom" canned="Alexandre Da Costa" placeholder="Ex. Lucas Ferreira" onStart={() => setKb(true)} onDone={() => ok('name')} />
          <ObField label="Email" canned="a.dacosta@gmail.com" placeholder="vous@club.com"
            hint={<span className="pro">{TX('Un email professionnel peut accélérer la validation de votre profil.')}</span>}
            onStart={() => setKb(true)} onDone={() => ok('email')} />
          <ObField label="Mot de passe" canned="Fp!2026-Stade" mask strength placeholder="12 caractères minimum" onStart={() => setKb(true)} onDone={() => ok('pwd')} />
        </div>
      </div>
      <div className="ob-foot">
        <button className="ob-cta" disabled={!all} onClick={ctx.next}>{TX('Créer mon compte')}</button>
        <div className="ob-note txt">{TX('En continuant, vous acceptez les ')}<span className="lnk">{TX("Conditions d'utilisation")}</span>{TX(' et la ')}<span className="lnk">{TX('Politique de confidentialité')}</span>.</div>
      </div>
      {/* Clavier iOS : monte dès qu'on tape, se range quand tout est rempli */}
      <div className={`ob-kb ${kb ? 'show' : ''}`} aria-hidden="true"><FPKeyboard dark={ctx.dark !== false} lang="fr" /></div>
    </div>
  );
}

// ═══════ ÉCRAN · RÔLE ═══════
function ObStepRole({ ctx }) {
  const [sel, setSel] = React.useState(ctx.d.roleChosen ? ctx.d.role : null);
  const [custom, setCustom] = React.useState(false);
  const [customDone, setCustomDone] = React.useState(false);
  // Verrou uniquement pendant l'avance (anti double-clic) : on peut toujours
  // changer d'avis, y compris en revenant en arrière depuis le plan
  const [advancing, setAdvancing] = React.useState(false);
  const pick = (r) => {
    if (advancing) return;
    if (r.id === sel) return;
    setSel(r.id);
    ctx.set('role', r.id); ctx.set('roleChosen', true); ctx.set('plan', 'pro');
    if (r.id === 'autre') { setCustom(true); return; }
    setCustom(false); setCustomDone(false); ctx.set('roleCustom', null);
    setAdvancing(true);
    setTimeout(ctx.next, 340);
  };
  return (
    <div className="ob-step" data-screen-label="Onboarding · Rôle">
      <div className="ob-body">
        <div className="ob-kicker">{TX('VOTRE PASS ID')}</div>
        <h1 className="ob-h1">{TX('Quel est votre rôle ?')}</h1>
        <p className="ob-sub">{TX('Votre rôle détermine ce que votre Pass débloque.')}</p>
        <div className="ob-cards two">
          {OB_ROLES.map((r) => (
            <button key={r.id} className={`ob-selcard compact ${sel === r.id ? 'sel' : ''}`} onClick={() => pick(r)}>
              <span className="ob-selcard-icon">{sel === r.id ? <OBI.Check s={16} /> : <ObRoleIcon icon={r.icon} />}</span>
              <span className="ob-selcard-main">
                <span className="ob-selcard-title">{TX(r.label)}</span>
                <div className="ob-selcard-desc">{TX(r.desc)}</div>
              </span>
            </button>
          ))}
        </div>
        {custom ? (
          <div className="ob-fields" style={{ marginTop: 16 }}>
            <ObField label="Précisez votre rôle" canned="Journaliste sportif" placeholder="Ex. Journaliste, kiné, parent…" key={'role-custom-' + window.FP_LANG}
              onDone={() => { setCustomDone(true); ctx.set('roleCustom', 'Journaliste sportif'); }} />
          </div>
        ) : null}
      </div>
      {custom ? (
        <div className="ob-foot">
          <button className="ob-cta" disabled={!customDone} onClick={ctx.next}>{TX('Continuer')}</button>
        </div>
      ) : null}
    </div>
  );
}

// ═══════ ÉCRAN · PLAN ═══════
function ObStepPlan({ ctx }) {
  const billing = ctx.d.billing;
  // PassPRO est la formule mise en avant pour tous les rôles : recommandée + pré-sélectionnée.
  const rec = 'pro';
  const sel = ctx.d.plan || rec;
  return (
    <div className="ob-step" data-screen-label="Onboarding · Choix du plan">
      <div className="ob-body">
        <h1 className="ob-h1">{TX('Choisissez votre Pass')}</h1>
        <p className="ob-sub"><strong>{TX("10 jours d'essai offerts")}</strong>{TX(' · Sans engagement')}<br /><span className="ob-subline"><span className="ob-free">{TX("0 CHF aujourd'hui")}</span></span></p>
        <div className="ob-bill">
          <button className={billing === 'monthly' ? 'on' : ''} onClick={() => ctx.set('billing', 'monthly')}>{TX('Mensuel')}</button>
          <button className={billing === 'annual' ? 'on' : ''} onClick={() => ctx.set('billing', 'annual')}>
            {TX('Annuel')} <span className="ob-bill-save">{TX("jusqu'à −33 %")}</span>
          </button>
        </div>
        <div className="ob-plans">
          {OB_PLANS.map((p) => (
            <button key={p.id} className={`ob-plan ${sel === p.id ? 'sel' : ''} ${p.id === rec ? 'rec' : ''}`} onClick={() => ctx.set('plan', p.id)}>
              {p.id === rec ? <span className="ob-plan-rec">{TX('RECOMMANDÉ')}</span> : null}
              <span className="ob-plan-head">
                <span className="ob-plan-radio"></span>
                <span className="ob-plan-main">
                  <span className="ob-plan-name">{p.name}</span>
                  <div className="ob-plan-tag">{TX(p.tag)}</div>
                </span>
                <span className="ob-plan-pricing">
                  <div className="ob-plan-price">{billing === 'monthly' ? p.m : p.ym} CHF</div>
                  <div className="ob-plan-per">{billing === 'monthly' ? TX('/ mois') : (window.FP_LANG === 'en' ? `/ month · CHF ${p.y}/yr` : `/ mois · ${p.y} CHF/an`)}</div>
                </span>
              </span>
              <span className="ob-plan-feats">
                {p.feats.map((f) => <span key={f} className="ob-feat"><OBI.Check /> {TX(f)}</span>)}
              </span>
            </button>
          ))}
        </div>
        <div className="ob-structhint" style={{ marginTop: 14 }}>
          <span className="ob-structhint-ic" aria-hidden="true">
            <svg viewBox="0 0 16 16" width="15" height="15" fill="none">
              <rect x="2.25" y="3" width="6" height="10.25" rx="1" stroke="currentColor" strokeWidth="1.2"></rect>
              <rect x="8.25" y="6" width="5.5" height="7.25" rx="1" stroke="currentColor" strokeWidth="1.2"></rect>
              <path d="M4.4 5.6h1.6M4.4 8h1.6M4.4 10.4h1.6M10.2 8.4h1.6M10.2 10.6h1.6" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round"></path>
            </svg>
          </span>
          <span className="ob-structhint-txt">
            <strong>{TX('Vous représentez une structure ?')}</strong>{TX(" Créez d'abord votre Pass personnel · votre structure se crée ensuite dans l'app, au palier de votre staff (dès 499,99 CHF/an, essai 10 jours).")}
          </span>
        </div>
      </div>
      <div className="ob-foot">
        <button className="ob-cta" onClick={ctx.next}>{TX('Essayer gratuitement pendant 10 jours')}</button>
        <div className="ob-note"><OBI.Lock /> <span>{TX("0 CHF aujourd'hui. Premier paiement uniquement après l'essai. Annulable à tout moment.")}</span></div>
      </div>
    </div>
  );
}

// ═══════ ÉCRAN · PAIEMENT ═══════
function ObPayFields({ onDone }) {
  const defs = [
    { label: 'Numéro de carte', v: '4242 4242 4242 4242', ph: '1234 1234 1234 1234', full: true },
    { label: 'Expiration', v: '08 / 29', ph: 'MM / AA' },
    { label: 'CVC', v: '123', ph: '···' },
  ];
  const [prog, setProg] = React.useState([0, 0, 0]);
  const [st, setSt] = React.useState('idle');
  const start = () => {
    if (st !== 'idle') return;
    setSt('typing');
    let f = 0; let i = 0;
    const iv = setInterval(() => {
      i += 1;
      const fi = f; const val = Math.min(i, defs[fi].v.length);
      setProg((p) => { const np = [...p]; np[fi] = val; return np; });
      if (i >= defs[f].v.length) {
        f += 1; i = 0;
        if (f >= defs.length) { clearInterval(iv); setSt('done'); onDone && onDone(); }
      }
    }, 30);
  };
  return (
    <div className="ob-fields cols" onClick={start}>
      {defs.map((d, k) => (
        <div key={d.label} className={`ob-fieldwrap ${d.full ? 'full' : ''}`}>
          <div className="ob-fieldlabel">{TX(d.label)}</div>
          <div className={`ob-input ${prog[k] >= d.v.length ? 'done' : prog[k] > 0 ? 'typing' : 'idle'}`}>
            {prog[k] === 0 ? <span className="ob-ph">{TX(d.ph)}</span> : <span>{d.v.slice(0, prog[k])}</span>}
            {prog[k] > 0 && prog[k] < d.v.length ? <span className="ob-caret"></span> : null}
          </div>
        </div>
      ))}
    </div>
  );
}

function ObStepPay({ ctx }) {
  const plan = OB_PLAN_BY_ID[ctx.d.plan] || OB_PLAN_BY_ID.start;
  const price = ctx.d.billing === 'monthly' ? (window.FP_LANG === 'en' ? `CHF ${plan.m}/mo` : `${plan.m} CHF/mois`) : (window.FP_LANG === 'en' ? `CHF ${plan.y}/yr` : `${plan.y} CHF/an`);
  const [cardOk, setCardOk] = React.useState(false);
  const [paySt, setPaySt] = React.useState('idle'); // idle | proc | ok
  const [via, setVia] = React.useState(null); // 'apple' | 'card'
  const pay = (method) => {
    if (paySt !== 'idle') return;
    setVia(method); setPaySt('proc');
    setTimeout(() => {
      setPaySt('ok');
      setTimeout(ctx.next, 650);
    }, 1300);
  };
  return (
    <div className="ob-step" data-screen-label="Onboarding · Paiement Stripe">
      <div className="ob-body">
        <h1 className="ob-h1 ob-h1-pay">{TX('10 jours gratuits')}<br />{TX("Aucun prélèvement aujourd'hui")}</h1>
        <div className="ob-recap">
          <div className="ob-recap-info">
            <div className="ob-recap-top"><b>{plan.name}</b><span className="ob-recap-badge">{TX('Recommandé')}</span></div>
            <div className="ob-recap-price">{price}{TX(' après les 10 jours gratuits')}</div>
          </div>
          <button className="ob-recap-edit" onClick={ctx.back}>{TX('Modifier')}</button>
        </div>
        <div className="ob-tl">
          <div className="ob-tl-item now">
            <span className="ob-tl-dot"><OBI.Lock s={11} /></span>
            <span>
              <div className="ob-tl-title">{TX("Aujourd'hui")} <em>· {obFrDate(0)}</em></div>
              <div className="ob-tl-desc">{TX('Accès immédiat.')} <b>{TX('0 CHF prélevé.')}</b></div>
            </span>
          </div>
          <div className="ob-tl-item">
            <span className="ob-tl-dot"><OBI.Check s={11} /></span>
            <span>
              <div className="ob-tl-title">{TX('Jour 7')} <em>· {obFrDate(7)}</em></div>
              <div className="ob-tl-desc">{TX("Rappel envoyé avant la fin de l'essai.")}</div>
            </span>
          </div>
          <div className="ob-tl-item">
            <span className="ob-tl-dot"><OBI.Check s={11} /></span>
            <span>
              <div className="ob-tl-title">{TX('Jour 10')} <em>· {obFrDate(10)}</em></div>
              <div className="ob-tl-desc">{TX('Premier paiement uniquement si vous continuez.')}</div>
            </span>
          </div>
        </div>
        {/* Voie express · paiement en un geste, distincte de la saisie carte */}
        <button className="ob-applepay" onClick={() => pay('apple')} disabled={paySt !== 'idle'}>
          {paySt === 'proc' && via === 'apple' ? <span className="ob-spinner"></span> : null}
          {paySt === 'ok' && via === 'apple' ? TX('Essai activé') : (
            <span className="ob-applepay-label">
              {TX('Continuer avec')}
              <svg className="ob-apple-logo" viewBox="0 0 17 21" width="15" height="18" fill="currentColor" aria-hidden="true">
                <path d="M14.05 11.17c-.02-2.2 1.8-3.26 1.88-3.31-1.02-1.5-2.62-1.7-3.18-1.73-1.35-.14-2.64.79-3.32.79-.69 0-1.74-.77-2.86-.75-1.47.02-2.83.86-3.58 2.17-1.53 2.66-.39 6.6 1.1 8.76.73 1.06 1.6 2.25 2.73 2.2 1.1-.04 1.51-.71 2.84-.71 1.32 0 1.69.71 2.85.69 1.18-.02 1.92-1.08 2.64-2.14.83-1.22 1.18-2.41 1.19-2.47-.03-.01-2.28-.87-2.31-3.46zM11.87 4.7c.6-.73 1.01-1.74.9-2.75-.87.04-1.92.58-2.55 1.3-.56.64-1.05 1.67-.92 2.65.97.08 1.96-.49 2.57-1.2z"></path>
              </svg>
              Pay
            </span>
          )}
        </button>
        <div className="ob-divider">{TX('OU PAR CARTE BANCAIRE')}</div>
        <ObPayFields onDone={() => setCardOk(true)} />
      </div>
      <div className="ob-foot">
        <button className="ob-cta start" disabled={paySt !== 'idle'} onClick={() => pay('card')}>
          {paySt === 'proc' && via === 'card' ? <span className="ob-spinner"></span> : paySt === 'ok' && via === 'card' ? <OBI.Check s={16} /> : null}
          {paySt === 'ok' && via === 'card' ? TX('Essai activé') : TX('Continuer gratuitement')}
        </button>
        <div className="ob-note"><OBI.Lock /> <span>{TX("Paiement sécurisé. Aucun prélèvement aujourd'hui. Annulable à tout moment.")}</span></div>
      </div>
    </div>
  );
}

Object.assign(window, {
  OBI, OB_ROLES, OB_ROLE_LABEL, OB_PLANS, OB_PLAN_BY_ID, obFrDate, ObRoleIcon,
  ObPassCard, ObField, ObStepWelcome, ObStepRole, ObStepPlan, ObStepPay, ObStepAccount, ObPayFields,
});
