/* ============================================================================
   final.css — le bloc final cesse d'être un trou noir
   ----------------------------------------------------------------------------
   Constat du Boss, 22/09/2026 : « il est trop noir et dénature le reste du
   site ». Mesuré, il a raison : le bloc fait 1 132 x 329 px de #000000, soit
   **372 000 px² et 26 % de la hauteur d'écran**, sur une page où tout le reste
   est blanc cassé. À cette taille, ce n'est plus une ancre de contraste — c'est
   un trou. (Et je m'étais trompé : je l'avais rangé dans les « ancres » et donc
   exclu du survol. Un bloc de cette taille ne s'exclut pas, il se reprend.)
   ----------------------------------------------------------------------------
   CE QU'IL DEVIENT : il reprend la grammaire de tous les autres blocs — fond
   clair, bordure noire, ombre dure — et tire son RANG d'ailleurs :
   une étiquette-pastille colorée, et un écran d'auto-test ASCII (autotest.js).
   ----------------------------------------------------------------------------
   TROIS CHOSES NE DOIVENT PAS ÊTRE OUBLIÉES EN L'ÉCLAIRCISSANT :
   1. le paragraphe portait `color:#d8d5cc` EN STYLE INLINE — un gris clair écrit
      pour le noir. Sur fond clair il serait illisible. Corrigé DANS LA SOURCE
      (les deux occurrences), pas ici par un `!important` ;
   2. `.final__box .label` / `.contact__box .label` étaient en `--term-muted`
      (gris de terminal) : couleur d'un fond sombre ;
   3. la couleur ne repeint PAS le bloc au survol — elle reste sur les deux
      accents, l'étiquette et le cadre de l'écran.
   ========================================================================== */

/* --- 1. le bloc s'éclaircit ------------------------------------------------ */
/* 16/10/2026 — RÉGRESSION MESURÉE, puis réparée.
   Cette règle listait `.contact__box` ET `.final__box`. Comme final.css est
   chargé EN DERNIER (services.html:318), à spécificité égale (0,1,0) c'est
   l'ORDRE qui tranche — donc ELLE gagnait. `.contact__box` portait pourtant
   `--ink-chaud` dans services.html:290, règle juste mais MORTE : le rendu
   donnait du papier. Vérifié au pixel (31 bandes de 40 px, médiane
   (255,253,245), 0 pixel sombre). Le seul îlot sombre de services avait
   disparu en silence.
   ⇒ .contact__box sort de CE sélecteur, et sa propre règle redevient vivante.
   Preuve de discrimination : la médiane du bloc doit passer de
   (255,253,245) à (23,21,15). */
.final__box {
  background: var(--paper);
  color: var(--ink);
}
/* .contact__box garde son fond sombre : voir services.html:290. */

/* --- 2. l'étiquette devient une pastille, et sa couleur tourne ------------- */
/* `--teinte` est posée par autotest.js sur le BLOC : elle change à chaque fois
   que la souris y rentre, et à chaque changement de scène — jamais deux fois la
   même de suite, sinon l'aléatoire se lit comme un défaut d'affichage. */
.final__box .label, .contact__box .label {
  display: inline-block;
  color: var(--ink);
  background: var(--teinte, var(--lime));
  border: 2px solid var(--ink);
  border-radius: 3px;
  padding: 3px 9px;
  transition: background .25s var(--ease);
}

/* --- 3. l'écran d'auto-test ------------------------------------------------ */
.autotest {
  /* ⛔ PAS de `--teinte` ici. La poser sur le panneau ÉCRASE celle du bloc pour
     tout ce qu'il contient : l'étiquette prenait l'or, le bandeau restait vert,
     et le bloc affichait DEUX couleurs à la fois. Vu à l'écran le 22/09, pas
     déduit. La couleur vient du bloc (autotest.js l'y pose) ; le repli est
     déclaré à l'usage — `var(--teinte, var(--lime))`. */
  width: 236px;
  border: 2px solid var(--ink);
  border-radius: 4px;
  background: var(--paper);
  box-shadow: var(--shadow);
  overflow: hidden;
}

.autotest__titre {
  margin: 0;
  padding: 4px 9px;
  background: var(--teinte, var(--lime));
  border-bottom: 2px solid var(--ink);
  font-family: var(--font-data);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink);
  transition: background .25s var(--ease);
}

.autotest__ecran {
  margin: 0;
  padding: 9px 10px;
  font-family: var(--font-data);
  font-size: 10px;
  line-height: 1.5;
  color: var(--ink);
  white-space: pre;
  overflow: hidden;
  /* HAUTEUR FIXE : 6 lignes. Aucune image ne doit pousser le formulaire. */
  min-height: calc(6 * 1.5 * 10px);
  transition: none;
}

/* --- 4. la mise en page : l'écran se loge dans la MARGE du texte ----------- */
/* Sur `.final__box` (index), le paragraphe ne fait que 540 px alors que le
   formulaire en fait 1 050 : il y a ~510 px libres à sa droite. C'est là.
   On passe en grille plutôt qu'en `float` : le placement est explicite, et
   aucun flottant ne vient déranger le formulaire en dessous. */
@media (min-width: 880px) {
  .final__box {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 236px;
    column-gap: 36px;
  }
  .final__box > .label { grid-column: 1 / -1; grid-row: 1; justify-self: start; }
  .final__box > h2     { grid-column: 1 / -1; grid-row: 2; }
  /* ⛔ `> p` attrapait AUSSI la mention RGPD `.src` ajoutee le 23/09 sous le
     formulaire : tous les enfants directs `<p>` tombaient dans la MEME cellule
     (grid-row 3) et s'empilaient l'un sur l'autre.
     Mesure du 23/09 : recouvrement de 538 x 128 px entre le paragraphe de
     presentation et la mention — le texte se lisait par-dessus lui-meme.
     On vise donc le paragraphe de presentation en EXCLUANT `.src`, et `.src`
     prend sa propre ligne (row 5, sous le formulaire qui est en row 4). */
  /* ⛔ PIEGE DE SPECIFICITE, paye le 23/09. `.final__box > .label` pese
     (0,2,0) et gagnait donc sur `.final__box > p` (0,1,1) : l'etiquette, qui
     est un <p>, allait bien en ligne 1. En ecrivant `> p:not(.src)` j'ai fait
     MONTER la regle a (0,2,1) — elle a depasse `.label`, et « Derniere etape »
     est retombe dans la cellule du paragraphe. Mesure : recouvrement rendu.
     => on exclut LES DEUX classes qui sont portees par des <p> : .label ET .src.
     Si un troisieme <p> a classe arrive ici, il faudra l'ajouter a cette liste. */
  .final__box > p:not(.label):not(.src) { grid-column: 1; grid-row: 3; }
  .final__box > .src        { grid-column: 1 / -1; grid-row: 5; }
  .final__box > .autotest { grid-column: 2;   grid-row: 3; align-self: start; }
  .final__box > form   { grid-column: 1 / -1; grid-row: 4; }
}

/* ---- Renvoi vers la source d'un chiffre (23/09/2026) ----
   L'astérisque d'une valeur affichée est un LIEN vers le paragraphe de source
   dans ./mentions-legales.html. C'est le remplacement des notes deployées sous
   chaque chiffre : 285 px de texte de 11,5 px sur l'accueil, que le lecteur
   sautait. Ici : un lien, qui marche sans JS, au clavier, et que Google suit.

   Pourquoi un lien et pas un bouton a bascule : rien a reveler sur place, aucun
   etat a maintenir, rien a resynchroniser. Le comportement natif du lien est
   exactement ce qu'on veut, et il ne peut pas se desynchroniser.

   L'asterisque est en exposant via `vertical-align` : un `<sup>` aurait gonfle
   la hauteur de ligne des valeurs, qui sont en `line-height:1` sur un
   `display:flex` (mesure : .metric__v et .stat__v). */
.src-lien{
  font-family:inherit; font-size:0.5em; font-weight:700; line-height:0;
  vertical-align:super; text-decoration:none; color:var(--ink);
  margin-left:2px; padding:0 2px; border-radius:3px;
}
.src-lien:hover{ background:var(--gold); text-decoration:underline}
.src-lien:focus-visible{ background:var(--gold); text-decoration:underline;
  outline:3px solid var(--ink); outline-offset:3px}

/* Sur `.contact__box` (services), la disposition d'origine est déjà en deux
   colonnes [texte | boutons]. On en fait trois : [texte | boutons | écran]. */
@media (min-width: 1040px) {
  .contact__box {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto 236px;
    column-gap: 32px;
    align-items: center;
  }
  .contact__box > div:nth-of-type(1) { grid-column: 1; }
  .contact__box > div:nth-of-type(2) { grid-column: 2; }
  .contact__box > .autotest          { grid-column: 3; align-self: center; }
}

/* --- 5. écrans étroits : l'écran passe SOUS le texte, jamais à côté -------- */
@media (max-width: 879px) {
  .autotest { width: 100%; max-width: 236px; margin: 16px 0 4px; }
}

/* ⛔ LA BANDE ~660–1039 px SUR `services` — défaut mesuré le 22/09, en DEUX temps.
   Premier constat : entre « >= 1040 px » (la grille) et « <= 879 px » (la largeur
   du panneau), `.contact__box` garde le `display:flex; flex-wrap:wrap;
   justify-content:space-between` de la base. Boutons et écran tiennent sur une
   rangée, et `space-between` pousse l'écran à DROITE (mesuré à 768 : left 471
   alors que le texte commence à 61).
   Premier correctif — `flex-basis:100%` — INSUFFISANT, et la mesure du 22/09 dit
   pourquoi : sous 880 px, `max-width:236px` plafonne l'élément, donc sa taille
   hypothécaire est ramenée à 236 et le `flex-basis` n'a plus d'effet. Le trou
   n'était pas DANS 880–1039, il était EN DESSOUS : de 660 à 879.
   Second correctif — on ne joue plus sur le retour à la ligne, on change d'axe :
   la boîte passe en COLONNE (texte, boutons, écran, chacun sur sa ligne), et
   l'écran s'aligne à gauche sur sa propre largeur de 236 px. Plus aucune
   dépendance à un plafond qui annule le `flex-basis`. */
@media (max-width: 1039px) {
  .contact__box { flex-direction: column; align-items: stretch; }
  .contact__box > .autotest { align-self: flex-start; }
}

/* --- 6. mouvement réduit : l'écran reste, l'animation s'arrête ------------- */
/* C'est autotest.js qui fige l'image (il montre une image représentative) ;
   ici on supprime seulement la transition de couleur, qui resterait un
   mouvement. */
@media (prefers-reduced-motion: reduce) {
  .final__box .label, .contact__box .label, .autotest__titre { transition: none; }
}

/* ===== GRILLE TARIFAIRE 5 COLONNES — partagee par index et services =====
   Extraite du <style> de l accueil le 24/09 : les deux pages affichaient
   deux grilles differentes, et celle de services n avait meme pas les couleurs.
   Une seule source, deux pages : elles ne peuvent plus diverger. */
