                /* Outil de travail : neutres tièdes choisis, corail réservé à l'action,
                   échelle d'état distincte de la marque, chiffres en mono tabulaire. */
                :root {
                    --bg:#f4f3f2; --surface:#ffffff; --sidebar:#fbfaf9; --surface-2:#f7f6f5;
                    --text:#191817; --muted:#6b6764; --faint:#9a9591;
                    --primary:#ef5f5a; --primary-d:#d64843; --primary-soft:#fdeceb; --accent:#f59a5f;
                    --border:#e8e6e3; --border-strong:#dcd9d5; --danger:#a8324a; --danger-d:#8f2a3e;
                    --green:#2e7d64; --amber:#b8791b; --blue:#4a5b8c; --purple:#7c6bb0;
                    /* Fonds d'état : toujours associés à un mot, jamais porteurs seuls du sens. */
                    --ok-soft:#e4f1eb; --warn-soft:#fbf1de; --late-soft:#f9e9ec; --info-soft:#eaedf6; --neutral-soft:#efedea;
                    --radius:10px; --radius-sm:6px;
                    --mono:ui-monospace,"SF Mono","JetBrains Mono","Cascadia Mono",Menlo,Consolas,monospace;
                    --shadow:0 1px 1px rgba(25,24,23,.03);
                    --shadow-md:0 6px 18px rgba(25,24,23,.07);

                    /* ---- Palette de dataviz (03/09/2026) — une seule, pour tous les graphes ----
                       Les courbes du Dashboard tiraient leurs teintes de trois endroits : une table
                       `COUL` dans le moteur, la charte des piliers SEO, et des hexadécimaux recopiés
                       dans les légendes des gabarits. Résultat : quatre bleus voisins, deux verts,
                       et une comparaison N-1 du même gris que le budget prévu — deux mesures
                       différentes sous la même encre.
                       ⚠️ **Une couleur, un rôle**, jamais un rang : le trafic est bleu partout, les
                       conversions vertes partout, le résultat commercial corail (la marque itroom).
                       Un lecteur qui passe d'un onglet à l'autre ne doit pas relire la légende.
                       ⚠️ Les repères (budget prévu, trajectoire attendue, cible) restent **neutres** :
                       ce sont des consignes, pas des mesures, et leur donner une teinte les ferait
                       lire comme une série de plus.
                       ⚠️ Jeu validé sur fond blanc (bande de clarté, plancher de chroma, séparation
                       daltonienne et contraste ≥ 3:1) — le corail et l'ambre sont les deux seuls à ne
                       pas se séparer assez pour cohabiter **sur un même graphe** : ils n'y sont
                       jamais ensemble (CA d'un côté, position moyenne de l'autre).
                       ⚠️ L'ancien ambre #F59C16 et l'aqua #5EBDB9 de la maquette SEO ont été
                       **re-échelonnés** : le premier tombait à 2,2:1 sur blanc, le second sous le
                       plancher de chroma (il se lisait gris). Une courbe pâle disparaît à
                       l'impression, et cette page part en PDF chez le client. */
                    --viz-bleu:#1a5fb4; --viz-corail:#ef5f5a; --viz-vert:#2e9e5b;
                    --viz-ambre:#b8791b; --viz-violet:#9333ea; --viz-teal:#0f9285;
                    --viz-muet:#6b6764; --viz-grille:#e8e6e3; --viz-encre:#191817;
                    /* Les rôles, seuls noms employés par les gabarits et le moteur : on écrit
                       « le trafic », jamais « le bleu ». Changer la teinte d'un rôle se fait alors
                       ici, sans relire trente `data-couleur`. */
                    --viz-trafic:var(--viz-bleu); --viz-conversion:var(--viz-vert);
                    --viz-argent:var(--viz-corail); --viz-position:var(--viz-ambre);
                    --viz-sea:var(--viz-bleu); --viz-sma:var(--viz-violet); --viz-seo:var(--viz-vert);
                    /* Encre des séries de comparaison (même période l'an dernier) : la teinte de la
                       série, fanée vers le gris tiède. Calculée dans le moteur, ce jalon en est la
                       cible — la mémoriser ici évite qu'un second gris apparaisse ailleurs. */
                    --viz-passe:#9a9591;
                }
                * { box-sizing:border-box; }
                html, body { height:100%; }
                body { margin:0; font-family:"Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; background:var(--bg); color:var(--text); font-size:15px; line-height:1.55; -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility; }
                a { color:var(--primary-d); text-decoration:none; }
                a:hover { text-decoration:underline; }
                h1 { margin:0 0 .2rem; font-size:1.55rem; font-weight:700; letter-spacing:-.02em; }
                h2 { font-size:1.12rem; font-weight:700; letter-spacing:-.01em; }
                h3 { font-size:.95rem; font-weight:700; }

                /* ---- App shell : rail + main ----
                   Un rail de 58 px, comme la maquette : l'outil rend la largeur au travail.
                   Les libellés restent en texte accessible (lus par les lecteurs d'écran, repris
                   en infobulle) ; ⌘K remplace le menu déplié pour tout ce qui ne tient pas ici. */
                .app { display:flex; min-height:100vh; }
                .rail { width:58px; flex-shrink:0; background:var(--sidebar); border-right:1px solid var(--border);
                    display:flex; flex-direction:column; align-items:center; gap:.2rem; padding:.75rem 0; position:sticky; top:0; height:100vh; }
                .rail .brand { display:block; padding:0 0 .7rem; }
                .rail .brand:hover { text-decoration:none; }
                .rail .brand .logo { width:30px; height:30px; display:block; }
                .rail .nav { display:flex; flex-direction:column; align-items:center; gap:.2rem; }
                /* Les groupes du menu deviennent un filet : on garde la respiration, pas les mots. */
                .rail .nav-label { width:22px; height:0; padding:0; margin:.35rem 0; border-top:1px solid var(--border-strong); overflow:hidden; }
                .rail .nav-link, .rail .cmdk-trigger { position:relative; width:34px; height:34px; margin:0; padding:0; gap:0;
                    display:grid; place-items:center; border-radius:8px; color:var(--faint); background:none; border:0; cursor:pointer; }
                .rail .nav-link:hover, .rail .cmdk-trigger:hover { background:var(--neutral-soft); color:var(--text); text-decoration:none; }
                /* Actif = encre pleine : la marque reste réservée à l'action, pas à la position. */
                .rail .nav-link.active { background:var(--text); color:var(--bg); box-shadow:none; }
                .rail .nav-link .ico, .rail .cmdk-trigger .ico { width:18px; height:18px; }
                .rail .cmdk-trigger kbd { display:none; }
                /* Le libellé n'est pas supprimé : il reste lisible pour l'assistance et les tests. */
                .rail .label { position:absolute; width:1px; height:1px; margin:-1px; padding:0; overflow:hidden; clip-path:inset(50%); white-space:nowrap; }
                .rail .nav-badge { position:absolute; top:-3px; right:-3px; margin:0; min-width:15px; height:15px; padding:0 .18rem;
                    display:grid; place-items:center; font-size:.6rem; line-height:1; }
                .rail .spacer { flex:1; }
                /* « Mon espace » vit au pied, avec l'identité : c'est la page perso (score,
                   badges), pas un outil de travail. Elle garde l'habillage d'une entrée de menu —
                   même taille, même actif — pour ne pas se lire comme un élément d'une autre
                   nature ; seule sa place change. */
                .rail .nav-perso { margin-bottom:.15rem; }
                .rail .userbox { flex-direction:column; gap:.3rem; padding:.5rem 0 0; margin:0; border-top:1px solid var(--border); width:38px; }
                .rail .userbox .uinfo { display:none; }
                .avatar { width:32px; height:32px; border-radius:50%; background:var(--primary-soft); color:var(--primary-d); display:flex; align-items:center; justify-content:center; font-weight:700; font-size:.78rem; flex-shrink:0; text-transform:uppercase; }
                .userbox .logout { color:var(--faint); display:inline-flex; padding:.25rem; border-radius:8px; }
                .userbox .logout:hover { color:var(--danger); background:var(--late-soft); text-decoration:none; }
                .userbox .logout svg { width:17px; height:17px; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
                .nav-link .ico { flex-shrink:0; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
                /* Icônes de geste (`_icones.html.twig`) : elles prennent la couleur et la taille de
                   leur bouton — c'est tout ce qu'un émoji ne sait pas faire. `vertical-align`
                   plutôt qu'un flex : elles vivent au fil du texte d'un bouton ou d'une étiquette. */
                .ic { fill:none; stroke:currentColor; stroke-width:1.9; stroke-linecap:round; stroke-linejoin:round;
                    vertical-align:-.16em; flex-shrink:0; }
                .nav-badge { background:var(--danger); color:#fff; font-size:.7rem; font-weight:700; padding:.05rem .42rem; border-radius:999px; }

                /* ---- Rail déplié ----
                   Le rail est l'état par défaut, mais il se déplie : les icônes seules ne
                   suffisent pas à tout le monde ni tout le temps. Choix mémorisé (localStorage). */
                html.nav-open .rail { width:232px; align-items:stretch; padding:.9rem .7rem; }
                html.nav-open .rail .brand { display:flex; align-items:center; gap:.55rem; padding:.1rem .3rem .9rem; font-weight:800; font-size:1.02rem; letter-spacing:-.02em; color:var(--text); }
                html.nav-open .rail .brand-text { display:inline; }
                html.nav-open .rail .nav { align-items:stretch; }
                html.nav-open .rail .nav-link, html.nav-open .rail .cmdk-trigger {
                    width:100%; height:auto; padding:.48rem .6rem; display:flex; align-items:center; justify-content:flex-start; gap:.65rem;
                    font-size:.92rem; font-weight:600; color:var(--muted); text-align:left; }
                html.nav-open .rail .nav-link.active { color:var(--bg); }
                html.nav-open .rail .label { position:static; width:auto; height:auto; margin:0; clip-path:none; overflow:visible; }
                html.nav-open .rail .nav-label { width:auto; height:auto; border-top:0; margin:.7rem 0 .1rem; padding:0 .6rem;
                    font-size:.68rem; text-transform:uppercase; letter-spacing:.08em; color:var(--faint); font-weight:700; }
                html.nav-open .rail .nav-badge { position:static; margin-left:auto; }
                html.nav-open .rail .cmdk-trigger { border:1px solid var(--border); background:var(--surface); margin-bottom:.5rem; }
                html.nav-open .rail .cmdk-trigger kbd { display:inline; margin-left:auto; }
                html.nav-open .rail .userbox { flex-direction:row; align-items:center; width:100%; gap:.6rem; padding:.6rem .3rem 0; }
                html.nav-open .rail .userbox .uinfo { display:flex; flex-direction:column; min-width:0; }
                html.nav-open .rail .userbox .uname { font-weight:600; font-size:.86rem; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
                html.nav-open .rail .userbox .urole { font-size:.72rem; color:var(--faint); }
                html.nav-open .rail .userbox .logout { margin-left:auto; }
                .brand-text { display:none; }
                .rail-toggle { width:34px; height:34px; display:grid; place-items:center; border:0; background:none; cursor:pointer;
                    color:var(--faint); border-radius:8px; padding:0; }
                .rail-toggle:hover { background:var(--neutral-soft); color:var(--text); }
                .rail-toggle svg { width:17px; height:17px; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; transition:transform .18s; }
                html.nav-open .rail-toggle { width:100%; justify-items:start; padding:0 .6rem; }
                html.nav-open .rail-toggle svg { transform:rotate(180deg); }

                .main { flex:1; min-width:0; }
                .content { max-width:none; margin:0; padding:2rem 2.4rem 3rem; width:100%; }

                /* ---- Palette de commandes (⌘K) ---- */
                /* Le clavier plutôt que le menu : n'importe quel client ou écran en trois frappes. */
                .cmdk-trigger { display:flex; align-items:center; gap:.5rem; width:calc(100% - 1.1rem); margin:0 .55rem .7rem; padding:.42rem .55rem; border:1px solid var(--border); background:var(--surface); border-radius:8px; cursor:pointer; color:var(--faint); font-family:inherit; font-size:.84rem; font-weight:600; text-align:left; }
                .cmdk-trigger:hover { border-color:var(--border-strong); color:var(--muted); }
                .cmdk-trigger .ico { width:15px; height:15px; flex-shrink:0; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
                .cmdk-trigger .label { flex:1; }
                .cmdk-trigger kbd, .cmdk-foot kbd { font-family:var(--mono); font-size:.68rem; color:var(--muted); background:var(--neutral-soft); border:1px solid var(--border); border-radius:4px; padding:.02rem .28rem; white-space:nowrap; }

                dialog#cmdk { width:620px; max-width:calc(100vw - 2rem); padding:0; border:1px solid var(--border-strong); border-radius:12px; background:var(--surface); box-shadow:var(--shadow-md); color:var(--text); margin-top:12vh; }
                dialog#cmdk::backdrop { background:rgba(25,24,23,.32); }
                .cmdk-in { display:flex; align-items:center; gap:.6rem; padding:.7rem .9rem; border-bottom:1px solid var(--border); }
                .cmdk-in .ico { width:17px; height:17px; flex-shrink:0; fill:none; stroke:var(--faint); stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
                .cmdk-in input { flex:1; min-width:0; width:auto; border:none; padding:0; font-size:1rem; font-weight:500; background:none; }
                .cmdk-in input:focus { outline:none; box-shadow:none; }
                .cmdk-scope { display:inline-flex; align-items:center; gap:.3rem; flex-shrink:0; background:var(--primary-soft); color:var(--primary-d); font-size:.8rem; font-weight:700; padding:.16rem .55rem; border-radius:6px; white-space:nowrap; }
                .cmdk-list { max-height:min(52vh,440px); overflow-y:auto; padding:.35rem; }
                .cmdk-grp { font-size:.67rem; text-transform:uppercase; letter-spacing:.08em; color:var(--faint); font-weight:700; padding:.55rem .6rem .25rem; }
                .cmdk-row { display:flex; align-items:center; gap:.6rem; padding:.44rem .6rem; border-radius:7px; cursor:pointer; font-size:.9rem; }
                .cmdk-row[aria-selected="true"] { background:var(--primary-soft); color:var(--primary-d); }
                .cmdk-row .t { flex:1; min-width:0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; font-weight:600; }
                .cmdk-row .h { font-size:.76rem; color:var(--faint); flex-shrink:0; }
                .cmdk-row[aria-selected="true"] .h { color:var(--primary-d); opacity:.75; }
                .cmdk-row .st { width:.5rem; height:.5rem; border-radius:2px; flex-shrink:0; background:var(--border-strong); }
                .cmdk-row .st.on { background:var(--green); }
                .cmdk-empty, .cmdk-more { padding:.8rem .6rem; font-size:.85rem; color:var(--muted); text-align:center; }
                .cmdk-foot { display:flex; gap:.9rem; flex-wrap:wrap; padding:.5rem .9rem; border-top:1px solid var(--border); font-size:.75rem; color:var(--faint); }

                /* ---- Bascule perf ROAS / CPA (comptes mixtes e-commerce + lead) ---- */
                .perf-seg { display:inline-flex; gap:.15rem; background:var(--neutral-soft); border:1px solid var(--border); border-radius:8px; padding:.15rem; vertical-align:middle; }
                .perf-seg button { border:0; background:none; cursor:pointer; padding:.25rem .6rem; border-radius:6px; font-size:.74rem; font-weight:700; color:var(--muted); font-family:inherit; }
                .perf-seg button:hover { color:var(--text); }
                .perf-seg button.active { background:#fff; color:var(--primary-d); box-shadow:var(--shadow); }
                .perf-root.perf-roas .pv-cpa { display:none; }
                .perf-root.perf-cpa .pv-roas { display:none; }

                /* ---- Cards ---- */
                .card { background:var(--surface); border:1px solid var(--border); border-radius:var(--radius); padding:1.2rem 1.3rem; box-shadow:var(--shadow); }
                .stack { display:flex; flex-direction:column; gap:1rem; }

                /* ---- KPI cards ---- */
                .kpi-row { display:grid; grid-template-columns:repeat(auto-fit,minmax(190px,1fr)); gap:1.1rem; }
                .kpi-row .card { padding:1.15rem 1.25rem; }
                                .kpi { display:flex; align-items:center; gap:1rem; }
                .kpi .ic { width:42px; height:42px; border-radius:9px; display:flex; align-items:center; justify-content:center; flex-shrink:0; }
                .kpi .ic svg { width:23px; height:23px; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
                .kpi .ic.blue { background:var(--info-soft); color:var(--blue); } .kpi .ic.red { background:var(--late-soft); color:var(--danger); }
                .kpi .ic.amber { background:var(--warn-soft); color:var(--amber); } .kpi .ic.green { background:var(--ok-soft); color:var(--green); }
                .kpi .num { font-family:var(--mono); font-size:1.45rem; font-weight:600; letter-spacing:-.03em; line-height:1.1; }
                .kpi .lbl { font-size:.78rem; color:var(--muted); }

                /* ---- Hero (score) ---- */
                .hero { background:linear-gradient(120deg,#f06a6a 0%,#fa8a5b 100%); color:#fff; border:none; box-shadow:var(--shadow-md); }
                .hero h1, .hero h2, .hero .stat .num { color:#fff; }
                .hero .stat .lbl { color:rgba(255,255,255,.88); }
                .hero .muted { color:rgba(255,255,255,.9); }
                .hero .level-pill { background:rgba(255,255,255,.24); color:#fff; }
                .hero .progress { background:rgba(255,255,255,.28); }
                .hero .progress > span { background:#fff; box-shadow:none; }
                .hero .badge-chip { background:rgba(255,255,255,.2); border-color:rgba(255,255,255,.32); color:#fff; }
                .hero .badge-chip.locked { background:rgba(255,255,255,.08); border-color:rgba(255,255,255,.2); color:rgba(255,255,255,.7); }

                /* ---- Tables ---- */
                /* Les chiffres se comparent en colonne : chasse fixe pour que l'œil descende sans relire. */
                table { width:100%; border-collapse:collapse; font-variant-numeric:tabular-nums; }
                .num, .countdown, .stat .num { font-family:var(--mono); font-variant-numeric:tabular-nums; }
                th, td { text-align:left; padding:.62rem .55rem; border-bottom:1px solid var(--border); vertical-align:middle; }
                thead th { font-size:.7rem; text-transform:uppercase; color:var(--muted); letter-spacing:.05em; font-weight:700; border-bottom:1px solid var(--border-strong); }
                tbody tr { transition:background .12s; }
                tbody tr:hover { background:var(--surface-2); }
                tbody tr:last-child td { border-bottom:none; }

                /* ⚠️ `hidden` doit gagner sur nos règles d'affichage. L'attribut ne pose qu'un
                   `display:none` de la feuille du navigateur, que **n'importe quelle** classe à
                   nous (`.btn { display:inline-flex }`, `.pill { display:inline-block }`) écrase :
                   un élément marqué `hidden` restait visible — les quatre états d'un même geste
                   s'affichaient côte à côte (« Rédiger », « Voir », « en cours », « Échec »). */
                [hidden] { display:none !important; }

                /* ---- Buttons ---- */
                .btn { display:inline-flex; align-items:center; gap:.4rem; border:1px solid transparent; cursor:pointer; padding:.55rem 1.05rem; border-radius:var(--radius-sm); font-size:.9rem; font-weight:600; background:var(--primary); color:#fff; box-shadow:none; transition:background .14s; line-height:1.2; }
                .btn:hover { background:var(--primary-d); text-decoration:none; box-shadow:none; }
                .btn:disabled { opacity:.5; cursor:not-allowed; box-shadow:none; }
                .btn-sm { padding:.34rem .65rem; font-size:.8rem; }
                .btn-secondary { background:var(--neutral-soft); color:#45413e; border-color:var(--border); box-shadow:none; }
                .btn-secondary:hover { background:#e7e4e0; box-shadow:none; transform:none; }
                .btn-danger { background:var(--danger); color:#fff; box-shadow:none; }
                .btn-danger:hover { background:var(--danger-d); }
                /* Geste de retrait (reporter, écarter, déplier) : présent sans peser. */
                .btn-ghost { background:none; border-color:transparent; color:var(--muted); }
                .btn-ghost:hover { background:var(--surface-2); color:var(--text); }

                /* ---- Forms ---- */
                .form-row { margin-bottom:1.05rem; display:flex; flex-direction:column; gap:.4rem; }
                .form-row label { font-weight:600; font-size:.88rem; }
                input, select, textarea, .form-row input, .form-row select, .form-row textarea {
                    padding:.6rem .75rem; border:1px solid var(--border-strong); border-radius:var(--radius-sm); font-size:.95rem; width:100%; font-family:inherit; color:var(--text); background:#fff; transition:border-color .12s, box-shadow .12s;
                }
                input::placeholder, textarea::placeholder { color:var(--faint); }
                input:focus, select:focus, textarea:focus { outline:none; border-color:var(--primary); box-shadow:0 0 0 3px var(--primary-soft); }
                /* ---- Cases à cocher et boutons radio ----
                   ⚠️ La règle ci-dessus décrit un **champ de saisie**, mais le sélecteur `input`
                   attrape aussi les cases à cocher : sans reprise, une case hérite de
                   `width:100%`, d'un rembourrage et d'une bordure — elle s'affiche en grand
                   rectangle blanc à côté de son libellé (vécu sur « Relever ce client par l'API
                   SE Ranking »). On ne se contente pas de lui rendre son rendu natif : celui-ci
                   est celui du système d'exploitation, donc bleu Windows ou gris macOS au milieu
                   d'une interface qui n'est ni l'un ni l'autre. On la redessine.
                   ⚠️ **Coché = encre pleine**, jamais la marque : même règle que `.chip.on` et
                   que l'entrée active du rail — le corail reste réservé à l'action. Le focus,
                   lui, est une interaction : il garde l'anneau `--primary-soft` des champs.
                   ⚠️ La taille étant **fixée ici**, un `style="width:auto"` posé sur une case
                   par un gabarit l'écraserait (styles en ligne prioritaires) et la ferait
                   disparaître, `appearance:none` la privant de dimension propre. Ces rattrapages
                   ont donc tous été retirés — ne pas en réintroduire. */
                input[type="checkbox"], input[type="radio"] {
                    appearance:none; -webkit-appearance:none;
                    width:17px; height:17px; flex:none; margin:0; padding:0; position:relative;
                    vertical-align:-3px; cursor:pointer;
                    border:1.5px solid var(--border-strong); border-radius:5px;
                    background:var(--surface); box-shadow:none;
                    transition:background-color .12s, border-color .12s, box-shadow .12s;
                }
                input[type="radio"] { border-radius:50%; }
                input[type="checkbox"]:hover:not(:disabled), input[type="radio"]:hover:not(:disabled) { border-color:var(--muted); }
                input[type="checkbox"]:checked, input[type="radio"]:checked { background:var(--text); border-color:var(--text); }
                /* La coche est dessinée (deux bords en équerre) plutôt qu'écrite : un caractère
                   « ✓ » dépendrait de la police et se décalerait d'un navigateur à l'autre. */
                input[type="checkbox"]:checked::after {
                    content:''; position:absolute; left:4.5px; top:1px; width:4px; height:8px;
                    border:solid var(--surface); border-width:0 2px 2px 0; transform:rotate(43deg);
                }
                input[type="radio"]:checked::after { content:''; position:absolute; inset:3.5px; border-radius:50%; background:var(--surface); }
                input[type="checkbox"]:focus-visible, input[type="radio"]:focus-visible {
                    outline:none; border-color:var(--primary); box-shadow:0 0 0 3px var(--primary-soft);
                }
                input[type="checkbox"]:disabled, input[type="radio"]:disabled { opacity:.45; cursor:not-allowed; }
                /* Un libellé qui accompagne une case se clique, donc il s'annonce cliquable. */
                label:has(> input[type="checkbox"]), label:has(> input[type="radio"]) { cursor:pointer; }
                textarea { resize:vertical; line-height:1.5; }

                /* ---- Liste de suggestions (autocomplétion d'une zone géographique) ----
                   Posée en absolu sous le champ : dans le flux, elle repousserait le reste du
                   formulaire à chaque frappe. Les entrées sont de vrais <button> — donc
                   atteignables au clavier, contrairement à une liste de <div> cliquables. */
                .geo-sugg { position:absolute; z-index:30; top:100%; left:0; right:0; margin-top:.15rem; max-height:16rem; overflow:auto; background:#fff; border:1px solid var(--border-strong); border-radius:var(--radius-sm); box-shadow:0 8px 24px rgba(15,23,42,.12); }
                .geo-sugg-item { display:block; width:100%; text-align:left; padding:.5rem .7rem; border:0; background:none; font-size:.88rem; color:var(--text); cursor:pointer; font-family:inherit; }
                .geo-sugg-item:hover, .geo-sugg-item:focus { background:var(--surface-2); outline:none; }

                /* ---- Grille de formulaire ----
                   Les champs se rangent d'eux-mêmes et ne descendent jamais sous une largeur
                   lisible : 190 px, en dessous desquels une liste déroulante tronque ses
                   options et un libellé passe à la ligne au milieu d'un mot.
                   ⚠️ À utiliser dès qu'un formulaire porte **plus de trois champs** : le
                   `.presta-row` qui servait à ça est calibré pour trois (des `flex` fixés au
                   rang), et les suivants s'y écrasaient à la largeur de leur contenu. */
                .form-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(190px,1fr)); gap:0 .9rem; }
                .form-grid > .form-row { margin-bottom:.95rem; }
                .form-grid > .wide { grid-column:1 / -1; }
                /* Intertitre de section dans un formulaire long : dire à quoi sert le bloc
                   qui suit coûte une ligne et évite de relire dix libellés. */
                .form-legend { font-family:var(--mono); font-size:.62rem; text-transform:uppercase; letter-spacing:.1em;
                    color:var(--faint); font-weight:600; margin:.2rem 0 .7rem; padding-top:.7rem; border-top:1px solid var(--border); }
                .form-note { border-left:3px solid var(--green); background:var(--ok-soft); border-radius:var(--radius-sm);
                    padding:.6rem .8rem; margin:.2rem 0 .3rem; }
                .form-note .closed { display:block; font-size:.79rem; color:var(--green); font-weight:600; }
                .form-note .retour { display:block; font-size:.82rem; color:var(--muted); margin-top:.15rem; white-space:pre-line; }

                /* ---- Fenêtres modales ----
                   Une fenêtre qui déborde de l'écran cache ses champs du bas **et** son bouton
                   de validation, sans rien laisser paraître : elle défile en son sein. */
                dialog.modal { border:none; border-radius:var(--radius); padding:0; width:calc(100% - 2rem);
                    max-height:88vh; overflow:auto; box-shadow:0 20px 50px rgba(25,24,23,.28); background:var(--surface); color:var(--text); }
                dialog.modal::backdrop { background:rgba(25,24,23,.32); }
                dialog.modal > form { padding:1.4rem 1.5rem; }
                dialog.modal h3 { margin:0 0 .3rem; }
                .modal-actions { display:flex; justify-content:flex-end; gap:.5rem; margin-top:.4rem;
                    position:sticky; bottom:-1.4rem; padding:.9rem 0 0; background:var(--surface); }

                /* ---- Bulle de survol d'une barre de Gantt (roadmap) ----
                   Positionnée en `fixed` par le JS : rendue dans le Gantt, elle serait coupée
                   par son `overflow:hidden`. `pointer-events:none` — elle informe, elle n'agit pas. */
                .gantt-tip { display:none; position:fixed; z-index:60; width:320px; max-width:calc(100vw - 2rem);
                    background:var(--surface); border:1px solid var(--border-strong); border-radius:var(--radius);
                    box-shadow:var(--shadow-md); padding:.7rem .8rem; pointer-events:none; color:var(--text); }
                .gantt-tip .tip-head { font-weight:700; font-size:.86rem; letter-spacing:-.01em; }
                .gantt-tip .tip-sub { font-size:.72rem; color:var(--muted); margin-top:.1rem; }
                .gantt-tip .tip-avancement { font-size:.8rem; margin-top:.5rem; }
                .gantt-tip .tip-retard { color:var(--danger); font-weight:600; }
                .gantt-tip .tip-chantier { font-size:.76rem; color:var(--muted); margin-top:.25rem; }
                .gantt-tip .tip-chantier b { font-family:var(--mono); color:var(--text); }
                .gantt-tip .tip-label { font-family:var(--mono); font-size:.6rem; text-transform:uppercase; letter-spacing:.1em;
                    color:var(--faint); margin:.6rem 0 .25rem; padding-top:.5rem; border-top:1px solid var(--border); }
                .gantt-tip .tip-tache { display:flex; gap:.5rem; font-size:.78rem; padding:.1rem 0; }
                .gantt-tip .tip-tache span { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
                .gantt-tip .tip-tache em { font-family:var(--mono); font-size:.7rem; font-style:normal; color:var(--muted); white-space:nowrap; }
                .gantt-tip .tip-tache em.late { color:var(--danger); font-weight:700; }
                .gantt-tip .tip-tache em.warn { color:var(--amber); font-weight:700; }

                /* ---- Flashes / alerts ---- */
                .flash { padding:.85rem 1rem; border-radius:var(--radius-sm); margin-bottom:1rem; font-size:.92rem; border:1px solid transparent; display:flex; gap:.5rem; }
                .flash::before { font-weight:700; }
                .flash-success { background:var(--ok-soft); color:#226b53; border-color:#c5e3d7; }
                .flash-success::before { content:"✓"; }
                .flash-danger { background:var(--late-soft); color:#94293e; border-color:#eec6ce; }
                .flash-danger::before { content:"!"; }
                .alert-error { background:var(--late-soft); color:#94293e; padding:.75rem 1rem; border-radius:var(--radius-sm); margin-bottom:1rem; border:1px solid #eec6ce; }
                ul.form-errors { margin:.2rem 0; padding-left:1.1rem; color:var(--danger); font-size:.85rem; }

                /* ---- Pills / badges / dots (pastel multicolore type Asana) ---- */
                .pill { display:inline-block; padding:.18rem .6rem; border-radius:999px; font-size:.74rem; font-weight:700; letter-spacing:.01em; }
                .pill-admin { background:var(--warn-soft); color:#8f5e15; }
                .pill-user { background:var(--info-soft); color:var(--blue); }
                .pill-actif, .pill-ok { background:var(--ok-soft); color:#226b53; }
                .pill-archive { background:var(--neutral-soft); color:var(--muted); }
                .pill-warn { background:var(--warn-soft); color:#8f5e15; }
                .pill-late { background:var(--late-soft); color:var(--danger); }
                .badge { background:var(--neutral-soft); color:var(--muted); padding:.15rem .5rem; border-radius:999px; font-size:.72rem; font-weight:600; }
                .dot { display:inline-block; width:.55rem; height:.55rem; border-radius:2px; margin-right:.45rem; vertical-align:middle; }
                .dot-ok { background:var(--green); } .dot-warn { background:var(--amber); } .dot-late { background:var(--danger); }

                /* ---- Charts ---- */
                .chart-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(300px,1fr)); gap:1.25rem; }
                .card-head { display:flex; align-items:center; gap:.7rem; margin-bottom:1.1rem; }
                .card-head .hicon { width:36px; height:36px; border-radius:10px; display:flex; align-items:center; justify-content:center; background:var(--primary-soft); color:var(--primary); flex-shrink:0; }
                .card-head .hicon svg { width:19px; height:19px; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
                .card-head h2 { margin:0; line-height:1.15; }
                .card-head .sub { font-size:.8rem; color:var(--muted); margin-top:.1rem; }
                .donut-wrap { display:flex; align-items:center; gap:1.4rem; flex-wrap:wrap; }
                .donut { width:138px; height:138px; flex-shrink:0; }
                .chart-legend { display:flex; flex-direction:column; gap:.55rem; }
                .chart-legend > div { display:flex; align-items:center; gap:.5rem; font-size:.9rem; }
                .chart-legend strong { margin-left:auto; font-variant-numeric:tabular-nums; }
                .barchart { display:flex; flex-direction:column; gap:.75rem; }
                .bar-row { display:grid; grid-template-columns:120px 1fr auto; gap:.7rem; align-items:center; }
                .bar-row .name { font-size:.88rem; font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
                .bar-track { height:8px; background:var(--border); border-radius:999px; overflow:hidden; }
                .bar-fill { height:100%; background:linear-gradient(90deg,var(--primary),var(--accent)); border-radius:999px; min-width:3px; }
                .bar-row .val { font-size:.8rem; color:var(--muted); font-variant-numeric:tabular-nums; white-space:nowrap; }
                .vbars { display:flex; align-items:stretch; gap:.55rem; height:172px; }
                .vbar { flex:1; display:flex; flex-direction:column; align-items:center; min-width:0; }
                .vbar .v { font-size:.72rem; font-weight:700; color:var(--text); }
                .vbar-track { flex:1; width:100%; max-width:38px; display:flex; align-items:flex-end; }
                .vbar-fill { width:100%; background:linear-gradient(180deg,#f8a06a,var(--primary)); border-radius:7px 7px 0 0; min-height:4px; }
                .vbar .l { font-size:.68rem; color:var(--muted); margin-top:.35rem; white-space:nowrap; }
                .presta-list { display:flex; flex-direction:column; gap:.5rem; margin-bottom:.6rem; }
                .presta-row { display:flex; gap:.5rem; align-items:center; }
                .presta-row > div { margin:0; }
                .presta-row > div:nth-child(1) { flex:2; }
                .presta-row > div:nth-child(2) { flex:0 0 90px; }
                .presta-row > div:nth-child(3) { flex:1.3; }
                .presta-row input, .presta-row select { width:100%; }
                .presta-remove, .coll-remove { flex:0 0 auto; line-height:1; padding:.4rem .6rem; }
                .copil-row { display:flex; gap:.5rem; align-items:center; }
                .copil-row > div { margin:0; }
                .copil-row > div:nth-child(1) { flex:0 0 170px; }
                .copil-row > div:nth-child(2) { flex:1; }
                .copil-row input { width:100%; }

                /* ---- Stats ---- */
                .stat { display:flex; flex-direction:column; gap:.1rem; }
                .stat .num { font-size:1.55rem; font-weight:750; letter-spacing:-.02em; }
                .stat .lbl { font-size:.74rem; color:var(--muted); text-transform:uppercase; letter-spacing:.04em; }

                /* ---- Gamification ---- */
                .progress { height:9px; background:#edeef1; border-radius:999px; overflow:hidden; }
                .progress > span { display:block; height:100%; background:linear-gradient(90deg,var(--primary),var(--accent)); border-radius:999px; transition:width .3s; }
                .score-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(100px,1fr)); gap:1rem; }
                .level-pill { display:inline-flex; align-items:center; gap:.35rem; background:var(--primary-soft); color:var(--primary-d); font-weight:700; padding:.25rem .7rem; border-radius:999px; font-size:.82rem; }
                .badges { display:flex; flex-wrap:wrap; gap:.45rem; }
                .badge-chip { display:inline-flex; align-items:center; gap:.35rem; padding:.32rem .65rem; border-radius:999px; font-size:.78rem; font-weight:600; border:1px solid var(--border); color:var(--muted); }
                .badge-chip.earned { background:#fdecec; border-color:#f6c0c0; color:#c0414c; }
                .badge-chip.locked { background:#f7f8fa; color:var(--faint); }
                .countdown { font-weight:700; font-size:.86rem; }
                .countdown.ok { color:var(--green); } .countdown.warn { color:#b3741f; } .countdown.late { color:var(--danger); }

                /* ---- Tabs ---- */
                .tabs { display:flex; gap:.4rem; margin-bottom:1.25rem; flex-wrap:wrap; border-bottom:1px solid var(--border); padding-bottom:.1rem; }
                .tab { padding:.4rem .85rem; border-radius:8px; font-size:.86rem; font-weight:600; color:var(--muted); }
                .tab:hover { background:#f3f3f5; text-decoration:none; color:var(--text); }
                .tab.active { background:var(--primary-soft); color:var(--primary-d); }

                /* ---- Grammaire d'écran (maquette) : en-tête, filtres, file d'actions ----
                   Le verbe avant le chiffre, la sévérité en filet plutôt qu'en pastille,
                   les chiffres en mono. Composants du système, pas d'un seul écran. */
                .pane-head { display:flex; align-items:baseline; gap:.65rem; flex-wrap:wrap; margin-bottom:1rem; }
                .pane-head h1, .pane-head h2 { margin:0; letter-spacing:-.02em; }
                .pane-head .meta { margin-left:auto; font-family:var(--mono); font-size:.73rem; color:var(--faint); font-variant-numeric:tabular-nums; }
                /* Salutation du jour : présente, jamais encombrante — le titre de l'écran
                   reste la date et le compte d'actions. */
                .salut { margin:0; font-size:1.15rem; font-weight:700; color:var(--primary-d); }

                .chips { display:flex; gap:.32rem; flex-wrap:wrap; align-items:center; }
                .chips .chip { font-family:inherit; font-size:.76rem; font-weight:600; padding:.2rem .68rem; border-radius:999px;
                    border:1px solid var(--border); color:var(--muted); background:var(--surface); cursor:pointer; }
                .chips .chip:hover { border-color:var(--border-strong); color:var(--text); }
                /* Actif = encre pleine : un filtre posé se voit d'un coup d'œil, sans couleur de marque. */
                .chips .chip.on { background:var(--text); border-color:var(--text); color:var(--bg); }
                .chips .chip .n { margin-left:.28rem; font-family:var(--mono); font-variant-numeric:tabular-nums; opacity:.7; }

                /* ---- Sélecteur de période (écrans de performance) ----
                   Les raccourcis sont des LIENS, pas des boutons de formulaire : un écran de perf se
                   partage et se met en favori, et une période choisie doit tenir dans l'URL. La plage
                   libre reste un formulaire GET, elle a deux valeurs à porter. */
                .periode { display:flex; flex-direction:column; gap:.4rem; align-items:flex-end; }
                .periode .raccourcis { display:flex; gap:.3rem; align-items:center; }
                .periode .raccourcis select { font-size:.76rem; padding:.16rem .4rem; max-width:200px; }
                .periode .ou { color:var(--faint); font-size:.72rem; }
                .periode .ligne { display:flex; gap:.4rem; align-items:center; flex-wrap:wrap; justify-content:flex-end; }
                .periode .plage { display:flex; gap:.3rem; align-items:center; }
                .periode .plage input[type="date"] { width:auto; font-size:.76rem; padding:.16rem .4rem; font-family:var(--mono); }
                .periode .plage .sep { color:var(--faint); font-size:.76rem; }
                /* Le libellé résolu, toujours affiché : un raccourci ne dit pas ses bornes, et
                   « 3 derniers mois » n'a pas le même sens le 1er du mois et le 30. */
                .periode .resume { font-family:var(--mono); font-size:.72rem; color:var(--faint); font-variant-numeric:tabular-nums; }
                /* Ce que les chiffres ne disent pas d'eux-mêmes : prorata, lacune de relève, période
                   antérieure au grain quotidien. Jamais discret — cette page s'imprime et s'envoie. */
                .periode-avis { margin:0 0 1.1rem; padding:.55rem .8rem; border:1px solid #f6d9b0; border-left:3px solid var(--warn, #d98324);
                    border-radius:var(--radius); background:#fdf6ec; color:#6b4a17; font-size:.79rem; }
                .periode-avis ul { margin:.25rem 0 0; padding-left:1.1rem; }
                .periode-avis li + li { margin-top:.2rem; }
                @media print { .periode .chips, .periode .plage { display:none; } }

                .flux { display:grid; gap:.5rem; }
                .act { display:grid; grid-template-columns:3px 1fr; border:1px solid var(--border); border-radius:var(--radius); background:var(--surface); overflow:hidden; transition:opacity .3s; }
                .act .sev { background:var(--faint); }
                .act.late .sev { background:var(--danger); }
                .act.warn .sev { background:var(--amber); }
                .act.ok .sev { background:var(--green); }
                .act.done { opacity:.4; }
                .act-body { padding:.75rem .9rem; display:grid; gap:.45rem; }
                .act-top { display:flex; align-items:center; gap:.5rem; flex-wrap:wrap; }
                .act-top .client { font-weight:700; letter-spacing:-.01em; }
                .act-top .when { margin-left:auto; font-family:var(--mono); font-size:.72rem; color:var(--faint); }
                .act p { margin:0; font-size:.89rem; }
                .act .why { font-size:.8rem; color:var(--muted); }
                .act-actions { display:flex; gap:.38rem; flex-wrap:wrap; padding-top:.1rem; }

                /* Ligne de tâche : ce qu'elle est à gauche, ce qui la qualifie **et** ce qu'on
                   peut en faire à droite.
                   ⚠️ Deux colonnes, plus trois : « Terminer » occupait une colonne à gauche, seul
                   de son côté, pendant que modifier / rythme / archiver s'alignaient à droite —
                   on cherchait un geste des deux côtés de la ligne. Tous les gestes sont
                   désormais dans le même groupe (`.ractions`), sous les étiquettes. */
                .task { display:grid; grid-template-columns:minmax(0,1fr) auto; gap:.75rem; align-items:start;
                    padding:.7rem .9rem; border:1px solid var(--border); border-radius:var(--radius); background:var(--surface); }
                .task .rcol { display:flex; flex-direction:column; align-items:flex-end; gap:.35rem; }
                /* ⚠️ Les gestes portent **leur verbe**, pas seulement une icône : une icône nue ne
                   se lit qu'au survol — donc jamais au clavier ni en tactile — et « archiver » se
                   confondait avec « terminer ». Ils s'enroulent sur les écrans étroits plutôt que
                   de pousser la ligne. */
                .task .ractions, .ractions { display:flex; align-items:center; gap:.35rem; flex-wrap:wrap;
                                             justify-content:flex-end; flex:none; }
                .task .t { display:block; font-weight:600; }
                .task .s { display:block; font-size:.79rem; color:var(--muted); margin-top:.1rem; }
                .task .thread { display:block; font-size:.8rem; color:var(--primary-d); margin-top:.2rem; }
                .task .rmeta { display:flex; align-items:center; gap:.45rem; flex-wrap:wrap; justify-content:flex-end; }
                /* Une tâche close dit quand, par qui, et ce qui a été fait : le retour de
                   clôture se lit sur la ligne, sans déplier le fil. */
                .task .closed { display:block; font-size:.79rem; color:var(--green); font-weight:600; margin-top:.2rem; }
                .task .retour { display:block; font-size:.82rem; color:var(--muted); margin-top:.1rem; white-space:pre-line; }
                /* ⚠️ La ligne close était à 50 % d'opacité : le retour qu'on vient d'y mettre y
                   serait illisible. Elle se signale par son titre barré et grisé, pas en
                   s'effaçant à moitié. */
                .task.done { opacity:.78; }
                .task.done .t { text-decoration:line-through; color:var(--muted); }
                /* L'échéance se lit en chasse fixe : « J-2 » et « Retard 9 j » s'alignent d'une ligne à l'autre. */
                .due { font-family:var(--mono); font-variant-numeric:tabular-nums; font-size:.75rem; font-weight:700; color:var(--muted); white-space:nowrap; }
                .due.warn { color:var(--amber); }
                .due.late { color:var(--danger); }
                .due.ok { color:var(--green); }

                .tag { font-family:var(--mono); font-size:.66rem; text-transform:uppercase; letter-spacing:.08em; font-weight:600;
                    padding:.1rem .42rem; border-radius:var(--radius-sm); background:var(--neutral-soft); color:var(--muted); white-space:nowrap; }
                .tag.sea { background:var(--info-soft); color:var(--blue); }
                .tag.seo { background:var(--ok-soft); color:var(--green); }
                .tag.rel { background:var(--primary-soft); color:var(--primary-d); }
                .tag.bud { background:var(--warn-soft); color:var(--amber); }
                .tag.late { background:var(--late-soft); color:var(--danger); }
                /* `.tag-switch` : une étiquette qui **agit** (bascule agence ⇄ client sur une
                   ligne de tâche). Même graphie que les étiquettes voisines — on ne fabrique pas
                   un bouton de plus là où l'information et le geste sont la même chose. */
                .tag-switch { border:0; cursor:pointer; font-family:var(--mono); }
                .tag-switch.muted { background:var(--surface-2, #f7f8fa); color:var(--faint); }
                .tag-switch:hover { filter:brightness(.94); }
                /* L'étiquette porte les deux icônes ; on affiche celle de l'état courant
                   (`.rel` = côté client). Le mot reste écrit : l'icône ne le remplace pas. */
                .tag-switch .ic-client, .tag-switch.rel .ic-agence { display:none; }
                .tag-switch.rel .ic-client { display:inline; }

                /* ---- Tableau dense (maquette) : en-têtes en mono, chiffres alignés ----
                   `.grid` s'ajoute au tableau ; `.n` marque une cellule de chiffres, `.cl`
                   la colonne qui identifie la ligne. L'état se lit en carré + mot (`.dotstate`),
                   jamais par la seule couleur. */
                table.grid { font-size:.85rem; }
                table.grid th { font-family:var(--mono); font-size:.62rem; text-transform:uppercase; letter-spacing:.1em;
                    color:var(--faint); font-weight:600; padding:0 .62rem .45rem 0; border-bottom:1px solid var(--border); white-space:nowrap; }
                table.grid td { padding:.55rem .62rem .55rem 0; border-bottom:1px solid var(--border); vertical-align:middle; }
                table.grid td.n, table.grid th.n { font-family:var(--mono); font-variant-numeric:tabular-nums; text-align:right; }
                table.grid .cl { font-weight:600; letter-spacing:-.01em; }
                table.grid tbody tr:hover td { background:var(--surface-2); }

                /* ---- `.table-card` : un tableau dense posé DANS une carte ----
                   La carte ne peut pas porter le rembourrage (les filets de lignes doivent
                   filer d'un bord à l'autre), ce sont donc les cellules des extrémités qui
                   rendent la gouttière — sinon la première colonne colle au bord de la carte.
                   ⚠️ `overflow-x:auto` et jamais `hidden` : un tableau plus large que la carte
                   doit défiler, pas être coupé (des colonnes disparaissaient en silence).
                   Les chiffres s'alignent à droite : sur une colonne de montants, c'est
                   l'unité (€ / % / ×) qui doit se superposer d'une ligne à l'autre. */
                .card.table-card { padding:0; overflow-x:auto; }
                .card.table-card table.grid { min-width:100%; }
                .card.table-card table.grid th:first-child,
                .card.table-card table.grid td:first-child { padding-left:1.3rem; }
                .card.table-card table.grid th:last-child,
                .card.table-card table.grid td:last-child { padding-right:1.3rem; }
                .card.table-card table.grid thead th { padding-top:1rem; }
                .card.table-card table.grid tbody tr:last-child td { border-bottom:none; }
                /* ---- Colonnes triables (`<th data-tri="clé" data-type="num|str">`) ----
                   Le mécanisme vivait en double dans le gabarit du Suivi mensuel des budgets ; il est
                   remonté ici pour que toute liste dense s'en serve sans le recopier.
                   ⚠️ L'affordance doit se voir **avant** le clic : sans le chevron au repos, une
                   colonne triable est indiscernable d'un en-tête ordinaire, et personne ne l'essaie. */
                th[data-tri] { cursor:pointer; user-select:none; white-space:nowrap; }
                th[data-tri]:hover { color:var(--text); }
                th[data-tri]::after { content:'⇅'; opacity:.35; font-size:.85em; margin-left:.3rem; }
                th[data-tri][aria-sort="ascending"]::after { content:'↑'; opacity:1; }
                th[data-tri][aria-sort="descending"]::after { content:'↓'; opacity:1; }
                th[data-tri]:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
                .dotstate { display:inline-flex; align-items:center; gap:.4rem; white-space:nowrap; }
                .dotstate i { width:7px; height:7px; border-radius:2px; display:block; background:var(--faint); flex-shrink:0; }
                .dotstate.late i { background:var(--danger); }
                .dotstate.warn i { background:var(--amber); }
                .dotstate.ok i { background:var(--green); }
                .dotstate span { font-size:.8rem; color:var(--muted); }
                .dotstate.late span { color:var(--danger); font-weight:600; }

                /* ---- Pilotage SEO — portage de la maquette ----
                   Portage **fidèle** de la maquette de pilotage SEO (6 indicateurs, 3 piliers).

                   ⚠️ Tout est scopé sous `.seo-page` et rien d'autre : la maquette réemploie des
                   noms de classe que le système de design utilise déjà pour autre chose (`.card`,
                   `.chip`, `.meta`, `.note`, `.name`, `.value`, `.status`, `.dot`, `.legend`).
                   Sans ce scope, ouvrir l'onglet SEO repeindrait des composants d'autres écrans —
                   et le seul symptôme serait visuel, donc invisible aux tests.

                   ⚠️ Les trois piliers gardent leur identité (technique / pertinence / popularité)
                   mais **puisent désormais dans la palette de dataviz** : la charte de la maquette
                   (#145A8E / #5EBDB9 / #F59C16) posait un quatrième bleu à côté de ceux du reporting,
                   un aqua qui se lisait gris et un ambre à 2,2:1 sur blanc. Le vert/ambre/framboise
                   des statuts reste celui de l'outil : un état ne se lit pas dans une couleur
                   d'identité, et ce bloc s'imprime dans le même PDF que les onglets SEA et SMA. */
                .seo-page { --sb:var(--viz-bleu); --st:var(--viz-teal); --so:var(--viz-ambre);
                    --sgrid:var(--viz-grille); --sbase:#c9c8c1; --sink:#0b0b0b; --sink2:#52514e; --smut:#898781;
                    --sgood:#0ca30c; --swarn:#e08a00; --scrit:#d03b3b;
                    --sring:rgba(11,11,11,.10); --sshadow:0 1px 2px rgba(11,11,11,.05),0 4px 16px rgba(11,11,11,.04); }

                /* ===================================================================
                   `.rep` — grammaire du **reporting client**, lu à l'écran et imprimé en PDF.

                   Le gabarit était habillé en attributs `style=` de bout en bout : chaque bloc
                   inventait sa bordure, son rembourrage et sa taille de titre, donc rien ne
                   s'alignait d'une carte à l'autre et l'écran se lisait comme un empilement de
                   chiffres. La grammaire vit ici, une fois, et reprend l'ADN de `.seo-page`
                   (libellés en mono majuscules, tuiles à ombre douce, intertitre à filet coloré)
                   pour que les quatre sous-onglets se lisent comme **un seul** tableau de bord.

                   ⚠️ Le scope `.rep` n'est pas décoratif : la maquette réemploie des noms que le
                   système utilise déjà (`.tile`, `.panel`, `.sec`, `.legend`). Sans lui, ouvrir
                   ce module repeindrait des composants d'autres écrans — et le seul symptôme
                   serait visuel, donc invisible aux tests.
                   =================================================================== */
                .rep { --rgrid:#e6e6e0; --rring:rgba(11,11,11,.10);
                       --rshadow:0 1px 2px rgba(11,11,11,.05),0 4px 16px rgba(11,11,11,.04);
                       display:flex; flex-direction:column; gap:1.15rem; }

                /* Intertitre de section : le filet coloré porte le canal, le `.tag` la période.
                   ⚠️ La période est **toujours** dite ici : un rapport imprimé mêle un mois
                   calendaire et des fenêtres glissantes, et rien dans les chiffres ne les sépare. */
                .rep .sec { display:flex; align-items:center; gap:.6rem; flex-wrap:wrap; margin:.9rem 2px -.15rem; }
                .rep .sec:first-child { margin-top:0; }
                .rep .sec .bar { width:5px; height:20px; border-radius:3px; display:block; background:var(--muted); }
                .rep .sec h2 { margin:0; font-size:1rem; letter-spacing:-.01em; }
                .rep .sec .tag { font-family:var(--mono); font-size:.66rem; text-transform:uppercase; letter-spacing:.08em;
                                 color:var(--muted); border:1px solid var(--border); border-radius:999px; padding:.1rem .5rem; }
                .rep .sec .sec-note { font-size:.76rem; color:var(--muted); margin-left:auto; }

                /* Panneau : la carte du module. Un en-tête, un corps, rien d'autre. */
                .rep .panel { background:var(--surface); border:1px solid var(--rring); border-radius:14px;
                              box-shadow:var(--rshadow); padding:1.05rem 1.15rem; }
                .rep .panel > h3 { margin:0 0 .15rem; font-size:.95rem; letter-spacing:-.01em; }
                .rep .panel > .desc { margin:0 0 .85rem; font-size:.78rem; color:var(--muted); }
                .rep .panel > .desc:last-child { margin-bottom:0; }
                /* D'où vient un bloc (cf. `pilotage/_source.html.twig`). Discret à l'écran,
                   **conservé à l'impression** : c'est dans le PDF envoyé au client que la question
                   « ça sort d'où ? » se pose, et une pastille masquée là n'aurait plus d'objet. */
                /* Barre de consommation budgétaire : le trait vertical marque le **rythme
                   attendu** (budget des mois clos). Sans lui, un pourcentage ne se compare à rien. */
                .progress.conso-bar { position:relative; height:14px; overflow:visible; }
                .progress.conso-bar .repere { position:absolute; top:-3px; bottom:-3px; width:2px;
                                              background:var(--text); border-radius:2px; z-index:2; }
                .progress.conso-bar .repere::after { content:''; position:absolute; top:-4px; left:-2px;
                                                     border-left:3px solid transparent; border-right:3px solid transparent;
                                                     border-top:4px solid var(--text); }
                .rep .panel > h3 { display:flex; align-items:baseline; gap:.5rem; flex-wrap:wrap; }
                /* Les trois cartes de texte généré portent un <h2> dans une barre flex : la
                   pastille s'y range à la suite du titre, sans le pousser hors de sa ligne. */
                .rep .panel h2 .srcs { margin-left:.5rem; }
                .srcs { margin-left:auto; display:inline-flex; align-items:baseline; gap:.25rem; flex-wrap:wrap; }
                .srcs-l { font-size:.62rem; text-transform:uppercase; letter-spacing:.06em; color:var(--faint); font-weight:600; }
                .src { font-size:.66rem; font-weight:600; color:var(--muted); background:var(--bg-soft, #f4f4f2);
                       border:1px solid var(--border); border-radius:999px; padding:.02rem .4rem;
                       white-space:nowrap; cursor:help; }
                /* Périmètre d'un panneau (cf. `pilotage/_perimetre.html.twig`) : sur quelles dates
                   portent ses chiffres. Même forme que la pastille « Source », posée dans le même
                   titre — « ça sort d'où ? » et « ça porte sur quand ? » se lisent ensemble.
                   ⚠️ `.hors` marque ce qui **ne suit pas** le sélecteur de période (snapshot
                   glissant, fait mensuel) : sans cette distinction, la pastille dirait la même
                   chose dans les deux cas, donc rien. Conservée à l'impression, comme la source. */
                .tag-per { font-family:var(--mono); font-size:.62rem; text-transform:uppercase; letter-spacing:.07em;
                           color:var(--muted); border:1px solid var(--border); border-radius:999px;
                           padding:.05rem .45rem; white-space:nowrap; font-weight:600; }
                .tag-per.hors { color:var(--amber); border-color:var(--warn-soft); background:var(--warn-soft); cursor:help; }
                .rep .panel > h3 .tag-per { margin-left:.15rem; }
                .rep .panel .ext { float:right; font-size:.72rem; color:var(--primary); text-decoration:none; }
                .rep .panel .ext:hover { text-decoration:underline; }

                /* Rangée de tuiles : jamais moins de deux colonnes — une tuile pleine largeur
                   perdrait la lecture en rangée, qui est ce qui fait le tableau de bord. */
                .rep .tiles { display:grid; grid-template-columns:repeat(auto-fit,minmax(158px,1fr)); gap:.7rem; }
                .rep .tile { background:var(--surface); border:1px solid var(--rring); border-radius:12px;
                             box-shadow:var(--rshadow); padding:.7rem .8rem .6rem; display:flex; flex-direction:column; gap:.15rem;
                             min-width:0; }
                .rep .tile .k { font-family:var(--mono); font-size:.63rem; text-transform:uppercase; letter-spacing:.09em;
                                color:var(--muted); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
                .rep .tile .v { font-size:1.5rem; font-weight:700; letter-spacing:-.03em; line-height:1.1;
                                font-variant-numeric:tabular-nums; }
                .rep .tile .v small { font-size:.78rem; font-weight:600; color:var(--muted); margin-left:.15rem; letter-spacing:0; }
                /* ⚠️ « — » n'est pas un zéro : une valeur absente se rend en muet et en petit,
                   sinon elle se lit comme une contre-performance mesurée. */
                .rep .tile .v.nd { font-size:1rem; font-weight:600; color:var(--muted); letter-spacing:0; }
                .rep .tile .f { font-size:.72rem; color:var(--muted); margin-top:.05rem; }
                .rep .tile .spark { width:100%; height:26px; margin-top:.25rem; display:block; }
                .rep .tile.wide { grid-column:span 2; }

                /* Delta : la flèche dit le sens réel, la couleur dit si c'est une bonne nouvelle —
                   les deux divergent sur une métrique de coût, d'où `.inv` posé par le gabarit. */
                .rep .d { font-size:.72rem; font-weight:700; white-space:nowrap; font-variant-numeric:tabular-nums; }
                .rep .d.up { color:var(--green); }
                .rep .d.down { color:var(--danger); }
                .rep .d.flat { color:var(--muted); }
                .rep .d .lbl { font-weight:500; opacity:.6; }

                /* Grilles de contenu : deux colonnes qui retombent en une seule sous 900 px. */
                .rep .cols { display:grid; grid-template-columns:1fr 1fr; gap:1.15rem; align-items:start; }
                .rep .cols.c13 { grid-template-columns:1fr 2fr; }
                /* Un graphe par KPI : trois côte à côte pour un compte e-commerce, deux pour un
                   compte lead (pas de CA). `auto-fit` évite un troisième créneau vide quand il n'y a
                   que deux graphes — une colonne fantôme les tasserait sur la gauche. */
                .rep .cols.ckpi { grid-template-columns:repeat(auto-fit, minmax(240px, 1fr)); }
                @media (max-width:900px) { .rep .cols, .rep .cols.c13 { grid-template-columns:1fr; } }

                /* Graphes : le SVG est tracé par le moteur inline (aucune librairie, le PDF sort net). */
                .rep .chart { width:100%; height:190px; display:block; }
                .rep .chart.tall { height:230px; }
                /* ⚠️ Les barres horizontales prennent **la hauteur de leur contenu**, pas les 190 px
                   des courbes : leur `viewBox` grandit avec le nombre de lignes (huit canaux font
                   ~340 unités). Enfermé dans 190 px, le graphe était mis en boîte par
                   `preserveAspectRatio` — donc réduit à ~60 %, libellés illisibles, avec du vide de
                   chaque côté : « mal cadré et minuscule à côté du tableau ». `height:auto` laisse le
                   ratio du viewBox décider. */
                .rep .chart[data-rank] { height:auto; }
                .rep .legend { display:flex; gap:.9rem; flex-wrap:wrap; margin-top:.5rem; font-size:.73rem; color:var(--muted); }
                .rep .legend .lg { display:inline-flex; align-items:center; gap:.35rem; }
                .rep .legend .sw { width:11px; height:11px; border-radius:3px; display:inline-block; }
                .rep .legend .sw.line { width:16px; height:3px; border-radius:2px; }
                /* ⚠️ Le **style du trait** fait partie de l'identité d'une série : la comparaison
                   est tiretée, le repère pointillé. Une pastille pleine dans la légende oblige à
                   deviner laquelle est laquelle — c'était le défaut des courbes N-1, tracées en
                   tirets et annoncées par un aplat. Le motif est un masque : la couleur reste celle
                   posée par le gabarit, une seule source de vérité. */
                .legend .sw.line.dash,
                .legend .sw.line.dot { border-radius:0; }
                .legend .sw.line.dash { -webkit-mask-image:repeating-linear-gradient(90deg,#000 0 5px,transparent 5px 9px);
                                        mask-image:repeating-linear-gradient(90deg,#000 0 5px,transparent 5px 9px); }
                .legend .sw.line.dot { -webkit-mask-image:repeating-linear-gradient(90deg,#000 0 2px,transparent 2px 5px);
                                       mask-image:repeating-linear-gradient(90deg,#000 0 2px,transparent 2px 5px); }
                /* ⚠️ État vide **explicite** : un graphe absent sans phrase se lit comme une panne.
                   Le moteur ne trace rien sous deux points — c'est ici qu'on dit pourquoi. */
                .rep .chart-empty { display:flex; align-items:center; justify-content:center; text-align:center;
                                    min-height:120px; padding:1rem; border:1px dashed var(--border); border-radius:10px;
                                    color:var(--muted); font-size:.8rem; line-height:1.5; }

                /* Barres horizontales (répartition par levier / par canal). */
                .rep .bars { display:flex; flex-direction:column; gap:.6rem; }
                .rep .bars .row .top { display:flex; justify-content:space-between; gap:.6rem; font-size:.82rem; margin-bottom:.25rem; }
                .rep .bars .row .top b { font-weight:600; }
                .rep .bars .row .top .val { color:var(--muted); font-variant-numeric:tabular-nums; }
                .rep .bars .row .top .val strong { color:var(--text); }
                .rep .bars .track { position:relative; height:9px; background:var(--surface-2); border-radius:999px; overflow:hidden; }
                .rep .bars .fill { position:absolute; inset:0 auto 0 0; border-radius:999px; background:var(--primary); }

                /* Liste d'items (recommandations, opportunités, journal) : un filet de sévérité
                   à gauche, comme les cartes d'action de Ma journée — même code visuel partout. */
                .rep .items { display:flex; flex-direction:column; gap:.4rem; }
                .rep .item { border-left:3px solid var(--border-strong); border-radius:8px; padding:.4rem .65rem;
                             background:var(--surface-2); font-size:.83rem; }
                .rep .item.late { border-left-color:var(--danger); }
                .rep .item.warn { border-left-color:var(--amber); }
                .rep .item.ok { border-left-color:var(--green); }
                .rep .item.off { opacity:.72; }
                .rep .item .sub { font-size:.75rem; color:var(--muted); margin-top:.1rem; word-break:break-word; }
                .rep .items.scroll { max-height:420px; overflow:auto; }

                .rep .note { font-size:.74rem; color:var(--muted); margin:.6rem 0 0; line-height:1.5; }
                .rep table.grid { width:100%; }
                .rep .scroll-x { overflow-x:auto; }

                /* Impression : ce qui ne s'imprime pas, et ce qui ne doit pas se couper. */
                @media print {
                    .rep .panel, .rep .tile { box-shadow:none !important; break-inside:avoid; }
                    .rep .sec { break-after:avoid; }
                    .rep .items.scroll { max-height:none; overflow:visible; }
                }

                /* Pastille d'état, portée par les tuiles d'indicateur. */
                .seo-page .dot { display:inline-block; width:11px; height:11px; border-radius:50%; vertical-align:middle; background:var(--sbase); }
                .seo-page .dot.good { background:var(--green); }
                .seo-page .dot.warn { background:var(--amber); }
                .seo-page .dot.crit { background:var(--danger); }

                /* En-tête du bloc : **une seule ligne**. Le nom du client à gauche, la provenance
                   des chiffres et le bouton qui les rafraîchit à droite — ils répondent à la même
                   question et n'ont pas à s'empiler sur trois lignes.
                   ⚠️ Le titre reste un `h1` (structure du document) mais ne se lit **pas** comme un
                   titre de page : on est déjà sur la page de ce client, sous l'intertitre
                   « Pilotage SEO ». À 1,35 rem avec la fraîcheur empilée à sa droite, ce seul
                   en-tête faisait la hauteur d'une tuile d'indicateur. */
                .seo-page .clienthead { display:flex; align-items:baseline; gap:.5rem 1rem; flex-wrap:wrap; margin:0 2px .7rem; }
                .seo-page .clienthead .who { display:flex; align-items:baseline; gap:.5rem; flex-wrap:wrap; min-width:0; }
                .seo-page .clienthead h1 { margin:0; font-size:1.05rem; letter-spacing:-.012em; }
                .seo-page .clienthead .sub { color:var(--sink2); font-size:.75rem; }
                .seo-page .clienthead .fresh { margin-left:auto; display:flex; align-items:baseline;
                                               gap:.2rem .55rem; flex-wrap:wrap; justify-content:flex-end;
                                               font-size:.72rem; color:var(--smut); }
                /* Chaque source reste insécable **pour elle-même** : sept sources sur une seule
                   ligne insécable déborderaient de la page et pousseraient le bouton hors de vue. */
                .seo-page .clienthead .fresh .frq { white-space:nowrap; }
                .seo-page .clienthead .fresh .frq + .frq::before { content:'·'; margin-right:.5rem; color:var(--sbase); }
                .seo-page .clienthead .fresh .lbl { font-weight:600; color:var(--sink2); }
                .seo-page .clienthead .fresh b { color:var(--sink2); }
                .seo-page .clienthead .fresh .ko { color:var(--swarn); font-weight:600; }
                .seo-page .clienthead .fresh form { margin:0; }
                .seo-page .clienthead .fresh .btn { font-size:.7rem; padding:.2rem .55rem; }

                /* Un intertitre seul dans son bloc (« Le mois », « Pilotage SEO ») : son 1,5 rem de
                   respiration s'ajoutait au `gap` du panneau, et le nom du client flottait à trois
                   centimètres de la section qui l'annonce. */
                .seo-page > .sec-title:first-child { margin-top:0; margin-bottom:.35rem; }

                /* Rangée des 6 indicateurs : six colonnes tant que la place le permet, jamais
                   moins de deux — une tuile pleine largeur perdrait la lecture en rangée. */
                .seo-page .kpirow { display:grid; grid-template-columns:repeat(6,1fr); gap:.75rem; }
                @media (max-width:1180px) { .seo-page .kpirow { grid-template-columns:repeat(3,1fr); } }
                @media (max-width:720px)  { .seo-page .kpirow { grid-template-columns:repeat(2,1fr); } }
                .seo-page .kpi { background:#fff; border:1px solid var(--sring); border-radius:12px;
                    padding:.85rem .9rem .7rem; box-shadow:var(--sshadow); display:flex; flex-direction:column;
                    gap:.5rem; position:relative; overflow:hidden; }
                .seo-page .kpi .pill-pillar { font-size:.62rem; letter-spacing:.04em; text-transform:uppercase;
                    color:var(--smut); font-weight:700; }
                .seo-page .kpi .name { font-size:.79rem; font-weight:600; line-height:1.25; min-height:2.6em; }
                .seo-page .kpi .value { font-size:1.75rem; font-weight:700; letter-spacing:-.03em; line-height:1;
                    font-variant-numeric:tabular-nums; }
                .seo-page .kpi .value small { font-size:.82rem; font-weight:600; color:var(--sink2); margin-left:.1rem; }
                /* ⚠️ Un indicateur non mesuré doit se distinguer d'un zéro au premier coup d'œil :
                   un « 0 » de repli se lit comme une contre-performance. */
                .seo-page .kpi .value.nm { font-size:.95rem; font-weight:600; color:var(--smut); letter-spacing:0; }
                .seo-page .kpi .why { font-size:.68rem; color:var(--smut); line-height:1.4; }
                .seo-page .kpi .row2 { display:flex; align-items:center; justify-content:space-between; gap:.4rem; flex-wrap:wrap; }
                .seo-page .delta { font-size:.72rem; font-weight:700; display:inline-flex; align-items:center; gap:.15rem;
                    font-variant-numeric:tabular-nums; }
                .seo-page .delta.up { color:var(--green); }
                .seo-page .delta.down { color:var(--danger); }
                .seo-page .delta.flat { color:var(--smut); }
                .seo-page .status { font-size:.66rem; font-weight:700; display:inline-flex; align-items:center; gap:.3rem;
                    padding:.12rem .45rem; border-radius:999px; border:1px solid var(--sring); }
                .seo-page .status.good { color:#1f6b57; background:var(--ok-soft); }
                .seo-page .status.warn { color:#8a5a12; background:var(--warn-soft); }
                .seo-page .status.crit { color:#8f2a3e; background:var(--late-soft); }
                .seo-page .status.nd   { color:var(--smut); background:var(--neutral-soft); }
                .seo-page .kpi .spark { width:100%; height:30px; display:block; }
                .seo-page .kpi .ext-mini { font-size:.64rem; color:var(--sb); font-weight:600; }
                .seo-page .kpi .mini-liens { display:flex; gap:.6rem; flex-wrap:wrap; }
                /* ⚠️ La barre du client est collante : sans marge de défilement, l'ancre « Voir les
                   cocons » ferait atterrir le titre **sous** elle, et on croirait le lien cassé.
                   Même raison que le `scroll-margin-top` des sections de la fiche client. */
                .seo-page .sec-title[id] { scroll-margin-top:76px; }
                /* Date de la donnée, sur chaque tuile : discrète mais toujours présente — un
                   chiffre de pilotage sans date ne se défend pas devant un client. */
                .seo-page .kpi .asof { font-size:.62rem; color:var(--smut); margin-top:.15rem; }
                .seo-page .kpi .asof b { color:var(--sink2); font-weight:600; }

                .seo-page .sec-title { margin:1.5rem 2px .7rem; display:flex; align-items:center; gap:.6rem; flex-wrap:wrap; }
                .seo-page .sec-title .bar { width:5px; height:20px; border-radius:3px; display:block; }
                .seo-page .sec-title h2 { margin:0; font-size:1rem; letter-spacing:-.01em; }
                .seo-page .sec-title .tag { font-size:.68rem; color:var(--smut); font-weight:600;
                    text-transform:uppercase; letter-spacing:.04em; }

                .seo-page .grid2 { display:grid; grid-template-columns:1fr 1fr; gap:1rem; }
                @media (max-width:900px) { .seo-page .grid2 { grid-template-columns:1fr; } }
                .seo-page .card { background:#fff; border:1px solid var(--sring); border-radius:12px;
                    padding:1rem 1.1rem; box-shadow:var(--sshadow); position:relative; }
                .seo-page .card h3 { margin:0 0 .15rem; font-size:.88rem; }
                .seo-page .card .desc { margin:0 0 .9rem; font-size:.74rem; color:var(--sink2); }
                .seo-page .card .subh { font-size:.74rem; font-weight:600; color:var(--sink2); margin:0 0 .5rem; }
                .seo-page .card .ext { position:absolute; top:.85rem; right:1rem; font-size:.68rem; font-weight:600;
                    color:var(--sb); border:1px solid var(--sring); padding:.2rem .5rem; border-radius:7px;
                    background:#fff; white-space:nowrap; }
                .seo-page .card .ext:hover { border-color:var(--sb); text-decoration:none; }
                .seo-page .noto-grid { display:grid; grid-template-columns:1fr 1fr; gap:1.6rem; }
                @media (max-width:800px) { .seo-page .noto-grid { grid-template-columns:1fr; } }
                .seo-page .legend { display:flex; gap:.9rem; flex-wrap:wrap; font-size:.72rem; color:var(--sink2); margin-top:.6rem; }
                .seo-page .legend .lg { display:inline-flex; align-items:center; gap:.35rem; }
                .seo-page .legend .sw { width:12px; height:12px; border-radius:3px; display:inline-block; }
                .seo-page .legend .sw.line { height:3px; border-radius:2px; width:16px; }
                .seo-page .note { margin-top:.5rem; font-size:.68rem; color:var(--smut); line-height:1.45; }

                /* Jauge du score de santé : les graduations posent les seuils (75 / 90) sur la
                   règle elle-même, plutôt que dans une légende qu'il faudrait aller lire. */
                .seo-page .meter { margin-top:.35rem; }
                .seo-page .meter .track { height:14px; border-radius:8px; background:var(--sgrid); overflow:hidden; }
                .seo-page .meter .fill { height:100%; border-radius:8px 0 0 8px; }
                .seo-page .meter .ticks { position:relative; height:16px; margin-top:.2rem; font-size:.62rem; color:var(--smut); }
                .seo-page .meter .ticks span { position:absolute; transform:translateX(-50%); }
                .seo-page .bignum { display:flex; align-items:baseline; gap:.6rem; margin:.2rem 0 .1rem; flex-wrap:wrap; }
                .seo-page .bignum .n { font-size:2.2rem; font-weight:700; letter-spacing:-.04em; line-height:1;
                    font-variant-numeric:tabular-nums; }
                .seo-page .bignum .u { font-size:.82rem; color:var(--sink2); font-weight:600; }
                .seo-page .up { color:var(--green); } .seo-page .down { color:var(--danger); } .seo-page .flat { color:var(--smut); }

                .seo-page .filters { display:flex; gap:1.1rem; flex-wrap:wrap; margin:.1rem 0 .8rem;
                    padding-bottom:.8rem; border-bottom:1px solid var(--sgrid); }
                .seo-page .fgroup { display:flex; align-items:center; gap:.35rem; flex-wrap:wrap; }
                .seo-page .fgroup .lbl { font-size:.62rem; text-transform:uppercase; letter-spacing:.03em;
                    color:var(--smut); font-weight:700; }
                .seo-page .chip { font-size:.72rem; font-weight:600; padding:.2rem .6rem; border-radius:999px;
                    border:1px solid var(--sring); background:#fff; color:var(--sink2); }
                .seo-page .chip:hover { border-color:var(--sbase); text-decoration:none; }
                .seo-page .chip.active { background:var(--sb); color:#fff; border-color:var(--sb); }
                .seo-page .chip.geo.active { background:var(--st); color:#083b39; border-color:var(--st); }

                /* Ligne de cocon : le nom, la barre de part en Top 10, la méta à droite. */
                .seo-page .cocon { display:flex; align-items:center; gap:.7rem; padding:.5rem 0; border-bottom:1px solid var(--sgrid); }
                .seo-page .cocon:last-child { border-bottom:0; }
                .seo-page .cocon .cname { width:160px; flex:none; font-size:.79rem; font-weight:600; }
                .seo-page .cocon .cname small { display:block; font-weight:400; color:var(--smut); font-size:.68rem;
                    overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
                /* La ligne de roadmap sous laquelle le pilier se travaille — même graphie que les
                   chantiers rattachés du Gantt : c'est le même rattachement, vu de l'autre côté. */
                .seo-page .cocon .cname small.crm { color:var(--sb); font-weight:600; }
                .seo-page .cocon .js-cocon-roadmap { flex:none; }
                .seo-page .cbar-wrap { flex:1; height:22px; background:var(--sgrid); border-radius:6px; position:relative; overflow:hidden; min-width:90px; }
                .seo-page .cbar { height:100%; border-radius:6px 0 0 6px; background:var(--sb);
                    display:flex; align-items:center; justify-content:flex-end; min-width:2px; }
                .seo-page .cbar .lab { color:#fff; font-size:.66rem; font-weight:700; padding-right:.45rem; font-variant-numeric:tabular-nums; }
                /* Repère d'objectif : un trait, pas une seconde barre — il dit « il faut arriver
                   là », il ne se compare pas en longueur avec le réalisé. */
                .seo-page .cbar-goal { position:absolute; top:0; bottom:0; width:2px; background:var(--sink); opacity:.5; }
                .seo-page .cocon .meta { width:132px; flex:none; text-align:right; font-size:.68rem; color:var(--sink2);
                    font-variant-numeric:tabular-nums; line-height:1.35; }
                .seo-page .cocon .meta .d { font-weight:700; }
                .seo-page .cocon .meta .vol { color:var(--smut); }

                .seo-page svg .gl { stroke:var(--sgrid); stroke-width:1; }
                .seo-page svg text { font-family:inherit; }
                .seo-page .tick { font-size:10px; fill:var(--smut); }
                .seo-page .vlabel { font-size:11px; font-weight:700; fill:var(--sink); }
                /* Un graphe sans assez de points ne doit pas se dessiner plat : une courbe plate
                   se lit comme une stagnation mesurée, alors qu'il n'y a rien de mesuré du tout. */
                .seo-page .chart-empty { display:flex; align-items:center; justify-content:center; text-align:center;
                    min-height:110px; padding:.8rem; border:1px dashed var(--sbase); border-radius:8px;
                    font-size:.72rem; color:var(--smut); line-height:1.5; }

                /* ---- Navigation d'un client (barre unique, collante) ---- */
                /* La fiche est un long défilement : la barre reste sous la main, et les ancres
                   remplacent les onglets sans découper la page en écrans séparés. */
                .client-nav { position:sticky; top:0; z-index:20; display:flex; flex-direction:column; gap:.3rem;
                    margin:-2rem -2.4rem 1.3rem; padding:.75rem 2.4rem .35rem;
                    background:color-mix(in srgb, var(--bg) 90%, transparent); backdrop-filter:blur(10px); border-bottom:1px solid var(--border); }
                .client-nav-id { display:flex; align-items:center; gap:.5rem; font-size:1.02rem; letter-spacing:-.01em; }
                .client-nav-id .back { font-size:1.05rem; line-height:1; padding:0 .15rem; }
                .client-nav-id .back:hover { color:var(--text); text-decoration:none; }
                .client-nav .tabs { margin:0; border-bottom:none; padding-bottom:0; gap:.2rem; }
                .client-nav-ancres { display:flex; flex-wrap:wrap; gap:.15rem .9rem; padding:.1rem 0 .25rem; }
                .client-nav-ancres a { font-size:.79rem; color:var(--muted); font-weight:500; }
                .client-nav-ancres a:hover { color:var(--primary-d); }
                /* Sans cela, une ancre atterrit sous la barre collante. */
                .sec { scroll-margin-top:118px; }

                /* ---- Misc ---- */
                .toolbar { display:flex; justify-content:space-between; align-items:center; gap:1rem; margin-bottom:1.1rem; flex-wrap:wrap; }
                .actions { display:flex; gap:.45rem; align-items:center; flex-wrap:wrap; }
                /* ---- Actions d'en-tête : même couleur, même place ----------------------------
                   ⚠️ Retour d'usage (03/09/2026) : le même geste était corail ici, gris là,
                   fantôme ailleurs — « Régénérer » se présentait de trois façons sur la même page.
                   Règle : **toute action portée par un titre** (d'écran ou de bloc) est un `.btn`
                   plein, et se range **à droite de ce titre**. On lit le titre, on sait où cliquer.
                   ⚠️ Elle ne vaut **pas** pour les gestes de ligne (✓ Fait, ✎, ✕) ni pour les
                   destructifs : un « Supprimer » qui ressemble à « Enregistrer » se clique par
                   réflexe, et c'est le seul endroit où la couleur porte un avertissement.
                   `margin-left:auto` colle le groupe à droite ; dans un titre de panneau `.rep`,
                   c'est `.srcs` qui a déjà poussé, les actions le suivent donc simplement. */
                .h-actions { margin-left:auto; display:inline-flex; align-items:center; gap:.4rem;
                             flex-wrap:wrap; align-self:center; }
                .rep .panel > h3 .h-actions, .rep .panel > h2 .h-actions { margin-left:.5rem; }
                .search { display:flex; gap:.45rem; align-items:center; }
                .search input, .search select { width:auto; }
                .muted { color:var(--muted); }
                .chiplist { display:flex; flex-wrap:wrap; gap:.35rem; }
                .chip { background:#f3f4f6; border:1px solid var(--border); padding:.12rem .55rem; border-radius:7px; font-size:.82rem; color:#4a4d54; }
                .chip.chip-ok { background:#e7f6ef; border-color:#bfe6d2; color:#15784f; }
                .chip.chip-late { background:#fde7ea; border-color:#f6c7cf; color:#c92a42; }
                .meta-grid { display:grid; grid-template-columns:auto 1fr; gap:.55rem 1.4rem; }
                .meta-grid dt { color:var(--muted); font-size:.85rem; font-weight:600; }
                .meta-grid dd { margin:0; }

                /* ---- Login ---- */
                .login-wrap { min-height:100vh; display:flex; align-items:center; justify-content:center; background:radial-gradient(800px 460px at 30% 12%, rgba(240,106,106,.14), transparent 55%), var(--bg); padding:1rem; }
                .login-box { width:380px; max-width:100%; box-shadow:var(--shadow-md); }
                .login-box h1 { font-size:1.4rem; text-align:center; margin-bottom:1.2rem; }
                /* Page d'accueil publique (visiteur anonyme sur la racine). */
                .home-wrap { min-height:100vh; display:flex; align-items:center; justify-content:center; padding:2rem 1.25rem; background:radial-gradient(800px 460px at 30% 12%, rgba(240,106,106,.14), transparent 55%), var(--bg); }
                .home { max-width:760px; text-align:center; }
                .home-logo { display:block; margin:0 auto .4rem; }
                .home h1 { font-size:2.3rem; margin:.5rem 0 .75rem; }
                .home-baseline { color:var(--muted); font-size:1.02rem; max-width:560px; margin:0 auto; }
                .home-cards { display:grid; grid-template-columns:repeat(auto-fit,minmax(196px,1fr)); gap:.85rem; margin:2.4rem 0; text-align:left; }
                .home-card { background:var(--surface); border:1px solid var(--border); border-radius:var(--radius); padding:1rem 1.05rem; box-shadow:var(--shadow); }
                .home-card h2 { font-size:.95rem; margin:0 0 .35rem; }
                .home-card p { margin:0; font-size:.87rem; color:var(--muted); line-height:1.55; }
                .home-cta { display:inline-block; padding:.7rem 1.8rem; font-size:.95rem; }
                .home-note { color:var(--faint); font-size:.82rem; margin:1.1rem auto 0; max-width:460px; }
                .home-foot { margin-top:2.6rem; padding-top:1.15rem; border-top:1px solid var(--border); font-size:.82rem; color:var(--faint); display:flex; flex-wrap:wrap; gap:.5rem; justify-content:center; }
                /* Pages légales publiques (confidentialité, conditions) : hors rail, largeur de lecture. */
                .legal-wrap { max-width:820px; margin:0 auto; padding:2.5rem 1.25rem 4rem; }
                .legal .updated { color:var(--muted); font-size:.85rem; margin:.35rem 0 2.2rem; }
                .legal h2 { margin:2rem 0 .5rem; }
                .legal p, .legal li { line-height:1.65; }
                .legal ul { padding-left:1.15rem; margin:.5rem 0; }
                .legal-foot { margin-top:2.5rem; padding-top:1rem; border-top:1px solid var(--border); font-size:.85rem; color:var(--muted); }

                /* ---- Responsive ---- */
                @media (max-width:860px) {
                    /* Le rail bascule en bandeau horizontal : mêmes icônes, même actif en encre. */
                    .app { flex-direction:column; }
                    .rail { width:auto; height:auto; position:static; flex-direction:row; flex-wrap:wrap; gap:.3rem; padding:.55rem .8rem;
                        border-right:0; border-bottom:1px solid var(--border); }
                    .rail .brand { padding:0 .3rem 0 0; }
                    .rail .nav { flex-direction:row; flex-wrap:wrap; }
                    .rail .nav-label { width:0; height:22px; margin:0 .3rem; border-top:0; border-left:1px solid var(--border-strong); }
                    .rail .userbox { flex-direction:row; width:auto; border-top:0; border-left:1px solid var(--border); padding:0 0 0 .5rem; }
                    .content { padding:1.4rem 1.1rem 2.5rem; }
                    /* La barre suit le rétrécissement du contenu, et ne colle plus : la
                       sidebar passe en bandeau, deux éléments collants se marcheraient dessus. */
                    .client-nav { position:static; margin:-1.4rem -1.1rem 1rem; padding:.7rem 1.1rem .35rem; }
                    .sec { scroll-margin-top:12px; }
                }
            