const { useState: useStateHL, useRef: useRefHL } = React;

/* ============================================================
   heartlink.jsx — Porte IV : Lien de cœur.  (Refonte DA 2026-07)
   ------------------------------------------------------------
   Lecture symbolique DÉTERMINISTE (heart-link.js, 5 axes) du lien entre
   deux prénoms. Aucune IA, aucun backend : tout se recalcule depuis les
   lettres, comme prenom.jsx / nommage.jsx.

   Refonte « plus produit » (demande de Seb) tout en gardant la charte
   terre & bois : hero MÉDAILLON (deux lettrines + jauge circulaire du
   score + nature), strip signature (élément de chacun + chiffre secret),
   signal rare en sceau, puis UNE SEULE grammaire de carte d'axe (au lieu
   des 5 widgets « compare » différents d'avant) — chaque carte : accent
   coloré par axe, en-tête (nom + accord chiffré), bande « duo » qui montre
   la vraie valeur de CHAQUE prénom reliée par un mot déduit du calcul, puis
   le texte. Boussole polie, récit éditorial, verrou soigné, apparitions
   animées (.heart-rise). Le CSS vit dans le <style> de index.html.

   Aperçu gratuit : nature + score + axe éléments + chiffre secret + signal
   rare (les leviers de partage restent ouverts). Verrou PRO : les 4 autres
   axes, la boussole, le récit, la question, l'enregistrement au Journal.
   Le partage (image + WhatsApp) reste GRATUIT : c'est le levier de
   croissance, pas une récompense.
   ============================================================ */

/* Métadonnées d'axe : couleur (charte) + teinte rgba translucide (pas de
   color-mix, compatibilité maximale) + libellé + aide. Chaque carte reçoit
   ces deux couleurs en variables CSS inline (--axc / --axc-tint). */
var HL_AXIS = {
  elements:   { label: "Éléments",          help: "le terrain commun",                                   color: "#8f4527", tint: "rgba(143,69,39,.13)" },
  phonetique: { label: "Rencontre sonore",  help: "fluidité et contraste, pas seulement la ressemblance", color: "#4f7a86", tint: "rgba(79,122,134,.14)" },
  rythme:     { label: "Rythme",            help: "la cadence des deux prénoms ensemble",                 color: "#bd9a40", tint: "rgba(189,154,64,.18)" },
  nombre:     { label: "Nombre",            help: "l'accord des gématries",                               color: "#6b7a3a", tint: "rgba(107,122,58,.16)" },
  lettres:    { label: "Lettres partagées", help: "ce que les deux graphies ont en commun",               color: "#7a5a39", tint: "rgba(122,90,57,.15)" }
};
var HL_ORDER = ["elements", "phonetique", "rythme", "nombre", "lettres"];

function firstSentenceHL(text) {
  var t = String(text || ""); var i = t.indexOf(". ");
  return i === -1 ? t : t.slice(0, i + 1);
}
function elementRelationWord(d) {
  if (d.a === d.b) return "même dominante";
  if (d.opposed) return "s'opposent";
  if (d.score >= 65) return "se complètent";
  return "coexistent";
}

function LockGlyph() {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <rect x="5" y="11" width="14" height="9" rx="2"></rect>
      <path d="M8 11V8a4 4 0 0 1 8 0v3"></path>
    </svg>);
}

/* ---------------------------------------------------------------------
   LA MOYENNE NE PARLE PAS. LE MEILLEUR ACCORD, SI.  (1er août 2026)
   ---------------------------------------------------------------------
   Le score global trônait en haut du résultat, en gros, dans une jauge
   circulaire. C'était la première chose qu'on lisait — et la moins
   parlante : une moyenne de cinq axes tombe presque toujours entre 50 et
   69 (mesuré : 87 % des couples du dictionnaire, écart-type 6,2). Tout le
   monde recevait donc à peu près le même chiffre tiède, et le couple le
   plus remarquable du corpus ressemblait à n'importe quel autre.

   Ce qui distingue un couple, c'est là où il se détache : Roméo & Juliette
   sont devant 92 % des couples en phonétique. Cette phrase-là dit quelque
   chose ; « 65/100 » ne dit rien.

   Donc : le meilleur accord passe en tête, en grand, avec son rang. Le
   score global descend en fin de lecture, dans une ligne sobre — il reste
   consultable, il ne commande plus l'impression.

   Corollaire : on classe sur le RANG, pas sur le score brut. Un 74 en
   rythme n'est que le 43ᵉ centile ; un 74 en lettres serait remarquable.
   Trier sur le score brut remettrait en avant l'axe le plus généreux, pas
   le plus distinctif.
   --------------------------------------------------------------------- */

