/* ENGENDRÉ — ne pas modifier à la main.
   Source : frontend/plateforme.html (blocs jetons · navigation · pied ·
   révélation · fondu), plus le menu déroulant et les composants des
   pages intérieures, qui vivent dans scripts/vitrine/coquille.py.
   Régénérer : python3 scripts/vitrine/build.py
   L'accueil garde SA feuille en ligne : il n'est pas touché, et le test
   tests/vitrine-coquille.test.js vérifie que les deux restent d'accord. */

    /* ========================================================================
       VITRINE CO-LABS — refonte du 15 août 2026 (demande Dany).
       Clair par défaut, sombre au bouton (theme.js pose data-theme sur <html>).
       UNE famille typographique (Georama), UN accent (#3b82f6 —
       décor et pictos ; #2563eb pour tout ce qui porte du TEXTE blanc, AA),
       pictos SVG dessinés sur mesure — la VAGUE du monogramme CL est la
       signature graphique (soulignés, courbes, sourires de bulles).
       Bannis et vérifiés absents : dégradés de texte, eyebrows majuscules
       systématiques, grilles de cartes identiques, emojis, glassmorphism.
       ===================================================================== */
    :root {
      --bg: #fafbfd;             /* blanc froid, teinté bleu à peine — jamais crème */
      --surface: #ffffff;
      --surface-2: #f0f4fa;
      --ink: #0e1420;            /* l'encre bleu-noir : titres */
      --text: #2a3242;           /* corps — 10,9:1 sur --bg */
      --soft: #57627a;           /* secondaire — 5,9:1 sur --bg */
      --line: #e3e8f1;
      --line-strong: #c6d0e0;
      --line-champ: #75808f;      /* bord d'un champ : 4,0:1 sur la carte — WCAG 1.4.11 exige 3:1 pour la borne d'un composant */
      --accent: #3b82f6;         /* LE bleu de la marque : pictos, décor, gros repères */
      --accent-action: #2563eb;  /* boutons pleins (blanc dessus = 4,7:1, AA) */
      --accent-texte: #1d4ed8;   /* liens et mots accentués (6,3:1 sur --bg) */
      --accent-lavis: #e9f0fe;   /* lavis bleu pour fonds légers */
      /* (`--bande` est parti avec la bande démo, 20.08.2026 : c'était le seul
         aplat de couleur pleine de la page, et le seul objet qui ne parlait
         pas la peau off-white de tout le reste. Le lien vers le salon de
         démonstration survit — il est passé sur les quatre points d'appel qui
         y menaient, en lien EXTERNE.) */
      --ok: #157a4c;
      --ko: #b3261e;
      /* ══ GEORAMA ═══════════════════════════════════════════════════════
         Production Type (Jean-Baptiste Levée, Paris), sous licence OFL —
         LA MÊME MAIN que Spectral, déjà auto-hébergée pour la console et le
         back-office : la vitrine et le produit parlent enfin de la même voix.
         POURQUOI ON A CHANGÉ, et ce n'est pas un goût. Dany trouvait la police
         « générique ». Le diagnostic était plus précis que le reproche : la
         page n'exerçait AUCUN axe variable — zéro `font-variation-settings`,
         zéro `font-stretch`, zéro `font-optical-sizing` — et le fichier
         auto-hébergé de Bricolage avait perdu son axe de LARGEUR au
         sous-ensemblage. On employait une police à trois axes dans sa version
         la plus neutre. Georama en offre un de 62,5 à 150 %, l'amplitude la
         plus large des neuf candidates examinées.
         ⚠️ 17 sept. 2026 : l'axe de largeur n'est plus exercé. Les titres de
         planche étaient les seuls à le faire (84 %), et c'est précisément ce
         qui les faisait lire comme des légendes — voir la règle
         `.pl > .wrap > h2`. La famille reste ; ce qu'elle a de propre se
         verra ailleurs qu'en resserrant un titre. */
      --gros: 'Georama', 'Avenir Next', 'Segoe UI', system-ui, sans-serif;
      --maxw: 1140px;
      --r-sm: 8px; --r-md: 14px; --r-lg: 22px;
      /* Échelle de z : rien d'arbitraire. */
      --z-nav: 50; --z-menu: 60;
    }
    :root[data-theme="dark"] {
      --bg: #0b1017;
      --surface: #121926;
      --surface-2: #182234;
      --ink: #f1f5fb;
      --text: #c7cfdd;           /* 10,8:1 sur #0b1017 */
      --soft: #8d99b0;           /* 5,6:1 */
      --line: #1f2a3d;
      --line-strong: #33415c;
      --line-champ: #7c879a;      /* 4,9:1 sur la carte sombre */
      --accent-action: #2563eb;
      --accent-texte: #8ab2fb;   /* 7,4:1 sur #0b1017 */
      --accent-lavis: #13233f;
      --ok: #4fc38a;
      --ko: #ff8a80;
    }

    /* `color-scheme` dit au NAVIGATEUR dans quelle peau peindre ce qu'il dessine
       lui-même : bordures de champ par défaut, ascenseurs, calendrier natif,
       remplissage automatique du mot de passe. Sans lui, en thème sombre, ces
       éléments restaient clairs — un champ blanc éclatant au milieu d'un
       formulaire sombre. Une déclaration par thème, et le navigateur suit. */
    :root { color-scheme: light; }
    :root[data-theme="dark"] { color-scheme: dark; }
    /* La sélection de texte empruntait le bleu par défaut du navigateur,
       étranger à la marque. On la teinte au lavis d'accent, dans les deux
       thèmes (le lavis bascule déjà, l'encre aussi). */
    ::selection { background: var(--accent-lavis); color: var(--ink); }

    * { box-sizing: border-box; }
    html { scroll-behavior: smooth; }
    @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
    body {
      margin: 0;
      font-family: var(--gros);
      font-size: 16.5px;
      line-height: 1.6;
      color: var(--text);
      background: var(--bg);
      -webkit-font-smoothing: antialiased;
    }
    .wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 clamp(18px, 4vw, 40px); }
    h1, h2, h3 { color: var(--ink); text-wrap: balance; margin: 0; }
    p { margin: 0; max-width: 62ch; text-wrap: pretty; }
    a { color: var(--accent-texte); text-decoration-thickness: 1px; text-underline-offset: 3px; }
    :focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }

    .picto { width: 24px; height: 24px; stroke: currentColor; fill: none; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; flex: none; }

    /* Le lien d'évitement — MOTIF REPRIS d'admin-plateforme.html, à l'accent
       bleu de la vitrine. Huit arrêts clavier d'en-tête précédaient le
       premier contenu, et cet en-tête est répété sur les pages sœurs :
       WCAG 2.4.1 Contourner des blocs. Les deux pages légales portent déjà
       un <main>, sept pages du dépôt portent déjà ce lien — la page qui
       reçoit le plus de premières visites était la seule à y manquer. */
    /* IL DESCEND EN `transform`, PAS EN `top` (19.08.2026). Il animait `top`,
       une propriété de MISE EN PAGE — la seule entorse restante à la règle du
       dépôt « n'animer que transform et opacity », et posée par la session qui
       venait justement de retirer les deux `transition: height` de la barre
       pour cette raison-là. Son `position: absolute` la rendait bénigne (elle
       ne remettait en page que lui-même), mais une règle qu'on s'applique
       « sauf quand ça ne coûte rien » n'est plus une règle, et la prochaine
       session aurait recopié le motif ailleurs.
       `translateY(-140%)` et non `-100%` : les 40 % de rabiot emportent
       l'ombre et l'anneau de focus hors du cadre, sinon un liseré du bouton
       reste visible en haut de page au repos. */
    .skip-link {
      position: absolute; left: 12px; top: 0; z-index: 2000;
      transform: translateY(-140%);
      background: var(--accent-action); color: #fff;
      padding: 10px 18px; border-radius: 0 0 10px 10px;
      font-weight: 700; text-decoration: none; transition: transform 160ms ease;
    }
    .skip-link:focus { transform: translateY(0); outline: 2px solid var(--accent); outline-offset: 2px; }
    @media (prefers-reduced-motion: reduce) { .skip-link { transition: none; } }
    #contenu:focus { outline: none; }

    /* Une ancre ne se cale jamais sous la barre collante.
       78 px ÉTAIT FAUX, ET SON COMMENTAIRE LE DISAIT SANS LE VOIR (corrigé le
       3 sept. 2026). La valeur était calée sur la barre compactée — 60 + 18 px
       d'air — « puisqu'une ancre s'applique toujours au-delà du seuil de
       compactage ». Or la classe `.nav.compacte` n'était posée par RIEN : la
       barre mesurait 97 px en toutes circonstances, et toute ancre atterrissait
       19 px SOUS elle. Le voisin `#contenu` porte d'ailleurs la mesure exacte de
       ce défaut, relevée à l'époque et traitée pour lui seul.
       LE CALCUL, maintenant que la compaction existe pour de bon. Le cas qui
       décide est celui où le navigateur défile barre ENCORE DÉPLIÉE, puis la
       compaction retire 36 px : la cible remonte d'autant. Il faut donc
       P − 36 ≥ 61 + 18, soit P ≥ 115. Les trois cas, mesurés :
       · cible loin, barre pleine au moment du saut → 115 − 36 = 79, air 18 px
       · cible en haut, aucune compaction         → 115, barre à 97, air 18 px
       · barre déjà compactée                     → 115, barre à 61, air 54 px
       Aucun ne passe sous la barre, et c'était la seule promesse à tenir. Surtout pas `calc(var(--nav-h)+…)` : --nav-h est déclarée sur
       .nav, elle n'est pas visible depuis [id].
       `scroll-padding-top` sur le CONTENEUR et non `scroll-margin-top` sur
       les cibles : la seconde forme ne protégeait que les éléments porteurs
       d'un id, or 24 des 35 arrêts clavier n'en ont pas — et le focus remonté
       au clavier passait sous la barre (mesuré : « Voir mon commerce en
       démo » masqué à 100 %). WCAG 2.4.11 Focus non masqué. */
    html { scroll-padding-top: 115px; }
    /* SOUS 720 px LA BARRE EST PLUS BASSE, ET ELLE PERD MOINS. 76 px dépliée,
       57 px mesurée compactée : la compaction ne retire que 20 px, pas 36. La
       même démonstration donne P − 20 ≥ 57 + 18, soit P ≥ 95. Garder 115 ne
       cachait rien — c'est le sens de l'inégalité — mais déposait l'ancre
       38 px plus bas que l'air voulu, sur l'écran où la place est la plus
       rare. */
    @media (max-width: 719px) { html { scroll-padding-top: 95px; } }

    /* DÉMÉNAGÉE ICI LE 1er SEPT. 2026, et le déplacement EST le changement :
       cette règle vivait dans la zone HÉROS→PIED, que `coquille.py` ignore à
       l'extraction — les 134 pages engendrées ne l'avaient donc pas. Elles
       reçoivent maintenant leurs ancres `#sN`, et une ancre qui se pose sous
       une barre collante ne se lit pas. */
