const { useState, useRef, useEffect, useMemo } = React;

/* ------------------------------------------------------------------ */
/*  CV Focus — CV et lettre de motivation rédigés par l'IA              */
/* ------------------------------------------------------------------ */

/* Les onze modèles. Chacun a sa propre mise en page ("base") : deux
   modèles ne partagent jamais la même. acc = couleur d'accent,
   fonce = couleur de la colonne ou du bandeau, fond = couleur de page. */
const MODELES = [
  /* Onyx Noir : la mise en page de Terracotta, en noir, blanc et or. */
  { id: "onyxnoir", nom: "Onyx Noir", base: "arche", acc: "#C9A227", fonce: "#0E0F12", fond: "#FFFFFF", sombre: true },
  { id: "terracotta", nom: "Terracotta", base: "arche", acc: "#9A5A53", fonce: "#E3BDB7", fond: "#F5EFE9" },
  { id: "camel", nom: "Camel", base: "camel", acc: "#6F5641", fonce: "#E7D7C6", fond: "#FDF6EE" },
  { id: "noirblanc", nom: "Noir et Blanc", base: "pave", acc: "#111111" },
  { id: "lin", nom: "Lin", base: "lin", acc: "#B39A6B", fonce: "#F1ECE5" },
  { id: "ardoise", nom: "Ardoise", base: "ardoise", acc: "#262A30", fonce: "#D3DAE2" },
  { id: "cyan", nom: "Cyan", base: "cyan", acc: "#1F7A8C", fonce: "#CFE2F3" },
  { id: "perle", nom: "Gris Perle", base: "perle", acc: "#2B2D33", fonce: "#EFEFEF" },
  { id: "graphite", nom: "Colonne Grise", base: "graphite", acc: "#6B6E73", fonce: "#3C3D40" },
  { id: "sauge", nom: "Vert Sauge", base: "sauge", acc: "#4E7D3A", fonce: "#E4F0DD" },
  { id: "prestige", nom: "Prestige", base: "colonne", acc: "#C9973B", fonce: "#14161A", serif: true, premium: true },
];

/* Les anciens modèles n'existent plus. Un dossier enregistré avec l'un
   d'eux (lien reçu par mail, retour de paiement) retombe sur le nouveau
   modèle le plus proche, au lieu d'une page cassée. */
const ANCIENS_MODELES = {
  onyx: "noirblanc", frise: "lin", chrono: "noirblanc", dossier: "camel", suisse: "perle",
  nuit: "onyxnoir", atelier: "terracotta", affiche: "cyan", classique: "perle", notaire: "ardoise",
};
function trouverModele(id) {
  return MODELES.find((x) => x.id === id) || MODELES.find((x) => x.id === ANCIENS_MODELES[id]) || null;
}
function idModeleValide(id) {
  return (trouverModele(id) || MODELES[0]).id;
}

/* CV au format suisse : quand la personne arrive de « Postuler en
   Suisse ». Seuls ces modèles sobres sont proposés, et le CV ajoute
   nationalité, date de naissance et permis de travail. Pour tous les
   autres visiteurs, rien ne change. */
const MODELES_SUISSES = ["perle", "ardoise", "lin", "noirblanc"];
const NATIONALITES = ["Française", "Suisse", "Belge", "Italienne", "Portugaise", "Espagnole", "Allemande", "Marocaine", "Algérienne", "Tunisienne"];
/* Facultatif : en Suisse, c'est l'employeur qui demande le permis
   après l'embauche. Valeur vide = rien sur le CV. */
const PERMIS_TRAVAIL = [
  { v: "", nom: "Pas encore (l'employeur fera la demande)" },
  { v: "Nationalité suisse", nom: "Nationalité suisse" },
  { v: "Permis C (établissement)", nom: "Permis C (établissement)" },
  { v: "Permis B (séjour)", nom: "Permis B (séjour)" },
  { v: "Permis G (frontalier)", nom: "Permis G (frontalier)" },
  { v: "Permis L (courte durée)", nom: "Permis L (courte durée)" },
];

/* Les cartes des pays pour chercher une offre (haut et bas de l'accueil). */
function CartesPays() {
  return (
    <div className="accueil-offres">
      <div className="accueil-titre">Chercher une offre d'emploi</div>
      <div className="cartes-pays">
        {[
          { href: "/offres.php", drapeau: <DrapeauFR />, nom: "France", note: "Partout en France" },
          { href: "/suisse.php", drapeau: <Drapeau />, nom: "Suisse", note: "Postuler en Suisse", nouveau: true },
          { href: "/luxembourg.php", drapeau: <DrapeauLU />, nom: "Luxembourg", note: "Postuler au Luxembourg", nouveau: true },
          { href: "/australie.php", drapeau: <DrapeauAU />, nom: "Australie", note: "Postuler en Australie", nouveau: true },
        ].map((p) => (
          <a className="carte-pays" href={p.href} key={p.nom}>
            <span className="carte-pays-drapeau">{p.drapeau}</span>
            <span className="carte-pays-texte">
              <b>{p.nom}{p.nouveau && <span className="pastille-nouveau">Nouveau</span>}</b>
              <small>{p.note}</small>
            </span>
            <span className="carte-pays-fleche" aria-hidden="true">→</span>
          </a>
        ))}
      </div>
    </div>
  );
}

/* Bloc de départ de l'accueil (en haut et après l'histoire) : le bouton
   principal mène aux offres en France, puis les pays, puis le CV seul. */
function BlocDepart({ onCreer }) {
  return (
    <>
      <a className="btn btn-primaire" href="/offres.php"
         style={{ display: "block", width: "100%", textAlign: "center", textDecoration: "none" }}>
        Trouver une offre d'emploi
      </a>
      <CartesPays />
      <button className="btn btn-fantome" onClick={onCreer}
              style={{ width: "100%", marginTop: 18, borderColor: "rgba(155,135,255,.45)" }}>
        Créer mon CV rapidement
      </button>
    </>
  );
}

/* Histoire de l'accueil : ce que fait CV Focus, en 4 étapes qui
   s'animent une à une au défilement. Tout est dessiné dans la page
   (aucun fichier à télécharger). Une étape reçoit la classe « joue »
   quand elle arrive à l'écran ; avec le réglage « réduire les
   animations », toutes la reçoivent tout de suite et restent immobiles. */
const OBJET_LETTRE = ["Candidature au poste de ", "Vendeur(se) – Boulangerie Martin"];

function Coins({ petits, delai }) {
  return (
    <span className={"coins" + (petits ? " petits" : "")} style={{ "--d": delai }} aria-hidden="true">
      <i /><i /><i /><i />
    </span>
  );
}

function CarteOffre({ haut, bouton }) {
  return (
    <div className="s-carte" style={{ top: haut }}>
      <Coins delai={bouton ? ".05s" : ".2s"} />
      <div className="s-tag ap" style={{ "--d": ".45s" }}>Offre d'emploi</div>
      <div className="s-titre ap" style={{ "--d": ".55s" }}>Vendeur(se)</div>
      <div className="s-lieu ap" style={{ "--d": ".7s" }}><b>Boulangerie Martin</b> · Nantes</div>
      <div className="s-desc ap" style={{ "--d": ".85s" }}>
        Vous assurez l'accueil client, tenez la caisse et aimez le travail en équipe.
      </div>
      {bouton && (
        <div className="s-btn" aria-hidden="true">
          <span className="pf">F</span>Postuler avec CV Focus
          <span className="s-reflet" />
          <span className="s-onde" />
          <Coins petits delai=".35s" />
          <span className="s-main">
            <svg viewBox="0 0 70 84"><path d="M6 4 L6 66 L22 52 L33 78 L45 73 L34 48 L56 48 Z" fill="#fff" stroke="#16161D" strokeWidth="4" strokeLinejoin="round" /></svg>
            <span className="doigt" />
          </span>
        </div>
      )}
    </div>
  );
}

function HistoireAccueil() {
  const boite = useRef(null);

  useEffect(() => {
    const etapes = boite.current ? [...boite.current.querySelectorAll(".etape")] : [];
    const reduit = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    if (reduit || !("IntersectionObserver" in window)) {
      etapes.forEach((e) => e.classList.add("joue"));
      return;
    }
    /* Une étape démarre dès que son haut apparaît en bas de l'écran
       (8 % visibles), et une seule fois : on cesse ensuite de l'observer. */
    const io = new IntersectionObserver((vues) => {
      vues.forEach((v) => {
        if (v.isIntersecting) { v.target.classList.add("joue"); io.unobserve(v.target); }
      });
    }, { threshold: 0.08 });
    etapes.forEach((e) => io.observe(e));
    return () => io.disconnect();
  }, []);

  let n = 0;
  const lettres = OBJET_LETTRE.map((morceau, m) => (
    <span className={m ? "v" : undefined} key={m} style={{ animation: "none", opacity: 1 }}>
      {[...morceau].map((c) => <span key={n} style={{ "--i": n++ }}>{c}</span>)}
    </span>
  ));

  return (
    <div className="histoire" ref={boite}>
      <div className="chapeau">Pas à pas</div>
      <h2>De l'offre à l'employeur, en 4 étapes</h2>

      <div className="etape">
        <div className="etape-texte"><span className="etape-num">1</span><h3>Trouvez une offre qui vous plaît.</h3></div>
        <div className="h-scene" aria-label="Une offre d'emploi : Vendeur(se), Boulangerie Martin, Nantes.">
          <CarteOffre haut="14cqw" />
        </div>
      </div>

      <div className="etape">
        <div className="etape-texte"><span className="etape-num">2</span><h3>Cliquez sur <em>Postuler avec CV Focus</em>.</h3></div>
        <div className="h-scene" aria-label="On clique sur le bouton Postuler avec CV Focus de l'offre.">
          <CarteOffre haut="8cqw" bouton />
        </div>
      </div>

      <div className="etape">
        <div className="etape-texte"><span className="etape-num">3</span><h3>Votre CV et votre lettre sont créés <em>exprès pour cette offre</em>.</h3></div>
        <div className="h-scene" aria-label="Les mots-clés de l'annonce vont dans le CV et la lettre. Tampon : Écrit pour cette offre.">
          <div className="s-bande">
            <div className="s-tag">Offre · Vendeur(se) – Boulangerie Martin</div>
            <div className="s-mots">
              <span className="k on" style={{ left: "4cqw", "--d": ".7s" }}>accueil client</span>
              <span className="k on" style={{ left: "33cqw", "--d": ".95s" }}>caisse</span>
              <span className="k on" style={{ left: "49cqw", "--d": "1.2s" }}>travail en équipe</span>
            </div>
          </div>

          <div className="s-doc cv secoue" style={{ "--d": ".35s" }}>
            <div className="tete" />
            <div className="lbl" style={{ top: "11cqw" }}>COMPÉTENCES</div>
            <div className="s-place" style={{ top: "14.5cqw", "--d": "1.62s" }}>accueil client</div>
            <div className="s-place" style={{ top: "20.5cqw", "--d": "1.92s" }}>caisse</div>
            <div className="lbl" style={{ top: "27.5cqw" }}>EXPÉRIENCE</div>
            <div className="barre" style={{ top: "31cqw", width: "32cqw", background: "#D9D5E8" }} />
            <div className="barre" style={{ top: "33.6cqw", width: "26cqw" }} />
            <div className="barre" style={{ top: "36.2cqw", width: "29cqw" }} />
            <div className="barre" style={{ top: "39.6cqw", width: "24cqw" }} />
          </div>
          <div className="s-doc lettre secoue" style={{ "--d": ".45s" }}>
            <div className="s-ecrit">{lettres}</div>
            <div className="barre" style={{ top: "15.5cqw", width: "16cqw", background: "#D9D5E8" }} />
            <div className="barre" style={{ top: "18.6cqw", width: "33cqw" }} />
            <div className="barre" style={{ top: "21.2cqw", width: "30cqw" }} />
            <div className="s-place" style={{ top: "24.5cqw", left: "3.4cqw", "--d": "2.22s" }}>travail en équipe</div>
            <div className="barre" style={{ top: "31cqw", width: "33cqw" }} />
            <div className="barre" style={{ top: "33.6cqw", width: "28cqw" }} />
            <div className="barre" style={{ top: "36.2cqw", width: "31cqw" }} />
            <div className="barre" style={{ top: "40.4cqw", left: "22cqw", width: "13cqw", background: "#D9D5E8" }} />
          </div>
          <span className="s-scan cv" style={{ "--d": ".35s" }} />
          <span className="s-scan lettre" style={{ "--d": ".45s" }} />

          <span className="s-vol" style={{ left: "11cqw", top: "37.5cqw", "--vol": "vol1", "--d": ".95s" }}>accueil client</span>
          <span className="s-vol" style={{ left: "11cqw", top: "43.5cqw", "--vol": "vol2", "--d": "1.25s" }}>caisse</span>
          <span className="s-vol" style={{ left: "55.4cqw", top: "47.5cqw", "--vol": "vol3", "--d": "1.55s" }}>travail en équipe</span>

          <span className="s-anneau" />
          <div className="s-tampon">✓ Écrit pour cette offre</div>
        </div>
      </div>

      <div className="etape">
        <div className="etape-texte"><span className="etape-num">4</span><h3>Votre candidature part <em>directement chez l'employeur</em>.</h3></div>
        <div className="h-scene" aria-label="Le CV et la lettre partent dans une enveloppe vers la Boulangerie Martin. Candidature envoyée.">
          <span className="s-anneau vert" />
          <div className="s-boutique">
            <svg viewBox="0 0 112 112" aria-hidden="true">
              <rect x="16" y="44" width="80" height="56" rx="6" fill="none" stroke="#9B87FF" strokeWidth="6" />
              <path d="M10 44 L18 16 H94 L102 44" fill="none" stroke="#9B87FF" strokeWidth="6" strokeLinejoin="round" />
              <path d="M10 44 q11 12 23 0 q11 12 23 0 q11 12 23 0 q11 12 23 0" fill="none" stroke="#9B87FF" strokeWidth="6" strokeLinejoin="round" />
              <rect x="46" y="68" width="20" height="32" rx="3" fill="#9B87FF" />
              <rect x="26" y="66" width="12" height="12" rx="2" fill="#B7A8FF" />
              <rect x="74" y="66" width="12" height="12" rx="2" fill="#B7A8FF" />
            </svg>
          </div>
          <span className="s-coche" aria-hidden="true">✓</span>
          <div className="s-nom">Boulangerie Martin</div>

          <svg className="s-chemin" viewBox="0 0 100 70" aria-hidden="true">
            <path d="M30 50 Q 66 56 76 26" fill="none" stroke="#9B87FF" strokeWidth=".7" strokeDasharray="1.6 1.8" strokeLinecap="round" />
            <circle cx="30" cy="50" r="1.2" fill="#9B87FF" />
          </svg>
          {[1, 2, 3, 4].map((i) => <span className="s-trace" key={i} style={{ "--i": i }} />)}
          <div className="s-envol">
            <div className="s-envapp">
              <div className="s-dos" />
              <span className="s-mini a" />
              <span className="s-mini b" />
              <svg className="s-poche" viewBox="0 0 420 280" preserveAspectRatio="none" aria-hidden="true">
                <path d="M0 96 L210 196 L420 96 L420 262 Q420 280 402 280 L18 280 Q0 280 0 262 Z" fill="#6E56F8" />
                <path d="M0 96 L210 196 L420 96" fill="none" stroke="rgba(255,255,255,.35)" strokeWidth="3" />
              </svg>
              <svg className="s-rabat" viewBox="0 0 420 168" preserveAspectRatio="none" aria-hidden="true">
                <path d="M6 0 H414 Q420 0 416 6 L222 160 Q210 170 198 160 L4 6 Q0 0 6 0 Z" fill="#8470FF" />
              </svg>
            </div>
          </div>

          <div className="s-envoye">✓ Candidature envoyée</div>
        </div>
      </div>
    </div>
  );
}

function Drapeau() {
  return (
    <svg className="drapeau" viewBox="0 0 32 32" aria-hidden="true">
      <rect width="32" height="32" rx="6" fill="#D52B1E" />
      <path d="M13 6h6v7h7v6h-7v7h-6v-7H6v-6h7z" fill="#fff" />
    </svg>
  );
}
function DrapeauFR() {
  return (
    <svg className="drapeau" viewBox="0 0 32 32" aria-hidden="true">
      <clipPath id="arrondi-fr"><rect width="32" height="32" rx="6" /></clipPath>
      <g clipPath="url(#arrondi-fr)">
        <rect width="11" height="32" fill="#002395" /><rect x="11" width="10" height="32" fill="#FFFFFF" /><rect x="21" width="11" height="32" fill="#ED2939" />
      </g>
    </svg>
  );
}
function LigneSuisse() {
  return <div className="ligne-suisse"><Drapeau />Candidature pour la Suisse · CV au format suisse</div>;
}

/* Luxembourg et Australie : mêmes principes que la Suisse. Le pays vient
   de l'annonce choisie (perso.formatPays = "LU" ou "AU"). */
function DrapeauLU() {
  return (
    <svg className="drapeau" viewBox="0 0 32 32" aria-hidden="true">
      <clipPath id="arrondi-lu"><rect width="32" height="32" rx="6" /></clipPath>
      <g clipPath="url(#arrondi-lu)">
        <rect width="32" height="11" fill="#EF3340" /><rect y="11" width="32" height="10" fill="#FFFFFF" /><rect y="21" width="32" height="11" fill="#00A3E0" />
      </g>
    </svg>
  );
}
function DrapeauAU() {
  return (
    <svg className="drapeau" viewBox="0 0 32 32" aria-hidden="true">
      <rect width="32" height="32" rx="6" fill="#012169" />
      <path d="M0 0l16 10M16 0L0 10" stroke="#fff" strokeWidth="2.4" />
      <path d="M8 0v10M0 5h16" stroke="#fff" strokeWidth="3.2" />
      <path d="M8 0v10M0 5h16" stroke="#C8102E" strokeWidth="1.6" />
      <circle cx="8" cy="22" r="2.4" fill="#fff" /><circle cx="24" cy="9" r="1.4" fill="#fff" />
      <circle cx="20" cy="17" r="1.4" fill="#fff" /><circle cx="27" cy="16" r="1.2" fill="#fff" /><circle cx="24" cy="26" r="1.5" fill="#fff" />
    </svg>
  );
}
function LignePays({ pays }) {
  if (pays === "CH") return <LigneSuisse />;
  if (pays === "LU") return <div className="ligne-suisse"><DrapeauLU />Candidature pour le Luxembourg · CV au format luxembourgeois</div>;
  if (pays === "AU") return <div className="ligne-suisse"><DrapeauAU />Candidature pour l'Australie · CV et lettre en anglais</div>;
  return null;
}
/* Au Luxembourg, ces langues comptent : on les propose dès le début. */
const LANGUES_LU = ["Français", "Allemand", "Luxembourgeois", "Anglais"];
const NIVEAUX_LANGUE = ["Notions", "Intermédiaire", "Courant", "Langue maternelle"];

/* Liste des langues proposées dans le CV : [nom français, nom anglais].
   Les plus courantes d'abord, puis les autres par ordre alphabétique.
   Le nom anglais sert au CV pour l'Australie. Même liste dans
   api/modele-pdf.php (LANGUES_EN). */
const LANGUES_COURANTES = [
  ["Français", "French"], ["Anglais", "English"], ["Espagnol", "Spanish"], ["Allemand", "German"],
  ["Italien", "Italian"], ["Portugais", "Portuguese"], ["Arabe", "Arabic"], ["Luxembourgeois", "Luxembourgish"],
  ["Néerlandais", "Dutch"], ["Chinois", "Chinese"], ["Russe", "Russian"], ["Turc", "Turkish"],
  ["Polonais", "Polish"], ["Roumain", "Romanian"],
];
const LANGUES_AUTRES = [
  ["Afrikaans", "Afrikaans"], ["Albanais", "Albanian"], ["Amharique", "Amharic"], ["Arménien", "Armenian"],
  ["Azéri", "Azerbaijani"], ["Bambara", "Bambara"], ["Basque", "Basque"], ["Bengali", "Bengali"],
  ["Berbère", "Berber"], ["Biélorusse", "Belarusian"], ["Birman", "Burmese"], ["Bosnien", "Bosnian"],
  ["Breton", "Breton"], ["Bulgare", "Bulgarian"], ["Cantonais", "Cantonese"], ["Catalan", "Catalan"],
  ["Coréen", "Korean"], ["Corse", "Corsican"], ["Créole haïtien", "Haitian Creole"], ["Créole réunionnais", "Reunion Creole"],
  ["Croate", "Croatian"], ["Danois", "Danish"], ["Dari", "Dari"], ["Estonien", "Estonian"],
  ["Finnois", "Finnish"], ["Flamand", "Flemish"], ["Géorgien", "Georgian"], ["Grec", "Greek"],
  ["Gujarati", "Gujarati"], ["Haoussa", "Hausa"], ["Hébreu", "Hebrew"], ["Hindi", "Hindi"],
  ["Hongrois", "Hungarian"], ["Igbo", "Igbo"], ["Indonésien", "Indonesian"], ["Irlandais", "Irish"],
  ["Islandais", "Icelandic"], ["Japonais", "Japanese"], ["Kabyle", "Kabyle"], ["Kazakh", "Kazakh"],
  ["Khmer", "Khmer"], ["Kurde", "Kurdish"], ["Langue des signes australienne (Auslan)", "Auslan"],
  ["Langue des signes française (LSF)", "French Sign Language"], ["Lao", "Lao"], ["Letton", "Latvian"],
  ["Lingala", "Lingala"], ["Lituanien", "Lithuanian"], ["Macédonien", "Macedonian"], ["Malais", "Malay"],
  ["Malgache", "Malagasy"], ["Maltais", "Maltese"], ["Mandarin", "Mandarin"], ["Marathi", "Marathi"],
  ["Mongol", "Mongolian"], ["Népalais", "Nepali"], ["Norvégien", "Norwegian"], ["Occitan", "Occitan"],
  ["Ourdou", "Urdu"], ["Ouzbek", "Uzbek"], ["Pachto", "Pashto"], ["Pendjabi", "Punjabi"],
  ["Persan (farsi)", "Persian (Farsi)"], ["Peul", "Fula"], ["Romani", "Romani"], ["Serbe", "Serbian"],
  ["Slovaque", "Slovak"], ["Slovène", "Slovenian"], ["Somali", "Somali"], ["Soninké", "Soninke"],
  ["Suédois", "Swedish"], ["Suisse allemand", "Swiss German"], ["Swahili", "Swahili"], ["Tagalog", "Tagalog"],
  ["Tamoul", "Tamil"], ["Tchèque", "Czech"], ["Tchétchène", "Chechen"], ["Télougou", "Telugu"],
  ["Thaï", "Thai"], ["Tibétain", "Tibetan"], ["Tigrigna", "Tigrinya"], ["Ukrainien", "Ukrainian"],
  ["Vietnamien", "Vietnamese"], ["Wolof", "Wolof"], ["Yiddish", "Yiddish"], ["Yoruba", "Yoruba"], ["Zoulou", "Zulu"],
];
const TOUTES_LANGUES = [...LANGUES_COURANTES, ...LANGUES_AUTRES];
/* Niveaux proposés dans la liste (« Maternelle » des anciens CV reste lisible). */
const NIVEAUX_CV = ["Langue maternelle", "Bilingue", "Courant", "Intermédiaire", "Notions"];
/* Pour comparer sans se soucier des accents ni des majuscules. */
const sansAccent = (t) => String(t || "").normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().trim();
/* Nom officiel de la liste si la personne a tapé une langue connue. */
const langueCanonique = (t) => {
  const v = sansAccent(t);
  const trouve = TOUTES_LANGUES.find(([fr, en]) => sansAccent(fr) === v || sansAccent(en) === v);
  return trouve ? trouve[0] : String(t || "").trim();
};
/* Nom anglais (CV pour l'Australie) ; une langue écrite à la main reste telle quelle. */
const langueEn = (t) => {
  const v = sansAccent(t);
  const trouve = TOUTES_LANGUES.find(([fr]) => sansAccent(fr) === v);
  return trouve ? trouve[1] : t;
};

/* Choix d'une langue : on tape quelques lettres (« an » → Anglais) et
   on choisit dans la liste. « Autre » garde ce qui est écrit. */
