/* ==========================================================================
   components.css — les composants du CHARTE §5, et rien d'autre
   Bordures plutot qu'ombres (§2.5) : en theme sombre, l'ombre ne se voit pas.
   Rayon 4-8 px (§2.4) : le « pill » appartient au SaaS grand public.
   ========================================================================== */

/* --- Boutons : trois niveaux, jamais plus (§5) --------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space);
  /* ⚠️ 40 -> 44 px et 15 -> 16 px au W12-a. Un bouton de 40 px a 15 px lit
     « composant de formulaire » ; le CTA unique du site (WEBSITE_GUIDE §2.1)
     doit lire « action ». 44 px est aussi la cible tactile recommandee. */
  height: 44px;
  padding: 0 22px;
  border-radius: var(--radius);
  border: 1px solid transparent;
  font: inherit;
  font-size: 16px;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}

/* ⚠️ Un seul par ecran : « Telecharger ». Le CTA du site est unique
   (WEBSITE_GUIDE §2.1), et deux boutons primaires n'en font aucun. */
.btn--primary { background: var(--accent-solid); color: var(--on-accent); }
/* ⚠️ Etait ecrit en dur ici, alors que l'en-tete de tokens.css interdit toute
   couleur hors de ce fichier. Corrige au W12-a : la valeur est un token, donc
   TokenContrastTest la controle — elle porte du texte blanc. */
.btn--primary:hover { background: var(--accent-solid-hover); }

.btn--secondary { background: transparent; color: var(--fg); border-color: var(--border); }
.btn--secondary:hover { border-color: var(--fg-muted); }

.btn--tertiary { height: auto; padding: 0; background: none; color: var(--accent-text); text-decoration: none; }
.btn--tertiary:hover { text-decoration: underline; }

.btn[disabled], .btn[aria-disabled="true"] { opacity: .5; cursor: not-allowed; }

/* Le CTA du hero et celui du rappel final. Toujours `.btn--primary` avec lui :
   ce n'est pas un quatrieme niveau (§5 en interdit un), c'est une taille. */
.btn--lg { height: 52px; padding: 0 28px; font-size: 17px; }

/* --- Carte (§5) : au survol la BORDURE s'eclaircit. Pas d'ombre, pas de
       translation — une carte qui bouge au survol fatigue sur une grille. --- */

.card {
  background: var(--bg-subtle);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 24px;
  transition: border-color var(--dur) var(--ease);
}
.card:hover { border-color: var(--fg-muted); }
.card > :last-child { margin-bottom: 0; }

/* --- La carte d'article du blog (W12-e, CHARTE §13) --------------------------

   🔴 UN TITRE DE CARTE N'EST PAS UN TITRE DE PAGE. Sans cette regle, le `h2` de
   la carte prend la taille du `h2` de section — mesure a 34 px dans une colonne
   de 368 px — et la grille se lit comme une liste de liens et non comme des
   articles. Le W12-c avait deja fait ce constat pour `.duo h2` ; la carte du
   blog est le meme defaut, sur la page ou atterrit le trafic.

   ⚠️ La classe est `.card--article` et NON `.card h2` : `.card` sert aussi au
   formulaire d'inscription, aux badges d'OS et aux deux cartes du `.duo`, qui
   porte deja son propre bareme. Une regle sur `.card h2` dependrait de l'ordre
   des declarations dans ce fichier pour ne pas ecraser `.duo h2` — un equilibre
   qu'un tri des regles casserait sans bruit. */

.card--article h2 {
  margin-top: 0;
  font-size: clamp(19px, 1.5vw, 22px);
  line-height: 1.35;
}

/* ⚠️ Le titre perd le bleu ET le souligne. Le §7 n'en souffre pas : la regle
   « un lien se distingue autrement que par la couleur » vise un lien NOYE DANS
   UN PARAGRAPHE, pas un titre qui est a lui seul la cible de sa carte. Le
   survol et le focus rendent les deux signaux, et l'anneau de focus global
   (base.css) reste intact — c'est lui qui porte l'acces au clavier. */
.card--article h2 a { color: var(--fg); text-decoration: none; }
.card--article h2 a:hover,
.card--article h2 a:focus-visible { color: var(--accent-text); text-decoration: underline; }

/* --- L'accueil du blog : une une, puis la grille (W12-e, CHARTE §13) ---------

   Dix cartes identiques sans point focal — c'est exactement le defaut que le
   §11.1 a corrige sur l'accueil (« quatre paves de prose identiques »), en
   plus grand nombre. Le premier article prend la pleine largeur.

   🔴 AUCUN GABARIT NE CHOISIT LA UNE, ET C'EST DELIBERE. Le choix est un
   `:first-child` en CSS : aucune boucle a dedoubler, donc aucune rencontre avec
   la precedence `th:each` (200) / `th:replace` (100) que `fragments/blog.html`
   documente deja, et aucun champ nouveau — la une est le meme resume que les
   autres, plus large. */

/* 🔴 LE SEUIL, ET IL VIENT D'UN DEFAUT VU SUR /en/blog/ — PAS D'UNE HYPOTHESE.

   Le blog anglais ne compte que DEUX articles. La une en absorbait un et laissait
   le second seul dans une rangee de trois colonnes : une carte de 368 px flottant
   a gauche de 784 px de vide, juste sous un bloc pleine largeur. Une mise en avant
   n'a de sens que s'il reste une grille derriere elle.

   `:nth-last-child(n+4)` se lit « premier enfant qui est aussi au moins quatrieme
   en partant de la fin », donc : quatre articles ou plus. En dessous, aucune une
   et la grille reste nominale — trois articles font une rangee pleine et propre.

   ⚠️ Le compte des colonnes depend de la largeur (`auto-fill`), donc ce seuil ne
   garantit pas une derniere rangee complete a toutes les tailles. Ce n'est pas
   son objet : il empeche le cas ou la une ne laisse RIEN derriere elle. */

.grid--blog > :first-child:nth-last-child(n+4) { grid-column: 1 / -1; }
.grid--blog > :first-child:nth-last-child(n+4) h2 {
  font-size: clamp(24px, 2.4vw, 32px);
  line-height: 1.25;
}

/* ⚠️ Sans plafond, le chapo de la une s'etale sur les 1152 px de la page et
   perd la longueur de ligne du §4. La carte, elle, garde toute sa largeur. */
.grid--blog > :first-child:nth-last-child(n+4) > p:last-child {
  max-width: var(--maxw-prose);
}

/* --- Barre de navigation (§5) : 64 px, floue, bordure basse ------------- */

.nav {
  position: sticky;
  top: 0;
  z-index: 10;
  height: var(--nav-height);
  background: color-mix(in srgb, var(--bg) 90%, transparent);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--border);
}
.nav__inner {
  max-width: var(--maxw-page);
  margin: 0 auto;
  padding: 0 calc(var(--space) * 3);
  height: 100%;
  display: flex;
  align-items: center;
  /* ⚠️ 16 px et non 24 depuis le W12-d : les outils et le CTA etaient jusqu'ici
     dans un meme `.nav__end` a 16 px, et le CTA a du en sortir pour survivre au
     repli. La marge de `.nav__links` ci-dessous rend au couple marque/liens les
     24 px qu'il avait — la barre large est inchangee au pixel pres. */
  gap: calc(var(--space) * 2);
}
/* La marque est un lien de la barre, donc une cible tactile du CHARTE §12 :
   134 x 28 px mesures au W12-m, la ou le §12 en demande 44 de haut. Elle est
   deja un element flex centre dans une barre de 64 px — centrer une boite de
   44 au lieu de 28 ne deplace aucun pixel. Sous 360 px le nom boucle et fait
   deja 56 px : la regle y est inactive. */