/* ============================== NAVIGATION ========================== */
    .nav {
      /* Deux variables, un seul endroit : la hauteur de barre ET celle du
         logo se pilotent ici, donc le mode compacté et le mode téléphone se
         redéclarent en deux lignes chacun — sans jamais rejouer la guerre de
         spécificité (une media query n'ajoute AUCUNE force). --nav-h sert
         aussi au panneau du burger, qui se cale dessus. */
      --nav-h: 96px;            /* 60px compactée · 76/56 au téléphone */
      --logo-h: 68px;           /* 40px compactée · 52/36 au téléphone */
      position: sticky; top: 0; z-index: var(--z-nav);
      /* = var(--bg) à 88 % — en rgba() LITTÉRAL et pas en color-mix : les
         couleurs héritées interpolent en sRGB comme le corps, donc le fondu
         jour/nuit garde la barre exactement au rythme de la page (un
         color-mix part en oklab et fonçait en avance — mesuré). */
      background: rgba(250, 251, 253, .88);
      backdrop-filter: blur(10px);
      border-bottom: 1px solid var(--line);
      transition: box-shadow .25s ease;
    }
    :root[data-theme="dark"] .nav { background: rgba(11, 16, 23, .88); }
    /* Après 20 px de défilement, la barre se détache — posé par JS. */
    .nav.ombre { box-shadow: 0 14px 34px -24px rgba(14, 20, 32, .3); }
    :root[data-theme="dark"] .nav.ombre { box-shadow: 0 14px 34px -20px rgba(0, 0, 0, .6); }
    /* La barre est sa propre bande : son contenu s'étend à 1280 px (la page
       reste à 1140) — sinon cinq liens + le CTA débordent de la colonne. */
    .nav .wrap { display: flex; align-items: center; gap: 16px; height: var(--nav-h); max-width: 1280px; }
    .brand { display: flex; align-items: center; gap: 11px; text-decoration: none; color: var(--ink); white-space: nowrap; }
    /* 68 px : la marge de netteté du PNG (540×320, ~303 px d'encre) s'arrête
       vers 106 px à DPR 3 — on reste franchement en dessous. Au-delà il
       faudrait un VRAI vectoriel, et `logo/Co-Labs Trens.svg` n'en est pas un
       (le même raster encodé deux fois, boîte asymétrique). */
    .brand img { height: var(--logo-h); width: auto; display: block; }
    /* LE NOM ET L'EMBLÈME SUR UNE SEULE LIGNE (19.08.2026, demande de Dany).
       Les mots « Hébergé en Suisse » quittent l'en-tête. Rien ne se perd :
       la promesse est déjà écrite trois fois plus bas — dans l'inventaire
       produit, dans le serment « Hébergement en Suisse » de #confiance (le
       seul endroit où elle est TENUE, avec ses conséquences), et dans la
       nouvelle ligne de conformité du pied.
       Le drapeau, lui, RESTE — arbitrage de Dany. Mais il ne pouvait pas
       rester EMPILÉ sous le nom : seul sous « Co-Labs », un carré rouge de
       15 px se lit comme une pastille de notification, pas comme un signal
       suisse (l'objection était déjà écrite dans ce fichier). En ligne, à
       6 px du mot, c'est un poinçon d'origine — la place qu'un emblème
       occupe sur un objet suisse.
       « Swiss Made » reste interdit ici : mention réglementée qui exige le
       siège en Suisse, et l'identité légale de Co-Labs n'est pas encore
       renseignée (docs/CONFORMITE-SECURITE.md).
       Effet mesurable : la largeur de la marque n'était pas pilotée par le
       mot « Co-Labs » (82,5 px) mais par la ligne drapeau + mots (131,5 px),
       parce que `.brand b` était en display:block dans le même <span>. */
    /* gap: 0 — le nom de domaine se lit d'un seul tenant (« Co-Labs.ch »,
       pas « Co-Labs .ch ») ; c'est le drapeau qui porte l'air (8 px). */
    .brand-nom { display: flex; align-items: center; gap: 0; line-height: 1.25; }
    .brand-nom .drapeau { margin-left: 8px; }
    .brand b { font-size: 1.34rem; font-weight: 800; letter-spacing: -0.015em; }
    /* « .ch » : MÊME corps que « Co-Labs », graisse 500, encre douce —
       la hiérarchie se joue à la graisse et au ton, jamais à une police
       neuve. `font-style: normal` redresse le <i> (voix alternative, pas
       italique). AA vérifié par composition alpha sur les DEUX fonds de
       barre translucides : var(--soft) compose à 5,6:1 en clair (pire cas :
       barre sur --surface blanc) et 5,3:1 en sombre — au-dessus de 4,5. */
    .brand-ch { font-style: normal; font-size: 1.34rem; font-weight: 500; letter-spacing: -0.015em; color: var(--soft); }
    /* Classe DÉDIÉE, surtout pas `.picto` : celle-ci impose fill:none +
       stroke:currentColor, ce qui effacerait un emblème PLEIN. Le filet et
       l'air séparent le rouge du bleu de marque — mesurés à 1,09:1 l'un
       contre l'autre (luminosités quasi identiques : ils vibreraient collés). */
    /* 20 px et non plus 15 (27.08.2026, demande de Dany : « le drapeau doit
       être carré et aux bonnes dimensions ») : le symbole #p-suisse porte
       DÉJÀ la géométrie fédérale exacte sur un carré 1:1 — on le MONTE, on
       ne le redessine pas. width == height, aucun transform sur la chaîne
       d'ancêtres : vérifié carré au rendu. Le rx=3 du symbole rend ≈1,9 px
       à cette taille : perçu carré, sans arête dure contre le fond. */
    .drapeau { width: 20px; height: 20px; flex: none; border-radius: 3px; box-shadow: 0 0 0 1px var(--line-strong); }
    /* Compactée après défilement : ponctuel (un aller, un retour), pas continu.
       28 px d'amplitude sur le logo et 36 px sur la barre — contre 8 et 10
       avant, ce qui explique que le mouvement ne se voyait pas.

       LE COMPACTAGE EST UNE BASCULE SÈCHE, ET C'EST UN CHOIX MESURÉ
       (19.08.2026). Il y avait ici deux `transition: height .25s` — les
       seules propriétés animées de la page qui remettaient en page le
       DOCUMENT. `height` est une propriété de MISE EN PAGE : chaque image des
       250 ms rejoue la mise en page, et le reflux n'est pas confiné puisque
       `.nav` est `sticky`, donc dans le flux — le titre du héros se déplaçait
       de 20 px pendant toute la transition, et les ~1 160 nœuds sous l'en-tête
       étaient recalculés à chaque fois.
       (Deux chiffres de ce paragraphe étaient faux et sont corrigés :
       la durée d'origine est bien `.25s` et non `.28s` — `git show
       HEAD:frontend/plateforme.html`, lignes 116 et 118 — et « les seules
       propriétés animées » ne tenait pas : `.skip-link` anime `top`, et
       `button.ag-libre` anime `background` et `border-color`. Ce qui
       justifiait la suppression n'était pas l'exclusivité, c'était le
       PÉRIMÈTRE du reflux : ces deux-là seules remettaient en page toute la
       feuille sous l'en-tête. Le lien d'évitement passe d'ailleurs au
       `transform` quelques centaines de lignes plus haut, ce qui referme le
       cas ; `background` et `border-color` ne font que peindre.)
       Mesuré à 390 px sur 20 franchissements du seuil, la MÊME séquence avec
       et sans les deux transitions : 1 644 mises en page contre 422, et
       165,5 ms contre 63,4 — soit 1 222 mises en page et 102 ms achetées pour
       un glissement de 36 px. C'est le genre de facture qui se paie sur un
       téléphone d'entrée de gamme, précisément là où la barre se compacte le
       plus souvent.
       POURQUOI PAS UN `transform` À LA PLACE : les deux objections d'origine
       tiennent toujours, et une troisième les scelle. `scale()` sur le logo
       laisse sa boîte de mise en page inchangée — soit 115 px morts dans une
       barre de 60, soit, si l'on cale la boîte sur l'état compacté, un nom
       « Co-Labs » qui vient se poser SOUS le logo agrandi, puisque `scale`
       change aussi la largeur. Et la hauteur de la barre elle-même reste une
       mise en page dans tous les cas : sortir `.nav` du flux pour l'éviter
       laisserait une bande de 36 px sans fond ni flou au-dessus du contenu.
       Un état qui bascule net se lit très bien — l'ombre portée, elle, garde
       sa transition : `box-shadow` ne peint que, elle ne remet rien en page. */
    .nav.compacte { --nav-h: 60px; --logo-h: 40px; }
    /* 26 px de gouttière (18 avant) : la barre a perdu « Connexion » et le
       sélecteur s'est resserré — l'air récupéré revient aux quatre liens,
       qui sont l'objet de la demande (« logiques, visibles »). */
    .links { display: flex; gap: 26px; margin-left: auto; }
    .links a { color: var(--text); text-decoration: none; font-weight: 500; font-size: 0.92rem; padding: 6px 2px; border-bottom: 2px solid transparent; white-space: nowrap; }
    .links a:hover { color: var(--ink); }
    /* Le lien de la section à l'écran — même soulignement que le survol :
       c'est un état, posé par le scrollspy (aria-current="true" avec). */
    .links a.actif { color: var(--ink); }
    /* LE TRAIT S'OUVRE DEPUIS LE CENTRE (4 sept. 2026, demande de Dany : le
       soulignement animé de l'espace client d'un salon, sur la vitrine aussi).
       Il était posé d'un coup par `border-bottom-color`. Un pseudo-élément à
       l'échelle : sur les liens de premier niveau et sur les titres de groupe
       — `::before` pour ceux-ci, `::after` y dessine déjà le chevron. Jamais
       sur les liens des panneaux, qui ont leur propre rangée. */
    .links > a, .navd > .navd-btn { position: relative; }
    .links > a::after, .navd > .navd-btn::before {
      content: ''; position: absolute; left: 2px; right: 2px; bottom: -2px; height: 2px;
      background: var(--accent); transform: scaleX(0); transform-origin: center; transition: transform 180ms ease;
    }
    .links > a:hover::after, .links > a.actif::after, .links > a:focus-visible::after,
    .navd:hover > .navd-btn::before, .navd:focus-within > .navd-btn::before { transform: scaleX(1); }
    @media (prefers-reduced-motion: reduce) { .links > a::after, .navd > .navd-btn::before { transition: none; } }
    /* AU DOIGT, LA BARRE SUIT LA MÊME RÈGLE QUE LE RESTE DE LA PAGE
       (19.08.2026). Le trou était dans le SEUIL, pas dans les liens : sous
       1120 px, `.links` est masqué et le burger prend la main — et toutes
       ses cibles font 44 px (le résumé 44×44, les rangées du panneau 51).
       Au-dessus, le burger disparaît et rend la main à des liens de 38 px,
       or 1120-1366 px, c'est exactement l'iPad et la tablette Android EN
       PAYSAGE, c'est-à-dire un grand écran AVEC un doigt. Mesuré au doigt de
       320 à 1440 : aucune cible sous 44 px jusqu'à 1119, puis quatre à 38 px
       d'un coup à 1120, et « Connexion » à 23 px dès 1240. Dans la même
       barre, « Voir ma démo » fait 44 et le bouton jour/nuit 44×44 : les
       liens de navigation étaient les seuls à ne pas suivre.
       `min-height` et jamais un padding — c'est déjà l'argument écrit sous
       les liens du pied : un padding dépend de l'interligne, donc du zoom
       texte et de la police de repli ; la hauteur, non. Le `display: flex`
       n'est là que pour le centrage vertical, sans quoi les 44 px ne
       seraient cliquables que sur la ligne de texte.
       LE SÉLECTEUR EST ÉCRIT À LA SPÉCIFICITÉ DE SON JUMEAU — (0,1,1) pour
       `.links a`. Une media query n'ajoute AUCUNE force, et ce fichier
       s'est déjà fait prendre deux fois là-dessus (voir les commentaires du
       burger et du volet). (Le jumeau `.lien-connexion` est parti avec
       « Connexion » le 27.08.2026 — la règle tactile de la barre n'a plus
       qu'un client.)
       La barre ne grandit pas : elle vaut 96 px au repos et 60 compactée,
       donc 44 px y tiennent sans rien pousser — vérifié aux deux états. */
    .nav-cta { display: flex; align-items: center; gap: 14px; margin-left: 12px; }
    .nav-cta .btn { white-space: nowrap; padding: 12px 18px; }
    /* L'icône du jour/nuit reste à 15 px — mais la BOÎTE reste 44×44. WCAG
       2.2 SC 2.5.8 n'exige que 24×24, mais 44 est la convention que ce dépôt
       s'applique partout ailleurs sur cette page : rapetisser la cible pour
       rapetisser le dessin, ce serait échanger de l'accessibilité contre
       rien. La taille se pose en CSS et jamais sur l'attribut du SVG :
       theme.js RÉÉCRIT le contenu du bouton à chaque bascule.
       Scopé à `.nav-cta` et non à `.nav` : le bouton du panneau du burger,
       qui est aussi dans `.nav`, garde son icône à sa taille normale — c'est
       une rangée de menu, pas un objet de chrome à rendre discret. */
    .nav-cta .theme-toggle svg { width: 15px; height: 15px; }
    /* Le filet vertical entre les réglages (langue, thème) et l'action de
       vente : 1 px de var(--line), 20 px de haut — la même grammaire que les
       filets horizontaux de toute la page, à la verticale. Décor pur. */
    /* ⛔ LA BARRE SE RESSERRAIT LÀ OÙ ELLE DEVAIT S'OUVRIR. Mesuré : 26 px
       entre deux entrées du menu, et 16 px seulement entre la dernière entrée
       et le sélecteur de langue — donc l'écart RÉTRÉCIT au moment précis où
       l'on change de nature d'objet. (Le lien « Le prix », qui fermait alors
       la navigation, a quitté la barre le 5 sept. 2026 : le prix se fait sur
       devis et vit au pied de chaque page.)
       Le filet passe donc AVANT les outils, et il est le SEUL de la barre :
       elle se lit maintenant en trois zones — la marque, la navigation, les
       outils et l'action. Il était auparavant entre le bouton jour/nuit et
       l'appel à l'action, c'est-à-dire à l'intérieur d'un même groupe.
       EN `::before` ET NON EN ÉLÉMENT : `placerLangue` insère le sélecteur en
       PREMIER enfant de `.nav-cta` — un élément se serait retrouvé derrière
       lui. Un pseudo-élément ne peut pas être dépassé. */
    .nav-cta::before { content: ''; width: 1px; height: 22px; background: var(--line);
      flex: none; margin-right: 6px; }
    /* (« Connexion » a quitté la barre le 27.08.2026 — ses règles
       `.lien-connexion` et le cran 1239 qui la masquait sont partis avec
       elle : le lien du pied et celui du burger n'ont pas de classe.) */
    @media (pointer: coarse) {
      .links a { min-height: 44px; display: flex; align-items: center; }
    }

    .btn {
      display: inline-flex; align-items: center; justify-content: center; gap: 9px;
      padding: 12px 22px; border-radius: 999px;
      font: 600 0.95rem var(--gros); text-decoration: none; cursor: pointer;
      color: var(--ink); background: transparent; border: 1.5px solid var(--line-strong);
      transition: border-color .18s cubic-bezier(.22,1,.36,1), background .18s cubic-bezier(.22,1,.36,1), transform .18s cubic-bezier(.22,1,.36,1);
      min-height: 44px;
    }
    .btn:hover { border-color: var(--accent); transform: translateY(-1px); }
    .btn.primary { background: var(--accent-action); border-color: var(--accent-action); color: #fff; }
    .btn.primary:hover { background: var(--accent-texte); border-color: var(--accent-texte); }
    :root[data-theme="dark"] .btn.primary:hover { background: #1d4ed8; border-color: #1d4ed8; }
    .btn .picto { width: 18px; height: 18px; }
    /* Le survol des boutons échappait à `reduced-motion` — seul mouvement de
       la page à le faire, alors qu'elle y neutralise déjà l'entrée du héros,
       la révélation au défilement, le fondu jour/nuit et le lien d'évitement.
       On retire le déplacement, jamais le retour de survol : la couleur du
       filet reste, et elle n'est pas du mouvement. */
    @media (prefers-reduced-motion: reduce) {
      .btn { transition: border-color .18s, background .18s; }
      .btn:hover { transform: none; }
    }

    /* Menu mobile — details natif, pas de JS. */
    .nav-mobile { display: none; margin-left: auto; }
    .nav-mobile summary { list-style: none; display: flex; align-items: center; justify-content: center; width: 44px; height: 44px; border-radius: 10px; border: 1.5px solid var(--line-strong); cursor: pointer; color: var(--ink); }
    .nav-mobile summary::-webkit-details-marker { display: none; }
    .nav-mobile[open] summary { background: var(--surface-2); }
    /* ⛔ LE PANNEAU DOIT DÉFILER TOUT SEUL, ET IL NE LE FAISAIT PAS.
       Mesuré à 390 × 844 : le panneau ouvert fait 2 023 px, il porte 39 cibles
       et 13 seulement tiennent à l'écran. Les 26 autres — dont le sélecteur de
       langue, le bouton jour/nuit et « Voir mon commerce en démo » — étaient
       HORS D'ATTEINTE : ni la molette, ni un glissement du doigt, ni la touche
       Fin ne les amenaient. La raison est mécanique et se lit dans les deux
       lignes du dessus : le panneau est `absolute` dans un `header.nav` qui est
       `sticky`. Défiler bouge le DOCUMENT sous une barre qui, elle, reste à
       top:0 — le panneau reste donc collé où il est, et sa moitié basse pend
       sous l'écran sans que rien ne puisse l'y ramener. La dernière cible
       restait à y=2012 après un défilement jusqu'au fond du document.
       Le défaut PRÉEXISTE à la barre unique : les 141 pages engendrées le
       portaient déjà, à l'identique au pixel près (vérifié sur la version
       d'avant, servie à côté). L'accueil, qui n'avait pas de burger, en était
       la seule page indemne — et il l'a reçu en recevant la barre. Le
       correctif est le même pour les 142.
       `100vh` PUIS `100dvh` : la seconde ligne écrase la première là où elle
       est comprise, et sur un téléphone c'est elle qui compte — la barre
       d'adresse fait varier la hauteur utile, et `vh` la fige à sa valeur
       haute, ce qui redonnerait un panneau plus grand que l'écran.
       `overscroll-behavior: contain` : arrivé en bas du panneau, le geste ne
       doit pas se poursuivre en défilement de la page derrière. */
    .nav-mobile-panneau {
      position: absolute; left: 0; right: 0; top: var(--nav-h); z-index: var(--z-menu);
      background: var(--surface); border-bottom: 1px solid var(--line);
      display: flex; flex-direction: column; gap: 4px; padding: 16px clamp(18px, 4vw, 40px) 22px;
      box-shadow: 0 18px 40px -22px rgba(14, 20, 32, .25);
      max-height: calc(100vh - var(--nav-h));
      max-height: calc(100dvh - var(--nav-h));
      overflow-y: auto; overscroll-behavior: contain;
    }
    /* LES TITRES DE GROUPE DU BURGER. Même rôle que les `h4` des colonnes du
       bureau, même clé de traduction — ils étaient simplement jetés au
       passage sur téléphone. Ils ne portent PAS de filet : le filet appartient
       aux liens, et un titre souligné se lirait comme une entrée cliquable.
       La marge du haut n'est pas posée sur le PREMIER (`:first-child`), qui
       est déjà collé au bord du panneau. */
    .nav-mobile-panneau h4 {
      margin: 18px 0 2px; padding: 0 6px; font: 600 .74rem var(--gros);
      letter-spacing: .06em; text-transform: uppercase; color: var(--soft);
    }
    .nav-mobile-panneau h4:first-child { margin-top: 2px; }
    .nav-mobile-panneau a { padding: 12px 6px; text-decoration: none; color: var(--text); font-weight: 500; border-bottom: 1px solid var(--line); }
    .nav-mobile-panneau a:last-child { border-bottom: 0; }
    .nav-mobile-panneau .btn.primary { color: #fff; justify-content: center; margin-top: 8px; border-bottom: 0; }
    /* AU TÉLÉPHONE, le bouton de thème quitte la barre et devient une RANGÉE
       LIBELLÉE dans le panneau du burger — c'est là qu'il est « moins
       exposé » sans devenir introuvable. theme.js gère déjà l'attribut
       `data-label` (il écrit « Mode sombre » / « Mode clair » dans un
       .tt-lbl) : rien à modifier chez lui. Le motif et le style de .tt-lbl
       sont repris de la console (admin-plateforme.html).
       DEUX boutons dans le DOM, un seul visible par cran : mount() de
       theme.js attache le clic à TOUS les .theme-toggle et updateButtons()
       les resynchronise tous (icône, aria-label, title, aria-pressed).
       Gain rendu à la barre au téléphone : 44 + 12 = 56 px.
       Les deux portent le SVG de lune écrit en dur — sans JavaScript, un
       bouton vide serait un carré de 44 px sans dessin.
       SPÉCIFICITÉ (0,3,0) OBLIGATOIRE, et c'est un piège vérifié : la peau
       « fantôme » de la barre est déclarée `.nav .theme-toggle` (0,2,0) et
       vient PLUS BAS dans la feuille. À égalité de spécificité, c'est elle
       qui gagnait — le bouton du panneau restait donc figé à 44 × 44, son
       libellé passait sur deux lignes et son icône, écrasée par le
       `flex-shrink` par défaut, mesurait 0 px de large. Mesuré, puis
       corrigé par la spécificité ET par `flex: none` sur le dessin. */
    .nav .nav-mobile-panneau .theme-toggle {
      display: none;                    /* le cran <768 le montre */
      width: auto; height: auto; min-height: 44px;
      justify-content: flex-start; gap: 12px;
      padding: 12px 6px; border: 0; border-radius: 0;
      border-bottom: 1px solid var(--line);
      background: transparent; color: var(--text);
    }
    .nav .nav-mobile-panneau .theme-toggle svg { width: 17px; height: 17px; flex: none; }
    .nav .nav-mobile-panneau .theme-toggle .tt-lbl { font: 500 1rem var(--gros); }
    .nav .nav-mobile-panneau .theme-toggle:hover { color: var(--ink); transform: none; }
    .nav .nav-mobile-panneau .theme-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

    /* Le bouton jour/nuit, en FANTÔME : theme.js injecte sa propre feuille
       (.theme-toggle bordé, 36 px) APRÈS la nôtre — on le surclasse par
       spécificité (.nav .theme-toggle), sans toucher theme.js. Icône seule
       au repos (couleur --soft), fond + filet subtils au survol, jamais la
       rotation décorative du défaut. Cible 44×44 conservée. */
    .nav .theme-toggle {
      width: 44px; height: 44px;
      border-color: transparent;         /* le filet reste en place (pas de saut), il devient invisible */
      color: var(--soft);
      transition: color .15s, background .15s, border-color .15s;
    }
    .nav .theme-toggle:hover { color: var(--ink); background: var(--surface-2); border-color: var(--line); transform: none; }
    .nav .theme-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

    /* ————————————— LE SÉLECTEUR DE LANGUE (27.08.2026) —————————————
       Le nœud est celui de i18n.js (buildSwitch) ; ici on ne fait que
       l'habiller, aux deux endroits où il vit. Plus AUCUN `order` : depuis
       l'épure de la barre, l'ordre du DOM est le bon (buildSwitch insère le
       sélecteur en premier enfant, placeLangue l'y REPOSE en premier au
       retour du panneau) — l'ordre clavier et l'ordre visuel coïncident.
       LARGEUR : resserré d'un cran de plus (padding 3/5, corps 0.66rem) —
       ~92 px pour les quatre au lieu de 130. La Suisse parle quatre langues :
       le sélecteur reste DANS la barre, il ne descend au panneau que sous
       1120 px, là où la barre ne peut physiquement plus le porter.
       La HAUTEUR reste à 44 px : 1120-1366 px, c'est l'iPad en paysage, donc
       un grand écran AVEC un doigt — la même raison qui a mis les liens de
       navigation à 44 px.
       Les couleurs de i18n.js visent la peau SOMBRE d'un site de salon
       (--gold, --gold-soft) : sur la vitrine claire, elles n'existent pas et
       la règle retombe sur ses valeurs de repli, du doré sur off-white. On
       repasse donc sur les tokens de CETTE page. */
    /* 2 px de gouttière au lieu de 1 : l'encre pleine a besoin d'un liseré de
       fond autour d'elle pour ne pas toucher le filet du conteneur. */
    /* ⛔ LA PASTILLE ET LA GOUTTE N'ÉTAIENT PAS SOUS LA LETTRE (retour de Dany,
       5 sept. 2026 : « plus centré »). i18n.js pose `gap: 2px` entre les
       boutons ; la pastille avançait par pas de 38 px pendant que les boutons
       avançaient de 40 — 1 px d'écart sous « FR », 5 px sous « EN ». Et la
       gouttière de 2 px collait l'aplat au filet à gauche quand la droite
       gardait de l'air. Mesuré, puis corrigé aux deux endroits : plus de
       gouttière entre les boutons (le pas vaut la largeur), et 3 px de
       respiration tout autour, les mêmes des quatre côtés. */
    .nav .colabs-lang { padding: 3px; gap: 0; border-color: var(--line-strong); background: transparent; }
    .nav .colabs-lang button {
      /* `min-width` PARCE QUE « IT » EST PLUS ÉTROIT QUE « EN » : sans lui, la
         pastille active change de largeur selon la langue choisie, et le
         groupe bouge sous le doigt. 26 px aligne les quatre à ce corps de
         police, et reste au-dessus des 24 px de WCAG 2.5.8 même quand la
         police de repli s'applique. */
      /* 0,66rem valait 10,6 px — sous le plancher de lisibilité pour des
         capitales, et le sélecteur passe en premier écran sur la page la plus
         visitée du site. 0,72rem = 11,5 px, et la pastille l'accompagne. */
      min-height: 44px; min-width: 26px; padding: 3px 5px;
      font: 650 0.72rem var(--gros); letter-spacing: .045em;
      color: var(--soft);
    }
    /* Le survol d'un INACTIF ne pose plus d'aplat : l'aplat appartient
       désormais à l'actif, et deux aplats se confondraient. Il ne fait que
       remonter l'encre. */
    .nav .colabs-lang button:hover { color: var(--ink); background: none; }
    .nav .colabs-lang button[aria-current="true"]:hover { color: var(--bg); }
    /* LA LANGUE ACTIVE NE SE MARQUE PAS EN BLEU PLEIN, et c'est une décision
       de DA prise à l'écran : i18n.js remplit le bouton courant avec l'accent
       (`--gold`, ici `--accent-action`) — soit exactement la peau de « Voir
       ma démo », posée à 12 px de lui. Deux pastilles bleues côte à côte, et
       l'appel à l'action cesse d'être le seul objet plein de la barre. On
       retombe donc sur l'idiome de contrôle segmenté que la page tient déjà
       (`.va-seg span.on`) : un aplat de surface, l'encre pleine.
       Le sélecteur écrit `background` en INLINE ? Non — il ne pose que
       `--i18n-on-accent`, une variable ; la règle de sa feuille est battue
       par spécificité (0,3,1 contre 0,2,1). Vérifié au navigateur. */
    /* LA PASTILLE GLISSE ICI AUSSI, ET AVEC LA MÊME TEINTE QU'AVANT.
       Demande de Dany : « le sélecteur de langue joli et animé ». On ne
       touche pas aux couleurs — leur choix est argumenté dix lignes plus
       haut — on ne fait que sortir l'aplat du bouton pour le poser sous
       le groupe, où il peut voyager. Largeur FIXE par bouton : une
       pastille qui glisse ne peut pas changer de taille en route.
       `:has()` lit le rang de la langue courante ; aucun script ajouté. */
    .nav .colabs-lang { position: relative; }
    /* ⛔ LA PASTILLE ACTIVE ÉTAIT INVISIBLE, ET C'EST MESURÉ (4 sept. 2026).
       Elle était peinte en `--surface-2` : #f0f4fa sur un fond de barre
       #fafbfd, soit **1,07:1**. Autant dire rien. On ne savait pas quelle
       langue était active — la seule chose que ce contrôle a à dire.
       Le remède avait pourtant une bonne raison, écrite plus haut : i18n.js
       remplit le bouton courant avec l'accent, et deux pastilles bleues à
       12 px de « Voir ma démo » lui auraient volé son rôle d'unique objet
       plein. Vrai. Mais la sortie n'était ni le bleu ni le lavis : c'est une
       TROISIÈME MATIÈRE. L'encre pleine n'est pas chromatique, elle n'entre
       donc en concurrence avec aucun bleu — et elle se lit à **17,80:1**.
       Elle s'inverse toute seule d'un thème à l'autre, puisqu'elle est faite
       des jetons : encre sur papier en clair (17,80:1), papier sur encre en
       sombre (17,44:1). Aucune couleur n'est écrite en dur.
       C'est aussi ce que faisait l'ancien sélecteur du pied, que Dany donne
       en référence : là-bas c'était un aplat doré plein — un APLAT, c'est le
       point. Ici l'aplat est neutre parce que la barre porte déjà du bleu. */
    .nav .colabs-lang::before { content: ''; position: absolute; z-index: 0;
      top: 6px; bottom: 6px; width: 38px; left: calc(3px + var(--i, 0) * 38px);
      border-radius: 999px; background: var(--ink);
      transition: left .38s cubic-bezier(.22, 1, .36, 1); }
    .nav .colabs-lang:has(button:nth-child(1)[aria-current="true"]) { --i: 0; }
    .nav .colabs-lang:has(button:nth-child(2)[aria-current="true"]) { --i: 1; }
    .nav .colabs-lang:has(button:nth-child(3)[aria-current="true"]) { --i: 2; }
    .nav .colabs-lang:has(button:nth-child(4)[aria-current="true"]) { --i: 3; }
    .nav .colabs-lang button { position: relative; z-index: 1; width: 38px; }
    /* `background: none` OBLIGATOIRE : i18n.js peint le bouton courant avec sa
       propre variable d'accent. Sans cette remise à zéro, un aplat doré de
       site de salon se poserait par-dessus notre encre. */
    .nav .colabs-lang button[aria-current="true"] { background: none; color: var(--bg); }
    /* LA GOUTTE D'EAU (5 sept. 2026, demande de Dany : « une animation de
       bulle quand on passe dessus, qui grossit, comme s'il y avait de l'eau »).
       Deux gestes, aucun script.
       ① LA LETTRE GROSSIT SOUS LE DOIGT — c'est une loupe. `scale(1.32)` avec
          une courbe qui DÉPASSE puis revient (.34,1.56,.64,1) : la goutte se
          pose, tremble, s'immobilise. Une courbe sans dépassement donnerait
          un zoom d'interface, pas de l'eau.
       ② UNE GOUTTE SE FORME SOUS LA LANGUE SURVOLÉE : un disque au reflet
          blanc en haut à gauche (la lumière d'une fenêtre sur de l'eau), une
          ombre portée à l'intérieur en bas à droite (le ménisque, plus dense
          au bord), et un liseré d'encre à 16 %. Elle GLISSE d'une langue à
          l'autre au lieu de renaître : `:has()` lit le rang du bouton
          survolé, comme la pastille d'encre lit celui de la langue active.
          Quand le doigt part, elle disparaît d'un coup : une goutte qui
          glisserait vers « FR » en s'effaçant serait un défaut, pas un effet.
       Le reflet reste BLANC dans les deux thèmes : c'est un reflet, pas une
       couleur — et sur l'encre de la langue active, c'est justement lui qui
       fait lire la surface comme mouillée. Le liseré et l'ombre, eux, sont
       faits de l'encre du thème : ils s'inversent avec elle.
       Ni sur un écran tactile (pas de survol : la goutte ne naîtrait que pour
       rester collée), ni dans le panneau du burger, ni sous reduced-motion. */
    .nav .colabs-lang::after {
      content: ''; position: absolute; z-index: 2; pointer-events: none;
      top: 50%; width: 36px; height: 36px; margin-top: -18px;
      left: calc(4px + var(--h, 0) * 38px);
      border-radius: 50%;
      background:
        radial-gradient(circle at 34% 28%, rgba(255, 255, 255, .62) 0, rgba(255, 255, 255, .22) 24%, rgba(255, 255, 255, 0) 46%),
        radial-gradient(circle at 50% 50%, color-mix(in srgb, var(--ink) 3%, transparent) 55%, color-mix(in srgb, var(--ink) 13%, transparent) 78%, transparent 82%);
      box-shadow:
        inset 0 0 0 1px color-mix(in srgb, var(--ink) 16%, transparent),
        inset -2px -3px 5px color-mix(in srgb, var(--ink) 10%, transparent),
        0 3px 8px -3px color-mix(in srgb, var(--ink) 30%, transparent);
      opacity: 0; transform: scale(.4);
      transition: transform .45s cubic-bezier(.34, 1.56, .64, 1), opacity .22s ease, left .38s cubic-bezier(.22, 1, .36, 1);
    }
    .nav .colabs-lang:hover::after { opacity: 1; transform: scale(1); }
    .nav .colabs-lang:not(:hover)::after { transition: none; }
    .nav .colabs-lang:has(button:nth-child(1):hover) { --h: 0; }
    .nav .colabs-lang:has(button:nth-child(2):hover) { --h: 1; }
    .nav .colabs-lang:has(button:nth-child(3):hover) { --h: 2; }
    .nav .colabs-lang:has(button:nth-child(4):hover) { --h: 3; }
    .nav .colabs-lang button { transition: transform .45s cubic-bezier(.34, 1.56, .64, 1), color .2s ease; transform-origin: 50% 50%; }
    .nav .colabs-lang button:hover { transform: scale(1.32); }
    .nav .nav-mobile-panneau .colabs-lang::after { display: none; }
    .nav .nav-mobile-panneau .colabs-lang button:hover { transform: none; }
    @media (hover: none) { .nav .colabs-lang::after { display: none; } .nav .colabs-lang button:hover { transform: none; } }
    @media (prefers-reduced-motion: reduce) {
      .nav .colabs-lang::before, .nav .colabs-lang::after, .nav .colabs-lang button { transition: none; }
      .nav .colabs-lang button:hover { transform: none; }
    }
    .nav .colabs-lang button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
    /* DANS LE PANNEAU DU BURGER (sous 1120 px) : une rangée pleine largeur,
       posée sous la rangée « Mode sombre » — le même geste au même endroit.
       Le filet du bas la rattache à la pile de liens ; les quatre boutons se
       partagent la largeur, donc chacun fait bien plus que 44 px de côté. */
    .nav .nav-mobile-panneau .colabs-lang {
      order: 0; display: flex; width: 100%; gap: 4px;
      margin: 8px 0 0; padding: 0; border: 0;
    }
    /* ⛔ PAS DE PASTILLE GLISSANTE DANS LE PANNEAU, et ce n'est pas un goût.
       La pastille de la barre avance par pas de 38 px, la largeur d'un bouton
       LÀ-BAS. Ici les quatre boutons s'étalent sur toute la largeur : 86 px
       chacun. Prouvé à 390 px, allemand actif — la pastille se posait à x=78,
       sous « IT », pendant que « DE » commençait à x=179. Un aplat d'encre
       sous la MAUVAISE langue. Tant qu'elle était peinte en `--surface-2`
       (1,07:1) personne ne pouvait le voir ; en encre pleine, si.
       Le panneau n'en a d'ailleurs aucun besoin : chaque bouton y porte déjà
       son filet, et l'actif son lavis d'accent. */
    .nav .nav-mobile-panneau .colabs-lang::before { display: none; }
    .nav .nav-mobile-panneau .colabs-lang button {
      flex: 1 1 0; min-height: 44px; padding: 10px 4px;
      border: 1.5px solid var(--line-strong); border-radius: 10px;
    }
    /* Dans le panneau, chaque bouton a déjà son filet : la langue active se
       marque donc par l'accent, comme le lien de la section à l'écran dans la
       barre (`.links a.actif`). Mesuré : 7,2:1 dans les deux thèmes. */
    .nav .nav-mobile-panneau .colabs-lang button[aria-current="true"] {
      background: var(--accent-lavis); color: var(--accent-texte); border-color: var(--accent);
    }
    /* TROIS crans DESSINÉS (quatre avant l'épure du 27.08.2026 — le cran
       1240, qui ne servait qu'à masquer « Connexion », est parti avec elle),
       et toujours MONOTONES : en rétrécissant, rien ne disparaît puis ne
       réapparaît.
       ≥1120 tout · 768–1119 burger + CTA + thème (les liens et la langue
       descendent au panneau) · <768 burger seul, le CTA vit dans le panneau
       et dans le héros, et le bouton de thème descend dans le panneau.
       + un cran de MARQUE sous 400 px : sans lui, « Co-Labs.ch » + le
       drapeau monté à 20 px ne laisseraient que quelques px de mou à 320. */
    @media (max-width: 480px) {
      /* La gouttière resserrée reste utile : elle rend 6 px à une barre qui
         porte encore le logo, le burger et le bouton de thème. */
      .nav .wrap { gap: 10px; }
    }
    @media (max-width: 1119px) {
      .links { display: none; }
      .nav-mobile { display: block; margin-left: 0; }
      /* LE SÉLECTEUR DE LANGUE N'APPARAÎT JAMAIS DANS LA BARRE SOUS CE CRAN,
         MÊME UNE IMAGE. `i18n.js` le fabrique dans `.header-actions` ; c'est
         `placerLangue` (vitrine-langue.js) qui le descend ensuite dans le
         panneau du burger. Entre les deux il y a un tour de rendu, et à 390 px
         ce tour-là coûtait 45 px de débord horizontal — invisible à l'œil,
         mesurable à la sonde, et bien réel sur un téléphone lent.
         Une règle CSS ferme la fenêtre : tant qu'il est dans la barre sous
         1120 px, il ne s'affiche pas. Le panneau, lui, le montre
         (`.nav .nav-mobile-panneau .colabs-lang`, plus bas). Bonus : sans
         JavaScript, la barre reste correcte au lieu de déborder. */
      .nav-cta .colabs-lang { display: none; }
      .nav-cta { margin-left: auto; }
    }
    @media (max-width: 767px) {
      .nav-cta .btn { display: none; }
      /* Le CTA parti, le filet ne séparerait plus rien. */
      .nav-cta::before { display: none; }
      /* Le bouton de thème change d'hôte : il sort de la barre et devient la
         rangée « Mode sombre » du panneau.
         LES DEUX SÉLECTEURS SONT ÉCRITS À LA SPÉCIFICITÉ DE LEUR JUMEAU
         HORS MEDIA QUERY — une media query n'ajoute AUCUNE force, et le
         dépôt s'est déjà fait avoir deux fois là-dessus. Ici la règle de
         base est `.nav .nav-mobile-panneau .theme-toggle` (0,3,0) : écrite
         en (0,2,0), la règle d'affichage perdait et le bouton restait
         invisible AU TÉLÉPHONE, c'est-à-dire précisément là où il est le
         seul disponible. Constaté à l'écran, pas déduit. */
      .nav-cta .theme-toggle { display: none; }
      .nav .nav-mobile-panneau .theme-toggle { display: inline-flex; }
      /* Sur téléphone, « logo plus gros » = « barre plus haute », et 96 px
         vaudraient 11,8 % d'un écran de 812 px. D'où 76 px au repos — et un
         compactage qui rend 20 px dès le premier geste (contre 10 avant) :
         le mouvement demandé sert PLUS ici que sur ordinateur. */
      .nav { --nav-h: 76px; --logo-h: 52px; }
      .nav.compacte { --nav-h: 56px; --logo-h: 36px; }
    }
    /* LE CRAN DE MARQUE (<400, nouveau le 27.08.2026) : le logo descend à
       44 px de haut (≈74 de large) et le nom à 1.2rem. Sans lui,
       « Co-Labs.ch » + le drapeau monté à 20 px feraient DÉBORDER la barre à
       320 px (la marque a grossi de ~39 px). ÉCRIT APRÈS le cran <768, et ce
       n'est pas un hasard : les deux posent `--logo-h` sur `.nav` à
       spécificité égale — plus haut dans la feuille, le 52 px du cran <768
       l'emporterait et ce cran serait mort. MESURÉ à 320 : marque 212 px,
       burger 44, mou 8 — serré mais sans débord (scrollWidth = 320). */
    @media (max-width: 399px) {
      .nav { --logo-h: 44px; }
      .brand b, .brand-ch { font-size: 1.2rem; }
    }

    /* ══ LE MENU REVIENT DANS SA SOURCE (3 sept. 2026) ═══════════════════
       Ces règles vivaient dans `coquille.py`, ajoutées à la feuille partagée
       APRÈS l'extraction. L'accueil ne les avait donc pas — et le jour où il a
       retrouvé la barre, il a reçu le balisage du menu sans son habillage :
       les panneaux se sont dépliés dans le flux et la page débordait de 45 px
       à 390 px (mesuré ; zéro débord sur les 141 autres, qui, elles, avaient
       le CSS). Le défaut était invisible tant que l'accueil n'avait pas de
       menu, ce qui est la pire espèce.
       Elles remontent ici, DANS le bloc NAVIGATION, donc au-dessus de la
       sentinelle : l'extraction les emporte vers `vitrine-coquille.css`, et il
       n'y a plus qu'UNE copie — l'accueil redevient la source de sa coquille,
       ce qu'il est pour tout le reste de cette feuille.
       Ce qui RESTE dans `coquille.py` (désormais `CSS_PAGES`) : la DA des
       pages intérieures — `.page-hero`, `.ledger`, `.oppo`. L'accueil ne porte
       aucun de ces objets ; les lui donner aurait été du poids mort. */

    /* ══ MENU DÉROULANT (31.08.2026) ══════════════════════════════════════
       Trois entrées de tête ouvrent un panneau, une quatrième est un lien nu.
       OUVERTURE SANS JAVASCRIPT : `:hover` à la souris, `:focus-within` au
       clavier. Le script n'ajoute qu'`aria-expanded` et la fermeture par Échap.
       Le panneau est en `visibility` + `opacity` et non en `display:none` :
       une transition ne s'anime pas depuis `display`, et `visibility` garde le
       contenu hors de l'ordre de tabulation tant qu'il est fermé. */
    .nav .links { position: relative; }
    .navd { position: relative; }
    .navd > .navd-btn {
      display: inline-flex; align-items: center; gap: 5px;
      border: none; background: none; padding: 0; margin: 0;
      font: inherit; color: inherit; cursor: pointer;
    }
    .navd > .navd-btn::after {
      content: ''; width: 6px; height: 6px; margin-top: -3px;
      border-right: 1.6px solid currentColor; border-bottom: 1.6px solid currentColor;
      transform: rotate(45deg); opacity: .55; transition: transform .18s, opacity .18s;
    }
    .navd:hover > .navd-btn::after,
    .navd:focus-within > .navd-btn::after { opacity: 1; transform: rotate(225deg) translate(-2px, -2px); }
    .navd-panneau {
      position: absolute; top: calc(100% + 14px); left: 50%; transform: translate(-50%, -6px);
      z-index: 60; padding: 20px 22px; border-radius: 16px;
      background: var(--surface); border: 1px solid var(--line);
      box-shadow: 0 18px 48px rgba(15, 26, 45, .14), 0 2px 6px rgba(15, 26, 45, .06);
      opacity: 0; visibility: hidden; transition: opacity .16s ease, transform .16s ease, visibility .16s;
      display: grid; gap: 8px 30px; text-align: left;
    }
    [data-theme="dark"] .navd-panneau { box-shadow: 0 18px 48px rgba(0, 0, 0, .5); }
    .navd:hover > .navd-panneau,
    .navd:focus-within > .navd-panneau { opacity: 1; visibility: visible; transform: translate(-50%, 0); }
    /* Le pont : sans lui, le curseur traverse 14 px de vide entre le bouton et
       le panneau, et le menu se referme sous la souris. */
    .navd-panneau::before { content: ''; position: absolute; left: 0; right: 0; top: -16px; height: 16px; }
    .navd-col > h4 {
      margin: 0 0 7px; font-size: .74rem; font-weight: 700; letter-spacing: .07em;
      text-transform: uppercase; color: var(--soft);
    }
    .navd-col a {
      display: block; padding: 6px 9px; margin: 0 -9px; border-radius: 8px;
      font-size: .93rem; font-weight: 500; color: var(--text); text-decoration: none;
      white-space: nowrap; transition: background .13s, color .13s;
    }
    .navd-col a:hover, .navd-col a:focus-visible { background: var(--accent-lavis); color: var(--accent-texte); }
    .navd-col a[aria-current="page"] { color: var(--accent-texte); font-weight: 700; }
    /* LES PISTES SONT EN `max-content`, PAS EN `1fr`. Un panneau en position
       absolue n'a pas de largeur propre : avec `minmax(0, 1fr)` les pistes
       s'effondraient et les colonnes se chevauchaient (mesuré au survol des
       métiers). `max-content` laisse chaque colonne prendre la largeur de son
       plus long libellé, et le panneau se dimensionne sur la somme. */
    .navd-panneau { width: max-content; max-width: min(92vw, 860px); }
    /* ⛔ LE PANNEAU À QUATRE COLONNES DÉBORDAIT DE SON PROPRE CADRE (retour de
       Dany, 5 sept. 2026, capture à l'appui) : les quatre pistes en
       `max-content` faisaient plus de 860 px, le plafond les rognait, et
       « L'API et les webhooks » touchait le filet de droite sans marge. Le
       plafond monte pour lui seul ; sous 1120 px le menu est un burger, donc
       92vw ne mord jamais avant 1 000 px de large. Et les colonnes sont
       SÉPARÉES : un filet vertical entre elles, avec la même respiration des
       deux côtés — la gouttière de la grille se partage entre les deux. */
    .navd-panneau.quatre { grid-template-columns: repeat(4, max-content); max-width: min(92vw, 1100px); gap: 8px 0; }
    /* Et ce panneau-là ne se centre plus sous SON bouton mais sous la
       NAVIGATION entière : centré sous « Le back-office », 878 px de large, il
       sortait de l'écran de 16 px à 1 120 px (le dernier cran avant le
       burger). `.links` est déjà `position: relative` ; il suffit que cette
       entrée cesse de l'être pour que le panneau se cale sur le groupe. */
    .navd:has(> .navd-panneau.quatre) { position: static; }
    .navd-panneau.quatre .navd-col { padding: 0 26px; }
    .navd-panneau.quatre .navd-col:first-child { padding-left: 0; }
    .navd-panneau.quatre .navd-col:last-child { padding-right: 0; }
    /* ⛔ ET IL RESSORTAIT À GAUCHE EN ITALIEN (18 sept. 2026) : le bouton de la
       barre s'est allongé (« Prova a prenotare », 163 px contre 117), tout le
       groupe `.links` glisse d'autant vers la gauche, et le panneau de 968 px
       centré dessous passait à −8 px de l'écran entre 1 120 et 1 135 px —
       mesuré en italien seulement, les trois autres langues gardant 2 px et
       plus. Sous 1 240 px les colonnes respirent moins : 6 gouttières × 8 px
       ramènent le panneau à 920 px, et il rentre. Au-delà, rien ne change. */
    @media (max-width: 1240px) {
      .navd-panneau.quatre .navd-col { padding: 0 18px; }
    }
    .navd-panneau.quatre .navd-col + .navd-col { border-left: 1px solid var(--line); }
    .navd-panneau.deux { gap: 8px 0; }
    .navd-panneau.deux .navd-col + .navd-col { border-left: 1px solid var(--line); padding-left: 26px; }
    .navd-panneau.deux .navd-col:first-child { padding-right: 26px; }
    .navd-panneau.deux { grid-template-columns: repeat(2, max-content); }
    .navd-panneau.une { grid-template-columns: max-content; }
    /* Les métiers : UNE colonne titrée, dont les liens s'étalent sur deux. */
    /* PAS de `> span` ici : `.navd-panneau.une .navd-col > span` pèse (0,3,1)
       et battait `.navd-panneau.une .navd-liens` (0,3,0) — la grille des
       métiers retombait en une seule colonne de seize lignes. Un élément dans
       un sélecteur suffit à renverser une règle plus spécifique en apparence ;
       c'est le même piège que les media queries qui n'ajoutent rien. */
    .navd-panneau.une .navd-col { display: block; }
    .navd-panneau.une .navd-col a { display: block; }
    .navd-panneau.une .navd-liens {
      display: grid; grid-template-columns: repeat(2, max-content); gap: 0 26px;
    }
    @media (max-width: 560px) { .navd-panneau.une .navd-liens { grid-template-columns: max-content; } }
    @media (prefers-reduced-motion: reduce) {
      .navd-panneau { transition: none; }
      .navd > .navd-btn::after { transition: none; }
    }
    /* Sous le cran du burger, les panneaux ne servent plus : le menu mobile
       liste tout à plat, et deux mécaniques d'ouverture au même endroit se
       marchent dessus. */
    @media (max-width: 1119px) { .navd-panneau { display: none; } }
/* ============================== PIED ================================ */
    footer { border-top: 1px solid var(--line); padding: 44px 0 54px; }
    footer .wrap { display: flex; align-items: flex-start; gap: 30px; flex-wrap: wrap; }
    /* La signature du pied : le logo en plus grand qu'en tête — c'est lui
       qui referme la page. L'asset fait 320 px de haut : net jusqu'en 3×. */
    footer .fin-marque { display: flex; align-items: center; gap: 13px; color: var(--ink); }
    footer .fin-marque img { height: 50px; width: auto; }
    footer .fin-marque span { line-height: 1.3; }
    footer .fin-marque b { font-weight: 800; font-size: 1.22rem; letter-spacing: -0.015em; }
    footer .fin-marque small { display: block; font-size: 0.74rem; color: var(--soft); }
    footer .copyright { font-size: 0.82rem; color: var(--soft); margin-top: 10px; }
    /* LA LIGNE DE CONFORMITÉ — celle qui a remplacé les trois badges du
       héros. Le pied est le seul bloc de la page qui pouvait les accueillir
       sans rien déplacer (541 px de libre entre la marque et les liens), et
       c'est déjà la famille exacte de ces phrases : il porte « Réceptionniste
       IA · Suisse » et « Genève, Suisse ».
       `--soft` mesure 5,91:1 en clair et 6,65:1 en sombre : aucun recalcul.
       DEUX LIENS ICI, ET PAS UN DE PLUS (19.08.2026) : « Hébergé en Suisse »
       et « Conforme nLPD » mènent à #confiance, qui n'avait plus aucun lien
       entrant depuis l'épure. Les deux autres mentions restent des <span> :
       une ligne de mentions n'est toujours pas un menu, et seules CES deux
       questions-là ont une réponse développée plus haut (le CHF et les quatre
       langues sont tenus au fil du texte, pas dans une section à eux).
       Souligné à la demande — pas de lien muet dans une ligne grise —, et la
       couleur reste `--soft` : mesuré 5,91:1 en clair, 6,65:1 en sombre,
       inchangé. */
    footer .fin-conformite { margin-top: 6px; font-size: 0.8rem; line-height: 1.6; color: var(--soft); max-width: 46ch; }
    footer .fin-conformite span, footer .fin-conformite a { white-space: nowrap; }
    footer .fin-conformite a { color: inherit; text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--line-strong); }
    footer .fin-conformite a:hover { color: var(--accent-texte); text-decoration-color: currentColor; }
    footer .fin-conformite a:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 3px; }
    .foot-links { margin-left: auto; display: grid; grid-template-columns: repeat(2, auto); gap: 8px 34px; }
    .foot-links a { color: var(--text); text-decoration: none; font-size: 0.9rem; padding: 4px 0; }
    .foot-links a:hover { color: var(--accent-texte); }
    @media (max-width: 640px) {
      footer .wrap { flex-direction: column; gap: 26px; }
      .foot-links { margin-left: 0; gap: 2px 30px; }
    }
    /* LA CIBLE TACTILE DÉPEND DU DOIGT, PAS DE LA LARGEUR (corrigé le
       19.08.2026). Le plancher de 44 px vivait sous `max-width: 640px` SEUL :
       mesuré sur un appareil tactile émulé, ces six liens faisaient 45 px à
       640 et 31 px dès 641 — donc 13 px sous le plancher que le dépôt
       s'applique partout ailleurs, sur une tablette tenue en main comme sur
       un portable tactile, à toutes les largeurs jusqu'à 1 119 px et au-delà.
       Le rail d'onglets du poste avait déjà tranché la question de la même
       façon (`@media (pointer: coarse) { .va-item { min-height: 44px } }`) :
       on reprend son motif, en UNION avec le cran de largeur pour couvrir
       aussi une fenêtre étroite à la souris.
       `min-height` et non un `padding` calculé : un padding dépend de
       l'interligne, donc du zoom texte et de la police de repli — la hauteur,
       non. Le centrage vertical est ce qui rend les 44 px cliquables sur
       toute leur surface et pas seulement sur la ligne de texte. */
    @media (max-width: 640px), (pointer: coarse) {
      .foot-links a { min-height: 44px; display: flex; align-items: center; }
    }

    /* (LA TRACE CRÈME `.fil-trace` A ÉTÉ SUPPRIMÉE le 20.08.2026, avec la
       scène #appel dont elle faisait partie. Trois raisons, dans l'ordre :
       elle pesait 24 des 69 éléments de la scène — 35 % de la structure —
       pour 34 mots, le pire rapport de la page ; elle était le DOUBLON
       LITTÉRAL du volet « Appels IA » de #admin, même première ligne au
       caractère près, donc la retirer ne fait perdre AUCUNE information ; et
       sa peau crème n'était jamais basculée en thème sombre (le script ne
       bascule que `.v-admin.peau-produit`), soit un rectangle à 16,2:1 au
       milieu d'un écran sombre — exactement le reproche fait à la bande
       bleue. Il en reste huit mots et un renvoi, dans `#etabli .fil-pied`.
       `.va-badge` est partie avec lui (18 sept. 2026) : elle citait des
       jetons `--w-*` déclarés nulle part, et `#admin` n'existe plus. */

    /* ── LA SUITE ─────────────────────────────────────────────────────────
       Pas des pastilles : une rangée de renvois en encre pleine, sur un filet.
       Une fin de page doit proposer la lecture suivante, pas des boutons. */
    .suite { border-top: 1px solid var(--line); padding: clamp(26px, 3.4vw, 44px) 0 clamp(38px, 5vw, 70px);
      display: flex; flex-wrap: wrap; gap: 10px clamp(24px, 4vw, 52px); }
    .suite a {
      display: inline-flex; align-items: baseline; gap: 9px;
      font-size: clamp(1.02rem, 1.5vw, 1.16rem); font-weight: 700; letter-spacing: -.015em;
      color: var(--ink); text-decoration: none;
    }
    .suite a::after { content: '→'; color: var(--accent); transition: transform .22s cubic-bezier(.22,1,.36,1); }
    .suite a:hover { color: var(--accent-texte); }
    .suite a:hover::after { transform: translateX(4px); }

    /* ── LES DESSINS ──────────────────────────────────────────────────────
       Un trait de crayon n'est PAS un contour : c'est une surface dont la
       largeur varie le long de la course (scripts/crayon.py). Les chemins
       sont donc REMPLIS, jamais tracés — `stroke` rendrait au trait
       l'épaisseur constante que tout le moteur sert à lui enlever.
       `.fond` est le ventre opaque d'un objet : ce qui passe derrière lui ne
       le traverse pas. Il prend la couleur du FOND, donc il suit le thème. */
    .fig { display: block; width: 100%; height: auto; overflow: visible;
      position: relative; z-index: 2; pointer-events: none; }
    .fig .trait { fill: var(--ink); }
    .fig .fond { fill: var(--bg); }
    /* La figure du héros : le dessin, puis la carte qu'il mord. La marge
       négative vaut le recouvrement déclaré par le générateur (16 unités de
       cadre) — les deux ne peuvent pas diverger, elle est calculée dessus. */
    .fig-bloc { margin: clamp(28px, 4vw, 52px) auto 0; max-width: 640px; }
    /* SUR UNE PAGE INTÉRIEURE, LE DESSIN SE RANGE SUR LA PROSE. Centré, il
       flottait à droite du bord gauche du texte et se lisait comme une image
       déposée là. Il prend donc la MÊME mesure que les paragraphes — même
       corps, même 66ch — de sorte que les deux ne peuvent pas diverger quand
       l'un des deux bouge. */
    .sect > .fig-bloc { margin-left: 0; margin-right: auto;
      font-size: 1.03rem; max-width: 66ch; }
    .metiers-suite { margin: 16px 0 0; }
    .metiers-suite a { display: inline-flex; align-items: baseline; gap: 9px; font-weight: 700;
      font-size: .98rem; letter-spacing: -.015em; color: var(--ink); text-decoration: none; }
    .metiers-suite a::after { content: '→'; color: var(--accent); transition: transform .22s cubic-bezier(.22,1,.36,1); }
    .metiers-suite a:hover { color: var(--accent-texte); }
    .metiers-suite a:hover::after { transform: translateX(4px); }
    @media (prefers-reduced-motion: reduce) { .metiers-suite a::after { transition: none; } }
    section .suite { padding-bottom: 0; }
    @media (max-width: 760px) {
      .suite { gap: 2px clamp(24px, 4vw, 52px); }
      .suite a, .metiers-suite a { min-height: 44px; align-items: center; }
      .page-hero .fil a { display: inline-flex; align-items: center; min-height: 44px; }
      footer .fin-conformite a, footer .fin-conformite span {
        display: inline-block; padding: 8px 0;
      }
      footer .fin-conformite { line-height: 1.2; }
    }
/* ==================== RÉVÉLATION AU DÉFILEMENT ======================
       Règle dure : rien n'est JAMAIS caché par défaut. Le voile n'existe
       que si le script d'entête a posé `rev-actif` sur <html> — ce qu'il
       ne fait ni sans JS, ni en reduced-motion, ni sans IntersectionObserver.
       Une fois révélé (`.vu`), c'est acquis : on n'observe plus. */
    .rev-actif [data-rev] {
      opacity: 0;
      transform: translateY(14px);
      transition: opacity .6s cubic-bezier(.22,1,.36,1), transform .6s cubic-bezier(.22,1,.36,1);
      transition-delay: var(--rd, 0ms);
    }
    .rev-actif [data-rev="g"] { transform: translateX(-18px); }   /* la chronologie glisse */
    .rev-actif [data-rev="pop"] { transform: translateY(8px) scale(.96); } /* bulles et vignettes */
    .rev-actif [data-rev].vu { opacity: 1; transform: none; }
    @media (prefers-reduced-motion: reduce) {
      .suite a::after { transition: none; }
      .rev-actif [data-rev] { opacity: 1; transform: none; transition: none; }
    }
    /* UNE PAGE QU'ON IMPRIME N'EST PAS DÉFILÉE. Sans ce bloc, tout ce qui
       attend son tour sort blanc : mesuré sur l'accueil, 914 mots absents
       d'une impression lancée depuis le haut de la page. Le voile est un
       réglage de LECTURE À L'ÉCRAN ; sur papier il n'a aucun sens. */
    @media print {
      .rev-actif [data-rev] { opacity: 1; transform: none; transition: none; }
    }
/* ===================== LE FONDU JOUR / NUIT =========================
       La classe `theme-fondu` ne vit que ~450 ms autour du basculement
       (posée par le script en pied de page) — JAMAIS en permanence : une
       transition globale permanente ferait fondre le premier rendu.
       `:root.theme-fondu *` (0,2,0) gagne sur `.rev-actif [data-rev]` et
       les transitions locales LE TEMPS du fondu — voulu : tout fond d'un
       seul mouvement, rien ne traîne. Les images (PNG du logo) ne sont pas
       concernées ; les pictos suivent la couleur du texte, c'est le but. */
    @media (prefers-reduced-motion: no-preference) {
      :root.theme-fondu body,
      :root.theme-fondu * {
        transition: background-color .4s ease, color .4s ease, border-color .4s ease, box-shadow .4s ease;
      }
    }

    /* ══ `hidden` DOIT GAGNER. QUATRIÈME FOIS QUE CE PIÈGE MORD ═════════════
       La règle du navigateur (`[hidden] { display: none }`) vit dans SA
       feuille : n'importe quelle déclaration d'auteur la bat, à n'importe
       quelle spécificité. Le fichier s'est fait prendre sur `.va-panel`, puis
       sur `.fl-decrocher` et `.fil-etat` — et une fois de plus le 20.08.2026 :
       `#st-attente-btn` portait bien `hidden` dans le document, mais
       `.btn { display: inline-flex }` le rendait quand même. Sans JavaScript,
       la commande était donc visible, tabulable, cliquable — et morte.
       LA PARADE EST ICI, UNE FOIS POUR TOUTES : l'attribut répété porte la
       règle à 0,2,0, ce qui bat toute règle de classe simple, sans recourir à
       `!important` — le fichier n'en compte aucun et n'en aura pas. Posée en
       DERNIER pour gagner aussi les égalités. */
    [hidden][hidden] { display: none; }
    /* ══════════════════════════════════════════════════════════════════════
       LA PAGE INTÉRIEURE — direction artistique (refaite le 18.09.2026).

       LA SCÈNE : une patronne de 46 ans, dimanche soir, à sa table de cuisine,
       qui a lu l'accueil et clique « Liah au téléphone » pour comprendre ce
       qu'elle y gagne. Elle vient d'une page qui lui MONTRE le produit
       (sept écrans annotés, des titres qui parlent d'elle) : la page qu'elle
       ouvre doit continuer la même conversation, pas changer de registre.

       CE QUE CES PAGES ÉTAIENT (brief du 31.08) : « un document de
       spécification, pas une brochure », dont la singularité était
       « d'imprimer ce qu'elles ne savent PAS faire ». Mesuré le 18.09 : un
       héros à gauche sans aucune action, une colonne de 605 px dans un cadre
       de 1 140 (47 % de vide), zéro écran du produit, huit sections
       identiques d'affilée, et pour seul objet récurrent le bloc hachuré du
       manque — 66 fois sur dix-neuf pages. La forme que l'œil retenait était
       la forme du manque.

       CE QU'ELLES SONT : la suite de l'accueil, en profondeur. Une grammaire
       en cinq temps — le héros CENTRÉ, court, avec l'action dès le premier
       écran · LA PLANCHE (le même écran que l'accueil, ré-annoté pour la
       page) · des sections à la deuxième personne, la prose à gauche et
       l'objet à droite · le relevé des garanties, en positif · une clôture
       avec l'unique bloc « pas encore » et les deux boutons. L'honnêteté ne
       part pas : elle se dit UNE fois par page, à sa place, et la page des
       limites porte le reste.

       AUCUNE COULEUR, AUCUNE POLICE, AUCUN RAYON NEUF : tout vient des jetons
       de l'accueil. Un seul système de titres (graisse 700, interlettrage
       −.02em, comme le h1 du héros et le h2 des planches). Les proportions
       suivent le nombre d'or (colonnes 1,618 / 1 ; pas 8·13·21·34·55·89 ;
       interligne 1,618 sur la prose) — la règle que Dany a demandée pour tout
       le programme.
       ══════════════════════════════════════════════════════════════════════ */

    /* ── LE HÉROS ─────────────────────────────────────────────────────────
       Centré, comme celui de l'accueil : marque, fil d'Ariane, le titre, la
       lead À L'ENCRE (c'est la phrase qu'on doit lire avant tout le reste),
       puis les deux boutons. Court : la planche doit affleurer sous le pli
       à 1440 (héros ≤ 560 px, mesuré). */
    .page-hero { padding: clamp(34px, 5vw, 55px) 0 clamp(34px, 4.6vw, 55px);
      display: flex; flex-direction: column; align-items: center; text-align: center; }
    /* LE MARK EST UN TRAIT, PAS UNE SILHOUETTE. Sans ces quatre propriétés le
       symbole hérite du `fill` noir par défaut : le téléphone dessiné sortait
       en aplat noir, à l'opposé du trait fin de l'accueil. Ce sont exactement
       les valeurs de `.picto` là-bas — même main, même épaisseur. */
    .page-hero .marque {
      display: block; width: 40px; height: 40px; margin: 0 auto 13px;
      color: var(--accent); stroke: currentColor; fill: none;
      stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
    }
    /* Le drapeau suisse, lui, est un aplat : il porte ses propres couleurs. */
    .page-hero .marque.drapeau { stroke: none; fill: revert; }
    /* LE FIL D'ARIANE N'EST PAS UN « EYEBROW ». Il situe la page dans le site ;
       il n'intitule pas la section suivante. Casse normale, aucun
       interlettrage étiré — la forme majuscule tracée est précisément le
       tell que la DA du projet bannit. */
    .page-hero .fil { margin: 0 0 13px; font-size: .92rem; color: var(--soft); }
    .page-hero .fil a { color: inherit; text-decoration: none; border-bottom: 1px solid var(--line-strong); }
    .page-hero .fil a:hover { color: var(--accent-texte); border-color: currentColor; }
    /* La coupe du h1 est celle du h2 de planche de l'accueil (700, −.02em),
       un cran plus grande : c'est le titre de la page. */
    .page-hero h1 {
      margin: 0; font-size: clamp(2.05rem, 1.3rem + 3.2vw, 3.6rem); font-weight: 700;
      letter-spacing: -.02em; line-height: 1.06; color: var(--ink);
      max-width: min(19ch, 100%); text-wrap: balance;
    }
    .page-hero .chapo {
      margin: 21px 0 0; font-size: clamp(1.1rem, 1.5vw, 1.25rem); line-height: 1.55;
      color: var(--ink); max-width: 56ch; text-wrap: pretty;
    }
    .page-hero .cta, .cta-bande .cta {
      display: flex; flex-wrap: wrap; justify-content: center; gap: 13px; margin: 34px 0 0;
    }
    @media (max-width: 480px) {
      .page-hero .cta .btn, .cta-bande .cta .btn { width: 100%; justify-content: center; min-height: 52px; }
    }
    @media (max-width: 600px) {
      .page-hero h1 { font-size: clamp(1.6rem, 7.4vw, 2.05rem); }
    }

    /* ── LA PLANCHE DE LA PAGE ────────────────────────────────────────────
       Le même bloc `.pl` que l'accueil (feuille vitrine-planches.css) ; ici
       il prolonge le héros : pas de filet, pas de titre (le h1 le porte),
       et l'air du dessous reste celui d'une section. */
    .pl.pl--page { border-top: none; padding-top: 0; }

    /* ── LA SECTION ───────────────────────────────────────────────────────
       Un filet, et de l'air. Pas de numéro, pas de sur-titre : le rythme
       vient de la règle horizontale et de l'alternance texte / objet.
       ⛔ `section.sect`, QUALIFIÉ PAR L'ÉLÉMENT : la fiche cliente de la
       planche ⑦ porte des rangées `.fic-r.sect` (le SECTEUR du métier), et
       `.sect { padding … }` les étirait à 110 px chacune — mesuré sur
       plateforme-clientele.html le 18 sept. 2026, les flèches à 246 px de
       leur cible. Une classe de page ne doit jamais attraper un écran
       reconstitué. */
    section.sect { padding: clamp(34px, 4.6vw, 55px) 0; border-top: 1px solid var(--line); }
    section.sect > h2, .sect-texte > h2 {
      margin: 0 0 8px; font-size: clamp(1.5rem, 1.1rem + 1.7vw, 2.15rem); font-weight: 700;
      letter-spacing: -.02em; line-height: 1.12; color: var(--ink);
      max-width: 24ch; text-wrap: balance;
    }
    section.sect .sous { margin: 0 0 21px; font-size: 1.06rem; line-height: 1.5; color: var(--text); max-width: 62ch; text-wrap: pretty; }
    section.sect p { margin: 0 0 .9em; max-width: 66ch; font-size: 1.03rem; line-height: 1.618; color: var(--text); text-wrap: pretty; }
    section.sect p:last-child { margin-bottom: 0; }
    section.sect a { color: var(--accent-texte); text-underline-offset: 3px; }
    /* LA PROSE À GAUCHE, L'OBJET À DROITE — au nombre d'or, au-dessus de
       1 100 px. Le relevé, l'opposition et le bloc d'attente prennent toute
       la largeur en dessous : ce sont des rangées, pas des colonnes. */
    .sect-objet { margin-top: 21px; }
    .sect-objet .fig-bloc { margin: 0; max-width: none; }
    @media (min-width: 1100px) {
      .sect--cote { display: grid; grid-template-columns: minmax(0, 1.618fr) minmax(0, 1fr);
        gap: 13px clamp(34px, 5vw, 89px); align-items: center; }
      .sect--cote > .sect-texte { grid-column: 1; grid-row: 1; }
      .sect--cote > .sect-objet { grid-column: 2; grid-row: 1; margin: 0; }
      .sect--cote > .ledger, .sect--cote > .duo, .sect--cote > .attente { grid-column: 1 / -1; }
    }

    /* ── LE LEDGER — les faits, alignés sur des filets ────────────────────
       IL N'Y A PAS DE COCHE. Quarante-deux puces cochées portaient une
       NÉGATION dans la première version de ces pages : une coche verte devant
       « ce transfert n'existe pas » est un mensonge de ponctuation. Le filet
       ne ment pas : il sépare, il n'approuve pas.
       Deux formes de rangée : le couple terme/valeur sur deux colonnes, et le
       fait en prose sur toute la largeur. C'est la donnée qui décide. */
    .ledger { list-style: none; margin: 21px 0 0; padding: 0; border-top: 1px solid var(--line); }
    .ledger > li {
      display: grid; grid-template-columns: minmax(0, 15rem) minmax(0, 1fr);
      gap: 2px 34px; padding: 13px 0; border-bottom: 1px solid var(--line);
      font-size: 1rem; line-height: 1.55;
    }
    .ledger > li.plein { grid-template-columns: minmax(0, 1fr); }
    .ledger .terme { font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; }
    .ledger .def { color: var(--text); max-width: 72ch; }
    .ledger b { color: var(--ink); font-weight: 700; }
    @media (max-width: 640px) {
      .ledger > li { grid-template-columns: minmax(0, 1fr); gap: 3px; }
    }

    /* ── L'OPPOSITION — deux ou trois colonnes, des filets, aucune carte ──
       Les cartes sont la réponse paresseuse, et une grille de cartes
       identiques est un interdit du projet. Quand deux choses se répondent,
       un filet vertical le dit mieux qu'une boîte : il met les deux au même
       niveau au lieu de les emballer. */
    .duo {
      display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
      gap: clamp(21px, 3vw, 34px); margin-top: 21px;
    }
    .duo > .carte { padding: 0; border: none; background: none; }
    .duo > .carte + .carte { border-left: none; }
    /* Le filet vit sur le CONTENEUR, en fond, pour ne pas devenir une bordure
       latérale colorée (interdit du projet) : c'est une règle de séparation,
       pas un accent posé sur un bloc. */
    @media (min-width: 720px) {
      .duo { position: relative; }
      .duo > .carte + .carte { padding-left: clamp(21px, 3vw, 34px); box-shadow: -1px 0 0 0 var(--line); }
    }
    .carte > h3 { margin: 0 0 8px; font-size: 1.08rem; font-weight: 700; color: var(--ink); letter-spacing: -.01em; }
    .carte p { margin: 0; font-size: .98rem; line-height: 1.6; color: var(--text); max-width: 46ch; }

    /* ── EN ATTENTE — dit une fois, à sa place ────────────────────────────
       Un contour tireté et une étiquette en casse normale ; plus de
       hachures ni de capitales interlettrées (la première version en
       portait, et c'était devenu le seul objet récurrent des pages). Le bloc
       garde sa respiration : l'air est plus grand avant lui, parce qu'on
       change de nature de propos. */
    .attente {
      margin: clamp(21px, 3.4vw, 34px) 0 0; padding: 18px 21px 21px;
      border: 1px dashed var(--line-strong); border-radius: var(--r-md);
      background: var(--surface); max-width: 74ch;
    }
    .attente > .et {
      display: block; margin: 0 0 8px;
      font-size: .86rem; font-weight: 700; color: var(--accent-texte);
    }
    .attente p { margin: 0; font-size: .98rem; line-height: 1.6; color: var(--text); max-width: none; }
    .attente p + p { margin-top: 9px; }
    .attente b { color: var(--ink); }
    .attente a { color: var(--accent-texte); }

    /* ── LA FAQ : SOMMAIRE ET INTERTITRES DE THÈME (1ᵉʳ octobre 2026) ──────
       Un sommaire en tête, groupé par thème, un lien par question vers son
       ancre ; deux colonnes au bureau, une au téléphone. Filets, pas de
       carte ; cibles de 44 px ; jetons de la coquille seulement. */
    .sommaire { margin: clamp(21px, 3vw, 34px) 0 0; padding-top: 21px; border-top: 1px solid var(--line); }
    .sommaire-t { margin: 0 0 13px; font-weight: 700; letter-spacing: -.015em; color: var(--ink); max-width: none; }
    .sommaire-groupes { display: grid; gap: 21px 34px; }
    @media (min-width: 900px) { .sommaire-groupes { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
    .sommaire-theme { margin: 0 0 5px; font-size: .84rem; font-weight: 600; color: var(--soft); max-width: none; }
    .sommaire ol { list-style: none; margin: 0; padding: 0; }
    .sommaire li a { display: flex; align-items: center; padding: 8px 0; min-height: 44px; line-height: 1.5;
      color: var(--text); text-decoration: none; border-bottom: 1px solid var(--line); }
    .sommaire li a:hover, .sommaire li a:focus-visible { color: var(--accent-texte); }
    .sommaire li a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
    .sect-theme { margin: clamp(34px, 4.6vw, 55px) 0 -8px; font-size: .84rem; font-weight: 600; color: var(--soft); max-width: none; }

    /* ── LA CLÔTURE ───────────────────────────────────────────────────────
       La même question sur chaque page, et les deux boutons du héros. Une
       visiteuse qui a lu jusqu'ici n'a pas à remonter pour agir. */
    .cta-bande { margin-top: clamp(21px, 3vw, 34px); padding: clamp(34px, 4.6vw, 55px) 0 clamp(21px, 3vw, 34px);
      border-top: 1px solid var(--line); text-align: center; }
    .cta-bande-t { margin: 0 auto; font-size: clamp(1.15rem, 1.7vw, 1.4rem); line-height: 1.4;
      color: var(--ink); max-width: 30ch; text-wrap: balance; }
    .cta-bande .cta { margin-top: 21px; }

    /* ── LA MENTION DE LANGUE ─────────────────────────────────────────────
       Elle dit ce que le sélecteur ne tient pas encore. Même famille visuelle
       que le bloc « en attente », en plus discret : c'est le même aveu. */
    .langue-note {
      margin: 0 0 clamp(30px, 4vw, 50px); padding: 13px 17px;
      border: 1px dashed var(--line); border-radius: var(--r-sm);
      font-size: .93rem; line-height: 1.55; color: var(--soft); max-width: 70ch;
    }
    .langue-note b { color: var(--text); font-weight: 700; }

    /* ── LA PROSE LÉGALE ──────────────────────────────────────────────────
       Les deux pages légales étaient des CULS-DE-SAC : une barre à elles
       (« ← Retour au site »), aucun menu, une feuille de style à part. Sur un
       site de trente-deux pages, deux pages qui ne portent pas la même barre
       sont un aveu d'assemblage — et ce sont justement les pages qu'on ouvre
       quand on doute. Elles passent dans la coquille commune ; leur texte,
       lui, n'est pas touché (clés i18n comprises).
       La prose garde une mesure plus étroite que les fiches : on la lit d'une
       traite, pas en relevé. */
    .prose { max-width: 68ch; padding: clamp(30px, 4.4vw, 56px) 0 clamp(38px, 5vw, 70px); }
    .prose h1 {
      margin: 0 0 10px; font-size: clamp(2rem, 4.4vw, 3rem); font-weight: 800;
      letter-spacing: -.032em; line-height: 1.06; color: var(--ink); text-wrap: balance;
    }
    .prose .lead { margin: 0 0 30px; font-size: clamp(1.04rem, 1.4vw, 1.2rem); line-height: 1.55; color: var(--text); }
    .prose h2 {
      margin: 34px 0 8px; padding-top: 22px; border-top: 1px solid var(--line);
      font-size: clamp(1.18rem, 1.9vw, 1.42rem); font-weight: 800;
      letter-spacing: -.02em; color: var(--ink); text-wrap: balance;
    }
    .prose h3 { margin: 22px 0 6px; font-size: 1.04rem; font-weight: 700; color: var(--ink); }
    .prose p { margin: 0 0 .9em; font-size: 1.02rem; line-height: 1.68; color: var(--text); text-wrap: pretty; }
    .prose ul, .prose ol { margin: 0 0 1em; padding-left: 1.15em; }
    .prose li { margin: .3em 0; font-size: 1.02rem; line-height: 1.6; color: var(--text); }
    .prose a { color: var(--accent-texte); text-underline-offset: 3px; }
    .prose strong, .prose b { color: var(--ink); font-weight: 700; }
    .prose table { width: 100%; border-collapse: collapse; margin: 14px 0 20px; font-size: .97rem; }
    .prose th, .prose td { text-align: left; padding: 10px 12px 10px 0; border-bottom: 1px solid var(--line); vertical-align: top; }
    .prose th { color: var(--soft); font-weight: 700; font-size: .84rem; }
    .prose td { color: var(--text); }

    /* ── LE PRIX — le socle en une ligne, les briques, l'estimateur ───────
       (20.09.2026, demande de Dany : « refaire la page des prix pour en
       faire quelque chose de clair »). Trois objets, et aucun n'est une
       carte : une LIGNE pour le socle (le nom, le mois, la mise en place,
       sur un filet), une LISTE pour les briques (une rangée par brique,
       nom + une phrase à gauche, deux montants à droite, alignés sur les
       chiffres tabulaires), un TOTAL sous un filet fort. Les montants
       viennent du catalogue exécuté ; la page se lit entière sans script.
       Colonnes des briques : la parole, puis 10 rem pour la mise en place
       (« Mise en place, une fois » tient sur une ligne), 6,5 rem pour le
       mois. Sous le relevé des repères, la prose reprend son air. */
    .tarif-socle {
      display: flex; flex-wrap: wrap; align-items: baseline; gap: 5px 34px;
      margin: 0 0 21px; padding: 21px 0; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
      font-size: 1.03rem; line-height: 1.4; color: var(--text);
    }
    .tarif-socle .tarif-nom { flex: 1 1 100%; font-size: clamp(1.2rem, 1rem + 1vw, 1.5rem); font-weight: 700; letter-spacing: -.02em; color: var(--ink); }
    .tarif-socle b { font-size: clamp(1.55rem, 1.15rem + 1.6vw, 2.1rem); font-weight: 700; letter-spacing: -.02em; color: var(--ink); font-variant-numeric: tabular-nums; }
    .estim { margin: 21px 0 0; }
    .estim-tete, .brique {
      display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 10rem) minmax(0, 6.5rem);
      gap: 8px 21px; align-items: center;
    }
    .estim-tete { padding: 0 0 8px; border-bottom: 1px solid var(--line-strong); font-size: .86rem; font-weight: 700; color: var(--soft); }
    .estim-tete > span:not(:first-child), .brique-mise, .brique-mois { text-align: right; font-variant-numeric: tabular-nums; }
    .briques { list-style: none; margin: 0; padding: 0; }
    .brique { padding: 13px 0; border-bottom: 1px solid var(--line); }
    /* La rangée entière est l'étiquette de sa case : on coche en touchant le
       nom, et la cible fait au moins 44 px de haut. */
    .brique label { display: flex; gap: 13px; align-items: flex-start; min-height: 44px; cursor: pointer; }
    .brique input[type="checkbox"] { flex: none; width: 21px; height: 21px; margin: 3px 0 0; accent-color: var(--accent-action); cursor: pointer; }
    .brique-nom b { display: block; font-size: 1.03rem; font-weight: 700; color: var(--ink); line-height: 1.4; }
    .brique-nom small { display: block; margin-top: 2px; font-size: .95rem; line-height: 1.5; color: var(--text); max-width: 52ch; text-wrap: pretty; }
    .brique--socle .brique-nom { padding-left: 34px; }
    .brique--socle .brique-nom small { color: var(--soft); }
    .brique-mise, .brique-mois { font-size: 1.03rem; font-weight: 700; color: var(--ink); white-space: nowrap; }
    /* Le libellé de chaque montant : lu par un lecteur d'écran, caché à
       l'œil tant que l'en-tête des colonnes le dit. */
    .brique-l { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
    .estim-periode { margin: 21px 0 0; padding: 0; border: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 5px 21px; }
    .estim-periode legend { float: left; margin: 0 13px 0 0; padding: 0; font-weight: 700; color: var(--ink); line-height: 44px; }
    .estim-periode label { display: inline-flex; align-items: center; gap: 8px; min-height: 44px; cursor: pointer; color: var(--text); }
    .estim-periode input[type="radio"] { width: 19px; height: 19px; margin: 0; accent-color: var(--accent-action); cursor: pointer; }
    .estim-total { margin: 13px 0 0; padding: 21px 0 0; border-top: 2px solid var(--ink); display: grid; gap: 8px; }
    .estim-total > div { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 5px 21px; }
    .estim-total .t { font-size: 1.03rem; color: var(--text); }
    .estim-total .v { display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: 8px; color: var(--text); }
    .estim-total b { font-size: clamp(1.35rem, 1.1rem + 1.2vw, 1.8rem); font-weight: 700; letter-spacing: -.02em; color: var(--ink); font-variant-numeric: tabular-nums; }
    /* Qualifiée par la section pour passer devant `section.sect p` — sans
       `!important`, qui est interdit de ce côté de la feuille. */
    section.sect .estim-note { margin: 13px 0 0; font-size: .93rem; line-height: 1.5; color: var(--soft); }
    section.sect .ledger + p { margin-top: 21px; }
    /* Au téléphone, les deux montants passent sous le nom, une ligne chacun,
       alignés sur le texte de la brique, et disent leur libellé eux-mêmes
       (l'en-tête des colonnes n'a plus de colonnes) : « Mise en place, une
       fois : 4’500.– », le même texte que lit le lecteur
       d'écran au bureau. Mesuré à 390 : sur UNE ligne, le montant du mois
       passait à la ligne et retombait contre la marge. */
    @media (max-width: 640px) {
      .estim-tete { display: none; }
      .brique { display: block; }
      .brique-mise, .brique-mois { display: block; padding-left: 34px; margin-top: 3px; text-align: left; font-weight: 700; }
      .brique-l { position: static; width: auto; height: auto; margin: 0; overflow: visible; clip-path: none; font-weight: 400; color: var(--soft); }
      /* La légende au-dessus des deux choix : à côté d'eux, « À l'année »
         passait seul à la ligne suivante (mesuré à 390). */
      .estim-periode legend { float: none; width: 100%; margin: 0; line-height: 1.4; }
    }
    @media (min-width: 1100px) {
      .sect--cote > .estim { grid-column: 1 / -1; }
    }

    /* ── LE MOUVEMENT ─────────────────────────────────────────────────────
       PAS de fondu-montée sur chaque section : c'est le réflexe saturé, et
       l'inverse de « décidé ». Deux moments seulement, et chacun révèle ce
       qu'il révèle : l'entrée du héros, et l'ARRIVÉE DES RANGÉES DU LEDGER,
       échelonnée — un relevé se lit ligne à ligne, l'animation le dit. */
    .rev-actif .page-hero > * { opacity: 0; transform: translateY(6px); }
    .rev-actif .page-hero.vu > * {
      opacity: 1; transform: none;
      transition: opacity .5s cubic-bezier(.22,1,.36,1), transform .5s cubic-bezier(.22,1,.36,1);
      transition-delay: var(--rd, 0ms);
    }
    .rev-actif .ledger > li { opacity: 0; transform: translateY(5px); }
    .rev-actif .ledger.vu > li {
      opacity: 1; transform: none;
      transition: opacity .42s cubic-bezier(.22,1,.36,1), transform .42s cubic-bezier(.22,1,.36,1);
      transition-delay: var(--rd, 0ms);
    }
    @media (prefers-reduced-motion: reduce) {
      .rev-actif .page-hero > *, .rev-actif .ledger > li { opacity: 1; transform: none; transition: none; }
    }
    /* Même raison qu'à l'impression de l'accueil : ces deux familles-ci
       n'attendent pas un `[data-rev]` mais le `.vu` de leur PARENT, que le
       bloc générique ne couvre donc pas. */
    @media print {
      .rev-actif .page-hero > *, .rev-actif .ledger > li { opacity: 1; transform: none; transition: none; }
    }