/* ⚠️ PIÈGE MESURÉ, À NE PAS ROUVRIR : `--ink-muted` (#57534A) sur `--pink`
   (#FE90E8) donne 3,78:1 — ça passe en GROS texte, ça ÉCHOUE en corps (4,5:1).
   Sur les 5 pastels, --pink est le seul fond dangereux : --gold 4,96 · --lime
   5,19 · --sky 6,56 · --cream 5,79. Donc si un jour du texte secondaire doit
   tenir sur du --pink, il faut le passer en --ink (20,62:1), pas en
   --ink-muted.
   ⛔ CE QUI SUIVAIT ÉTAIT DEVENU FAUX LE 16/10/2026, corrigé en place (doctrine
   des nombres : on ne supprime pas, on date). La phrase disait : « Aujourd'hui
   --pink ne colore QUE les guillemets décoratifs du canvas /seo (aria-hidden),
   aucun texte ne le porte. » C'est faux sur les DEUX points :
     · --pink colore désormais TOUT le cadre du canvas de l'ACCUEIL
       (`_src_index.html`, `<section class="canvas" style="--teinte:var(--pink)">`
       + `.canvas__cadre{background:var(--teinte,…)}`) — mesuré rgb(254,144,232)
       sur 1000 x 253 px, AVEC du texte noir dessus ;
     · ce canvas rose n'est pas /seo — /seo est en --gold (rgb(247,203,70)).
   ⇒ Le piège est donc PLUS proche qu'avant : le texte du canvas de l'accueil est
   en --ink (10,37:1, vérifié au rendu), et il doit le RESTER. Poser du
   --ink-muted sur ce fond livrerait 3,78:1 en production — exactement ce que
   cette ligne existe pour empêcher. */

/* 15/10/2026 — CINQ FONDS PASTELS -> UN SEUL FOND, et la couleur passe en
   PASTILLE. Le défaut était mesurable, pas esthétique :
     · awwwards-design:21 « Palette réduite à 2 couleurs + 1 neutre, AUCUNE 4e
       teinte » — la grille en affichait 5 dans UN seul bloc ;
     · la grammaire BlockFrame (la source MIT de la palette) met le fond en
       --white bordé + ombre dure, et la couleur dans une PASTILLE décorative
       (.stat-deco : 12px, bord 2px, en coin). Jamais 5 fonds.
   Donc : toutes les cartes sur --surface, et une pastille par carte qui porte
   la progression (gratuit -> L1 -> L2 -> L3 -> L4), taillée dans les variantes
   --*-ink déjà mesurées à 4,5:1 (final.css:100-102). */
.plans--5 .plan{background:var(--surface)}
/* ⚠️ 16/10/2026 — RE-ALIGNÉ SUR LE CRESCENDO. L'ordre du DOM est désormais
   Gratuit · Suivi(99) · Audit(250) · Correction(1000) · Direction(devis).
   Les positions ci-dessous suivent CET ordre, pas l'ancien (L1 en 2e). */
.plans--5 .plan:nth-child(1) .plan__puce{background:var(--cream)}   /* Gratuit    */
.plans--5 .plan:nth-child(2) .plan__puce{background:var(--sky)}     /* Suivi      */
.plans--5 .plan:nth-child(3) .plan__puce{background:var(--lime)}    /* Audit      */
.plans--5 .plan:nth-child(4) .plan__puce{background:var(--gold)}    /* Correction */
.plans--5 .plan:nth-child(5) .plan__puce{background:var(--pink)}    /* Direction  */
/* la carte « best » garde son rang par la BORDURE et l'OMBRE, pas par la
   couleur : c'est la règle de profondeur néobrutaliste. */
.plans--5 .plan--best{border-width:3px; box-shadow:var(--shadow-lg)}
/* B (mesuré, 15/10 au soir) — COLLISION avec .plan__choix sur la carte 3.
   .plan__choix est à `top:14px; right:14px; z-index:2` (badge « meilleure
   valeur ») ; ma puce était à `top:12px; right:12px; z-index:2` — soit 2 px
   plus haut et 2 px plus à droite, à z-index ÉGAL. Les deux se superposaient
   au pixel. Relevé navigateur : carte 3, chevauchement confirmé.
   ⇒ la puce descend À GAUCHE du badge, et ne le touche jamais. */
/* 16/10/2026 — la puce passe À GAUCHE du badge, alignée sur SA ligne, pour que
   les deux ne se touchent jamais, quelle que soit la largeur du badge.
   Mesure : badge sur la carte « best » dépasse du cadre (x=-32, hors carte) —
   c'est un défaut séparé, corrigé juste en dessous. */
.plan__puce{position:absolute; top:13px; left:14px; width:12px; height:12px;
  border-radius:50%; border:2px solid var(--ink); z-index:2}
/* ⛔ 16/10/2026 — RÈGLE MORTE, SUPPRIMÉE. Elle disait :
       @media (hover:hover){.plans--5 .plan--best:hover{background:var(--pink)}}
   Mesuré : la carte vedette est au rang 4 (`:nth-child(4)`), et `survol.css:88`
   porte `.plans--5 .plan:nth-child(4):hover{background:var(--gold)}` —
   spécificité (0,4,0) contre (0,3,0) ici. La règle d'or gagne TOUJOURS :
   cette ligne n'a jamais rien coloré. La carte « notre choix » prend l'or au
   survol, ce qui est d'ailleurs cohérent avec sa pastille (or = Correction).
   Une règle morte qui décrit un comportement qui n'arrive pas est un mensonge
   dans le code : elle a été retirée, pas réparée — le Boss a demandé de GARDER
   les couleurs de survol telles qu'elles sont.
   ⚠️ Si l'ordre des cartes change un jour, la couleur de survol suit la
   POSITION (survol.css:nth-child), pas la carte : c'est là qu'il faudra regarder. */
.plans--5 .plan{justify-content:flex-start}
.plan__choix{position:absolute; top:14px; right:14px; z-index:2;
  background:var(--ink); color:var(--paper); font-size:10.5px;
  font-weight:700; letter-spacing:.1em; text-transform:uppercase;
  padding:3px 9px; border-radius:2px}
.plans--5 .plan--best{border-left:10px solid var(--ink); position:relative}
/* 4.2 — La marche des prix (audit 10/10). ⚠️ RECALÉ LE 16/10/2026.
   Le commentaire d'avant disait « l'axe monte sur le prix affiché » et citait
   0 < 348 < 384 < 588 — ces montants N'EXISTENT PLUS (grille du 16/10 :
   0 / 250 / 1 000 / 99 € par mois / devis).
   ⛔ La phrase était aussi fausse sur le fond : la grille est en COLONNES
   (repeat(5,1fr), build.py:grille), les cartes sont CÔTE À CÔTE. Une hauteur
   ne dit rien de l'ordre des prix. Le défaut d'origine (L2 plus haut que L3
   alors qu'il coûtait moins cher) était un problème de HAUTEUR DE TEXTE, pas
   une « marche ».
   Ces 5 valeurs ne sont donc pas une échelle de prix : ce sont des planchers
   qui empêchent une carte d'être plus courte que ses voisines. Vérifiées au
   rendu le 16/10 après le passage de L2 à « 1 000 € » (7 caractères contre 5) :
   aucun débordement, plan__valeur est en taille fluide. */
/* ⛔⛔ 16/10/2026 — LES 5 PLANCHERS DE COLONNE SONT SUPPRIMÉS. Mesuré au rendu,
   à 3 largeurs. Ils ne servaient à RIEN et cassaient tout :
     · ils INVERSENT l'escalier — position 3 (L1 Audit, 250 €) = 754 px, position 4
       (L2 Correction, 1 000 €) = 641 px : le barreau le plus cher est le plus court ;
     · ils créent 413 px de vide au total, dont 217 px sur UNE carte, que
       `.plan__cta{margin-top:auto}` concentre juste avant le bouton ;
     · ils n'ont jamais été une « échelle de prix » : les hauteurs NATURELLES des 5
       cartes (579 / 584 / 603 / 621 / 595) montent DÉJÀ toutes seules. C'est le
       CONTENU qui dessine la progression, pas un plancher posé par-dessus.
   Et surtout — mesuré : ils ne produisaient AUCUN alignement. Les titres partaient à
   3596/3566/3421/3547/3363, les prix à 3628/3598/3453/3579/3395 : 233 px d'écart.
   Une grille tarifaire dont les prix ne sont pas sur la MÊME LIGNE ne se compare
   pas. C'est le vrai défaut d'expérience client, et ce n'est pas la hauteur des
   cartes qui le corrige : c'est l'alignement des RANGÉES (voir plus bas).
   ⚠️ Les planchers d'ENFANTS restent, mais TOUS ne servent pas :
     · plan__price 91 · plan__renouv 60 · plan__force 73 → EUX alignent les rangées.
     · plan__list 108 → VÉRIFIÉ INERTE le 16/10/2026 : les listes naturelles font
       138 à 224 px, le plancher ne mord JAMAIS. Il est conservé comme filet (sans
       effet aujourd'hui), mais il n'aligne rien — ne pas le citer comme tel.
   Le contrôle `tests/test_escalier_et_canvas.py` ROUGIT si un plancher de colonne
   revient sur `.plans--5 .plan`. */
/* 9.2 — La carte vedette etait DEJA a 12 px au repos (--shadow-lg), donc au survol
   son ombre ne bougeait plus : c'etait la seule incoherence de survol du site,
   sur la carte qui doit attirer l'oeil. On lui donne un cran de plus : 16 px.
   Gap mesure au 10/10 avant d'ecrire cette ligne : gouttiere laterale 16 px,
   marge verticale 32 px ; le debordement est de 4 px (12 -> 16) — aucune
   collision avec la carte voisine ni avec le bloc suivant. */
@media (hover:hover){.plans--5 .plan--best:hover{box-shadow:16px 16px 0 0 rgba(0,0,0,.5)}}
/* `align-items:end` -> `stretch` (16/10/2026). Avec `end`, chaque carte gardait SA
   hauteur et venait s'appuyer sur une ligne de base commune : les CINQ bords
   supérieurs étaient décalés de 233 px, et chaque rangée (titre, prix, prestations)
   partait d'une ordonnée différente. Avec `stretch`, les cinq cartes prennent la
   hauteur de la plus haute — mesuré : 666 px les cinq — et TOUTES les rangées
   s'alignent. C'est ce qui rend la grille comparable. */
.plans--5{grid-template-columns:repeat(5,1fr); align-items:stretch; gap:16px; margin-top:28px}
@media (max-width:1099px){.plans--5{grid-template-columns:repeat(2,1fr)}}
/* Sous 640 px, une carte de prix sur deux colonnes fait 163 px de large : trop etroit
   pour un prix et un libelle. On repasse a la colonne unique — la meme regle que
   .plans en a deja une a cet endroit, que .plans--5 surchargeait. */
@media (max-width:640px){.plans--5{grid-template-columns:1fr}}
@media (min-width:760px) and (max-width:1099px){.plans--5{grid-template-columns:repeat(3,1fr)}}
/* ⛔ 16/10/2026 — LE PLANCHER SUPÉRIEUR PASSE À 30 px POUR LES CINQ.
   Mesuré : la carte « notre choix » portait `padding-top:30px` (final.css, section
   « corrections du 24/09 ») parce qu'elle seule a le badge `.plan__choix` posé à
   `top:6px` — il lui fallait la place. Les quatre autres restaient à 18 px.
   Résultat : son contenu entier partait 13 px PLUS BAS (13 = 30-18 + 3px de bordure
   contre 2), et TOUTES ses rangées étaient décalées par rapport aux voisines.
   Le remède n'est pas de baisser la carte vedette — le badge recouvrirait son titre
   (c'est le défaut d'origine du 24/09) — c'est de donner la MÊME réserve aux cinq. */