.nav__brand {
  display: flex; align-items: center; min-height: 44px;
  font-weight: 700; font-size: 17px; color: var(--fg); text-decoration: none; letter-spacing: -0.01em;
}
.nav__links { display: flex; gap: calc(var(--space) * 3); list-style: none; margin: 0 0 0 var(--space); padding: 0; }
.nav__links a { color: var(--fg-muted); text-decoration: none; font-size: 15px; }
.nav__links a:hover, .nav__links a[aria-current="page"] { color: var(--fg); }
.nav__outils { margin-left: auto; display: flex; align-items: center; gap: calc(var(--space) * 2); }

/* --- Le repli mobile (§12) — W12-d ------------------------------------

   🔴 ZERO JAVASCRIPT : <details> porte l'ouverture, le clavier et
   aria-expanded nativement. Le CHARTE §8 tient le blog et le manuel a zero
   script, et la CSP par empreinte du W10-a refuserait un second script en
   ligne SANS erreur visible — le menu ne s'ouvrirait qu'en production.

   ⚠️ Ce qui n'est PAS fait, et qui est assume : fermeture a l'Echap et au
   clic exterieur. Les deux demandent du script. La degradation est propre —
   on referme par le meme bouton — et c'est ecrit plutot que contourne.

   Le panneau se positionne contre `.nav`, qui est `sticky` et fait donc
   bloc conteneur : il saigne d'un bord a l'autre, sous la barre, au lieu
   d'etre borne par les 1200 px de `.nav__inner`. Il est OPAQUE : la barre
   est translucide et floutee, un panneau qui le serait aussi laisserait
   lire le contenu de la page a travers ses liens. ---------------------- */

.nav__repli { display: none; }

.nav__bascule {
  display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px;
  color: var(--fg-muted);
  border: 1px solid var(--border); border-radius: var(--radius);
  cursor: pointer; font-size: 17px; line-height: 1;
  /* Les deux facons de supprimer le triangle natif : `list-style` couvre
     Firefox et Chrome recents, le pseudo-element WebKit couvre Safari. */
  list-style: none;
}
.nav__bascule::-webkit-details-marker { display: none; }
.nav__bascule:hover { color: var(--fg); border-color: var(--fg-muted); }
.nav__bascule-fermer { display: none; }
.nav__repli[open] .nav__bascule-ouvrir { display: none; }
.nav__repli[open] .nav__bascule-fermer { display: inline; }

.nav__panneau {
  position: absolute;
  top: 100%; left: 0; right: 0;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  padding: var(--space) calc(var(--space) * 3) calc(var(--space) * 2);
}
.nav__panneau-liens { list-style: none; margin: 0; padding: 0; }
.nav__panneau-liens a {
  display: flex; align-items: center;
  /* 44 px : la cible tactile du CHARTE §4, qui vaut ici autant que pour un
     bouton — c'est la navigation entiere du site sur un telephone. */
  min-height: 44px;
  color: var(--fg); text-decoration: none; font-size: 16px;
  border-bottom: 1px solid var(--border-muted);
}
.nav__panneau-liens a[aria-current="page"] { color: var(--accent-text); }
.nav__panneau-outils {
  display: flex; align-items: center; gap: calc(var(--space) * 2);
  padding-top: calc(var(--space) * 2);
}

@media (max-width: 899px) {
  /* La barre large et le repli ne coexistent jamais : celui des deux qui ne
     sert pas est en `display: none`, donc hors de l'ordre de tabulation et
     hors de l'arbre d'accessibilite. Aucune cible en double.

     🔴 `.nav` N'EST PAS DECORATIF DANS CE SELECTEUR — mesure au W12-m. Ecrite
     sans lui au W12-d, la regle attrapait aussi le pied de page, qui reutilise
     `.nav__links` pour son style : sous 900 px, Contact, FAQ, Versions,
     Mentions legales et Confidentialite n'etaient rendus sur AUCUNE page, et
     le panneau du menu ne les porte pas. Les mentions legales etaient donc
     injoignables sur telephone. Un selecteur non restreint attrape ce qui
     partage la classe, pas ce qu'on avait en tete. */
  .nav .nav__links, .nav .nav__outils { display: none; }
  .nav__repli { display: block; }

  /* 🔴 Ces trois valeurs ont ete MESUREES dans un navigateur, pas estimees.
     A 360 px il reste 345 px utiles, et marque + CTA + bascule en demandent
     340 : la marge est de cinq pixels, et c'est ce qui a impose de reprendre
     la gouttiere (8 px), le retrait lateral (12 px) et le rembourrage du CTA
     (14 px). Les estimations faites a la lecture donnaient 16 px de trop. */
  .nav__inner { gap: var(--space); padding: 0 calc(var(--space) * 1.5); }
  .nav__cta { margin-left: auto; padding: 0 14px; }
}

/* 🔴 Le nom du site ne boucle pas — MAIS SEULEMENT A PARTIR DE 360 px.

   A 360 px et au-dessus, « Debug XML Tool » tenait sur deux lignes dans une
   barre de 64 px : mesure au W12-d, hauteur 56 px pour un interlignage de
   28,05. `white-space: nowrap` le corrige.

   ⚠️ Sous 360 px il faut le laisser boucler, et c'est l'inverse d'un detail.
   Le W12-b avait releve que « `.nav__brand` en nowrap ferait deborder la page
   horizontalement » et avait renonce pour cette raison : c'est VRAI a 320 px,
   ou la regle inconditionnelle sort la barre de 22 px hors de la fenetre.
   Mesure, elle aussi. Le nom boucle donc a 320 px — c'est laid et c'est sans
   consequence — plutot que d'imposer un defilement horizontal a la page
   entiere, que le CHARTE §4 et `PW-32.13` refusent tous les deux.

   🔴 C'est le defaut que ce lot a failli introduire en corrigeant l'autre.
   ------------------------------------------------------------------------ */
@media (min-width: 360px) and (max-width: 899px) {
  .nav__brand { white-space: nowrap; }
}

/* Bascule de theme : un vrai <button> avec aria-pressed (§7), pas une case
   deguisee. Les deux glyphes sont dans le HTML, le CSS n'en montre qu'un. */
.theme-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px;
  background: transparent; color: var(--fg-muted);
  border: 1px solid var(--border); border-radius: var(--radius);
  cursor: pointer; font-size: 15px; line-height: 1;
}
.theme-toggle:hover { color: var(--fg); border-color: var(--fg-muted); }
.theme-toggle__dark { display: none; }
.theme-toggle[aria-pressed="true"] .theme-toggle__light { display: none; }
.theme-toggle[aria-pressed="true"] .theme-toggle__dark { display: inline; }

/* --- Bloc de code (§5) : etiquette de langue a gauche, Copier a droite --- */

