/* Page /plan-de-ciblage — le Plan de ciblage Boom Maker.
 *
 * Principe du produit : le prospect possède l'OBJECTIF et les FAITS, Boom Maker
 * possède la ROUTE. On ne lui demande donc que ce que lui seul sait (son meilleur
 * client, ce qui lui manque, où ça coince), et on lui apporte ce qu'il n'a pas
 * (les cercles de cibles, l'ICP qu'il n'a pas nommé, les angles).
 *
 * Anti-friction : il colle son site, on le lit, ON PRÉ-REMPLIT. Il corrige au lieu
 * de rédiger. Une seule vraie phrase à écrire (le clone), le reste en pastilles.
 *
 * Le scan dit la vérité : le cerveau met environ 90 secondes (mesuré). On annonce
 * « moins de 2 min » et on fait avancer de vraies étapes. C'est la surprise qui
 * fait fuir un prospect, pas l'attente annoncée.
 *
 * RESTE À BRANCHER :
 *   - le comptage Basile, qui donnera le chiffre de profondeur de marché (le
 *     cerveau produit les critères, jamais le nombre) ;
 *   - Stripe sur le bouton de déblocage ;
 *   - le PDF téléchargeable.
 */

const { useState, useEffect, useRef } = React;
const { Logo, ArrowUpRight, ThemeToggle, FooterMin } = window.BMShared;

const CALENDLY = 'https://calendly.com/julia-boommaker/30min';
const PRIX = 49;

/* Cadrage assumé du produit : on ne prétend pas dresser la carte exhaustive du
 * marché, on sort les trois cercles à activer en premier. Phrase écrite en dur
 * plutôt que demandée au modèle : c'est une promesse commerciale, elle ne doit
 * pas varier d'un plan à l'autre. Elle vaut aussi bien pour une offre sectorielle
 * qu'horizontale, et coupe court à l'objection « il en manque un ». */
const CADRAGE_CERCLES =
  'Ce sont les trois cercles qu\'on activerait en premier, pas la liste exhaustive de ton marché. D\'autres existent : ceux-là passent devant.';

const TAILLES = ['Solo', '1 à 10', '10 à 50', '50 à 250', '250 et +'];
const COINCE = ['Les trouver', 'Les contacter', 'Traiter les réponses', 'Les closer'];

/* Pièces jointes : Vercel plafonne le corps de requête à ~4,5 Mo et le base64
 * gonfle d'environ un tiers. Ces bornes sont aussi appliquées côté serveur. */
const MAX_OCTETS = 2 * 1024 * 1024;
/* Plafond sur le TOTAL, pas seulement par fichier : deux fichiers de 2 Mo font
 * 4 Mo, soit ~5,4 Mo une fois encodés, au-dessus de la limite de Vercel. La
 * requête serait alors rejetée AVANT d'atteindre le serveur, ce qui se traduit
 * par un « Failed to fetch » sans le moindre message. */
const MAX_TOTAL_OCTETS = 2.5 * 1024 * 1024;
const MAX_FICHIERS = 2;
const poidsLisible = (o) => (o < 1024 * 1024 ? `${Math.round(o / 1024)} Ko` : `${(o / 1024 / 1024).toFixed(1)} Mo`);

/* Quand le cerveau élargit la fourchette « jusqu'aux grands groupes », il pose une
 * borne haute technique (150000). Écrire « 20 à 150000 salariés » fait bricolé :
 * au-delà d'un vrai plafond d'ICP, on dit « et plus ». */
const effectifLisible = (min, max) =>
  Number.isFinite(max) && max >= 5000
    ? `${min} salariés et plus`
    : `${min} à ${max} salariés`;

/* Étapes du scan, calées sur la vraie durée mesurée (environ 90 s). La dernière
 * tient tant que la réponse n'est pas là : on n'annonce jamais une fin qui n'a
 * pas eu lieu. */
const ETAPES = [
  'Lecture de ton site…',
  'Analyse de ton activité et de tes valeurs…',
  'Identification de ton vrai levier…',
  'Construction de tes cercles de cibles…',
  'Repérage de la cible que tu n\'avais pas nommée…',
  'Recherche des angles qui déclenchent…',
  'Derniers recoupements…',
];

/* Le chiffre vient de Basile, jamais du modèle. Basile PLAFONNE son total à
 * 100 000 (vérifié : « toutes les entreprises françaises actives » renvoie 100000).
 * On écrit donc « plus de 100 000 » et jamais « 100 000 » : le chiffre héros du
 * produit ne doit pas être un plafond déguisé en compte. */
const nf = new Intl.NumberFormat('fr-FR');

const useCompteur = (cible, actif) => {
  const [n, setN] = useState(0);
  useEffect(() => {
    if (!actif || !cible) return;
    const reduit = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    if (reduit) { setN(cible); return; }
    let raf, debut = null;
    const pas = (t) => {
      if (!debut) debut = t;
      const p = Math.min((t - debut) / 1400, 1);
      setN(Math.round((1 - Math.pow(1 - p, 3)) * cible));
      if (p < 1) raf = requestAnimationFrame(pas);
    };
    raf = requestAnimationFrame(pas);
    // Filet : requestAnimationFrame est gelé si l'onglet passe en arrière-plan.
    const filet = setTimeout(() => setN(cible), 1600);
    return () => { cancelAnimationFrame(raf); clearTimeout(filet); };
  }, [cible, actif]);
  return n;
};

const BADGE_CLS = {
  'Priorité 1': 'dg-b-p1',
  'Angle non exploité': 'dg-b-new',
  'Quick win': 'dg-b-win',
};

/* L'exemple était en texte grisé : lisible, mais inutilisable. Pour s'en servir
 * il fallait le recopier à la main, donc presque personne ne le faisait et le
 * formulaire redevenait une page blanche. Un clic l'écrit dans le champ, il n'y
 * a plus qu'à corriger ce qui ne colle pas.
 * N'apparaît que si le champ est vide : une fois qu'il a écrit, proposer
 * d'écraser sa phrase serait un piège. */
const Exemple = ({ texte, vide, onUtiliser }) => {
  if (!texte || !vide) return null;
  return (
    <button type="button" className="dg-ex" onClick={onUtiliser}>
      <span aria-hidden="true">↵</span> Partir de cet exemple
    </button>
  );
};

