const { useState, useEffect, useRef } = React;

/* helpers partagés (définis dans core.js) */
const { formatDate, OFFER, loadPro, savePro, visibleEntries, hiddenCount,
  exportJournal, exportFiches, OracleCtx } = window;

function loadJournal() {
  try {return JSON.parse(localStorage.getItem("oracle_journal") || "[]");}
  catch (e) {return [];}
}
function saveJournal(j) {localStorage.setItem("oracle_journal", JSON.stringify(j));}

/* ---------------- Lettrine enluminée ---------------- */
function Lettrine({ mot, size }) {
  const { letter, src } = window.lettrineFor(mot);
  const s = size || 96;
  return (
    <div className="lettrine" style={{ width: s + "px", height: s + "px" }}>
      {src ?
      <img className="lettrine-img" src={src} alt={"Lettrine " + letter} draggable="false" style={{ width: "100%", height: "100%" }} /> :
      <span className="lettrine-fallback">{letter}</span>}
    </div>);

}
window.Lettrine = Lettrine;

/* ---------------- Carte à partager (téléchargeable) ---------------- */
function ShareCard({ word, piste, cat, onClose }) {
  const ref = useRef(null);
  const [busy, setBusy] = useState(false);
  const { src, letter } = window.lettrineFor(word);
  async function download() {
    if (!window.htmlToImage || !ref.current) return;
    setBusy(true);
    try {
      const url = await window.htmlToImage.toPng(ref.current, { pixelRatio: 2.6, cacheBust: true });
      const a = document.createElement("a");
      a.href = url;a.download = "oracle-" + (word || "mot").toLowerCase().replace(/\s+/g, "-") + ".png";
      a.click();
    } catch (e) {/* silencieux */} finally {setBusy(false);}
  }
  return (
    <React.Fragment>
      <div className="backdrop" onClick={onClose}></div>
      <div className="share" role="dialog" aria-modal="true">
        <button className="legal-x" onClick={onClose} aria-label="Fermer">×</button>
        <div className="share-card" ref={ref}>
          <div className="share-top">
            <span className="share-seal"><span className="share-seal-mark"></span><span className="share-seal-name">NomPrénom</span></span>
            {cat ? <span className="share-cat">{cat}</span> : null}
          </div>
          <div className="share-plate">
            {src ?
            <img className="share-letter-img" src={src} alt={"Lettrine " + letter} draggable="false" /> :
            <span className="share-letter-fallback">{letter}</span>}
          </div>
          <div className="share-foot">
            <div className="share-word">{word}</div>
            {piste ? <div className="share-piste">{piste}</div> : null}
            <div className="share-ribbon">tire un mot · écoute ce qu'il réveille</div>
          </div>
        </div>
        <div className="share-actions">
          <button className="btn-primary" onClick={download} disabled={busy}>{busy ? "Préparation…" : "Télécharger la carte"}</button>
          <span className="hint-inline">une image à garder ou partager</span>
        </div>
      </div>
    </React.Fragment>);

}
window.ShareCard = ShareCard;

/* ---------------- Questions pliables (partagé) ---------------- */
function Questions({ q, label }) {
  const [open, setOpen] = useState(false);
  if (!q || !q.length) return null;
  return (
    <div className={"q-block" + (open ? " q-open" : "")}>
      <button className="q-toggle" onClick={() => setOpen(!open)}>
        <span className="q-chevron">›</span>{label || "Questions à se poser"}
        <span className="q-count">{q.length}</span>
      </button>
      {open &&
      <ul className="questions">{q.map((x, i) => <li key={i}>{x}</li>)}</ul>
      }
    </div>);

}
window.Questions = Questions;

/* ============================================================
   JOURNAL
   ============================================================ */