.code {
  position: relative;
  background: var(--bg-code);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  margin: calc(var(--space) * 3) 0;
}
.code__bar {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--space) calc(var(--space) * 1.5) var(--space) calc(var(--space) * 2);
  border-bottom: 1px solid var(--border-muted);
}
.code__lang { font-size: 12px; color: var(--fg-muted); text-transform: uppercase; letter-spacing: .05em; }
.code__copy {
  background: transparent; border: 1px solid var(--border); border-radius: var(--radius-sm);
  color: var(--fg-muted); font: inherit; font-size: 12px; padding: 2px 8px; cursor: pointer;
}
.code__copy:hover { color: var(--fg); border-color: var(--fg-muted); }
.code pre { margin: 0; padding: calc(var(--space) * 2); overflow-x: auto; }

/* 🔴 Le bloc de code d'une page de documentation prend SA mesure — W12-f.

   `width` et non `max-width` : le bloc doit pouvoir DEPASSER la colonne qui le
   contient, ce qu'un `max-width` ne permet jamais. Il deborde donc de 58 px
   vers la droite, la ou une page de prose n'a que du vide.

   ⚠️ ET SEULEMENT AU-DESSUS DE 900 px, la coupure du repli mobile (W12-d).
   En dessous, le bloc reprend la largeur de sa colonne et `overflow-x` de
   `.code pre` reprend la main : un bloc plus large que la fenetre ferait
   defiler la PAGE horizontalement, ce qui est le pire des deux maux. A 900 px
   la page offre 852 px utiles pour 622 de prose — les 58 px de debord tiennent
   avec 172 de marge. */
@media (min-width: 900px) {
  .prose--doc .code { width: var(--maxw-code); }
}

/* 🔴 UN MESSAGE DE L'APPLICATION, RECOPIE MOT POUR MOT — W12-f.

   Les pages d'aide citent sept messages du bundle produit, et c'est leur raison
   d'etre : c'est le texte que le client a sous les yeux et qu'il collera dans un
   moteur de recherche. Ils etaient ecrits `<p><code>` — donc en ligne, donc
   coupes en trois pastilles empilees a droite dechiquetee des que le message
   depassait la colonne, ce que font les quatre messages d'echec d'activation.
   Un message produit ne se lit pas comme un mot de code au fil du texte : il se
   lit comme une citation, et il en prend la forme.

   ⚠️ `display: block` sur un <code> reste du contenu phrastique valide dans un
   <p> — c'est la mise en forme qui change, pas la structure, et le gabarit
   continue donc de dire « ceci est du texte de l'application ».

   ⚠️ `overflow-wrap: anywhere` et non `overflow-x: auto` comme `.code pre` : un
   message d'erreur est une phrase, elle doit se replier. C'est un bloc de code
   qu'on fait defiler, jamais une phrase. */
.msg {
  display: block;
  background: var(--bg-code);
  border-left: 3px solid var(--border);
  border-radius: 0 var(--radius) var(--radius) 0;
  padding: calc(var(--space) * 1.5) calc(var(--space) * 2);
  overflow-wrap: anywhere;
}

/* Coloration produite au rendu par CodeHighlighter — aucun script client.
   Les teintes suivent le theme de l'editeur, sans sortir de la palette. */
.tok-comment { color: var(--tok-comment); font-style: italic; }
.tok-tag     { color: var(--tok-tag); }
.tok-attr    { color: var(--tok-attr); }
.tok-str     { color: var(--tok-str); }
.tok-cdata   { color: var(--tok-cdata); }
.tok-entity  { color: var(--tok-entity); }
.tok-pi      { color: var(--fg-muted); }

/* --- Badge d'OS (§5) : trois etats, et le troisieme n'est pas un detail --- */

.os { display: flex; align-items: center; gap: calc(var(--space) * 2); }
.os__name { font-weight: 600; }
.os__facts { font-family: var(--font-mono); font-size: 13px; color: var(--fg-muted); }

/* ⏳ En preparation (Linux) : pas de bouton de telechargement, un champ e-mail
   a la place. C'est l'instrument de mesure du WEBSITE_GUIDE §9.2 — mettre un
   bouton grise le rendrait muet.

   🔴 `opacity: .75` A ETE RETIREE AU W12-c, apres mesure. Lighthouse la relevait
   a 3,8:1 sur `.os__facts` : le token est deja --fg-muted, et une opacite posee
   PAR-DESSUS le fait tomber sous AA. Le defaut date du W5 et n'avait jamais ete
   mesure — c'est la premiere fois qu'un relevé est pris sur /telecharger.
   ⚠️ La carte reste secondaire sans cela : elle n'a pas de bouton, son titre
   porte ⏳, et son libelle dit « en preparation ». Delaver du texte pour dire
   « pas encore » est de toute facon un signal qu'un lecteur d'ecran n'entend
   pas. */
.os--soon .btn--primary { display: none; }

/* ❌ Non prevu (macOS) : mention discrete, aucun champ. Capter une attente
   qu'on ne compte pas satisfaire coute plus cher que de la decliner. */
.os--none { color: var(--fg-muted); font-size: 14px; }

/* ⚠️ `.notify` a disparu au W6-b. Le champ « Prevenez-moi » du badge Linux
   n'est plus une ligne isolee : il porte desormais la mention RGPD et le lien
   vers /confidentialite, comme les deux autres points de capture, et partage
   leur bloc `.inscription` plus bas. Une seconde mise en forme aurait fait du
   formulaire le plus expose celui qui ressemble le moins aux autres. */

/* --- Tableau de prix (§5) : UNE colonne. Il n'y a qu'une offre ; trois
       colonnes inventeraient des paliers qui n'existent pas. --------------- */

/* ⚠️ `margin-inline: auto` ajoute au W12-c. Une carte de 380 px calee a gauche
   d'une page de 1200 laisse 800 px de vide a sa droite : c'est ce qui faisait
   lire le prix comme une note de bas de page plutot que comme une offre. */
.price {
  margin-inline: auto;
  max-width: 380px;
  background: var(--bg-subtle);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 32px;
  text-align: center;
}
.price__amount { font-size: 48px; font-weight: 700; line-height: 1.1; letter-spacing: -0.02em; }
.price__terms { font-size: 14px; color: var(--fg-muted); margin: var(--space) 0 calc(var(--space) * 3); }

/* --- Encadres (§5) : bordure gauche 3 px, quatre tons, sans icone criarde -- */

.callout {
  border-left: 3px solid var(--border);
  background: var(--bg-subtle);
  border-radius: 0 var(--radius) var(--radius) 0;
  padding: calc(var(--space) * 2) calc(var(--space) * 3);
  margin: calc(var(--space) * 3) 0;
}
.callout > :last-child { margin-bottom: 0; }
.callout__title { font-weight: 600; margin-bottom: var(--space); }
.callout--info      { border-left-color: var(--accent-solid); }
.callout--attention { border-left-color: var(--warning); }
.callout--important { border-left-color: var(--danger); }
.callout--astuce    { border-left-color: var(--success); }

/* --- Temoignage (§5) : sans guillemets geants ni portrait. Un avatar
       generique trahit l'absence de photo reelle et affaiblit la preuve. ---- */