/** Le rang d'un axe, avec repli sur le score si la table de référence manque. */
function rangDe(d) {
  return (d && typeof d.rang === "number") ? d.rang : ((d && d.score) || 0);
}

/** Les axes du plus distinctif au plus commun. */
function axesParRang(res, ordre) {
  return ordre.slice().sort(function (x, y) {
    var dx = rangDe(res.dims[y]) - rangDe(res.dims[x]);
    return dx !== 0 ? dx : (res.dims[y].score - res.dims[x].score);
  });
}

/* ---------------- Sceau : jauge circulaire du score ----------------
   PLUS APPELÉ depuis le 1er août 2026 — le score global ne trône plus en
   tête du résultat (voir la note ci-dessus). Conservé tel quel : la carte
   à partager pourrait vouloir un jour cette jauge, et son CSS vit encore
   dans index.html. Si dans six mois personne ne l'a rappelé, la supprimer
   avec ses styles plutôt que la laisser vieillir en silence. */
function HeartSeal({ score }) {
  var C = 2 * Math.PI * 60;               // r = 60
  var off = C * (1 - Math.max(0, Math.min(100, score)) / 100);
  return (
    <div className="heart-seal">
      <svg viewBox="0 0 148 148">
        <defs>
          <linearGradient id="heartSealGrad" x1="0" y1="0" x2="1" y2="1">
            <stop offset="0" stopColor="#b15b36" />
            <stop offset="1" stopColor="#8f4527" />
          </linearGradient>
        </defs>
        <circle className="heart-seal-track" cx="74" cy="74" r="60" />
        <circle className="heart-seal-arc" cx="74" cy="74" r="60" strokeDasharray={C.toFixed(2)} strokeDashoffset={off.toFixed(2)} />
      </svg>
      <div className="heart-seal-mid">
        <span className="heart-seal-num">{score}</span>
        <span className="heart-seal-max">/ 100</span>
      </div>
    </div>);
}

/* ---------------- Ce que ce couple a de remarquable ----------------
   Remplace la jauge du score global en tête de résultat : l'axe le mieux
   classé, la seule chose du résultat qui distingue ce couple des autres.

   L'AXE EST TOUJOURS NOMMÉ, le rang seulement quand il y a de quoi. Mesuré
   sur 3 996 couples tirés du dictionnaire, 24 % n'ont aucun axe au-dessus
   du seuil de mention : se taire dans ce cas aurait laissé un quart des
   résultats avec un blanc là où trônait le score. « L'accord le plus fort :
   Rythme » reste vrai — c'est bien là que ce couple tient le mieux — sans
   prétendre que c'est un exploit. On ne décore pas, on ne ment pas. */
function HeartPointFort({ res }) {
  var ordre = axesParRang(res, HL_ORDER);
  var k = ordre[0], d = res.dims[k];
  if (!d) return null;
  var meta = HL_AXIS[k] || {};
  return (
    <div className={"heart-fort" + (d.mention ? " is-notable" : "")}>
      <span className="heart-fort-k">L'accord le plus fort</span>
      <span className="heart-fort-axe">{meta.label || k}</span>
      {d.mention && <span className="heart-fort-rang">{d.mention}</span>}
    </div>);
}

/* ---------------- Hero médaillon ---------------- */
function HeartHero({ res, cover }) {
  const Lettrine = window.Lettrine;
  return (
    <div className="heart-hero">
      {cover ?
        <img src={cover} alt="" className="heart-cover" draggable="false" /> :
        (Lettrine &&
          <div className="heart-plate">
            <Lettrine mot={res.a.label} size={100} />
            <span className="heart-amp2">&amp;</span>
            <Lettrine mot={res.b.label} size={100} />
          </div>)}
      <h2 className="heart-names">
        {res.fullMode ? res.a.fullLabel : res.a.label} <em>&amp;</em> {res.fullMode ? res.b.fullLabel : res.b.label}
      </h2>
      {/* Là où le sceau du score global trônait : ce que ce couple a de
          remarquable. Voir la note en tête de fichier. */}
      <HeartPointFort res={res} />
      <div className="heart-nature">{res.nature.label}</div>
      {res.fullMode && <p className="name-cat-help" style={{ marginTop: "8px" }}>calculé sur prénom + nom, pas seulement le prénom</p>}
      {res.oddsText && <p className="heart-odds">{res.oddsText}</p>}
    </div>);
}