function ChoixLangue({ valeur, onChange, onValider }) {
  const [ouvert, setOuvert] = useState(false);
  const [actif, setActif] = useState(0);
  const boite = useRef(null);
  const q = sansAccent(valeur);
  const liste = useMemo(() => {
    if (!q) return TOUTES_LANGUES.map((l) => l[0]);
    const debut = [], dedans = [];
    TOUTES_LANGUES.forEach(([fr, en]) => {
      const a = sansAccent(fr), b = sansAccent(en);
      if (a.startsWith(q) || b.startsWith(q) || a.split(/[\s(]+/).some((m) => m.startsWith(q))) debut.push(fr);
      else if (a.includes(q)) dedans.push(fr);
    });
    return [...debut, ...dedans];
  }, [q]);
  const exact = liste.some((l) => sansAccent(l) === q);
  const choix = [...liste, ...(q && !exact ? [{ autre: valeur.trim() }] : [])];

  useEffect(() => {
    const fermer = (e) => { if (boite.current && !boite.current.contains(e.target)) setOuvert(false); };
    document.addEventListener("mousedown", fermer);
    document.addEventListener("touchstart", fermer);
    return () => { document.removeEventListener("mousedown", fermer); document.removeEventListener("touchstart", fermer); };
  }, []);
  useEffect(() => { setActif(0); }, [q]);

  const prendre = (c) => {
    onChange(typeof c === "string" ? c : c.autre);
    setOuvert(false);
  };

  return (
    <div ref={boite} className="choix-langue">
      <input
        value={valeur}
        onChange={(e) => { onChange(e.target.value); setOuvert(true); }}
        onFocus={() => setOuvert(true)}
        placeholder="Tapez ou choisissez : Anglais, Espagnol…"
        autoComplete="off"
        role="combobox"
        aria-expanded={ouvert}
        onKeyDown={(e) => {
          if (e.key === "ArrowDown") { e.preventDefault(); setOuvert(true); setActif((a) => Math.min(a + 1, choix.length - 1)); }
          else if (e.key === "ArrowUp") { e.preventDefault(); setActif((a) => Math.max(a - 1, 0)); }
          else if (e.key === "Escape") setOuvert(false);
          else if (e.key === "Enter") {
            e.preventDefault();
            /* Entrée choisit la langue surlignée ; une 2e fois, elle l'ajoute. */
            if (ouvert && q && choix[actif]) { prendre(choix[actif]); return; }
            if (valeur.trim()) { setOuvert(false); onValider && onValider(); }
          }
        }}
      />
      {ouvert && (
        <div className="choix-langue-liste" role="listbox">
          {choix.map((c, i) => {
            const autre = typeof c !== "string";
            const separateur = !q && i === LANGUES_COURANTES.length;
            return (
              <React.Fragment key={autre ? "autre" : c}>
                {separateur && <div className="choix-langue-titre">Toutes les langues</div>}
                {!q && i === 0 && <div className="choix-langue-titre">Les plus courantes</div>}
                <button
                  type="button"
                  role="option"
                  aria-selected={i === actif}
                  className={`choix-langue-option ${i === actif ? "on" : ""}`}
                  onMouseDown={(e) => e.preventDefault()}
                  onMouseEnter={() => setActif(i)}
                  onClick={() => prendre(c)}
                >
                  {autre ? <>Autre : « {c.autre} »</> : c}
                </button>
              </React.Fragment>
            );
          })}
          {!q && (
            <div className="choix-langue-titre" style={{ textTransform: "none", letterSpacing: 0 }}>
              Autre langue ? Écrivez simplement son nom.
            </div>
          )}
        </div>
      )}
    </div>
  );
}
/* Australie : le visa est facultatif ; vide = rien sur le CV. */
const VISAS_AU = [
  { v: "", nom: "Non précisé" },
  { v: "Australian citizen", nom: "Citoyen australien" },
  { v: "Permanent resident", nom: "Résident permanent" },
  { v: "Working Holiday visa (subclass 417/462)", nom: "Working Holiday Visa (PVT, 417/462)" },
  { v: "Temporary Skill Shortage visa (subclass 482)", nom: "Visa de travail sponsorisé (482)" },
  { v: "Student visa (subclass 500)", nom: "Visa étudiant (500)" },
  { v: "Visa holder with work rights", nom: "Autre visa avec droit de travailler" },
];

/* Australie : l'aperçu du CV et de la lettre en anglais. Le texte est
   déjà rédigé en anglais par l'IA ; on traduit ce que les modèles
   écrivent eux-mêmes (titres, étiquettes, mois, langues). Même
   dictionnaire que le PDF (api/modele-pdf.php, texte_en). */
const TITRES_EN = {
  "PROFIL": "PROFILE", "Profil": "Profile", "PROFIL PROFESSIONNEL": "PROFESSIONAL PROFILE",
  "EXPÉRIENCES PROFESSIONNELLES": "WORK EXPERIENCE", "Expériences professionnelles": "Work experience",
  "EXPÉRIENCE PROFESSIONNELLE": "WORK EXPERIENCE", "Expérience professionnelle": "Work experience",
  "PARCOURS PROFESSIONNEL": "WORK EXPERIENCE", "PARCOURS": "EXPERIENCE", "Parcours": "Experience",
  "FORMATION": "EDUCATION", "FORMATIONS": "EDUCATION", "Formation": "Education", "Formations": "Education",
  "Diplômes et formations": "Education", "DIPLÔMES ET FORMATIONS": "EDUCATION",
  "LANGUES": "LANGUAGES", "Langues": "Languages",
  "COMPÉTENCES ET QUALITÉS": "SKILLS", "Compétences et qualités": "Skills", "Centres d’intérêt": "Interests",
  "COMPÉTENCES": "SKILLS", "Compétences": "Skills", "QUALITÉS": "QUALITIES", "Qualités": "Qualities",
  "COORDONNÉES": "CONTACT DETAILS", "Coordonnées": "Contact details",
  "ADRESSE": "ADDRESS", "Adresse": "Address", "TÉLÉPHONE": "PHONE", "Téléphone": "Phone",
  "PERMIS": "LICENCE", "Permis": "Licence", "DESTINATAIRE": "TO", "EXPÉDITEUR": "FROM",
};
const MOTS_EN = [
  [/\bPermis de conduire B\b/g, "Driver's licence"], [/\bPermis B\b/g, "Driver's licence"],
  [/aujourd'hui/g, "present"], [/AUJOURD'HUI/g, "PRESENT"],
  [/janvier/g, "January"], [/février/g, "February"], [/avril/g, "April"], [/juillet/g, "July"],
  [/septembre/g, "September"], [/octobre/g, "October"], [/novembre/g, "November"], [/décembre/g, "December"],
  [/\bjanv\./g, "Jan"], [/févr\./g, "Feb"], [/\bmars\b/g, "Mar"], [/\bavr\./g, "Apr"], [/\bmai\b/g, "May"],
  [/\bjuin\b/g, "Jun"], [/\bjuil\./g, "Jul"], [/août/g, "Aug"], [/\bsept\./g, "Sep"], [/\boct\./g, "Oct"],
  [/\bnov\./g, "Nov"], [/déc\./g, "Dec"],
  [/Français/g, "French"], [/\bAnglais\b/g, "English"], [/\bAllemand\b/g, "German"], [/\bLuxembourgeois\b/g, "Luxembourgish"],
  [/\bEspagnol\b/g, "Spanish"], [/\bItalien\b/g, "Italian"], [/\bPortugais\b/g, "Portuguese"], [/Néerlandais/g, "Dutch"],
  [/\bArabe\b/g, "Arabic"], [/Langue maternelle/g, "Native"], [/\bMaternelle\b/g, "Native"], [/\bBilingue\b/g, "Bilingual"], [/\bCourant\b/g, "Fluent"],
  [/Intermédiaire/g, "Intermediate"], [/\bNotions\b/g, "Basic"], [/Débutant/g, "Beginner"],
];
function texteEn(t) {
  const brut = t.trim();
  if (TITRES_EN[brut]) return t.replace(brut, TITRES_EN[brut]);
  return MOTS_EN.reduce((s, [re, en]) => s.replace(re, en), t);
}
function Anglais({ actif, children }) {
  const boite = useRef(null);
  React.useLayoutEffect(() => {
    if (!actif || !boite.current) return;
    const parcours = document.createTreeWalker(boite.current, NodeFilter.SHOW_TEXT);
    let n;
    while ((n = parcours.nextNode())) {
      const nouveau = texteEn(n.nodeValue);
      if (nouveau !== n.nodeValue) n.nodeValue = nouveau;
    }
  });
  return <div ref={boite}>{children}</div>;
}

const CSS = `
@import url('https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&family=Plus+Jakarta+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&family=Source+Serif+4:opsz,wght@8..60,400;8..60,600&display=swap');

.app {
  --fond: #08080B;
  --surface: #121217;
  --surface2: #1B1B22;
  --bord: rgba(255,255,255,.09);
  --bord-fort: rgba(255,255,255,.16);
  --texte: #F4F4F7;
  --doux: #92929E;
  --accent: #6E56F8;
  --accent-clair: #9B87FF;
  --menthe: #22D3A6;
  font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
  background: var(--fond); color: var(--texte); min-height: 100vh;
  position: relative; overflow-x: hidden; -webkit-font-smoothing: antialiased;
}
.app *, .app *::before, .app *::after { box-sizing: border-box; }
.halo { position: absolute; top: -220px; left: 50%; transform: translateX(-50%); width: 780px; height: 560px; background: radial-gradient(ellipse at center, rgba(110,86,248,.32), rgba(110,86,248,0) 65%); pointer-events: none; filter: blur(20px); }
.wrap { position: relative; max-width: 620px; margin: 0 auto; padding: 18px 18px 140px; }

.top { display: flex; align-items: center; justify-content: space-between; padding: 6px 0 26px; }
.logo { font-family: 'Sora', sans-serif; font-weight: 800; font-size: 21px; letter-spacing: -.04em; display: flex; align-items: center; gap: 8px; cursor: pointer; }
.pastille { width: 26px; height: 26px; border-radius: 9px; background: linear-gradient(145deg, var(--accent-clair), var(--accent)); display: grid; place-items: center; font-size: 12px; font-weight: 800; color: #fff; box-shadow: 0 4px 16px rgba(110,86,248,.45); }
.etiq { font-family: 'JetBrains Mono', monospace; font-size: 10.5px; color: var(--doux); border: 1px solid var(--bord); border-radius: 999px; padding: 5px 11px; }

/* Rappel de l'annonce, quand la candidature part d'une offre trouvee sur le site. */
.rappel-offre { background: rgba(110,86,248,.09); border: 1px solid rgba(110,86,248,.32);
  border-radius: 16px; padding: 13px 15px; margin-bottom: 20px; }
.rappel-offre .quoi { font-family: 'JetBrains Mono', monospace; font-size: 10px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--accent-clair); display: block; margin-bottom: 5px; }
.rappel-offre b { font-family: 'Sora', sans-serif; font-size: 15.5px; letter-spacing: -.02em; display: block; }
.rappel-offre .boite { font-size: 13px; color: var(--doux); display: block; margin-top: 3px; }
.rappel-offre a { font-size: 12.5px; color: var(--accent-clair); text-decoration: none; display: inline-block; margin-top: 8px; }

/* ---------- vente ---------- */
.hero h1 { font-family: 'Sora', sans-serif; font-weight: 800; font-size: clamp(34px, 9vw, 52px); line-height: 1.02; letter-spacing: -.045em; margin: 10px 0 18px; }
.hero h1 .lueur { background: linear-gradient(100deg, var(--accent-clair), #5CE1E6); -webkit-background-clip: text; background-clip: text; color: transparent; }
.hero p.intro { font-size: 17px; line-height: 1.6; color: #C3C3CD; margin: 0 0 14px; }
.hero p.appui { font-size: 15px; line-height: 1.6; color: var(--doux); margin: 0 0 26px; }
/* ---------- Histoire animée de l'accueil (4 étapes au défilement) ----------
   Chaque étape s'anime quand elle arrive à l'écran (classe « joue »).
   Les tailles sont en cqw (% de la largeur de la scène) : tout garde
   ses proportions, de l'ordinateur au téléphone. L'état de repos de
   chaque élément est son état final : sans animation (réglage
   « réduire les animations »), les étapes s'affichent telles quelles. */
.histoire { margin-top: 58px; }
.histoire > h2 { font-family: 'Sora', sans-serif; font-weight: 700; font-size: 27px; letter-spacing: -.035em; margin: 0 0 30px; line-height: 1.15; }
.etape { margin: 0 0 58px; }
.etape-texte { display: flex; gap: 13px; align-items: flex-start; margin-bottom: 14px; }
.etape-num { width: 34px; height: 34px; border-radius: 50%; flex-shrink: 0; display: grid; place-items: center; font-family: 'Sora', sans-serif; font-weight: 800; font-size: 15px;
  color: #fff; background: linear-gradient(140deg, var(--accent-clair), var(--accent)); box-shadow: 0 0 22px rgba(110,86,248,.5); }
.etape h3 { font-family: 'Sora', sans-serif; font-weight: 700; font-size: 20px; letter-spacing: -.02em; line-height: 1.3; margin: 4px 0 0; }
.etape h3 em { font-style: normal; color: var(--accent-clair); }
.h-scene { position: relative; container-type: inline-size; aspect-ratio: 10 / 7; border-radius: 22px; overflow: hidden; border: 1px solid var(--bord);
  background: radial-gradient(circle at 50% 38%, rgba(110,86,248,.18), transparent 62%), var(--surface); }
.h-scene * { box-sizing: border-box; }
.etape:not(.joue) .etape-texte, .etape:not(.joue) .h-scene { opacity: 0; transform: translateY(28px); }
.etape.joue .etape-texte { animation: etapeMonte .6s cubic-bezier(.2,.7,.3,1) both; }
.etape.joue .h-scene { animation: etapeMonte .7s .1s cubic-bezier(.2,.7,.3,1) both; }
@keyframes etapeMonte { from { opacity: 0; transform: translateY(28px); } to { opacity: 1; transform: none; } }

/* Apparition simple, avec un délai propre à chaque élément (--d) */
.etape:not(.joue) .ap { opacity: 0; }
.etape.joue .ap { animation: apparait .5s var(--d, 0s) cubic-bezier(.2,.7,.3,1) both; }
@keyframes apparait { from { opacity: 0; transform: translateY(2.4cqw); } to { opacity: 1; transform: none; } }

/* Carte de l'offre */
.s-carte { position: absolute; left: 9cqw; width: 82cqw; padding: 4.4cqw 5cqw; border-radius: 4cqw;
  background: linear-gradient(180deg, #17171F, #111116); border: 1px solid var(--bord); box-shadow: 0 3cqw 8cqw rgba(0,0,0,.45); }
.s-tag { font-family: 'JetBrains Mono', monospace; font-size: 2.3cqw; letter-spacing: .18em; text-transform: uppercase; color: var(--accent-clair); }
.s-titre { font-family: 'Sora', sans-serif; font-weight: 800; font-size: 7.6cqw; letter-spacing: -.045em; line-height: 1.08; margin: 1.4cqw 0 .8cqw; }
.s-lieu { font-size: 3.3cqw; color: var(--doux); font-weight: 500; }
.s-lieu b { color: var(--texte); font-weight: 600; }
.s-desc { font-size: 3.4cqw; line-height: 1.55; color: #B9B9C4; margin-top: 2.2cqw; }
.s-btn { position: relative; margin-top: 3.4cqw; height: 9.4cqw; border-radius: 2.6cqw; display: flex; align-items: center; justify-content: center; gap: 1.6cqw;
  font-family: 'Sora', sans-serif; font-weight: 700; font-size: 3.7cqw; color: #fff; letter-spacing: -.02em;
  background: linear-gradient(140deg, #BEB0FF, #826CFF); box-shadow: 0 0 7cqw rgba(155,135,255,.75), inset 0 1px 0 rgba(255,255,255,.4); }
.s-btn .pf { width: 4.6cqw; height: 4.6cqw; border-radius: 1.3cqw; background: #fff; color: var(--accent); display: grid; place-items: center; font-weight: 800; font-size: 2.6cqw; }
.etape.joue .s-btn { animation: boutonClic 1s 1.5s both; }
@keyframes boutonClic {
  0% { transform: none; background: linear-gradient(140deg, #9B87FF, #6E56F8); box-shadow: 0 1.5cqw 4cqw rgba(110,86,248,.45), inset 0 1px 0 rgba(255,255,255,.35); }
  12% { transform: translateY(.7cqw) scale(.97); background: linear-gradient(140deg, #8E78FF, #5F47EE); box-shadow: 0 .4cqw 1.5cqw rgba(110,86,248,.4), inset 0 .6cqw 1.4cqw rgba(40,20,120,.5); }
  30% { transform: none; }
  100% { transform: none; background: linear-gradient(140deg, #BEB0FF, #826CFF); box-shadow: 0 0 7cqw rgba(155,135,255,.75), inset 0 1px 0 rgba(255,255,255,.4); }
}
.etape:not(.joue) .s-btn { background: linear-gradient(140deg, #9B87FF, #6E56F8); box-shadow: 0 1.5cqw 4cqw rgba(110,86,248,.45); }
.s-reflet { position: absolute; inset: 0; border-radius: inherit; overflow: hidden; pointer-events: none; }
.s-reflet::after { content: ""; position: absolute; top: 0; bottom: 0; width: 30%; left: -40%; opacity: 0;
  background: linear-gradient(100deg, transparent, rgba(255,255,255,.6), transparent); }
.etape.joue .s-reflet::after { animation: reflet .7s 1.7s ease-out both; }
@keyframes reflet { 0% { left: -40%; opacity: 1; } 99% { opacity: 1; } 100% { left: 120%; opacity: 0; } }
.s-onde { position: absolute; inset: 0; border-radius: 3cqw; border: .6cqw solid var(--accent-clair); opacity: 0; pointer-events: none; }
.etape.joue .s-onde { animation: ondeBouton .7s 1.62s ease-out both; }
@keyframes ondeBouton { from { opacity: .9; transform: scale(1); } to { opacity: 0; transform: scale(1.18, 1.6); } }
.k { position: relative; white-space: nowrap; }
.k.on { color: #fff; }
.k.on::before { content: ""; position: absolute; left: -.8cqw; right: -.8cqw; top: .2cqw; bottom: 0; border-radius: 1cqw; z-index: -1;
  background: linear-gradient(100deg, #6E56F8, #9B87FF); transform-origin: left center; }
.s-desc, .s-mots { isolation: isolate; }

/* Coins de mise au point */
.coins { position: absolute; inset: -2.4cqw; pointer-events: none; }
.coins i { position: absolute; width: 6cqw; height: 6cqw; border: 0 solid var(--accent-clair); filter: drop-shadow(0 0 1.4cqw rgba(155,135,255,.85)); }
.coins i:nth-child(1) { left: 0; top: 0; border-width: .9cqw 0 0 .9cqw; border-radius: 1.4cqw 0 0 0; --x: -1; --y: -1; }
.coins i:nth-child(2) { right: 0; top: 0; border-width: .9cqw .9cqw 0 0; border-radius: 0 1.4cqw 0 0; --x: 1; --y: -1; }
.coins i:nth-child(3) { left: 0; bottom: 0; border-width: 0 0 .9cqw .9cqw; border-radius: 0 0 0 1.4cqw; --x: -1; --y: 1; }
.coins i:nth-child(4) { right: 0; bottom: 0; border-width: 0 .9cqw .9cqw 0; border-radius: 0 0 1.4cqw 0; --x: 1; --y: 1; }
.coins.petits { inset: -1.8cqw; }
.coins.petits i { width: 4.4cqw; height: 4.4cqw; border-width: 0; }
.coins.petits i:nth-child(1) { border-width: .75cqw 0 0 .75cqw; } .coins.petits i:nth-child(2) { border-width: .75cqw .75cqw 0 0; }
.coins.petits i:nth-child(3) { border-width: 0 0 .75cqw .75cqw; } .coins.petits i:nth-child(4) { border-width: 0 .75cqw .75cqw 0; }
.etape:not(.joue) .coins i { opacity: 0; }
.etape.joue .coins i { animation: coinEntre .7s var(--d, .2s) cubic-bezier(.2,.8,.3,1) both, coinFlash .4s calc(var(--d, .2s) + .62s) both; }
@keyframes coinEntre { from { opacity: 0; transform: translate(calc(var(--x) * 9cqw), calc(var(--y) * 9cqw)); } to { opacity: 1; transform: none; } }
@keyframes coinFlash { 50% { border-color: #EAE5FF; transform: translate(calc(var(--x) * .8cqw), calc(var(--y) * .8cqw)); } }

/* Flèche de souris, ou doigt sur téléphone */
.s-main { position: absolute; right: 9%; top: 44%; width: 7cqw; pointer-events: none; z-index: 3; }
.s-main svg { display: block; width: 100%; filter: drop-shadow(0 1cqw 1.6cqw rgba(0,0,0,.55)); transform-origin: 10% 8%; }
.s-main .doigt { display: none; width: 7cqw; height: 7cqw; margin: -3.5cqw 0 0 -3.5cqw; border-radius: 50%;
  background: rgba(255,255,255,.55); border: .5cqw solid #fff; box-shadow: 0 0 3cqw rgba(255,255,255,.6); }
@media (hover: none) { .s-main svg { display: none; } .s-main .doigt { display: block; } }
.etape:not(.joue) .s-main { opacity: 0; }
.etape.joue .s-main { animation: mainArrive 1.1s .45s cubic-bezier(.2,.7,.3,1) both; }
.etape.joue .s-main svg, .etape.joue .s-main .doigt { animation: mainClic .25s 1.5s both; }
@keyframes mainArrive { from { opacity: 0; transform: translate(26cqw, 24cqw); } 25% { opacity: 1; } to { opacity: 1; transform: none; } }
@keyframes mainClic { 50% { transform: scale(.82); } }

/* Étape 3 : l'annonce, le CV, la lettre */
.s-bande { position: absolute; left: 8cqw; top: 4cqw; width: 84cqw; height: 14.5cqw; border-radius: 3cqw; padding: 2.2cqw 4cqw;
  background: #15151C; border: 1px solid var(--bord); }
.s-bande .s-tag { font-size: 2.1cqw; }
.s-mots { position: absolute; left: 0; right: 0; top: 7cqw; font-size: 3.3cqw; font-weight: 600; color: #B9B9C4; }
.s-mots .k { position: absolute; padding: 0 1.2cqw; color: #fff; }
.s-mots .k::before { left: 0; right: 0; }
.etape:not(.joue) .s-mots .k::before { transform: scaleX(0); }
.etape.joue .s-mots .k::before { animation: surligne .35s var(--d) ease-out both, palit .4s calc(var(--d) + .35s) both; }
@keyframes surligne { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes palit { from { opacity: 1; } to { opacity: .45; } }
.s-mots .k::before { opacity: .45; }
.s-doc { position: absolute; top: 23cqw; width: 40cqw; height: 45cqw; border-radius: 2.4cqw; overflow: hidden; background: #FBFAFF; color: #1B1B22;
  box-shadow: 0 2.4cqw 6cqw rgba(0,0,0,.5); }
.s-doc.cv { left: 8cqw; } .s-doc.lettre { left: 52cqw; }
.etape:not(.joue) .s-doc { clip-path: inset(0 0 100% 0); }
.etape.joue .s-doc { animation: dessine .55s var(--d) cubic-bezier(.5,0,.3,1) both; }
@keyframes dessine { from { clip-path: inset(0 0 100% 0); } to { clip-path: inset(0 0 0 0); } }
.etape.joue .s-doc.secoue { animation: dessine .55s var(--d) cubic-bezier(.5,0,.3,1) both, secoue .35s 3.35s both; }
@keyframes secoue { 25% { transform: translateY(1cqw); } 55% { transform: translateY(-.6cqw); } 80% { transform: translateY(.3cqw); } }
.s-scan { position: absolute; top: 23cqw; width: 44cqw; height: .9cqw; border-radius: 1cqw; opacity: 0; pointer-events: none; z-index: 2;
  background: linear-gradient(90deg, transparent, #B7A8FF 15%, #fff 50%, #B7A8FF 85%, transparent); box-shadow: 0 0 3cqw 1cqw rgba(155,135,255,.75); }
.s-scan.cv { left: 6cqw; } .s-scan.lettre { left: 50cqw; }
.etape.joue .s-scan { animation: scan .55s var(--d) cubic-bezier(.5,0,.3,1) both; }
@keyframes scan { 0% { opacity: 1; transform: translateY(0); } 90% { opacity: 1; } 100% { opacity: 0; transform: translateY(45cqw); } }
.s-doc .tete { height: 8cqw; background: linear-gradient(135deg, #6E56F8, #9B87FF); position: relative; }
.s-doc .tete::before { content: ""; position: absolute; left: 3cqw; top: 1.8cqw; width: 4.4cqw; height: 4.4cqw; border-radius: 50%; background: rgba(255,255,255,.8); }
.s-doc .tete::after { content: ""; position: absolute; left: 9cqw; top: 2.6cqw; width: 18cqw; height: 1.4cqw; border-radius: 1cqw; background: #fff; box-shadow: 0 2.2cqw 0 -.2cqw rgba(255,255,255,.6); }
.s-doc .lbl { position: absolute; left: 3cqw; font-family: 'JetBrains Mono', monospace; font-size: 1.8cqw; letter-spacing: .12em; color: var(--accent); }
.s-doc .barre { position: absolute; left: 3cqw; height: 1.2cqw; border-radius: 1cqw; background: #E6E3F0; }
.s-place { position: absolute; left: 3cqw; padding: 0 1.4cqw; height: 4.4cqw; line-height: 4cqw; border-radius: 1.2cqw; white-space: nowrap;
  font-size: 2.8cqw; font-weight: 700; color: #5A43E0; background: rgba(110,86,248,.12); border: .3cqw solid rgba(110,86,248,.38); }
.etape:not(.joue) .s-place { opacity: 0; }
.etape.joue .s-place { animation: pose .35s var(--d) cubic-bezier(.3,1.6,.5,1) both; }
@keyframes pose { from { opacity: 0; transform: scale(1.3); box-shadow: 0 0 3cqw rgba(110,86,248,.9); } to { opacity: 1; transform: none; } }
.s-ecrit { position: absolute; left: 3.4cqw; top: 2.6cqw; width: 34cqw; font-family: 'Sora', sans-serif; font-weight: 700; font-size: 2.75cqw; line-height: 1.3; letter-spacing: -.02em; }
.s-ecrit .v { color: var(--accent); }
.etape:not(.joue) .s-ecrit span { opacity: 0; }
.etape.joue .s-ecrit span { animation: lettre .01s calc(1.25s + var(--i) * 28ms) both; }
@keyframes lettre { from { opacity: 0; } to { opacity: 1; } }
.s-vol { position: absolute; white-space: nowrap; padding: 0 1.2cqw; border-radius: 1cqw; font-size: 3.3cqw; line-height: 1.45; font-weight: 600; color: #fff;
  background: linear-gradient(100deg, #6E56F8, #9B87FF); box-shadow: 0 0 3cqw rgba(155,135,255,.75); transform-origin: 0 0; opacity: 0; pointer-events: none; z-index: 3; }
.etape.joue .s-vol { animation: var(--vol) .7s var(--d) cubic-bezier(.5,0,.35,1) both; }
@keyframes vol1 { 0% { opacity: 1; transform: translate(1cqw, -26.5cqw); } 50% { transform: translate(-9cqw, -14cqw) scale(1.15) rotate(-7deg); } 92% { opacity: 1; transform: scale(.85); } 100% { opacity: 0; transform: scale(.85); } }
@keyframes vol2 { 0% { opacity: 1; transform: translate(30cqw, -32.5cqw); } 50% { transform: translate(24cqw, -14cqw) scale(1.15) rotate(7deg); } 92% { opacity: 1; transform: scale(.85); } 100% { opacity: 0; transform: scale(.85); } }
@keyframes vol3 { 0% { opacity: 1; transform: translate(1.6cqw, -36.5cqw); } 50% { transform: translate(14cqw, -20cqw) scale(1.15) rotate(8deg); } 92% { opacity: 1; transform: scale(.85); } 100% { opacity: 0; transform: scale(.85); } }
.s-tampon { position: absolute; left: 50%; top: 45cqw; z-index: 4; white-space: nowrap; transform: translate(-50%, -50%) rotate(-7deg);
  font-family: 'Sora', sans-serif; font-weight: 800; font-size: 4.6cqw; letter-spacing: -.02em; color: var(--accent);
  background: rgba(255,255,255,.95); border: .7cqw solid var(--accent); border-radius: 2.4cqw; padding: 1.3cqw 3.2cqw;
  box-shadow: 0 2cqw 5cqw rgba(110,86,248,.45), inset 0 0 0 .35cqw rgba(255,255,255,.9), inset 0 0 0 .7cqw rgba(110,86,248,.35); }
.etape:not(.joue) .s-tampon { opacity: 0; }
.etape.joue .s-tampon { animation: tampon .45s 3.1s both; }
@keyframes tampon {
  0% { opacity: 0; transform: translate(-50%, -50%) rotate(-7deg) scale(2.6); filter: blur(.8cqw); }
  55% { opacity: 1; transform: translate(-50%, -50%) rotate(-7deg) scale(1); filter: none; }
  75% { transform: translate(-50%, -50%) rotate(-7deg) scale(.94); }
  100% { opacity: 1; transform: translate(-50%, -50%) rotate(-7deg) scale(1); }
}
.s-anneau { position: absolute; left: 50%; top: 45cqw; width: 20cqw; height: 20cqw; margin: -10cqw 0 0 -10cqw; border-radius: 50%;
  border: .5cqw solid var(--accent-clair); opacity: 0; pointer-events: none; z-index: 3; }
.etape.joue .s-anneau { animation: anneau .6s 3.35s ease-out both; }
.etape.joue .s-anneau.vert { animation-delay: 2.05s; }
@keyframes anneau { from { opacity: .85; transform: scale(1); } to { opacity: 0; transform: scale(4.5); } }

/* Étape 4 : l'enveloppe part chez l'employeur */
.s-boutique { position: absolute; left: 70cqw; top: 6cqw; width: 18cqw; height: 18cqw; border-radius: 4.6cqw; display: grid; place-items: center;
  background: linear-gradient(150deg, rgba(155,135,255,.3), rgba(110,86,248,.1)); border: 1px solid rgba(155,135,255,.45); box-shadow: 0 0 6cqw rgba(110,86,248,.35); }
.s-boutique svg { width: 11cqw; }
.etape:not(.joue) .s-boutique { opacity: 0; }
.etape.joue .s-boutique { animation: apparait .5s .2s both, pulse .35s 2.05s both; }
@keyframes pulse { 50% { transform: scale(1.14); } }
.s-nom { position: absolute; left: 79cqw; top: 26cqw; transform: translateX(-50%); white-space: nowrap; font-family: 'Sora', sans-serif; font-weight: 700; font-size: 3cqw; }
.etape:not(.joue) .s-nom { opacity: 0; }
.etape.joue .s-nom { animation: fondu .4s .45s both; }
@keyframes fondu { from { opacity: 0; } to { opacity: 1; } }
.s-coche { position: absolute; left: 84.5cqw; top: 3cqw; width: 6.4cqw; height: 6.4cqw; border-radius: 50%; display: grid; place-items: center; z-index: 2;
  background: var(--menthe); color: #06231B; font-weight: 800; font-size: 3.6cqw; border: .5cqw solid var(--surface); box-shadow: 0 0 3cqw rgba(34,211,166,.7); }
.etape:not(.joue) .s-coche { opacity: 0; }
.etape.joue .s-coche { animation: pose .35s 2.1s cubic-bezier(.3,1.6,.5,1) both; }
.s-anneau.vert { left: 79cqw; top: 15cqw; border-color: var(--menthe); }
.s-envoye { position: absolute; left: 50%; bottom: 5cqw; transform: translateX(-50%); white-space: nowrap; font-family: 'Sora', sans-serif; font-weight: 700; font-size: 3.9cqw;
  color: var(--menthe); background: rgba(34,211,166,.12); border: 1px solid rgba(34,211,166,.45); border-radius: 999px; padding: 1.4cqw 3.6cqw; }
.etape:not(.joue) .s-envoye { opacity: 0; }
.etape.joue .s-envoye { animation: envoye .45s 2.2s cubic-bezier(.2,.7,.3,1) both; }
@keyframes envoye { from { opacity: 0; transform: translate(-50%, 2.5cqw); } to { opacity: 1; transform: translateX(-50%); } }
.s-envol { position: absolute; left: 14cqw; top: 34cqw; width: 40cqw; height: 26.7cqw; opacity: 0; pointer-events: none; }
.etape.joue .s-envol { animation: envol .75s 1.3s cubic-bezier(.45,0,.4,1) both; }
@keyframes envol {
  0% { opacity: 1; transform: none; }
  14% { opacity: 1; transform: translateY(1.5cqw) scale(1.05, .9); }
  55% { opacity: 1; transform: translate(31cqw, -6cqw) rotate(14deg) scale(.62); }
  94% { opacity: 1; transform: translate(45cqw, -32cqw) rotate(-4deg) scale(.24); }
  100% { opacity: 0; transform: translate(45cqw, -32cqw) rotate(-4deg) scale(.2); }
}
.s-envapp { position: absolute; inset: 0; }
.s-chemin { position: absolute; inset: 0; width: 100%; height: 100%; opacity: .7; pointer-events: none; }
.etape:not(.joue) .s-chemin { opacity: 0; }
.etape.joue .s-chemin { animation: chemin .6s 1.55s ease-out both; }
@keyframes chemin { from { opacity: 0; clip-path: inset(0 100% 0 0); } to { opacity: .7; clip-path: inset(0 0 0 0); } }
.etape.joue .s-envapp { animation: apparait .45s .15s both; }
.s-dos { position: absolute; inset: 0; border-radius: 2cqw; background: linear-gradient(180deg, #3A2E8C, #251D5E); z-index: 1; }
.s-poche, .s-rabat { position: absolute; left: 0; top: 0; width: 100%; display: block; overflow: visible; }
.s-poche { height: 100%; z-index: 3; }
.s-rabat { height: 60%; z-index: 4; transform-origin: top center; }
.etape.joue .s-rabat { animation: rabat .25s 1s ease-in-out both; }
@keyframes rabat { from { transform: scaleY(-1); z-index: 0; } 50% { z-index: 0; } 51% { z-index: 4; } to { transform: scaleY(1); z-index: 4; } }
.s-mini { position: absolute; top: 2cqw; width: 15cqw; height: 20cqw; border-radius: 1.2cqw; background: #FBFAFF; z-index: 2; overflow: hidden;
  box-shadow: 0 1cqw 2cqw rgba(0,0,0,.35); }
.s-mini::before { content: ""; position: absolute; left: 0; right: 0; top: 0; height: 3.6cqw; background: linear-gradient(135deg, #6E56F8, #9B87FF); }
.s-mini::after { content: ""; position: absolute; left: 1.6cqw; top: 5.4cqw; width: 11cqw; height: 1cqw; border-radius: 1cqw; background: #E3DFEF;
  box-shadow: 0 2.2cqw 0 #E3DFEF, 0 4.4cqw 0 #E3DFEF, 0 6.6cqw 0 #E3DFEF, 0 8.8cqw 0 #E3DFEF; }
.s-mini.a { left: 4.5cqw; transform: rotate(-5deg); } .s-mini.b { left: 20.5cqw; transform: rotate(5deg); }
.s-mini.b::before { height: 0; } .s-mini.b::after { top: 2.4cqw; }
.etape:not(.joue) .s-mini { opacity: 0; }
.etape.joue .s-mini { animation: miniEntre .5s .5s cubic-bezier(.5,0,.3,1) both, fondu .1s 1.18s reverse both; }
@keyframes miniEntre { from { opacity: 1; translate: 0 -15cqw; } to { opacity: 1; translate: 0 0; } }
.s-mini { opacity: 0; }
.s-trace { position: absolute; left: 32cqw; top: 46cqw; width: 2.4cqw; height: 2.4cqw; border-radius: 50%; background: #B7A8FF; opacity: 0; pointer-events: none;
  box-shadow: 0 0 2.4cqw .8cqw rgba(155,135,255,.75); }
.etape.joue .s-trace { animation: envol .75s calc(1.3s + var(--i) * 40ms) cubic-bezier(.45,0,.4,1) both; }

/* Réglage « réduire les animations » : tout s'affiche directement */
@media (prefers-reduced-motion: reduce) {
  .histoire *, .histoire *::before, .histoire *::after { animation: none !important; transition: none !important; }
}
.bande { margin: 30px -18px 0; overflow: hidden; -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent); }
.bande-titre { font-family: 'JetBrains Mono', monospace; font-size: 10.5px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--doux); text-align: center; margin-bottom: 14px; }
.defile { display: flex; gap: 10px; width: max-content; animation: glisse 32s linear infinite; }
.defile.envers { animation-direction: reverse; margin-top: 10px; animation-duration: 38s; }
.tuile { flex-shrink: 0; width: 116px; background: var(--surface); border: 1px solid var(--bord);
  border-radius: 18px; padding: 16px 10px 13px; text-align: center; }
.tuile .rond-ic { width: 46px; height: 46px; margin: 0 auto 9px; border-radius: 14px; display: grid; place-items: center;
  background: linear-gradient(150deg, rgba(155,135,255,.22), rgba(110,86,248,.1)); color: var(--accent-clair); }
.tuile b { display: block; font-size: 12px; font-weight: 600; color: #C9C9D4; line-height: 1.3; }
.tuile img { width: 100%; height: 76px; object-fit: cover; border-radius: 12px; margin-bottom: 9px; display: block; }
@keyframes glisse { from { transform: translateX(0) } to { transform: translateX(-50%) } }
@media (prefers-reduced-motion: reduce) { .defile { animation: none } }

.garantie { display: flex; gap: 13px; align-items: flex-start; background: rgba(34,211,166,.08);
  border: 1px solid rgba(34,211,166,.35); border-radius: 18px; padding: 16px; margin: 18px 0; }
.garantie .sceau { width: 42px; height: 42px; border-radius: 50%; flex-shrink: 0; display: grid; place-items: center;
  background: rgba(34,211,166,.16); color: var(--menthe); font-size: 19px; }
.garantie b { font-family: 'Sora', sans-serif; font-size: 15px; display: block; margin-bottom: 4px;
  color: var(--menthe); letter-spacing: -.02em; }
.garantie p { margin: 0; font-size: 13.5px; color: #C6C6D0; line-height: 1.5; }


.section { margin-top: 56px; }
.chapeau { font-family: 'JetBrains Mono', monospace; font-size: 10.5px; letter-spacing: .16em; text-transform: uppercase; color: var(--accent-clair); margin-bottom: 10px; }
.section h2 { font-family: 'Sora', sans-serif; font-weight: 700; font-size: 27px; letter-spacing: -.035em; margin: 0 0 8px; line-height: 1.15; }
.section p.chapo { font-size: 15px; color: var(--doux); line-height: 1.55; margin: 0 0 20px; }

.marches { display: flex; flex-direction: column; gap: 11px; }
.marche { display: flex; gap: 14px; background: var(--surface); border: 1px solid var(--bord); border-radius: 18px; padding: 16px; }
.marche b { font-family: 'Sora', sans-serif; font-size: 15.5px; display: block; margin-bottom: 4px; letter-spacing: -.02em; }
.marche p { margin: 0; font-size: 14px; line-height: 1.5; color: var(--doux); }
.marche .rang { font-family: 'JetBrains Mono', monospace; font-size: 12px; color: var(--accent-clair); flex-shrink: 0; padding-top: 2px; }

.plans { display: flex; flex-direction: column; gap: 13px; }
.plan { background: var(--surface); border: 1px solid var(--bord); border-radius: 22px; padding: 22px; position: relative; }
.plan.bronze { border-color: rgba(182,131,83,.35); background: linear-gradient(180deg, rgba(182,131,83,.07), var(--surface) 50%); }
.plan.argent { border-color: rgba(110,86,248,.55); background: linear-gradient(180deg, rgba(110,86,248,.09), var(--surface) 55%); box-shadow: 0 18px 50px rgba(110,86,248,.16); }
.plan.or { border-color: rgba(230,183,80,.58); background: linear-gradient(180deg, rgba(230,183,80,.14), var(--surface) 58%); box-shadow: 0 20px 55px rgba(230,183,80,.15); }
.metal { display: inline-block; font-family: 'JetBrains Mono', monospace; font-size: 9.5px; letter-spacing: .16em; text-transform: uppercase; padding: 4px 9px; border-radius: 999px; margin-bottom: 9px; }
.metal.bronze { color: #C08A5A; background: rgba(182,131,83,.14); border: 1px solid rgba(182,131,83,.3); }
.metal.argent { color: var(--accent-clair); background: rgba(110,86,248,.14); border: 1px solid rgba(110,86,248,.34); }
.metal.or { color: #E6B750; background: rgba(230,183,80,.14); border: 1px solid rgba(230,183,80,.4); }
.plan.or .tarif b { background: linear-gradient(120deg, #F6DA92, #C9973B); -webkit-background-clip: text; background-clip: text; color: transparent; }
.plan.or li::before { color: #E6B750; }
.btn-or { background: linear-gradient(140deg, #F3D07A, #C9973B); color: #2A1D05; box-shadow: 0 8px 24px rgba(201,151,59,.3); }
.btn-or:hover { filter: brightness(1.06); }
.badge { position: absolute; top: -10px; right: 20px; background: linear-gradient(140deg, #F6DA92, #C9973B); color: #2A1D05; font-size: 11px; font-weight: 700; padding: 5px 11px; border-radius: 999px; }
.plan h3 { font-family: 'Sora', sans-serif; font-size: 19px; margin: 0 0 4px; letter-spacing: -.03em; }
.plan .phrase { font-size: 13.5px; color: var(--doux); margin: 0 0 16px; line-height: 1.45; }
.tarif { display: flex; align-items: baseline; gap: 8px; margin-bottom: 18px; }
.tarif b { font-family: 'Sora', sans-serif; font-size: 34px; letter-spacing: -.04em; }
.tarif span { font-size: 12.5px; color: var(--doux); }
.plan ul { list-style: none; margin: 0 0 18px; padding: 0; }
.plan li { font-size: 14px; line-height: 1.45; padding-left: 25px; position: relative; margin-bottom: 9px; color: #D6D6DE; }
.plan li::before { content: '✓'; position: absolute; left: 0; top: 0; color: var(--menthe); font-weight: 700; }
.plan li.non { color: #62626D; }
.plan li.non::before { content: '×'; color: #62626D; }

.faq details { border-bottom: 1px solid var(--bord); padding: 15px 0; }
.faq summary { font-size: 15px; font-weight: 600; cursor: pointer; list-style: none; display: flex; justify-content: space-between; gap: 12px; }
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after { content: '+'; color: var(--accent-clair); font-size: 19px; line-height: 1; }
.faq details[open] summary::after { content: '−'; }
.faq p { margin: 11px 0 0; font-size: 14px; line-height: 1.6; color: var(--doux); }

/* ---------- paiement ---------- */
.moyens { display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px; }
.moyens-info { background: var(--surface); border: 1px solid var(--bord); border-radius: 16px; padding: 15px 16px; margin: 16px 0; }
.moyens-titre { font-size: 13.5px; color: var(--doux); margin-bottom: 10px; }
.moyens-liste { display: flex; flex-wrap: wrap; gap: 7px; }
.moyen-etiq { font-size: 13px; font-weight: 600; color: var(--texte); background: var(--surface2);
  border: 1px solid var(--bord); border-radius: 999px; padding: 7px 12px; }
.moyen { display: flex; align-items: center; gap: 12px; background: var(--surface); border: 1px solid var(--bord); border-radius: 15px; padding: 14px 15px; cursor: pointer; text-align: left; width: 100%; transition: border-color .15s; }
.moyen:hover { border-color: var(--bord-fort); }
.moyen.on { border-color: var(--accent); background: rgba(110,86,248,.08); }
.moyen .pion { width: 18px; height: 18px; border-radius: 50%; border: 2px solid var(--bord-fort); flex-shrink: 0; display: grid; place-items: center; }
.moyen.on .pion { border-color: var(--accent); }
.moyen.on .pion::after { content: ''; width: 8px; height: 8px; border-radius: 50%; background: var(--accent); }
.moyen b { display: block; font-size: 15px; font-weight: 600; color: var(--texte); }
.moyen span { font-size: 12.5px; color: var(--doux); }
.rassure { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 14px; }
.rassure span { font-size: 12px; color: var(--doux); background: var(--surface2); border: 1px solid var(--bord); border-radius: 999px; padding: 6px 11px; }
.mention { font-size: 12px; color: #62626D; text-align: center; margin-top: 12px; line-height: 1.5; }

/* ---------- parcours ---------- */
.pas { display: flex; gap: 5px; margin-bottom: 24px; }
.pas i { flex: 1; height: 3px; border-radius: 3px; background: rgba(255,255,255,.11); transition: background .3s; }
.pas i.on { background: linear-gradient(90deg, var(--accent), var(--accent-clair)); }
.pas i.fait { background: var(--menthe); }
.t-etape { font-family: 'Sora', sans-serif; font-weight: 700; font-size: 27px; letter-spacing: -.035em; margin: 0 0 6px; }
.s-etape { color: var(--doux); font-size: 15px; line-height: 1.55; margin: 0 0 22px; }
/* Candidature pour la Suisse : petite ligne violette au-dessus du titre de l'étape. */
.ligne-suisse { display: flex; align-items: center; gap: 7px; font-family: 'JetBrains Mono', monospace; font-size: 10.5px;
  letter-spacing: .14em; text-transform: uppercase; color: var(--accent-clair); margin: 0 0 8px; }
.drapeau { width: 14px; height: 14px; flex: none; display: inline-block; vertical-align: -2px; }
.puce .drapeau { margin-right: 6px; }
/* Petite étiquette « Nouveau » du bouton « Postuler en Suisse » de l'accueil. */
.pastille-nouveau { font-size: 10.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: #fff; background: var(--accent); border-radius: 999px; padding: 3px 9px; }
/* ---------- accueil : offres par pays, autres options ---------- */
.accueil-offres { margin-top: 34px; }
.accueil-titre { font-family: 'JetBrains Mono', monospace; font-size: 11px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--doux); margin: 0 0 12px; }
.cartes-pays { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.carte-pays { display: flex; align-items: center; gap: 12px; padding: 14px 14px; border-radius: 14px; text-decoration: none;
  background: var(--surface2); border: 1px solid var(--bord); color: var(--texte); min-width: 0;
  transition: border-color .2s, background .2s, transform .2s; }
.carte-pays:hover { border-color: rgba(110,86,248,.6); background: rgba(110,86,248,.08); transform: translateY(-1px); }
.carte-pays-drapeau { width: 34px; height: 34px; border-radius: 10px; background: rgba(255,255,255,.05);
  display: flex; align-items: center; justify-content: center; flex: none; }
.carte-pays-drapeau .drapeau { width: 20px; height: 20px; vertical-align: 0; }
.carte-pays-texte { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.carte-pays-texte b { font-family: 'Sora', sans-serif; font-size: 15px; letter-spacing: -.02em; display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.carte-pays-texte small { font-size: 12.5px; color: var(--doux); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.carte-pays .pastille-nouveau { font-size: 9px; padding: 2px 7px; }
.carte-pays-fleche { color: var(--accent-clair); font-size: 16px; flex: none; transition: transform .2s; }
.carte-pays:hover .carte-pays-fleche { transform: translateX(3px); }
.accueil-autres { margin-top: 26px; padding-top: 20px; border-top: 1px solid var(--bord); text-align: center; }
.accueil-autres-titre { display: block; font-size: 13.5px; color: var(--doux); margin-bottom: 10px; }
.accueil-autres-liens { display: flex; justify-content: center; gap: 8px; flex-wrap: wrap; }
.lien-discret { display: inline-flex; align-items: center; gap: 8px; padding: 9px 15px; border-radius: 999px; border: 1px solid var(--bord);
  background: transparent; color: #C3C3CD; font-family: inherit; font-size: 13.5px; font-weight: 500; text-decoration: none; cursor: pointer;
  transition: border-color .2s, color .2s; }
.lien-discret:hover { border-color: rgba(110,86,248,.6); color: var(--texte); }
.lien-discret svg { width: 15px; height: 15px; fill: none; stroke: var(--accent-clair); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
@media (max-width: 420px) {
  .cartes-pays { gap: 8px; }
  .carte-pays { padding: 12px 11px; gap: 9px; }
  .carte-pays-drapeau { width: 30px; height: 30px; }
  .carte-pays-fleche { display: none; }
  .carte-pays-texte small { white-space: normal; line-height: 1.3; }
}

/* ---------- choix de la langue (liste avec recherche) ---------- */
.choix-langue { position: relative; }
.choix-langue-liste { position: absolute; z-index: 30; left: 0; right: 0; top: calc(100% + 6px); max-height: 280px; overflow-y: auto;
  background: var(--surface2); border: 1px solid var(--bord); border-radius: 12px; padding: 6px; box-shadow: 0 14px 40px rgba(0,0,0,.45); }
.choix-langue-titre { font-family: 'JetBrains Mono', monospace; font-size: 10px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--doux); padding: 8px 10px 4px; }
.choix-langue-option { display: block; width: 100%; text-align: left; background: none; border: 0; border-radius: 8px; padding: 9px 10px;
  color: var(--texte); font-family: inherit; font-size: 14.5px; cursor: pointer; }
.choix-langue-option.on { background: rgba(110,86,248,.18); }

.vue { animation: monte .32s cubic-bezier(.2,.7,.3,1) both; }
@keyframes monte { from { opacity: 0; transform: translateY(9px) } to { opacity: 1; transform: none } }
@media (prefers-reduced-motion: reduce) { .vue { animation: none !important } }

.grille { display: grid; grid-template-columns: 1fr 1fr; gap: 11px; }
.champ { display: flex; flex-direction: column; gap: 6px; }
.champ.large { grid-column: 1 / -1; }
.champ label { font-size: 12px; font-weight: 600; color: var(--doux); }
.champ input {
  font-family: 'Plus Jakarta Sans', sans-serif; font-size: 15.5px; font-weight: 500; color: var(--texte);
  background: var(--surface2); border: 1px solid var(--bord); border-radius: 13px; padding: 13px 14px; width: 100%;
  transition: border-color .15s, box-shadow .15s;
}
.champ input::placeholder { color: #5B5B66; font-weight: 400; }
.champ input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px rgba(110,86,248,.22); }
.champ input:disabled { opacity: .4; }
/* Liste déroulante (permis de travail en Suisse) : même allure que les champs. */
.champ select {
  font-family: 'Plus Jakarta Sans', sans-serif; font-size: 15.5px; font-weight: 500; color: var(--texte);
  background: var(--surface2); border: 1px solid var(--bord); border-radius: 13px; padding: 13px 14px; width: 100%;
  color-scheme: dark; cursor: pointer;
}
.champ select:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px rgba(110,86,248,.22); }
.champ input[type="month"] { color-scheme: dark; }
.champ input[type="file"] { font-size: 14px; font-weight: 500; color: var(--doux); background: var(--surface2);
  border: 1px dashed var(--bord-fort); border-radius: 13px; padding: 13px 14px; width: 100%; cursor: pointer; }
.champ input[type="file"]:hover { border-color: var(--accent); }
.photo-choisie { display: flex; align-items: center; gap: 14px; }
.photo-choisie img { width: 76px; height: 76px; border-radius: 14px; object-fit: cover; border: 1px solid var(--bord); flex-shrink: 0; }
.champ textarea { font-family: 'Plus Jakarta Sans', sans-serif; font-size: 15px; color: var(--texte); background: var(--surface2); border: 1px solid var(--bord); border-radius: 13px; padding: 13px 14px; width: 100%; min-height: 92px; resize: vertical; line-height: 1.5; }
.champ textarea::placeholder { color: #5B5B66; }
.champ textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px rgba(110,86,248,.22); }
.carte { background: var(--surface); border: 1px solid var(--bord); border-radius: 20px; padding: 16px; margin-bottom: 11px; }
.carte-tete { display: flex; justify-content: space-between; align-items: center; margin-bottom: 13px; }
.num { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: var(--accent-clair); letter-spacing: .1em; }
.sup { background: none; border: 0; color: var(--doux); font-size: 13px; cursor: pointer; padding: 4px; }
.sup:hover { color: #FF7A7A; }
.coche { display: flex; align-items: center; gap: 10px; font-size: 14.5px; cursor: pointer; color: #D6D6DE; }
.coche input { width: 18px; height: 18px; accent-color: var(--accent); }

.btn { font-family: 'Plus Jakarta Sans', sans-serif; font-weight: 600; font-size: 15.5px; border-radius: 14px; padding: 14px 20px; cursor: pointer; border: 1px solid transparent; transition: transform .1s, filter .15s; }
.btn:active { transform: scale(.985); }
.btn:disabled { opacity: .38; cursor: not-allowed; }
.btn-primaire { background: linear-gradient(140deg, var(--accent-clair), var(--accent)); color: #fff; box-shadow: 0 8px 24px rgba(110,86,248,.35); }
.btn-primaire:hover:not(:disabled) { filter: brightness(1.08); }
.btn-fantome { background: var(--surface2); color: var(--texte); border-color: var(--bord); }
.btn-fantome:hover { border-color: var(--bord-fort); }
.btn-ajout { width: 100%; background: transparent; border: 1px dashed var(--bord-fort); color: var(--doux); border-radius: 16px; padding: 13px; }
.btn-ajout:hover { color: var(--accent-clair); border-color: var(--accent); }
.rangee { display: flex; gap: 9px; }
.rangee .btn { flex: 1; }
.socle { position: sticky; bottom: 0; margin: 24px -18px -140px; padding: 14px 18px calc(18px + env(safe-area-inset-bottom)); background: linear-gradient(to top, var(--fond) 62%, rgba(8,8,11,0)); backdrop-filter: blur(6px); }

.puces { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 6px; margin-bottom: 14px; scrollbar-width: none; }
.puces::-webkit-scrollbar { display: none; }
.puce { font-size: 13px; font-weight: 600; padding: 9px 15px; border-radius: 999px; border: 1px solid var(--bord); background: var(--surface2); color: var(--doux); cursor: pointer; white-space: nowrap; }
.puce.on { background: var(--texte); color: #0B0B0F; border-color: var(--texte); }

.retouche { background: var(--surface); border: 1px solid var(--bord); border-radius: 20px; padding: 16px; margin-top: 16px; }
.retouche h4 { font-family: 'Sora', sans-serif; font-size: 15px; margin: 0 0 5px; letter-spacing: -.02em; }
.statut { display: flex; align-items: center; justify-content: space-between; gap: 10px; border-top: 1px solid var(--bord); margin-top: 12px; padding-top: 12px; font-size: 13px; color: var(--doux); }
.statut button { font-size: 12.5px; font-weight: 600; padding: 7px 12px; border-radius: 10px; border: 1px solid var(--bord); background: var(--surface2); color: var(--texte); cursor: pointer; }
.retouche p { margin: 0 0 12px; font-size: 13.5px; color: var(--doux); line-height: 1.5; }
.exemples { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 12px; }
.exemple { font-size: 12px; color: var(--accent-clair); background: rgba(110,86,248,.13); border: 1px solid rgba(110,86,248,.3); border-radius: 999px; padding: 6px 11px; cursor: pointer; }
.duo { display: flex; gap: 8px; }
.duo .champ { flex: 1; }

.alerte { background: rgba(255,110,110,.09); border: 1px solid rgba(255,110,110,.32); border-radius: 14px; padding: 13px 15px; font-size: 14px; line-height: 1.45; color: #FFC4C4; margin-bottom: 14px; }
.travail { display: flex; align-items: center; gap: 10px; font-size: 13px; color: var(--doux); margin-top: 14px; }
/* Écran « Comment voulez-vous commencer ? » (import d'un ancien CV). */
.choix-depart { display: block; width: 100%; text-align: left; border-radius: 20px; padding: 18px; margin-bottom: 12px; border: 1px solid var(--bord); background: var(--surface); color: var(--texte); font-family: inherit; cursor: pointer; transition: border-color .15s, transform .1s; }
.choix-depart:hover { border-color: var(--bord-fort); }
.choix-depart:active { transform: scale(.99); }
.choix-depart.fort { background: linear-gradient(150deg, rgba(155,135,255,.22), rgba(110,86,248,.10)); border-color: rgba(155,135,255,.55); box-shadow: 0 10px 30px rgba(110,86,248,.25); }
.choix-depart.fort:hover { border-color: var(--accent-clair); }
.choix-depart .ic { width: 42px; height: 42px; border-radius: 13px; display: grid; place-items: center; background: var(--surface2); border: 1px solid var(--bord); margin-bottom: 12px; }
.choix-depart.fort .ic { background: linear-gradient(145deg, var(--accent-clair), var(--accent)); border: 0; }
.choix-depart b { font-family: 'Sora', sans-serif; font-size: 18px; letter-spacing: -.02em; display: block; margin-bottom: 5px; }
.choix-depart p { margin: 0; color: #C9C9D3; font-size: 14px; line-height: 1.5; }
.choix-depart .formats { font-family: 'JetBrains Mono', monospace; font-size: 10.5px; color: var(--doux); margin-top: 10px; letter-spacing: .04em; }
.gratuit { display: inline-block; font-family: 'JetBrains Mono', monospace; font-size: 10px; font-weight: 500; color: var(--menthe); border: 1px solid rgba(34,211,166,.4); border-radius: 999px; padding: 3px 9px; margin-left: 8px; vertical-align: 3px; letter-spacing: .06em; }
.photo-tel { display: block; width: 100%; margin: -4px 0 12px; background: none; border: 1px dashed var(--bord-fort); border-radius: 14px; color: var(--accent-clair); font-family: inherit; font-size: 14px; font-weight: 600; padding: 12px; cursor: pointer; }
.photo-tel:hover { border-color: var(--accent); }
.cadenas { display: flex; gap: 8px; font-size: 12.5px; color: var(--doux); line-height: 1.5; margin-top: 6px; }
.lecture-cv { text-align: center; padding: 46px 10px 30px; }
.lecture-cv .roue { width: 46px; height: 46px; border-radius: 50%; border: 3px solid rgba(155,135,255,.25); border-top-color: var(--accent-clair); margin: 0 auto 20px; animation: tourne .9s linear infinite; }
@keyframes tourne { to { transform: rotate(360deg); } }
.erreur-import { background: rgba(255,110,110,.09); border: 1px solid rgba(255,110,110,.32); border-radius: 16px; padding: 14px 15px; font-size: 14px; line-height: 1.5; color: #FFC4C4; margin-bottom: 16px; }
.erreur-import b { display: block; color: #FFDADA; margin-bottom: 3px; }
.bandeau-import { display: flex; gap: 10px; align-items: flex-start; background: rgba(34,211,166,.08); border: 1px solid rgba(34,211,166,.35); border-radius: 16px; padding: 13px 14px; margin-bottom: 18px; font-size: 14px; line-height: 1.5; color: #D6D6DE; }
.bandeau-import b { display: block; color: var(--texte); font-size: 15px; margin-bottom: 2px; }
.bandeau-import .orange { color: #F5A524; font-weight: 600; }
/* Après un import : les cases que l'ancien CV ne donnait pas, en orange. */
.champ.manque > label:first-child { color: #F5A524; }
.champ.manque > label:first-child::after { content: " · à compléter"; font-weight: 400; }
.champ.manque input, .champ.manque textarea { border-color: rgba(245,165,36,.7); box-shadow: 0 0 0 3px rgba(245,165,36,.12); }
.note-date { font-size: 12px; color: var(--doux); line-height: 1.4; }
.photo-reprise { font-size: 12.5px; color: var(--menthe); line-height: 1.45; }
.rond { width: 8px; height: 8px; border-radius: 50%; background: var(--accent-clair); box-shadow: 0 0 12px var(--accent); animation: bat 1s ease-in-out infinite; }
@keyframes bat { 0%,100% { opacity: .3; transform: scale(.8) } 50% { opacity: 1; transform: scale(1.25) } }

/* ---------- feuille ---------- */
.verrou-cv { position: relative; }
.verrou-cv .piste { pointer-events: none; user-select: none; -webkit-user-select: none; }
.filigrane { position: absolute; inset: 0; pointer-events: none; overflow: hidden;
  display: flex; flex-wrap: wrap; align-content: center; justify-content: center;
  transform: rotate(-24deg) scale(1.5); opacity: 1; }
.filigrane span { font-family: 'Sora', sans-serif; font-weight: 800; font-size: 23px; letter-spacing: .12em;
  color: transparent; -webkit-text-stroke: 1px rgba(20,22,26,.3);
  white-space: nowrap; padding: 15px 20px; }
.cadenas { display: flex; align-items: center; gap: 13px; background: var(--surface); border: 1px solid var(--bord);
  border-radius: 18px; padding: 15px 16px; margin-top: 12px; }
.cadenas b { font-family: 'Sora', sans-serif; font-size: 15px; display: block; margin-bottom: 3px; letter-spacing: -.02em; }
.cadenas p { margin: 0; font-size: 13.5px; color: var(--doux); line-height: 1.45; }
.livre { background: rgba(34,211,166,.09); border: 1px solid rgba(34,211,166,.35); border-radius: 18px;
  padding: 16px; margin-bottom: 18px; }
.livre b { font-family: 'Sora', sans-serif; font-size: 16px; color: var(--menthe); display: block; margin-bottom: 4px; }
.livre p { margin: 0; font-size: 14px; color: #C6C6D0; line-height: 1.5; }
.scene { background: #0E0E12; border: 1px solid var(--bord); padding: 14px; border-radius: 20px; overflow: hidden; }
.piste { transform-origin: top left; position: relative; }
/* CV trop long, même à 85 % : on montre où s'arrête la page 1. */
.fin-page { position: absolute; left: 0; width: 794px; border-top: 2px dashed #E6B750; pointer-events: none; }
.fin-page span { position: absolute; right: 8px; top: 4px; background: #E6B750; color: #1B1B22; font: 700 12px/1 'Plus Jakarta Sans', sans-serif; padding: 5px 9px; border-radius: 999px; }
/* Conseil (formulaire), avertissement « trop long » et choix après « Raccourcir ». */
.conseil { display: flex; gap: 10px; align-items: flex-start; background: rgba(110,86,248,.09); border: 1px solid rgba(110,86,248,.32); border-radius: 14px; padding: 12px 14px; color: #C3C3CD; font-size: 14px; line-height: 1.5; margin-bottom: 16px; }
.conseil b { color: var(--texte); }
.avert-long { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; background: rgba(230,183,80,.10); border: 1px solid rgba(230,183,80,.40); border-radius: 14px; padding: 12px 14px; margin-bottom: 14px; }
.avert-long > span { flex: 1; min-width: 200px; color: #F3DFAE; font-size: 14px; line-height: 1.45; }
.choix-raccourci { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; background: var(--surface); border: 1px solid var(--bord); border-radius: 14px; padding: 12px 14px; margin-bottom: 14px; }
.choix-raccourci > span { flex: 1; min-width: 200px; color: #C3C3CD; font-size: 14px; line-height: 1.45; }
.btn-petit { padding: 10px 16px; font-size: 14px; border-radius: 12px; }
.feuille { width: 794px; min-height: 1123px; background: #fff; color: #14161A; padding: 46px 52px; border-radius: 3px; box-shadow: 0 18px 50px rgba(0,0,0,.6); font-family: 'Plus Jakarta Sans', sans-serif; }
.feuille.serif { font-family: 'Source Serif 4', Georgia, serif; }
.feuille .exp-tete { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
.feuille .exp-poste { font-weight: 700; font-size: 15.5px; }
.feuille .exp-boite { font-size: 13.5px; color: #5A5A66; }
.feuille .exp-date { font-size: 11.5px; color: #70707C; white-space: nowrap; }
.feuille .bloc { margin-bottom: 17px; }
/* Profil placé dans la grande colonne (Gris Perle, Cyan) : texte normal. */
.feuille .profil-corps { font-size: 13.5px; line-height: 1.6; margin: 0 0 4px; }
/* Compétences et qualités en étiquettes, dans la grande colonne. */
.feuille .interets { font-size: 12px; line-height: 1.45; margin: 5px 0 2px; }
.feuille .etiquettes { display: flex; flex-wrap: wrap; gap: 7px; margin: 2px 0 6px; }
.feuille .etq { font-size: 12px; line-height: 1.3; padding: 4px 11px; border: 1px solid rgba(0,0,0,.2); border-radius: 999px; }
.feuille .tags { display: flex; flex-wrap: wrap; gap: 6px; }
.feuille .tag { font-size: 12.5px; border: 1px solid #E3E3EA; border-radius: 999px; padding: 5px 11px; }
.feuille .miss { margin: 7px 0 0; padding: 0; }
.feuille .miss li { list-style: none; font-size: 13.5px; line-height: 1.5; padding-left: 16px; position: relative; margin-bottom: 3px; color: #2A2A33; }
.feuille .miss li::before { content: ''; position: absolute; left: 0; top: 7px; width: 6px; height: 6px; border-radius: 50%; background: var(--acc); }
.feuille p.txt { font-size: 13.5px; line-height: 1.55; margin: 0; }

/* photo de profil : même cadrage carré que dans le PDF */
.feuille .photo-cv { width: 96px; height: 96px; object-fit: cover; border-radius: 12px;
  display: block; margin-bottom: 14px; }

/* ---------- réglages communs aux modèles ---------- */
.feuille .ent { margin-bottom: 16px; }
.feuille .ent-tete { display: flex; justify-content: space-between; align-items: baseline; gap: 14px; }
.feuille .ent-titre { font-weight: 700; font-size: 14.5px; }
.feuille .ent-sous { font-size: 13px; color: #5A5A66; margin-top: 1px; }
.feuille .ent-date { font-size: 11.5px; color: #74747F; white-space: nowrap; }
.feuille .ent .miss { margin-top: 6px; }
.feuille .liste { margin: 0; padding: 0; }
.feuille .liste li { list-style: none; font-size: 12.5px; line-height: 1.5; margin-bottom: 5px; }
.feuille .coord { font-size: 12.5px; line-height: 1.4; margin-bottom: 9px; word-break: break-word; }
.feuille .coord b { display: block; font-size: 12px; }
.feuille .accroche { font-size: 13.5px; line-height: 1.6; margin: 0; }

/* ---------- Prestige : bandeau latéral sombre et doré ---------- */
.m-colonne { display: flex; padding: 0; }
.m-colonne .cote { width: 248px; background: var(--fonce); color: #fff; padding: 46px 26px; flex-shrink: 0; }
.m-colonne .cote .photo-cv { width: 104px; height: 104px; }
.m-colonne .corps { padding: 46px 40px; flex: 1; }
.m-colonne .nom { font-family: 'Sora', sans-serif; font-weight: 800; font-size: 26px; line-height: 1.06; letter-spacing: -.025em; }
.feuille.serif .nom { font-family: 'Source Serif 4', serif; font-weight: 600; }
.m-colonne .role { font-size: 13.5px; color: var(--acc); margin-top: 8px; font-weight: 700; }
.m-colonne h4 { font-size: 10px; letter-spacing: .16em; text-transform: uppercase; opacity: .55; margin: 26px 0 10px; font-weight: 700; }
.m-colonne .cote p, .m-colonne .cote li { font-size: 12px; line-height: 1.5; margin: 0 0 5px; list-style: none; opacity: .92; }
.m-colonne .cote ul { margin: 0; padding: 0; }
.m-colonne .corps h3 { font-size: 10.5px; letter-spacing: .16em; text-transform: uppercase; margin: 0 0 14px; padding-bottom: 7px; border-bottom: 1px solid #E7E7EC; font-weight: 700; }
.m-colonne .corps h3.suite { margin-top: 28px; }

/* ---------- Onyx Noir : colonne noire, titres encadrés ---------- */
.m-cadre { display: flex; padding: 0; }
.m-cadre .cote { width: 252px; background: #0E0F12; color: #fff; flex-shrink: 0; }
.m-cadre .cote .dedans { padding: 26px 22px 40px; }
.m-cadre .cote .photo-cv { width: 252px; height: 246px; border-radius: 0; margin: 0; display: block; object-fit: cover; }
.m-cadre .corps { padding: 44px 42px; flex: 1; }
.m-cadre .nom { font-family: 'Sora', sans-serif; font-weight: 800; font-size: 25px; line-height: 1.05; letter-spacing: -.02em; }
.m-cadre .role { font-size: 13px; color: var(--acc); margin-top: 7px; font-weight: 700; letter-spacing: .04em; }
.m-cadre .cote h4 { font-size: 11px; letter-spacing: .1em; text-transform: uppercase; font-weight: 700;
  border: 1px solid rgba(255,255,255,.45); border-radius: 3px; padding: 6px 10px; margin: 24px 0 12px; }
.m-cadre .cote p, .m-cadre .cote li { font-size: 12px; line-height: 1.55; margin: 0 0 6px; list-style: none; opacity: .9; }
.m-cadre .cote ul { margin: 0; padding: 0; }
.m-cadre .corps h3 { font-size: 12.5px; letter-spacing: .08em; text-transform: uppercase; font-weight: 700;
  color: #14161A; border: 1.5px solid #14161A; border-radius: 3px; padding: 9px 13px; margin: 0 0 16px; }
.m-cadre .corps h3.suite { margin-top: 28px; }
.m-cadre .exp-poste { font-size: 15px; font-weight: 700; }

/* ---------- Terracotta : colonne rose arrondie en haut, titres à empattements ---------- */
.m-arche { display: flex; padding: 0; background: var(--fond); color: #2B2424; }
.m-arche .cote { width: 262px; flex-shrink: 0; background: var(--fonce); border-top-right-radius: 120px; padding: 36px 26px 40px; }
.m-arche .photo-cv { width: 196px; height: 196px; border-radius: 26px; margin: 0 0 26px; }
.m-arche .coord { margin-bottom: 11px; }
.m-arche .coord b { font-size: 12.5px; }
.m-arche h4 { font-family: 'Source Serif 4', Georgia, serif; font-weight: 600; font-size: 21px; margin: 26px 0 10px; }
.m-arche .cote .liste li { padding-left: 14px; position: relative; }
.m-arche .cote .liste li::before { content: ''; position: absolute; left: 0; top: 8px; width: 5px; height: 5px; border-radius: 50%; background: #2B2424; }
.m-arche .corps { flex: 1; padding: 52px 46px 40px; }
.m-arche .nom { font-family: 'Source Serif 4', Georgia, serif; font-weight: 600; font-size: 40px; line-height: 1.02; }
.m-arche .role { font-family: 'Source Serif 4', Georgia, serif; font-size: 19px; margin-top: 8px; color: var(--acc); }
.m-arche .accroche { margin-top: 22px; }
.m-arche h3 { font-family: 'Source Serif 4', Georgia, serif; font-weight: 600; font-size: 22px; margin: 30px 0 14px; }
.m-arche h3::after { content: ''; display: block; width: 38px; height: 3px; background: var(--acc); margin-top: 6px; }
.m-arche .ent-date { display: block; color: #8A7F7C; margin-bottom: 2px; }
.m-arche .ent-sous { color: var(--acc); font-weight: 600; }
.m-arche .miss li::before { background: #2B2424; width: 5px; height: 5px; top: 8px; }
/* Onyx Noir : même mise en page, colonne noire, texte blanc, touches dorées. */
.m-arche.sombre { color: #14161A; }
.m-arche.sombre .cote { color: #fff; }
.m-arche.sombre .cote h4, .m-arche.sombre .cote .coord b { color: var(--acc); }
.m-arche.sombre .cote .liste li::before, .m-arche.sombre .miss li::before { background: var(--acc); }
.m-arche.sombre .ent-date { color: #6B6E73; }

/* ---------- Camel : grand en-tête crème, le métier en titre, frise ---------- */
.m-camel { padding: 0; display: flex; flex-direction: column; color: #3A3027; }
.m-camel .tete { background: var(--fond); display: flex; gap: 34px; align-items: center; padding: 42px 44px 36px; }
.m-camel .photo-cv { width: 176px; height: 176px; border-radius: 50%; margin: 0; flex-shrink: 0; }
.m-camel .nom { font-size: 15px; letter-spacing: .32em; font-weight: 600; color: var(--acc); }
.m-camel .role { font-family: 'Source Serif 4', Georgia, serif; font-weight: 600; font-size: 34px; line-height: 1.12;
  letter-spacing: .05em; color: var(--acc); margin-top: 8px; }
.m-camel .accroche { font-size: 13px; line-height: 1.65; color: #6B5E52; margin-top: 16px; }
.m-camel .bas { display: flex; flex: 1; }
.m-camel .cote { width: 262px; flex-shrink: 0; background: var(--fonce); padding: 34px 30px 40px; }
.m-camel .corps { flex: 1; padding: 34px 40px 40px; }
.m-camel h3, .m-camel h4 { font-size: 13px; letter-spacing: .28em; font-weight: 700; color: var(--acc); margin: 0 0 16px; }
.m-camel .suite { margin-top: 30px; }
.m-camel .coord { padding-left: 12px; border-left: 2px solid var(--acc); margin-bottom: 10px; }
.m-camel .cote .liste li { padding-left: 14px; position: relative; font-size: 13px; }
.m-camel .cote .liste li::before { content: '•'; position: absolute; left: 0; }
.m-camel .frise { border-left: 1.5px solid var(--acc); padding-left: 22px; margin-left: 4px; }
.m-camel .ent { position: relative; }
.m-camel .ent::before { content: ''; position: absolute; left: -27px; top: 4px; width: 8px; height: 8px; border-radius: 50%; background: var(--acc); }
.m-camel .ent-titre { font-size: 14px; }
.m-camel .ent-sous { font-style: italic; color: #5E5247; }
.m-camel .miss li::before { background: var(--acc); }

/* ---------- Noir et Blanc : pavés noirs pleins, dates en colonne ---------- */
.m-pave { display: flex; padding: 44px 40px; color: #111; }
.m-pave .cote { width: 232px; flex-shrink: 0; padding-right: 26px; }
.m-pave .photo-cv { width: 206px; height: 206px; border-radius: 0; margin: 0 0 24px; }
.m-pave h4, .m-pave h3 { background: #111; color: #fff; font-size: 14px; font-weight: 700; letter-spacing: .04em;
  padding: 8px 13px; margin: 26px 0 14px; }
.m-pave .cote h4:first-of-type { margin-top: 0; }
.m-pave .coord b { font-size: 12.5px; letter-spacing: .03em; }
.m-pave .corps { flex: 1; border-left: 2px solid #111; padding-left: 28px; }
.m-pave .nom { font-size: 38px; font-weight: 800; line-height: 1.02; }
.m-pave .role { font-size: 20px; font-style: italic; margin-top: 8px; }
.m-pave .filet { height: 2px; background: #111; margin: 18px 0 20px; }
.m-pave .ent-g { display: flex; gap: 18px; }
.m-pave .ent-g .ent-date { width: 86px; flex-shrink: 0; white-space: normal; color: #333; font-size: 12px; line-height: 1.45; }
.m-pave .ent-titre { font-size: 14px; font-weight: 800; }
.m-pave .miss li::before { background: #111; border-radius: 0; width: 5px; height: 5px; top: 8px; }

/* ---------- Lin : colonne beige claire, repères dorés ---------- */
.m-lin { display: flex; padding: 0; color: #22211F; }
.m-lin .cote { width: 236px; flex-shrink: 0; background: var(--fonce); padding: 30px 24px 40px; }
.m-lin .photo-cv { width: 142px; height: 142px; border-radius: 50%; margin: 0 auto 26px; border: 2px solid var(--acc);
  padding: 4px; background: var(--fonce); }
.m-lin .coord { padding-left: 16px; position: relative; font-size: 12px; margin-bottom: 7px; }
.m-lin .coord::before { content: ''; position: absolute; left: 0; top: 6px; width: 6px; height: 6px; border-radius: 50%; background: var(--acc); }
.m-lin h4 { font-size: 16px; font-weight: 700; margin: 26px 0 10px; }
.m-lin .cote .liste li { font-size: 12.5px; padding-bottom: 6px; border-bottom: 2px solid rgba(179,154,107,.45); margin-bottom: 8px; }
.m-lin .corps { flex: 1; padding: 42px 38px 40px 34px; }
.m-lin .nom { font-size: 30px; font-weight: 700; letter-spacing: -.01em; }
.m-lin .role { font-size: 17px; color: var(--acc); margin-top: 6px; font-weight: 600; }
.m-lin .accroche { font-size: 13px; margin-top: 12px; text-align: justify; }
.m-lin h3 { position: relative; font-size: 19px; font-weight: 600; margin: 30px 0 14px -34px; padding-left: 34px; }
.m-lin h3::before { content: ''; position: absolute; left: -5px; top: 50%; margin-top: -5px; width: 10px; height: 10px; border-radius: 50%; background: var(--acc); }
.m-lin h3::after { content: ''; position: absolute; left: 5px; top: 50%; width: 22px; height: 1.5px; background: var(--acc); }
.m-lin .ent-titre { font-size: 14px; }
.m-lin .ent-sous { color: var(--acc); font-weight: 600; }
.m-lin .ent-date { text-align: right; }
.m-lin .miss li { padding-left: 12px; }
.m-lin .miss li::before { content: '-'; background: none; width: auto; height: auto; top: 0; border-radius: 0; }

/* ---------- Ardoise : bandeau sombre en haut, corps à empattements ---------- */
.m-ardoise { display: flex; padding: 0; color: #22262B; }
.m-ardoise .cote { width: 250px; flex-shrink: 0; padding: 30px 24px 40px;
  background: linear-gradient(var(--acc) 0, var(--acc) 150px, var(--fonce) 150px); }
.m-ardoise .cote.sans-photo { padding-top: 178px; }
.m-ardoise .photo-cv { width: 192px; height: 192px; border-radius: 0; border: 5px solid #fff; margin: 0 0 24px; }
.m-ardoise .corps { flex: 1; }
.m-ardoise .tete { background: var(--acc); color: #fff; height: 150px; padding: 0 36px; display: flex; flex-direction: column; justify-content: center; }
.m-ardoise .nom { font-size: 34px; font-weight: 800; line-height: 1.05; }
.m-ardoise .role { font-size: 12.5px; letter-spacing: .18em; margin-top: 10px; opacity: .8; font-weight: 600; }
.m-ardoise .dedans { padding: 30px 36px 40px; font-family: 'Source Serif 4', Georgia, serif; }
.m-ardoise h3, .m-ardoise h4 { font-family: 'Plus Jakarta Sans', sans-serif; font-size: 14px; letter-spacing: .08em; font-weight: 800;
  padding-bottom: 7px; border-bottom: 3px solid var(--acc); margin: 26px 0 12px; }
.m-ardoise .dedans h3:first-child, .m-ardoise .cote h4:first-of-type { margin-top: 0; }
.m-ardoise .cote .liste li { padding-left: 12px; position: relative; }
.m-ardoise .cote .liste li::before { content: '•'; position: absolute; left: 0; }
.m-ardoise .ent-titre { font-family: 'Plus Jakarta Sans', sans-serif; font-size: 14px; }
.m-ardoise .ent-sous { font-style: italic; color: #3E4650; font-size: 13.5px; }
.m-ardoise .ent-date { display: block; color: #6A727C; margin-top: 2px; }
.m-ardoise .miss li { font-size: 13.5px; }
.m-ardoise .miss li::before { background: var(--acc); }

/* ---------- Cyan : en-tête en trois blocs, cadre noir autour de la photo ---------- */
.m-cyan { padding: 0; display: flex; flex-direction: column; color: #1E2A30; }
.m-cyan .tete { display: flex; min-height: 214px; }
.m-cyan .tete .gauche { width: 300px; background: var(--fonce); padding: 46px 24px 24px 40px; }
.m-cyan .nom { font-size: 30px; font-weight: 800; letter-spacing: .06em; line-height: 1.12; }
.m-cyan .role { font-size: 14px; letter-spacing: .14em; margin-top: 10px; }
.m-cyan .tete .milieu { width: 196px; flex-shrink: 0; }
.m-cyan .photo-cv { width: 166px; height: 166px; border-radius: 0; border: 5px solid #111; margin: 22px 0 0 14px; }
.m-cyan .tete .droite { flex: 1; background: var(--acc); color: #fff; margin-top: 40px; padding: 30px 26px 22px 28px; }
.m-cyan .tete .droite .coord { font-size: 12px; margin-bottom: 6px; }
.m-cyan .bas { display: flex; flex: 1; }
.m-cyan .cote { width: 232px; flex-shrink: 0; padding: 30px 22px 40px 40px; }
.m-cyan .corps { flex: 1; padding: 30px 40px 40px 26px; border-left: 1px solid #D6DEE3; margin-top: 30px; }
.m-cyan .cote .accroche { background: var(--fonce); font-size: 12.5px; line-height: 1.55; padding: 14px; }
.m-cyan h3, .m-cyan h4 { color: var(--acc); font-size: 18px; font-weight: 600; letter-spacing: .1em; margin: 26px 0 12px; }
.m-cyan h4 { font-size: 16px; padding-top: 16px; border-top: 1px solid #DDE3E6; }
.m-cyan .corps h3:first-child { margin-top: 0; }
.m-cyan .ent-titre { font-size: 14px; }
.m-cyan .ent-sous { font-weight: 700; color: #1E2A30; font-size: 12.5px; }
.m-cyan .miss li::before { background: var(--acc); }

/* ---------- Gris Perle : colonne grise, la photo à cheval, titres fins ---------- */
.m-perle { display: flex; padding: 0; color: #26282D; }
.m-perle .cote { width: 250px; flex-shrink: 0; background: var(--fonce); padding: 44px 28px 40px 34px; }
.m-perle .cote .coord { font-size: 11.5px; margin-bottom: 4px; }
.m-perle h3, .m-perle h4 { font-size: 16px; font-weight: 400; letter-spacing: .04em; margin: 30px 0 12px; }
.m-perle .cote p.txt { font-size: 12.5px; line-height: 1.6; }
.m-perle .corps { flex: 1; padding: 40px 38px 40px 32px; }
.m-perle .tete { display: flex; align-items: center; gap: 22px; }
.m-perle .photo-cv { width: 132px; height: 132px; border-radius: 18px; margin: 0 0 0 -82px; border: 5px solid #fff; flex-shrink: 0; }
.m-perle .prenom { font-size: 36px; font-weight: 400; line-height: 1.05; }
.m-perle .nomf { font-size: 36px; font-weight: 800; line-height: 1.05; }
.m-perle .role { font-size: 11.5px; letter-spacing: .08em; margin-top: 7px; }
.m-perle .ent-titre { font-weight: 500; font-size: 15px; }
.m-perle .ent-sous { font-weight: 700; font-style: italic; color: #26282D; font-size: 12.5px; }
.m-perle .miss li::before { background: #26282D; width: 4px; height: 4px; top: 8px; }

/* ---------- Colonne Grise : colonne graphite, coordonnées en haut à droite ---------- */
.m-graphite { display: flex; padding: 0; color: #55585E; }
.m-graphite .cote { width: 246px; flex-shrink: 0; background: var(--fonce); color: #fff; padding: 40px 30px; }
.m-graphite .photo-cv { width: 140px; height: 140px; border-radius: 50%; margin: 0 auto 14px; }
.m-graphite .cote .sec { padding: 18px 0; border-bottom: 1px solid rgba(255,255,255,.35); }
.m-graphite h4 { color: #fff; font-size: 14px; font-weight: 800; margin: 0 0 8px; }
.m-graphite .cote .liste li, .m-graphite .cote p { font-size: 12.5px; color: #F0F0F0; margin: 0 0 6px; }
.m-graphite .corps { flex: 1; padding: 54px 42px 40px 40px; }
.m-graphite .tete { display: flex; justify-content: space-between; gap: 20px; margin-bottom: 6px; }
.m-graphite .nom2 { font-size: 34px; font-weight: 800; color: #5E6166; line-height: 1.02; }
.m-graphite .role { font-size: 12px; letter-spacing: .08em; color: #8A8D92; margin-top: 8px; }
.m-graphite .coords { width: 200px; flex-shrink: 0; }
.m-graphite .coords div { font-size: 11px; padding: 6px 0 6px 26px; border-bottom: 1px solid #E3E4E6; position: relative; word-break: break-word; }
.m-graphite .coords div::before { content: ''; position: absolute; left: 0; top: 50%; margin-top: -8px; width: 16px; height: 16px; border-radius: 50%; background: #2F3033; }
.m-graphite h3 { font-size: 15px; font-weight: 800; color: #6B6E73; margin: 30px 0 12px; }
.m-graphite .ent-titre { color: #3F4247; font-size: 14px; }
.m-graphite .miss li { color: #55585E; }
.m-graphite .miss li::before { background: #6B6E73; }

/* ---------- Vert Sauge : la couleur est à droite, nom centré ---------- */
.m-sauge { display: flex; padding: 0; color: #3F5436; }
.m-sauge .cote { width: 250px; flex-shrink: 0; padding: 48px 26px 40px 32px; color: #1F2A1B; }
.m-sauge .photo-cv { width: 168px; height: 168px; border-radius: 50%; margin: 0 auto 28px; }
.m-sauge h4 { font-size: 13px; letter-spacing: .3em; font-weight: 500; margin: 26px 0 10px; }
.m-sauge .cote .liste li { font-size: 12.5px; }
.m-sauge .corps { flex: 1; background: var(--fonce); padding: 46px 0 40px; }
.m-sauge .nom2 { text-align: center; font-size: 34px; letter-spacing: .16em; font-weight: 400; color: var(--acc); line-height: 1.22; padding: 0 30px; }
.m-sauge .bande { background: #C8DFBB; color: #34512A; text-align: center; font-size: 14px; letter-spacing: .06em; font-weight: 600; padding: 12px 20px; margin: 18px 0 6px; }
.m-sauge .dedans { padding: 0 36px; }
.m-sauge h3 { font-size: 14px; letter-spacing: .26em; font-weight: 600; color: var(--acc); margin: 26px 0 12px; }
.m-sauge .accroche { font-size: 13px; }
.m-sauge .ent-titre { font-weight: 600; color: #34512A; font-size: 13.5px; }
.m-sauge .ent-sous { color: #3F5436; font-size: 12.5px; }
.m-sauge .ent-date { display: block; font-style: italic; color: #5E7254; }
.m-sauge .miss li { color: #3F5436; }
.m-sauge .miss li::before { background: var(--acc); }

/* =========================== lettres =========================== */
.lettre { font-size: 14px; line-height: 1.72; }
.lettre .objet { font-weight: 600; margin: 26px 0 22px; }
.lettre p { margin: 0 0 14px; }
.lettre .sign { margin-top: 30px; text-align: right; }
.lettre .petit { font-size: 12.5px; line-height: 1.55; }

/* Prestige */
.lettre .haut { display: flex; justify-content: space-between; gap: 30px; margin-bottom: 38px; }
.lettre.marge { padding-left: 74px; position: relative; }
.lettre.marge::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 26px; background: var(--acc); }
.lettre.marge .objet { border-left: 3px solid var(--acc); padding-left: 12px; }

/* Onyx Noir */
.l-cadre { display: flex; padding: 0; }
.l-cadre .cote { width: 230px; flex-shrink: 0; background: var(--fonce); color: #fff; padding: 44px 22px; }
.l-cadre .nom { font-family: 'Sora', sans-serif; font-weight: 800; font-size: 22px; line-height: 1.08; }
.l-cadre .role { font-size: 12px; color: var(--acc); font-weight: 700; margin-top: 6px; letter-spacing: .04em; }
.l-cadre h4 { font-size: 11px; letter-spacing: .1em; font-weight: 700; border: 1px solid rgba(255,255,255,.45); border-radius: 3px; padding: 6px 10px; margin: 26px 0 10px; }
.l-cadre .corps { flex: 1; padding: 50px 46px; }
.l-cadre h3 { font-size: 12.5px; letter-spacing: .08em; font-weight: 700; border: 1.5px solid #14161A; border-radius: 3px; padding: 9px 13px; margin: 0; }

/* Terracotta */
.l-arche { display: flex; padding: 0; background: var(--fond); color: #2B2424; }
.l-arche .cote { width: 232px; flex-shrink: 0; background: var(--fonce); border-top-right-radius: 110px; padding: 56px 24px 40px; }
.l-arche .nom { font-family: 'Source Serif 4', Georgia, serif; font-weight: 600; font-size: 26px; line-height: 1.08; }
.l-arche .role { font-family: 'Source Serif 4', Georgia, serif; font-size: 16px; color: var(--acc); margin: 6px 0 24px; }
.l-arche .coord { margin-bottom: 10px; }
.l-arche h4 { font-family: 'Source Serif 4', Georgia, serif; font-weight: 600; font-size: 19px; margin: 26px 0 8px; }
.l-arche .corps { flex: 1; padding: 56px 46px 40px; }
.l-arche .date { text-align: right; }
.l-arche .objet { font-family: 'Source Serif 4', Georgia, serif; font-size: 17px; }
.l-arche.sombre { color: #14161A; }
.l-arche.sombre .cote { color: #fff; }
.l-arche.sombre .cote h4, .l-arche.sombre .cote .coord b { color: var(--acc); }

/* Camel */
.l-camel { padding: 0; color: #3A3027; }
.l-camel .tete { background: var(--fond); padding: 44px 52px 34px; }
.l-camel .nom { font-size: 14px; letter-spacing: .32em; font-weight: 600; color: var(--acc); }
.l-camel .titre { font-family: 'Source Serif 4', Georgia, serif; font-weight: 600; font-size: 32px; letter-spacing: .05em; color: var(--acc); margin-top: 8px; line-height: 1.12; }
.l-camel .ligne { font-size: 12.5px; color: #6B5E52; margin-top: 12px; }
.l-camel .corps { padding: 36px 52px 46px; }
.l-camel .dest { text-align: right; margin-bottom: 8px; }
.l-camel .objet { font-size: 13px; letter-spacing: .12em; color: var(--acc); }

/* Noir et Blanc */
.l-pave { color: #111; }
.l-pave .nom { font-size: 34px; font-weight: 800; line-height: 1.02; }
.l-pave .role { font-size: 18px; font-style: italic; margin-top: 6px; }
.l-pave .filet { height: 2px; background: #111; margin: 16px 0 22px; }
.l-pave .duo { display: flex; gap: 30px; }
.l-pave .duo > div { flex: 1; }
.l-pave h4 { background: #111; color: #fff; font-size: 13px; font-weight: 700; letter-spacing: .04em; padding: 7px 12px; margin: 0 0 10px; }
.l-pave .date { text-align: right; margin-top: 18px; }

/* Lin */
.l-lin { color: #22211F; border-left: 26px solid var(--fonce); }
.l-lin .tete { display: flex; justify-content: space-between; gap: 24px; padding-bottom: 20px; border-bottom: 1.5px solid rgba(179,154,107,.5); }
.l-lin .nom { font-size: 28px; font-weight: 700; }
.l-lin .role { font-size: 16px; color: var(--acc); font-weight: 600; margin-top: 4px; }
.l-lin .coords { text-align: right; }
.l-lin .dest { margin-top: 26px; }
.l-lin .objet { position: relative; padding-left: 52px; margin-left: -52px; }
.l-lin .objet::before { content: ''; position: absolute; left: -5px; top: 50%; margin-top: -5px; width: 10px; height: 10px; border-radius: 50%; background: var(--acc); }
.l-lin .objet::after { content: ''; position: absolute; left: 5px; top: 50%; width: 38px; height: 1.5px; background: var(--acc); }

/* Ardoise */
.l-ardoise { padding: 0; color: #22262B; }
.l-ardoise .tete { background: var(--acc); color: #fff; padding: 40px 52px 32px; }
.l-ardoise .nom { font-size: 32px; font-weight: 800; }
.l-ardoise .role { font-size: 12.5px; letter-spacing: .18em; margin-top: 8px; opacity: .8; font-weight: 600; }
.l-ardoise .bandeau { background: var(--fonce); font-size: 12.5px; padding: 11px 52px; }
.l-ardoise .corps { padding: 34px 52px 46px; font-family: 'Source Serif 4', Georgia, serif; }
.l-ardoise .dest { text-align: right; }
.l-ardoise .date { text-align: right; margin-top: 14px; }
.l-ardoise .objet { font-family: 'Plus Jakarta Sans', sans-serif; font-weight: 800; padding-bottom: 7px; border-bottom: 3px solid var(--acc); }

/* Cyan */
.l-cyan { padding: 0; color: #1E2A30; }
.l-cyan .tete { display: flex; }
.l-cyan .gauche { width: 360px; background: var(--fonce); padding: 42px 30px 30px 52px; }
.l-cyan .nom { font-size: 28px; font-weight: 800; letter-spacing: .06em; line-height: 1.12; }
.l-cyan .role { font-size: 13px; letter-spacing: .14em; margin-top: 8px; }
.l-cyan .droite { flex: 1; background: var(--acc); color: #fff; margin-top: 36px; padding: 26px 28px; }
.l-cyan .corps { padding: 36px 52px 46px; }
.l-cyan .dest { text-align: right; }
.l-cyan .objet { color: var(--acc); letter-spacing: .04em; }

/* Gris Perle */
.l-perle { display: flex; padding: 0; color: #26282D; }
.l-perle .cote { width: 220px; flex-shrink: 0; background: var(--fonce); padding: 56px 24px 40px 30px; }
.l-perle h4 { font-size: 14px; font-weight: 400; letter-spacing: .06em; margin: 26px 0 8px; }
.l-perle .corps { flex: 1; padding: 50px 46px 40px 40px; }
.l-perle .prenom { font-size: 34px; font-weight: 400; line-height: 1.05; }
.l-perle .nomf { font-size: 34px; font-weight: 800; line-height: 1.05; }
.l-perle .role { font-size: 11.5px; letter-spacing: .08em; margin-top: 7px; }

/* Colonne Grise */
.l-graphite { display: flex; padding: 0; color: #55585E; }
.l-graphite .cote { width: 232px; flex-shrink: 0; background: var(--fonce); color: #fff; padding: 56px 28px 40px; }
.l-graphite .sec { padding: 16px 0; border-bottom: 1px solid rgba(255,255,255,.35); }
.l-graphite h4 { color: #fff; font-size: 14px; font-weight: 800; margin: 0 0 6px; }
.l-graphite .corps { flex: 1; padding: 56px 42px 40px; }
.l-graphite .tete { display: flex; justify-content: space-between; gap: 20px; }
.l-graphite .nom2 { font-size: 32px; font-weight: 800; color: #5E6166; line-height: 1.02; }
.l-graphite .role { font-size: 12px; letter-spacing: .08em; color: #8A8D92; margin-top: 8px; }
.l-graphite .coords { width: 200px; flex-shrink: 0; }
.l-graphite .coords div { font-size: 11px; padding: 6px 0 6px 26px; border-bottom: 1px solid #E3E4E6; position: relative; word-break: break-word; }
.l-graphite .coords div::before { content: ''; position: absolute; left: 0; top: 50%; margin-top: -8px; width: 16px; height: 16px; border-radius: 50%; background: #2F3033; }
.l-graphite h3 { font-size: 15px; font-weight: 800; color: #6B6E73; margin: 30px 0 0; }

/* Vert Sauge */
.l-sauge { display: flex; padding: 0; color: #3F5436; }
.l-sauge .cote { width: 220px; flex-shrink: 0; padding: 56px 24px 40px 30px; color: #1F2A1B; }
.l-sauge h4 { font-size: 12px; letter-spacing: .3em; font-weight: 500; margin: 26px 0 8px; }
.l-sauge .cote h4:first-child { margin-top: 0; }
.l-sauge .corps { flex: 1; background: var(--fonce); padding: 50px 0 40px; }
.l-sauge .nom2 { text-align: center; font-size: 30px; letter-spacing: .16em; color: var(--acc); line-height: 1.22; padding: 0 26px; }
.l-sauge .bande { background: #C8DFBB; color: #34512A; text-align: center; font-size: 13px; letter-spacing: .06em; font-weight: 600; padding: 11px 20px; margin: 16px 0 0; }
.l-sauge .dedans { padding: 0 38px; }
.l-sauge .objet { color: var(--acc); letter-spacing: .06em; }

.compteur { display: flex; gap: 9px; margin-bottom: 16px; }
.compteur div { flex: 1; background: var(--surface); border: 1px solid var(--bord); border-radius: 16px; padding: 13px; }
.compteur b { font-family: 'Sora', sans-serif; font-size: 22px; letter-spacing: -.03em; display: block; }
.compteur b.vert { color: var(--menthe); }
.compteur span { font-size: 11.5px; color: var(--doux); }
.offre { background: var(--surface); border: 1px solid var(--bord); border-radius: 20px; padding: 17px; margin-bottom: 11px; }
.offre.envoye { border-color: rgba(34,211,166,.35); background: rgba(34,211,166,.06); }
.offre-tete { display: flex; justify-content: space-between; gap: 12px; align-items: flex-start; margin-bottom: 8px; }
.offre h5 { font-family: 'Sora', sans-serif; font-size: 16px; font-weight: 700; margin: 0 0 3px; letter-spacing: -.02em; }
.offre .lieu { font-size: 13px; color: var(--doux); }
.canal { font-size: 10.5px; font-family: 'JetBrains Mono', monospace; padding: 5px 9px; border-radius: 8px; white-space: nowrap; }
.canal.auto { background: rgba(34,211,166,.14); color: var(--menthe); border: 1px solid rgba(34,211,166,.3); }
.canal.clic { background: rgba(110,86,248,.14); color: var(--accent-clair); border: 1px solid rgba(110,86,248,.3); }
.offre p.desc { font-size: 13.5px; color: #B4B4BF; line-height: 1.5; margin: 0 0 13px; }
.fait-tag { display: flex; align-items: center; gap: 7px; font-size: 13.5px; color: var(--menthe); font-weight: 600; }
.avis { background: rgba(110,86,248,.08); border: 1px solid rgba(110,86,248,.25); border-radius: 16px; padding: 13px 15px; font-size: 13.5px; line-height: 1.55; color: #C9C4F5; margin-bottom: 16px; }
.verrou { text-align: center; background: var(--surface); border: 1px solid rgba(110,86,248,.4); border-radius: 22px; padding: 30px 22px; background-image: linear-gradient(180deg, rgba(110,86,248,.12), transparent 60%); }
.verrou h3 { font-family: 'Sora', sans-serif; font-size: 21px; margin: 12px 0 8px; letter-spacing: -.03em; }
.verrou p { font-size: 14.5px; color: var(--doux); line-height: 1.55; margin: 0 auto 20px; max-width: 40ch; }

@media (max-width: 560px) {
  .grille { grid-template-columns: 1fr }
  .champ.demi { grid-column: 1 / -1 }
}

@media print {
  body * { visibility: hidden !important; }
  #impression, #impression * { visibility: visible !important; }
  #impression { position: absolute !important; left: 0 !important; top: 0 !important; }
  .scene { padding: 0 !important; background: #fff !important; border: 0 !important; }
  .piste { transform: none !important; height: auto !important; margin-left: 0 !important; }
  .feuille { box-shadow: none !important; }
  @page { size: A4; margin: 0; }
}
`;

/* ---------------------------- appel IA ---------------------------- */

async function demanderIA(tache, donnees) {
  const r = await fetch("api/ia.php", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ tache, donnees }),
  });
  if (!r.ok) {
    const e = await r.json().catch(() => ({}));
    throw new Error(e.erreur || "Erreur");
  }
  return r.json();
}