.quote { margin: 0; max-width: var(--maxw-prose); }
.quote__text { font-size: 18px; line-height: 1.5; margin: 0 0 calc(var(--space) * 1.5); }
.quote__source { font-size: 14px; color: var(--fg-muted); font-style: normal; }

/* 🔴 Aucune regle ne dessine un emplacement vide : quand il n'y a aucun
   temoignage, le bloc entier ne se rend pas et la section suivante remonte
   (WEBSITE_GUIDE §9.6). Pas de cadre en pointilles, pas de « bientot ». */

/* --- Ligne de journal des versions (§5) ---------------------------------- */

.release { border-top: 1px solid var(--border-muted); padding: calc(var(--space) * 3) 0; }
.release__head { display: flex; align-items: baseline; gap: calc(var(--space) * 2); }
.release__version { font-family: var(--font-mono); font-size: 16px; font-weight: 400; color: var(--fg); }
/* ⚠️ La date est le contenu : elle prouve que le produit est vivant. Absolue,
   jamais relative — « il y a 3 mois » se perime mal et vieillit tout seul. */
.release__date { font-size: 14px; color: var(--fg-muted); }
.release__notes { margin: var(--space) 0 0; padding-left: calc(var(--space) * 3); }

/* --- Etiquette de rubrique (§5) : pas de couleur par rubrique. Trois teintes
       concurrentes casseraient l'accent unique du §2. --------------------- */

.tag {
  display: inline-block;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--fg-muted);
  background: var(--bg-subtle);
  border: 1px solid var(--border-muted);
  border-radius: var(--radius-sm);
  padding: 2px 8px;
  text-decoration: none;
}
a.tag:hover { color: var(--fg); border-color: var(--border); }

/* --- Index de rubrique (§5) --------------------------------------------- */

.rubriques { display: flex; flex-wrap: wrap; gap: calc(var(--space) * 2); list-style: none; margin: 0 0 calc(var(--space) * 4); padding: 0; }
.rubriques a { color: var(--accent-text); text-decoration: none; font-size: 15px; }
.rubriques a:hover { text-decoration: underline; }
.rubriques [aria-current="page"] { color: var(--fg); font-weight: 600; font-size: 15px; }

/* ⚠️ Aucune regle ne distingue une rubrique en noindex (moins de trois
   articles) : c'est une consigne aux moteurs, pas une information de lecteur. */

/* --- Capture d'ecran (§6) : cadre discret, jamais de faux chrome macOS --- */

.shot { border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; display: block; }
.shot img, .shot video { display: block; width: 100%; height: auto; }
.shot__caption { font-size: 14px; color: var(--fg-muted); margin-top: var(--space); }

/* --- Les pages d'issue : une phrase, une action --------------------------

   🔴 CE N'EST PLUS `.error-page`, ET LE RENOMMAGE EST LE PROPOS. Une 404 et un
   « c'est confirme » sont la meme page : un titre, une phrase, un geste, rien
   d'autre. Elles etaient composees differemment parce que l'une avait herite
   d'une classe et l'autre non — l'erreur centree et bornee, la confirmation
   collee en haut a gauche d'un ecran vide. Un seul nom, un seul traitement.

   ⚠️ Les 42rem sont ceux de `.chapo` : un bloc centre se lit dans la meme
   mesure, qu'il ouvre une page ou qu'il la conclue. `.error-page` n'en avait
   aucune — un texte d'erreur un peu long se serait etale sur 1152 px. */

.issue { max-width: 42rem; margin-inline: auto; text-align: center; padding: calc(var(--space) * 12) 0; }
.issue__code { font-family: var(--font-mono); font-size: 14px; color: var(--fg-muted); }
.issue h1 { margin-bottom: calc(var(--space) * 2); }
.issue form { margin-top: calc(var(--space) * 3); }

/* ⚠️ C'est `.issue` qui borne, pas `.prose`. Sans cette ligne, la colonne de
   622 px se posait a GAUCHE du bloc de 672 : le texte restait centre dans une
   boite decalee de 25 px, donc centre par rapport a rien. */
.issue .prose { max-width: none; }

/* =========================================================================
   W3 — Blog et admin
   ========================================================================= */

/* --- Corps d'article rendu depuis Markdown ------------------------------

   Ces regles portent sur du HTML que PERSONNE n'ecrit a la main : il sort de
   MarkdownRenderer. Elles doivent donc couvrir tout ce que commonmark peut
   produire, et pas seulement ce que le premier article contient — un tableau
   sans style ne se remarque qu'une fois l'article publie. */

.prose h2 { margin-top: calc(var(--space) * 6); }
.prose h3 { margin-top: calc(var(--space) * 5); }
.prose h2:first-child, .prose h3:first-child { margin-top: 0; }

.prose ul, .prose ol { padding-left: calc(var(--space) * 3); margin: 0 0 calc(var(--space) * 2); }
.prose li + li { margin-top: calc(var(--space) * .5); }

.prose blockquote {
  margin: calc(var(--space) * 3) 0;
  padding-left: calc(var(--space) * 2);
  border-left: 2px solid var(--border);
  color: var(--fg-muted);
}

/* Le bloc de code produit par MarkdownRenderer deborde la colonne de 68
   caracteres : du code casse a 68 colonnes n'est plus du code. */
.prose .code { max-width: none; margin: calc(var(--space) * 3) 0; }

/* `code` en ligne : un fond, jamais une bordure — elle ferait sauter la ligne
   de base au milieu d'une phrase. */
.prose :not(pre) > code {
  background: var(--bg-code);
  padding: 1px 5px;
  border-radius: var(--radius-sm);
}

/* --- Tableaux (articles et admin) --------------------------------------- */

/* 🔴 `display: block` SUR UN <table> ANNULE LE `width: 100%` QUI LE PRECEDE —
   defaut trouve au W12-h.

   Un <table> passe en `display: block` ne disparait pas de l'algorithme de
   tableau : ses `thead`/`tbody` gardent leur display propre et forcent la
   creation d'une boite de tableau ANONYME. Cette boite-la est en largeur auto,
   donc au contenu — et elle est inatteignable par un selecteur. Resultat : la
   boite exterieure fait bien 1152 px, le tableau visible beaucoup moins.
   Mesure a 1280 px : fiche de commande 378 px, seaux de sequence 527,
   versions 726, temoignages 744. La regle DECLARAIT une largeur qu'elle ne
   pouvait pas appliquer, et c'est cela le defaut, pas le rendu.

   La correction separe les deux roles : `.table-defile` porte le defilement,
   `.table` redevient un vrai tableau.

   ✅ `.prose table` A REJOINT LE MEME TRAITEMENT AU W12-k. Le blog etait reste
   sur l'ancien parce que ce HTML sort de MarkdownRenderer, qui n'emettait aucun
   conteneur — il n'y avait pas de `.table-defile` a poser. Le rendu en emet un
   depuis (voir TableauDefilant), donc les deux surfaces ont la meme regle et le
   defaut ne survit nulle part.

   ⚠️ Cette regle depend d'un conteneur produit par le Java. Si `.table-defile`
   disparaissait du rendu Markdown, le tableau du blog ne deborderait pas en
   silence : il ferait defiler LA PAGE sur mobile. C'est MarkdownRendererTest qui
   tient cette extremite. */