/* ---------------- Strip signature ---------------- */
function HeartSignature({ res }) {
  return (
    <div className="heart-sig">
      <span className="heart-sig-chip"><span className="heart-sig-dot" style={{ background: res.a.elemColor }}></span><b>{res.a.label}</b> · {res.a.sig.elements[0]}</span>
      <span className="heart-sig-chip"><span className="heart-sig-dot" style={{ background: res.b.elemColor }}></span><b>{res.b.label}</b> · {res.b.sig.elements[0]}</span>
      <span className="heart-sig-chip"><span className="diamond">◆</span> Chiffre secret <span className="cipher">{res.cipher}</span></span>
    </div>);
}

/* ---------------- Signal rare (sceau) ---------------- */
function HeartSignal({ secret }) {
  if (!secret) return null;
  return (
    <div className="heart-signal">
      <span className="heart-signal-seal">✦</span>
      <span className="heart-signal-tag">Signal rare · {secret.label}</span>
      <p className="heart-signal-text">{secret.texte}</p>
    </div>);
}

/* ---------------- Corps « duo » de chaque axe ----------------
   Une seule grammaire : la valeur RÉELLE de chaque prénom, reliée par un
   mot (ou une mini-échelle pour le nombre, des pastilles pour les lettres)
   déduit du calcul. Répond au « on ne comprend pas le rapport entre chaque
   nom sur chaque axe » : c'est toujours A · relation · B. */
function HeartAxisBody({ k, res }) {
  const d = res.dims[k];
  const HL = window.HeartLink;

  if (k === "elements") {
    const cA = (HL && HL.ELEM_COLOR[d.a]) || "#7a5a39";
    const cB = (HL && HL.ELEM_COLOR[d.b]) || "#7a5a39";
    return (
      <div className="heart-duo">
        <div className="heart-duo-side"><span className="heart-duo-name">{res.a.label}</span><span className="heart-elem-chip" style={{ background: cA }}>{d.a}</span></div>
        <span className="heart-rel">{elementRelationWord(d)}</span>
        <div className="heart-duo-side"><span className="heart-duo-name">{res.b.label}</span><span className="heart-elem-chip" style={{ background: cB }}>{d.b}</span></div>
      </div>);
  }

  if (k === "phonetique") {
    return (
      <div className="heart-duo">
        <div className="heart-duo-side"><span className="heart-duo-name">{res.a.label}</span><span className="heart-duo-val">{d.attackA || "—"}<small> · {d.endA || "—"}</small></span></div>
        <span className="heart-rel">{d.signatureLabel}</span>
        <div className="heart-duo-side"><span className="heart-duo-name">{res.b.label}</span><span className="heart-duo-val">{d.attackB || "—"}<small> · {d.endB || "—"}</small></span></div>
      </div>);
  }

  if (k === "rythme") {
    const gap = Math.abs(d.syllA - d.syllB);
    const rel = gap === 0 ? "même cadence" : (gap === 1 ? "écart léger" : "écart de " + gap);
    return (
      <div className="heart-duo">
        <div className="heart-duo-side"><span className="heart-duo-name">{res.a.label}</span><span className="heart-duo-val">{d.syllA} <small>syll.</small></span></div>
        <span className="heart-rel">{rel}</span>
        <div className="heart-duo-side"><span className="heart-duo-name">{res.b.label}</span><span className="heart-duo-val">{d.syllB} <small>syll.</small></span></div>
      </div>);
  }

  if (k === "nombre") {
    const gap = Math.abs(d.a - d.b);
    const rel = d.mirror ? "miroir" : (gap === 0 ? "même nombre" : "écart de " + gap);
    const posA = ((d.a - 1) / 8) * 100, posB = ((d.b - 1) / 8) * 100;
    const left = Math.min(posA, posB), width = Math.abs(posA - posB);
    return (
      <React.Fragment>
        <div className="heart-duo">
          <div className="heart-duo-side"><span className="heart-duo-name">{res.a.label}</span><span className="heart-duo-val">{d.a}</span></div>
          <span className="heart-rel">{rel}</span>
          <div className="heart-duo-side"><span className="heart-duo-name">{res.b.label}</span><span className="heart-duo-val">{d.b}</span></div>
        </div>
        <div className="heart-mini">
          <div className="heart-mini-ends"><span>1</span><span>9</span></div>
          <div className="heart-mini-track">
            <div className="heart-mini-fill" style={{ left: left + "%", width: width + "%" }}></div>
            <div className="heart-mini-dot" style={{ left: posA + "%" }}></div>
            <div className="heart-mini-dot" style={{ left: posB + "%", opacity: .6 }}></div>
          </div>
        </div>
      </React.Fragment>);
  }

  // lettres
  if (!d.letters.length) return <p className="heart-shared-none">Aucune lettre commune entre les deux graphies — ce qui les relie se joue ailleurs.</p>;
  return (
    <div className="heart-shared">
      <div className="heart-shared-tags">
        {d.letters.map((l, i) => <span className="heart-shared-tag" key={i}>{l}</span>)}
      </div>
      <span className="heart-shared-cap">{d.letters.length} lettre{d.letters.length > 1 ? "s" : ""} en commun</span>
    </div>);
}