async function telechargerPDF({ commande, type, modele, donnees }) {
  const r = await fetch("api/pdf.php", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ commande, type, modele, donnees }),
  });
  if (!r.ok) throw new Error("PDF indisponible");
  const blob = await r.blob();
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = type === "lettre" ? "lettre-de-motivation.pdf" : "cv.pdf";
  a.click();
  URL.revokeObjectURL(url);
}

/* La photo est recadrée en carré et réduite à 400 px ici, dans le
   navigateur. Deux raisons : dompdf ne sait pas recadrer une image, et
   une photo de téléphone pèse plusieurs mégaoctets là où quarante
   kilooctets suffisent. */
function preparerPhoto(fichier) {
  return new Promise((resolve, reject) => {
    if (!fichier.type.startsWith("image/")) return reject(new Error("format"));
    if (fichier.size > 12 * 1024 * 1024) return reject(new Error("poids"));

    const lecteur = new FileReader();
    lecteur.onerror = () => reject(new Error("lecture"));
    lecteur.onload = () => {
      const img = new Image();
      img.onerror = () => reject(new Error("image"));
      img.onload = () => {
        const cote = Math.min(img.width, img.height);
        const T = 400;
        const c = document.createElement("canvas");
        c.width = T;
        c.height = T;
        const ctx = c.getContext("2d");
        ctx.fillStyle = "#fff";
        ctx.fillRect(0, 0, T, T);
        ctx.drawImage(img, (img.width - cote) / 2, (img.height - cote) / 2, cote, cote, 0, 0, T, T);
        resolve(c.toDataURL("image/jpeg", 0.82));
      };
      img.src = lecteur.result;
    };
    lecteur.readAsDataURL(fichier);
  });
}