function FichePrenom({ e }) {
  return (
    <div className="fiche">
      <div className="fiche-blocage">
        <span className="fiche-tag">À qui</span>
        <p>{e.relation === "proche" ? "Un proche" + (e.relLabel ? " — " + e.relLabel : "") : "Le tien"}</p>
      </div>
      {e.etym &&
      <div className="fiche-word">
          <div className="fiche-pos">La racine <span>— {e.etym.origine}</span></div>
          <div className="fiche-mot">{e.etym.sens}</div>
          {e.etym.piste && <p className="fiche-piste">{e.etym.piste}</p>}
        </div>
      }
      {(e.initiale || e.finale) &&
      <div className="fiche-word">
          <div className="fiche-pos">Les lettres <span>— élan · tendance</span></div>
          <div className="fiche-mot">{e.initiale}{e.finale ? <span className="fiche-cat">{e.initiale} → {e.finale}</span> : null}</div>
        </div>
      }
      {e.ombres && e.ombres.length > 0 &&
      <div className="fiche-word">
          <div className="fiche-pos">L'ombre <span>— prénoms mis de côté</span></div>
          <div className="fiche-mot">{e.ombres.map((o) => o.prenom).join(", ")}</div>
        </div>
      }
      {e.synthese &&
      <div className="fiche-synth"><span className="fiche-tag">Synthèse</span><p>{e.synthese}</p></div>
      }
      {e.questions && e.questions.length > 0 &&
      <div className="fiche-word"><ul className="fiche-q">{e.questions.map((q, i) => <li key={i}>{q}</li>)}</ul></div>
      }
      {e.intention &&
      <div className="fiche-note"><span className="fiche-tag">Ta note</span><p>{e.intention}</p></div>
      }
    </div>);

}

function JournalEntry({ entry, onDelete }) {
  const [open, setOpen] = useState(false);
  const [share, setShare] = useState(false);
  const e = entry;
  const summaryWords = [(e.prenomsFull || e.prenom) + (e.nom ? " " + e.nom : "")];
  const tag = "Prénom";
  const tagClass = "tag-prenom";
  const heroMot = e.prenom;
  const heroPiste = e.etym ? e.etym.sens : "";
  const heroCat = "Prénom";
  const subline = e.relation === "proche" ? e.relLabel || "un proche" : "le tien";
  const ShareCard = window.ShareCard;

  return (
    <div className={"entry" + (open ? " entry-open" : "")}>
      <div className="entry-top">
        <span className="entry-date">{formatDate(e.id)} · <span className={tagClass}>{tag}</span></span>
        <div className="entry-top-actions">
          <button className="entry-share" onClick={() => setShare(true)} title="Créer une carte à partager">Partager</button>
          <button className="entry-del" onClick={() => onDelete(e.id)}>retirer</button>
        </div>
      </div>
      <button className="entry-summary" onClick={() => setOpen(!open)}>
        <p className="entry-words">
          {summaryWords.map((w, i) =>
          <React.Fragment key={i}>{i > 0 && <span className="sep"> · </span>}{w}</React.Fragment>
          )}
        </p>
        <span className="entry-chevron">{open ? "Replier" : "Voir la fiche"}</span>
      </button>
      {!open && subline && <p className="entry-blocage">{subline}</p>}
      {open && <FichePrenom e={e} />}
      {share && ShareCard && <ShareCard word={heroMot} piste={heroPiste} cat={heroCat} onClose={() => setShare(false)} />}
    </div>);

}