const PageNav = () => {
  const [open, setOpen] = useState(false);
  return (
    <>
      <nav className="nav" id="nav">
        <a href="/" className="nav-logo" aria-label="Boom Maker"><Logo /></a>
        <div className="nav-links">
          <div className="nav-dropdown">
            <button type="button" className="nav-dropdown-trigger" aria-haspopup="true">
              Expertises <span className="nav-dropdown-caret">▾</span>
            </button>
            <div className="nav-dropdown-menu" role="menu">
              <a href="/expertises">3 piliers</a>
              <a href="/cas-usage">Cas d'usage</a>
              <a href="/partners">Partenaires</a>
            </div>
          </div>
          <a href="/tarifs">Tarifs</a>
          <a href="/plan-de-ciblage">Plan de ciblage</a>
          <a href="/news">News</a>
          <div className="nav-dropdown">
            <button type="button" className="nav-dropdown-trigger" aria-haspopup="true">
              À propos <span className="nav-dropdown-caret">▾</span>
            </button>
            <div className="nav-dropdown-menu" role="menu">
              <a href="/a-propos">BoomMaker</a>
              <a href="/#faq">FAQ</a>
            </div>
          </div>
          <a href="/contact">Contact</a>
        </div>
        <ThemeToggle />
        <a href="/audit-gratuit" className="btn btn-glow nav-cta-desktop">Audit gratuit <ArrowUpRight /></a>
        <button className="nav-burger" aria-label="Menu" aria-expanded={open}
                onClick={() => setOpen((o) => !o)}>
          <span /><span /><span />
        </button>
      </nav>
      <div className={`nav-mobile ${open ? 'open' : ''}`} onClick={() => setOpen(false)}>
        <span className="nav-mobile-group">/ Expertises</span>
        <a href="/expertises">3 piliers</a>
        <a href="/cas-usage">Cas d'usage</a>
        <a href="/partners">Partenaires</a>
        <a href="/tarifs">Tarifs</a>
        <a href="/plan-de-ciblage">Plan de ciblage</a>
        <a href="/news">News</a>
        <span className="nav-mobile-group">/ À propos</span>
        <a href="/a-propos">BoomMaker</a>
        <a href="/#faq">FAQ</a>
        <a href="/contact">Contact</a>
        <a href="/audit-gratuit" className="btn btn-glow">Audit gratuit <ArrowUpRight /></a>
      </div>
    </>
  );
};

const PageBreadcrumb = () => (
  <nav className="expertises-breadcrumb" aria-label="Fil d'Ariane">
    <a href="/">Accueil</a>
    <span className="expertises-breadcrumb-sep" aria-hidden="true">/</span>
    <a href="/cas-usage">Cas d'usage</a>
    <span className="expertises-breadcrumb-sep" aria-hidden="true">/</span>
    <span aria-current="page">Plan de ciblage</span>
  </nav>
);

async function poster(url, corps) {
  let r;
  try {
    r = await fetch(url, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(corps),
    });
  } catch (e) {
    /* fetch ne rejette QUE si la requête n'a jamais abouti : coupure réseau, ou
     * corps refusé avant d'atteindre le serveur. On marque l'erreur comme
     * réseau : sur l'analyse, ce cas ne doit PAS faire échouer le scan, car le
     * serveur continue son travail et le sondage retrouvera le plan. */
    /* On n'accuse le poids des documents QUE s'il y en a réellement. Un prospect
     * qui n'a rien joint et lit « ton document est trop lourd » cherche un
     * problème qui n'existe pas. */
    const avecFichiers = Array.isArray(corps?.fichiers) && corps.fichiers.length > 0;
    const err = new Error(
      avecFichiers
        ? "La connexion n'a pas abouti. Ton document est peut-être trop lourd : réessaie sans, ou avec un fichier plus léger."
        : "La connexion n'a pas abouti. Vérifie ta connexion et réessaie."
    );
    err.reseau = true;
    throw err;
  }
  if (r.status === 413) {
    throw new Error('Tes documents sont trop lourds. Retire-en un ou choisis un fichier plus léger.');
  }
  const data = await r.json().catch(() => ({}));
  if (!r.ok) {
    const err = new Error(data.message || "Quelque chose s'est mal passé. Réessaie.");
    // Le statut permet de distinguer « pas encore payé » (402, on patiente) d'une
    // vraie panne, au retour de paiement où Make met quelques secondes à cocher Payé.
    err.status = r.status;
    err.code = data.error;
    throw err;
  }
  return data;
}

/* Document imprimable — la version « à envoyer au client ». Caché à l'écran
 * (.dg-print), il devient la seule chose visible à l'impression (voir le CSS).
 * On reprend la voix du produit (tutoiement) pour rester cohérent avec la page. */
const PlanPrintable = ({ plan }) => {
  const s = plan.profondeur_marche?.socle_comptable;
  const prof = plan.profondeur;
  const reagir = plan.ce_qui_les_fait_reagir || {};
  const date = new Date().toLocaleDateString('fr-FR', { day: 'numeric', month: 'long', year: 'numeric' });
  return (
    <div className="dg-print" aria-hidden="true">
      <header className="dgp-head">
        <span className="dgp-brand">Boom Maker</span>
        <span className="dgp-meta">Plan de ciblage · {date}</span>
      </header>

      <h1 className="dgp-title">Ton plan de ciblage</h1>
      <p className="dgp-sub">Qui viser, combien ils sont, et comment leur parler.</p>

      <section className="dgp-sec">
        <h2>Ce qu'on a compris de toi</h2>
        <p>{plan.contexte_entreprise}</p>
      </section>

      {s && (
        <section className="dgp-sec">
          <h2>Ton socle adressable</h2>
          {prof?.ok && (
            <p className="dgp-big">{prof.plafonne ? 'plus de ' : ''}{nf.format(prof.total)}</p>
          )}
          <p className="dgp-libelle">{plan.profondeur_marche.libelle}</p>
          <p className="dgp-crit">{(s.naf || []).join(' · ')} × {s.zone} × {effectifLisible(s.effectif_min, s.effectif_max)}</p>
          {s.taille_pourquoi && (
            <>
              <p className="dgp-note" style={{ marginTop: '10px' }}>{s.taille_pourquoi}</p>
              {s.taille_source === 'elargie' && (
                <p className="dgp-meta2">Fourchette élargie au-delà de l'indication initiale, pour la raison ci-dessus.</p>
              )}
            </>
          )}
          {s.fonctions?.length > 0 && (
            <p className="dgp-crit" style={{ marginTop: '8px' }}>Qui décide : {s.fonctions.join(' · ')}</p>
          )}
        </section>
      )}

      <section className="dgp-sec">
        <h2>Tes cercles de cibles</h2>
        {plan.regle_des_cercles && <p className="dgp-note">{plan.regle_des_cercles}</p>}
        <p className="dgp-note">{CADRAGE_CERCLES}</p>
        {(plan.cercles || []).map((c) => (
          <div className="dgp-card" key={c.rang}>
            <div className="dgp-rank">Cercle 0{c.rang}</div>
            <h3>{c.nom}</h3>
            {c.volume?.ok && (
              <p className="dgp-vol">
                {c.volume.plafonne ? 'plus de ' : ''}{nf.format(c.volume.total)} entreprises
              </p>
            )}
            <p className="dgp-logique">{c.logique}</p>
            <p>{c.pourquoi}</p>
          </div>
        ))}
      </section>

      <section className="dgp-sec">
        <h2>Cibles recommandées par Boom Maker</h2>
        {(plan.icp_recommandes || []).map((icp) => (
          <div className="dgp-card" key={icp.nom}>
            <span className="dgp-badge">{icp.badge}</span>
            <h3>{icp.nom}</h3>
            <p>{icp.pourquoi}</p>
            <p className="dgp-meta2">{icp.taille_cible} · {icp.fonction_decideur}</p>
          </div>
        ))}
      </section>

      <section className="dgp-sec">
        <h2>Ce qui les fait réagir</h2>
        {reagir.declencheur && <div className="dgp-row"><span>Le vrai déclencheur</span><p>{reagir.declencheur}</p></div>}
        {reagir.angle_a_eviter && <div className="dgp-row"><span>Angle à éviter</span><p>{reagir.angle_a_eviter}</p></div>}
        {reagir.preuve && <div className="dgp-row"><span>Preuve à sortir</span><p>{reagir.preuve}</p></div>}
      </section>

      {plan.exclusions?.length > 0 && (
        <section className="dgp-sec">
          <h2>Hors marché</h2>
          {plan.exclusions.map((e) => (
            <div className="dgp-row" key={e.qui}><span>{e.qui}</span><p>{e.pourquoi}</p></div>
          ))}
        </section>
      )}

      <section className="dgp-sec">
        <h2>Continuer avec Boom Maker</h2>
        <p className="dgp-lead">
          Ton plan est prêt. Voici comment on le transforme en rendez-vous, à deux
          niveaux selon jusqu'où tu veux qu'on aille.
        </p>
        <div className="dgp-tiers">
          <div className="dgp-tier">
            <div className="dgp-tier-rang">Niveau 1</div>
            <h3>On te livre les listes</h3>
            <p>On source et on enrichit les vrais contacts de ce plan : les personnes à joindre, avec leurs coordonnées. Tu repars avec une liste prête à activer.</p>
            <div className="dgp-tier-prix">À partir de <b>490 €</b> one-shot</div>
          </div>
          <div className="dgp-tier">
            <div className="dgp-tier-rang">Niveau 2</div>
            <h3>On fait tourner la machine</h3>
            <p>Setup, messages, envoi et suivi des réponses, en continu chaque mois. On installe ta prospection de bout en bout et on la pilote pour toi.</p>
            <div className="dgp-tier-prix">À partir de <b>590 €</b> / mois</div>
          </div>
        </div>
        <p className="dgp-tiers-note">Prix hors taxes. Le devis exact se cale sur ton volume et ton périmètre, on le voit ensemble.</p>
      </section>

      <footer className="dgp-foot">
        <div>Boom Maker · boommaker.io · julia@boommaker.io</div>
        <div>Le plan est prêt. Reste à le faire tourner : réserve 20 min sur boommaker.io.</div>
      </footer>
    </div>
  );
};