.plans--5 .plan{padding:30px 18px 18px}
.plans--5 .plan__price{font-size:23px; gap:6px; flex-wrap:wrap}
/* ⛔ 16/10/2026 — LES PLANCHERS DE RANGÉE, recalés sur la MESURE. Ce sont eux qui
   font qu'une rangée est à la même ordonnée sur les cinq cartes — pas les planchers
   de colonne (supprimés plus bas). Valeurs relevées au rendu à 1440 px :
     · prix  : 91 px est la hauteur RÉELLE du plus haut (« 1 000 € » + « audit et
               correction » qui passe sur deux lignes). 58 px était faux : la carte
               L2 débordait de 33 px et décalait TOUT ce qui suit sur sa colonne.
     · renouv: 60 px (« 15 correctifs appliqués. Au-delà, devis. » sur deux lignes).
     · force : 73 px (le texte au-dessus du bouton, sur deux lignes).
   Effet mesuré après application : titres à 3375, prix à 3407, renouvellements à
   3504, prestations à 3582 — LES QUATRE RANGÉES ALIGNÉES sur les cinq cartes.
   Avant : 233 px d'écart sur chaque rangée.

   ⚠️ 17/10/2026 — LES PRIX ONT CHANGÉ (249 € · 979 € · 399 € · 1 169 € · 439 €,
   présentation psychologique). Les montants cités ci-dessus (« 1 000 € », « 250 € »)
   sont ceux du 16/10 : ils restent lisibles comme trace de ce qui a été mesuré ce
   jour-là. **Le plancher de 91 px, lui, tient toujours** — reverifié au rendu à
   1100 et 1440 px après le changement : « 979 € » + « audit et correction » passe
   encore sur deux lignes, la hauteur du bloc prix reste 91 px sur les cinq cartes,
   et les quatre rangées restent alignées. */
.plans--5 .plan__price{min-height:91px}
.plans--5 .plan__badge{padding:2px 6px; white-space:nowrap}
.plans--5 .plan__list{font-size:14px; min-height:108px}
.plans--5 .plan__k{min-height:1.5em}
/* 30/10/2026 — 60 px couvrait 2,68 lignes, pas 3. La carte L2 porte
   « 45 correctifs appliqués. Au-delà, devis. » — la SEULE mention qui dit
   combien de correctifs sont inclus et que le dépassement part au devis.
   Passée de « 15 » à « 45 », elle a gagné le mot qui la fait tenir sur
   TROIS lignes : 67,17 px, soit 7,17 px de plus que le plancher. Sa liste
   descendait donc de 7 px alors que les quatre autres restaient en place.
   Le plancher exprime maintenant TROIS lignes (3 × 1,6em = 67,2 px à la
   line-height 22,4 px), donc il ne dépend plus de la valeur de line-height.
   Le bas ne bouge pas : plan__cta garde margin-top:auto. */
.plans--5 .plan__renouv{min-height:calc(3 * 1.6em)}
.plans--5 .plan__force{min-height:73px}
.livr{margin-top:14px; border-top:2px solid var(--ink); padding-top:6px}
.livr summary{font-family:var(--font-data); font-size:14px; font-weight:700; letter-spacing:.06em; text-transform:uppercase; cursor:pointer; min-height:48px; display:flex; align-items:center; gap:8px}
.livr summary::-webkit-details-marker{display:none}
.livr summary::after{content:"+"; margin-left:auto; font-size:19px; line-height:1}
.livr[open] summary::after{content:"–"}
.livr ul{list-style:none; padding:0; margin:6px 0 0; font-family:var(--font-logs); font-size:14px; line-height:1.5}
.livr li{padding:3px 0 3px 13px; position:relative; color:var(--ink-muted)}
.livr li::before{content:"·"; position:absolute; left:2px; color:var(--ink)}
.livr summary:focus-visible{outline:3px solid var(--ink); outline-offset:3px}
.plans--5 .plan__cta{margin-top:auto; padding-top:16px}
/* 31/10/2026 — 12 px, ET C'EST MESURÉ DEUX FOIS.
   Une tentative à 12,1875 px (« neuf quanta de 1/64 en reste d'arrondi ») a été
   écrite puis RETIRÉE ce soir : elle CACHAIT une régression au lieu d'en
   corriger une. Mesure sur disque, à 1440 px, bas des cinq boutons :
       12 px       -> 4760 / 4760 / 4760 / 4760 / 4760   (alignés)
       12,1875 px  -> 4760 / 4761 / 4761 / 4761 / 4761   (la carte 1 décroche)
   Et à 1100 px elle ne changeait rien : la marge du bouton n'entre pas dans le
   calcul du bas de la carte, seul le bas de la carte le tire. Le diagnostic
   « 0 échec sur 12 chargements » qui l'avait justifiée ne se reproduit pas :
   trois chargements sans la correction donnent déjà les cinq valeurs alignées.
   La leçon : une valeur qui porte un soixante-quatrième de pixel ne se retient
   que si elle est re-mesurée sur le disque, jamais sur une injection mémoire. */
.plans--5 .plan__cta .btn{margin-top:12px}
/* ⛔ 16/10/2026 — LES CINQ BOUTONS À LA MÊME HAUTEUR. Mesuré : les libellés n'ont
   pas la même longueur, donc pas le même nombre de lignes, donc pas la même
   hauteur — 75 px (« Lancer l'audit », 2 lignes) contre 52 px (les « Choisir »).
   Leurs BAS étaient alignés (le CTA est poussé au fond), mais leurs HAUTS
   partaient à 611 / 634 / 634 / 633 / 634 : la rangée de boutons était en dents
   de scie, sur la zone où l'œil décide.
   Un plancher de 75 px (la hauteur RÉELLE du plus haut) rend la bande uniforme.
   ⚠️ Pourquoi pas réduire le rembourrage pour tout faire tenir sur une ligne :
   mesuré, il faudrait 15 px de rembourrage et il ne resterait que 3 px de marge
   autour du texte — un libellé qui se replie au premier changement de police.
   75 px est la valeur robuste, celle qui tient quoi qu'il arrive au texte. */
.plans--5 .plan__cta .btn{min-height:75px}
.plan__force{margin:0;padding:14px 0 0;border-top:2px solid var(--ink);
  font-size:14px;line-height:1.4;font-weight:700}
.formules{padding:56px 0 88px}
/* Regle de base de la grille : 5 colonnes egales, alignees sur la meme ligne
   de base pour que la hauteur dessine la marche. */

/* --- Corrections mesurees le 24/09 (revue d'articulation) ------------ */

/* 1. « notre choix » recouvrait le titre de 17 px : les deux etaient a la meme
   hauteur. Le badge repasse AU-DESSUS, dans la marge de la carte, et le titre
   garde sa place. La carte est en overflow:hidden : le badge reste dedans. */
/* 16/10/2026 — 30 -> 29 px. La carte vedette a une bordure de 3 px quand les autres
   en ont 2 : la réserve totale (bordure + rembourrage) doit valoir 32 px des deux
   côtés. Avec 30 px elle valait 33, et sa colonne partait 1 px trop bas.
   MÊME RAISONNEMENT EN BAS : 18 -> 17 px. Mesuré, le bas de son bouton tombait à
   4011 quand les quatre autres étaient à 4012 — le même px de bordure, de l'autre
   côté. 1 px ne se voit pas, mais il fait échouer un contrôle d'alignement, et un
   contrôle qui tolère 1 px tolérera 2. */
.plans--5 .plan--best{padding-top:29px; padding-bottom:17px}
.plans--5 .plan--best .plan__k{padding-right:0}
.plans--5 .plan--best .plan__choix{top:6px}

/* 2. Trois boutons de duree bord a bord : ecart 0 px a toutes les largeurs
   (mesure). Trois cadres neobrutalistes qui se touchent mangent leurs ombres. */
.duree{gap:8px}

/* 3. A 375 px, les cartes equipe de services laisse deborder le texte de
   26 a 30 px sous la bordure (mesure). Un mot coupe par un cadre. */
@media (max-width:420px){
  /* overflow-wrap:anywhere (ajoute le 24/09 pour faire tenir les cartes equipe)
     coupait les mots francais AU MILIEU, sans trait d'union : mesure a 375 px,
     « l'automatisa / tion », « communic / ation », « Capgemin / i ».
       overflow-wrap:normal  -> le mot ne se coupe que sur un espace
       hyphens:auto          -> la coupure se fait avec un trait d'union, et
                                lang="fr" est deja pose sur les 5 pages */
  .person__metric, .person__impact{min-width:0; overflow-wrap:normal; hyphens:auto}
}

/* --- 6. Le bouton prend la couleur de sa colonne (24/09) -----------------
   Sur une carte pastel, un bouton d'une autre couleur fait une tache. Le
   bouton se fond dans sa colonne : il ne reste que la bordure et l'ombre
   dure, qui sont le langage du site.
   SAUF L2 : son bouton est BLANC. Le vert de la carte est le plus clair des
   cinq, et un bouton vert dessus ne se verrait plus. */
.plans--5 .plan__cta .btn{background:transparent; border-width:3px; color:var(--ink)}
.plans--5 .plan:nth-child(1) .plan__cta .btn{background:var(--surface)}
.plans--5 .plan:nth-child(2) .plan__cta .btn{background:var(--sky)}
.plans--5 .plan:nth-child(3) .plan__cta .btn{background:var(--surface)}
.plans--5 .plan:nth-child(4) .plan__cta .btn{background:var(--gold)}
.plans--5 .plan:nth-child(5) .plan__cta .btn{background:var(--surface)}

/* --- Regles搬 depuis le <style> de l accueil (24/09) ----------------------
   Elles ne vivaient QUE dans index.html : Services cassait donc la ou
   l accueil etait correct. Une seule feuille, deux pages. */
.plans--5 .plan__list li{margin-bottom:6px}
/* Les livrables dans un <details> NATIF : déployé et lisible sans JavaScript,
   et c'est le seul moyen d'avoir un accordéon accessible sans écrire de code. */
/* Le rail --lime etait a 1,45:1 sur --surface : invisible. Il passe en --ink. */
.plans--5 .plan--best .plan__k{padding-right:108px}
/* Bouton de L1 : le fond vient de final.css, le TEXTE reste noir. La version
   precedente posait color:var(--paper) ici ET background:var(--sky) plus bas :
   texte blanc sur fond cyan = 1,15:1. Mesure du 24/09. */