function Journal({ entries, onClose, onDelete }) {
  const { pro, openPaywall } = React.useContext(OracleCtx);
  const visible = visibleEntries(entries, pro);
  const prenoms = visible.filter((e) => e.space === "prenom");
  const hiddenPre = hiddenCount(entries.filter((e) => e.space === "prenom"), pro);
  const prenomsMoi = prenoms.filter((e) => e.relation !== "proche");
  const prenomsProches = prenoms.filter((e) => e.relation === "proche");

  const title = !pro ? "Ton journal" : "Tes prénoms";

  return (
    <React.Fragment>
      <div className="backdrop" onClick={onClose}></div>
      <aside className="journal">
        <div className="journal-head">
          <h2>{title}</h2>
          <div className="journal-head-actions">
            {pro && prenoms.length > 0 &&
            <button className="ghost-btn" onClick={() => exportJournal(prenoms)}>Exporter</button>
            }
            {pro &&
            <button className="ghost-btn" onClick={() => { const n = exportFiches(); }} title="Exporter les fiches générées (JSON) pour amorcer la base partagée">Exporter le dictionnaire</button>
            }
            <button className="ghost-btn" onClick={onClose}>Fermer</button>
          </div>
        </div>
        {!pro &&
        <div className="journal-body">
          <div className="journal-locked journal-locked-full">
            <div className="eyebrow">Le journal fait partie de l'offre augmentée</div>
            <p>Garder les prénoms que tu explores — le tien, ceux de tes proches, ceux que tu envisages pour un enfant — fait partie de l'<b>{OFFER.name}</b>. L'accès gratuit te laisse lire autant que tu veux ; le journal, lui, garde la trace.</p>
            <button className="upsell-btn" onClick={openPaywall}>Activer l'offre augmentée →</button>
            {entries.length > 0 &&
            <p className="journal-locked-fine">{entries.length} prénom{entries.length > 1 ? "s" : ""} déjà gardé{entries.length > 1 ? "s" : ""} sur cet appareil — {entries.length > 1 ? "ils t'attendront" : "il t'attendra"} ici dès l'activation.</p>}
          </div>
        </div>}
        {pro &&
        <div className="journal-body">
          {prenoms.length === 0 &&
          <p className="journal-empty">Aucun prénom gardé pour l'instant.<br />Explore l'essence d'un prénom — le tien, ou celui d'un proche.</p>
          }
          {prenomsMoi.length > 0 &&
          <React.Fragment>
              <div className="journal-group">Le tien</div>
              {prenomsMoi.map((e) => <JournalEntry key={e.id} entry={e} onDelete={onDelete} />)}
            </React.Fragment>
          }
          {prenomsProches.length > 0 &&
          <React.Fragment>
              <div className="journal-group">Tes proches</div>
              {prenomsProches.map((e) => <JournalEntry key={e.id} entry={e} onDelete={onDelete} />)}
            </React.Fragment>
          }
          {hiddenPre > 0 &&
          <div className="journal-locked">
                <p>{hiddenPre} prénom{hiddenPre > 1 ? "s" : ""} de plus de 7&nbsp;jours {hiddenPre > 1 ? "sont masqués" : "est masqué"}.</p>
                <button className="upsell-btn" onClick={openPaywall}>Garder tout l'historique →</button>
              </div>
          }
        </div>}
      </aside>
    </React.Fragment>);

}

/* ============================================================
   PAYWALL — l'offre Oracle augmenté
   ============================================================ */
function Paywall({ pro, onActivate, onCancel, onClose, busy, note, reassureHTML, onShareUnlock, shareLabel }) {
  const plans = OFFER.plans || [];
  const [plan, setPlan] = useState("month");
  const [shareState, setShareState] = useState(null); // null | "done" | "copied"
  const o = OFFER;
  const sel = plans.find((p) => p.id === plan) || plans[0] || {};
  const live = window.OraclePay && window.OraclePay.isLive && window.OraclePay.isLive();
  const canShare = !pro && window.OracleShare;
  function doShare() {
    if (!window.OracleShare) return;
    const r = window.OracleShare.shareUnlock({ slug: shareLabel || "oracle", label: shareLabel });
    setShareState("done");
    if (onShareUnlock) onShareUnlock();
  }
  async function doCopy() {
    if (!window.OracleShare) return;
    await window.OracleShare.copyShareLink(shareLabel || "oracle");
    setShareState("copied");
    if (onShareUnlock) onShareUnlock();
  }
  return (
    <React.Fragment>
      <div className="backdrop" onClick={onClose}></div>
      <div className="paywall" role="dialog" aria-modal="true">
        <button className="paywall-x" onClick={onClose} aria-label="Fermer">×</button>
        <div className="paywall-k">{pro ? "Ton pass" : "Passe à l'augmenté"}</div>
        <h2 className="paywall-title">{o.name}</h2>
        <p className="paywall-tag">{o.tagline}</p>

        <ul className="paywall-benefits">
          {o.benefits.map((b, i) =>
          <li key={i}><b>{b.t}</b><span>{b.d}</span></li>
          )}
        </ul>

        {!pro &&
        <React.Fragment>
            {note === "cancel" &&
            <p className="paywall-cancel">Paiement interrompu — tu peux reprendre quand tu veux.</p>
            }
            {note === "error" &&
            <p className="paywall-cancel paywall-cancel-err">Le paiement n'a pas pu démarrer. Réessaie dans un instant.</p>
            }
            <div className={"plans plans-" + plans.length}>
              {plans.map((p) =>
              <button key={p.id} className={"plan" + (plan === p.id ? " plan-on" : "")} onClick={() => setPlan(p.id)}>
                {p.save && <span className="plan-save">{p.save}</span>}
                <span className="plan-price">{p.price}</span>
                <span className="plan-per">{p.per}</span>
                <span className="plan-sub">{p.sub}</span>
              </button>
              )}
            </div>
            <button className="btn-primary paywall-cta" onClick={() => onActivate(plan)} disabled={busy}>
              {busy
                ? "Redirection vers le paiement…"
                : (live ? "Payer — " : "Activer — ") + sel.price + " · " + sel.per}
            </button>
            {canShare && !shareState &&
            <div className="share-unlock">
              <div className="share-or"><span>ou, gratuitement</span></div>
              <p className="share-unlock-lead">Partage l'Oracle à un proche — un clin d'œil, « regarde ce que ça lit dans notre prénom » — et la lecture complète s'ouvre pour 48&nbsp;h.</p>
              <button className="btn-share-wa" onClick={doShare}>
                <svg viewBox="0 0 24 24" width="19" height="19" aria-hidden="true"><path fill="currentColor" d="M.057 24l1.687-6.163a11.867 11.867 0 0 1-1.587-5.946C.16 5.335 5.495 0 12.05 0a11.82 11.82 0 0 1 8.413 3.488 11.82 11.82 0 0 1 3.48 8.414c-.003 6.557-5.338 11.892-11.893 11.892a11.9 11.9 0 0 1-5.688-1.448L.057 24zm6.597-3.807c1.676.995 3.276 1.591 5.392 1.592 5.448 0 9.886-4.434 9.889-9.885.002-5.462-4.415-9.89-9.881-9.892-5.452 0-9.887 4.434-9.889 9.884a9.82 9.82 0 0 0 1.51 5.26l-.999 3.648 3.978-1.715zm11.387-5.464c-.074-.124-.272-.198-.57-.347-.297-.149-1.758-.868-2.031-.967-.272-.099-.47-.149-.669.149-.198.297-.768.967-.941 1.165-.173.198-.347.223-.644.074-.297-.149-1.255-.462-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.297-.347.446-.521.151-.172.2-.296.3-.495.099-.198.05-.372-.025-.521-.075-.148-.669-1.611-.916-2.206-.242-.579-.487-.501-.669-.51l-.57-.01c-.198 0-.52.074-.792.372s-1.04 1.016-1.04 2.479 1.065 2.876 1.213 3.074c.149.198 2.095 3.2 5.076 4.487.709.306 1.263.489 1.694.625.712.227 1.36.195 1.872.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413z"/></svg>
                Partager sur WhatsApp &amp; débloquer
              </button>
              <button className="btn-share-copy" onClick={doCopy}>copier le lien à la place</button>
            </div>
            }
            {canShare && shareState &&
            <div className="share-done">
              <span className="share-done-ico">✓</span>
              <p>{shareState === "copied" ? "Lien copié — merci\u00a0! " : "Merci du partage\u00a0! "}Ta lecture complète est ouverte pour 48&nbsp;h.</p>
              <button className="btn-primary" onClick={onClose}>Lire maintenant →</button>
            </div>
            }
            {reassureHTML
              ? <div className="paywall-reassure" dangerouslySetInnerHTML={{ __html: reassureHTML }}></div>
              : <p className="paywall-fine">{live
                  ? "Paiement unique et sécurisé par Stripe. Pas d'abonnement, rien à résilier."
                  : "Maquette — paiement Stripe non branché (mode démo). L'état est mémorisé sur cet appareil."}</p>}
          </React.Fragment>
        }

        {pro &&
        <React.Fragment>
            <div className="paywall-active"><span className="dot"></span>Accès augmenté actif</div>
            <button className="btn-secondary" onClick={onCancel}>Revenir au gratuit</button>
          </React.Fragment>
        }
      </div>
    </React.Fragment>);

}

/* ============================================================
   PAGE D'ENTRÉE — deux espaces
   ============================================================ */
function Landing({ onChoose }) {
  return (
    <div className="fade-screen landing">
      <div className="landing-bg" aria-hidden="true"></div>
      <div className="eyebrow">NomPrénom</div>
      <h1 className="lede">Ce que ton prénom<br />raconte <em>déjà</em> de toi.</h1>
      <p className="sub">Une lecture symbolique des prénoms et des noms — ni numérologie, ni destin. Écoute ce que ton prénom (et ton nom) dit de toi, ou cherche, pour l'enfant qui vient, un prénom accordé à votre foyer.</p>

      <div className="doors">
        <button className="door door-prenom" onClick={() => onChoose("prenom")}>
          <div className="door-k">L'un</div>
          <div className="door-t">L'essence du prénom</div>
          <p className="door-d">Une lecture symbolique de ton prénom (et de ton nom) : sa racine, le dessin de ses lettres, la matière de ses sons. Pas de numérologie ni de destin — un miroir, pas un verdict.</p>
          <span className="door-go">Entrer →</span>
        </button>
        <button className="door door-nommage" onClick={() => onChoose("nommage")}>
          <div className="door-k">L'autre</div>
          <div className="door-t">Trouver un prénom</div>
          <p className="door-d">Pour l'enfant qui vient : on lit l'élément de vos propres prénoms — Feu, Eau, Air, Terre — puis on tire des prénoms qui s'y accordent, dans le sens que vous choisissez. Équilibrer, ancrer, ou amplifier.</p>
          <span className="door-go">Entrer →</span>
        </button>
      </div>

      <button className="door door-dico door-wide" onClick={() => onChoose("dico")}>
        <div className="door-wide-main">
          <div className="door-k">Et aussi</div>
          <div className="door-t">Le dictionnaire des prénoms &amp; noms</div>
          <p className="door-d">Pour flâner : cherche un prénom, parcours l'alphabet, ouvre la fiche complète de celui qui t'arrête.</p>
        </div>
        <span className="door-go">Parcourir →</span>
      </button>
      {window.RappelsCard && <window.RappelsCard />}
    </div>);

}

/* ============================================================
   LÉGAL / À PROPOS
   ============================================================ */
const LEGAL_UPDATED = "1er juin 2026";

function Legal({ tab, onTab, onClose }) {
  return (
    <React.Fragment>
      <div className="backdrop" onClick={onClose}></div>
      <div className="legal" role="dialog" aria-modal="true">
        <button className="legal-x" onClick={onClose} aria-label="Fermer">×</button>
        <div className="legal-tabs">
          <button className={"legal-tab" + (tab === "apropos" ? " legal-tab-on" : "")} onClick={() => onTab("apropos")}>À propos</button>
          <button className={"legal-tab" + (tab === "confidentialite" ? " legal-tab-on" : "")} onClick={() => onTab("confidentialite")}>Confidentialité</button>
          <button className={"legal-tab" + (tab === "conditions" ? " legal-tab-on" : "")} onClick={() => onTab("conditions")}>Conditions</button>
        </div>

        <div className="legal-body">
          {tab === "apropos" &&
          <React.Fragment>
              <h2>À propos d'NomPrénom</h2>
              <div className="legal-warn">
                <b>Ce n'est pas de la voyance.</b> NomPrénom ne prédit pas l'avenir et ne donne aucun avis médical, psychologique, juridique ou financier. C'est une lecture symbolique : ton prénom et ton nom te tendent un miroir pour réfléchir par toi-même. Si tu traverses une période difficile, parle à un professionnel de santé ou à une personne de confiance.
              </div>
              <p>NomPrénom propose une lecture symbolique des prénoms et des noms — une approche d'introspection, dans l'esprit du travail de Tristan Moir, Franc Lopvet et Christophe Allain. Tout part des <b>lettres</b> : l'étymologie, la langue des oiseaux et la graphie. Ce n'est ni de la numérologie, ni une prédiction.</p>
              <h3>Deux portes</h3>
              <p><b>L'essence du prénom</b> — une lecture symbolique de ton prénom et de ton nom, à partir de leur étymologie, de la graphie des lettres et de la matière sonore. Un miroir pour réfléchir, pas un verdict.</p>
              <p><b>Trouver un prénom</b> — pour l'enfant qui vient : des suggestions de prénoms accordées au terrain de la famille et au nom, calculées à partir des lettres.</p>
              <p className="legal-updated">Mis à jour le {LEGAL_UPDATED}.</p>
            </React.Fragment>
          }

          {tab === "confidentialite" &&
          <React.Fragment>
              <h2>Confidentialité</h2>
              <p>Nous avons conçu NomPrénom pour qu'il en demande le moins possible sur toi.</p>
              <h3>Ce qui reste sur ton appareil</h3>
              <p>Ton <b>journal de tirages</b>, tes <b>notes</b>, le blocage que tu écris et ton statut d'abonnement sont enregistrés <b>localement dans ton navigateur</b>. Ils ne sont pas envoyés à un serveur et restent sur cet appareil. Si tu changes d'appareil, vides le cache de ton navigateur ou utilises la navigation privée, ces données peuvent être perdues.</p>
              <p>Les <b>fiches de prénoms &amp; noms</b> déjà consultées sont conservées localement pour s'afficher instantanément. Tu peux les exporter en JSON :</p>
              <p><button className="ghost-btn" onClick={() => { const n = window.exportFiches ? window.exportFiches() : 0; if (!n) alert("Aucune fiche en cache pour le moment — consulte d'abord quelques prénoms."); }}>Exporter mes fiches (JSON)</button></p>
              <h3>Mesure d'audience</h3>
              <p>Cette version ne dépose pas de cookies publicitaires et ne te piste pas à des fins commerciales.</p>
              <h3>Tes droits</h3>
              <p>Comme tes données vivent sur ton appareil, tu peux les effacer à tout moment en supprimant des tirages dans le journal ou en vidant les données de site de ton navigateur.</p>
              <p className="legal-updated">Mis à jour le {LEGAL_UPDATED}.</p>
            </React.Fragment>
          }

          {tab === "conditions" &&
          <React.Fragment>
              <h2>Conditions d'utilisation</h2>
              <div className="legal-warn">
                NomPrénom est proposé à des fins de <b>divertissement et de réflexion personnelle</b>. Il ne remplace en aucun cas un avis médical, psychologique, juridique ou financier professionnel.
              </div>
              <h3>Usage</h3>
              <p>Tu utilises l'oracle sous ta seule responsabilité. Les tirages sont aléatoires et les interprétations, qu'elles soient pré-écrites ou générées par IA, sont des suggestions ouvertes — non des vérités ni des conseils.</p>
              <h3>Abonnement « Oracle augmenté »</h3>
              <p>Dans cette version de démonstration, l'activation de l'offre est <b>simulée</b> : aucun paiement n'est réellement prélevé et aucune somme n'est due. Les tarifs affichés sont indicatifs et préfigurent une future offre. Les conditions de facturation, de résiliation et de remboursement seront précisées lors de la mise en place d'un paiement réel.</p>
              <h3>Disponibilité</h3>
              <p>Le service est fourni « en l'état », sans garantie de disponibilité continue. Les fonctions d'IA dépendent de services tiers et peuvent être momentanément indisponibles.</p>
              <p className="legal-updated">Mis à jour le {LEGAL_UPDATED}.</p>
            </React.Fragment>
          }
        </div>
      </div>
    </React.Fragment>);

}

function Footer({ onOpen }) {
  return (
    <footer className="footer">
      <div className="footer-links">
        <a className="footer-link" href="index.html">Accueil</a>
        <a className="footer-link" href="blog.html">Le Journal — articles</a>
        <a className="footer-link" href="prenom/index.html">Fiches prénoms</a>
        <button className="footer-link" onClick={() => onOpen("apropos")}>À propos</button>
        <button className="footer-link" onClick={() => onOpen("confidentialite")}>Confidentialité</button>
        <button className="footer-link" onClick={() => onOpen("conditions")}>Conditions</button>
      </div>
    </footer>);

}

/* ============================================================
   APP
   ============================================================ */
function App() {
  const [space, setSpace] = useState(null); // null | "prenom" | "nommage"
  const [pending, setPending] = useState(null); // {prenom, nom} — fiche complète demandée depuis le nommage
  const [journal, setJournal] = useState(loadJournal());
  const [showJournal, setShowJournal] = useState(false);
  const [pro, setPro] = useState(loadPro());
  const [showPaywall, setShowPaywall] = useState(false);
  const [legalTab, setLegalTab] = useState(null); // null | "apropos" | "confidentialite" | "conditions"
  const [payBusy, setPayBusy] = useState(false);   // tunnel en cours (redirection Stripe)
  const [payNote, setPayNote] = useState(null);    // "cancel" | "success" | null
  const [reassureHTML, setReassureHTML] = useState("");

  // L'habilitation « augmentée » est portée par un jeton (window.OraclePay,
  // défini dans paiement.js). On s'aligne dessus : au prêt du paiement, au
  // retour d'un tunnel Stripe, et à chaque changement de jeton.
  useEffect(() => {
    const sync = () => setPro(loadPro());
    const onReady = (e) => {
      sync();
      if (e && e.detail && e.detail.reassureHTML) setReassureHTML(e.detail.reassureHTML);
    };
    const onReturn = (e) => {
      sync();
      const st = e && e.detail && e.detail.status;
      if (st === "cancel") { setPayNote("cancel"); setShowPaywall(true); }
      else if (st === "success") { setPayNote("success"); }
    };
    window.addEventListener("oracle-entitlement-changed", sync);
    window.addEventListener("oracle-pay-ready", onReady);
    window.addEventListener("oracle-pay-return", onReturn);
    if (window.OraclePay && window.OraclePay.ready) {
      window.OraclePay.ready.then(() => {
        sync();
        if (window.OraclePay.getReassureHTML) setReassureHTML(window.OraclePay.getReassureHTML());
      });
    }
    return () => {
      window.removeEventListener("oracle-entitlement-changed", sync);
      window.removeEventListener("oracle-pay-ready", onReady);
      window.removeEventListener("oracle-pay-return", onReturn);
    };
  }, []);

  function addEntry(entry) {
    const next = [entry, ...journal];
    setJournal(next);saveJournal(next);
  }
  function deleteEntry(id) {
    const next = journal.filter((e) => e.id !== id);
    setJournal(next);saveJournal(next);
  }
  // prénom retenu depuis « Trouver un prénom » → garde une trace au journal (dédupliquée)
  function addNommageEntry(entry) {
    setJournal((prev) => {
      if (prev.some((e) => e.fromNommage && e.nomKey === entry.nomKey)) return prev;
      const next = [entry, ...prev];saveJournal(next);return next;
    });
  }
  // Active l'abonnement via window.OraclePay.
  //  • mode "live"  : redirige vers la page de paiement Stripe (la page
  //    quitte ; le déverrouillage se fait au retour, via le jeton).
  //  • mode "mock"  : forge un jeton local et déverrouille tout de suite.
  async function activate(plan) {
    setPayNote(null);
    if (!window.OraclePay) { setPro(true); savePro(true); setShowPaywall(false); return; }
    setPayBusy(true);
    try {
      const r = await window.OraclePay.startCheckout(plan);
      if (r && r.redirect) return;               // navigation vers Stripe en cours
      if (r && r.ok) { setPro(loadPro()); setShowPaywall(false); }
      else { setPayNote("error"); }
    } finally { setPayBusy(false); }
  }
  async function cancelPro() {
    if (!window.OraclePay) { setPro(false); savePro(false); setShowPaywall(false); return; }
    const r = await window.OraclePay.openPortal();
    if (r && r.redirect) return;                 // navigation vers le portail Stripe
    setPro(loadPro());
    setShowPaywall(false);
  }

  // Routing léger — restauré après bascule racine→site/ (les fiches statiques en dépendent) :
  //  • ?prenom=…&nom=… → ouvre directement la lecture (liens « prénoms similaires »).
  //  • #dico/#nommage/#prenom → persiste l'écran (retour arrière revient au dico, pas à l'accueil).
  useEffect(() => {
    try {
      const params = new URLSearchParams(window.location.search);
      const pr = (params.get("prenom") || "").trim();
      if (pr) {
        setPending({ prenom: pr, nom: (params.get("nom") || "").trim() });
        setSpace("prenom");
        window.history.replaceState(null, "", window.location.pathname + "#prenom");
        return;
      }
      const h = (window.location.hash || "").replace(/^#/, "");
      if (h === "dico" || h === "nommage" || h === "prenom") setSpace(h);
    } catch (e) {/* noop */}
  }, []);

  useEffect(() => {
    const want = space ? "#" + space : "";
    if ((window.location.hash || "") !== want) {
      try { window.history.replaceState(null, "", window.location.pathname + want); } catch (e) {/* noop */}
    }
  }, [space]);

  const PrenomSpace = window.PrenomSpace;
  const NommageSpace = window.NommageSpace;
  const DictionarySpace = window.DictionarySpace;
  const ctx = { pro: pro, openPaywall: () => setShowPaywall(true) };

  const spaceLabel = space === "prenom" ? "Essence du prénom" : space === "dico" ? "Dictionnaire" : "Trouver un prénom";

  return (
    <OracleCtx.Provider value={ctx}>
      <div className="stage">
        <header className="topbar">
          <button className="brand" onClick={() => setSpace(null)} title="Retour à l'entrée">
            <span className="brand-mark"></span>
            <span className="brand-name">NomPrénom</span>
            {space && <span className="brand-space">/ {spaceLabel}</span>}
          </button>
          <div className="topbar-right">
            <button className={"plan-pill" + (pro ? " plan-pill-pro" : "")} onClick={() => setShowPaywall(true)}
              title="Double-clic : basculer Augmenté / Gratuit (vérification, sans paiement)"
              onDoubleClick={(e) => { e.preventDefault(); const nv = !pro; savePro(nv); setPro(nv); }}>
              <span className="plan-pill-dot"></span><span className="plan-pill-txt">{pro ? "Augmenté" : "Gratuit"}</span>
            </button>
            <button className="ghost-btn" onClick={() => setShowJournal(true)}>
              <span className="dot"></span>Journal{journal.length > 0 ? " · " + journal.length : ""}
            </button>
          </div>
        </header>

        <main className="shell">
          {space === null && <Landing onChoose={setSpace} />}
          {space === "prenom" && PrenomSpace && <PrenomSpace onSave={addEntry} pending={pending} onConsumePending={() => setPending(null)} />}
          {space === "nommage" && NommageSpace && <NommageSpace onJournal={addNommageEntry} onOpenFiche={(prenom, nom) => { setPending({ prenom: prenom, nom: nom || "" }); setSpace("prenom"); }} />}
          {space === "dico" && DictionarySpace && <DictionarySpace onOpenFiche={(prenom, nom) => { setPending({ prenom: prenom, nom: nom || "" }); setSpace("prenom"); }} />}
        </main>

        <Footer onOpen={setLegalTab} />

        {showJournal &&
        <Journal entries={journal} onClose={() => setShowJournal(false)} onDelete={deleteEntry} />
        }
        {showPaywall &&
        <Paywall pro={pro} onActivate={activate} onCancel={cancelPro} onClose={() => { setShowPaywall(false); setPayNote(null); }} busy={payBusy} note={payNote} reassureHTML={reassureHTML} onShareUnlock={() => setPro(loadPro())} shareLabel={space === "prenom" ? "ce prénom" : "notre famille"} />
        }
        {legalTab &&
        <Legal tab={legalTab} onTab={setLegalTab} onClose={() => setLegalTab(null)} />
        }
      </div>
    </OracleCtx.Provider>);

}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);