/* Identifiant tiré par le navigateur AVANT de lancer l'analyse. C'est la clé de
 * toute la fiabilité : il permet de retrouver le plan même si la connexion
 * longue meurt en route (voir api/plan-statut.js). Même format que côté serveur. */
const nouvelIdClient = () => {
  const b = new Uint8Array(16);
  crypto.getRandomValues(b);
  return Array.from(b, (x) => x.toString(16).padStart(2, '0')).join('');
};

const attendre = (ms) => new Promise((r) => setTimeout(r, ms));

/**
 * Demande « mon plan est-il prêt ? » toutes les 3 secondes.
 *
 * Chaque requête dure une fraction de seconde : rien ne peut la couper, à la
 * différence de l'analyse elle-même qui immobilise la connexion 2,5 minutes.
 * Une sonde qui échoue n'est pas grave, on retente à la suivante.
 */
async function sonderPlan(id, encoreUtile) {
  const debut = Date.now();
  const LIMITE = 5 * 60 * 1000;
  while (Date.now() - debut < LIMITE) {
    await attendre(3000);
    if (!encoreUtile()) return new Promise(() => {}); // le chemin direct a gagné
    try {
      const r = await poster('/api/plan-statut', { id });
      if (r.statut === 'pret') return r;
    } catch (e) {
      /* on retente : une sonde ratée ne doit jamais faire échouer l'analyse */
    }
  }
  throw new Error(
    "L'analyse prend plus de temps que prévu. Ton plan est peut-être déjà prêt : " +
      'recharge cette page dans une minute, on le retrouvera automatiquement.'
  );
}