function HeartAxisCard({ k, res, locked, tag, fort }) {
  const meta = HL_AXIS[k];
  const d = res.dims[k];
  const ctx = React.useContext(window.OracleCtx);
  const openPaywall = ctx && ctx.openPaywall;
  // Carte verrouillée = vraie invite cliquable (ouvre le paywall), pas un
  // simple flou décoratif : cohérent avec le curseur/hover "à débloquer".
  const lockProps = locked ? {
    onClick: openPaywall, role: "button", tabIndex: 0,
    onKeyDown: (e) => { if (openPaywall && (e.key === "Enter" || e.key === " ")) { e.preventDefault(); openPaywall(); } },
    "aria-label": meta.label + " — débloquer avec l'Oracle complet"
  } : {};
  return (
    <div {...lockProps} className={"heart-axis" + (locked ? " is-locked" : "") + (fort ? " is-fort" : "")} style={{ "--axc": meta.color, "--axc-tint": meta.tint }}>
      <div className="heart-axis-head">
        <span className="heart-axis-name">
          <span className="heart-axis-dot"></span>{meta.label}
          {!locked && <span className="heart-axis-help">{meta.help}</span>}
        </span>
        {locked
          ? <span className="heart-axis-lock"><LockGlyph /> Oracle complet</span>
          : <span className="heart-axis-chip">{d.score}<small>/100</small></span>}
      </div>
      {/* Le rang situe le score : un 74 vaut le 43e centile en rythme, mais
          serait remarquable en lettres. On ne l'affiche que lorsqu'il y a
          quelque chose de bon a dire — jamais pour signaler un bas classement.

          Il s'affiche MÊME VERROUILLÉ : c'est le seul endroit où un visiteur
          gratuit apprend que ce couple a quelque chose de remarquable. Cacher
          « devant 92 % des couples » derrière le paywall revenait à garder
          l'argument de vente pour ceux qui ont déjà acheté. La lecture
          détaillée, elle, reste payante. */}
      {d.mention && <div className="heart-axis-rang">{d.mention}</div>}
      {tag && <div className="heart-axis-tag">{tag}</div>}
      <div className="heart-axis-body">
        <HeartAxisBody k={k} res={res} />
        <div className="heart-axis-detail"><p>{locked ? "Cet accord fait partie de l'Oracle complet — la lecture détaillée des deux prénoms sur cet axe t'y attend." : d.detail}</p></div>
      </div>
    </div>);
}

function HeartAxes({ res, pro }) {
  /* Le classement se fait sur le RANG, pas sur le score brut, et dans les
     deux modes. Avant, le tri n'existait qu'en payant — et il portait sur
     le score, ce qui remontait l'axe le plus généreux plutôt que celui où
     le couple se détache vraiment. En gratuit, l'ordre était figé : le
     meilleur accord pouvait se retrouver quatrième, sous des cadenas. */
  const order = axesParRang(res, HL_ORDER);
  const topKey = order[0], lowKey = order[order.length - 1];
  return (
    <div className="heart-axes">
      {order.map((k, i) => {
        const locked = !pro && k !== "elements";
        let tag = null;
        if (k === topKey) tag = "Accord le plus fort";
        else if (pro && k === lowKey) tag = "Le plus contrasté";
        // Le premier est traité en grand : c'est lui qui porte le résultat.
        return <HeartAxisCard key={k} k={k} res={res} locked={locked} tag={tag} fort={i === 0} />;
      })}
    </div>);
}