.prose table,
.table {
  width: 100%;
  max-width: none;
  border-collapse: collapse;
  /* La marge appartient au conteneur, pas au tableau : la porter ici la ferait
     compter deux fois. */
  margin: 0;
  font-size: 15px;
}

/* Le conteneur qui defile — la promesse tenue par une vraie boite. Un tableau
   large fait defiler SON conteneur, jamais la page : un defilement horizontal du
   document casse la lecture sur mobile. */
.table-defile { overflow-x: auto; margin: calc(var(--space) * 3) 0; }

/* ⚠️ Une fiche n'est pas une liste. Deux colonnes etirees sur 1152 px posent
   « jean@example.com » a un ecran de son etiquette : l'oeil perd la ligne entre
   les deux, ce que le defaut de largeur ci-dessus masquait par accident. */
.table--fiche { max-width: 42rem; }

.prose th, .prose td, .table th, .table td {
  border-bottom: 1px solid var(--border-muted);
  padding: var(--space) calc(var(--space) * 1.5);
  text-align: left;
  vertical-align: top;
}

.prose th, .table th { font-weight: 600; color: var(--fg); white-space: nowrap; }

/* --- Pagination ---------------------------------------------------------- */

.pagination {
  display: flex;
  align-items: center;
  gap: calc(var(--space) * 2);
  margin-top: calc(var(--space) * 6);
  padding-top: calc(var(--space) * 3);
  border-top: 1px solid var(--border-muted);
}
.pagination .meta { margin: 0; }

/* --- Champs de formulaire (admin, et le W6 pour les formulaires publics) - */

label {
  display: block;
  font-size: 14px;
  font-weight: 600;
  margin-bottom: calc(var(--space) * .5);
}

.champ {
  width: 100%;
  padding: calc(var(--space) * 1.25) calc(var(--space) * 1.5);
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-family: inherit;
  font-size: 16px;
  line-height: 1.5;
}
.champ:focus { border-color: var(--accent-text); }

/* ⚠️ Un champ en lecture seule doit SE VOIR, sinon l'operateur croit pouvoir le
   modifier et ne comprend pas pourquoi rien ne change. Le fond suffit : garder
   la couleur du texte intacte, un gris clair echouerait en AA. */
.champ[readonly] { background: var(--bg-subtle); cursor: not-allowed; }

.champ--code { font-family: var(--font-mono); font-size: 14.5px; }

.champ__erreur { display: block; font-size: 14px; color: var(--danger); margin-top: calc(var(--space) * .5); }

/* 🔴 UN FORMULAIRE A UNE MESURE — W12-h.

   `.champ` est en `width: 100%`, ce qui est juste ; le defaut est qu'aucun
   formulaire d'admin ne bornait le 100 %. Un numero de version, un ordre
   d'affichage, une date etaient donc des champs de 1152 px. La largeur d'un
   champ annonce la valeur attendue : a 1152 px elle n'annonce plus rien, et
   l'oeil doit relire l'etiquette pour savoir ce qu'il saisit. Mesure sur les
   cinq formulaires : 6 a 8 champs, tous a 1152.

   ⚠️ 42 rem, la mesure de `.chapo` et de `--maxw-code`, et ce n'est pas un
   nombre pris au hasard : le corps d'un article se REND a 622 px sur le site
   public. L'ecrire dans une boite de 672 est le plus proche qu'on puisse etre
   de la ligne que le lecteur verra, sans mentir dans l'autre sens. */
.formulaire { max-width: 42rem; }

/* --------------------------------------------------------------------------
   Formulaire de capture d'adresse (W6-b) — les trois points du §7.1 partagent
   ce bloc, comme ils partagent un seul fragment Thymeleaf.

   ⚠️ Une colonne, pas la ligne du kitchen-sink. Le champ et le bouton cote a
   cote tenaient tant qu'il n'y avait ni cases a cocher ni mention RGPD ; avec
   elles, la ligne casse sur mobile et la mention finit sous le pli.
   -------------------------------------------------------------------------- */

.inscription form { display: grid; gap: var(--space); }
.inscription__titre { margin-top: 0; }
.inscription .btn { justify-self: start; }

.inscription__cases { display: grid; gap: calc(var(--space) * .75); }

/* 🔴 La case et son libelle forment UNE SEULE cible cliquable : un <label>
   englobant. Une case de 13 px atteinte au doigt est le defaut de formulaire le
   plus banal, et il ne se voit sur aucun ecran de bureau. */
.inscription__case {
  display: flex; align-items: flex-start; gap: calc(var(--space) * .75);
  font-weight: 400; margin-bottom: 0; cursor: pointer;
}
.inscription__case input { width: 18px; height: 18px; margin-top: 2px; flex: none; }

.inscription__mention { margin-bottom: 0; }

/* --------------------------------------------------------------------------
   Accueil (W5-b) — les neuf sections du WEBSITE_GUIDE §9.5.

   ⚠️ Une seule colonne par defaut, deux au-dela de 900 px. Ecrit dans ce
   sens-la : la mise en page mobile est celle qu'on obtient sans media query,
   donc celle qui ne peut pas etre oubliee.

   🔴 Aucune de ces regles ne pose `opacity` ni `transform` : l'animation vit
   dans motion.css, sous @supports, et elle seule. Un etat masque ecrit ici
   afficherait une page vide chez qui ne sait pas l'animer.
   -------------------------------------------------------------------------- */

.hero, .feature {
  display: grid;
  gap: calc(var(--space) * 4);
  align-items: center;
}

@media (min-width: 900px) {
  /* 🔴 `auto-fit`, ET SURTOUT PAS `1fr 1fr` — corrige au W12-a.

     Les cinq medias de l'accueil sont absents du depot (prerequis n°17) et le
     fragment `capture` porte son propre th:if : il ne rend donc RIEN, pas meme
     un conteneur vide. Avec deux pistes ecrites en dur, le texte restait en
     colonne 1 et la colonne 2 restait vide — l'accueil entier s'affichait sur
     la moitie gauche de l'ecran, hero compris, avec un vide permanent a droite.

     ⚠️ Le commentaire de home.html affirmait exactement l'inverse : « absente du
     depot, la colonne disparait et le texte occupe la largeur ». C'etait faux,
     et aucun test ne pouvait le voir — ils lisent du HTML, et le HTML etait
     correct. Il fallait un moteur de rendu, que ce depot n'a jamais eu.

     `auto-fit` effondre la piste vide et rend sa fraction a celle qui reste :
     un enfant occupe toute la largeur, deux se partagent en deux colonnes. La
     regle vaut donc pour l'etat d'aujourd'hui ET pour celui du jour ou les
     medias arriveront, sans rien a rebrancher. */
  .hero, .feature { grid-template-columns: repeat(auto-fit, minmax(min(100%, 26rem), 1fr)); }

  /* Une section sur deux inverse l'ordre visuel, jamais l'ordre du document :
     le texte reste premier dans le HTML, donc premier pour un lecteur d'ecran
     et pour un moteur. C'est `order` qui deplace, et lui seul. */
  .feature--inverse > :last-child { order: -1; }
}

/* Media absent, la colonne n'existe pas : le texte occupe alors toute la
   largeur sans qu'aucune regle ne le prevoie — c'est la grille qui s'ajuste. */