const PageContent = () => {
  const [site, setSite] = useState('');
  const [lecture, setLecture] = useState(false);
  const [lectureLongue, setLectureLongue] = useState(false);
  const [champs, setChamps] = useState(null);
  const [erreur, setErreur] = useState('');

  const [clone, setClone] = useState('');
  const [manque, setManque] = useState('');
  const [taille, setTaille] = useState('1 à 10');
  const [coince, setCoince] = useState('Les trouver');
  const [fichiers, setFichiers] = useState([]);

  const [scan, setScan] = useState(false);
  const [etape, setEtape] = useState(0);
  const [diag, setDiag] = useState(null);
  const [complet, setComplet] = useState(null); // le plan payant, récupéré après paiement
  const [ouvert, setOuvert] = useState(false);
  /* Lien perso de Julia : ?perso=<secret> → plans gratuits illimités, sans Stripe.
   * Capturé à l'ouverture, AVANT que le scan ne réécrive l'URL en ?id=. */
  const [perso] = useState(() => {
    try { return new URLSearchParams(window.location.search).get('perso') || ''; } catch { return ''; }
  });
  const [deblocage, setDeblocage] = useState(false);
  // Capture du mail avant le téléchargement du PDF (chaque PDF devient un lead).
  const [pdfDemande, setPdfDemande] = useState(false); // l'encart mail est ouvert
  const [pdfMail, setPdfMail] = useState('');
  const [pdfMailOk, setPdfMailOk] = useState(false); // mail déjà donné : on n'embête plus
  const [pdfEnvoi, setPdfEnvoi] = useState(false);
  const [pdfErreur, setPdfErreur] = useState('');
  const [retour, setRetour] = useState(false); // on revient de Stripe

  const refCible = useRef(null);
  const refPromesse = useRef(null);
  const refZone = useRef(null);
  const refApres = useRef(null);
  const refResultats = useRef(null);
  const refClone = useRef(null);
  const refManque = useRef(null);

  /* Reprendre l'exemple : on écrit vraiment la phrase dans le champ, puis on
   * place le curseur à la fin. L'exemple grisé seul obligeait à tout retaper,
   * alors qu'il n'a souvent qu'un mot à corriger. */
  const remplir = (setter, texte, ref) => {
    if (!texte) return;
    setter(texte);
    setTimeout(() => {
      const el = ref.current;
      if (!el) return;
      el.focus();
      el.setSelectionRange(texte.length, texte.length);
    }, 0);
  };

  const versLe = (ref) => {
    if (!ref.current) return;
    const reduit = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    ref.current.scrollIntoView({ behavior: reduit ? 'auto' : 'smooth', block: 'start' });
  };

  /* Le scan fait défiler de vraies étapes pendant que la requête tourne, et tient
   * la dernière tant que la réponse n'est pas revenue. */
  useEffect(() => {
    if (!scan) return;
    const it = setInterval(() => setEtape((e) => Math.min(e + 1, ETAPES.length - 1)), 13000);
    return () => clearInterval(it);
  }, [scan]);

  /* Retour de paiement. La redirection Stripe recharge la page, donc l'aperçu a
   * disparu : on repart de l'identifiant présent dans l'URL et on redemande le plan
   * complet, que le serveur ne rendra qu'après avoir validé le paiement. */
  useEffect(() => {
    const p = new URLSearchParams(window.location.search);
    // Le lien de paiement Stripe revient sur une URL fixe (?paye=1), sans l'id.
    // On le récupère alors depuis le stockage local, posé avant la redirection.
    let planId = p.get('id');
    const retourPaiement = p.get('paye') === '1';
    if (retourPaiement && !planId) {
      try { planId = window.localStorage.getItem('bm_plan_paye'); } catch {}
    }
    if (!planId) return;

    if (retourPaiement) {
      /* Retour de paiement. Make met quelques secondes à cocher Payé après le
       * paiement Stripe : on sonde /api/plan jusqu'à ce qu'il débloque, plutôt
       * que d'afficher une erreur à quelqu'un qui vient de payer. */
      setRetour(true);
      try { window.localStorage.removeItem('bm_plan_paye'); } catch {}
      window.history.replaceState({}, '', `/plan-de-ciblage?id=${planId}`);
      (async () => {
        for (let essai = 0; essai < 15; essai++) {
          try {
            await recupererPlan(planId);
            setTimeout(() => versLe(refResultats), 60);
            return;
          } catch (e) {
            // 402 = pas encore coché par Make : on patiente et on réessaie.
            if (e.status !== 402) { setErreur(e.message); return; }
            await new Promise((r) => setTimeout(r, 2500));
          }
        }
        // Toujours pas débloqué après ~40 s : on rassure sans mentir. Le rechargement
        // par ?id finira par afficher le plan dès que Make a coché Payé.
        setErreur('Ton paiement est bien reçu. Ton plan se débloque dans un instant, recharge la page si besoin.');
      })().finally(() => setRetour(false));
      return;
    }

    /* Reprise d'un plan déjà généré : connexion perdue pendant l'analyse, onglet
     * fermé, ou lien partagé. On le retrouve par son identifiant. Filet de
     * sécurité : plus aucune analyse n'est perdue. On affiche le plan si gratuit
     * ou payé, sinon le paywall (via acces_libre renvoyé par le serveur). */
    setRetour(true);
    poster('/api/plan-statut', { id: planId })
      .then((r) => {
        if (r.statut === 'pret') {
          setDiag(r);
          setTimeout(() => versLe(refResultats), 60);
        }
      })
      .catch(() => {})
      .finally(() => setRetour(false));
  }, []);

  /* Pièces jointes. Les limites viennent de Vercel, qui plafonne le corps de requête
   * à ~4,5 Mo, et le base64 gonfle un fichier d'environ un tiers. On refuse ici, avec
   * un message clair, plutôt que de laisser partir une requête qui échouerait. */
  const ajouterFichier = (e) => {
    const f = e.target.files?.[0];
    e.target.value = ''; // sinon impossible de re-choisir le même fichier après retrait
    if (!f) return;
    setErreur('');
    if (fichiers.length >= MAX_FICHIERS) {
      setErreur(`On se limite à ${MAX_FICHIERS} documents, c'est déjà largement assez.`);
      return;
    }
    if (f.type !== 'application/pdf' && !f.type.startsWith('image/')) {
      setErreur('Formats acceptés : PDF ou image (png, jpg).');
      return;
    }
    if (f.size > MAX_OCTETS) {
      setErreur(`« ${f.name} » pèse ${(f.size / 1024 / 1024).toFixed(1)} Mo, la limite est de 2 Mo par document.`);
      return;
    }
    const dejaPris = fichiers.reduce((s, x) => s + (x.taille || 0), 0);
    if (dejaPris + f.size > MAX_TOTAL_OCTETS) {
      setErreur(
        `Trop lourd au total : tes documents feraient ${((dejaPris + f.size) / 1024 / 1024).toFixed(1)} Mo, ` +
          `la limite est de 2,5 Mo. Retire un document ou choisis un fichier plus léger.`
      );
      return;
    }
    const lecteur = new FileReader();
    lecteur.onload = () => {
      // On enlève le préfixe « data:...;base64, » : l'API n'attend que les données.
      const data = String(lecteur.result).split(',')[1] || '';
      setFichiers((l) => [...l, { nom: f.name, type: f.type, taille: f.size, data }]);
    };
    lecteur.onerror = () => setErreur("Ce fichier n'a pas pu être lu, essaie un autre.");
    lecteur.readAsDataURL(f);
  };

  const retirerFichier = (i) => setFichiers((l) => l.filter((_, j) => j !== i));

  const lireSite = async () => {
    setErreur('');
    if (!site.trim()) { setErreur("Colle l'adresse de ton site, par exemple https://ton-site.fr"); return; }
    setLecture(true);
    /* Un site qui livre son texte directement est lu en 4 ou 5 secondes. Celui
     * qui se dessine dans le navigateur (Webflow, React) oblige à en ouvrir un
     * vrai côté serveur : une trentaine de secondes. Passé 7 secondes on sait
     * qu'on est dans ce cas, et on le dit. Une attente expliquée se supporte,
     * une attente muette fait fermer l'onglet. */
    const lent = setTimeout(() => setLectureLongue(true), 7000);
    try {
      const r = await poster('/api/diagnostic-lecture', { site });
      setChamps({
        cible: r.cible, promesse: r.promesse, zone: r.zone,
        /* Exemples grisés taillés pour SON métier. Si la lecture n'en a pas
         * produit (site trop pauvre), on n'en invente pas : le champ reste sans
         * exemple plutôt qu'avec celui d'un autre secteur. */
        exempleClone: (r.exemple_clone || '').trim(),
        exempleManque: (r.exemple_manque || '').trim(),
      });
      setTimeout(() => versLe(refApres), 60);
    } catch (e) {
      setErreur(e.message);
    } finally {
      clearTimeout(lent);
      setLectureLongue(false);
      setLecture(false);
    }
  };

  const lancerScan = async () => {
    setErreur('');
    if (!clone.trim()) { setErreur('Décris ton meilleur client en une phrase, on en a besoin pour viser juste.'); return; }
    if (!manque.trim()) { setErreur('Dis-nous ce qui te manque aujourd\'hui, c\'est ce qui oriente tout le plan.'); return; }

    const edites = {
      ...champs, // garde les exemples grisés si le scan échoue et qu'on revient au formulaire
      cible: refCible.current?.textContent.trim() || champs.cible,
      promesse: refPromesse.current?.textContent.trim() || champs.promesse,
      zone: refZone.current?.textContent.trim() || champs.zone,
    };
    setChamps(edites); // sinon une erreur réseau ferait perdre ses corrections

    setScan(true);
    setEtape(0);

    /* On tire l'identifiant ICI et on l'inscrit tout de suite dans l'URL : si le
     * prospect perd sa connexion ou ferme l'onglet par mégarde, un simple
     * rechargement retrouve son plan au lieu de tout reperdre. */
    const id = nouvelIdClient();
    window.history.replaceState({}, '', `/plan-de-ciblage?id=${id}`);

    let directeAGagne = false;
    try {
      /* Deux chemins en parallèle vers le même plan :
       *  - la requête d'analyse, rapide mais fragile (2,5 min sans échange, que
       *    beaucoup de réseaux coupent) ;
       *  - le sondage, en requêtes courtes que rien ne coupe.
       * Le premier qui aboutit gagne. Une panne réseau sur le chemin direct ne
       * doit PAS faire échouer le scan : le serveur, lui, va au bout. */
      const directe = poster('/api/diagnostic', { id, site, ...edites, clone, taille, manque, coince, fichiers, perso })
        .then((r) => ({ plan: r }))
        .catch((e) => ({ err: e }));

      const r = await Promise.race([
        directe.then(({ plan, err }) => {
          if (plan) { directeAGagne = true; return plan; }
          // Vraie erreur (champs, quota, panne du modèle) : inutile de sonder.
          if (err && !err.reseau) throw err;
          // Coupure réseau : on laisse le sondage faire son travail.
          return new Promise(() => {});
        }),
        sonderPlan(id, () => !directeAGagne),
      ]);

      setDiag(r);
      setTimeout(() => versLe(refResultats), 60);
    } catch (e) {
      setErreur(e.message);
    } finally {
      setScan(false);
    }
  };

  /* Le déverrouillage ne se décide PAS dans le navigateur : on part chez Stripe, et
   * c'est /api/plan qui, au retour, vérifie le paiement côté serveur avant de rendre
   * le contenu. Forcer `ouvert` ici ne révélerait que des barres grises : le plan
   * payant n'est pas dans la page. */
  const debloquer = async () => {
    setErreur('');
    setDeblocage(true);
    try {
      // Plan gratuit (un des 3 de l'IP, ou coupe-circuit) : on révèle directement.
      if (diag.acces_libre) {
        await recupererPlan(diag.id);
        setDeblocage(false);
        return;
      }
      /* Plan payant : direction le lien de paiement Stripe, avec l'identifiant du
       * plan accroché en client_reference_id. Après paiement, le scénario Make
       * lit cet identifiant et coche Payé dans Airtable, ce qui débloque le plan.
       * On garde aussi l'identifiant en local : le lien de paiement revient sur une
       * URL fixe (sans l'id), c'est le stockage local qui nous permet de retrouver
       * SON plan au retour. */
      if (!diag.lien_paiement) {
        throw new Error("Le paiement n'est pas encore disponible. Écris-nous, on débloque ton plan à la main.");
      }
      try { window.localStorage.setItem('bm_plan_paye', diag.id); } catch {}
      const sep = diag.lien_paiement.includes('?') ? '&' : '?';
      window.location.href = `${diag.lien_paiement}${sep}client_reference_id=${encodeURIComponent(diag.id)}`;
    } catch (e) {
      setErreur(e.message);
      setDeblocage(false);
    }
  };

  const recupererPlan = async (planId) => {
    const r = await poster('/api/plan', { id: planId });
    const p = r.plan;
    setComplet(p);
    /* Au retour de Stripe la page a été rechargée : l'aperçu n'existe plus. On le
     * reconstruit depuis le plan, sinon le client vient de payer pour une page vide. */
    setDiag({
      id: planId,
      contexte_entreprise: p.contexte_entreprise,
      profondeur_marche: p.profondeur_marche,
      profondeur: p.profondeur || { ok: false },
      structure: {
        cercles: (p.cercles || []).map((c) => ({ rang: c.rang, chaleur: c.chaleur })),
        badges: (p.icp_recommandes || []).map((i) => i.badge),
      },
      confiance: p.confiance,
    });
    setOuvert(true);
  };

  /* Téléchargement du PDF, précédé de la capture du mail (obligatoire).
   * Clic sur le bouton : si le mail a déjà été donné, on imprime tout de suite ;
   * sinon on ouvre l'encart. La validation enregistre le mail PUIS imprime. */
  const lancerPdf = () => {
    if (pdfMailOk) { window.print(); return; }
    setPdfErreur('');
    setPdfDemande(true);
  };

  const validerMailPdf = async () => {
    setPdfErreur('');
    const mail = pdfMail.trim();
    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(mail)) {
      setPdfErreur('Vérifie ton adresse email.');
      return;
    }
    setPdfEnvoi(true);
    try {
      await poster('/api/plan-email', { id: diag.id, email: mail });
      setPdfMailOk(true);
      setPdfDemande(false);
      // Laisse le rendu se stabiliser avant d'ouvrir la boîte d'impression.
      setTimeout(() => window.print(), 150);
    } catch (e) {
      setPdfErreur(e.message);
    } finally {
      setPdfEnvoi(false);
    }
  };

  const socle = diag?.profondeur_marche?.socle_comptable;
  const prof = diag?.profondeur;
  const compte = useCompteur(prof?.ok ? prof.total : 0, !!prof?.ok);

  return (
    <>
      <PageNav />
      <main>
        <article className="audit-hero">
          <div className="container">
            <PageBreadcrumb />
            <span className="eyebrow"><span className="pulse" /> Plan de ciblage · 5 min</span>
            <h1>Tu prospectes. Mais <em>qui</em>, au juste&nbsp;?</h1>
            <p className="lead">
              Qui viser, combien ils sont, et comment leur parler. Colle ton site :
              on compte tes vraies cibles et on te dit par qui commencer.
            </p>
          </div>
        </article>

        <section className="expertises-content tarifs-content">
          <div className="container">

            {!scan && !diag && (
              <div className="dg-card dg-form">
                <div className="dg-form-head">
                  <h2>Colle ton site. On fait le reste.</h2>
                  <span className="dg-step">01 · Ton site</span>
                </div>

                <div className="dg-site-row">
                  {/* Entrée vaut clic sur « Lire mon site » : coller une adresse
                      puis appuyer sur Entrée est le réflexe de tout le monde, et
                      ne rien voir se passer donne l'impression d'un site cassé. */}
                  <input type="url" value={site} placeholder="https://ton-site.fr"
                         aria-label="Adresse de ton site" disabled={lecture}
                         onChange={(e) => setSite(e.target.value)}
                         onKeyDown={(e) => {
                           if (e.key === 'Enter' && !lecture) { e.preventDefault(); lireSite(); }
                         }} />
                  <button type="button" className="btn btn-glow" onClick={lireSite} disabled={lecture}>
                    {lecture ? 'Lecture du site…' : <>Lire mon site <ArrowUpRight /></>}
                  </button>
                </div>
                <span className="dg-fnote">
                  <span aria-hidden="true">🔎</span>{' '}
                  {lectureLongue
                    ? 'Ton site affiche son contenu en JavaScript : on ouvre un vrai navigateur pour le lire. Compte une trentaine de secondes.'
                    : "On en extrait ton offre, tes valeurs et ta façon de parler. Tu n'auras qu'à corriger."}
                </span>
                {erreur && !champs && <div className="dg-error">{erreur}</div>}

                {champs && (
                  <div className="dg-after" ref={refApres}>
                    <div className="dg-prefill"><span aria-hidden="true">✨</span> Pré-rempli depuis ton site. Corrige si on s'est trompé.</div>

                    <p className="dg-madlib">
                      Tu aides <span className="dg-inline" contentEditable suppressContentEditableWarning ref={refCible}>{champs.cible}</span>
                      {' '}à <span className="dg-inline" contentEditable suppressContentEditableWarning ref={refPromesse}>{champs.promesse}</span>,
                      {' '}partout en <span className="dg-inline" contentEditable suppressContentEditableWarning ref={refZone}>{champs.zone}</span>.
                    </p>

                    <div className="dg-q">
                      <div className="dg-q-head">
                        <span className="dg-q-emoji" aria-hidden="true">🧬</span>
                        <div>
                          <h3>Clone ton meilleur client</h3>
                          <p>Si tu pouvais en cloner un seul, ce serait qui ? Une phrase suffit.</p>
                        </div>
                      </div>
                      <textarea rows="2" value={clone} onChange={(e) => setClone(e.target.value)}
                                aria-label="Ton meilleur client" ref={refClone}
                                placeholder={champs.exempleClone || ''} />
                      <Exemple texte={champs.exempleClone} vide={!clone.trim()}
                               onUtiliser={() => remplir(setClone, champs.exempleClone, refClone)} />
                      <div className="dg-pick">
                        <span className="dg-mini">Sa taille</span>
                        {TAILLES.map((t) => (
                          <button type="button" key={t} className={`dg-chip ${taille === t ? 'on' : ''}`}
                                  aria-pressed={taille === t} onClick={() => setTaille(t)}>{t}</button>
                        ))}
                      </div>
                    </div>

                    <div className="dg-q">
                      <div className="dg-q-head">
                        <span className="dg-q-emoji" aria-hidden="true">🎯</span>
                        <div>
                          <h3>Qu'est-ce qui te manque aujourd'hui ?</h3>
                          <p>Des clients, des projets, des rendez-vous ? Dis-le simplement. C'est ce qui oriente tout le plan.</p>
                        </div>
                      </div>
                      <input type="text" value={manque} onChange={(e) => setManque(e.target.value)}
                             aria-label="Ce qui te manque" ref={refManque}
                             placeholder={champs.exempleManque || ''} />
                      <Exemple texte={champs.exempleManque} vide={!manque.trim()}
                               onUtiliser={() => remplir(setManque, champs.exempleManque, refManque)} />
                    </div>

                    <div className="dg-q">
                      <div className="dg-q-head">
                        <span className="dg-q-emoji" aria-hidden="true">🩺</span>
                        <div>
                          <h3>Ça coince où, aujourd'hui ?</h3>
                          <p>Sois franc. On attaque l'étape qui bloque vraiment, pas celle qu'on suppose.</p>
                        </div>
                      </div>
                      <div className="dg-pick">
                        {COINCE.map((c) => (
                          <button type="button" key={c} className={`dg-chip ${coince === c ? 'on' : ''}`}
                                  aria-pressed={coince === c} onClick={() => setCoince(c)}>{c}</button>
                        ))}
                      </div>
                    </div>

                    <div className="dg-q">
                      <div className="dg-q-head">
                        <span className="dg-q-emoji" aria-hidden="true">📎</span>
                        <div>
                          <h3>Un document qui parle de ton activité ?</h3>
                          <p>Plaquette, présentation, plan marketing. Facultatif, mais ça affine nettement l'analyse.</p>
                        </div>
                      </div>

                      {fichiers.length < MAX_FICHIERS && (
                        <label className="dg-drop">
                          <span className="dg-drop-clip" aria-hidden="true">📎</span>
                          <span>
                            {fichiers.length ? 'Ajouter un autre document' : 'Choisir un document'}
                            <span className="dg-drop-note">PDF ou image · 2 Mo par document · 2,5 Mo au total</span>
                          </span>
                          <input type="file" accept="application/pdf,image/*" onChange={ajouterFichier}
                                 style={{ display: 'none' }} aria-label="Ajouter un document" />
                        </label>
                      )}

                      {fichiers.length > 0 && (
                        <ul className="dg-fichiers">
                          {fichiers.map((f, i) => (
                            <li key={`${f.nom}-${i}`}>
                              <span className="dg-f-nom">{f.nom}</span>
                              <span className="dg-f-poids">{poidsLisible(f.taille)}</span>
                              <button type="button" onClick={() => retirerFichier(i)}
                                      aria-label={`Retirer ${f.nom}`}>Retirer</button>
                            </li>
                          ))}
                        </ul>
                      )}
                    </div>

                    {erreur && <div className="dg-error">{erreur}</div>}

                    <div className="dg-go">
                      <button type="button" className="btn btn-glow" onClick={lancerScan}>
                        Lancer mon plan de ciblage <ArrowUpRight />
                      </button>
                      <span className="dg-note">Résultat en moins de 2 min</span>
                    </div>
                  </div>
                )}
              </div>
            )}

            {scan && (
              <div className="dg-card dg-scan">
                <div className="dg-radar"><span className="dg-radar-dot" /></div>
                <div className="dg-status" role="status">{ETAPES[etape]}</div>
                <p className="dg-note">
                  On analyse vraiment ton marché, ça prend moins de 2 minutes. Ne ferme pas cette page.
                </p>
              </div>
            )}

            {diag && (
              <div className="dg-results" ref={refResultats}>
                {complet && (
                  <div className="dg-printbar">
                    <div>
                      <strong>Ton plan de ciblage est prêt.</strong>
                      <span> Télécharge-le pour analyser ton plan de ciblage.</span>
                    </div>
                    {!pdfDemande ? (
                      <button type="button" className="btn btn-glow" onClick={lancerPdf}>
                        Télécharger en PDF <ArrowUpRight />
                      </button>
                    ) : (
                      <form
                        className="dg-pdfmail"
                        onSubmit={(e) => { e.preventDefault(); validerMailPdf(); }}
                      >
                        <label className="dg-pdfmail-l">Où on t'envoie ton plan&nbsp;?</label>
                        <div className="dg-pdfmail-row">
                          <input
                            type="email"
                            inputMode="email"
                            autoFocus
                            placeholder="ton@email.fr"
                            value={pdfMail}
                            onChange={(e) => setPdfMail(e.target.value)}
                            aria-label="Ton adresse email"
                            disabled={pdfEnvoi}
                          />
                          <button type="submit" className="btn btn-glow" disabled={pdfEnvoi}>
                            {pdfEnvoi ? 'Un instant…' : <>Recevoir mon plan <ArrowUpRight /></>}
                          </button>
                        </div>
                        {pdfErreur && <div className="dg-error">{pdfErreur}</div>}
                      </form>
                    )}
                  </div>
                )}
                <div className="dg-card dg-teaser">
                  <div className="eyebrow"><span className="pulse" />Aperçu de ton plan de ciblage</div>
                  <div className="dg-depth-k">Ce qu'on a compris de toi</div>
                  <p className="dg-depth-p">{diag.contexte_entreprise}</p>

                  {socle && (
                    <div className="dg-socle">
                      <div className="dg-depth-k">Ton socle adressable</div>
                      {prof?.ok ? (
                        <div className="dg-bignum">
                          {prof.plafonne && <span className="dg-plus">plus de </span>}
                          {nf.format(compte)}
                          <small>{diag.profondeur_marche.libelle}</small>
                        </div>
                      ) : (
                        <p className="dg-depth-p">{diag.profondeur_marche.libelle}</p>
                      )}
                      <p className="dg-madlib">
                        <span className="dg-inline">{socle.naf.join(' · ')}</span>
                        {' × '}<span className="dg-inline">{socle.zone}</span>
                        {' × '}<span className="dg-inline">{effectifLisible(socle.effectif_min, socle.effectif_max)}</span>
                      </p>
                      {!prof?.ok && (
                        <div className="dg-pending">Le comptage n'a pas pu aboutir. Tes critères de ciblage restent valables.</div>
                      )}
                    </div>
                  )}

                  <div className="dg-teasers">
                    <div className="dg-tz">
                      <div className="dg-tzk"><span aria-hidden="true">🔒</span> Dans le plan</div>
                      <div className="dg-tzv">{diag.cercles?.length || 3} cercles de cibles</div>
                      <div className="dg-tzs">du cœur vers la périphérie, avec la logique de chacun</div>
                    </div>
                    <div className="dg-tz">
                      <div className="dg-tzk"><span aria-hidden="true">🔒</span> Dans le plan</div>
                      <div className="dg-tzv">1 cible que tu n'exploites pas</div>
                      <div className="dg-tzs">un point d'entrée que tu n'as pas nommé</div>
                    </div>
                    <div className="dg-tz">
                      <div className="dg-tzk"><span aria-hidden="true">🔒</span> Dans le plan</div>
                      <div className="dg-tzv">Les angles qui déclenchent</div>
                      <div className="dg-tzs">et ceux qui te grillent</div>
                    </div>
                  </div>
                </div>

                <div className="dg-peek">↓ Ton plan de ciblage complet ↓</div>

                <div className={`dg-locked ${ouvert ? 'unlocked' : ''}`}>
                  {/* Le contenu payant n'est PAS dans le navigateur : il reste dans l'ERP
                      et ne descend qu'après vérification du paiement (voir /api/plan).
                      Ici on ne dessine que la structure réelle du plan (le nombre de
                      cercles, leur chaleur, les badges) : de quoi montrer qu'il y a de
                      la matière, sans en livrer une ligne. */}
                  <div className="dg-locked-body" aria-hidden={!complet}>
                    {/* Le raisonnement sur la fourchette de taille et les décideurs à
                        viser : le cerveau les produisait déjà, la page ne les montrait
                        pas. C'est pourtant là que la méthode Boom Maker se voit, pas
                        seulement son résultat. Contenu payant (jamais dans l'aperçu). */}
                    <div className="dg-depth-k">Pourquoi cette taille d'entreprise</div>
                    {complet ? (
                      <>
                        <p className="dg-para">
                          {complet.profondeur_marche?.socle_comptable?.taille_pourquoi}
                        </p>
                        {complet.profondeur_marche?.socle_comptable?.taille_source === 'elargie' && (
                          <div className="dg-elargi">
                            <span aria-hidden="true">↔</span> Fourchette élargie au-delà de ce que tu avais indiqué, pour la raison ci-dessus.
                          </div>
                        )}
                        {complet.profondeur_marche?.socle_comptable?.taille_source === 'a_confirmer' && (
                          <div className="dg-elargi">
                            <span aria-hidden="true">?</span> À confirmer avec toi : la taille ne se déduisait pas avec certitude.
                          </div>
                        )}
                        {complet.profondeur_marche?.socle_comptable?.fonctions?.length > 0 && (
                          <div className="dg-pick">
                            <span className="dg-mini">Qui décide</span>
                            {complet.profondeur_marche.socle_comptable.fonctions.map((f) => (
                              <span className="dg-role" key={f}>{f}</span>
                            ))}
                          </div>
                        )}
                      </>
                    ) : (
                      <>
                        <div className="dg-barre dg-barre-large" />
                        <div className="dg-barre dg-barre-courte" />
                      </>
                    )}

                    <div className="dg-divider" />
                    <div className="dg-depth-k">Tes cercles de cibles</div>
                    {complet ? (
                      <>
                        <p className="dg-para">{complet.regle_des_cercles}</p>
                        <p className="dg-para dg-cadrage">{CADRAGE_CERCLES}</p>
                      </>
                    ) : <div className="dg-barre dg-barre-large" />}
                    <div className="dg-segs">
                      {(complet?.cercles || diag.structure?.cercles || []).map((c) => (
                        <div className="dg-seg" key={c.rang}>
                          <div className="dg-seg-rank">Cercle 0{c.rang}</div>
                          {complet ? (
                            <>
                              <h4>{c.nom}</h4>
                              {c.volume?.ok && (
                                <div className="dg-seg-vol">
                                  {c.volume.plafonne && <span className="dg-vol-plus">plus de </span>}
                                  {nf.format(c.volume.total)}
                                  <span className="dg-vol-u"> entreprises</span>
                                </div>
                              )}
                              <div className="dg-seg-cnt">{c.logique}</div>
                              <p className="dg-tzs">{c.pourquoi}</p>
                            </>
                          ) : (
                            <>
                              <div className="dg-barre dg-barre-titre" />
                              <div className="dg-barre" />
                              <div className="dg-barre dg-barre-courte" />
                            </>
                          )}
                          <div className="dg-heat"><i style={{ width: `${c.chaleur}%` }} /></div>
                        </div>
                      ))}
                    </div>

                    <div className="dg-divider" />
                    <div className="dg-icp-head">
                      <span className="dg-depth-k">Cibles recommandées par Boom Maker</span>
                      <p className="dg-para">Au-delà de ton déclaratif : les cibles qu'on activerait en premier.</p>
                    </div>
                    <div className="dg-icps">
                      {complet
                        ? complet.icp_recommandes.map((icp) => (
                            <div className="dg-icp" key={icp.nom}>
                              <span className={`dg-badge ${BADGE_CLS[icp.badge] || 'dg-b-p1'}`}>{icp.badge}</span>
                              <h5>{icp.nom}</h5>
                              <p>{icp.pourquoi}</p>
                              <p className="dg-tzs">{icp.taille_cible} · {icp.fonction_decideur}</p>
                            </div>
                          ))
                        : (diag.structure?.badges || []).map((b, i) => (
                            <div className="dg-icp" key={i}>
                              <span className={`dg-badge ${BADGE_CLS[b] || 'dg-b-p1'}`}>{b}</span>
                              <div className="dg-barre dg-barre-titre" />
                              <div className="dg-barre" />
                              <div className="dg-barre" />
                              <div className="dg-barre dg-barre-courte" />
                            </div>
                          ))}
                    </div>

                    <div className="dg-divider" />
                    <div className="dg-rowset">
                      {[
                        ['Le vrai déclencheur', complet?.ce_qui_les_fait_reagir?.declencheur],
                        ['Angle à éviter', complet?.ce_qui_les_fait_reagir?.angle_a_eviter],
                        ['Preuve à sortir', complet?.ce_qui_les_fait_reagir?.preuve],
                      ].map(([k, v]) => (
                        <div className="dg-prow" key={k}>
                          <div className="dg-pk">{k}</div>
                          <div className="dg-pv">
                            {complet ? v : (<><div className="dg-barre" /><div className="dg-barre dg-barre-courte" /></>)}
                          </div>
                        </div>
                      ))}
                      {complet?.exclusions?.length > 0 && (
                        <div className="dg-prow">
                          <div className="dg-pk">Hors marché</div>
                          <div className="dg-pv">
                            {complet.exclusions.map((e) => (
                              <div key={e.qui}>{e.qui} <span className="dg-muted">{e.pourquoi}</span></div>
                            ))}
                          </div>
                        </div>
                      )}
                    </div>

                    {/* La méthode Boom Maker : signal détecté vers angle qu'on en tire.
                        Volontairement ABSENTE du PDF (voir PlanPrintable) : le prospect
                        repart avec son plan, la mécanique d'écriture reste ici. Et on ne
                        rédige jamais un message : on montre le raisonnement, pas le texte. */}
                    <div className="dg-divider" />
                    <div className="dg-icp-head">
                      <span className="dg-depth-k">Comment on écrit tes messages</span>
                      <p className="dg-para">
                        Le signal qu'on détecte chez ta cible, et l'angle qu'on en tire. La rédaction,
                        c'est ce qu'on installe ensemble.
                      </p>
                    </div>
                    <div className="dg-methode">
                      {complet
                        ? (complet.methode_ecriture || []).map((m, i) => (
                            <div className="dg-sig" key={i}>
                              <div className="dg-sig-col">
                                <span className="dg-sig-k">Signal détecté</span>
                                <p>{m.signal}</p>
                              </div>
                              <div className="dg-sig-fleche" aria-hidden="true">→</div>
                              <div className="dg-sig-col">
                                <span className="dg-sig-k">Angle</span>
                                <p>{m.angle}</p>
                              </div>
                            </div>
                          ))
                        : [0, 1, 2, 3].map((i) => (
                            <div className="dg-sig" key={i}>
                              <div className="dg-sig-col"><div className="dg-barre" /></div>
                              <div className="dg-sig-fleche" aria-hidden="true">→</div>
                              <div className="dg-sig-col"><div className="dg-barre" /></div>
                            </div>
                          ))}
                    </div>
                  </div>

                  <div className="dg-veil">
                    <div className="dg-paycard">
                      <div className="dg-ready"><span className="pulse" />Ton plan de ciblage est prêt</div>
                      <h3>On a cartographié <em>tout</em> ton marché</h3>
                      <p>
                        Tes cercles de cibles, le client idéal que tu n'avais pas nommé et les
                        angles qui déclenchent. {diag.acces_libre ? 'Ouvre le plan complet.' : 'Débloque le plan complet.'}
                      </p>
                      {!diag.acces_libre && <div className="dg-price"><b>{PRIX}€ HT</b><span>soit 58,80 € TTC · paiement unique</span></div>}
                      <button type="button" className="btn btn-glow" onClick={debloquer} disabled={deblocage}>
                        {deblocage
                          ? (diag.acces_libre ? 'Ouverture…' : 'Ouverture du paiement…')
                          : (diag.acces_libre
                              ? <>Voir mon plan de ciblage <ArrowUpRight /></>
                              : <>Débloquer mon plan de ciblage <ArrowUpRight /></>)}
                      </button>
                      {!diag.acces_libre && <div className="dg-paynote">Paiement sécurisé Stripe · accès immédiat · <a href="/cgv" target="_blank" rel="noopener">CGV</a></div>}
                      {erreur && <div className="dg-error">{erreur}</div>}
                    </div>
                  </div>
                </div>

                {/* Accompagnement Boom Maker : le pont entre le plan gratuit et le
                    travail payant. Volontairement sobre (le Plan de ciblage n'est
                    pas un tunnel de vente), il donne les ordres de prix pour
                    qualifier avant le RDV. Prix « à partir de » : le plancher est
                    public, le devis exact se cale en rendez-vous. */}
                <div className="dg-offre">
                  <div className="dg-depth-k">Continuer avec Boom Maker</div>
                  <p className="dg-para">
                    Ton plan est prêt. Voici comment on le transforme en rendez-vous,
                    à deux niveaux selon jusqu'où tu veux qu'on aille.
                  </p>
                  <div className="dg-tiers">
                    <div className="dg-tier">
                      <div className="dg-tier-rang">Niveau 1</div>
                      <h4>On te livre les listes</h4>
                      <p>
                        On source et on enrichit les vrais contacts de ce plan : les
                        personnes à joindre, avec leurs coordonnées. Tu repars avec une
                        liste prête à activer.
                      </p>
                      <div className="dg-tier-prix">
                        <span className="dg-tier-ap">À partir de</span>
                        <b>490 €</b>
                        <span className="dg-tier-u">one-shot</span>
                      </div>
                    </div>
                    <div className="dg-tier dg-tier-phare">
                      <div className="dg-tier-rang">Niveau 2</div>
                      <h4>On fait tourner la machine</h4>
                      <p>
                        Setup, messages, envoi et suivi des réponses, en continu chaque
                        mois. On installe ta prospection de bout en bout et on la pilote
                        pour toi.
                      </p>
                      <div className="dg-tier-prix">
                        <span className="dg-tier-ap">À partir de</span>
                        <b>590 €</b>
                        <span className="dg-tier-u">/ mois</span>
                      </div>
                    </div>
                  </div>
                  <p className="dg-tiers-note">
                    Prix hors taxes. Le devis exact se cale sur ton volume et ton
                    périmètre, on le voit ensemble.
                  </p>
                </div>

                <div className="dg-cta">
                  <h3>Un dernier pas : <em>on en parle</em>.</h3>
                  <p>
                    20 minutes pour te dire par quel niveau commencer et te chiffrer
                    précisément. Sans engagement.
                  </p>
                  <a href={CALENDLY} target="_blank" rel="noopener" className="btn btn-glow">
                    Réserver 20 min avec Boom Maker <ArrowUpRight />
                  </a>
                </div>
              </div>
            )}
          </div>
        </section>
      </main>
      <FooterMin />
      {complet && <PlanPrintable plan={complet} />}
    </>
  );
};

ReactDOM.createRoot(document.getElementById('root')).render(<PageContent />);