/* ---------------- Boussole relationnelle ---------------- */
function CompassBlock({ compass, locked }) {
  if (!compass) return null;
  return (
    <React.Fragment>
      <div className="heart-section">
        <div className="eyebrow">Lecture</div>
        <div className="heart-section-title">La boussole relationnelle</div>
        <p className="name-cat-help">Quatre lectures tirées des cinq accords.</p>
      </div>
      {locked ?
        <div className="heart-compass" style={{ filter: "blur(5px)", userSelect: "none", opacity: .85 }}>
          <div className="heart-compass-axis">
            <div className="heart-compass-head"><span className="heart-compass-label">Se reconnaître · Se différencier · Se rencontrer · Traverser la tension</span></div>
            <p>{compass.axes.recognition.reading} {compass.axes.differentiation.reading}</p>
          </div>
        </div> :
        <div className="heart-compass">
          {compass.order.map((key) => {
            const ax = compass.axes[key];
            const lc = ax.levelColor;
            return (
              <div className="heart-compass-axis" key={key}>
                <div className="heart-compass-head">
                  <span className="heart-compass-label">{ax.label}</span>
                  <span className="heart-compass-level" style={lc ? { background: lc.bg, color: lc.text } : null}>{ax.level}</span>
                  <span className="heart-compass-score">{ax.score}/100</span>
                </div>
                <p>{ax.insight || ax.reading}</p>
              </div>);
          })}
          <div className="heart-compass-growth">
            <span className="tag">Piste de croissance · {compass.growth.label}</span>
            <p>{compass.growth.question}</p>
          </div>
          <p className="heart-compass-note">{compass.methodologyNote}</p>
        </div>}
    </React.Fragment>);
}

/* ---------------- Récit du lien ---------------- */
function TaleBlock({ narrative }) {
  if (!narrative) return null;
  return (
    <React.Fragment>
      <div className="heart-section">
        <div className="eyebrow">Récit</div>
        <div className="heart-section-title">Le récit du lien</div>
        <p className="name-cat-help">Une lecture racontée, tirée des cinq accords.</p>
      </div>
      <div className="heart-tale">
        <h3 className="heart-tale-title">{narrative.title}</h3>
        <p className="heart-tale-lead">{narrative.opening}</p>
        <p>{narrative.secret}</p>
        <p>{narrative.closing}</p>
      </div>
    </React.Fragment>);
}

/* ---------------- Verrou (aperçu gratuit) ---------------- */
function HeartLockCard({ openPaywall }) {
  return (
    <div className="heart-lock">
      <div className="heart-lock-eyebrow">Le lien complet</div>
      <p className="heart-lock-text">Tu vois l'essentiel — la nature du lien, le score, l'axe éléments et le chiffre secret. L'<b>Oracle complet</b> ouvre le reste :</p>
      <ul className="heart-lock-list">
        <li className="heart-lock-item"><span className="k">◆</span> Les quatre autres accords : rencontre sonore, rythme, nombre, lettres partagées.</li>
        <li className="heart-lock-item"><span className="k">◆</span> La boussole relationnelle — se reconnaître, se différencier, se rencontrer, traverser la tension — et sa piste de croissance.</li>
        <li className="heart-lock-item"><span className="k">◆</span> Le récit du lien, propre à votre duo, et la question à se poser.</li>
        <li className="heart-lock-item"><span className="k">◆</span> Garder ce lien dans ton Journal.</li>
      </ul>
      <button className="heart-lock-btn" onClick={openPaywall}>
        <span className="k">Avec l'offre augmentée</span>
        <span className="t">Débloquer le lien complet →</span>
      </button>
    </div>);
}