/* 🔴 `container-type: inline-size` : c'est ce qui permet au cadre de debogueur de
   se dimensionner sur SA PROPRE largeur et non sur celle de la fenetre. Sa boite
   est imposee par un `aspect-ratio` ; a hauteur contrainte, la seule facon de
   garantir que neuf lignes de code, un panneau et deux barres y tiennent a
   TOUTES les largeurs est de tout exprimer en em et de tenir l'em au cqw. Le coefficient — 1,95 %
   de la largeur du conteneur — est celui que la ligne la plus longue du fragment
   impose : au-dela, le sum() sort par la droite.
   Une media query n'y suffit pas : la colonne du hero ne fait pas la largeur de
   la fenetre, et le rapport des deux change avec le point de rupture.
   ⚠️ `inline-size` et surtout pas `size` : ce dernier exige une hauteur explicite
   et annulerait l'aspect-ratio. */
.hero__media {
  min-width: 0;
  container-type: inline-size;
}

/* ⚠️ Corollaire de l'effondrement ci-dessus : rendu a toute la largeur, le titre
   du hero s'etalerait sur 1152 px et se lirait mal. Le plafond ne s'applique
   qu'a ce cas — quand le media est la, la colonne fait deja moins que ca. */
.hero__text { max-width: 46rem; }

/* ────────────────────────────────────────────────────────────────────────────
   Le hero — W12-b
   ──────────────────────────────────────────────────────────────────────────── */

/* Le surtitre dit la CATEGORIE, que le h1 ne dit pas : celui-ci promet un
   benefice (« voyez enfin ce que fait votre XSLT »), et un visiteur arrive d'un
   forum doit savoir en un coup d'oeil qu'il regarde un debogueur pour Windows.
   Interlettrage ouvert et petites capitales : c'est ce qui le fait lire comme
   une etiquette et non comme une premiere phrase. */
.hero__surtitre {
  margin: 0 0 calc(var(--space) * 2);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-muted);
}

/* 🔴 --fg-muted, PAS --fg-meta-large. Cette phrase est la ligne d'accroche du
   site ; a 3,46:1 elle echouait en AA et Lighthouse l'a relevee (W12-b). Sur le
   fond sombre du marketing, --fg-muted donne 6,4:1 pour la meme intention
   visuelle — un gris nettement plus clair que le titre, sans etre illisible.
   ⚠️ 21 px et non 18 : le titre monte a 76 px depuis le W12-a, et un sous-titre
   reste a 18 px se met a ressembler a une legende. */
.hero__lede {
  max-width: 34rem;
  margin: 0 0 calc(var(--space) * 4);
  font-size: clamp(18px, 1.6vw, 21px);
  line-height: 1.5;
  color: var(--fg-muted);
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: calc(var(--space) * 2);
  margin-bottom: calc(var(--space) * 1.5);
}

/* ────────────────────────────────────────────────────────────────────────────
   LE CADRE DE DEBOGUEUR — W12-b (fragments/ide.html, CadreDuHero)

   🔴 Il remplace la boucle video absente, et il doit donc en occuper la place
   EXACTE : meme rapport de forme (1280x720, MediaSite.HERO), meme elevation.
   Sans quoi le jour du remplacement la page se remet en page toute seule.

   ⚠️ Ce composant est la seule exception du site a « bordures plutot qu'ombres »
   (CHARTE §2.5, amende au W12-a) : c'est un visuel produit, il doit sembler
   incruste dans la page et non colle dessus.
   ──────────────────────────────────────────────────────────────────────────── */

.ide {
  display: grid;
  grid-template-rows: auto 1fr auto auto;
  aspect-ratio: 1280 / 720;
  min-height: 0;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--bg-code);
  box-shadow: var(--shadow-produit);
  font-family: var(--font-mono);

  /* 🔴 TOUT LE CADRE SE MESURE A PARTIR DE CET EM, et tout le reste est en em.
     Le premier `font-size` est le repli des navigateurs sans requetes de
     conteneur (~2 %) ; le second gagne partout ailleurs et fait suivre la
     totalite du composant — hauteurs de barres, gouttiere, interlignes.
     ⚠️ Sans cela, la premiere version debordait : neuf lignes a 13 px fixes ne
     tiennent pas dans les ~340 px de haut qu'impose le rapport 1280/720 sur une
     demi-colonne, et l'editeur se faisait couper a la sixieme ligne. */
  font-size: 11px;
  font-size: 1.95cqw;
  line-height: 1.45;

  /* ⚠️ Le cadre est un dessin, pas du texte a selectionner. Sans ceci, un
     glisser sur la page part en selection bleue au milieu du hero. */
  user-select: none;
}

/* La barre d'onglets. Rectangulaires et jointifs, souligne a l'actif : le chrome
   de JetBrains. 🔴 Jamais les trois pastilles de macOS — le produit est livre
   pour Windows, et la cible passe sa journee dans cet environnement. */
.ide__chrome {
  display: flex;
  align-items: stretch;
  gap: 1px;
  background: var(--ide-chrome);
  border-bottom: 1px solid var(--border);
}

.ide__onglet {
  padding: 0 1.2em;
  line-height: 2.4em;
  font-size: .95em;
  color: var(--fg-muted);
  border-bottom: 2px solid transparent;
  white-space: nowrap;
}

.ide__onglet--actif {
  color: var(--fg);
  background: var(--bg-code);
  border-bottom-color: var(--accent-solid);
}

.ide__outils {
  display: flex;
  align-items: center;
  gap: .7em;
  margin-left: auto;
  padding: 0 1em;
}

/* `currentColor` : les glyphes suivent la couleur du texte des deux palettes
   sans qu'aucune couleur soit ecrite ici (CHARTE §1 — les couleurs vivent dans
   tokens.css, et celle-ci est heritee, donc elle n'est pas ecrite du tout). */
.ide__glyphe {
  width: 1.05em;
  height: 1.05em;
  fill: currentColor;
  stroke: currentColor;
  color: var(--fg-muted);
}

.ide__editeur {
  min-height: 0;
  overflow: hidden;
  padding: .4em 0;
}

/* 🔴 PAS de `white-space: pre` ici, sur le conteneur flex — sur `.ide__code`
   seulement. Pose sur la ligne, il preserve les retours a la ligne que le
   GABARIT ecrit entre la gouttiere et le code : ils deviennent des elements
   flex anonymes, et chaque ligne de code s'affiche sur trois hauteurs avec son
   numero decale dessous. Vu a l'ecran au W12-b, invisible dans le HTML. */
.ide__ligne {
  display: flex;
  align-items: baseline;
  gap: 1em;
  padding-right: 1.2em;
}

/* La ligne ou l'execution est arretee. 🔴 Un ecart minime de --bg-code, et c'est
   contraint : c'est un fond glisse SOUS du texte deja colore, donc chaque jeton
   y perd du contraste. Le bleu franc qu'on dessinerait spontanement fait tomber
   --tok-comment sous 4,5:1. TokenContrastTest verifie les six jetons sur ce
   fond, dans les deux palettes.

   ⚠️ Le fond est porte par un ::before et non par la regle elle-meme : motion.css
   le fait apparaitre, et le §5.1 n'autorise que `transform` et `opacity` — animer
   `background` declencherait une repeinte a chaque image. */