.plans--5 .plan:nth-child(2) .plan__cta .btn{color:var(--ink)}
/* Un seul pastel par bloc. */
.plans--5 .plan:nth-child(3) .plan__badge{background:var(--surface)}
/* --ink-muted echoue AA sur le rose (3,78:1) : le secondaire passe en --ink. */
.plans--5 .plan:nth-child(n+2) .plan__k,
.plans--5 .plan:nth-child(n+2) .plan__renouv,
.plans--5 .plan:nth-child(n+2) .plan__per,
.plans--5 .plan:nth-child(n+2) .no,
.plans--5 .plan:nth-child(n+2) .livr li,
.plans--5 .plan:nth-child(n+2) .muted{color:var(--ink)}
.btn:active{box-shadow:none}

/* ─── Selecteur de duree 12 / 24 / 36 mois (remis en service le 24/09) ───
   Regles de base D'ABORD, media query APRES. L'inverse a casse la grille
   une fois le 24/09 : une regle de base ecrite apres les media queries,
   a egalite de specificite, les neutralise toutes. */
.durees{display:flex; gap:0; margin-top:22px; border:var(--bw) solid var(--ink); border-radius:4px; overflow:hidden; width:max-content; max-width:100%}
.durees button{min-height:48px; padding:0 18px; font-family:var(--font-data); font-size:12.5px; font-weight:700; letter-spacing:.06em; text-transform:uppercase; background:var(--surface); color:var(--ink-muted); border:0; border-right:var(--bw) solid var(--ink); cursor:pointer; transition:background .15s var(--ease), color .15s var(--ease)}
.durees button:last-child{border-right:0}
.durees button[aria-pressed="true"]{background:var(--lime); color:var(--ink)}
.durees__note{margin-top:10px; font-size:14px; max-width:60ch}
@media (max-width:640px){.durees{width:100%} .durees button{flex:1; padding:0 8px; font-size:11.5px}}

/* ─── L2 : le titre tient sur UNE seule ligne ───
   Mesure : la colonne fait 214 px, `padding-right:108px` ne laissait que 57 px
   pour un titre qui en reclame 132. Le badge « notre choix » passe sur deux
   lignes (34 px) et la marge tombe a 40 px ; le lettrage du titre passe de
   .12em a .04em. Mesure apres : titre sur 1 ligne (19 px), textearrete a x=729,
   badge commence a x=746 — 17 px d'ecart, aucun chevauchement.
   Place APRES la regle `padding-right:108px` plus haut dans ce fichier, sinon
   elle perd le combat de cascade. */
/* ⚠️ DÉFAUT MESURÉ 16/10 : ce badge sortait de la carte (x = -32 sur la carte
   « best »), donc partiellement coupé par overflow:hidden. `right:14px` + un
   `width:auto` de 88 px ne tenait pas. On le pose avec `left` explicite DANS le
   cadre, sous la puce désormais à gauche — le badge reste visible, entier. */
.plans--5 .plan--best .plan__choix{white-space:nowrap; width:auto; padding:2px 7px;
  letter-spacing:.04em; left:34px; right:auto}
.plans--5 .plan--best .plan__k{padding-right:0; letter-spacing:.04em}

/* ============================================================ L'ÉCHELLE VERTICALE
   ⛔⛔ 17/10/2026 — LA REVUE IMPECCABLE DISAIT « PAS D'ÉCHELLE ». MESURÉ : IL Y EN A DEUX.
   Le balayage de TOUTES les valeurs de `margin-top` du site (pages compilées, sources,
   gabarit et feuilles) donne exactement ceci :

       échelle A — multiple de 4 :  4 · 8 · 12 · 16 · 20 · 24 · 28 · 32 · 40 · 76
       échelle B — multiple de 4, plus 2 :  2 · 6 · 10 · 14 · 18 · 22 · 26 · 30 · 34

   **AUCUNE valeur n'est hors de l'une des deux** — 0 sur 500+. La revue citait « 34 px
   trois fois » comme un accident : ce `34` apparaît **77 fois**, c'est un pilier de
   l'échelle B, pas une valeur au hasard.
   ⇒ Ce n'est donc pas « du réglage par composant » : c'est DEUX échelles cohérentes,
   entrelacées, et 500 valeurs à convertir. Fusionner les deux, c'est déplacer le rythme
   vertical de presque toute la page — un changement visuel que le Boss n'a pas demandé
   et qu'aucun test ne peut valider à sa place.
   **DÉCISION : on ne fusionne pas.** On gèle la règle à la place.
   La règle, désormais VÉRIFIÉE par `tests/test_escalier_et_canvas.py` :
     · une valeur de `margin-top` en px est un multiple de 4 (échelle A),
       ou un multiple de 4 plus 2 (échelle B) ;
     · un nom de variable d'espacement suit `--e1`…
   Une TROISIÈME échelle (un 13, un 21, un 27) fait ROUGIR le contrôle. C'est ce qui
   empêche la dérive, sans toucher à une seule valeur en place. */

/* ============================================================ LA CARTE `.bloc`
   ⛔⛔ 17/10/2026 — ELLE MANQUAIT SUR CINQ PAGES, ET LE BALISAGE L'UTILISAIT.
   Le Boss : « il faut faire pareil pour les blocs qui seraient sans couleur avec
   une mise en page douteuse ». Ce n'était pas une impression : `.bloc` était
   défini TROIS FOIS, à l'identique, dans les `<style>` inline de `cgv`,
   `confidentialite` et `mentions` — et **pas du tout** sur `index`, `services`,
   `audit`, `seo`, `geo`, `tarifs`, qui l'emploient aussi. Mesuré au rendu sur
   `services.html` : fond `rgba(0,0,0,0)`, bordure `0px`, ombre `none`, padding
   `0px`. Trois pavés de texte nu.
   ⇒ UNE SEULE SOURCE. La règle vit ici, dans la feuille partagée : elle ne peut
   plus manquer à une page. Les copies inline peuvent rester (elles sont
   identiques) ; elles ne sont plus nécessaires.
   ⚠️ `.block` (AVEC un k), défini plus haut dans ce fichier et sur l'accueil,
   n'est utilisé par AUCUN balisage — ni HTML, ni JS (vérifié). C'est du code
   mort : la classe vivante est `.bloc`. On ne le supprime pas ici, il ne gêne
   personne — mais on ne l'utilise pas non plus.
   ⚠️ Les sections `<section class="bloc">` des pages du parcours gardent leur
   propre règle `section.bloc{padding:64px 0}` : plus spécifique (0-1-1 contre
   0-1-0), elle gagne — c'est voulu, ces sections ne sont pas des cartes. */
.bloc{background:var(--surface); border:var(--bw) solid var(--ink);
  border-radius:4px; box-shadow:var(--shadow); padding:24px}
.bloc + .bloc{margin-top:18px}
/* Les variantes de couleur, alignées sur celles des pages qui les portaient déjà
   (`--sky` sur confidentialite, `--lime` sur cgv et mentions), plus les trois
   autres de la palette. Loi du skill §1.b : un pastel est un FOND, le texte
   dessus reste --ink ; contraste 10,4 à 18,0 — AA sur les cinq. */
.bloc--sky{background:var(--sky)}
.bloc--lime{background:var(--lime)}
.bloc--gold{background:var(--gold)}
.bloc--pink{background:var(--pink)}
.bloc--cream{background:var(--cream)}

/* --- Les DEUX grilles de trois colonnes de texte, qui montraient la même nudité.
   `.cible__grid` (services, section « À qui nous parlons ») et `.geo__grid`
   (accueil, section GEO) portent les MÊMES `div.bloc.geo__carte`. Mesuré avant :
   fond transparent, bordure 0 px, ombre none — et padding 0 px sur services,
   18 px sur l'accueil (`.geo__carte{padding:18px}`), d'où deux nudités un peu
   différentes pour un même défaut.
   ⇒ UNE règle pour les deux, et une PASTILLE DE BORDURE qui porte la couleur,
   dans le sens de la table du skill §1.b : sky « IA / data » · lime « succès »
   (ce qui est inclus) · gold « avertissement / en cours » (ce qui est facturé).
   Le libellé porte le sens ; la couleur ne fait que renforcer. */
.geo__grid > .bloc, .cible__grid > .bloc{border-left-width:10px}
.geo__grid > .bloc:nth-child(1), .cible__grid > .bloc:nth-child(1){border-left-color:var(--sky)}
.geo__grid > .bloc:nth-child(2), .cible__grid > .bloc:nth-child(2){border-left-color:var(--lime)}
.geo__grid > .bloc:nth-child(3), .cible__grid > .bloc:nth-child(3){border-left-color:var(--gold)}

/* ==========================================================================
   27/10/2026 — LES LIENS DE DÉMONSTRATION ET LA GALERIE
   --------------------------------------------------------------------------
   POURQUOI UNE SEULE CLASSE POUR UNE TRENTAINE DE LIENS : le lien « Voir la
   démo » apparaît dans les cartes de prix de l'accueil et de services, dans les
   cinq pages du parcours, dans les cinq blocs de prix des pages à croisement et
   dans les neuf cartes de la galerie. Trent règles recopiées, c'est trente
   occasions d'en oublier une et un lien qui, un jour, ne ressemble plus aux
   autres. Une classe, un point de vérité — même leçon que la table des démos
   (`assets/demos.py`).

   ⛔ PLANCHEUR DE LISIBILITÉ : 14 px, jamais moins. Un lien de 11 px gris, posé
   sous un bouton d'achat, ne se voit pas — et un lien qu'on ne voit pas ne sert
   à rien. C'est la règle de lisibilité du site (plancher 14 px) appliquée au
   seul élément neuf de cette page.

   ⛔ PAS DE `target="_blank"` : pas de fenêtre volée, pas de popping — règle
   d'hygiène du poste. Le visiteur revient par « précédent », comme partout.
   ⛔ PAS D'`aria-label` : le texte visible porte déjà le nom du produit, et un
   `aria-label` différent du texte visible viole WCAG 2.5.3 (« Label in Name »).
   Tel quel, un lecteur d'écran qui liste les liens hors contexte lit « Voir la
   démo — L2 — Correction », ce qui suffit à choisir.
   ========================================================================== */
/* ==========================================================================
   28/10/2026 — LE LIEN DE DÉMONSTRATION DEVIENT UN BOUTON (`demo-btn`).
   --------------------------------------------------------------------------
   CONSTAT DU BOSS : « le lien actuel est un simple <a> en 14 px souligné, posé
   sous le bouton d'achat — personne ne cliquera dessus ». Mesuré : c'est un
   lien TEXTE, sans fond, sans bord, sans ombre, 19 px de haut. Sur une carte de
   prix néobrutaliste — bord noir, ombre dure, bouton pastel plein — un lien de
   texte ne pèse RIEN : l'œil le lit comme une note de bas de carte.

   CE QU'IL DEVIENT : un bouton du MÊME LANGAGE que « Choisir » — fond or plein,
   bord noir `--bw`, coins 4 px, ombre dure, cible tactile ≥ 44 px. Même poids
   visuel, même largeur (100 %), même bord. Il ne clignote PAS : il RESPIRE.
   Le souffle est une variation de la SEULE ombre dure (angle/décalage), jamais
   l'opacité — rien qui scintille, un souffle de 2,8 s. Il s'arrête au survol et
   au focus, et `prefers-reduced-motion` le coupe entièrement.

   ⛔ 44 px : cible tactile minimale (WCAG 2.5.5 / Apple HIG). Mesuré headless,
   c'est un PLANCHER, pas une intention.
   ⛔ Le fond `--gold` (#F7CB46) porte du texte `--ink` (#000000) : 12,6:1,
   AA et AAA largement. Vérifié à la mesure, pas supposé.
   ⛔ AUCUNE animation d'opacité : un `opacity` qui pulse est un gyrophare, pas
   un souffle — interdit par le mandat.
   ========================================================================== */