/* Photo d'un ancien CV (prise au téléphone le plus souvent) : on la
   réduit à 2400 px avant l'envoi. Le navigateur la remet aussi dans le
   bon sens. Si elle ne s'ouvre pas ici, on envoie le fichier tel quel. */
function allegerPhotoCV(fichier) {
  return new Promise((resolve) => {
    const url = URL.createObjectURL(fichier);
    const img = new Image();
    img.onerror = () => { URL.revokeObjectURL(url); resolve(fichier); };
    img.onload = () => {
      const k = Math.min(1, 2400 / Math.max(img.width, img.height));
      const c = document.createElement("canvas");
      c.width = Math.round(img.width * k);
      c.height = Math.round(img.height * k);
      const ctx = c.getContext("2d");
      ctx.fillStyle = "#fff";
      ctx.fillRect(0, 0, c.width, c.height);
      ctx.drawImage(img, 0, 0, c.width, c.height);
      URL.revokeObjectURL(url);
      c.toBlob((b) => resolve(b || fichier), "image/jpeg", 0.9);
    };
    img.src = url;
  });
}

const MOIS = ["janv.", "févr.", "mars", "avr.", "mai", "juin", "juil.", "août", "sept.", "oct.", "nov.", "déc."];
function joliDate(v) {
  if (!v) return "";
  const [a, m] = v.split("-");
  return m ? `${MOIS[parseInt(m, 10) - 1]} ${a}` : a;
}
function periode(e) {
  const d = joliDate(e.debut);
  const f = e.actuel ? "aujourd'hui" : joliDate(e.fin);
  /* Même date au début et à la fin (souvent une seule année) : une seule fois. */
  return (f === d ? [d] : [d, f]).filter(Boolean).join(" – ");
}

/* --------------------------- feuille A4 ---------------------------- */

/* Le CV tient sur une page (14/10/2026). Quand le contenu est trop long,
   on réduit d'un même facteur la taille du texte, les interlignes et les
   espaces verticaux (marges, espacements entre les blocs), jusqu'à 85 %.
   La largeur des colonnes ne bouge pas. Le PDF fait exactement la même
   chose (api/modele-pdf.php, pdf_echelle) à partir du facteur trouvé ici. */
const ECHELLE_MIN = 0.85;
const HAUTEUR_PAGE = 1123;
const PROPS_ECHELLE = ["fontSize", "lineHeight", "marginTop", "marginBottom", "paddingTop", "paddingBottom"];

function ajusterFeuille(feuille, k) {
  const elements = [feuille, ...feuille.querySelectorAll("*")];
  /* On remet d'abord tout comme à l'origine. */
  elements.forEach((el) => {
    if (el.__echelle) {
      PROPS_ECHELLE.forEach((p) => { el.style[p] = el.__echelle[p]; });
      delete el.__echelle;
    }
  });
  if (k >= 0.999) return;
  /* On lit toutes les valeurs d'origine, puis on écrit (plus rapide). */
  const lues = elements.map((el) => {
    const cs = getComputedStyle(el);
    return PROPS_ECHELLE.map((p) => cs[p]);
  });
  elements.forEach((el, i) => {
    el.__echelle = {};
    PROPS_ECHELLE.forEach((p, j) => {
      el.__echelle[p] = el.style[p];
      const v = lues[i][j];
      const n = parseFloat(v);
      if (/px$/.test(v) && n) el.style[p] = (n * k).toFixed(2) + "px";
    });
  });
}

/* Cherche le plus grand facteur (de 100 % à 85 %) qui fait tenir le CV. */
function trouverEchelle(feuille) {
  let k = 1;
  ajusterFeuille(feuille, 1);
  while (feuille.scrollHeight > HAUTEUR_PAGE + 1 && k > ECHELLE_MIN + 0.0001) {
    k = Math.max(ECHELLE_MIN, Math.round((k - 0.01) * 100) / 100);
    ajusterFeuille(feuille, k);
  }
  return { k, trop: feuille.scrollHeight > HAUTEUR_PAGE + 1, hauteur: Math.max(HAUTEUR_PAGE, feuille.scrollHeight) };
}