/* ---------------- Carte à partager (image + WhatsApp, gratuite) ---------------- */
function HeartShareCard({ res, onClose }) {
  const ref = useRefHL(null);
  const [busy, setBusy] = useStateHL(false);
  const A = window.lettrineFor(res.a.label);
  const B = window.lettrineFor(res.b.label);
  const fileBase = "lien-de-coeur-" + res.a.label.toLowerCase() + "-" + res.b.label.toLowerCase();

  function waText() {
    return "On a lu le lien de cœur entre " + res.a.label + " et " + res.b.label + " sur NomPrénom — "
      + res.nature.label + " (" + res.score + "/100, chiffre secret " + res.cipher + ")"
      + (res.secret ? " avec un signal rare : " + res.secret.label + "." : ".")
      + " Essaie avec vos prénoms, c'est calculé depuis les lettres : "
      + (window.location.origin + window.location.pathname + "#heartlink");
  }
  function shareWhatsapp() {
    var enc = encodeURIComponent(waText());
    var isMobile = /Android|iPhone|iPad|iPod|Mobile/i.test(navigator.userAgent || "");
    var url = (isMobile ? "whatsapp://send?text=" : "https://api.whatsapp.com/send?text=") + enc;
    try {
      var win = window.open(url, "_blank");
      if (!win && isMobile) window.open("https://api.whatsapp.com/send?text=" + enc, "_blank");
    } catch (e) {/* silencieux */}
  }
  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 = fileBase + ".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>
            <span className="share-cat">Lien de cœur</span>
          </div>
          <div className="share-plate" style={{ gap: "3%" }}>
            {A.src ? <img className="share-letter-img" src={A.src} alt={"Lettrine " + A.letter} draggable="false" style={{ width: "38%" }} /> : <span className="share-letter-fallback" style={{ fontSize: "clamp(60px,20vw,100px)" }}>{A.letter}</span>}
            <span style={{ fontFamily: "'Spectral',serif", fontStyle: "italic", fontSize: "clamp(20px,6vw,28px)", color: "var(--clay-deep)", position: "relative", zIndex: 2 }}>&amp;</span>
            {B.src ? <img className="share-letter-img" src={B.src} alt={"Lettrine " + B.letter} draggable="false" style={{ width: "38%" }} /> : <span className="share-letter-fallback" style={{ fontSize: "clamp(60px,20vw,100px)" }}>{B.letter}</span>}
          </div>
          <div className="share-foot">
            <div className="share-word">{res.a.label} &amp; {res.b.label}</div>
            <div className="share-piste">{res.nature.label} · {res.score}/100 · nº{res.cipher}</div>
            <div className="share-ribbon">calculé depuis les lettres · ni voyance ni destin</div>
          </div>
        </div>
        <div className="share-actions">
          <button className="btn-primary" onClick={shareWhatsapp}>Partager sur WhatsApp</button>
          <button className="ghost-btn" onClick={download} disabled={busy}>{busy ? "Préparation…" : "Télécharger l'image"}</button>
          <span className="hint-inline">une image à garder ou partager</span>
        </div>
      </div>
    </React.Fragment>);
}

/* ---------------- Écran de résultat ---------------- */
function HeartLinkResult({ res, onSave, onReset, saved }) {
  const { pro, openPaywall } = React.useContext(window.OracleCtx);
  const [share, setShare] = useStateHL(false);

  return (
    <div className="fade-screen">
      <div className="eyebrow heart-rise">Lien de cœur</div>

      <div className="heart-rise"><HeartHero res={res} /></div>
      <div className="heart-rise"><HeartSignature res={res} /></div>
      {res.secret && <div className="heart-rise"><HeartSignal secret={res.secret} /></div>}

      <div className="heart-rise">
        <div className="heart-section">
          <div className="eyebrow">La lecture</div>
          <div className="heart-section-title">Les cinq accords</div>
          {/* Le texte disait « Le premier est ouvert » — vrai tant que l'ordre
              était figé et que « Éléments » venait en tête. Depuis que les
              accords sont classés par rang, le premier est le plus distinctif,
              et ce n'est pas forcément celui qui est offert. */}
          <p className="name-cat-help">{pro
            ? "Calculés depuis les lettres des deux prénoms — du plus distinctif au plus commun."
            : "Classés du plus distinctif au plus commun. L'accord des éléments est ouvert ; les quatre autres t'attendent dans l'Oracle complet."}</p>
        </div>
        <HeartAxes res={res} pro={pro} />
      </div>

      {pro &&
      <div className="heart-rise">
        <CompassBlock compass={res.compass} locked={false} />
        <TaleBlock narrative={res.narrative} />
        {res.question && <p className="heart-question">{res.question}</p>}
      </div>}

      {!pro && <div className="heart-rise"><HeartLockCard openPaywall={openPaywall} /></div>}

      {/* Le score global, en fin de lecture et en petit. Il a quitté le haut
          du résultat : une moyenne de cinq axes tombe presque toujours entre
          50 et 69, elle ne distingue personne et donnait le ton avant que
          quoi que ce soit d'intéressant ait été dit. On ne le supprime pas —
          il chiffre l'ensemble, et certains y tiennent — mais il conclut au
          lieu d'annoncer. La jauge circulaire, elle, ne revient pas. */}
      <div className="heart-total heart-rise">
        <span className="heart-total-k">Moyenne des cinq accords</span>
        <span className="heart-total-num">{res.score}<small>/100</small></span>
        <span className="heart-total-note">Un couple se lit à ce qui le distingue, pas à sa moyenne.</span>
      </div>

      <div className="heart-actions heart-rise">
        <button className="btn-primary" onClick={onReset}>← Lire un autre lien</button>
        <button className="ghost-btn" onClick={() => setShare(true)}><span className="dot"></span>Partager ce lien</button>
        {pro &&
        <button className="ghost-btn" disabled={saved} onClick={onSave}><span className="dot"></span>{saved ? "Enregistré au Journal" : "Garder au Journal"}</button>}
      </div>
      <p className="heart-ruban">calculé depuis les lettres · ni voyance ni destin — un lien à interroger, pas un verdict</p>

      {share && <HeartShareCard res={res} onClose={() => setShare(false)} />}
    </div>);
}