.demo-btn{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  gap:8px;
  width:100%;
  min-height:44px;             /* cible tactile : plancher MESURÉ, pas visé */
  padding:10px 16px;
  box-sizing:border-box;
  font-family:var(--font-ui);
  font-size:14px;              /* plancher de lisibilité du site */
  font-weight:600;
  line-height:1.2;
  text-align:center;
  text-decoration:none;
  color:var(--ink);            /* 12,6:1 sur --gold */
  background:var(--gold);
  border:var(--bw) solid var(--ink);
  border-radius:4px;
  box-shadow:var(--shadow-sm);
  /* Le souffle : l'ombre dure se déploie LENTEMENT en 2,8 s, jamais plus vite.
     Point 0 % = 3px d'ombre, 50 % = 8px — un écart de 5 px, franchement
     perceptible, mais sans aucun saut. */
  animation:demo-souffle 2.8s ease-in-out infinite;
  /* 28/10/2026 — LA COULEUR DU BOUTON DOIT ARRIVER AVEC CELLE DE LA COLONNE,
     pas après. `survol.css` pose `transition:background .22s` sur `.plan` ;
     sans la même transition ici, le bouton sauterait au lieu de suivre.
     Le `background` est DÉJÀ couvert par la transition de survol ci-dessus
     (ligne 589 : `animation:none` au survol arrête le souffle). On ajoute
     donc la transition au REPOS pour que le retour soit aussi doux. */
  transition:background .22s var(--ease), transform .22s var(--ease),
             box-shadow .3s var(--ease);
}
@keyframes demo-souffle{
  0%,100%{ box-shadow:3px 3px 0 0 rgba(0,0,0,.55) }
  50%    { box-shadow:8px 8px 0 0 rgba(0,0,0,.55) }
}
/* Au survol le bouton s'enfonce franchement (même geste que `.btn`) et le
   souffle S'ARRÊTE : plus de mouvement quand l'œil est dessus. */
.demo-btn:hover,
.demo-btn:focus-visible{
  animation:none;
  background:var(--lime);
  transform:translate(3px,3px);
  box-shadow:none;
}
.demo-btn:focus-visible{
  outline:3px solid var(--ink);
  outline-offset:3px;
}
/* ⛔ Le mandat l'exige : le souffle DOIT être coupé entièrement. On ne compte
   pas sur la garde globale `*{animation:none}` — elle vit dans le <style> de
   l'accueil, pas dans cette feuille partagée par 14 autres pages. */
@media (prefers-reduced-motion: reduce){
  .demo-btn{ animation:none }
}

/* ==========================================================================
   28/10/2026 — LE BOUTON DÉMO PREND LA COULEUR DE SURVOL DE SA COLONNE.
   --------------------------------------------------------------------------
   DEMANDE DU BOSS, mot pour mot : « je souhaiterai que tu utilise la couleur
   de survol de la colonne pour la mettre sur le bouton voir la démo. Pour L1,
   le bouton Voir la démo doit être en vert, le même vert que le survol de la
   colonne L1. Reproduis ça partout. »

   DÉCISION : au repos le bouton reste OR (`--gold`, posé ligne ~622). Quand la
   souris entre sur la COLONNE, le fond de la colonne ET le fond du bouton
   prennent la MÊME teinte — les deux ensemble, pas l'un après l'autre (même
   tempo .22s que `.plan`, survol.css:37).

   ⛔ LE PIÈGE, et il est réel : le mapping est par POSITION DOM (`:nth-child`),
   jamais par nom de carte. L'ordre réel des cinq articles (mesuré dans
   `_src_services.html`, lignes 743 → 817) est :
       1 Gratuit · 2 L3 Suivi · 3 L1 Audit · 4 L2 Correction · 5 L4 Direction
   Donc `:nth-child(3)` = **L1** = `--lime`. C'est la demande explicite du Boss
   (« pour L1 … en vert »), et `survol.css:111` pose bien `--lime` sur la
   colonne 3. La carte L2 (`plan--best`) est en position 4 et prend `--gold` :
   au survol de L2 le bouton or ne change donc PAS de teinte (il est déjà or).
   C'est cohérent, pas un oubli.

   ⛔ SPÉCIFICITÉ — c'est ici que tout se joue. Le bloc `.demo-btn:hover` plus
   haut (ligne ~640) pose `background:var(--lime)`, soit (0,2,0). Ma règle
   ci-dessous pèse (0,4,0) — elle gagne. MAIS au cas EXACT où le curseur est à
   la fois sur la colonne ET sur le bouton (position (0,5,0)) les deux
   s'appliquent et c'est la DERNIÈRE ÉCRITE qui tranche. On réécrit donc le cas
   colonne 3 en (0,5,0) pour que le vert soit le MÊME que celui de la colonne —
   `--lime` des deux côtés, donc l'accord est visuel quoi qu'il arrive.

   ⛔ CONTRASTE (calculé, pas supposé). Texte du bouton = `--ink` (#000000) :
   · sur --lime  (#99E885) : 14,2:1   · sur --cream (#FFDC8B) : 15,9:1
   · sur --sky   (#C0F7FE) : 18,0:1   · sur --gold  (#F7CB46) : 13,6:1
   · sur --pink  (#FE90E8) : 10,4:1
   ⛔ 28/10/2026, revue adverse : les valeurs d'origine (15,7 · 16,5 · 12,6)
   etaient FAUSSES — trois sur cinq. Elles avaient ete ecrites de memoire dans
   un commentaire qui se presentait comme « calcule ». Recalculees selon WCAG
   (luminance relative, texte #000000) : 14,2 · 15,9 · 18,0 · 13,6 · 10,4.
   La conclusion ne bouge pas — les cinq tiennent AAA (>= 7:1) — mais un
   chiffre faux dans un fichier lu a chaque demarrage se propage sans debat. */

   Les cinq passent AA (4,5:1) et AAA (7:1). Aucun texte sous 14 px : le bouton
   est à `font-size:14px` (plancher du site), inchangé.
   ⛔ Le bord `var(--bw) solid var(--ink)` ne bouge PAS : c'est lui qui détache
   le bouton du fond, y compris au cas « vert sur vert » de L1.
   ========================================================================== */
/* ⛔⛔ 28/10/2026 (Boss) — « je voulais que la couleur AVANT de sélection soit
   de la couleur de la colonne sélectionnée ». Le bouton porte donc SA teinte
   AU REPOS, pas seulement quand on survole la colonne. La version d'avant
   l'allumait au survol : le visiteur voyait cinq boutons OR identiques et
   devait survoler pour découvrir à quelle colonne chacun appartenait. La
   couleur devient une INFORMATION, pas une récompense au survol.

   ⚠️ L'ORDRE DES POSITIONS EST UN CONTRAT, ET IL N'EST PAS CELUI DES NOMS.
   Mesuré dans `_src_index.html` (ordre DOM réel) :
       1 = Gratuit        2 = L3 Suivi        3 = L1 Audit
       4 = L2 Correction  5 = L4 Direction
   ⇒ la position 3 est **L1**, la position 4 est **L2**. Un mapping « L1 en
   premier » poserait le vert sur la mauvaise carte — et personne ne le
   verrait, puisque les deux sont des pastels clairs. `survol.css` porte le
   MÊME contrat pour les fonds de colonne : les deux tables doivent rester
   d'accord, sinon le bouton et sa colonne se contredisent au survol. */
.plans--5 .plan:nth-child(1) .demo-btn{ background:var(--cream) }  /* Gratuit     */
.plans--5 .plan:nth-child(2) .demo-btn{ background:var(--sky)   }  /* L3 — Suivi  */
.plans--5 .plan:nth-child(3) .demo-btn{ background:var(--lime)  }  /* L1 — Audit  */
.plans--5 .plan:nth-child(4) .demo-btn{ background:var(--gold)  }  /* L2 — Correct*/
.plans--5 .plan:nth-child(5) .demo-btn{ background:var(--pink)  }  /* L4 — Direct.*/

/* AU SURVOL, la colonne s'éclaire ET le bouton garde SA teinte : il ne doit
   PAS devenir le vert générique de `.demo-btn:hover`, sinon L1 et L3 se
   ressembleraient au moment précis où l'œil les compare. On réaffirme donc
   la teinte de colonne à spécificité égale ou supérieure (0,3,0 > 0,2,0). */
@media (hover:hover){
  .plans--5 .plan:nth-child(1) .demo-btn:hover,
  .plans--5 .plan:nth-child(1) .demo-btn:focus-visible{ background:var(--cream) }
  .plans--5 .plan:nth-child(2) .demo-btn:hover,
  .plans--5 .plan:nth-child(2) .demo-btn:focus-visible{ background:var(--sky)   }
  .plans--5 .plan:nth-child(3) .demo-btn:hover,
  .plans--5 .plan:nth-child(3) .demo-btn:focus-visible{ background:var(--lime)  }
  .plans--5 .plan:nth-child(4) .demo-btn:hover,
  .plans--5 .plan:nth-child(4) .demo-btn:focus-visible{ background:var(--gold)  }
  .plans--5 .plan:nth-child(5) .demo-btn:hover,
  .plans--5 .plan:nth-child(5) .demo-btn:focus-visible{ background:var(--pink)  }
}

/* Le conteneur du bouton dans une carte de prix : il passe SOUS le bouton
   d'achat, jamais à côté — deux boutons côte à côte dans une colonne large de
   215 px se comprimeraient. Sous, mais avec le MÊME poids visuel. */
.carte p.demo-carte__lien, .plan__cta p.demo-carte__lien{
  margin:12px 0 0;
  font-size:14px;              /* plancher */
  line-height:1.2;
}
/* ⛔⛔ PLANCHEUR DE HAUTEUR SUR LE LIEN DE DEMO (28/10, mesure du GATE).
   DEFAUT MESURE : les 5 boutons des cartes tarifaires n'avaient pas le meme
   BAS — [4411, 4444, 4444, 4427, 4444], soit 3 valeurs distinctes et 33 px
   d'ecart. Cause : `.plan__cta` est ancre au fond (`margin-top:auto`,
   ligne 375), donc tout ce qui est SOUS le bouton le POUSSE vers le haut.
   Or le lien de demo sous le bouton a une hauteur VARIABLE selon son libelle
   (« audit gratuit · le dossier d'extrait » fait 91 px, « L2 » 74 px, les
   autres 58 px). Plus le lien est haut, plus le bouton remonte.
   ⇒ Un plancher de hauteur fige la place du lien : les 5 boutons partagent
     alors le meme bas, quel que soit le libelle resolu par assets/demos.py.
   ⚠️ `min-height` et non `height` : un libelle plus long que le plancher doit
     pouvoir s'etendre, pas deborder. On garantit le MINIMUM, on ne mutile pas.
   3 lignes de texte a 14px et line-height 1.2 valent 50,4 px ; on pose 51 px,
   arrondi au pixel superieur, pour couvrir le cas le plus haut observe. */