function Scene({ children, verrouille, ajuster, onAjuste }) {
  const boite = useRef(null);
  const [k, setK] = useState(1);
  const [decalage, setDecalage] = useState(0);
  const [hauteur, setHauteur] = useState(HAUTEUR_PAGE);
  const signature = useRef("");
  const dernier = useRef(null);
  /* La page A4 entière doit tenir à l'écran : on la réduit à la largeur
     disponible, et aussi à la hauteur de la fenêtre (sans descendre sous
     55 %, pour qu'elle reste lisible). Plus étroite que la place, elle
     est centrée. */
  useEffect(() => {
    const calc = () => {
      if (!boite.current) return;
      const largeur = boite.current.clientWidth - 30;
      const parHauteur = Math.max(0.55, (window.innerHeight - 140) / HAUTEUR_PAGE);
      const nk = Math.min(1, largeur / 794, parHauteur);
      setK(nk);
      setDecalage(Math.max(0, (largeur - 794 * nk) / 2));
    };
    calc();
    const ro = new ResizeObserver(calc);
    if (boite.current) ro.observe(boite.current);
    window.addEventListener("resize", calc);
    return () => { ro.disconnect(); window.removeEventListener("resize", calc); };
  }, []);

  /* Ajustement à une page, recalculé seulement quand le contenu change. */
  const ajusterMaintenant = (forcer) => {
    const feuille = boite.current && boite.current.querySelector(".feuille");
    if (!ajuster || !feuille) return;
    const img = feuille.querySelector("img");
    const sig = feuille.className + "|" + feuille.textContent + "|" + (img ? img.src.length : 0);
    if (!forcer && sig === signature.current) return;
    signature.current = sig;
    const r = trouverEchelle(feuille);
    setHauteur(r.hauteur);
    const cle = r.k + "|" + r.trop;
    if (cle !== dernier.current) {
      dernier.current = cle;
      if (onAjuste) onAjuste({ k: r.k, trop: r.trop });
    }
  };
  React.useLayoutEffect(() => { ajusterMaintenant(false); });
  /* Les polices arrivent parfois après le premier affichage. */
  useEffect(() => {
    if (!ajuster || !document.fonts) return;
    document.fonts.ready.then(() => ajusterMaintenant(true));
  }, [ajuster]);

  const h = ajuster ? hauteur : HAUTEUR_PAGE;
  return (
    <div
      className={`scene ${verrouille ? "verrou-cv" : ""}`}
      ref={boite}
      id={verrouille ? undefined : "impression"}
    >
      <div className="piste" style={{ transform: `scale(${k})`, height: h * k + "px", marginLeft: decalage + "px" }}>
        {children}
        {ajuster && hauteur > HAUTEUR_PAGE + 1 && (
          <div className="fin-page" style={{ top: HAUTEUR_PAGE }}><span>Fin de la page 1</span></div>
        )}
      </div>
      {verrouille && (
        <div className="filigrane">
          {Array.from({ length: 40 }).map((_, i) => (
            <span key={i}>CV FOCUS · APERÇU</span>
          ))}
        </div>
      )}
    </div>
  );
}

/* ------------------------------ modèles ---------------------------- */

const BASES = ["cadre", "arche", "camel", "pave", "lin", "ardoise", "cyan", "perle", "graphite", "sauge", "colonne"];

function CV({ m, perso, exps, formations, extra }) {
  const base = BASES.includes(m.base) ? m.base : "pave";
  /* Format suisse : nationalité, date de naissance et permis de travail
     s'ajoutent, et « Permis B » devient « Permis de conduire B » (en
     Suisse, le permis B est un titre de séjour). */
  const suisse = !!perso.formatSuisse;
  const lignesSuisses = suisse
    ? [
        perso.nationalite && { k: "Nationalité", v: perso.nationalite, seul: "Nationalité : " + perso.nationalite },
        perso.naissance && { k: "Date de naissance", v: perso.naissance, seul: "Date de naissance : " + perso.naissance },
        perso.permisTravail && { k: "Permis de travail", v: perso.permisTravail, seul: perso.permisTravail },
      ].filter(Boolean)
    : [];
  /* Australie : le visa, s'il a été indiqué (facultatif). */
  const ligneVisa = perso.formatPays === "AU" && perso.visa ? [{ k: "Visa", v: perso.visa, seul: "Visa: " + perso.visa }] : [];
  const permisConduire = !perso.permis
    ? null
    : suisse
      ? { k: "Permis de conduire", v: "B", seul: "Permis de conduire B" }
      : { k: "Permis", v: "B", seul: "Permis B" };
  const contact = [
    perso.ville, perso.tel, perso.email,
    ...lignesSuisses.map((c) => c.seul),
    ...ligneVisa.map((c) => c.seul),
    permisConduire ? permisConduire.seul : "",
    ...String(perso.plus || "").split(/[,;\n]/).map((x) => x.trim()).filter(Boolean),
  ].filter(Boolean);
  const nom = `${perso.prenom} ${perso.nom}`.trim().toUpperCase() || "VOTRE NOM";
  const nomMixte = `${perso.prenom} ${perso.nom}`.trim() || "Prénom Nom";
  const prenom = String(perso.prenom || "").trim() || "Prénom";
  const nomFam = String(perso.nom || "").trim().toUpperCase() || "NOM";
  const metier = perso.metierVise ? perso.metierVise.toUpperCase() : "";
  const style = { "--acc": m.acc, "--fonce": m.fonce || "#14161A", "--fond": m.fond || "#FFFFFF" };
  const cls = `feuille m-${base}${m.serif ? " serif" : ""}${m.sombre ? " sombre" : ""}`;
  const forms = formations;

  /* La photo est facultative : sans elle, la mise en page ne bouge pas. */
  /* Australie : jamais de photo sur le CV (usage du pays). */
  const Photo = perso.photo && perso.formatPays !== "AU" ? <img className="photo-cv" src={perso.photo} alt="" /> : null;

  /* Chaque élément appris tient sur sa propre ligne, jamais en un seul pavé. */
  const lignesForm = (f) =>
    Array.isArray(f.detail)
      ? f.detail.filter(Boolean)
      : String(f.detail || "")
          .split(/\n|·|;/)
          .map((x) => x.trim())
          .filter(Boolean);
  const langues = extra.langues || [];
  /* Australie : le nom des langues en anglais (le niveau est traduit par <Anglais>). */
  const enAnglais = perso.formatPays === "AU";
  const lang = langues.map((l) => `${enAnglais ? langueEn(l.langue) : l.langue} — ${l.niveau}`);
  const comp = extra.competences || [];
  const qual = extra.qualites || [];

  /* Coordonnées, avec ou sans étiquette selon le modèle. */
  const coord = [
    perso.ville && { k: "Adresse", v: perso.ville, seul: perso.ville },
    perso.tel && { k: "Téléphone", v: perso.tel, seul: perso.tel },
    perso.email && { k: "Email", v: perso.email, seul: perso.email },
    ...lignesSuisses,
    ...ligneVisa,
    permisConduire,
    ...String(perso.plus || "").split(/[,;\n]/).map((x) => x.trim()).filter(Boolean).map((x) => ({ k: "", v: x, seul: x })),
  ].filter(Boolean);
  const Coord = ({ etiquette, majuscules }) =>
    coord.map((c, i) => (
      <div className="coord" key={i}>
        {etiquette && c.k ? <><b>{majuscules ? c.k.toUpperCase() : c.k}</b>{c.v}</> : c.seul}
      </div>
    ));

  const Liste = ({ items }) => <ul className="liste">{items.map((x, i) => <li key={i}>{x}</li>)}</ul>;

  /* Compétences et qualités : petites étiquettes en bas de la grande
     colonne. La colonne étroite ne garde que le court (coordonnées,
     langues) : une liste de compétences l'encombrait. Même chose sur le PDF. */
  const Etiquettes = ({ items }) => (
    <div className="etiquettes">{items.map((x, i) => <span className="etq" key={i}>{x}</span>)}</div>
  );
  /* Centres d'intérêt : une seule ligne courte, seulement si elle est remplie. */
  const interets = String(perso.interets || "").trim();
  /* Un seul titre pour les deux : plus compact, le CV tient sur une page. */
  const MotsCles = ({ maj, suite }) => (
    <>
      {comp.length + qual.length > 0 && (
        <>
          <h3 className={suite ? "suite" : undefined}>{maj ? "COMPÉTENCES ET QUALITÉS" : "Compétences et qualités"}</h3>
          <Etiquettes items={[...comp, ...qual]} />
        </>
      )}
      {interets && <p className="interets"><b>Centres d’intérêt</b> : {interets}</p>}
    </>
  );

  /* Expériences et formations, mises à plat pour que chaque modèle les
     affiche à sa façon : date au-dessus, à droite, à gauche, sur la même
     ligne que l'entreprise, ou en dessous. */
  const entreesExp = exps.map((e) => ({
    titre: String(e.poste || "").toUpperCase(),
    sous: [String(e.entreprise || "").toUpperCase(), e.ville].filter(Boolean).join(", "),
    date: periode(e),
    d1: joliDate(e.debut),
    d2: e.actuel ? "aujourd'hui" : joliDate(e.fin) === joliDate(e.debut) ? "" : joliDate(e.fin),
    lignes: e.missions || [],
  }));
  const entreesForm = forms.map((f) => ({
    titre: String(f.diplome || "").toUpperCase(),
    sous: [f.ecole, f.ville].filter(Boolean).join(", "),
    date: f.annee || "",
    d1: f.annee || "",
    d2: "",
    lignes: lignesForm(f),
  }));
  const Entrees = ({ liste, v, sep = " | " }) =>
    liste.map((x, i) => {
      const pts = x.lignes.length > 0 && <ul className="miss">{x.lignes.map((l, j) => <li key={j}>{l}</li>)}</ul>;
      if (v === "haut")
        return (
          <div className="ent" key={i}>
            {x.date && <span className="ent-date">{x.date}</span>}
            <div className="ent-titre">{x.titre}</div>
            {x.sous && <div className="ent-sous">{x.sous}</div>}
            {pts}
          </div>
        );
      if (v === "gauche")
        return (
          <div className="ent ent-g" key={i}>
            <div className="ent-date">{x.d1}{x.d2 && <><br />{x.d2}</>}</div>
            <div style={{ flex: 1 }}>
              <div className="ent-titre">{x.titre}{x.sous && ` - ${x.sous}`}</div>
              {pts}
            </div>
          </div>
        );
      if (v === "ligne")
        return (
          <div className="ent" key={i}>
            <div className="ent-titre">{x.titre}</div>
            {(x.sous || x.date) && <div className="ent-sous">{[x.sous, x.date].filter(Boolean).join(sep)}</div>}
            {pts}
          </div>
        );
      if (v === "empile")
        return (
          <div className="ent" key={i}>
            <div className="ent-titre">{x.titre}</div>
            {x.sous && <div className="ent-sous">{x.sous}</div>}
            {x.date && <span className="ent-date">{x.date}</span>}
            {pts}
          </div>
        );
      return (
        <div className="ent" key={i}>
          <div className="ent-tete">
            <div>
              <div className="ent-titre">{x.titre}</div>
              {x.sous && <div className="ent-sous">{x.sous}</div>}
            </div>
            {x.date && <div className="ent-date">{x.date}</div>}
          </div>
          {pts}
        </div>
      );
    });

  /* Mise en page d'origine d'Onyx Noir et de Prestige. */
  const Exp = ({ e }) => (
    <>
      <div className="exp-tete">
        <div>
          <div className="exp-poste">{String(e.poste || "").toUpperCase()}</div>
          <div className="exp-boite">{String(e.entreprise || "").toUpperCase()}{e.ville && ` · ${e.ville}`}</div>
        </div>
        <div className="exp-date">{periode(e)}</div>
      </div>
      <ul className="miss">{(e.missions || []).map((x, j) => <li key={j}>{x}</li>)}</ul>
    </>
  );
  const Experiences = () => exps.map((e, i) => <div className="bloc" key={i}><Exp e={e} /></div>);
  const Formations = () =>
    forms.map((f, i) => (
      <div key={i} style={{ marginBottom: 9 }}>
        <div className="exp-tete">
          <div>
            <span className="exp-poste" style={{ fontSize: 14 }}>{String(f.diplome || "").toUpperCase()}</span>
            <span className="exp-boite"> — {f.ecole}{f.ville && `, ${f.ville}`}</span>
          </div>
          <div className="exp-date">{f.annee}</div>
        </div>
        {lignesForm(f).length > 0 && <ul className="miss">{lignesForm(f).map((x, j) => <li key={j}>{x}</li>)}</ul>}
      </div>
    ));

  /* ---------------- Prestige : bandeau latéral doré ---------------- */
  if (base === "colonne")
    return (
      <div className={cls} style={style}>
        <aside className="cote">
          {Photo}
          <div className="nom">{nom}</div>
          {metier && <div className="role">{metier}</div>}
          <h4>Contact</h4>
          {contact.map((c, i) => <p key={i}>{c}</p>)}
          {lang.length > 0 && <><h4>Langues</h4><ul>{lang.map((c, i) => <li key={i}>{c}</li>)}</ul></>}
        </aside>
        <div className="corps">
          {extra.accroche && (<><h3>Profil</h3><p className="txt" style={{ marginBottom: 4 }}>{extra.accroche}</p><h3 className="suite">Parcours</h3></>)}
          {!extra.accroche && <h3>Parcours</h3>}
          <Experiences />
          {forms.length > 0 && <><h3 className="suite">Formation</h3><Formations /></>}
          <MotsCles suite />
        </div>
      </div>
    );

  /* ---------------- Onyx Noir : colonne noire, titres encadrés ---------------- */
  if (base === "cadre")
    return (
      <div className={cls} style={style}>
        <aside className="cote">
          {Photo}
          <div className="dedans">
            <div className="nom">{nom}</div>
            {metier && <div className="role">{metier}</div>}
            <h4>Profil</h4>
            {contact.map((c, i) => <p key={i}>{c}</p>)}
            {lang.length > 0 && <><h4>Langues</h4><ul>{lang.map((c, i) => <li key={i}>{c}</li>)}</ul></>}
          </div>
        </aside>
        <div className="corps">
          {extra.accroche && <><h3>Profil</h3><p className="txt" style={{ marginBottom: 6 }}>{extra.accroche}</p></>}
          <h3 className={extra.accroche ? "suite" : ""}>Expériences professionnelles</h3>
          <Experiences />
          {forms.length > 0 && <><h3 className="suite">Formations</h3><Formations /></>}
          <MotsCles suite />
        </div>
      </div>
    );

  /* ---------------- Terracotta ---------------- */
  if (base === "arche")
    return (
      <div className={cls} style={style}>
        <aside className="cote">
          {Photo}
          <Coord etiquette />
          {lang.length > 0 && <><h4>Langues</h4><Liste items={lang} /></>}
        </aside>
        <div className="corps">
          <div className="nom">{nom}</div>
          {metier && <div className="role">{metier}</div>}
          {extra.accroche && <p className="accroche">{extra.accroche}</p>}
          <h3>Expériences professionnelles</h3>
          <Entrees liste={entreesExp} v="haut" />
          {entreesForm.length > 0 && <><h3>Formations</h3><Entrees liste={entreesForm} v="haut" /></>}
          <MotsCles />
        </div>
      </div>
    );

  /* ---------------- Camel ---------------- */
  if (base === "camel")
    return (
      <div className={cls} style={style}>
        <div className="tete">
          {Photo}
          <div>
            <div className="nom">{nom}</div>
            {metier && <div className="role">{metier}</div>}
            {extra.accroche && <p className="accroche">{extra.accroche}</p>}
          </div>
        </div>
        <div className="bas">
          <aside className="cote">
            <h4>CONTACT</h4>
            <Coord />
            {lang.length > 0 && <><h4 className="suite">LANGUES</h4><Liste items={lang} /></>}
          </aside>
          <div className="corps">
            <h3>EXPÉRIENCE PROFESSIONNELLE</h3>
            <div className="frise"><Entrees liste={entreesExp} v="ligne" /></div>
            {entreesForm.length > 0 && (
              <><h3 className="suite">FORMATIONS</h3><div className="frise"><Entrees liste={entreesForm} v="ligne" /></div></>
            )}
            <MotsCles maj suite />
          </div>
        </div>
      </div>
    );

  /* ---------------- Lin ---------------- */
  if (base === "lin")
    return (
      <div className={cls} style={style}>
        <aside className="cote">
          {Photo}
          <Coord />
          {lang.length > 0 && <><h4>Langues</h4><Liste items={lang} /></>}
        </aside>
        <div className="corps">
          <div className="nom">{nomMixte}</div>
          {perso.metierVise && <div className="role">{perso.metierVise}</div>}
          {extra.accroche && <p className="accroche">{extra.accroche}</p>}
          <h3>Expérience professionnelle</h3>
          <Entrees liste={entreesExp} v="droite" />
          {entreesForm.length > 0 && <><h3>Diplômes et formations</h3><Entrees liste={entreesForm} v="droite" /></>}
          <MotsCles />
        </div>
      </div>
    );

  /* ---------------- Ardoise ---------------- */
  if (base === "ardoise")
    return (
      <div className={cls} style={style}>
        <aside className={Photo ? "cote" : "cote sans-photo"}>
          {Photo}
          <h4>COORDONNÉES</h4>
          <Coord />
          {lang.length > 0 && <><h4>LANGUES</h4><Liste items={lang} /></>}
        </aside>
        <div className="corps">
          <div className="tete">
            <div className="nom">{nomMixte}</div>
            {metier && <div className="role">{metier}</div>}
          </div>
          <div className="dedans">
            {extra.accroche && <><h3>PROFIL PROFESSIONNEL</h3><p className="accroche">{extra.accroche}</p></>}
            <h3>PARCOURS PROFESSIONNEL</h3>
            <Entrees liste={entreesExp} v="empile" />
            {entreesForm.length > 0 && <><h3>FORMATION</h3><Entrees liste={entreesForm} v="empile" /></>}
            <MotsCles maj />
          </div>
        </div>
      </div>
    );

  /* ---------------- Cyan ---------------- */
  if (base === "cyan")
    return (
      <div className={cls} style={style}>
        <div className="tete">
          <div className="gauche">
            <div className="nom">{prenom}<br />{nomFam}</div>
            {perso.metierVise && <div className="role">{perso.metierVise}</div>}
          </div>
          {Photo && <div className="milieu">{Photo}</div>}
          <div className="droite"><Coord /></div>
        </div>
        <div className="bas">
          <aside className="cote">
            {lang.length > 0 && <><h4>Langues</h4><Liste items={lang} /></>}
          </aside>
          <div className="corps">
            {/* Le profil dans la grande colonne, en texte normal (comme le PDF). */}
            {extra.accroche && <><h3>Profil</h3><p className="profil-corps">{extra.accroche}</p></>}
            <h3>Expériences professionnelles</h3>
            <Entrees liste={entreesExp} v="ligne" sep=" — " />
            {entreesForm.length > 0 && <><h3>Formations</h3><Entrees liste={entreesForm} v="ligne" sep=" — " /></>}
            <MotsCles />
          </div>
        </div>
      </div>
    );

  /* ---------------- Gris Perle ---------------- */
  if (base === "perle")
    return (
      <div className={cls} style={style}>
        <aside className="cote">
          <Coord />
          {lang.length > 0 && <><h4>LANGUES</h4><Liste items={lang} /></>}
        </aside>
        <div className="corps">
          <div className="tete">
            {Photo}
            <div>
              <div className="prenom">{prenom}</div>
              <div className="nomf">{nomFam}</div>
              {metier && <div className="role">{metier}</div>}
            </div>
          </div>
          {/* Le profil dans la grande colonne, sous le nom (comme le PDF). */}
          {extra.accroche && <><h3>PROFIL</h3><p className="profil-corps">{extra.accroche}</p></>}
          <h3>EXPÉRIENCES PROFESSIONNELLES</h3>
          <Entrees liste={entreesExp} v="ligne" />
          {entreesForm.length > 0 && <><h3>FORMATION</h3><Entrees liste={entreesForm} v="ligne" /></>}
          <MotsCles maj />
        </div>
      </div>
    );

  /* ---------------- Colonne Grise ---------------- */
  if (base === "graphite")
    return (
      <div className={cls} style={style}>
        <aside className="cote">
          {Photo}
          {lang.length > 0 && <div className="sec"><h4>LANGUES</h4><Liste items={lang} /></div>}
        </aside>
        <div className="corps">
          <div className="tete">
            <div>
              <div className="nom2">{prenom.toUpperCase()}<br />{nomFam}</div>
              {metier && <div className="role">{metier}</div>}
            </div>
            {coord.length > 0 && <div className="coords">{coord.map((c, i) => <div key={i}>{c.seul}</div>)}</div>}
          </div>
          {extra.accroche && <><h3>PROFIL</h3><p className="accroche">{extra.accroche}</p></>}
          <h3>EXPÉRIENCES PROFESSIONNELLES</h3>
          <Entrees liste={entreesExp} v="droite" />
          {entreesForm.length > 0 && <><h3>FORMATION</h3><Entrees liste={entreesForm} v="droite" /></>}
          <MotsCles maj />
        </div>
      </div>
    );

  /* ---------------- Vert Sauge ---------------- */
  if (base === "sauge")
    return (
      <div className={cls} style={style}>
        <aside className="cote">
          {Photo}
          <h4>CONTACT</h4>
          <Coord />
          {lang.length > 0 && <><h4>LANGUES</h4><Liste items={lang} /></>}
        </aside>
        <div className="corps">
          <div className="nom2">{prenom.toUpperCase()}<br />{nomFam}</div>
          {metier && <div className="bande">{metier}</div>}
          <div className="dedans">
            {extra.accroche && <><h3>PROFIL PROFESSIONNEL</h3><p className="accroche">{extra.accroche}</p></>}
            <h3>EXPÉRIENCES PROFESSIONNELLES</h3>
            <Entrees liste={entreesExp} v="empile" />
            {entreesForm.length > 0 && <><h3>FORMATION</h3><Entrees liste={entreesForm} v="empile" /></>}
            <MotsCles maj />
          </div>
        </div>
      </div>
    );

  /* ---------------- Noir et Blanc (et modèle de secours) ---------------- */
  return (
    <div className={cls} style={style}>
      <aside className="cote">
        {Photo}
        <h4>PROFIL</h4>
        <Coord etiquette majuscules />
        {lang.length > 0 && <><h4>LANGUES</h4><Liste items={lang} /></>}
      </aside>
      <div className="corps">
        <div className="nom">{nom}</div>
        {metier && <div className="role">{metier}</div>}
        <div className="filet" />
        {extra.accroche && <p className="accroche">{extra.accroche}</p>}
        <h3>EXPÉRIENCES PROFESSIONNELLES</h3>
        <Entrees liste={entreesExp} v="gauche" />
        {entreesForm.length > 0 && <><h3>FORMATIONS</h3><Entrees liste={entreesForm} v="gauche" /></>}
        <MotsCles maj />
      </div>
    </div>
  );
}

function Lettre({ m, perso, lettre }) {
  const base = BASES.includes(m.base) ? m.base : "pave";
  const nom = `${perso.prenom} ${perso.nom}`.trim().toUpperCase();
  const nomMixte = `${perso.prenom} ${perso.nom}`.trim();
  const prenom = String(perso.prenom || "").trim() || "Prénom";
  const nomFam = String(perso.nom || "").trim().toUpperCase() || "NOM";
  const metier = perso.metierVise ? perso.metierVise.toUpperCase() : "";
  const boite = String(lettre.entreprise || "").toUpperCase();
  const style = { "--acc": m.acc, "--fonce": m.fonce || "#14161A", "--fond": m.fond || "#FFFFFF" };
  /* Australie : lettre en anglais (date et objet compris). */
  const en = perso.formatPays === "AU";
  const aujourdhui = new Date().toLocaleDateString(en ? "en-AU" : "fr-FR", { day: "numeric", month: "long", year: "numeric" });
  const ligneDate = en
    ? `${perso.ville ? `${perso.ville}, ` : ""}${aujourdhui}`
    : `${perso.ville ? `${perso.ville}, le ` : "Le "}${aujourdhui}`;
  /* Le poste ressort en majuscules, même au milieu de l'objet rédigé par l'IA. */
  const posteL = (lettre.poste || perso.metierVise || "").trim();
  const objetBrut = lettre.objet || (en ? `Re: Application for the position of ${posteL || "…"}` : `Objet : candidature au poste de ${posteL || "…"}`);
  const objet = posteL
    ? objetBrut.replace(new RegExp(posteL.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "gi"), posteL.toUpperCase())
    : objetBrut;
  const contacts = [perso.ville, perso.tel, perso.email].filter(Boolean);

  const Corps = () => (
    <>
      <div className="objet">{objet}</div>
      {(lettre.texte || "Le texte de votre lettre apparaîtra ici.").split("\n").filter(Boolean).map((p, i) => (
        <p key={i}>{p}</p>
      ))}
      <div className="sign">{nom}</div>
    </>
  );
  const Exped = () => contacts.map((x, i) => <div key={i}>{x}</div>);
  const Dest = () => (
    <>
      {boite && <b>{boite}</b>}
      {lettre.adresse && <div>{lettre.adresse}</div>}
      {lettre.villeEntreprise && <div>{lettre.villeEntreprise}</div>}
    </>
  );

  /* Prestige */
  if (base === "colonne")
    return (
      <div className="feuille lettre marge serif" style={style}>
        <div className="haut">
          <div><strong>{nom}</strong><Exped /></div>
          <div style={{ textAlign: "right" }}><Dest /><br />{ligneDate}</div>
        </div>
        <Corps />
      </div>
    );

  /* Onyx Noir */
  if (base === "cadre")
    return (
      <div className="feuille lettre l-cadre" style={style}>
        <aside className="cote petit">
          <div className="nom">{nom}</div>
          {metier && <div className="role">{metier}</div>}
          <h4>EXPÉDITEUR</h4><Exped />
          <h4>DESTINATAIRE</h4><Dest />
          <h4>DATE</h4><div>{ligneDate}</div>
        </aside>
        <div className="corps"><h3>LETTRE DE MOTIVATION</h3><Corps /></div>
      </div>
    );

  /* Terracotta */
  if (base === "arche")
    return (
      <div className={"feuille lettre l-arche" + (m.sombre ? " sombre" : "")} style={style}>
        <aside className="cote petit">
          <div className="nom">{nom}</div>
          {metier && <div className="role">{metier}</div>}
          {perso.ville && <div className="coord"><b>Adresse</b>{perso.ville}</div>}
          {perso.tel && <div className="coord"><b>Téléphone</b>{perso.tel}</div>}
          {perso.email && <div className="coord"><b>Email</b>{perso.email}</div>}
          <h4>Destinataire</h4><Dest />
        </aside>
        <div className="corps"><div className="date">{ligneDate}</div><Corps /></div>
      </div>
    );

  /* Camel */
  if (base === "camel")
    return (
      <div className="feuille lettre l-camel" style={style}>
        <div className="tete">
          <div className="nom">{nom}</div>
          <div className="titre">LETTRE DE MOTIVATION</div>
          {contacts.length > 0 && <div className="ligne">{contacts.join("  |  ")}</div>}
        </div>
        <div className="corps">
          <div className="dest"><Dest /><div style={{ marginTop: 12 }}>{ligneDate}</div></div>
          <Corps />
        </div>
      </div>
    );

  /* Lin */
  if (base === "lin")
    return (
      <div className="feuille lettre l-lin" style={style}>
        <div className="tete">
          <div>
            <div className="nom">{nomMixte || "Prénom Nom"}</div>
            {perso.metierVise && <div className="role">{perso.metierVise}</div>}
          </div>
          <div className="coords petit"><Exped /></div>
        </div>
        <div className="dest"><Dest /><div style={{ marginTop: 10 }}>{ligneDate}</div></div>
        <Corps />
      </div>
    );

  /* Ardoise */
  if (base === "ardoise")
    return (
      <div className="feuille lettre l-ardoise" style={style}>
        <div className="tete">
          <div className="nom">{nomMixte || "Prénom Nom"}</div>
          {metier && <div className="role">{metier}</div>}
        </div>
        {contacts.length > 0 && <div className="bandeau">{contacts.join("   ·   ")}</div>}
        <div className="corps">
          <div className="dest"><Dest /></div>
          <div className="date">{ligneDate}</div>
          <Corps />
        </div>
      </div>
    );

  /* Cyan */
  if (base === "cyan")
    return (
      <div className="feuille lettre l-cyan" style={style}>
        <div className="tete">
          <div className="gauche">
            <div className="nom">{prenom}<br />{nomFam}</div>
            {perso.metierVise && <div className="role">{perso.metierVise}</div>}
          </div>
          <div className="droite petit"><Exped /></div>
        </div>
        <div className="corps">
          <div className="dest"><Dest /><div style={{ marginTop: 10 }}>{ligneDate}</div></div>
          <Corps />
        </div>
      </div>
    );

  /* Gris Perle */
  if (base === "perle")
    return (
      <div className="feuille lettre l-perle" style={style}>
        <aside className="cote petit">
          <Exped />
          <h4>DESTINATAIRE</h4><Dest />
          <h4>DATE</h4><div>{ligneDate}</div>
        </aside>
        <div className="corps">
          <div className="prenom">{prenom}</div>
          <div className="nomf">{nomFam}</div>
          {metier && <div className="role">{metier}</div>}
          <Corps />
        </div>
      </div>
    );

  /* Colonne Grise */
  if (base === "graphite")
    return (
      <div className="feuille lettre l-graphite" style={style}>
        <aside className="cote petit">
          <div className="sec"><h4>À / AU</h4><Dest /></div>
          <div className="sec"><h4>DE LA PART DE</h4><div>{nomMixte}</div>{perso.metierVise && <div>{perso.metierVise}</div>}</div>
          <div className="sec"><h4>DATE</h4><div>{ligneDate}</div></div>
        </aside>
        <div className="corps">
          <div className="tete">
            <div>
              <div className="nom2">{prenom.toUpperCase()}<br />{nomFam}</div>
              {metier && <div className="role">{metier}</div>}
            </div>
            {contacts.length > 0 && <div className="coords">{contacts.map((x, i) => <div key={i}>{x}</div>)}</div>}
          </div>
          <h3>LETTRE DE MOTIVATION</h3>
          <Corps />
        </div>
      </div>
    );

  /* Vert Sauge */
  if (base === "sauge")
    return (
      <div className="feuille lettre l-sauge" style={style}>
        <aside className="cote petit">
          <h4>CONTACT</h4><Exped />
          <h4>DESTINATAIRE</h4><Dest />
          <h4>DATE</h4><div>{ligneDate}</div>
        </aside>
        <div className="corps">
          <div className="nom2">{prenom.toUpperCase()}<br />{nomFam}</div>
          {metier && <div className="bande">{metier}</div>}
          <div className="dedans"><Corps /></div>
        </div>
      </div>
    );

  /* Noir et Blanc (et modèle de secours) */
  return (
    <div className="feuille lettre l-pave" style={style}>
      <div className="nom">{nom}</div>
      {metier && <div className="role">{metier}</div>}
      <div className="filet" />
      <div className="duo petit">
        <div><h4>EXPÉDITEUR</h4><Exped /></div>
        <div><h4>DESTINATAIRE</h4><Dest /></div>
      </div>
      <div className="date">{ligneDate}</div>
      <Corps />
    </div>
  );
}

/* Pictogrammes dessinés en SVG : rien à télécharger, rien à payer,
   et aucun risque de lien mort. Si tu veux de vraies photos plus tard,
   ajoute un champ "photo" à un métier ci-dessus et remplis-le avec
   l'adresse d'une image libre de droits (Pexels, Unsplash). */
const TRACES = {
  pain: "M4 15c0-3 2-7 5-7s5 4 5 7-2 4-5 4-5-1-5-4Zm11-7c2 0 4 3 4 6s-1 4-3 4M9 9l2 8M12 8l2 8",
  colis: "M4 8.5 12 4l8 4.5v7L12 20l-8-4.5v-7Zm0 0 8 4.5m0 0 8-4.5m-8 4.5V20",
  camion: "M3 7h10v9H3zM13 10h4l3 3v3h-7zM7 19a2 2 0 1 0 0-4 2 2 0 0 0 0 4Zm10 0a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z",
  volant: "M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18Zm0 6a3 3 0 1 0 0 6 3 3 0 0 0 0-6Zm0-6v6m-9 3h6m6 0h6m-9 3v6",
  soin: "M12 20s-7-4.5-7-9a4 4 0 0 1 7-2.6A4 4 0 0 1 19 11c0 4.5-7 9-7 9Z",
  caddie: "M3 5h2l2.5 10h10L20 8H6M9 20a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm8 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z",
  assiette: "M12 4a7 7 0 1 0 0 14 7 7 0 0 0 0-14Zm0 3a4 4 0 1 0 0 8 4 4 0 0 0 0-8ZM5 20h14",
  chariot: "M4 5h6v9H4zM4 17h9M13 17V9h3l3 4v4M17 20a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z",
  balai: "m6 20 5-9m0 0 3-6 4 2-3 6m-4-2 4 2M9 17h6",
  truelle: "M5 5h8l-4 7-4-7Zm4 7v7m-2 0h4",
  ciseaux: "M6 6l12 12M18 6 6 18M7 8a2 2 0 1 0 0-4 2 2 0 0 0 0 4Zm0 12a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z",
  bouclier: "M12 3 5 6v6c0 4 3 7 7 9 4-2 7-5 7-9V6l-7-3Zm-2 9 2 2 3-4",
  cle: "M15 4a4 4 0 0 0-3.5 6L4 17.5V20h3l7.5-7.5A4 4 0 1 0 15 4Zm1 3.5h.01",
  eclair: "M13 3 5 14h6l-1 7 8-11h-6l1-7Z",
  goutte: "M12 3s6 6.5 6 10a6 6 0 0 1-12 0c0-3.5 6-10 6-10Z",
  pinceau: "M7 14 17 4l3 3-10 10-4 1 1-4Zm0 0 3 3M4 20h6",
  dossier: "M4 6h6l2 2h8v11H4V6Zm4 8h8m-8 3h5",
};