/* ---------------- Exemple compact (écran de saisie) ----------------
   Une carte médaillon compacte par défaut (pour ne pas noyer l'écran de
   saisie sous un résultat complet), qui se déplie en exemple complet — le
   même moteur, rien en dur (seuls les deux prénoms le sont). Alice &
   Baptiste : couple qui fait apparaître un signal rare + correspond à la
   gravure cœur A/B (le seul visuel dédié qu'on possède). */
function HeartLinkExample() {
  const [open, setOpen] = useStateHL(false);
  const ex = window.HeartLink && window.HeartLink.compute("Alice", "Baptiste");
  if (!ex) return null;
  const line = ex.secret ? ("Un signal rare les traverse : " + ex.secret.label + ".") : firstSentenceHL(ex.nature.texte);
  return (
    <div className="heart-example">
      <p className="name-cat-help" style={{ marginBottom: "14px" }}>Un exemple, pour voir avant d'essayer</p>
      <div className="heart-ex-card">
        <img className="heart-ex-cover" src="heart-cover-ab.png" alt="" draggable="false" />
        <div className="heart-ex-body">
          <div className="heart-ex-names">Alice <em>&amp;</em> Baptiste</div>
          <div className="heart-ex-meta">{ex.nature.label} · <b>{ex.score}/100</b> · nº{ex.cipher}</div>
          <p className="heart-ex-line">{line}</p>
        </div>
      </div>
      <div className="heart-ex-more">
        <button className="ghost-btn" onClick={() => setOpen(!open)}><span className="dot"></span>{open ? "Replier l'exemple ↑" : "Voir l'exemple complet →"}</button>
      </div>

      {open &&
      <div className="heart-ex-full">
        <HeartHero res={ex} cover="heart-cover-ab.png" />
        <HeartSignature res={ex} />
        {ex.secret && <HeartSignal secret={ex.secret} />}
        <div className="heart-section">
          <div className="eyebrow">La lecture</div>
          <div className="heart-section-title">Les cinq accords</div>
          <p className="name-cat-help">Calculés depuis les lettres des deux prénoms.</p>
        </div>
        <HeartAxes res={ex} pro={true} />
        <CompassBlock compass={ex.compass} locked={false} />
        <TaleBlock narrative={ex.narrative} />
        <p className="name-cat-help" style={{ marginTop: "14px" }}>Tout ce que tu viens de voir — les cinq accords, la boussole et le récit — c'est le contenu de l'Oracle complet.</p>
      </div>}
    </div>);
}