.ide__ligne--courante { position: relative; }

.ide__ligne--courante::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--ide-ligne);
}

.ide__gouttiere {
  position: relative;
  flex: 0 0 auto;
  width: 3.2em;
  padding-right: .6em;
  text-align: right;
  color: var(--ide-gouttiere);
  font-size: .9em;
  user-select: none;
}

/* La pastille de point d'arret, a gauche du numero. Elle n'a pas de texte : son
   seuil est celui d'un element graphique porteur de sens, 3:1, et elle tient
   4,1:1 sur la gouttiere dans les deux palettes. */
.ide__arret {
  position: absolute;
  left: .3em;
  top: 50%;
  width: .75em;
  height: .75em;
  margin-top: -.375em;
  border-radius: 50%;
  background: var(--ide-arret);
}

/* `position: relative` sur les deux enfants : le ::before de la ligne courante
   est un frere positionne, et sans cela il peindrait par-dessus le code. */
/* 🔴 `font-size: inherit` EST OBLIGATOIRE ICI, ce n'est pas une precaution.
   base.css pose `code, kbd, samp, pre { font-size: 14.5px }` — une taille FIXE
   sur le selecteur de type. Sans cette ligne, le <code> de chaque ligne ignore
   completement l'echelle en cqw du cadre : le composant entier se redimensionne
   et le code, lui, reste a 14,5 px. Le symptome est un editeur qui deborde de
   sa boite a toutes les largeurs, et il resiste a tous les reglages du cqw —
   c'est ce qui l'a rendu penible a diagnostiquer au W12-b. */
.ide__code {
  position: relative;
  color: var(--fg);
  white-space: pre;
  font-size: inherit;
  line-height: inherit;
}

/* Le panneau des variables, en bas comme dans l'outil. Il ne montre que quatre
   entrees : au-dela il faudrait le faire defiler, et un panneau tronque dans une
   capture se lit comme un bug. */
.ide__panneau {
  border-top: 1px solid var(--border);
  background: var(--bg-elevated);
  padding: .5em 1.2em;
  min-height: 0;
  overflow: hidden;
}

.ide__panneau-titre {
  font-family: var(--font-sans);
  font-size: .82em;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--fg-muted);
  margin-bottom: .3em;
}

.ide__vars { margin: 0; }

.ide__var {
  display: flex;
  gap: .6em;
  font-size: .95em;
  line-height: 1.5;
}

.ide__var dt { color: var(--tok-attr); flex: 0 0 auto; }
.ide__var dd { margin: 0; color: var(--fg); }
.ide__var dd::before { content: "= "; color: var(--fg-muted); }

.ide__statut {
  font-family: var(--font-sans);
  font-size: .88em;
  color: var(--fg-muted);
  background: var(--ide-chrome);
  border-top: 1px solid var(--border);
  padding: .45em 1.2em;
}

/* 🔴 LE CADRE DISPARAIT SOUS 900 px, ET CE N'EST PAS UN REGLAGE DE PERFORMANCE.

   A 360 px de large, le rapport 1280/720 laisse 200 px de haut pour neuf lignes
   de code, un panneau et deux barres : la police tombe a ~7 px. Ce n'est plus
   une demonstration, c'est une vignette grise qui ressemble a une capture ratee
   — et elle prouve moins que rien du tout. Le hero mobile est donc du texte et
   un bouton, ce qui est de toute facon la bonne forme sur un telephone.

   ⚠️ La regle vise `.ide` et SURTOUT PAS `.hero__media` : le jour ou la video
   sera deposee, elle devra se voir sur mobile — une video de 340 px se regarde,
   un editeur de 340 px ne se lit pas. Cacher le conteneur cacherait les deux.

   Effet de bord mesure au W12-b, et il n'est pas negligeable : cent elements de
   moins a mettre en page et une sequence d'animation en moins a jouer rendent au
   mobile les 220 ms de blocage que le cadre lui coutait. */
@media (max-width: 899px) {
  .ide { display: none; }
}

/* ════════════════════════════════════════════════════════════════════════════
   LE RYTHME DES SECTIONS — W12-c

   Le defaut que ce bloc corrige n'etait dans aucun fichier : il etait dans la
   REPETITION. L'accueil enchainait neuf sections au meme fond, a la meme
   largeur, dans le meme sens de lecture, et les quatre du milieu portaient
   toutes `.feature` — donc, les medias etant absents, quatre pavés de prose
   identiques. Chacune etait correcte ; la suite ne l'etait pas.

   Cinq traitements, un par intention, et ils se lisent dans cet ordre :

     .recit          un recit — colonne etroite, centree, corps releve
     .section--dalle une rupture — bande inversee pleine largeur (base.css)
     .trio           trois reperes de meme rang — pastilles numerotees
     .feature__appui un aparte — la colonne du media, tant qu'il n'existe pas
     .duo            deux fonctions de meme rang — deux cartes cote a cote
     .cloture        une fin — bloc centre, plus rien apres

   🔴 AUCUN N'INVENTE DE CONTENU. Chaque traitement se pose sur des cles de
   message qui existaient deja avant ce lot ; c'est la contrainte qui a decide
   du decoupage, pas l'inverse. Une grille de trois cartes aurait demande une
   troisieme fonction que le produit n'a pas.
   ════════════════════════════════════════════════════════════════════════════ */

/* --- Le recit (accueil §2) : la voix de l'editeur, a la premiere personne.
       Colonne plus etroite que .prose et centree dans la page — c'est ce qui
       la distingue d'une section d'argumentaire sans changer un mot.

   ⚠️ Le texte reste aligne a gauche. Seuls le titre et la signature se centrent :
       deux paragraphes de six lignes centres se lisent mal, et le §4 du CHARTE
       porte sur la longueur de ligne autant que sur la mesure. --------------- */

.recit { max-width: 40rem; margin-inline: auto; }
.recit h2 { text-align: center; }
/* ⚠️ `:not(.meta)` : sans lui la signature passait de 14 a 16,5 px et devenait un
   paragraphe de plus. C'est une metadonnee, elle doit rester en retrait. */
.recit p:not(.meta) { font-size: clamp(16.5px, 1.15vw, 18px); line-height: 1.65; }
.recit__signature {
  text-align: center;
  margin-top: calc(var(--space) * 4);
  font-style: normal;
}

/* --- Le trio : trois reperes de meme rang, sur la dalle.

   🔴 La pastille numerotee est le SEUL emploi de --accent-brand de cette page,
   et c'est un fond, jamais une couleur de texte (CHARTE §3.3) : le chiffre est
   ecrit en --bg-dalle par-dessus, ce qui donne 4,9:1 dans les deux palettes.

   ⚠️ `auto-fit` et non `repeat(3, 1fr)`, pour la meme raison qu'au W12-a : une
   quatrieme puce ajoutee un jour dans le bundle se rangerait toute seule, et
   trois pistes en dur laisseraient la quatrieme sous les autres, seule. ------ */

.trio {
  display: grid;
  gap: calc(var(--space) * 4);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
  list-style: none;
  margin: calc(var(--space) * 6) 0 0;
  padding: 0;
  counter-reset: trio;
}
.trio li { margin: 0; }
.trio li::before {
  counter-increment: trio;
  content: counter(trio);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  margin-bottom: calc(var(--space) * 1.5);
  border-radius: 50%;
  background: var(--accent-brand);
  color: var(--bg-dalle);
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 700;
}