function Picto({ ic }) {
  return (
    <svg viewBox="0 0 24 24" width="26" height="26" fill="none" stroke="currentColor"
         strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d={TRACES[ic] || TRACES.dossier} />
    </svg>
  );
}

/* Mot de passe : six caractères au minimum, et pas seulement des
   lettres. Un mot de passe trop simple met en danger les CV gardés
   dans le compte. */
function mdpValable(m) {
  if (!m || m.length < 6) return false;
  const lettre = /[a-zA-Z]/.test(m);
  const autre  = /[0-9]|[^a-zA-Z0-9]/.test(m);
  return lettre && autre;
}

const ORDRE = ["infos", "exps", "cv", "lettre"];

/* Étapes du parcours pour le tableau de bord (comptage anonyme, voir
   assets/cvfocus-visite.js). Ne fait rien si ce petit script manque. */
const stat = (e) => { try { window.cvfocusStat && window.cvfocusStat(e); } catch {} };

/* ------------------------------- appli ----------------------------- */

function CVFocus() {
  const [ecran, setEcran] = useState("accueil");
  const [commande, setCommande] = useState(null);
  const [charge, setCharge] = useState("");
  const [erreur, setErreur] = useState("");
  const [modeleId, setModeleId] = useState(MODELES[0].id);
  const [modeleLettreId, setModeleLettreId] = useState(null);
  const [retouche, setRetouche] = useState("");
  const [mailLivraison, setMailLivraison] = useState("");
  const [livre, setLivre] = useState(false);
    const [jetonDossier, setJetonDossier] = useState(null);
  const [restantes, setRestantes] = useState(0);

  const [perso, setPerso] = useState({ prenom: "", nom: "", metierVise: "", email: "", tel: "", ville: "", permis: false, plus: "", photo: "" });
  const [exps, setExps] = useState([{ poste: "", entreprise: "", ville: "", debut: "", fin: "", actuel: false, missions: [] }]);
  const [formations, setFormations] = useState([{ diplome: "", ecole: "", ville: "", annee: "", detail: "" }]);
  const [extra, setExtra] = useState({ accroche: "", competences: [], qualites: [], langues: [] });
  const [nouvComp, setNouvComp] = useState("");
  const [nouvLangue, setNouvLangue] = useState({ langue: "", niveau: "Courant" });
  const [lettre, setLettre] = useState({ entreprise: "", adresse: "", villeEntreprise: "", poste: "", objet: "", texte: "" });
  const [retoucheLettre, setRetoucheLettre] = useState("");
  /* Ajustement du CV à une page : facteur trouvé par l'aperçu, et « trop long même à 85 % ». */
  const [ajuste, setAjuste] = useState({ k: 1, trop: false });
  /* Texte d'avant « Raccourcir », pour pouvoir y revenir. */
  const [avantRaccourci, setAvantRaccourci] = useState(null);
  /* L'annonce d'ou vient la personne, quand elle arrive depuis « Rechercher
     une offre d'emploi ». Nulle dans le parcours habituel. */
  const [offre, setOffre] = useState(null);
  /* Compte et abonnement. */
  const [moi, setMoi] = useState(null);
  const [voile, setVoile] = useState(false);
  const [modeCompte, setModeCompte] = useState("inscription");
  const [suiteFormule, setSuiteFormule] = useState(null);
    /* Ce que la personne voulait faire quand on lui a demande de se
     connecter. On le rejoue tout seul une fois son compte cree. */
  const [suiteAction, setSuiteAction] = useState(null);
  /* Numéro du paiement Stripe au retour d'un abonnement : envoyé avec le
     dossier pour que le serveur vérifie lui-même le paiement. */
  const paiementRef = useRef("");
  /* Lien pour postuler sur le site de l'annonce, rendu par le serveur
     après l'envoi du dossier : gros bouton de la page de remerciement. */
  const [lienPostuler, setLienPostuler] = useState("");
  const [cpt, setCpt] = useState({ prenom: "", nom: "", email: "", mdp: "" });
  const [erreurCompte, setErreurCompte] = useState("");
  const [mdpVisible, setMdpVisible] = useState(false);
  const [menuCompte, setMenuCompte] = useState(false);
  const [texteCV, setTexteCV] = useState("");
  const [metierAnalyse, setMetierAnalyse] = useState("");
  const [analyse, setAnalyse] = useState(null);
  const [fichierCV, setFichierCV] = useState(null);
  /* Import d'un ancien CV (écran « depart ») : lecture en cours, message
     d'erreur, résumé de ce qui a été lu, et ce que l'ancien CV disait
     déjà (profil, compétences, qualités) : l'IA le reprend à la rédaction. */
  const [lectureCV, setLectureCV] = useState(false);
  const [erreurImport, setErreurImport] = useState("");
  const [importe, setImporte] = useState(null);
  const [ancien, setAncien] = useState(null);
  const champImport = useRef(null);
  const champPhotoTel = useRef(null);
  const champPhoto = useRef(null);
  const [chargeAbo, setChargeAbo] = useState("");


  /* CV au format suisse : décidé à l'arrivée depuis une annonce suisse,
     puis gardé dans « perso » pour suivre le dossier partout (paiement,
     lien de reprise, fabrication du PDF). */
  const modeSuisse = !!perso.formatSuisse;
  /* Pays de la candidature : "CH", "LU", "AU", ou "" pour la France. */
  const paysCV = perso.formatPays || (modeSuisse ? "CH" : "");
  const modeLux = paysCV === "LU";
  const modeAus = paysCV === "AU";
  const modelesProposes = modeSuisse ? MODELES.filter((x) => MODELES_SUISSES.includes(x.id)) : MODELES;
  useEffect(() => {
    if (modeSuisse && !MODELES_SUISSES.includes(modeleId)) setModeleId(MODELES_SUISSES[0]);
  }, [modeSuisse, modeleId]);
  /* La page d'où vient l'annonce : la recherche du pays, ou celle de France. */
  const PAGES_PAYS = { CH: "/suisse.php", LU: "/luxembourg.php", AU: "/australie.php" };
  const pageOffres = (offre && PAGES_PAYS[offre.pays]) || "/offres.php";
  /* Ajoute la langue choisie (nom de la liste si elle y est). Une langue
     déjà présente est simplement mise à jour avec le nouveau niveau. */
  const ajouterLangue = () => {
    const nom = langueCanonique(nouvLangue.langue);
    if (!nom) return;
    setExtra((x) => ({
      ...x,
      langues: [...(x.langues || []).filter((l) => sansAccent(l.langue) !== sansAccent(nom)), { langue: nom, niveau: nouvLangue.niveau }],
    }));
    setNouvLangue({ langue: "", niveau: "Courant" });
  };

  /* Luxembourg : niveau d'une des langues clés, lu et écrit dans extra.langues. */
  const niveauLangue = (nom) => ((extra.langues || []).find((l) => l.langue === nom) || {}).niveau || "";
  const majLangue = (nom, niveau) => setExtra((e) => {
    const autres = (e.langues || []).filter((l) => l.langue !== nom);
    return { ...e, langues: niveau ? [...autres, { langue: nom, niveau }] : autres };
  });

  const m = trouverModele(modeleId) || MODELES[0];
  const mLettre = modeleLettreId ? trouverModele(modeleLettreId) || m : m;
  const majExp = (i, k, v) => setExps((l) => l.map((e, j) => (j === i ? { ...e, [k]: v } : e)));
  const majForm = (i, k, v) => setFormations((l) => l.map((e, j) => (j === i ? { ...e, [k]: v } : e)));
  /* Le dossier complet, tel qu'il part vers le serveur pour fabriquer un PDF. */
  /* « echelle » : réglage trouvé par l'aperçu pour que le CV tienne sur une page (repris par le PDF). */
  const dossier = () => ({ perso, exps, formations, extra, lettre, modeleId, modeleLettreId, echelle: ajuste.k });
  /* Une fois le dossier payé et envoyé, les écrans de rédaction sont
     clos : on ne modifie plus un CV déjà livré. */
  const aller = (e) => {
        setEcran(commande && !jetonDossier && e !== "merci" ? "merci" : e);
    window.scrollTo({ top: 0 });
  };
  /* Début d'un CV : d'abord le choix « Importer mon ancien CV » ou
     « Remplir à la main », sauf si la personne a déjà commencé à remplir. */
  const commencer = () => aller(perso.prenom || perso.nom || exps.some((e) => e.poste) ? "infos" : "depart");
  const remplirMain = () => { setErreurImport(""); setImporte(null); setAncien(null); aller("infos"); };

  /* L'ancien CV part au serveur, l'IA le lit, et le formulaire se remplit.
     Gratuit : aucun crédit n'est utilisé. Le fichier n'est pas gardé. */
  async function importerCV(f) {
    if (!f) return;
    setErreurImport("");
    const nomF = String(f.name || "").toLowerCase();
    const estImage = String(f.type || "").startsWith("image/") || /\.(jpe?g|png|webp|heic|heif)$/.test(nomF);
    const estPdf = f.type === "application/pdf" || nomF.endsWith(".pdf");
    const estWord = nomF.endsWith(".docx");
    if (nomF.endsWith(".doc")) {
      setErreurImport("Les anciens fichiers Word (.doc) ne sont pas acceptés. Dans Word, enregistrez votre CV en .docx ou en PDF, puis réessayez.");
      return;
    }
    if (!estImage && !estPdf && !estWord) {
      setErreurImport("Ce format n'est pas accepté. Envoyez un PDF, un fichier Word (.docx) ou une photo (JPG ou PNG).");
      return;
    }
    if (f.size > 10 * 1024 * 1024) {
      setErreurImport("Le fichier dépasse 10 Mo. Envoyez un fichier plus léger.");
      return;
    }
    setLectureCV(true);
    window.scrollTo({ top: 0 });
    try {
      const envoi = estImage ? await allegerPhotoCV(f) : f;
      const corps = new FormData();
      corps.append("cv", envoi, estImage && envoi !== f ? "cv.jpg" : f.name);
      const r = await fetch("api/import-cv.php", { method: "POST", body: corps });
      const j = await r.json().catch(() => ({}));
      if (!r.ok || !j.ok) throw new Error(j.erreur || "Nous n'avons pas réussi à lire votre CV. Réessayez dans un instant.");
      appliquerImport(j);
    } catch (e) {
      setErreurImport(e && e.message && !/fetch|network/i.test(e.message)
        ? e.message
        : "La connexion a été coupée pendant l'envoi. Réessayez.");
    }
    setLectureCV(false);
  }

  /* Ce que l'IA a lu remplace le formulaire. Ce qu'elle n'a pas trouvé
     reste vide : la case passe en orange (voir « manque »). Le poste
     recherché vient de l'ancien CV, jamais de l'annonce. */
  function appliquerImport(j) {
    const p = j.perso || {};
    const photoLue = perso.formatPays !== "AU" && p.photo ? p.photo : "";
    setPerso((x) => ({
      ...x,
      prenom: p.prenom || "", nom: p.nom || "", metierVise: p.metierVise || "",
      tel: p.tel || "", email: p.email || "", ville: p.ville || "",
      permis: !!p.permis, interets: p.interets || "",
      nationalite: p.nationalite || x.nationalite || "",
      naissance: p.naissance || x.naissance || "",
      photo: photoLue || x.photo || "",
      photoImportee: !!photoLue,
    }));
    const vide = { poste: "", entreprise: "", ville: "", debut: "", fin: "", actuel: false, missions: [] };
    setExps(Array.isArray(j.exps) && j.exps.length ? j.exps.map((e) => ({ ...vide, ...e, missions: [] })) : [vide]);
    setFormations(Array.isArray(j.formations) && j.formations.length
      ? j.formations.map((f) => ({ diplome: "", ecole: "", ville: "", annee: "", detail: "", ...f }))
      : [{ diplome: "", ecole: "", ville: "", annee: "", detail: "" }]);
    setExtra((x) => ({
      ...x, accroche: "", competences: [], qualites: [],
      langues: Array.isArray(j.langues) && j.langues.length ? j.langues : x.langues,
    }));
    setAncien({ accroche: j.accroche || "", competences: j.competences || [], qualites: j.qualites || [] });
    setImporte({ gardees: (j.exps || []).length, trouvees: j.experiencesTrouvees || 0, photo: !!photoLue });
    aller("infos");
  }
  /* Après un import, une case restée vide est signalée en orange. */
  const manque = (v) => (importe && !String(v || "").trim() ? " manque" : "");
  const aRemplir = (v, exemple) => (importe && !String(v || "").trim() ? "Pas trouvé dans votre CV" : exemple);

  /* Photo choisie par la personne (la sienne, ou à la place de celle reprise de l'ancien CV). */
  async function choisirPhoto(ev) {
    const f = ev.target.files && ev.target.files[0];
    ev.target.value = "";
    if (!f) return;
    setErreur("");
    try {
      const carree = await preparerPhoto(f);
      setPerso((p) => ({ ...p, photo: carree, photoImportee: false }));
    } catch {
      setErreur("Cette image n'a pas pu être lue. Essayez un fichier JPEG ou PNG.");
    }
  }

  /* Bouton « Retour » du bas de page : recule d'un écran dans le parcours.
   Quand on est déjà sur le premier écran, on ressort vers l'annonce
   d'origine, ou vers la liste des offres si on ne vient pas de là. */
const reculer = () => {
  if (ecran === "infos") { aller("depart"); return; }
  if (ecran === "depart" && !offre) { aller("accueil"); return; }
  const i = ORDRE.indexOf(ecran);
  if (i > 0) { aller(ORDRE[i - 1]); return; }
  window.location.href = offre && offre.id
    ? pageOffres + "?offre=" + encodeURIComponent(offre.id)
    : pageOffres;
};

  /* Retour de Stripe : on restaure le dossier, on l'envoie par mail,
     et on affiche la page de remerciement. */
  useEffect(() => {
    const p = new URLSearchParams(window.location.search);

    /* Statistiques anonymes (tableau de bord) : retour d'un paiement. */
    if (/^cs_/.test(p.get("session") || "")) stat("paiement");

    /* Reprise d'un dossier depuis le lien reçu par mail. */
    const repriseJeton = p.get("dossier");
    if (repriseJeton) {
      fetch("api/dossier.php", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ jeton: repriseJeton }),
      })
        .then((r) => r.json())
        .then((j) => {
          if (j.erreur) { setErreur("Ce lien n'est plus valable."); return; }
          setPerso(j.dossier.perso);
          setExps(j.dossier.exps);
          setFormations(j.dossier.formations);
          setExtra(j.dossier.extra);
          setModeleId(idModeleValide(j.dossier.modeleId));
          setModeleLettreId(j.dossier.modeleLettreId ? idModeleValide(j.dossier.modeleLettreId) : null);
          setLettre({ entreprise: "", adresse: "", villeEntreprise: "", poste: "", objet: "", texte: "" });
          setMailLivraison(j.email);
          setJetonDossier(repriseJeton);
          setRestantes(j.restantes);
          setEcran("lettre");
        })
        .catch(() => setErreur("Ce lien n'est plus valable."));
      window.history.replaceState({}, "", window.location.pathname);
      return;
    }
    /* Arrivee depuis une annonce : on va chercher l'offre gardee sur le
       serveur, on pre-remplit ce qu'elle nous apprend, et on ouvre
       directement le formulaire au lieu de la page d'accueil. */
    const idOffre = p.get("offre");
    /* Les anciens liens ne portaient qu'un drapeau (?offre=1) : le serveur
       renvoyait alors « la » derniere annonce vue, et une offre d'un test
       precedent pouvait ressortir a la place de celle qu'on venait
       d'ouvrir. Desormais l'URL porte l'identifiant France Travail et on
       reclame cette annonce-la, en verifiant que c'est bien elle qui
       revient. */
    const legacyOffre = idOffre === "1" || idOffre === "oui";
    if (idOffre) {
      fetch(legacyOffre ? "api/offre-en-cours.php"
                        : "api/offre-en-cours.php?id=" + encodeURIComponent(idOffre))
        .then((r) => r.json())
        .then((j) => {
          /* Pas d'annonce, ou pas celle demandee : on n'ecrit rien. Mieux
             vaut renvoyer vers la recherche qu'une lettre adressee a la
             mauvaise entreprise. */
          if (!j.offre || (!legacyOffre && j.offre.id !== idOffre)) {
            setErreur("Cette annonce n'a pas pu etre retrouvee. Ouvrez-la a nouveau depuis la recherche d'offres, pour que votre lettre parte bien sur cette entreprise.");
            return;
          }
          setOffre(j.offre);
          /* Annonce suisse : le CV passe au format suisse, sans choix à faire. */
          if (j.offre.pays === "CH") {
            setPerso((p) => ({ ...p, formatSuisse: true, formatPays: "CH" }));
          } else if (j.offre.pays === "LU" || j.offre.pays === "AU") {
            /* Luxembourg : CV en français, langues mises en avant.
               Australie : CV et lettre en anglais, sans photo. */
            setPerso((p) => ({ ...p, formatSuisse: false, formatPays: j.offre.pays }));
          }
          /* On ne pre-remplit pas « Poste recherche » : le CV reste celui de
             la personne, pas celui d'une annonce. Seule la lettre vise
             l'entreprise. */
          /* L'annonce l'emporte toujours sur un dossier précédent : avant,
             un poste resté d'une autre candidature (« Opérateur ») passait
             dans l'objet de la lettre. Objet et texte repartent de zéro
             pour être rédigés pour cette offre-là. */
          setLettre((l) => ({
            ...l,
            entreprise: j.offre.entreprise || "",
            poste: j.offre.poste || "",
            villeEntreprise: j.offre.lieu || "",
            adresse: "",
            objet: "",
            texte: "",
          }));
          setEcran("depart");
        })
        .catch(() => {});
      window.history.replaceState({}, "", window.location.pathname);
      return;
    }

        /* Retour de Stripe apres un abonnement : on ressort le CV et la
       lettre ranges avant le depart, on revient sur l'ecran quitte, et
       on envoie le dossier par mail sans rien demander de plus. */
    if (p.get("reprise")) {
      paiementRef.current = p.get("session") || "";
      let gardé = null;
      try { gardé = JSON.parse(sessionStorage.getItem("cvfocus") || "null"); } catch {}
      const ecranGardé = sessionStorage.getItem("cvfocus-ecran") || "lettre";
      try {
        sessionStorage.removeItem("cvfocus");
        sessionStorage.removeItem("cvfocus-ecran");
      } catch {}
      window.history.replaceState({}, "", window.location.pathname);
      if (gardé) {
        setPerso(gardé.perso);
        setExps(gardé.exps);
        setFormations(gardé.formations);
        setExtra(gardé.extra);
        setLettre(gardé.lettre);
        setModeleId(idModeleValide(gardé.modeleId));
        setModeleLettreId(gardé.modeleLettreId ? idModeleValide(gardé.modeleLettreId) : null);
        setEcran(ecranGardé);
        setSuiteAction("dossier");
      }
      return;
    }
    const jeton = p.get("commande");
    if (!jeton) return;

    let repris = null;
    try { repris = JSON.parse(sessionStorage.getItem("cvfocus") || "null"); } catch {}

    if (repris) {
      setPerso(repris.perso);
      setExps(repris.exps);
      setFormations(repris.formations);
      setExtra(repris.extra);
      setLettre(repris.lettre);
      setModeleId(idModeleValide(repris.modeleId));
      setModeleLettreId(repris.modeleLettreId ? idModeleValide(repris.modeleLettreId) : null);
    }
    setCommande(jeton);
    setEcran("merci");
    window.history.replaceState({}, "", window.location.pathname);

    if (repris) {
      fetch("api/livraison.php", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ commande: jeton, donnees: repris }),
      })
        .then((r) => r.json())
        .then((j) => {
          if (j.envoye) { setMailLivraison(j.email || ""); setLivre(true); }
        })
        .catch(() => {});
    }
  }, []);

  /* Téléchargement d'un PDF déjà payé, depuis les écrans CV et lettre. */
  async function telecharger(type) {
    setErreur("");
    try {
      await telechargerPDF({
        commande,
        type,
        modele: type === "lettre" ? (modeleLettreId || modeleId) : modeleId,
        donnees: dossier(),
      });
    } catch {
      setErreur("Le téléchargement n'a pas abouti. Vos documents restent disponibles dans votre mail.");
    }
  }

  /* Détaille une formation précise, à la demande, sans toucher au reste. */
  async function detaillerFormation(i) {
    const f = formations[i];
    if (!f?.diplome.trim()) return;
    setCharge("form" + i); setErreur("");
    try {
      const j = await demanderIA("detailFormation", { formation: f, perso });
      if (Array.isArray(j.detail)) majForm(i, "detail", j.detail.join("\n"));
    } catch {
      setErreur("Le détail n'a pas pu être rédigé. Réessayez.");
    }
    setCharge("");
  }

  /* Propose des compétences sans écraser celles que la personne a écrites. */
  async function genererCompetences() {
    setCharge("comp"); setErreur("");
    try {
      const j = await demanderIA("competences", { perso, exps, formations, extra });
      if (Array.isArray(j.competences)) {
        setExtra((x) => {
          const vues = new Set(x.competences.map((c) => c.toLowerCase().trim()));
          const neuves = j.competences.filter((c) => c && !vues.has(c.toLowerCase().trim()));
          return { ...x, competences: [...x.competences, ...neuves] };
        });
      }
    } catch {
      setErreur("Les compétences n'ont pas pu être proposées. Réessayez.");
    }
    setCharge("");
  }

  async function genererCV() {
    setCharge("cv"); setErreur("");
    try {
      const j = await demanderIA("cv", { perso, exps, formations, ancien });
      /* Australie : l'IA renvoie aussi les intitulés traduits en anglais
         (poste recherché, postes occupés, diplômes) : le CV est en anglais. */
      const en = (t) => (modeAus && typeof t === "string" && t.trim() ? t.trim() : "");
      setExps((l) => l.map((e, i) => {
        const t = j.experiences.find((x) => x.index === i);
        if (!t) return e;
        /* CV importé : jamais plus de missions que l'ancien CV n'en donnait
           pour ce poste (l'IA les remet au propre, sans en ajouter). */
        const n = (e.missionsCV || []).filter((x) => String(x).trim()).length;
        const missions = n && Array.isArray(t.missions) ? t.missions.slice(0, Math.min(n, 4)) : t.missions;
        return { ...e, missions, poste: en(t.titre_en) || e.poste };
      }));
      if (Array.isArray(j.formations)) {
        setFormations((l) => l.map((f, i) => {
          const t = j.formations.find((x) => x.index === i);
          if (!t) return f;
          const g = { ...f, diplome: en(t.diplome_en) || f.diplome };
          return !f.detail ? { ...g, detail: t.detail } : g;
        }));
      }
      if (en(j.metier_vise_en)) setPerso((p) => ({ ...p, metierVise: j.metier_vise_en.trim() }));
      setExtra((e) => ({ ...e, accroche: j.accroche || "", competences: j.competences || [], qualites: j.qualites || [] }));
      aller("cv");
    } catch {
      setErreur("La rédaction n'a pas abouti. Vérifiez que chaque poste a un intitulé, puis relancez.");
    }
    setCharge("");
  }

  async function retoucherCV(texte) {
    const demande = (texte ?? retouche).trim();
    if (!demande) return;
    setCharge("retouche"); setErreur("");
    try {
      const j = await demanderIA("retoucheCV", { perso, exps, formations, extra, demande });
      setExps((l) => l.map((e, i) => {
        const t = j.experiences.find((x) => x.index === i);
        return t ? { ...e, missions: t.missions } : e;
      }));
      setExtra((e) => ({
        ...e,
        accroche: j.accroche ?? e.accroche,
        competences: j.competences ?? e.competences,
        qualites: j.qualites ?? e.qualites,
      }));
      setRetouche("");
    } catch {
      setErreur("La retouche n'a pas abouti. Reformulez votre demande en une phrase.");
    }
    setCharge("");
  }

  /* « Raccourcir » : l'IA raccourcit les missions des expériences quand le
     CV ne tient pas sur une page, même avec un texte plus petit. On garde
     le texte d'avant pour pouvoir y revenir. */
  async function raccourcir() {
    setCharge("raccourcir"); setErreur("");
    const avant = exps;
    try {
      const j = await demanderIA("raccourcir", { perso, exps });
      if (!j || !Array.isArray(j.experiences)) throw new Error();
      setAvantRaccourci(avant);
      setExps((l) => l.map((e, i) => {
        const t = j.experiences.find((x) => x.index === i);
        return t && Array.isArray(t.missions) && t.missions.length ? { ...e, missions: t.missions } : e;
      }));
    } catch {
      setErreur("Le raccourcissement n'a pas abouti. Réessayez dans un instant.");
    }
    setCharge("");
  }
  const garderRaccourci = () => setAvantRaccourci(null);
  const revenirTexte = () => {
    if (avantRaccourci) setExps(avantRaccourci);
    setAvantRaccourci(null);
  };

  async function retoucherLaLettre(texte) {
    const demande = (texte ?? retoucheLettre).trim();
    if (!demande || !lettre.texte) return;
    setCharge("retoucheLettre"); setErreur("");
    try {
      const j = await demanderIA("retoucheLettre", { perso, exps, formations, extra, lettre, demande });
      setLettre((l) => ({ ...l, objet: j.objet || l.objet, texte: j.texte || l.texte }));
      setRetoucheLettre("");
    } catch {
      setErreur("La retouche n'a pas abouti. Reformulez votre demande en une phrase.");
    }
    setCharge("");
  }

  async function genererLettre() {
    setCharge("lettre"); setErreur("");
    try {
      const j = await demanderIA("lettre", { perso, exps, formations, extra, lettre });
      /* Le nom de l'employeur retrouvé dans l'annonce remplit la case
         « Destinataire » quand elle était vide. */
      setLettre((l) => ({
        ...l, objet: "", texte: j.texte || "",
        entreprise: l.entreprise || (typeof j.entreprise === "string" ? j.entreprise.trim() : ""),
      }));
    } catch {
      setErreur("La lettre n'a pas pu être écrite. Réessayez dans un instant.");
    }
    setCharge("");
  }
  
  /* Envoi d'une lettre supplémentaire, prise sur le crédit restant. */
  async function envoyerLettreSup() {
    if (!jetonDossier || !lettre.texte) return;
    setCharge("envoi"); setErreur("");
    try {
      const r = await fetch("api/nouvelle-lettre.php", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ jeton: jetonDossier, lettre, modeleLettreId }),
      });
      const j = await r.json();
      if (j.erreur) throw new Error(j.erreur);
      setRestantes(j.restantes);
      setLivre(true);
    } catch {
      setErreur("L'envoi n'a pas abouti. Réessayez dans un instant.");
    }
    setCharge("");
  }

    /* Au retour du paiement, le dossier part des que le compte est
     charge. On attend ce compte : sans lui, le serveur refuserait
     l'envoi. */
  useEffect(() => {
    if (suiteAction === "dossier" && moi) {
      setSuiteAction(null);
      envoyerAbonne();
    }
  }, [suiteAction, moi]);
  /* Compte administrateur : profil de démonstration (vidéos du site) déjà
     rempli à l'arrivée sur « Vos informations ». Seuls les champs vides
     sont remplis (une annonce choisie garde son poste et son pays), et tout
     reste modifiable. Le profil vient du serveur, qui le refuse à tout
     autre compte (api/admin-demo.php). */
  const demoChargee = useRef(false);
  useEffect(() => {
    if (!moi || !moi.admin || ecran !== "infos" || demoChargee.current) return;
    demoChargee.current = true;
    if (perso.prenom || perso.nom) return;   // dossier déjà commencé ou repris
    fetch("api/admin-demo.php")
      .then((r) => (r.ok ? r.json() : null))
      .then((d) => {
        if (!d || !d.perso) return;
        setPerso((p) => {
          const r = { ...p };
          Object.entries(d.perso).forEach(([k, v]) => { if (!r[k] && v) r[k] = v; });
          return r;
        });
        setExps((l) => (Array.isArray(d.exps) && l.every((e) => !e.poste && !e.entreprise) ? d.exps : l));
      })
      .catch(() => {});
  }, [moi, ecran]);

  /* Entonnoir du tableau de bord : CV commencé, puis CV fini. */
  useEffect(() => {
    if (ecran === "infos") stat("cv_commence");
    if (ecran === "cv") stat("cv_fini");
  }, [ecran]);

  /* On regarde au chargement si quelqu'un est déjà connecté. */
  useEffect(() => {
    fetch("api/moi.php")
      .then((r) => r.json())
      .then((j) => {
        if (j && j.connecte) { setMoi(j); return; }
        /* Arrivée depuis le mail de relance (?connexion=1) : on ouvre
           directement la fenêtre de connexion. */
        if (new URLSearchParams(window.location.search).get("connexion")) {
          setSuiteFormule(null); setModeCompte("connexion"); setErreurCompte(""); setVoile(true);
        }
      })
      .catch(() => {});
  }, []);

  /* Ouvre la page de paiement Stripe pour une formule. */
  async function lancerAbonnement(id) {
    
    /* On range le CV et la lettre avant de partir chez Stripe : le
       navigateur va fermer cette page, et tout ce qui est a l'ecran
       disparait avec elle. On les ressortira au retour. */
    try {
      sessionStorage.setItem("cvfocus", JSON.stringify(dossier()));
      sessionStorage.setItem("cvfocus-ecran", ecran);
    } catch {}
    setChargeAbo(id); setErreurCompte("");
    try {
      const r = await fetch("api/abonnement.php", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ formule: id }),
      });
      const j = await r.json();
      if (!j.url) throw new Error(j.erreur || "");
      window.location.href = j.url;
    } catch (e) {
      setErreurCompte(e.message || "Le paiement n'a pas pu s'ouvrir. Réessayez dans un instant.");
      setChargeAbo("");
    }
  }

  /* Création de compte ou connexion, puis enchaînement sur le paiement. */
  async function validerCompte() {
    setErreurCompte("");
    const email = cpt.email.trim();
    if (!email || !cpt.mdp) { setErreurCompte("Remplissez votre mail et votre mot de passe."); return; }

    let url, corps;
    if (modeCompte === "inscription") {
      if (!cpt.prenom.trim() || !cpt.nom.trim()) { setErreurCompte("Indiquez votre prénom et votre nom."); return; }
      if (!mdpValable(cpt.mdp)) {
        setErreurCompte("Au moins 6 caractères, avec une lettre et un chiffre ou un symbole.");
        return;
      }
      url = "api/inscription.php";
      corps = { email, mot_de_passe: cpt.mdp, prenom: cpt.prenom.trim(), nom: cpt.nom.trim() };
    } else {
      url = "api/connexion.php";
      corps = { email, mot_de_passe: cpt.mdp };
    }

    setChargeAbo("compte");
    try {
      const r = await fetch(url, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(corps),
      });
      const j = await r.json();
      if (!r.ok) throw new Error(j.erreur || "Ça n'a pas marché. Réessayez.");

      const q = await fetch("api/moi.php").then((x) => x.json()).catch(() => null);
      if (q && q.connecte) setMoi(q);
      setChargeAbo("");

            if (suiteFormule) {
        const f = suiteFormule;
        setSuiteFormule(null); setVoile(false);
        lancerAbonnement(f);
        return;
      }

      /* La personne s'est connectee au milieu de son travail : on ferme
         la fenetre et on la laisse exactement ou elle etait. Son CV et sa
         lettre restent a l'ecran. On ne la renvoie au debut que si elle
         n'avait encore rien commence. */
      setVoile(false);
            /* On reprend son travail la ou il en etait : le dossier part tout
         seul, ou l'analyse se relance, sans qu'elle ait a recliquer sur
         le meme bouton. */
      const suite = suiteAction;
      setSuiteAction(null);
      if (suite === "dossier") { setTimeout(envoyerAbonne, 0); return; }
      if (suite === "analyse") { setTimeout(lancerAnalyse, 0); return; }
      if (ecran === "accueil") commencer();
    } catch (e) {
      setChargeAbo("");
      setErreurCompte(e.message);
    }
  }

  /* Analyse d'un CV : PDF, photo ou texte collé. Le serveur vérifie
     le quota, fait lire le CV par l'IA, puis décompte l'analyse. */
  async function lancerAnalyse() {
    if (!moi) {
                  setSuiteAction("analyse");
      setSuiteFormule(null); setModeCompte("inscription"); setErreurCompte(""); setVoile(true);
      return;
    }
    if (!fichierCV && texteCV.trim().length < 120) {
      setErreur("Envoyez votre CV en PDF ou en photo, ou collez son texte en entier.");
      return;
    }

    setCharge("analyse"); setErreur(""); setAnalyse(null);
    try {
      const corps = new FormData();
      corps.append("metierVise", metierAnalyse);
      if (fichierCV) corps.append("cv", fichierCV);
      else corps.append("texte", texteCV);

      const r = await fetch("api/analyse-cv.php", { method: "POST", body: corps });
      const j = await r.json();

      if (!r.ok) { setErreur(j.erreur || "Analyse indisponible."); setCharge(""); return; }

      setAnalyse(j);
      const q = await fetch("api/moi.php").then((x) => x.json()).catch(() => null);
      if (q && q.connecte) setMoi(q);
      window.scrollTo({ top: 0 });
    } catch {
      setErreur("L'analyse n'a pas abouti. Réessayez dans un instant.");
    }
    setCharge("");
  }

  async function seDeconnecter() {
    try { await fetch("api/deconnexion.php", { method: "POST" }); } catch {}
    setMoi(null); setMenuCompte(false);
    window.location.href = "/";
  }

  /* Envoi du dossier à un abonné : le serveur vérifie la formule,
     envoie les PDF par mail, puis décompte les documents du mois. */
  async function envoyerAbonne() {
    if (!moi) {
            setSuiteAction("dossier");
      setSuiteFormule(null); setModeCompte("inscription"); setErreurCompte(""); setVoile(true);
      return;
    }
    setCharge("envoiAbo"); setErreur("");
    try {
      const r = await fetch("api/livraison-abonne.php", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ donnees: dossier(), session: paiementRef.current }),
      });
      const j = await r.json();

      /* La personne vient de payer : on ne la renvoie jamais vers les
         formules (elle repaierait). Le message dit que son paiement est
         enregistré ; le bouton reste là pour réessayer. */
      if (r.status === 402 && j.paiement) {
        setErreur(j.erreur || "Votre paiement est bien enregistré. Réessayez l'envoi dans un instant.");
        setCharge("");
        return;
      }
      if (r.status === 402) {
        setErreur((j.erreur || "Il vous faut un abonnement.") + " On vous emmène aux formules.");
        setCharge("");
         
                /* On range le dossier avant de quitter cette page pour les
           tarifs : sans ca, il disparait des qu'on change de page. */
        try {
          sessionStorage.setItem("cvfocus", JSON.stringify(dossier()));
          sessionStorage.setItem("cvfocus-ecran", ecran);
        } catch {}
        setTimeout(() => { window.location.href = "/tarifs.html?fin=1"; }, 2000);
        return;
      }
      if (!r.ok) throw new Error(j.erreur || "");

      paiementRef.current = "";
      setLienPostuler(/^https?:\/\//i.test(j.postuler || "") ? j.postuler : "");
      setMailLivraison(j.email || "");
      setLivre(true);
      setEcran("merci");
      window.scrollTo({ top: 0 });
    } catch (e) {
      setErreur(e.message || "L'envoi n'a pas abouti. Réessayez dans un instant.");
    }
    setCharge("");
  }

  const carriere = moi && moi.formule && moi.formule.id === "carriere";
  const abonne = moi && moi.formule && moi.formule.id !== "gratuit";
  const persoOK = perso.prenom && perso.nom && perso.metierVise;
  const paye = Boolean(commande);
  const mailOK = /.+@.+\..+/.test(mailLivraison);
  const expsOK = exps.every((e) => e.poste);
  /* Vrai des que l'IA a redige au moins une fois : sert a proposer un
     retour au CV plutot qu'une reecriture qui effacerait les corrections. */
  const cvFait = exps.some((e) => e.missions && e.missions.length > 0);
  const suggestions = ["Rends l'accroche plus courte", "Ajoute que je conduis un chariot élévateur", "Simplifie le vocabulaire"];
  const rang = ORDRE.indexOf(ecran);