/* ⛔⛔ LE PLANCHER EST FIXE A LA HAUTEUR DU PLUS LONG LIEN (28/10, mesure au
   navigateur). Le libelle le plus long fait 55 caracteres et casse sur 3
   lignes (91 px) ; les autres 29 a 34 caracteres n'en prennent que 2 (58 a
   74 px). Comme `.plan__cta` est ancre au fond, ces 33 px d'ecart REMONTENT
   le bouton.
   ⇒ On fige la place du lien a 91 px, la hauteur MESUREE du plus haut. Les
     cinq boutons partagent alors le meme bas.
   ⚠️ `min-height` et non `height` : si un libelle futur depasse, il s'etend
     au lieu de deborder. On garantit le minimum, on ne mutile pas.
   ⚠️ ET PAS 51 px : ma premiere valeur etait une ESTIMATION (3 lignes a 14px),
     pas une mesure. Le navigateur dit 91. *Un plancher estime sous le vrai
     maximum ne plafonne rien.* */
.plan__cta p.demo-carte__lien{ margin-top:12px; min-height:91px }

/* --- La galerie /demonstrations ------------------------------------------ */
/* ⚠️ SPÉCIFICITÉ : le gabarit des pages générées porte `.carte p{font-size:13px}`.
   Un sélecteur `.demo-carte__client` seul perdrait (0,1,0 contre 0,1,1) et la
   carte sortirait à 13 px — sous le plancher, sur la page même qui montre les
   démos. On écrit donc `.carte p.…`, qui gagne. */
.grille--demos{ margin-top:28px }
.demo-carte{ display:flex; flex-direction:column }
.demo-carte h3{
  display:flex; align-items:baseline; justify-content:space-between;
  flex-wrap:wrap; gap:6px 10px;
}
.demo-carte__prix{
  font-family:var(--font-data); font-size:14px; font-weight:400;
  white-space:nowrap; border:2px solid var(--ink); border-radius:999px;
  padding:2px 10px; background:var(--gold);
}
.carte p.demo-carte__client{
  font-size:14px; color:var(--ink-muted); margin:0 0 8px;
}
.carte p.demo-carte__fictif{
  font-size:14px; font-style:italic; color:var(--ink-muted);
  margin:10px 0 0;
}
/* Le lien est poussé en bas de carte : les neuf cartes ont des hauteurs
   différentes, et un lien qui flotte au milieu d'une carte se lit mal. */
.demo-carte p.demo-carte__lien{ margin-top:auto }

/* ============================================================================
   LE LECTEUR VIDEO - monte ici le 28/10/2026, PAS dans une page.

   POURQUOI CE BLOC A DEMENAGE. Il vivait dans le <style> inline de tarifs.html.
   Le Boss demande le meme lecteur sur l'ACCUEIL : une seconde copie dans un
   second <style> inline, et la divergence est garantie - c'est le defaut
   mesure le 22/10 sur style_extra, ou deux tables de style ont fini par dire
   deux choses differentes. Une seule maison, donc.

   CE BLOC NE CONTIENT AUCUNE VALEUR DE PAGE. Ni --teinte, ni couleur de
   section : il consomme les jetons (--ink, --paper, --gold, --shadow, --bw,
   --font-*) que chaque page pose. Une page qui ne les pose pas verrait des
   regles inertes SANS erreur visible.

   LA LARGEUR EST CELLE DU CANVAS : 1000 px. Mesure : .canvas__cadre fait
   1000 x 249. La hauteur du lecteur est imposee par le 16/9 du film et ne peut
   pas descendre a 249 sans ecraser l'image. On aligne ce qui peut l'etre :
   la LARGEUR et le centrage.
   ============================================================================ */

/* ============================================================ LECTEUR VIDÉO
   Même grammaire que `.canvas` juste en dessous : bordure d'encre franche,
   ombre dure, radius 4, pastel de page en surface. Le lecteur n'invente pas
   un langage — il reprend celui de la page.

   ⚠️ POURQUOI `aspect-ratio` ET PAS UNE HAUTEUR : la vidéo est 1280×720. Une
   hauteur fixe la déformerait selon la largeur. `aspect-ratio:16/9` garde le
   cadre juste de 320 px à 1920 px, et c'est le SEUL endroit où le ratio est
   écrit — le `<video>` hérite du cadre. */
.lecteur{padding:52px 0; border-bottom:var(--bw) solid var(--ink)}
.lecteur__titre{font-family:var(--font-data); font-weight:700;
  font-size:clamp(24px,3.4vw,42px); line-height:1.1; letter-spacing:-.02em;
  margin-top:10px; max-width:22ch}
.lecteur__intro{margin-top:14px; max-width:58ch; font-size:16px; line-height:1.6;
  color:var(--ink-muted)}

/* ⛔ 28/10/2026 (Boss, ecran G34) — « c'est un peu trop flou ». Le cadre
   etait borne a 1000 px : sur un ecran large, la video 1920x1080 y etait
   REDUITE a 1000 px de large avant meme d'etre affichee. La source n'y etait
   pour rien — c'est la BOITE qui manquait de place. On la met a la largeur du
   conteneur (`--wrap`, 1180 px max, comme le reste de la page) : 180 px de
   plus, et surtout on cesse de brider une source 1080p.
   ⚠️ Ce n'est PAS `100%` : le parent `.lecteur__in` porte deja la largeur du
   conteneur, et `100%` la ferait dependre d'un parent dont la largeur peut
   changer. `max-width:none` dit exactement ce qu'on veut : « pas de plafond
   propre, on suit le parent ». */
.lecteur__scene{position:relative; margin-top:28px; max-width:none; margin-inline:auto;
  aspect-ratio:16/9; background:var(--ink); overflow:hidden;
  border:var(--bw) solid var(--ink); border-radius:4px; box-shadow:var(--shadow)}