/* --- La capture de la dalle : la plus large du site, et la seule qui porte
       --shadow-produit sur une page marketing.

   🔴 Le selecteur vise `.shot-figure` et non une classe posee dans le gabarit :
   `th:replace` remplace l'element entier, attribut class compris. Une classe
   ecrite a l'appel disparaitrait sans la moindre erreur.

   ⚠️ 68 rem et non toute la page : au-dela, une capture de 1280 px est agrandie
   et le texte de l'interface se met a baver. C'est la largeur native du media
   du prerequis n°17 qui fixe le plafond, pas la grille. -------------------- */

.section--dalle .shot-figure {
  max-width: 68rem;
  margin: calc(var(--space) * 7) auto 0;
}
.section--dalle .shot {
  border-color: var(--border-dalle);
  box-shadow: var(--shadow-produit);
}

/* --- L'aparte : ce qui occupe la colonne du media tant qu'aucune capture
       n'existe (prerequis n°17).

   🔴 IL EST EXCLUSIF DE LA CAPTURE, exactement comme le cadre de debogueur du
   W12-b l'est de la video du hero — meme dispositif, meme piege : les th:if
   vivent sur des <th:block>, jamais sur l'element remplace. Le jour ou l'image
   arrive, l'aparte disparait et la colonne redevient ce qu'elle devait etre.

   ⚠️ Il ne repete pas le paragraphe d'a cote : il en extrait la phrase qui
   tranche. Un aparte qui redit son voisin est du remplissage, et un visiteur
   le lit comme tel. */

.feature__appui {
  margin: 0;
  border-left: 3px solid var(--accent-brand);
  padding-left: calc(var(--space) * 3);
  font-size: clamp(19px, 1.8vw, 25px);
  line-height: 1.35;
  letter-spacing: -0.01em;
  color: var(--fg);
}

/* --- Le duo : deux fonctions de meme rang, deux cartes.

   ⚠️ `align-items: start` : sans lui les deux cartes s'etirent a la hauteur de
   la plus haute et la plus courte se retrouve avec un bas vide. Elles n'ont pas
   la meme longueur de texte et ne l'auront jamais.

   ⚠️ Le titre reste un h2 dans chaque carte : ce sont deux sections du plan §9.5
   fusionnees a la mise en page, pas retrogradees dans la hierarchie. --------- */

.duo {
  display: grid;
  gap: calc(var(--space) * 3);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr));
  align-items: start;
}
.duo h2 { margin-top: 0; font-size: clamp(22px, 2vw, 27px); }
.duo h3 { margin-top: 0; }
.duo p:last-child { margin-bottom: 0; }

/* La capture, le jour ou elle existera, ferme la carte : marge en haut
   seulement, aucune en bas — la carte porte deja son propre rembourrage. */
.duo .shot-figure { margin: calc(var(--space) * 3) 0 0; }

/* --- La cloture : le dernier bloc de la page, centre. Rien ne vient apres,
       donc rien ne justifie de garder l'oeil a gauche. --------------------- */

.cloture { max-width: 44rem; margin-inline: auto; text-align: center; }
.cloture .btn { margin-top: calc(var(--space) * 2); }

/* --- Le chapo d'une page interieure : le pendant sobre du hero. Centre, borne
       plus court que .prose, il donne un debut a une page qui n'en avait pas —
       un h1 colle a son paragraphe, colle a la premiere section. ------------ */

/* 🔴 `.chapo p` PORTE SA PROPRE COULEUR, ET SURTOUT PAS `.meta-large`.
   C'est le defaut que Lighthouse a releve au W12-b sur le sous-titre du hero, et
   qui vivait encore ici : --fg-meta-large plafonne a 3,46:1 sur le fond sombre,
   et le « grand texte » de WCAG commence a 18,66 px EN GRAS ou 24 px en normal —
   18 px en graisse normale prend donc le seuil complet de 4,5. Les trois chapos
   du W12-c en sortent, sur --fg-muted (6,0:1). ⚠️ Les treize autres emplois de
   `.meta-large` du depot restent en place : les reprendre n'est le mandat
   d'aucun lot, et l'ecart est ecrit dans tokens.css. */
.chapo { max-width: 42rem; margin-inline: auto; text-align: center; }
.chapo h1 { margin-bottom: calc(var(--space) * 2); }
.chapo p {
  margin-bottom: 0;
  font-size: clamp(17px, 1.4vw, 19px);
  line-height: 1.5;
  color: var(--fg-muted);
}

/* --- La colonne des badges d'OS (/telecharger). Trois lignes de texte et un
       bouton etales sur 1152 px se lisaient comme un tableau d'administration.
       Bornee et centree, la pile redevient ce qu'elle est : un choix court. -- */

.telechargements { max-width: 46rem; margin-inline: auto; }

.limits { list-style: none; margin: 0; padding: 0; max-width: var(--maxw-prose); }
.limits li { margin-bottom: calc(var(--space) * 1.5); }
.limits li:last-child { margin-bottom: 0; }

.temoignages { display: grid; gap: calc(var(--space) * 3); }

@media (min-width: 900px) {
  .temoignages { grid-template-columns: repeat(2, 1fr); }
}

/* ────────────────────────────────────────────────────────────────────────────
   Utilitaires — W10-a, et ils n'existent que pour une raison de securite.

   🔴 La CSP du site pose `style-src 'self'` SANS 'unsafe-inline'. Un navigateur
   applique alors cette directive aux attributs style="…" eux-memes : les 34 qui
   trainaient dans les gabarits auraient ete ignores en silence, en production
   uniquement, et le seul symptome aurait ete une page legerement de travers.

   Les autoriser par 'unsafe-inline' aurait rendu la directive decorative. Ces
   sept classes sont donc le prix — et il est juste : un raccourci ecrit trente-
   quatre fois dans des gabarits est de toute facon une regle manquante.

   ⚠️ Rien d'autre n'a vocation a s'ajouter ici. Une septieme, une huitieme
   utilitaire, et la charte se met a vivre dans les gabarits (CHARTE §1).
   Un test refuse tout nouvel attribut style="…". */
.u-m0        { margin: 0; }
.u-mt-8      { margin-top: 8px; }
.u-mt-24     { margin-top: 24px; }
.u-mb-16     { margin-bottom: 16px; }
.u-flex-1    { flex: 1; }
.u-wrap      { flex-wrap: wrap; }
/* 🔴 `margin-inline: auto` AJOUTE AU W12-h, ET C'EST L'ECRAN DE CONNEXION QUI
   LE DEMANDAIT. Sans lui, la boite de 416 px se posait en HAUT A GAUCHE d'une
   page de 1200, sous 96 px de vide — trait pour trait la composition que le
   W12-g vient de corriger sur les six pages d'issue, cette fois sur le PREMIER
   ecran de l'admin. Une boite volontairement etroite dans une page large veut
   etre centree ; il n'existe pas de cas ou l'on veut l'inverse, et c'est
   pourquoi la regle vit ici plutot que dans le gabarit. */
.u-etroit    { max-width: 26rem; margin-inline: auto; }