/* ---------------- Espace « Lien de cœur » ---------------- */
function HeartLinkSpace({ onJournal }) {
  const [a, setA] = useStateHL("");
  const [b, setB] = useStateHL("");
  const [nomA, setNomA] = useStateHL("");
  const [nomB, setNomB] = useStateHL("");
  const [showNames, setShowNames] = useStateHL(false);
  const [res, setRes] = useStateHL(null);
  const [saved, setSaved] = useStateHL(false);

  function lire() {
    if (!a.trim() || !b.trim()) return;
    const baseline = window.nommageBaseline ? window.nommageBaseline() : null;
    const r = window.HeartLink && window.HeartLink.compute(a, b, baseline, nomA, nomB);
    if (!r) return;
    setRes(r); setSaved(false);
    try { window.scrollTo({ top: 0, behavior: "smooth" }); } catch (e) { window.scrollTo(0, 0); }
  }
  function reset() { setRes(null); setA(""); setB(""); setNomA(""); setNomB(""); setShowNames(false); setSaved(false); }
  function garder() {
    if (!res || saved) return;
    onJournal && onJournal({
      id: Date.now(), space: "heartlink",
      a: res.fullMode ? res.a.fullLabel : res.a.label,
      b: res.fullMode ? res.b.fullLabel : res.b.label,
      score: res.score, nature: res.nature.label,
      synthese: res.synthese
    });
    setSaved(true);
  }

  if (res) return <HeartLinkResult res={res} onSave={garder} onReset={reset} saved={saved} />;

  return (
    <div className="fade-screen">
      <div className="eyebrow">Lien de cœur</div>
      <h1 className="lede">Ce que deux prénoms <em>tiennent ensemble</em>.</h1>
      <p className="sub">Une lecture symbolique du lien entre deux prénoms — le tien et celui d'un proche, d'un ami, d'un partenaire. Calculée depuis les lettres, comme le reste de l'oracle : ni voyance, ni verdict de compatibilité. Un miroir à interroger à deux.</p>

      <div className="field">
        <div className="heart-duo-field">
          <div className="prenom-input-wrap">
            <label className="field-label" htmlFor="hlA">Premier prénom</label>
            <input id="hlA" className="prenom-input" type="text" autoComplete="off" placeholder="Le tien…" value={a}
              onChange={(e) => setA(e.target.value)} onKeyDown={(e) => e.key === "Enter" && lire()} />
          </div>
          <span className="heart-amp">&amp;</span>
          <div className="prenom-input-wrap">
            <label className="field-label" htmlFor="hlB">Second prénom</label>
            <input id="hlB" className="prenom-input" type="text" autoComplete="off" placeholder="Celui de l'autre…" value={b}
              onChange={(e) => setB(e.target.value)} onKeyDown={(e) => e.key === "Enter" && lire()} />
          </div>
        </div>

        <div className={"heart-more" + (showNames ? " open" : "")}>
          <button type="button" className="heart-more-toggle" onClick={() => setShowNames(!showNames)}>
            <span className="chev"></span>{showNames ? "Masquer les noms de famille" : "Ajouter les noms de famille"}
          </button>
          {showNames &&
          <React.Fragment>
            <div className="heart-more-panel">
              <div className="prenom-input-wrap">
                <label className="field-label" htmlFor="hlNomA">Son nom <span className="opt">— {a.trim() || "elle/lui"}</span></label>
                <input id="hlNomA" className="prenom-input" type="text" autoComplete="off" placeholder="…" value={nomA}
                  onChange={(e) => setNomA(e.target.value)} onKeyDown={(e) => e.key === "Enter" && lire()} />
              </div>
              <span className="heart-amp">&amp;</span>
              <div className="prenom-input-wrap">
                <label className="field-label" htmlFor="hlNomB">Son nom <span className="opt">— {b.trim() || "elle/lui"}</span></label>
                <input id="hlNomB" className="prenom-input" type="text" autoComplete="off" placeholder="…" value={nomB}
                  onChange={(e) => setNomB(e.target.value)} onKeyDown={(e) => e.key === "Enter" && lire()} />
              </div>
            </div>
            <p className="prenom-hint">Une lecture sur l'identité complète (prénom + nom) plutôt que le seul prénom. Mets les deux, ou laisse vide.</p>
          </React.Fragment>}
        </div>

        <div className="actions">
          <button className="btn-primary" disabled={!a.trim() || !b.trim()} onClick={lire}>Lire le lien →</button>
          <span className="hint-inline">le tien, ou celui d'un proche</span>
        </div>
      </div>

      <div className="heart-legend">
        {HL_ORDER.map((k) => (
          <span className="heart-legend-chip" key={k}><span className="heart-legend-dot" style={{ background: HL_AXIS[k].color }}></span>{HL_AXIS[k].label}</span>
        ))}
      </div>

      <HeartLinkExample />
    </div>);
}

window.HeartLinkSpace = HeartLinkSpace;