const STYLE_BTN2 = { width: "100%", marginTop: 12, display: "block", textAlign: "center", textDecoration: "none", padding: "16px 24px", borderRadius: 14, border: "1.5px solid #6E56F8", color: "#B6A6FF", fontWeight: 600, boxSizing: "border-box" };
  return (
    <div className="app">
      <style>{CSS}</style>
      <div className="halo" />
      <div className="wrap">
        <div className="top">
          <div className="logo" onClick={() => aller("accueil")}><span className="pastille">F</span>cvfocus.fr</div>
          <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
            {rang >= 0 && <div className="etiq">{`étape ${rang + 1}/${ORDRE.length}`}</div>}
            {moi ? (
              <div style={{ position: "relative" }}>
                <button
                  onClick={() => setMenuCompte(!menuCompte)}
                  style={{
                    display: "flex", alignItems: "center", gap: 8, cursor: "pointer",
                    background: "var(--surface2)", border: "1px solid var(--bord)",
                    borderRadius: 999, padding: "5px 11px 5px 5px", color: "var(--texte)",
                    fontFamily: "inherit", fontSize: 13, fontWeight: 600,
                  }}
                >
                  <span style={{
                    width: 24, height: 24, borderRadius: "50%", display: "grid", placeItems: "center",
                    background: "linear-gradient(145deg, var(--accent-clair), var(--accent))",
                    color: "#fff", fontSize: 12, fontWeight: 700,
                  }}>
                    {(moi.compte?.prenom || "?").slice(0, 1).toUpperCase()}
                  </span>
                  {moi.formule?.nom || "Compte"}
                </button>

                {menuCompte && (
                  <div
                    className="carte"
                    style={{ position: "absolute", right: 0, top: 40, width: 262, zIndex: 40, marginBottom: 0, textAlign: "left" }}
                  >
                    <div style={{ fontSize: 13.5, fontWeight: 600 }}>
                      {moi.compte?.prenom} {moi.compte?.nom}
                    </div>
                    <div style={{ fontSize: 12.5, color: "var(--doux)", marginBottom: 12, wordBreak: "break-all" }}>
                      {moi.compte?.email}
                    </div>

                    <div style={{ borderTop: "1px solid var(--bord)", paddingTop: 12, marginBottom: 12 }}>
                      <div style={{ fontSize: 12, color: "var(--doux)" }}>Votre formule</div>
                      <div style={{ fontFamily: "'Sora', sans-serif", fontSize: 16, letterSpacing: "-.02em" }}>
                        {moi.formule?.nom}
                      </div>
                      <div style={{ fontSize: 12.5, color: "var(--doux)", marginTop: 6, lineHeight: 1.5 }}>
                        Documents ce mois-ci :{" "}
                        <b style={{ color: "var(--texte)" }}>
                          {moi.restant?.documents === "illimite" ? "sans limite" : moi.restant?.documents}
                        </b>
                        <br />
                        Analyses :{" "}
                        <b style={{ color: "var(--texte)" }}>
                          {moi.restant?.analyses === "illimite" ? "sans limite" : moi.restant?.analyses}
                        </b>
                        {moi.credits > 0 && (
                          <>
                            <br />
                            Candidatures offertes : <b style={{ color: "var(--texte)" }}>{moi.credits}</b>
                          </>
                        )}
                      </div>
                    </div>

                    <a
                      className="btn btn-fantome"
                      href="/tarifs.html"
                      style={{ display: "block", textAlign: "center", textDecoration: "none", fontSize: 14, padding: "10px 14px" }}
                    >
                      {moi.formule?.id === "gratuit" ? "Choisir une formule" : "Changer de formule"}
                    </a>
                    {moi.admin && (
                      <a
                        className="btn btn-primaire"
                        href="/admin.php"
                        style={{ display: "block", textAlign: "center", textDecoration: "none", fontSize: 14, padding: "10px 14px", marginTop: 8 }}
                      >
                        Mon tableau de bord
                      </a>
                    )}
                    <button
                      onClick={seDeconnecter}
                      style={{
                        width: "100%", marginTop: 8, background: "none", border: 0, color: "var(--doux)",
                        fontFamily: "inherit", fontSize: 13, cursor: "pointer", padding: 8,
                      }}
                    >
                      Me déconnecter
                    </button>
                  </div>
                )}
              </div>
            ) : (
              <button
                className="etiq"
                style={{ background: "none", cursor: "pointer", fontFamily: "'JetBrains Mono', monospace" }}
                onClick={() => { setSuiteFormule(null); setModeCompte("connexion"); setErreurCompte(""); setVoile(true); }}
              >
                se connecter
              </button>
            )}
          </div>
        </div>

        {offre && ecran !== "accueil" && (
          <div className="rappel-offre">
            <span className="quoi">Votre candidature pour</span>
            <b>{offre.intitule}</b>
            {(offre.entreprise || offre.lieu) && (
              <span className="boite">
                {[offre.entreprise, offre.lieu].filter(Boolean).join(" · ")}
              </span>
            )}
            {offre.id && (
              <a href={pageOffres + "?offre=" + encodeURIComponent(offre.id)}>Revoir l'annonce →</a>
            )}
          </div>
        )}

        {/* ========================= PAGE DE VENTE ======================== */}
        {ecran === "accueil" && (
          <div className="hero vue">
            <h1>CV et lettre<br /><span className="lueur">parfaits.</span></h1>
            {/* Trouver une offre d'abord, puis les pays, puis le CV seul. */}
            <BlocDepart onCreer={commencer} />
            {erreurCompte && !voile && <div className="alerte" style={{ marginTop: 14 }}>{erreurCompte}</div>}

            {/* Ce que fait CV Focus, étape par étape, au défilement. */}
            <HistoireAccueil />

            {/* Après l'histoire : on repropose de commencer, dans le même ordre. */}
            <BlocDepart onCreer={commencer} />

            {/* Les autres options, plus discrètes. */}
            <div className="accueil-autres">
              <span className="accueil-autres-titre">Vous avez déjà une annonce ou un CV ?</span>
              <div className="accueil-autres-liens">
                <a className="lien-discret" href="/annonce.php">
                  <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M10 14a4 4 0 0 0 5.66 0l3-3a4 4 0 0 0-5.66-5.66l-1 1M14 10a4 4 0 0 0-5.66 0l-3 3a4 4 0 0 0 5.66 5.66l1-1" /></svg>
                  Coller le lien d'une annonce
                </a>
                {/* L'analyse n'est plus proposée gratuitement : le lien ne reste
                    que pour les abonnés, dont la formule comprend des analyses. */}
                {abonne && (
                  <button className="lien-discret" onClick={() => { setAnalyse(null); setErreur(""); setEcran("analyse"); window.scrollTo({ top: 0 }); }}>
                    <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8zM14 3v5h5M9 13h6M9 17h4" /></svg>
                    Analyser mon CV
                  </button>
                )}
              </div>
            </div>

            <div className="section">
              <div className="chapeau">Comment ça marche</div>
              <h2>Quatre champs, c'est tout</h2>
              <div className="marches">
                <div className="marche">
                  <span className="rang">01</span>
                  <div>
                    <b>Vous tapez le minimum</b>
                    <p>Le métier, l'entreprise, les dates. Pas de page blanche, pas de « décrivez vos missions ».</p>
                  </div>
                </div>
                <div className="marche">
                  <span className="rang">02</span>
                  <div>
                    <b>L'IA rédige à votre place</b>
                    <p>Les vraies missions de votre métier, l'accroche, les compétences. Vous corrigez en une phrase si besoin.</p>
                  </div>
                </div>
                <div className="marche">
                  <span className="rang">03</span>
                  <div>
                    <b>Vous recevez votre dossier</b>
                    <p>Votre CV et votre lettre arrivent en PDF dans votre boîte mail, prêts à envoyer.</p>
                  </div>
                </div>
              </div>
            </div>

            <div className="section faq">
              <div className="chapeau">Questions</div>
              <h2>Ce qu'on nous demande souvent</h2>
              <details>
                <summary>Je n'ai jamais fait de CV, ça marche quand même ?</summary>
                <p>C'est fait pour ça. Vous n'avez rien à rédiger : vous tapez le nom du métier que vous avez exercé et on écrit les missions à votre place.</p>
              </details>
              <details>
                <summary>Et si je veux changer de métier ?</summary>
                <p>Indiquez le poste que vous visez au départ. Le CV et la lettre mettent alors en avant ce que votre parcours apporte au nouveau métier, sans jamais dire que vous manquez d'expérience.</p>
              </details>
              <details>
                <summary>Je peux modifier ce que l'IA a écrit ?</summary>
                <p>Autant de fois que vous voulez, en écrivant simplement ce que vous voulez changer, en français normal.</p>
              </details>
              <details>
                <summary>Je peux revenir en arrière pour corriger ?</summary>
                <p>À tout moment. Chaque écran a un bouton Retour, et vous pouvez ajouter une expérience ou une formation oubliée sans perdre le CV déjà rédigé.</p>
              </details>
              <details>
                <summary>Faut-il mettre une photo ?</summary>
                <p>C'est vous qui choisissez, la photo est facultative. En France elle n'est pas obligatoire, et beaucoup de recruteurs préfèrent s'en passer pour éviter les discriminations.</p>
              </details>
              <details>
                <summary>Combien de temps ça prend ?</summary>
                <p>Trois minutes en moyenne. Vous ne remplissez que quatre champs par expérience : le métier, l'entreprise, la ville et les dates.</p>
              </details>
            </div>

            <div style={{ marginTop: 46, paddingTop: 22, borderTop: "1px solid var(--bord)", display: "flex", gap: 16, flexWrap: "wrap", fontSize: 13 }}>
              <a href="legal/mentions-legales.html" style={{ color: "var(--doux)", textDecoration: "none" }}>Mentions légales</a>
              <a href="legal/cgv.html" style={{ color: "var(--doux)", textDecoration: "none" }}>CGV</a>
              <a href="legal/confidentialite.html" style={{ color: "var(--doux)", textDecoration: "none" }}>Confidentialité</a>
            </div>

            <div className="socle">
              <button className="btn btn-primaire" style={{ width: "100%" }} onClick={commencer}>
                Créer mon CV rapidement
              </button>
            </div>
          </div>
        )}

        {/* ========================== REMERCIEMENT ======================= */}
        {ecran === "merci" && (
          <div className="vue" style={{ textAlign: "center", padding: "40px 0 20px" }}>
            <div
              style={{
                width: 74, height: 74, margin: "0 auto 22px", borderRadius: "50%",
                background: "rgba(34,211,166,.12)", border: "1px solid rgba(34,211,166,.4)",
                display: "grid", placeItems: "center", fontSize: 32, color: "var(--menthe)",
              }}
            >
              ✓
            </div>
            <h2 className="t-etape" style={{ marginBottom: 10 }}>
              Merci {perso.prenom} !
            </h2>
            <p className="s-etape" style={{ maxWidth: 460, margin: "0 auto 26px" }}>
              C'est parti. Votre CV et votre lettre de motivation, en PDF et sans filigrane,
              partent maintenant sur <b style={{ color: "var(--texte)" }}>{mailLivraison || "votre adresse mail"}</b>.
              Comptez une minute, et pensez à regarder vos spams si vous ne voyez rien arriver.
            </p>

            {/* Venue d'une annonce : on l'envoie postuler tout de suite, sur
                le site de l'annonce (jamais nommé). */}
            {lienPostuler && (
              <a
                className="btn btn-primaire"
                href={lienPostuler}
                target="_blank"
                rel="noopener"
                style={{ display: "block", width: "100%", textDecoration: "none", textAlign: "center", padding: "18px 24px", fontSize: 16.5, marginBottom: 12, boxSizing: "border-box" }}
              >
                Postuler sur le site de l'annonce ↗
              </a>
            )}
            {lienPostuler && (
              <p className="s-etape" style={{ fontSize: 13.5, maxWidth: 460, margin: "0 auto 22px" }}>
                Joignez-y le CV et la lettre reçus par mail. Le lien est aussi dans le mail.
              </p>
            )}

            <div className="livre" style={{ textAlign: "left", marginBottom: 22 }}>
              <b>Gardez ce mail</b>
              <p>
                Vos deux documents y resteront disponibles autant de temps que vous voudrez. Vous pouvez les
                renvoyer à un employeur directement depuis votre boîte mail.
              </p>
            </div>
          </div>
        )}

        {rang >= 0 && (
          <div className="pas">
            {ORDRE.map((e, i) => (
              <i key={e} className={i === rang ? "on" : i < rang ? "fait" : ""} />
            ))}
          </div>
        )}

        {/* ------------- 0. comment commencer : importer ou à la main ------------- */}
        {ecran === "depart" && (
          <div className="vue">
            <LignePays pays={paysCV} />
            {lectureCV ? (
              <div className="lecture-cv">
                <div className="roue" />
                <h2 className="t-etape" style={{ fontSize: 23 }}>On lit votre CV…</h2>
                <p className="s-etape" style={{ margin: 0 }}>Cela prend environ 20 secondes. Ne fermez pas la page.</p>
              </div>
            ) : (
              <>
                <h2 className="t-etape">Comment voulez-vous commencer ?</h2>
                <p className="s-etape">Vous avez déjà un CV ? Importez-le : on remplit le formulaire à votre place.</p>
                {erreurImport && (
                  <div className="erreur-import">
                    <b>Votre CV n'a pas pu être lu</b>
                    {erreurImport}
                    <div className="rangee" style={{ marginTop: 12 }}>
                      <button className="btn btn-primaire" onClick={() => champImport.current && champImport.current.click()}>Réessayer</button>
                      <button className="btn btn-fantome" onClick={remplirMain}>Remplir à la main</button>
                    </div>
                  </div>
                )}
                <button className="choix-depart fort" onClick={() => champImport.current && champImport.current.click()}>
                  <div className="ic">
                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 15V3M7 8l5-5 5 5" /><path d="M4 15v4a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-4" /></svg>
                  </div>
                  <b>Importer mon ancien CV<span className="gratuit">GRATUIT</span></b>
                  <p>On lit votre CV et on remplit le formulaire à votre place. Vous n'avez plus qu'à vérifier.</p>
                  <div className="formats">PDF · WORD · PHOTO · 10 Mo maximum</div>
                </button>
                <button className="photo-tel" onClick={() => champPhotoTel.current && champPhotoTel.current.click()}>
                  📷 Prendre mon CV papier en photo
                </button>
                <button className="choix-depart" onClick={remplirMain}>
                  <div className="ic">
                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#9B87FF" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 20h9" /><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z" /></svg>
                  </div>
                  <b>Remplir à la main</b>
                  <p>Vous tapez vos informations vous-même. Comptez environ 3 minutes.</p>
                </button>
                <input
                  ref={champImport}
                  type="file"
                  accept=".pdf,.docx,application/pdf,application/vnd.openxmlformats-officedocument.wordprocessingml.document,image/jpeg,image/png,image/heic,image/heif"
                  style={{ display: "none" }}
                  onChange={(ev) => { const f = ev.target.files && ev.target.files[0]; ev.target.value = ""; importerCV(f); }}
                />
                <input
                  ref={champPhotoTel}
                  type="file"
                  accept="image/*"
                  capture="environment"
                  style={{ display: "none" }}
                  onChange={(ev) => { const f = ev.target.files && ev.target.files[0]; ev.target.value = ""; importerCV(f); }}
                />
                <div className="cadenas"><span aria-hidden="true">🔒</span><span>Votre fichier est lu puis supprimé tout de suite. Il n'est jamais gardé.</span></div>
                <div style={{ marginTop: 22 }}>
                  <button className="btn btn-fantome" style={{ padding: "11px 18px" }} onClick={reculer}>Retour</button>
                </div>
              </>
            )}
          </div>
        )}

        {/* ------------------------ 1. informations ---------------------- */}
        {ecran === "infos" && (
          <div className="vue">
            <LignePays pays={paysCV} />
            <h2 className="t-etape">Vos informations</h2>
            <p className="s-etape">
              Ce qui permet au recruteur de vous rappeler.
              {modeSuisse && " En Suisse, le CV indique aussi votre nationalité."}
              {modeLux && " Au Luxembourg, les langues que vous parlez comptent beaucoup : indiquez-les ci-dessous."}
              {modeAus && " Votre CV et votre lettre seront rédigés en anglais. Écrivez le poste recherché en anglais si vous le pouvez."}
            </p>
            {importe && (
              <div className="bandeau-import">
                <span aria-hidden="true">✅</span>
                <div>
                  <b>Votre CV a bien été lu.</b>
                  Vérifiez vos informations et corrigez si besoin. Les cases <span className="orange">en orange</span> n'étaient pas dans votre CV : complétez-les.
                  {importe.photo && " Votre photo a été reprise de votre ancien CV."}
                </div>
              </div>
            )}
            <div className="grille">
              <div className={"champ demi" + manque(perso.prenom)}>
                <label>Prénom</label>
                <input value={perso.prenom} onChange={(e) => setPerso({ ...perso, prenom: e.target.value })} placeholder={aRemplir(perso.prenom, "Kevin")} />
              </div>
              <div className={"champ demi" + manque(perso.nom)}>
                <label>Nom</label>
                <input value={perso.nom} onChange={(e) => setPerso({ ...perso, nom: e.target.value })} placeholder={aRemplir(perso.nom, "Martin")} />
              </div>
              <div className={"champ large" + manque(perso.metierVise)}>
                <label>Poste recherché</label>
                <input value={perso.metierVise} onChange={(e) => setPerso({ ...perso, metierVise: e.target.value })} placeholder={aRemplir(perso.metierVise, "Chauffeur-livreur")} />
              </div>
              <div className={"champ demi" + manque(perso.tel)}>
                <label>Téléphone</label>
                <input value={perso.tel} onChange={(e) => setPerso({ ...perso, tel: e.target.value })} placeholder={aRemplir(perso.tel, "06 12 34 56 78")} />
              </div>
              <div className={"champ demi" + manque(perso.email)}>
                <label>Email</label>
                <input value={perso.email} onChange={(e) => setPerso({ ...perso, email: e.target.value })} placeholder={aRemplir(perso.email, "kevin.martin@email.fr")} />
              </div>
              <div className={"champ large" + manque(perso.ville)}>
                <label>Ville</label>
                <input value={perso.ville} onChange={(e) => setPerso({ ...perso, ville: e.target.value })} placeholder={aRemplir(perso.ville, "Corbeil-Essonnes")} />
              </div>
              {modeSuisse && (
                <>
                  <div className="champ demi">
                    <label>Nationalité</label>
                    <input
                      value={perso.nationalite || ""}
                      onChange={(e) => setPerso({ ...perso, nationalite: e.target.value })}
                      placeholder="Française"
                      list="liste-nationalites"
                    />
                    <datalist id="liste-nationalites">
                      {NATIONALITES.map((n) => <option key={n} value={n} />)}
                    </datalist>
                  </div>
                  <div className="champ demi">
                    <label>Date de naissance <span style={{ fontWeight: 400 }}>(facultatif)</span></label>
                    <input
                      value={perso.naissance || ""}
                      onChange={(e) => setPerso({ ...perso, naissance: e.target.value })}
                      placeholder="jj.mm.aaaa"
                      inputMode="numeric"
                    />
                  </div>
                  <div className="champ large">
                    <label>Permis de travail en Suisse <span style={{ fontWeight: 400 }}>(facultatif)</span></label>
                    <select value={perso.permisTravail || ""} onChange={(e) => setPerso({ ...perso, permisTravail: e.target.value })}>
                      {PERMIS_TRAVAIL.map((p) => <option key={p.v} value={p.v}>{p.nom}</option>)}
                    </select>
                    <span style={{ fontSize: 12.5, color: "var(--doux)", lineHeight: 1.45 }}>
                      Si vous n'avez pas encore de permis, rien ne s'affiche sur le CV : c'est l'employeur qui fait la demande après l'embauche.
                    </span>
                  </div>
                </>
              )}
              {modeLux && (
                <div className="champ large">
                  <label>Vos langues</label>
                  <div className="grille" style={{ gap: 8 }}>
                    {LANGUES_LU.map((nom) => (
                      <div className="champ demi" key={nom}>
                        <select value={niveauLangue(nom)} onChange={(e) => majLangue(nom, e.target.value)}>
                          <option value="">{nom} : non</option>
                          {NIVEAUX_LANGUE.map((n) => <option key={n} value={n}>{nom} : {n.toLowerCase()}</option>)}
                        </select>
                      </div>
                    ))}
                  </div>
                  <span style={{ fontSize: 12.5, color: "var(--doux)", lineHeight: 1.45 }}>
                    Français, allemand, luxembourgeois, anglais : elles apparaîtront en bonne place sur votre CV.
                  </span>
                </div>
              )}
              {modeAus && (
                <div className="champ large">
                  <label>Visa pour l'Australie <span style={{ fontWeight: 400 }}>(facultatif)</span></label>
                  <select value={perso.visa || ""} onChange={(e) => setPerso({ ...perso, visa: e.target.value })}>
                    {VISAS_AU.map((v) => <option key={v.v} value={v.v}>{v.nom}</option>)}
                  </select>
                  <span style={{ fontSize: 12.5, color: "var(--doux)", lineHeight: 1.45 }}>
                    S'il est indiqué, il apparaît sur le CV en anglais. Pas de photo ni de date de naissance : en Australie, on ne les met pas sur un CV.
                  </span>
                </div>
              )}
              {!modeAus && (
              <div className="champ large">
                <label>Photo <span style={{ fontWeight: 400 }}>{modeSuisse ? "(recommandée en Suisse)" : modeLux ? "(courante au Luxembourg)" : "(facultatif)"}</span></label>
                {perso.photo ? (
                  <>
                    <div className="photo-choisie">
                      <img src={perso.photo} alt="" />
                      <div style={{ display: "flex", flexDirection: "column", gap: 8, flex: 1 }}>
                        <button className="btn btn-primaire" style={{ padding: "11px 16px" }} onClick={() => champPhoto.current && champPhoto.current.click()}>
                          Changer la photo
                        </button>
                        <button className="btn btn-fantome" style={{ padding: "10px 16px" }} onClick={() => setPerso({ ...perso, photo: "", photoImportee: false })}>
                          Retirer la photo
                        </button>
                      </div>
                    </div>
                    {perso.photoImportee && (
                      <span className="photo-reprise">Photo reprise de votre ancien CV. Elle ne vous plaît pas ? Touchez « Changer la photo » pour mettre la vôtre.</span>
                    )}
                    <input ref={champPhoto} type="file" accept="image/jpeg,image/png" style={{ display: "none" }} onChange={choisirPhoto} />
                  </>
                ) : (
                  <input type="file" accept="image/jpeg,image/png" onChange={choisirPhoto} />
                )}
                <span style={{ fontSize: 12.5, color: "var(--doux)", lineHeight: 1.45 }}>
                  {modeSuisse
                    ? "Elle sera recadrée en carré. En Suisse, la photo fait partie du CV : la plupart des recruteurs l'attendent. Choisissez une photo récente, de face, sur fond clair."
                    : modeLux
                    ? "Elle sera recadrée en carré. Au Luxembourg, la photo est courante sur un CV, sans être obligatoire."
                    : "Elle sera recadrée en carré. La photo n'est pas obligatoire sur un CV en France, et beaucoup de recruteurs préfèrent s'en passer : n'en mettez une que si vous le souhaitez."}
                </span>
              </div>
              )}
              <div className="champ large">
                <label className="coche">
                  <input type="checkbox" checked={perso.permis} onChange={(e) => setPerso({ ...perso, permis: e.target.checked })} />
                  {modeSuisse ? "J'ai le permis de conduire B" : modeAus ? "J'ai le permis de conduire (driver's licence)" : "J'ai le permis B"}
                </label>
              </div>
              <div className="champ large">
                <label>Centres d'intérêt <span style={{ fontWeight: 400 }}>(facultatif)</span></label>
                <input
                  value={perso.interets || ""}
                  onChange={(e) => setPerso({ ...perso, interets: e.target.value })}
                  placeholder="Football, cuisine, bénévolat"
                />
                <span style={{ fontSize: 12.5, color: "var(--doux)", lineHeight: 1.45 }}>
                  Une ligne courte, en bas de votre CV. Laissez vide si vous ne voulez rien afficher.
                </span>
              </div>
              <div className="champ large">
                <label>Autre chose à dire sur vous ?</label>
                <textarea
                  value={perso.plus}
                  onChange={(e) => setPerso({ ...perso, plus: e.target.value })}
                  placeholder="Permis poids lourd, CACES 3, langues parlées, disponible tout de suite, véhicule personnel…"
                />
                <span style={{ fontSize: 12.5, color: "var(--doux)", lineHeight: 1.45 }}>
                  Écrivez-le comme ça vous vient. On s'occupe de le placer au bon endroit dans votre CV et votre lettre.
                </span>
              </div>
            </div>
            {erreur && <div className="alerte" style={{ marginTop: 16 }}>{erreur}</div>}
            <div className="socle">
              <div className="rangee">
                                {rang >= 0 && (
                  <button className="btn btn-fantome" style={{ flex: "0 0 90px" }} onClick={reculer}>Retour</button>
                )}
                {cvFait && (
                  <button className="btn btn-fantome" onClick={() => aller("cv")}>Revenir au CV</button>
                )}
                <button className="btn btn-primaire" disabled={!persoOK} onClick={() => aller("exps")}>Continuer</button>
              </div>
            </div>
          </div>
        )}

        {/* ------------------------- 2. expériences ---------------------- */}
        {ecran === "exps" && (
          <div className="vue">
            <LignePays pays={paysCV} />
            <h2 className="t-etape">Vos expériences</h2>
            <p className="s-etape">Le métier, l'entreprise, les dates. Les missions, on s'en occupe.</p>
            {importe && (
              <div className="bandeau-import">
                <span aria-hidden="true">✅</span>
                <div>
                  <b>Vos expériences ont été reprises de votre CV.</b>
                  Vérifiez-les et corrigez si besoin. Les missions de votre ancien CV seront remises au propre par l'IA, sans rien inventer.
                  {importe.trouvees > importe.gardees && ` Votre ancien CV comptait ${importe.trouvees} expériences : nous avons gardé les ${importe.gardees} plus récentes, pour que votre CV tienne sur une page.`}
                </div>
              </div>
            )}
            <div className="conseil">
              <span aria-hidden="true">💡</span>
              <span><b>Conseil :</b> mettez surtout vos expériences les plus importantes ou les plus récentes. Votre CV tiendra sur une seule page, c'est ce que préfèrent les recruteurs.</span>
            </div>
            {exps.map((e, i) => (
              <div className="carte" key={i}>
                <div className="carte-tete">
                  <span className="num">POSTE {String(i + 1).padStart(2, "0")}</span>
                  {exps.length > 1 && <button className="sup" onClick={() => setExps(exps.filter((_, j) => j !== i))}>Retirer</button>}
                </div>
                <div className="grille">
                  <div className={"champ large" + manque(e.poste)}>
                    <label>Métier occupé</label>
                    <input value={e.poste} onChange={(ev) => majExp(i, "poste", ev.target.value)} placeholder={aRemplir(e.poste, "Boulanger")} />
                  </div>
                  <div className={"champ demi" + manque(e.entreprise)}>
                    <label>Entreprise</label>
                    <input value={e.entreprise} onChange={(ev) => majExp(i, "entreprise", ev.target.value)} placeholder={aRemplir(e.entreprise, "Fournil Saint-Michel")} />
                  </div>
                  <div className="champ demi">
                    <label>Ville</label>
                    <input value={e.ville} onChange={(ev) => majExp(i, "ville", ev.target.value)} placeholder="Évry" />
                  </div>
                  <div className={"champ demi" + manque(e.debut)}>
                    <label>Début</label>
                    <input type="month" value={e.debut} onChange={(ev) => majExp(i, "debut", ev.target.value)} />
                    {/^\d{4}$/.test(e.debut || "") && <span className="note-date">Votre CV indique {e.debut}. Ajoutez le mois si vous le connaissez.</span>}
                  </div>
                  <div className={"champ demi" + (e.actuel ? "" : manque(e.fin))}>
                    <label>Fin</label>
                    <input type="month" value={e.fin} disabled={e.actuel} onChange={(ev) => majExp(i, "fin", ev.target.value)} />
                    {!e.actuel && /^\d{4}$/.test(e.fin || "") && <span className="note-date">Votre CV indique {e.fin}. Ajoutez le mois si vous le connaissez.</span>}
                  </div>
                  <div className="champ large">
                    <label className="coche">
                      <input type="checkbox" checked={e.actuel} onChange={(ev) => majExp(i, "actuel", ev.target.checked)} />
                      J'y travaille encore
                    </label>
                  </div>
                  {Array.isArray(e.missionsCV) && e.missionsCV.length > 0 && (
                    <div className="champ large">
                      <label>Missions de votre ancien CV</label>
                      <textarea
                        rows={Math.min(7, e.missionsCV.length + 1)}
                        value={e.missionsCV.join("\n")}
                        onChange={(ev) => majExp(i, "missionsCV", ev.target.value.split("\n"))}
                      />
                      <span style={{ fontSize: 12.5, color: "var(--doux)", lineHeight: 1.45 }}>
                        Une mission par ligne. L'IA les remettra au propre, sans rien inventer.
                      </span>
                    </div>
                  )}
                </div>
              </div>
            ))}
            <button className="btn btn-ajout" onClick={() => setExps([...exps, { poste: "", entreprise: "", ville: "", debut: "", fin: "", actuel: false, missions: [] }])}>
              + Ajouter un poste
            </button>

            <h3 style={{ fontFamily: "'Sora', sans-serif", fontSize: 18, letterSpacing: "-.02em", margin: "30px 0 12px" }}>
              Formation <span style={{ color: "var(--doux)", fontWeight: 400, fontSize: 14 }}>facultatif</span>
            </h3>
            {formations.map((f, i) => (
              <div className="carte" key={i}>
                <div className="grille">
                  <div className="champ large">
                    <label>Diplôme ou certificat</label>
                    <input value={f.diplome} onChange={(ev) => majForm(i, "diplome", ev.target.value)} placeholder="CAP Boulanger" />
                  </div>
                  <div className={"champ large" + (f.diplome ? manque(f.ecole) : "")}>
                    <label>Établissement</label>
                    <input value={f.ecole} onChange={(ev) => majForm(i, "ecole", ev.target.value)} placeholder={f.diplome ? aRemplir(f.ecole, "CFA d'Évry") : "CFA d'Évry"} />
                  </div>
                  <div className="champ demi">
                    <label>Ville</label>
                    <input value={f.ville} onChange={(ev) => majForm(i, "ville", ev.target.value)} placeholder="Évry" />
                  </div>
                  <div className={"champ demi" + (f.diplome ? manque(f.annee) : "")}>
                    <label>Année</label>
                    <input value={f.annee} onChange={(ev) => majForm(i, "annee", ev.target.value)} placeholder={f.diplome ? aRemplir(f.annee, "2019") : "2019"} />
                  </div>
                  <div className="champ large">
                    <label>Ce que vous y avez appris <span style={{ fontWeight: 400 }}>(facultatif)</span></label>
                    <textarea
                      rows={4}
                      value={Array.isArray(f.detail) ? f.detail.join("\n") : f.detail}
                      onChange={(ev) => majForm(i, "detail", ev.target.value)}
                      placeholder="Écrivez-le vous-même, ou laissez l'IA détailler votre diplôme"
                    />
                    <button
                      className="btn btn-ajout"
                      style={{ marginTop: 8 }}
                      disabled={!f.diplome.trim() || charge === "form" + i}
                      onClick={() => detaillerFormation(i)}
                    >
                      {charge === "form" + i ? "On détaille votre formation…" : "✨ Détailler cette formation avec l'IA"}
                    </button>
                  </div>
                </div>
              </div>
            ))}
            <button className="btn btn-ajout" onClick={() => setFormations([...formations, { diplome: "", ecole: "", ville: "", annee: "", detail: "" }])}>
              + Ajouter une formation
            </button>

            <h3 style={{ fontFamily: "'Sora', sans-serif", fontSize: 18, letterSpacing: "-.02em", margin: "30px 0 12px" }}>
              Langues <span style={{ color: "var(--doux)", fontWeight: 400, fontSize: 14 }}>facultatif</span>
            </h3>
            {extra.langues.length > 0 && (
              <div className="exemples" style={{ marginBottom: 12 }}>
                {extra.langues.map((l, i) => (
                  <button
                    key={i}
                    className="exemple"
                    onClick={() => setExtra((e) => ({ ...e, langues: e.langues.filter((_, j) => j !== i) }))}
                  >
                    {l.langue} — {l.niveau} ✕
                  </button>
                ))}
              </div>
            )}
            <div className="carte">
              <div className="grille">
                <div className="champ demi">
                  <label>Langue</label>
                  <ChoixLangue
                    valeur={nouvLangue.langue}
                    onChange={(v) => setNouvLangue({ ...nouvLangue, langue: v })}
                    onValider={ajouterLangue}
                  />
                </div>
                <div className="champ demi">
                  <label>Niveau</label>
                  <select value={nouvLangue.niveau} onChange={(e) => setNouvLangue({ ...nouvLangue, niveau: e.target.value })}>
                    {NIVEAUX_CV.map((n) => <option key={n} value={n}>{n}</option>)}
                  </select>
                </div>
                <div className="champ large">
                  <button
                    className="btn btn-ajout"
                    disabled={!nouvLangue.langue.trim()}
                    onClick={ajouterLangue}
                  >
                    + Ajouter cette langue
                  </button>
                </div>
              </div>
            </div>

            {ancien && ancien.competences.length + ancien.qualites.length > 0 && (
              <>
                <h3 style={{ fontFamily: "'Sora', sans-serif", fontSize: 18, letterSpacing: "-.02em", margin: "30px 0 8px" }}>
                  Compétences de votre ancien CV
                </h3>
                <p className="s-etape" style={{ fontSize: 13.5, margin: "0 0 10px" }}>
                  L'IA les reprendra en premier sur votre nouveau CV. Touchez-en une pour la retirer.
                </p>
                <div className="exemples" style={{ marginBottom: 12 }}>
                  {ancien.competences.map((c, k) => (
                    <button key={"c" + k} className="exemple" onClick={() => setAncien((a) => ({ ...a, competences: a.competences.filter((_, j) => j !== k) }))}>{c} ✕</button>
                  ))}
                  {ancien.qualites.map((c, k) => (
                    <button key={"q" + k} className="exemple" onClick={() => setAncien((a) => ({ ...a, qualites: a.qualites.filter((_, j) => j !== k) }))}>{c} ✕</button>
                  ))}
                </div>
              </>
            )}

            {erreur && <div className="alerte" style={{ marginTop: 16 }}>{erreur}</div>}
            {charge === "cv" && <div className="travail"><span className="rond" /> On rédige vos missions…</div>}
            {cvFait && (
              <p className="s-etape" style={{ margin: "18px 0 0", fontSize: 13.5 }}>
                Vous avez déjà un CV rédigé. « Revenir au CV » garde toutes vos corrections.
                « Réécrire » relance l'IA et remplace les missions, l'accroche et les compétences.
              </p>
            )}
            <div className="socle">
              <div className="rangee">
                <button className="btn btn-fantome" style={{ flex: "0 0 90px" }} onClick={reculer}>Retour</button>
                {cvFait && (
                  <button className="btn btn-fantome" onClick={() => aller("cv")}>Revenir au CV</button>
                )}
                <button className="btn btn-primaire" disabled={!expsOK || charge === "cv"} onClick={genererCV}>
                  {charge === "cv" ? "Rédaction…" : cvFait ? "Réécrire mon CV" : "Rédiger mon CV"}
                </button>
              </div>
            </div>
          </div>
        )}

        {/* ----------------------------- 3. CV --------------------------- */}
        {ecran === "cv" && (
          <div className="vue">
            <LignePays pays={paysCV} />
            <h2 className="t-etape">{modeSuisse ? "Votre CV au format suisse" : modeLux ? "Votre CV au format luxembourgeois" : modeAus ? "Votre CV en anglais" : "Votre CV"}</h2>
            <p className="s-etape">
              {modeSuisse
                ? "Ces modèles suivent les usages suisses : photo et nationalité en en-tête."
                : "Faites défiler les modèles, rien ne se perd."}
            </p>
            <div className="puces">
              {modelesProposes.map((x) => {
                const bloque = x.premium && !carriere;
                return (
                  <button
                    key={x.id}
                    className={`puce ${modeleId === x.id ? "on" : ""}`}
                    style={bloque ? { opacity: .65, borderColor: "rgba(230,183,80,.45)", color: "#E6B750" } : undefined}
                    onClick={() => (bloque ? setErreur("Le modèle Prestige est réservé à la formule Carrière.") : setModeleId(x.id))}
                  >
                    {modeSuisse && <Drapeau />}{x.premium && "🔒 "}{x.nom}
                  </button>
                );
              })}
            </div>
            {livre && (
              <div className="livre">
                <b>C'est envoyé</b>
                <p>Votre CV et votre lettre viennent de partir sur {mailLivraison}. Vous pouvez aussi les télécharger ici.</p>
              </div>
            )}
            {avantRaccourci && (
              <div className="choix-raccourci">
                <span>✓ Vos expériences ont été raccourcies, sans rien inventer.{ajuste.trop ? " Le CV dépasse encore un peu : vous pouvez raccourcir encore." : " Votre CV tient sur une page."}</span>
                <button className="btn btn-primaire btn-petit" onClick={garderRaccourci}>Garder</button>
                <button className="btn btn-fantome btn-petit" onClick={revenirTexte}>Revenir à mon texte</button>
              </div>
            )}
            {ajuste.trop && (
              <div className="avert-long" role="status">
                <span>⚠ Votre CV est trop long. Raccourcissez-le en un clic avec Raccourcir.</span>
                <button className="btn btn-primaire btn-petit" disabled={charge === "raccourcir"} onClick={raccourcir}>
                  {charge === "raccourcir" ? "Un instant…" : "✂ Raccourcir"}
                </button>
              </div>
            )}
            <Scene verrouille={!paye} ajuster onAjuste={setAjuste}>
              <Anglais actif={modeAus}>
                <CV m={m} perso={perso} exps={exps} formations={formations.filter((f) => f.diplome)} extra={extra} />
              </Anglais>
            </Scene>
            {!paye && (
              <div className="cadenas">
                <span style={{ fontSize: 22 }}>🔒</span>
                <div>
                  <b>Version de travail</b>
                                    <p>Relisez et corrigez autant que vous voulez. Le filigrane qui barre la page n'est là que pour l'aperçu : il disparaît dès que vous recevez votre candidature.</p>
                </div>
              </div>
            )}

            <div className="retouche">
              <h4>Vos compétences</h4>
              <p>Retirez celles qui ne vous ressemblent pas. Ajoutez les vôtres, ou laissez l'IA vous en proposer.</p>
              <div className="exemples">
                {extra.competences.map((c, i) => (
                  <button
                    key={i}
                    className="exemple"
                    onClick={() => setExtra((e) => ({ ...e, competences: e.competences.filter((_, j) => j !== i) }))}
                  >
                    {c} ✕
                  </button>
                ))}
              </div>
              <div className="duo">
                <div className="champ">
                  <input
                    value={nouvComp}
                    onChange={(e) => setNouvComp(e.target.value)}
                    onKeyDown={(e) => {
                      if (e.key === "Enter" && nouvComp.trim()) {
                        setExtra((x) => ({ ...x, competences: [...x.competences, nouvComp.trim()] }));
                        setNouvComp("");
                      }
                    }}
                    placeholder="Ajouter une compétence"
                  />
                </div>
                <button
                  className="btn btn-fantome"
                  disabled={!nouvComp.trim()}
                  onClick={() => {
                    setExtra((x) => ({ ...x, competences: [...x.competences, nouvComp.trim()] }));
                    setNouvComp("");
                  }}
                >
                  Ajouter
                </button>
              </div>
              <button
                className="btn btn-ajout"
                style={{ marginTop: 10 }}
                disabled={charge === "comp"}
                onClick={genererCompetences}
              >
                {charge === "comp" ? "On cherche vos compétences…" : "✨ Générer des compétences avec l'IA"}
              </button>
            </div>

            <div className="retouche">
              <h4>Corriger en une phrase</h4>
              <p>Dites ce que vous voulez changer, en français normal.</p>
              <div className="exemples">
                {suggestions.map((s) => <button key={s} className="exemple" onClick={() => retoucherCV(s)}>{s}</button>)}
              </div>
              <div className="duo">
                <div className="champ">
                  <input value={retouche} onChange={(e) => setRetouche(e.target.value)} onKeyDown={(e) => e.key === "Enter" && retoucherCV()} placeholder="Ce que vous voulez changer" />
                </div>
                <button className="btn btn-fantome" disabled={charge === "retouche"} onClick={() => retoucherCV()}>
                  {charge === "retouche" ? "…" : "Appliquer"}
                </button>
              </div>
              {charge === "retouche" && <div className="travail"><span className="rond" /> On applique…</div>}
            </div>
            <div className="retouche">
              <h4>Il manque quelque chose ?</h4>
              <p>Retournez ajouter une expérience, une formation ou corriger vos coordonnées. Votre CV vous attend, vous ne perdez rien.</p>
              <div className="duo">
                <button className="btn btn-fantome" style={{ flex: 1 }} onClick={() => aller("infos")}>
                  Mes informations
                </button>
                <button className="btn btn-fantome" style={{ flex: 1 }} onClick={() => aller("exps")}>
                  Expériences et formation
                </button>
              </div>
            </div>

            {erreur && <div className="alerte" style={{ marginTop: 14 }}>{erreur}</div>}
            <div className="socle">
              <div className="rangee">
                <button className="btn btn-fantome" style={{ flex: "0 0 90px" }} onClick={reculer}>Retour</button>
                {paye && (
                  <button className="btn btn-fantome" onClick={() => telecharger("cv")}>Télécharger</button>
                )}
                <button className="btn btn-primaire" onClick={() => aller("lettre")}>Écrire la lettre</button>
              </div>
            </div>
          </div>
        )}

        {/* --------------------------- 4. lettre ------------------------- */}
        {ecran === "lettre" && (
          <div className="vue">
            <LignePays pays={paysCV} />
            <h2 className="t-etape">Votre lettre</h2>
            <p className="s-etape">Elle reprend votre CV et l'adapte à l'entreprise que vous visez.</p>
            <div className="grille">
              <div className="champ demi">
                <label>Entreprise visée</label>
                <input value={lettre.entreprise} onChange={(e) => setLettre({ ...lettre, entreprise: e.target.value })} placeholder="Boulangerie du Marché" />
              </div>
              <div className="champ demi">
                <label>Poste visé</label>
                <input value={lettre.poste} onChange={(e) => setLettre({ ...lettre, poste: e.target.value })} placeholder={perso.metierVise} />
              </div>
              <div className="champ demi">
                <label>Adresse de l'entreprise</label>
                <input value={lettre.adresse} onChange={(e) => setLettre({ ...lettre, adresse: e.target.value })} placeholder="12 rue de la Gare" />
              </div>
              <div className="champ demi">
                <label>Ville de l'entreprise</label>
                <input value={lettre.villeEntreprise} onChange={(e) => setLettre({ ...lettre, villeEntreprise: e.target.value })} placeholder="Évry-Courcouronnes" />
              </div>
              <div className="champ large">
                <button className="btn btn-fantome" style={{ width: "100%" }} disabled={charge === "lettre"} onClick={genererLettre}>
                  {charge === "lettre" ? "Rédaction…" : lettre.texte ? "Réécrire la lettre" : "Écrire la lettre"}
                </button>
              </div>
            </div>
            {charge === "lettre" && <div className="travail" style={{ marginBottom: 14 }}><span className="rond" /> On écrit votre motivation pour ce poste…</div>}
            {erreur && <div className="alerte" style={{ marginTop: 14 }}>{erreur}</div>}
            {lettre.texte && (
              <>
                <p className="s-etape" style={{ margin: "22px 0 10px" }}>
                  Votre lettre reprend le modèle de votre CV. Vous pouvez lui en donner un autre.
                </p>
                <div className="puces">
                  <button className={`puce ${!modeleLettreId ? "on" : ""}`} onClick={() => setModeleLettreId(null)}>
                    Comme mon CV
                  </button>
                  {modelesProposes.map((x) => {
                    const bloque = x.premium && !carriere;
                    return (
                      <button
                        key={x.id}
                        className={`puce ${modeleLettreId === x.id ? "on" : ""}`}
                        style={bloque ? { opacity: .65, borderColor: "rgba(230,183,80,.45)", color: "#E6B750" } : undefined}
                        onClick={() => (bloque ? setErreur("Le modèle Prestige est réservé à la formule Carrière.") : setModeleLettreId(x.id))}
                      >
                        {x.premium && "🔒 "}{x.nom}
                      </button>
                    );
                  })}
                </div>
              </>
            )}
            <div style={{ marginTop: 18 }}>
              <Scene verrouille={!paye}><Anglais actif={modeAus}><Lettre m={mLettre} perso={perso} lettre={lettre} /></Anglais></Scene>
              {!paye && lettre.texte && (
                <div className="cadenas">
                  <span style={{ fontSize: 22 }}>🔒</span>
                  <div>
                    <b>Version de travail</b>
                                        <p>Corrigez ce que vous voulez. Le filigrane qui barre la page n'est là que pour l'aperçu : il disparaît dès que vous recevez votre candidature.</p>
                  </div>
                </div>
              )}
            </div>

            {lettre.texte && (
              <div className="retouche">
                <h4>Corriger en une phrase</h4>
                <p>Dites ce que vous voulez changer, l'objet compris. Le reste de la lettre se remet d'accord tout seul.</p>
                <div className="exemples">
                  {[
                    "C'est pour un stage d'observation pour mes études",
                    "Dis que je suis disponible tout de suite",
                    "Fais plus court et plus simple",
                  ].map((x) => (
                    <button key={x} className="exemple" onClick={() => retoucherLaLettre(x)}>{x}</button>
                  ))}
                </div>
                <div className="duo">
                  <div className="champ">
                    <input
                      value={retoucheLettre}
                      onChange={(e) => setRetoucheLettre(e.target.value)}
                      onKeyDown={(e) => e.key === "Enter" && retoucherLaLettre()}
                      placeholder="Ce que vous voulez changer"
                    />
                  </div>
                  <button className="btn btn-fantome" disabled={charge === "retoucheLettre"} onClick={() => retoucherLaLettre()}>
                    {charge === "retoucheLettre" ? "…" : "Appliquer"}
                  </button>
                </div>
                {charge === "retoucheLettre" && <div className="travail"><span className="rond" /> On réécrit…</div>}
              </div>
            )}
                        {!jetonDossier && (
            <div className="retouche">
              <h4>Il manque quelque chose ?</h4>
              <p>Vous pouvez retourner corriger votre CV, ajouter une expérience ou une formation oubliée. Votre lettre reste enregistrée.</p>
              <div className="duo">
                <button className="btn btn-fantome" style={{ flex: 1 }} onClick={() => aller("cv")}>
                  Mon CV
                </button>
                <button className="btn btn-fantome" style={{ flex: 1 }} onClick={() => aller("exps")}>
                  Expériences et formation
                </button>
              </div>
              </div>
                          )}
            {jetonDossier && restantes < 1 && (
              <div className="verrou" style={{ marginTop: 16 }}>
                <h3>Vous avez utilisé vos lettres</h3>
                <p>
                  Toutes les lettres de votre formule ont été envoyées par mail. Vous les retrouverez
                  dans votre boîte, avec votre CV.
                </p>
                <button
                  className="btn btn-primaire"
                  onClick={() => { window.location.href = window.location.pathname; }}
                >
                  Créer un nouveau dossier
                </button>
              </div>
            )}
            <div className="socle">
              <div className="rangee">
                <button className="btn btn-fantome" style={{ flex: "0 0 90px" }} onClick={reculer}>Retour</button>
                                          {jetonDossier ? (restantes > 0 && (
              <button
                className="btn btn-primaire"
                disabled={!lettre.texte || charge === "envoi" || restantes < 1}
                onClick={envoyerLettreSup}
              >
                {charge === "envoi" ? "Envoi…" : `Recevoir cette lettre (${restantes} restante(s))`}
              </button>
                          )) : paye ? (
              <button className="btn btn-fantome" onClick={() => telecharger("lettre")}>Télécharger</button>
            ) : (
              <button
                className="btn btn-primaire"
                disabled={!lettre.texte || charge === "envoiAbo"}
                onClick={envoyerAbonne}
              >
                {charge === "envoiAbo" ? "Envoi en cours…" : "Envoyer ma candidature"}
              </button>
            )}
              </div>
            </div>
          </div>
        )}

        {/* ======================= ANALYSE DE CV ======================= */}
        {ecran === "analyse" && (
          <div className="vue">
            <h2 className="t-etape">Analyser mon CV</h2>
            <p className="s-etape">
              Collez le texte de votre CV actuel. On le note comme le ferait un logiciel de tri
              de candidatures, et on vous dit ce qui bloque.
            </p>

            {!analyse && (
              <>
                <div className="grille">
                  <div className="champ large">
                    <label>Poste que vous visez</label>
                    <input
                      value={metierAnalyse}
                      onChange={(e) => setMetierAnalyse(e.target.value)}
                      placeholder="Chauffeur-livreur"
                    />
                  </div>
                  <div className="champ large">
                    <label>Votre CV</label>
                    {fichierCV ? (
                      <div className="photo-choisie">
                        <div style={{ flex: 1, fontSize: 14, fontWeight: 600, wordBreak: "break-all" }}>{fichierCV.name}</div>
                        <button className="btn btn-fantome" onClick={() => setFichierCV(null)}>Retirer</button>
                      </div>
                    ) : (
                      <input
                        type="file"
                        accept="application/pdf,image/jpeg,image/png,image/webp"
                        onChange={(ev) => {
                          const f = ev.target.files && ev.target.files[0];
                          ev.target.value = "";
                          if (!f) return;
                          if (f.size > 8 * 1024 * 1024) { setErreur("Le fichier dépasse 8 Mo."); return; }
                          setErreur(""); setFichierCV(f);
                        }}
                      />
                    )}
                    <span style={{ fontSize: 12.5, color: "var(--doux)", lineHeight: 1.45 }}>
                      PDF ou photo, 8 Mo maximum. Une capture d'écran ou une photo prise au téléphone
                      fonctionne, du moment qu'elle est nette. Votre fichier n'est pas conservé.
                    </span>
                  </div>

                  {!fichierCV && (
                  <div className="champ large">
                    <label>Ou collez le texte de votre CV</label>
                    <textarea
                      rows={12}
                      value={texteCV}
                      onChange={(e) => setTexteCV(e.target.value)}
                      placeholder="Ouvrez votre CV, sélectionnez tout, copiez, et collez ici."
                      style={{ minHeight: 220 }}
                    />
                    <span style={{ fontSize: 12.5, color: "var(--doux)", lineHeight: 1.45 }}>
                      Utile si vous n'avez pas le fichier sous la main.
                    </span>
                  </div>
                  )}
                </div>

                {erreur && <div className="alerte" style={{ marginTop: 14 }}>{erreur}</div>}
                {charge === "analyse" && <div className="travail"><span className="rond" /> On lit votre CV…</div>}

                <div className="socle">
                  <div className="rangee">
                    <button className="btn btn-fantome" style={{ flex: "0 0 90px" }} onClick={() => setEcran("accueil")}>Retour</button>
                    <button className="btn btn-primaire" disabled={charge === "analyse"} onClick={lancerAnalyse}>
                      {charge === "analyse" ? "Analyse…" : "Analyser mon CV"}
                    </button>
                  </div>
                </div>
              </>
            )}

            {analyse && (
              <>
                <div className="carte" style={{ textAlign: "center", padding: 26 }}>
                  <div style={{ fontFamily: "'Sora', sans-serif", fontWeight: 800, fontSize: 56, letterSpacing: "-.04em", lineHeight: 1,
                    color: analyse.score >= 75 ? "var(--menthe)" : analyse.score >= 55 ? "#E6B750" : "#FF8A8A" }}>
                    {analyse.score}<span style={{ fontSize: 22, color: "var(--doux)" }}>/100</span>
                  </div>
                  <div style={{ height: 6, borderRadius: 6, background: "rgba(255,255,255,.11)", margin: "16px 0 14px", overflow: "hidden" }}>
                    <div style={{ width: Math.max(0, Math.min(100, analyse.score)) + "%", height: "100%",
                      background: "linear-gradient(90deg, var(--accent), var(--accent-clair))" }} />
                  </div>
                  <p style={{ margin: 0, fontSize: 14.5, color: "#C6C6D0", lineHeight: 1.55 }}>{analyse.verdict}</p>
                </div>

                {Array.isArray(analyse.points_forts) && analyse.points_forts.length > 0 && (
                  <div className="retouche">
                    <h4>Ce qui va bien</h4>
                    <ul style={{ listStyle: "none", margin: 0, padding: 0 }}>
                      {analyse.points_forts.map((x, i) => (
                        <li key={i} style={{ fontSize: 14, lineHeight: 1.5, paddingLeft: 24, position: "relative", marginBottom: 8, color: "#D6D6DE" }}>
                          <span style={{ position: "absolute", left: 0, color: "var(--menthe)", fontWeight: 700 }}>✓</span>{x}
                        </li>
                      ))}
                    </ul>
                  </div>
                )}

                {abonne ? (
                  <>
                    {Array.isArray(analyse.faiblesses) && analyse.faiblesses.length > 0 && (
                      <div className="retouche">
                        <h4>Ce qui bloque, et comment le corriger</h4>
                        {analyse.faiblesses.map((f, i) => (
                          <div key={i} style={{ borderTop: i ? "1px solid var(--bord)" : 0, paddingTop: i ? 12 : 0, marginTop: i ? 12 : 0 }}>
                            <b style={{ fontSize: 14.5, display: "block", marginBottom: 4 }}>{f.titre}</b>
                            <p style={{ margin: "0 0 6px", fontSize: 13.5, color: "var(--doux)", lineHeight: 1.5 }}>{f.pourquoi}</p>
                            <p style={{ margin: 0, fontSize: 13.5, color: "var(--accent-clair)", lineHeight: 1.5 }}>À faire : {f.corriger}</p>
                          </div>
                        ))}
                      </div>
                    )}

                    {Array.isArray(analyse.mots_cles_manquants) && analyse.mots_cles_manquants.length > 0 && (
                      <div className="retouche">
                        <h4>Mots-clés qui manquent</h4>
                        <p>Les logiciels de tri les cherchent pour ce métier. Ajoutez ceux qui correspondent vraiment à ce que vous savez faire.</p>
                        <div className="exemples">
                          {analyse.mots_cles_manquants.map((x, i) => <span className="exemple" key={i}>{x}</span>)}
                        </div>
                      </div>
                    )}
                  </>
                ) : (
                  <div className="verrou" style={{ marginTop: 16 }}>
                    <h3>Le détail est réservé aux abonnés</h3>
                    <p>
                      Vous avez votre score. Le détail de ce qui bloque, les corrections exactes et les
                      mots-clés manquants sont dans les formules payantes.
                    </p>
                    <a className="btn btn-primaire" href="/tarifs.html?fin=1"
                       style={{ display: "block", textAlign: "center", textDecoration: "none" }}>
                      Voir les formules
                    </a>
                  </div>
                )}

                {erreur && <div className="alerte" style={{ marginTop: 14 }}>{erreur}</div>}

                <div className="socle">
                  <div className="rangee">
                    <button className="btn btn-fantome" style={{ flex: "0 0 90px" }} onClick={() => setEcran("accueil")}>Accueil</button>
                    <button className="btn btn-fantome" onClick={() => { setAnalyse(null); setErreur(""); setFichierCV(null); setTexteCV(""); }}>Analyser un autre CV</button>
                    <button className="btn btn-primaire" onClick={() => setEcran("infos")}>Refaire mon CV</button>
                  </div>
                </div>
              </>
            )}
          </div>
        )}

        {/* ==================== fenêtre compte ==================== */}
        {voile && (
          <div
            onClick={(e) => { if (e.target === e.currentTarget) setVoile(false); }}
            style={{
              position: "fixed", inset: 0, background: "rgba(5,5,8,.78)", backdropFilter: "blur(4px)",
              display: "flex", alignItems: "center", justifyContent: "center", padding: 18, zIndex: 50,
            }}
          >
            <div className="carte" style={{ width: "100%", maxWidth: 420, maxHeight: "92vh", overflow: "auto", marginBottom: 0 }}>
              <div className="puces" style={{ marginBottom: 16 }}>
                <button className={`puce ${modeCompte === "inscription" ? "on" : ""}`} onClick={() => { setModeCompte("inscription"); setErreurCompte(""); }}>
                  Créer un compte
                </button>
                <button className={`puce ${modeCompte === "connexion" ? "on" : ""}`} onClick={() => { setModeCompte("connexion"); setErreurCompte(""); }}>
                  Se connecter
                </button>
              </div>

              <h4 style={{ fontFamily: "'Sora', sans-serif", fontSize: 19, margin: "0 0 5px", letterSpacing: "-.03em" }}>
                {modeCompte === "inscription" ? "Créer votre compte" : "Se connecter"}
              </h4>
              <p style={{ color: "var(--doux)", fontSize: 13.5, margin: "0 0 16px", lineHeight: 1.5 }}>
                {suiteFormule
                  ? "Encore une étape, puis vous passez au paiement."
                  : "Votre compte garde vos documents et votre abonnement."}
              </p>

              {erreurCompte && <div className="alerte">{erreurCompte}</div>}

              <div className="grille">
                {modeCompte === "inscription" && (
                  <>
                    <div className="champ demi">
                      <label>Prénom</label>
                      <input value={cpt.prenom} onChange={(e) => setCpt({ ...cpt, prenom: e.target.value })} placeholder="Kevin" />
                    </div>
                    <div className="champ demi">
                      <label>Nom</label>
                      <input value={cpt.nom} onChange={(e) => setCpt({ ...cpt, nom: e.target.value })} placeholder="Martin" />
                    </div>
                  </>
                )}
                <div className="champ large">
                  <label>Adresse mail</label>
                  <input
                    value={cpt.email}
                    onChange={(e) => setCpt({ ...cpt, email: e.target.value })}
                    placeholder="kevin.martin@email.fr"
                    inputMode="email"
                  />
                </div>
                <div className="champ large">
                  <label>Mot de passe</label>
                  <div style={{ position: "relative" }}>
                    <input
                      type={mdpVisible ? "text" : "password"}
                      value={cpt.mdp}
                      onChange={(e) => setCpt({ ...cpt, mdp: e.target.value })}
                      onKeyDown={(e) => e.key === "Enter" && validerCompte()}
                      placeholder={modeCompte === "inscription" ? "6 caractères, lettres et chiffres" : "Votre mot de passe"}
                      style={{ paddingRight: 46 }}
                    />
                    <button
                      type="button"
                      onClick={() => setMdpVisible(!mdpVisible)}
                      aria-label={mdpVisible ? "Cacher le mot de passe" : "Voir le mot de passe"}
                      style={{
                        position: "absolute", right: 6, top: "50%", transform: "translateY(-50%)",
                        background: "none", border: 0, cursor: "pointer", padding: 8,
                        color: mdpVisible ? "var(--accent-clair)" : "var(--doux)", lineHeight: 0,
                      }}
                    >
                      <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round">
                        <path d="M2 12s3.6-6.5 10-6.5S22 12 22 12s-3.6 6.5-10 6.5S2 12 2 12Z" />
                        <circle cx="12" cy="12" r="3" />
                        {!mdpVisible && <path d="M4 20 20 4" />}
                      </svg>
                    </button>
                  </div>
                  {modeCompte === "inscription" && (
                    <span style={{ fontSize: 12, color: "var(--doux)", lineHeight: 1.45 }}>
                      Six caractères au minimum, avec au moins une lettre et un chiffre ou un symbole.
                    </span>
                  )}
                </div>
              </div>

              <button
                className="btn btn-primaire"
                style={{ width: "100%", marginTop: 16 }}
                disabled={chargeAbo === "compte"}
                onClick={validerCompte}
              >
                {chargeAbo === "compte"
                  ? "Un instant…"
                  : modeCompte === "inscription" ? "Créer mon compte" : "Me connecter"}
              </button>
              {modeCompte === "inscription" && (
                <p style={{ fontSize: 12, color: "var(--doux)", lineHeight: 1.45, textAlign: "center", margin: "10px 0 0" }}>
                  Vous pourrez recevoir des mails de CV Focus. Vous pouvez vous désinscrire à tout moment.
                </p>
              )}
              <button
                style={{ width: "100%", marginTop: 10, background: "none", border: 0, color: "var(--doux)", fontFamily: "inherit", fontSize: 13.5, cursor: "pointer", padding: 8 }}
                onClick={() => setVoile(false)}
              >
                Annuler
              </button>
            </div>
          </div>
        )}

        {/* ------------------------ 5. candidatures ---------------------- */}
       
      </div>
    </div>
  );
}

/* ------------------------------------------------------------------ */
ReactDOM.createRoot(document.getElementById("racine")).render(<CVFocus />);