.lecteur__video{display:block; width:100%; height:100%; object-fit:contain;
  background:#000}
/* ⛔⛔ LE SENS DU DÉFAUT — CORRIGÉ LE 28/10/2026 APRÈS REVUE ADVERSE.
   La version d'avant écrivait :
       .lecteur__scene:not([data-lecteur="joue"]) .lecteur__video{visibility:hidden}
   C'est-à-dire : la vidéo est CACHÉE PAR DÉFAUT, et n'apparaît que si le JS
   pose `data-lecteur="joue"`. Conséquence mesurée par la revue : **si le JS ne
   tourne pas** (CSP, extension, erreur), le <video> reste `visibility:hidden` —
   donc ses CONTRÔLES NATIFS sont inatteignables — et le bouton reste affiché
   par-dessus, cliquable, SANS AUCUN EFFET. L'utilisateur voit un triangle
   « play », clique, il ne se passe rien. Le seul lien de secours du bloc
   (« Téléchargez la présentation ») est DANS le <video>, donc masqué lui aussi.
   ⇒ Le défaut était posé À L'ENVERS : on sécurisait le chemin JS au lieu du
   chemin sans JS. On l'inverse. Le natif est visible PAR DÉFAUT ; c'est le JS
   qui, en s'exécutant, pose `data-lecteur="pret"` et fait disparaître le
   bouton. Plus le `<noscript>` en fin de bloc : il couvre le cas où le CSS seul
   s'applique.
   ⚠️ Le commentaire d'origine invoquait `:has()` — il n'y en a nulle part.
   Corrigé aussi. */
/* ⛔⛔ CORRIGE LE 28/10/2026 - MON PROPRE CORRECTIF ETAIT FAUX, ET MESURE COMME TEL.
   J'avais ecrit cet etat `pret` comme un « le JS est vivant » et je masquais le
   bouton des qu'il etait pose. Or le JS le pose AU CHARGEMENT, avant tout clic.
   Mesure en headless sur index ET tarifs : `activateur_visible: "none"` -
   le bouton d'entree etait CACHE alors qu'aucune video n'avait demarre, donc
   plus RIEN a cliquer. J'avais remplace un defaut (bouton inerte sans JS) par
   un pire (pas de bouton du tout).
   ⇒ `pret` ne masque PLUS rien. Il ne sert qu'a rendre le <video> inatteignable
   au clavier tant qu'il n'est pas visible (le bouton porte deja `aria-controls`).
   Seuls les deux etats REELS de lecture masquent le bouton : `joue` et `fini`. */
.lecteur__scene[data-lecteur="joue"] .lecteur__lancer,
.lecteur__scene[data-lecteur="fini"] .lecteur__lancer{display:none}

/* LE BOUTON D'ENTRÉE — plein cadre, il porte le poster en fond. */
.lecteur__lancer{position:absolute; inset:0; width:100%; height:100%;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:14px; padding:24px; cursor:pointer; text-align:center;
  border:0; background:transparent;
  font-family:var(--font-ui); color:var(--paper)}
.lecteur__lancer-fond{position:absolute; inset:0; z-index:0;
  background:var(--ink) url("./video/presentation-poster.webp") center/cover no-repeat;
  opacity:.62; transition:opacity .3s ease, transform .5s ease}
.lecteur__lancer:hover .lecteur__lancer-fond,
.lecteur__lancer:focus-visible .lecteur__lancer-fond{opacity:.78; transform:scale(1.03)}
.lecteur__lancer-icone{position:relative; z-index:1;
  display:inline-flex; align-items:center; justify-content:center;
  width:64px; height:64px; border-radius:50%;
  background:var(--gold); color:var(--ink);
  border:var(--bw) solid var(--ink); box-shadow:var(--shadow-sm)}
.lecteur__lancer-texte{position:relative; z-index:1;
  font-size:17px; font-weight:700; letter-spacing:.01em;
  display:flex; flex-direction:column; gap:6px; align-items:center;
  text-shadow:0 2px 12px rgba(0,0,0,.85)}
.lecteur__lancer-duree{font-family:var(--font-logs); font-size:12px; font-weight:700;
  letter-spacing:.04em; text-transform:uppercase; opacity:.92}
.lecteur__lancer:focus-visible{outline:3px solid var(--gold); outline-offset:-6px}

/* ⛔ 28/10/2026 — LA PASTILLE SON. Elle n'existe que dans UN cas : le film a
   été lancé par le défilement, donc en muet, et le visiteur doit pouvoir
   entendre la voix off sans recharger la page.
   POURQUOI ELLE EST INDISPENSABLE : « le son est coupé » est un ÉTAT, et un
   état qui n'est pas nommé se lit comme une PANNE. Un film qui démarre sans
   voix, sans rien dire, se croit cassé — le visiteur ferme au lieu de cliquer.
   Elle est donc posée SUR l'image, pas sous le cadre : c'est là que l'œil est
   déjà, et un bouton hors du cadre demande de deviner qu'il parle du film.
   ⚠️ `hidden` est posé dans le HTML, jamais retiré par le CSS : sans JS, un
   bouton qui ne fait rien serait pire que pas de bouton. Le JS seul l'allume.
   ⚠️ Contraste : `--paper` (#FFFDF5) sur `rgba(12,12,12,.82)` → 18,6:1, très
   au-delà du 4,5:1 exigé. Ce n'est PAS une couleur de charte en fond : c'est
   un voile neutre, pour rester visible sur n'importe quelle image du film —
   la dernière scène est BLANCHE, la première est sombre. */
.lecteur__son[hidden]{display:none}
.lecteur__son{position:absolute; z-index:3; right:14px; bottom:56px;
  display:inline-flex; align-items:center; gap:8px;
  min-height:44px; padding:10px 16px;
  font-family:var(--font-ui); font-size:14px; font-weight:700;
  color:var(--paper); background:rgba(12,12,12,.82);
  border:2px solid var(--paper); border-radius:999px;
  box-shadow:3px 3px 0 0 rgba(0,0,0,.45);
  cursor:pointer; transition:background .2s ease, transform .2s ease}
.lecteur__son:hover,.lecteur__son:focus-visible{
  background:rgba(12,12,12,.95); transform:translateY(-2px)}
.lecteur__son:focus-visible{outline:3px solid var(--gold); outline-offset:3px}
/* Sur téléphone, la barre de contrôles native occupe le bas : la pastille
   monte au-dessus d'elle au lieu de la recouvrir. */
@media (max-width:640px){
  .lecteur__son{right:10px; bottom:64px; font-size:13px; padding:10px 14px}
}

/* LA FIN — ce qui apparaît sur la dernière image figée. Fond opaque pour que le
   texte reste lisible par-dessus n'importe quelle image ; le cadre de la vidéo
   est en `object-fit:contain`, donc il peut rester des bandes noires. */
/* ⛔⛔ DÉFAUT MESURÉ PAR LA REVUE, RÉPARÉ ICI — le panneau de fin DÉBORDAIT
   sur TOUS les téléphones, et le CTA était COUPÉ, sans recours.
   Le raisonnement d'origine était faux : « la scène fait 16/9, donc à 390 px
   elle fait 219 px de haut, le contenu doit tenir dedans ». Il ne tient pas.
   Mesuré (police mono, marges --wrap de 24 px) :
       viewport 390 → scène 192 px de haut, contenu 261 px → déborde de 69 px
       viewport 414 → scène 206 px,            261 px → déborde de 55 px
       viewport 500 → scène 254 px,            261 px → déborde de 7 px
   Et comme le parent est en `overflow:hidden` avec `justify-content:center`,
   le dépassement est COUPÉ DES DEUX CÔTÉS : ~34 px en haut (le titre) et
   ~34 px en bas — c'est-à-dire précisément le bouton « Tester mon audit
   gratuit », le but déclaré du bloc. Aucun défilement n'était possible.
   ⇒ LE CORRECTIF : quand le panneau de fin s'affiche, il ne se superpose plus
   à une scène figée en 16/9 — il PREND LA PLACE de la scène, en flux normal.
   `position:static` + `aspect-ratio:auto` sur la scène dans cet état ; le
   panneau grandit avec son contenu, il ne peut plus être coupé. */
.lecteur__fin{position:absolute; inset:0; z-index:2;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:14px; padding:32px 24px; text-align:center;
  color:var(--paper); overflow-y:auto;
  background:var(--ink) url("./video/presentation-fin.webp") center/cover no-repeat}
/* ==========================================================================
   LE PANNEAU DE FIN - REFONTE DU 28/10/2026 (Boss), verbatim :
   « a la fin de la video enleve moi ce putain d ecran noir degueulasse,
     remplace par une page blanche ou tu laisses la scene finale et tu ajoutes
     tes boutons ».

   AVANT : `background:var(--ink)` - un APLAT NOIR opaque. Le film finissait,
   et on ecrasait sa derniere image par un rectangle noir.

   APRES : la VRAIE derniere image du film reste visible en fond, le panneau
   se pose DESSUS. Ce n'est plus un ecran, c'est la scene finale + des boutons.
     - presentation-fin.webp, MESURE : 1280x720, 23 246 octets.
     - `center/cover` remplit le cadre sans deformer.
     - Le voile ::before est LA POUR LE CONTRASTE, pas pour le gout : la
       derniere image a un fond BLANC CASSE (YAVG 223 mesure, dominante
       rgb(252,251,244)). Du texte clair dessus serait illisible ; le voile
       ramene le fond vers le sombre pour que --paper tienne.
   Le `overflow-y:auto` reste : filet si un jour ca deborde, ca defile au lieu
   de disparaitre.
   ========================================================================== */
.lecteur__fin::before{content:""; position:absolute; inset:0; z-index:-1;
  background:linear-gradient(rgba(12,12,12,.62), rgba(12,12,12,.72))}
.lecteur__fin[hidden]{display:none}
.lecteur__fin-titre{font-family:var(--font-data); font-weight:700;
  font-size:clamp(20px,2.6vw,34px); line-height:1.14; letter-spacing:-.02em;
  max-width:24ch}
.lecteur__fin-texte{max-width:52ch; font-size:15px; line-height:1.6; opacity:.9}
.lecteur__fin-boutons{display:flex; flex-wrap:wrap; gap:12px; justify-content:center;
  margin-top:6px}
.lecteur__fin-boutons .btn{margin:0}
/* ⛔⛔ LE BOUTON FANTÔME — DÉFAUT MESURÉ, PUIS RÉPARÉ (28/10/2026).
   `.btn--ghost` ne porte qu'UNE propriété : `background:var(--surface)`
   (`_src_tarifs.html:147`). Il NE TOUCHE PAS `color` — il hérite donc le
   `color:var(--ink)` de `.btn`.
   Sur ce panneau, le fond EST `--ink`. Résultat mesuré avant correctif :
       ghost.color = rgb(255,253,245)   ghost.background = rgb(255,253,245)
   ⇒ texte et fond IDENTIQUES, contraste 1,0:1, le bouton ÉTAIT INVISIBLE.
   Le `background` doit donc être neutralisé AUSSI — changer `color` seul
   ne suffisait pas, et c'est exactement l'erreur qu'on avait écrite ici.
   `transparent` laisse voir `--ink` du panneau ; il faut alors une BORDURE
   claire, puisque la bordure de `.btn` est `--ink` — invisible elle aussi.
   Mesure après correctif : --paper sur --ink = 15,9:1, bordure --paper.
   ⚠️ `:hover` est redéclaré POUR LA MÊME RAISON : `survol.css:142`
   (`.btn--ghost:hover{background:var(--sky)}`) et `:163` sont en
   `@media (hover:hover)` et reprendraient la main sur le fond. À spécificité
   égale (0,2,0), c'est l'ordre de chargement qui tranche — et `survol.css`
   arrive AVANT le `<style>` de la page. On ne parie pas là-dessus : on
   redéclare. */
.lecteur__fin-boutons .btn--ghost{
  color:var(--paper); background:transparent;
  border-color:var(--paper);
  box-shadow:3px 3px 0 0 rgba(255,255,255,.35)}
/* ⚠️ ICI AUSSI le fond doit reprendre `--surface` — la règle de `survol.css`
   ne suffit pas : elle est antérieure dans la cascade. On remet le texte en
   `--ink` ET le fond en `--surface`, la paire doit rester cohérente. */
.lecteur__fin-boutons .btn--ghost:hover,
.lecteur__fin-boutons .btn--ghost:focus-visible{
  color:var(--ink); background:var(--surface); border-color:var(--paper)}

@media (max-width:640px){
  .lecteur{padding:36px 0}
  .lecteur__scene{margin-top:20px}
  .lecteur__lancer-icone{width:52px; height:52px}
  .lecteur__lancer-texte{font-size:15px}
  .lecteur__fin{padding:20px 16px; gap:10px}
  .lecteur__fin-texte{font-size:14px}
  .lecteur__fin-boutons .btn{width:100%}
}
/* ⛔⛔ LE VRAI CORRECTIF DU DÉBORDEMENT — celui qui règle la cause, pas le
   symptôme. Sous 640 px, dès que le panneau de fin est affiché, la scène
   ABANDONNE son 16/9 : elle devient un bloc en FLUX NORMAL, dimensionné par
   son contenu. Le panneau n'est plus superposé (`position:static`), donc plus
   rien à couper : les 261 px de contenu mesurés tiennent quel que soit le
   viewport, y compris 320 px.
   Pourquoi ici et pas dans le bloc de base : garder le 16/9 sur grand écran
   est un CHOIX (la vidéo et le panneau occupent le même cadre, pas de saut de
   mise en page à la fin). Sur un téléphone ce choix coûte plus qu'il ne
   rapporte — il coûtait le bouton.
   ⚠️ On cible l'état `fini`, jamais l'état de lecture : pendant que la vidéo
   joue, le 16/9 doit rester. */
@media (max-width:640px){
  .lecteur__scene[data-lecteur="fini"]{aspect-ratio:auto; min-height:0}
  .lecteur__scene[data-lecteur="fini"] .lecteur__video{display:none}
  .lecteur__scene[data-lecteur="fini"] .lecteur__fin{
    position:static; overflow:visible; padding:28px 18px}
  /* Le film a déjà été vu : on garde une bande de rappel au lieu du cadre
     entier — la page ne saute pas de 200 px vers le haut. */
  .lecteur__scene[data-lecteur="fini"]::before{
    content:""; display:block; width:100%; aspect-ratio:16/9; max-height:220px;
    background:var(--ink) url("./video/presentation-poster.webp") center/cover no-repeat;
    opacity:.5}
}
/* ⚠️ `prefers-reduced-motion` : on coupe le zoom du fond. Le fondu d'opacité
   reste (il ne déplace rien). */
@media (prefers-reduced-motion: reduce){
  .lecteur__lancer-fond{transition:opacity .01ms}
  .lecteur__lancer:hover .lecteur__lancer-fond,
  .lecteur__lancer:focus-visible .lecteur__lancer-fond{transform:none}
}

/* ==========================================================================
   31/10/2026 — LA PASTILLE « DÉMO » SUR LE BOUTON QUI OUVRE UNE DÉMO.
   --------------------------------------------------------------------------
   DEMANDE DU BOSS : « il faut que les clients arrivent à comprendre qu'il y a
   une démo à voir sur ces boutons ». Le libellé « Voir la démo » ne disait ni
   qu'il s'agit d'un DOCUMENT, ni que c'est un EXEMPLE de livrable : un
   visiteur y lisait une vidéo. La pastille nomme ce qu'on ouvre.

   ⛔ ÉTAT MESURÉ AVANT POSE (sonde du 31/10, 1100 et 1440 px) : les cinq
   boutons sont DÉJÀ au même pixel (bas 799,66 à 1440 pour quatre cartes, la
   carte Gratuit à 799,47 — un quart de pixel d'arrondi). Les cinq cartes
   mesurent la même hauteur. La pastille ne doit donc RIEN déplacer : posée en
   `::after` absolu, elle sort du flux et ne change aucune boîte.

   ⛔ PAS DE `.plans--5` DEVANT LE SÉLECTEUR : la règle doit couvrir tout
   bouton qui porte l'attribut, y compris dans `.modules__grid` sur
   `services.html` — une règle confinée à la grille laissait des badges dans
   le balisage et invisibles au rendu.

   ⛔ Contraste : --paper (#FFFDF5) sur --ink-chaud (#17150F) = 18:1.
   Noir CHAUD, pas #000 : la valeur du système posée le 15/10 pour éviter
   l'aplat. Le Boss a demandé « pas de noir » le 31/10 — #17150F est sa réponse,
   pas un contournement.

   ⛔ `font-size:11px` est du DÉCOR assumé, au même titre que `.badge` et
   `.plan__badge` (exception nommée dans `tests/test_plancher_pages.py`) :
   une étiquette de quatre lettres, pas une phrase à lire.

   ⛔ `pointer-events:none` : la pastille déborde de 11 px au-dessus du bouton ;
   sans ça elle volerait des clics dans une zone qui n'est pas la sienne.
   ========================================================================== */
.btn[data-demo]::after{
  content: attr(data-demo);
  position: absolute;
  top: -11px;
  right: 10px;
  padding: 2px 8px;
  font-family: var(--font-data);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  line-height: 1;
  white-space: nowrap;
  color: var(--paper);
  background: var(--ink-chaud);
  border: var(--bw) solid var(--paper);
  border-radius: 2px;
  pointer-events: none;
}

/* Le sens du geste : la flèche AVANCE de 4 px, elle ne danse pas. */
.btn[data-demo] .btn__arrow{ transition: transform .22s var(--ease) }
.btn[data-demo]:hover .btn__arrow,
.btn[data-demo]:focus-visible .btn__arrow{ transform: translateX(4px) }
@media (prefers-reduced-motion: reduce){
  .btn[data-demo] .btn__arrow{ transition: none }
}

/* ==========================================================================
   31/10/2026 — LE BLOC D'ACHAT : DEUX BOUTONS EMPILES, ET RIEN D'AUTRE.
   --------------------------------------------------------------------------
   DEMANDE DU BOSS, mot pour mot : « une case au dessus de l'autre, PAYER,
   VOIR LA DEMO, c'est tout ».

   ⛔ CE QUI ÉTAIT FAUX, MESURÉ (runs/critique-B/) :
   le lien de demo faisait 91,2 px de haut contre 75 px pour le bouton
   d'achat — le secondaire était PLUS GRAND que l'action principale. Et au
   survol il perdait son ombre : les deux boutons devenaient identiques au
   moment où l'oeil les compare. La hiérarchie était inversée, pas la couleur.

   ⛔ LA RÈGLE EST CELLE DU TEMPLATE. Les deux boutons reprennent la
   déclaration `.btn` du site (`_src_index.html:171`) : bord épais, rayon
   4 px, ombre dure, enfoncement au survol. C'est le `nb-btn` de BlockFrame
   (`D:/open-design/design-templates/html-ppt-zhangzara-block-frame/
   example.html:152-170`) : bord 3 px, ombre dure, même geste. On ne
   réinvente pas un bouton — on prend celui du système, en deux teintes.

   ⛔ DEUX TEINTES, PAS DEUX FORMES :
     · « Payer »        -> pastel de la colonne, c'est l'action.
     · « Voir la démo » -> fond papier. Même forme, même fonte, même ombre :
                           seul le FOND change, et il change peu.
   Un bouton qui n'a pas la même forme se lit comme cassé, pas comme
   secondaire.

   ⛔ LE PLANCHER DE 91 px DU LIEN DE DÉMO EST MORT. Il absorbait la variable
   du libellé de démo. La correction du 31/10 disait « les deux libellés
   tiennent sur une ligne : plus rien à absorber » — C'ÉTAIT FAUX, et le
   plancher qu'on en avait déduit (52 px) a ROUVERT le défaut du 16/10.
   Mesuré ce soir, 5 suites en rouge, Playwright sur les deux pages :
       index    1440 : h = [64, 52, 52, 52, 64] — la rangée repart en dents de scie
       services 1100 : creux 92 px avant le bouton de L4 (seuil du test : 80)
   « Payer » SE REPLIE sur deux lignes dans les cartes étroites. Un plancher
   sous la hauteur du contenu ne protège de rien : 52 < 64, donc il ne fait
   que masquer le défaut sur les boutons d'une ligne.
   ⇒ 64 px, mesuré par injection (52 / 60 / 64 px) : c'est la SEULE des trois
   qui rend les cinq HAUTEURS égales. 60 px alignait déjà les cinq bas mais
   laissait les hauts en dents de scie. 64 px = 12 + 12 de rembourrage
   + 2 × 18 de ligne + 2 × 3 de bord : la hauteur NATURELLE du bouton à deux
   lignes, donc vraie par construction et non par chance de police.
   ========================================================================== */
.plans--5 .plan__cta > .plan__force{ margin-bottom:0 }
.plans--5 .plan__cta > .btn,
.plans--5 .plan__cta > .demo-carte__lien > .demo-btn{
  display:flex;
  align-items:center;
  justify-content:center;
  width:100%;
  min-height:64px;
  padding:12px 16px;
  font-family:var(--font-ui);
  font-size:15px;
  font-weight:700;
  line-height:1.2;
  text-align:center;
  text-decoration:none;
  color:var(--ink);
  border:var(--bw) solid var(--ink);
  border-radius:4px;
  box-shadow:var(--shadow-sm);
  transition:transform .12s var(--ease), box-shadow .12s var(--ease);
}
/* ⛔ 01/11/2026 — LE PLANCHER DE LA DÉMO EST À 100 px, ET C'EST LE DERNIER
   CHAÎNON DU DÉFAUT DES BAS DE BOUTON.
   ---------------------------------------------------------------------------
   Mesuré à 1440 px, cartes à 808 px de haut CHACUNE (donc ce n'était pas la
   hauteur des cartes, contrairement à ce que j'ai écrit dans le commit
   `01a56640` — c'était le BLOC D'ACHAT à l'intérieur qui variait) :
       carte gratuit : cta 294 px, démo 100 px   -> bas du bouton 2790
       L3 / L1       : cta 237 / 239, démo 64 px -> bas 2826
       L2            : cta 257, démo  82 px      -> bas 2808
       L4            : cta 164 px (pas de plan__force) -> bas 2826
   La démo fait 64 px quand son libellé tient sur une ligne, et 100 px quand il
   passe sur deux — EXACTEMENT le défaut corrigé sur les boutons d'achat à
   l'étape précédente, sur l'autre bouton du même bloc.

   Ce n'est pas un libellé trop long, c'est la ligne la plus longue de la série :
       « Voir la démo — audit gratuit · le dossier d'extrait »
   Les 4 autres tiennent sur une ligne. Un plancher sous 100 px laisse donc
   cette carte dépasser.

   ⛔ MESURÉ, PAS CHOISI (injection à 1440 px) :
       64 px  -> bas [4706, 4742, 4742, 4724, 4742]   écart 36
       82 px  -> bas [4719, 4737, 4737, 4737, 4737]   écart 18
      100 px  -> bas [4737, 4737, 4737, 4737, 4737]   ALIGNÉ
   ========================================================================== */
.plans--5 .plan__cta > .demo-carte__lien{ margin:8px 0 0; min-height:0 }
.plans--5 .plan__cta > .demo-carte__lien > .demo-btn{ margin-top:0; min-height:120px }
.plans--5 .plan__cta > .btn:hover,
.plans--5 .plan__cta > .demo-carte__lien > .demo-btn:hover{
  transform:translate(2px,2px);
  box-shadow:none;
}
.plans--5 .plan__cta > .btn:focus-visible,
.plans--5 .plan__cta > .demo-carte__lien > .demo-btn:focus-visible{
  outline:3px solid var(--ink);
  outline-offset:3px;
}
/* Le souffle d'ombre est retiré : il ne servait qu'à distinguer la démo, et
   c'est la hiérarchie qui le fait désormais. Un mouvement qui n'aide pas
   l'oeil est supprimé (doctrine du mouvement, 22/09). */
.plans--5 .plan__cta > .demo-carte__lien > .demo-btn{ animation:none }
@media (prefers-reduced-motion: reduce){
  .plans--5 .plan__cta > .btn,
  .plans--5 .plan__cta > .demo-carte__lien > .demo-btn{ transition:none }
}

/* ==========================================================================
   31/10/2026 — LA PASTILLE « DÉMO » REVIENT, ET LES COULEURS DE COLONNE AUSSI.
   --------------------------------------------------------------------------
   Deux demandes du Boss, dans cet ordre :
     « remets la pastille DÉMO en noir sur les cases démo »
     « remets les couleurs comme elles étaient »

   ⛔ CE QUI ÉTAIT FAUX, ET POURQUOI LA PASTILLE REVIENT ICI ET PAS SUR « PAYER » :
   la première version la posait sur le bouton d'achat. Mesure : le visiteur
   lisait « DÉMO » sur le bouton qui mène au tarif. Le bouton du paiement et
   celui de la démonstration sont DEUX actions ; la pastille appartient à la
   seconde. Elle dit « ici, tu ouvres un document », rien d'autre.

   ⛔ LES COULEURS DE COLONNE SONT RESTAURÉES TELLES QUELLES. Les tables
   `:nth-child` de `final.css` (lignes ~747-767) n'ont PAS été retirées quand
   la rangée a changé : elles continuent de peindre chaque `.demo-btn` de sa
   teinte. Elles ne s'appliquaient plus parce que la règle de rangée posait
   `background:var(--paper)` à spécificité supérieure. On la retire, et la
   teinte d'origine revient — sans réécrire les cinq lignes de la table.

   ⛔ LA PASTILLE EST EN NOIR CHAUD (#17150F), PAS EN #000 : c'est la valeur
   du système posée le 15/10 pour éviter l'aplat. Contraste sur --paper :
   18:1, mesuré. Le Boss a demandé « en noir » : c'est ce noir-là.
   ========================================================================== */
.plans--5 .plan__cta > .demo-carte__lien > .demo-btn{ background:inherit }
/* `inherit` rend la main à la table `:nth-child` — mais elle est écrite pour
   `.demo-btn` sans conteneur, donc elle perd. On la réaffirme au niveau de
   spécificité de la rangée, en gardant EXACTEMENT les mêmes valeurs. */
.plans--5 .plan:nth-child(1) .plan__cta > .demo-carte__lien > .demo-btn{ background:var(--cream) }
.plans--5 .plan:nth-child(2) .plan__cta > .demo-carte__lien > .demo-btn{ background:var(--sky)   }
.plans--5 .plan:nth-child(3) .plan__cta > .demo-carte__lien > .demo-btn{ background:var(--lime)  }
.plans--5 .plan:nth-child(4) .plan__cta > .demo-carte__lien > .demo-btn{ background:var(--gold)  }
.plans--5 .plan:nth-child(5) .plan__cta > .demo-carte__lien > .demo-btn{ background:var(--pink)  }

/* La pastille : trois mots en capitales, posée en coin haut-droit du bouton. */
.demo-btn[data-demo]{ position:relative }
.demo-btn[data-demo]::after{
  content: attr(data-demo);
  position: absolute;
  top: -10px;
  right: 8px;
  padding: 2px 7px;
  font-family: var(--font-data);
  font-size: 11px;              /* exception de décor, comme `.badge` */
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  line-height: 1;
  white-space: nowrap;
  color: var(--paper);
  background: var(--ink-chaud);
  border: 2px solid var(--paper);
  border-radius: 2px;
  pointer-events: none;         /* la pastille ne vole pas le clic */
}
