/*
Theme Name: YH Brands
Author: site-builder-wp
Description: Le thème sur mesure de YH Brands : la mise en page, les couleurs et la typographie du site. Les textes et les images s'éditent dans Pages. Ne pas modifier les fichiers du thème : toute modification est écrasée à la livraison suivante.
Version: 0.1.0
Requires at least: 7.0
Requires PHP: 8.4
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: sb-yhb
Tags: one-column, full-width-template, featured-images
Update URI: false
*/

/* Tokens — Yan Haltey Brands. Le seul endroit où vit l'identité.
 *
 * Relevés dans le prototype, pas choisis ici :
 *   assets/site.css      thème sombre  (--bg, --text, --accent)
 *   assets/carte.css     zone claire   (--bg, --text, --accent-ink)
 *
 * Deux règles de la maison, à ne pas défaire :
 *   1. AUCUN blanc pur. L'ivoire est #FAF8F3, le fond clair #F4F1EA.
 *   2. Le magenta #E6008A est juste SUR un bouton et illisible EN texte sur
 *      fond crème. Sa variante assombrie #B7006F est celle qu'utilise déjà
 *      la zone claire du prototype — c'est elle qui va dans --sb-accent-text.
 *   3. --sb-accent est le MILIEU du dégradé du bouton d'origine (#7A2CA6 →
 *      #A02597 → #E6008A), et non sa fin. WordPress aplatit le dégradé : pris
 *      sur le magenta pur, aucun texte ne passe AA (4,18 au mieux 4,44).
 *      #A02597 donne 6,17 avec l'ivoire, et reste une couleur du prototype.
 */
:root{
  --sb-ink-bg:#14141A;   --sb-ink-fg:#FAF8F3;
  --sb-paper-bg:#F4F1EA; --sb-paper-fg:#0B0B0E;
  --sb-accent:#A02597;   --sb-accent-fg:#FAF8F3;
  --sb-accent-text:#B7006F; /* accent lisible en TEXTE sur fond clair — WCAG AA */

  /* L'accent lisible en TEXTE sur fond SOMBRE — le pendant exact de
     --sb-accent-text, qui manquait.
     Relevé, pas choisi : `site.css:37` pose `--accent:#FF2EA6` en thème sombre.
     C'est déjà la couleur de la lueur ci-dessous, et celle des mots mis en
     valeur des témoignages du prototype ; elle vivait donc dans la marque sans
     jamais avoir de nom, ce qui obligeait chaque pattern à se la reconstruire
     par un `color-mix` vers l'ivoire — et à inventer une teinte différente à
     chaque fois (mesuré : #BD68B2 aux témoignages, #C376B8 au sélecteur,
     #C77BB8 au surtitre de héros, #FF9BD4 au bandeau de consentement : quatre
     roses délavés là où la marque n'en a qu'un).

     POURQUOI PAS LE MAGENTA PUR : #E6008A ne tient pas en texte sur l'encre —
     4,13:1 sur --sb-ink-bg et 4,47:1 sur --yhb-ink, mesurés au navigateur, tous
     deux sous les 4,5:1 de WCAG AA. C'est la symétrie de ce que dit la règle 2
     ci-dessus pour le fond clair : un accent de marque est presque toujours
     trop sombre pour du petit texte sur fond sombre.
     #FF2EA6 mesuré : 5,41:1 sur --sb-ink-bg (#14141A), 5,85:1 sur --yhb-ink
     (#0A0910), 4,84:1 sur la plaque des cartes encre (#1F1F24). AA partout. */
  --sb-accent-encre:#FF2EA6;

  /* Le FOND d'un bouton plein, et lui seul.
     Aplatir le dégradé du prototype sur son milieu gardait le contraste mais
     donnait un bouton violet là où l'œil attend du magenta — relevé deux fois
     à l'écran. Le dégradé rend la teinte juste sans rien concéder : la moitié
     gauche reste au violet foncé, donc le texte blanc y passe AA, exactement
     comme dans le prototype dont c'est la copie (site.css:183).
     `--sb-accent` demeure la couleur PLATE, pour les filets et le texte, où
     un magenta pur échouerait. */
  /* L'AMORCE de ce dégradé, nommée — parce qu'elle sert aussi À PLAT.
     `#7A2CA6` n'était écrit nulle part ailleurs qu'ici… et pourtant la feuille
     de marque le réemploie seul, hors de tout dégradé : en filet de 1 px sur la
     section des marques (`rgba(122,44,166,.35)`) et en couleur de TEXTE du
     bouton inversé posé sur la bande d'accent. Une valeur recopiée à la main
     dans une autre feuille est une divergence en attente — c'est le reproche
     que ce fichier fait déjà à `--yhb-grad-btn` et aux gris sourds.
     On la nomme donc ici, à la source, et le dégradé la CONSOMME : les deux ne
     peuvent plus diverger, puisqu'il n'y a plus qu'une écriture.

     « amorce » et non « violet foncé » : ce qui la définit n'est pas sa teinte
     mais sa PLACE — c'est par elle que commence le fond d'un bouton plein, et
     c'est à ce titre qu'on la réemploie ailleurs.

     Elle ne LIT que sur le clair — MESURÉ : 6,80:1 sur le crème, 2,39:1 sur
     l'encre commune, 2,59:1 sur l'encre profonde. Un texte qui la prend n'est
     donc juste que sur un fond clair ; voir le filet de `.sb-band .sb-btn`
     dans la feuille de marque, qui est dans ce cas et le documente. */
  --sb-accent-fond-amorce:#7A2CA6;
  --sb-accent-fond:linear-gradient(112deg,var(--sb-accent-fond-amorce) 0%,#A02597 50%,#E6008A 100%);

  /* La LUEUR — l'ombre portée magenta qui fait flotter un bloc d'accent.
     Relevée sur `.dtile` de la page profil du prototype :
     `box-shadow: #FF2EA6 0 14px 40px -18px`. Le magenta y est plus clair que
     l'accent (#FF2EA6 contre #E6008A) : une ombre doit être plus lumineuse que
     ce qu'elle éclaire, sinon elle salit au lieu de faire rayonner.
     L'étalement négatif (-18px) resserre le halo sous le bloc au lieu de le
     répandre — c'est ce qui distingue une lueur d'un flou.
     Promue en jeton parce qu'elle n'existait QUE sur cette page, alors que
     c'est un élément de langage de la marque.

     Le magenta clair n'est plus écrit ici : c'est --sb-accent-encre, déclaré
     plus haut. Les deux valaient déjà #FF2EA6 — la lueur n'en était que le
     premier emploi. Les faire dériver d'une source unique évite qu'un jour
     l'une bouge sans l'autre. */
  --sb-lueur:0 14px 40px -18px var(--sb-accent-encre);
  /* Signalé par chiffres.html puis par temoignages.html : sans ce jeton,
     tous les petits textes en monospace retombaient sur la police système
     au lieu d'IBM Plex Mono, qui est pourtant chargée par le thème. */
  --sb-font-mono:"IBM Plex Mono",ui-monospace,"SF Mono",Menlo,monospace;
  --sb-font-title:"Manrope","Helvetica Neue",Helvetica,Arial,sans-serif;
  --sb-font-body:"Manrope","Helvetica Neue",Helvetica,Arial,sans-serif;
  --sb-radius:14px;      --sb-motion:.2s;
  --sb-fg:var(--sb-paper-fg); --sb-bg:var(--sb-paper-bg);
}


/* Patterns — brand-agnostic. Tout passe par les tokens de la marque. */
:root{
  --sb-max:72rem; --sb-gap:clamp(1rem,3vw,2rem); --sb-pad:clamp(2.5rem,7vw,6rem);
}
*,*::before,*::after{box-sizing:border-box}
body{margin:0;font-family:var(--sb-font-body);color:var(--sb-fg);background:var(--sb-bg);
     line-height:1.6;-webkit-font-smoothing:antialiased}
img{max-width:100%;height:auto;display:block}
h1,h2,h3{font-family:var(--sb-font-title);line-height:1.15;margin:0 0 .5em;text-wrap:balance}
h1{font-size:clamp(2rem,5vw,3.5rem)} h2{font-size:clamp(1.5rem,3.5vw,2.5rem)} h3{font-size:1.15rem}
/* La mesure de ligne est reprise en var(--sb-mesure-texte) par lib/ruptures.php :
   la fixer ici la rendrait insurchargeable par une marque. */
p{margin:0 0 1em}

.sb-wrap{max-width:var(--sb-max);margin-inline:auto;padding-inline:var(--sb-gap)}
.sb-wrap--split{display:grid;gap:var(--sb-gap);grid-template-columns:1fr}
/* La bascule du rail est générée par lib/ruptures.php, d'après brands/<marque>/
   ruptures.json. Deux nombres pour une même bascule tromperaient le lecteur. */
section{padding-block:var(--sb-pad)}

/* Tonalités : mêmes variables, valeurs inversées. Une seule source de vérité couleur. */
.sb-ink{--sb-fg:var(--sb-ink-fg);--sb-bg:var(--sb-ink-bg);color:var(--sb-fg);background:var(--sb-bg)}
.sb-paper{--sb-fg:var(--sb-paper-fg);--sb-bg:var(--sb-paper-bg);color:var(--sb-fg);background:var(--sb-bg)}
.sb-band{color:var(--sb-accent-fg);background:var(--sb-accent);text-align:center}

.sb-btn{display:inline-block;padding:.75em 1.5em;border-radius:var(--sb-radius,.25rem);
  background:var(--sb-accent);color:var(--sb-accent-fg);text-decoration:none;font-weight:600;
  transition:opacity var(--sb-motion,.2s) ease}
.sb-btn:hover{opacity:.85}
.sb-band .sb-btn{background:var(--sb-accent-fg);color:var(--sb-accent)}

.sb-lead{font-size:1.15rem;opacity:.85}
.sb-grid{display:grid;gap:var(--sb-gap);grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));
  list-style:none;padding:0;margin:2rem 0 0}
.sb-card h3{margin-bottom:.25em}
.sb-card p{margin:0;opacity:.85}
.sb-price{font-weight:600;color:var(--sb-accent-text,var(--sb-accent))}

.sb-steps{counter-reset:sb;list-style:none;padding:0;margin:2rem 0 0;display:grid;gap:var(--sb-gap)}
.sb-step{counter-increment:sb;padding-left:3rem;position:relative}
.sb-step::before{content:counter(sb);position:absolute;left:0;top:0;width:2rem;height:2rem;
  display:grid;place-items:center;border-radius:50%;background:var(--sb-accent);
  color:var(--sb-accent-fg);font-weight:700}

.sb-figures{display:grid;gap:var(--sb-gap);grid-template-columns:repeat(auto-fit,minmax(9rem,1fr));
  list-style:none;padding:0;margin:2rem 0 0;text-align:center}
.sb-figure strong{display:block;font-family:var(--sb-font-title);font-size:clamp(2rem,5vw,3rem);
  color:var(--sb-accent-text,var(--sb-accent));line-height:1}
.sb-figure span{opacity:.8;font-size:.95rem}

.sb-portrait{margin:0}
.sb-portrait img{border-radius:var(--sb-radius,.25rem)}

.sb-header,.sb-footer{padding-block:1.25rem;background:var(--sb-ink-bg);color:var(--sb-ink-fg)}
.sb-header .sb-wrap,.sb-footer .sb-wrap{display:flex;gap:var(--sb-gap);
  align-items:center;justify-content:space-between;flex-wrap:wrap}
.sb-logo{font-family:var(--sb-font-title);font-size:1.25rem;color:inherit;text-decoration:none}
/* `.sb-footer ul` a été retiré de ce sélecteur : le pied est rendu par
   lib/pied.php avec ses propres classes composant, et cette règle en nappe les
   court-circuitait toutes — spécificité (0,1,1) contre (0,1,0). Résultat mesuré
   à l'écran : les liens de menu s'alignaient en ligne alors que
   `.sb-pied__liste` demande `block`, et les icônes sociales s'empilaient alors
   que `.sb-pied__reseaux` demande `flex`. Exactement l'inverse des deux règles
   nommées, et sans qu'aucune ne soit fausse. */
.sb-header ul{display:flex;gap:1.25rem;list-style:none;margin:0;padding:0;flex-wrap:wrap}
.sb-header a,.sb-footer a{color:inherit;text-decoration:none}
.sb-header a:hover,.sb-footer a:hover{text-decoration:underline}
.sb-footer p{margin:0}

:focus-visible{outline:3px solid var(--sb-accent);outline-offset:2px}
@media(prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}

.sb-ink{--sb-accent-text:var(--sb-accent)}

/* Récit — mesure de ligne resserrée : c'est du texte long, il se lit.
   La valeur est en var(--sb-mesure-prose), générée par lib/ruptures.php. */
.sb-prose p{margin:0 0 1.25em}
.sb-prose p:last-child{margin-bottom:0}

/* Lieu */
.sb-adresse{font-style:normal;font-size:1.1rem;margin-bottom:.75em}
.sb-carte img{border-radius:var(--sb-radius,.25rem);width:100%}

/* Intégré — le conteneur ne doit pas casser si le widget déborde. */
.sb-embed{margin-top:2rem;overflow:auto}
.sb-embed iframe{max-width:100%;border:0}
/* Emplacement sans intégration affichable : rien, pas même une gouttière. Pour
   un visiteur, le conteneur du gabarit est strictement vide — :empty s'applique.
   Pour qui édite, il porte la note, et reste donc visible. */
.sb-embed:empty{display:none}
/* Le mot réservé à qui peut éditer la page — jamais lu par un visiteur. */
.sb-embed__avis{font-size:.9rem;opacity:.7;font-style:italic}

/* Intégration tierce — le cadre porte le ratio, l'iframe le remplit. Le ratio
   vient du code collé (une carte arrive en 600×450, un lecteur en 16/9) et se
   pose en style en ligne : la place est donc réservée AVANT que le tiers
   réponde, ce qui supprime le décalage de mise en page au chargement.

   `position:relative` fait du cadre le repère du bouton de façade : sans lui,
   ce bouton en position:absolute sort du cadre.

   ⚠️ CES RÈGLES VIENNENT DE lib/pattern-embed.php, ET RIEN NE LES LIVRAIT.
   `sb_pattern_embed_css()` les portait dans une fonction que personne
   n'appelait — ni build.php, ni le thème. MESURÉ dans le navigateur sur
   /noyau/, avant report : max-width « none », overflow « visible »,
   border-radius « 0px », background « rgba(0, 0, 0, 0) ». Aucune n'était
   arrivée à l'écran ; seul `position:relative`, écrit ici la veille pour la
   façade, existait. Elles sont donc reportées et la fonction supprimée — c'est
   ce que son propre docbloc demandait, et deux copies du même CSS finissent
   par diverger. */
.sb-embed__cadre{
  position:relative;width:100%;max-width:64rem;margin-inline:auto;
  overflow:hidden;border-radius:var(--sb-radius,.25rem);
  /* Le fond n'est pas décoratif : entre le moment où la place est réservée et
     celui où le tiers répond — ou pendant qu'il attend le consentement — le
     cadre serait un trou blanc au milieu de la section. `currentColor` et non
     une couleur écrite : la teinte suit la tonalité de la section, comme le
     voile de la façade plus bas. */
  background:color-mix(in srgb,currentColor 8%,transparent);
}
.sb-embed__cadre>iframe{display:block;width:100%;height:100%;border:0}

/* La façade du lecteur vidéo — le tiers n'est appelé qu'au clic ; ce qui suit
   est ce qu'on voit avant. Voir lib/pattern-embed.php, sb_embed_facade(). */
.sb-embed__facade{
  position:absolute;inset:0;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:.5rem;
  width:100%;height:100%;padding:1.5rem;border:0;cursor:pointer;
  font:inherit;text-align:center;
  /* Aucune couleur en dur : --sb-fg et --sb-bg basculent seuls sous .sb-ink
     (plus haut dans ce fichier), et la section « integre » de /noyau/ EST
     .sb-ink. */
  color:var(--sb-fg);background:var(--sb-bg);
}
/* La vignette est le fond, pas un enfant du flux. */
.sb-embed__vignette{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
/* Le voile garde le nom du bouton lisible quelle que soit l'image dessous : un
   titre clair sur une image claire ne se lit pas, et c'est le nom de la seule
   commande du cadre. color-mix sur le jeton de fond — la teinte suit la
   tonalité de la section, elle n'est pas écrite ici. */
.sb-embed__facade::after{
  content:"";position:absolute;inset:0;
  background:color-mix(in srgb,var(--sb-bg) 55%,transparent);
}
.sb-embed__jouer,.sb-embed__nom{position:relative;z-index:1}
.sb-embed__jouer{
  display:grid;place-items:center;width:4.5rem;height:4.5rem;border-radius:999px;
  background:var(--sb-accent);color:var(--sb-accent-fg);
  transition:transform var(--sb-motion,.2s) ease-out;
}
.sb-embed__facade:hover .sb-embed__jouer{transform:scale(1.06)}
.sb-embed__nom{font-family:var(--sb-font-title);font-size:1.125rem;font-weight:600}
/* `.sb-embed__hote` retiré le 21.08 : le bouton ne nomme plus l'hôte du
   lecteur. Du CSS qui décrit un balisage disparu est un piège pour qui lira
   cette feuille ensuite. */
/* Lu, jamais vu : la part du nom du bouton que le texte visible ne dit pas.
   Même technique que la mise hors flux du message de WP Armour, plus bas. */
.sb-embed__lire{
  position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);
  white-space:nowrap;
}
/* L'anneau de focus se pose SUR une photo ; en retrait, il garde son contraste
   contre le voile plutôt que de se perdre dans l'image.
   ⚠️ LA CONTREPARTIE .sb-ink N'EST PAS COSMÉTIQUE. MESURÉ : --sb-accent
   (#A02597) sur --sb-ink-bg (#14141A) donne 2,80:1, sous les 3:1 qu'exige
   WCAG 1.4.11 pour un indicateur de focus. --sb-accent-encre (#FF2EA6), que
   tokens.css déclare pour cet usage, donne 5,41:1. */
/* ⚠️ L'ANNEAU EST PORTÉ PAR LE VOILE, ET C'EST OBLIGATOIRE. Posé sur le bouton,
   il est INVISIBLE : ::after est un descendant positionné, et un descendant
   positionné peint au-dessus de l'outline de son parent. Vérifié au navigateur
   — la règle s'appliquait, la couleur était juste, et on ne voyait rien. Sur
   ::after, l'anneau est dans la même couche que le voile, donc au-dessus de la
   vignette. On retire l'anneau du bouton pour ne pas en dessiner deux. */
.sb-embed__facade:focus-visible{outline:0}
.sb-embed__facade:focus-visible::after{outline:3px solid var(--sb-accent-text);outline-offset:-5px}
.sb-ink .sb-embed__facade:focus-visible::after{outline-color:var(--sb-accent-encre)}
/* Sans JavaScript, le bouton ne commande rien : il s'efface, et le <noscript>
   du module rend le lecteur — cette population retrouve la page d'avant la
   façade. La règle est ici et non dans un <style> posé à côté du <noscript> :
   html-validate refuse un <style> sous <div>, et le canal 5 le compte. */
@media (scripting: none){ .sb-embed__facade{display:none} }

/* Liste de marques : les tuiles portent TOUJOURS une plaque claire.
   La tonalité des sections alterne selon leur position — on ne la fige pas —
   mais un logo, lui, est un fichier figé : la variante encre d'une marque
   devient invisible dès que la section tombe en tonalité sombre. La plaque
   rend la lisibilité indépendante de la position de la section. */
.sb-marques .sb-card{background:var(--sb-paper-bg);color:var(--sb-paper-fg);
  border-radius:var(--sb-radius,.25rem);padding:1.25rem;text-align:center}
.sb-marques .sb-logo{max-width:150px;max-height:52px;width:auto;height:auto;
  object-fit:contain;margin:0 auto .75rem;display:block}


/* ══ Compteurs — GÉNÉRÉ depuis patterns/chiffres.html ══════════════════════
   Le dégradé se dérive de l'accent : celui du prototype démarre presque
   noir et le chiffre s'éteignait en tonalité encre. --sb-degrade reste
   disponible si une marque veut imposer le sien. */
:root{ --sb-chiffres-magenta:var(--sb-accent-text,var(--sb-accent)); }

  .sb-chiffres{
    /* L'espace entre deux compteurs sert DEUX fois : à la gouttière de la
       rangée, et à placer le filet en son milieu. Un seul nombre, sinon le
       filet dérive dès qu'on retouche la gouttière. */
    --sb-compteurs-espace:clamp(1.75rem,5vw,3.5rem);
    /* Aucune marque ne déclare aujourd'hui de token de dégradé. On le dérive
       donc de l'accent, et --sb-degrade laisse une marque poser le sien —
       celui d'YHB va du violet au magenta — sans toucher au pattern. */
    --sb-chiffres-debut:var(--sb-accent);
    --sb-chiffres-fin:var(--sb-chiffres-magenta);
    --sb-chiffres-surtitre:var(--sb-chiffres-magenta);
    --sb-chiffres-degrade:var(--sb-degrade,linear-gradient(112deg,
      var(--sb-chiffres-debut) 0%,var(--sb-chiffres-fin) 100%));
    text-align:center;
  }

  /* En tonalité encre, l'accent d'YHB tombe à 2,8:1 sur le fond sombre — sous
     les 3:1 que WCAG demande même au grand texte, et loin des 4,5:1 du surtitre
     qui est petit. La tonalité vient du rang : le pattern ne sait pas où il
     tombera, il doit tenir aux deux. On éclaircit les bornes vers la couleur du
     texte encre, en gardant les deux teintes distinctes.
     Mesuré avec les tokens YHB : violet 4,76:1 · magenta 4,78:1 ·
     surtitre 6,41:1 sur l'encre #14141A ; 5,81 · 5,74 · 5,74 sur le papier
     #F4F1EA. La légende tient à 8,5:1 et 10,0:1. */
  .sb-chiffres.sb-ink{
    --sb-chiffres-debut:color-mix(in oklab,var(--sb-accent) 75%,var(--sb-ink-fg));
    --sb-chiffres-fin:color-mix(in oklab,var(--sb-chiffres-magenta) 75%,var(--sb-ink-fg));
    --sb-chiffres-surtitre:color-mix(in oklab,var(--sb-chiffres-magenta) 60%,var(--sb-ink-fg));
  }

  /* Le surtitre n'avait de règle nulle part, alors que hero, lieu, chiffres,
     constellation, rendez-vous et récit l'emploient tous. Chacun héritait donc
     du corps de texte : 16-17 px en Manrope, là où le prototype pose du mono
     capitalisé de 12 px. La règle avait d'abord été posée portée à la seule
     section chiffres, faute de vouloir décider pour les autres — ce scrupule
     laissait cinq patterns faux pour en corriger un.
     Elle est ici GLOBALE ; seule la couleur reste au pattern, parce qu'elle
     dépend du fond qui la porte. */
  /* La lueur, en classe utilitaire : un bloc d'accent qui doit sembler flotter.
     Le jeton `--sb-lueur` appartient à la marque ; la classe, à la chaîne. Sans
     jeton déclaré, elle ne fait rien — une marque sans lueur n'en a pas besoin
     d'une par défaut, et un halo posé d'office sur un site sobre le salirait. */
  .sb-lueur{box-shadow:var(--sb-lueur,none)}

  .sb-eyebrow{
    font-family:var(--sb-font-mono,ui-monospace,SFMono-Regular,Menlo,monospace);
    font-size:max(.75rem,12px);letter-spacing:.14em;text-transform:uppercase;
    color:var(--sb-muted,inherit);margin:0 0 .75rem;
  }
  .sb-chiffres .sb-eyebrow{ color:var(--sb-chiffres-surtitre) }

  .sb-compteurs{
    display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:center;
    gap:var(--sb-compteurs-espace);
    list-style:none;padding:0;margin:2.5rem 0 0;
  }
  .sb-compteur{position:relative;flex:0 0 auto;min-width:7rem}

  /* Le filet est un pseudo-élément et non un élément à part : le nombre de
     compteurs vient du client, et une séparation saisie à la main serait à
     reprendre à chaque ajout. Il se pose dans la gouttière, à gauche de tout
     compteur qui en suit un autre.
     Limite connue : si la rangée passe à la ligne, le premier compteur de la
     seconde ligne garde son filet, seul dans le vide. La rangée est faite pour
     trois ou quatre chiffres et le filet disparaît sous 40rem, là où le
     repliement est certain. Au-delà de quatre compteurs, retirer le filet. */
  .sb-compteur + .sb-compteur::before{
    content:"";position:absolute;top:50%;
    left:calc(var(--sb-compteurs-espace) / -2);
    transform:translateY(-50%);
    width:1px;height:3.5rem;background:currentColor;opacity:.2;
  }

  .sb-compteur__valeur{
    display:block;font-family:var(--sb-font-title);
    font-size:clamp(2.6rem,6.5vw,4.25rem);font-weight:700;
    letter-spacing:-.02em;line-height:1;
    background:var(--sb-chiffres-degrade);
    -webkit-background-clip:text;background-clip:text;color:transparent;
  }

  /* Un texte découpé dans un dégradé n'a plus de couleur : en contraste forcé,
     le système ne peut plus le repeindre et le chiffre disparaît purement et
     simplement. On lui rend une couleur dès que le mode est actif. */
  @media (forced-colors:active){
    .sb-compteur__valeur{background:none;color:CanvasText;-webkit-text-fill-color:CanvasText}
  }

  .sb-compteur__legende{
    display:block;margin-top:.75rem;
    font-family:var(--sb-font-mono,ui-monospace,SFMono-Regular,Menlo,monospace);
    font-size:max(.75rem,12px);letter-spacing:.14em;text-transform:uppercase;
    line-height:1.45;opacity:.75;
  }

  @media (max-width:40rem){
    .sb-compteurs{gap:1.5rem}
    .sb-compteur + .sb-compteur::before{display:none}
  }


/* ══ Habillage des composants d'extension ═══════════════════════════════════
   Fluent Forms et FluentBooking posent leur propre feuille, pensée pour un
   thème clair et une pleine largeur. Sur une section encre, cela donne des
   champs blancs qui crèvent la page, et une largeur qui déborde la mesure de
   lecture. Ces règles les ramènent dans les tokens de la marque.

   Portées aux ZONES de nos patterns (`.sb-formulaire__zone`,
   `.sb-rendezvous__zone`) et jamais aux classes des extensions seules : une
   extension mise à jour renomme ses classes, notre zone ne bouge pas. */

.sb-formulaire__zone,
.sb-rendezvous__zone{max-width:44rem;margin-inline:auto}

/* ── Les champs suivent la tonalité de leur section ────────────────────────── */
.sb-formulaire__zone :is(input[type=text],input[type=email],input[type=tel],input[type=url],textarea,select){
  background:color-mix(in srgb,currentColor 6%,transparent);
  color:inherit;
  border:1px solid color-mix(in srgb,currentColor 22%,transparent);
  border-radius:var(--sb-radius,.75rem);
  padding:.85rem 1rem;
  /* 16px plancher : sous cette taille, Safari iOS zoome de force à la prise
     de focus et le visiteur perd sa place dans le formulaire. */
  font:inherit;font-size:max(1rem,16px);
  width:100%;
}
.sb-formulaire__zone ::placeholder{color:inherit;opacity:.45}
.sb-formulaire__zone :is(input,textarea,select):focus-visible{
  outline:2px solid var(--sb-accent);outline-offset:1px;
  border-color:transparent;
}
.sb-formulaire__zone label{color:inherit;font-weight:500}

/* Le bouton d'envoi reprend l'accent de la marque, comme tous les autres. */
.sb-formulaire__zone :is(button[type=submit],input[type=submit],.ff-btn-submit){
  background:var(--sb-accent);color:var(--sb-accent-fg);
  border:0;border-radius:999px;
  padding:.8em 2em;font:inherit;font-weight:600;cursor:pointer;
}

/* ── Le calendrier ────────────────────────────────────────────────────────── */
/* Il garde SA feuille claire — c'est une interface dense, la rendre sombre
   demanderait de reprendre chacun de ses états. On l'encadre comme une carte
   posée sur la section, ce que fait déjà le prototype. */
.sb-rendezvous__zone{border-radius:var(--sb-radius,1rem);overflow:hidden}
.sb-rendezvous__zone .fluent_booking_app{max-width:100%}

/* ── L'écran de confirmation ──────────────────────────────────────────────── */
/* 🔴 IL ARRIVE PAR DEUX CHEMINS, ET LES DEUX COMPTENT. C'est ce qui explique
   les deux préfixes de chaque règle ci-dessous, et pourquoi une correction qui
   n'en traite qu'un donne l'impression de ne pas tenir :

     · DANS LA PAGE — le script de l'extension remplace le calendrier par
       `div.fcal_booking_confirmed`. Le thème est là, donc cette feuille aussi ;

     · EN PAGE AUTONOME — `?fluent-booking=booking&type=confirmation…`, un
       document HTML complet servi SANS `wp_head()`, donc sans le thème. Le
       module `rendezvous.php` y ajoute cette feuille par le filtre
       `fluent_booking/booking_confirmation_page_vars` — voir
       lib/pattern-rendezvous.php. Sans ce filtre, rien de ce qui suit ne
       s'applique là-bas. Et on y arrive au moindre rechargement : le script
       pousse cette adresse dans l'historique juste après avoir affiché le
       premier écran.

   `.booking-confirmation-page` est la classe que l'extension pose sur le
   <body> de ce document. On s'y accroche à contrecœur — la règle de la maison
   veut qu'on parte de NOS zones — mais il n'y a là aucun élément à nous : la
   vue entière appartient à l'extension, et le filtre ne peut ajouter qu'une
   feuille, pas un conteneur. Le jour où elle renomme cette classe, l'écran
   retombe sur son habillage d'origine : dégradé, jamais cassé. */

/* Les variables de l'extension reçoivent les jetons de la marque — le geste
   exact du formulaire : on ne dispute pas ses sélecteurs, on lui passe les
   couleurs et elle se repeint seule. Ce qui survit à sa prochaine version. */
:is(.sb-rendezvous__zone,.booking-confirmation-page) .fcal_confirmation{
  --fcal_dark:var(--sb-paper-fg);
  --fcal_slot_border:color-mix(in srgb,var(--sb-paper-fg) 14%,transparent);
  --fcal_primary_color:var(--sb-accent-text);
  --fcal_thBG:color-mix(in srgb,var(--sb-paper-fg) 5%,transparent);
  font-family:var(--sb-font-body);
  /* La gouttière est celle de la carte, pas celle du contenu : deux épaisseurs
     empilées donnaient le bloc trop haut et le titre trop bas. */
  padding:0;
}

/* R78. LE PANNEAU A SA PROPRE BOÎTE, DONC IL DÉCLARE FOND ET TEXTE ENSEMBLE.
   Même règle qu'au message de confirmation d'un formulaire : ce qui se pose
   sur le fond de la section en suit la tonalité, ce qui a sa propre boîte
   porte la paire. L'extension, elle, ne déclarait que le fond — un blanc pur,
   que la maison n'emploie nulle part. */
.sb-rendezvous__zone .fcal_booking_confirmed,
.booking-confirmation-page .fcal_conf_wrap{
  background:var(--sb-paper-bg);color:var(--sb-paper-fg);
  border:1px solid color-mix(in srgb,var(--sb-paper-fg) 12%,transparent);
  border-radius:var(--sb-radius,1rem);
  box-shadow:none;
  padding:clamp(1.5rem,4vw,2.5rem);
  /* 38rem : la mesure du formulaire. Un reçu se lit en une colonne étroite,
     pas sur la largeur d'un calendrier. `min-width` remis à zéro — l'extension
     y pose 600 px en dur, ce qui déborde toute colonne plus étroite. */
  margin:0 auto;max-width:38rem;min-width:0;
}

/* UNE carte, pas deux. Selon la disposition, l'extension emboîte la
   confirmation dans le cadre du calendrier : le cadre extérieur devient un
   liseré blanc autour du nôtre. On l'efface dès qu'il ne porte plus que la
   confirmation — le calendrier, lui, garde son cadre intact. */
.sb-rendezvous__zone .fcal_calendar_inner:has(.fcal_booking_confirmed){
  background:transparent;border:0;box-shadow:none;
}

/* La page autonome n'a ni en-tête ni pied de site : c'est son FOND qui doit
   dire au visiteur qu'il n'a pas quitté le site. Fond et texte ensemble, comme
   partout. `--fcal_body_bg` est la variable par laquelle l'extension peint
   `.confirmation_page` : la lui passer évite de courir après son sélecteur. */
body.booking-confirmation-page{
  --fcal_body_bg:var(--sb-ink-bg);
  background:var(--sb-ink-bg);color:var(--sb-ink-fg);
  font-family:var(--sb-font-body);
}
/* `height:100vh` chez l'extension : une note un peu longue déborde alors du
   fond, et la page se termine sur une bande de couleur étrangère. */
.booking-confirmation-page .confirmation_page{
  height:auto;min-height:100vh;padding-block:clamp(2rem,8vh,4rem);
}
/* Le retour à l'accueil est posé sur l'ENCRE, pas sur la carte : l'accent
   lisible sur fond clair y tomberait à un contraste de 2:1. On prend la
   couleur de texte de l'encre, et c'est le soulignement qui fait le lien. */
.booking-confirmation-page .fcal_back_btn{color:var(--sb-ink-fg)}
.booking-confirmation-page .fcal_back_btn a{color:inherit;text-decoration:underline;text-underline-offset:.25em}
.booking-confirmation-page .fcal_back_btn svg{stroke:currentColor}

/* L'en-tête. Le filet de 2 px est le trait le plus étranger de l'écran : un
   cheveu d'un pixel, pris dans la tonalité de la boîte, sépare aussi bien. */
:is(.sb-rendezvous__zone,.booking-confirmation-page) .fcal_confirmation .fcal_confirm_header{
  border-bottom:1px solid color-mix(in srgb,currentColor 14%,transparent);
  padding-bottom:clamp(1rem,3vw,1.5rem);
  margin-bottom:clamp(1rem,3vw,1.5rem);
}
:is(.sb-rendezvous__zone,.booking-confirmation-page) .fcal_confirmation .fcal_confirm_header h2{
  font-family:var(--sb-font-title);
  font-size:clamp(1.3rem,3.2vw,1.7rem);font-weight:600;
  letter-spacing:-.02em;line-height:1.15;
  color:inherit;margin:1rem 0 0;
}
:is(.sb-rendezvous__zone,.booking-confirmation-page) .fcal_confirmation .fcal_confirm_header p{
  color:inherit;opacity:.75;margin:.35rem 0 0;
}

/* LA PASTILLE. Elle était une IMAGE — `check-mark.png`, un vert qui n'est
   d'aucune marque — donc hors d'atteinte de toute feuille de style. Le module
   vide `confirm_icon` pour une réservation confirmée (voir
   lib/pattern-rendezvous.php) : la vue retombe alors sur son propre
   pictogramme d'agenda, tracé en `currentColor`. Il ne reste qu'à colorer le
   disque — et à défaire le gris que l'extension impose au tracé, qui
   ignorerait `currentColor`. */
:is(.sb-rendezvous__zone,.booking-confirmation-page) .fcal_confirmation .fcal_check_holder{
  background:color-mix(in srgb,var(--sb-accent) 12%,transparent);
  color:var(--sb-accent-text);
}
:is(.sb-rendezvous__zone,.booking-confirmation-page) .fcal_confirmation .fcal_check_holder svg{
  stroke:currentColor;width:22px;height:22px;
}

/* Les intitulés reprennent le SURTITRE de la maison — mono, capitales,
   interlettrage large — au lieu du gras d'un rem de l'extension. C'est ce
   seul détail qui fait lire le bloc comme une fiche du site plutôt que comme
   le reçu d'une extension. La largeur de 200 px posée sur l'intitulé est sans
   effet dans une colonne : on la retire pour ne pas la relire à chaque fois. */
:is(.sb-rendezvous__zone,.booking-confirmation-page) .fcal_confirmation .fcal_confirm_section{
  padding:0;
}
:is(.sb-rendezvous__zone,.booking-confirmation-page) .fcal_confirmation .fcal_confirm_section + .fcal_confirm_section{
  margin-top:1.15rem;
}
:is(.sb-rendezvous__zone,.booking-confirmation-page) .fcal_confirmation .fcal_confirm_section_title{
  width:auto;margin:0 0 .2rem;line-height:1.45;
}
:is(.sb-rendezvous__zone,.booking-confirmation-page) .fcal_confirmation .fcal_confirm_section h4{
  font-family:var(--sb-font-mono,ui-monospace,SFMono-Regular,Menlo,monospace);
  font-size:max(.72rem,11px);font-weight:500;
  letter-spacing:.14em;text-transform:uppercase;
  color:inherit;opacity:.6;margin:0;
}
/* Le gris #666 de l'extension suppose un fond blanc. Ce qui se pose sur le
   fond de la boîte en suit la tonalité — c'est la même règle qu'aux libellés
   d'un formulaire, et elle vaut ici pour la même raison. */
:is(.sb-rendezvous__zone,.booking-confirmation-page) .fcal_confirmation .fcal_confirm_section_content{
  color:inherit;font-size:1rem;line-height:1.55;
}
:is(.sb-rendezvous__zone,.booking-confirmation-page) .fcal_confirmation .fcal_confirm_section_content p{
  font-size:inherit;margin:0;
}

/* Le pied — « Besoin d'un changement ? » puis « Ajouter au calendrier ». */
/* Aligné à gauche, comme tout le corps de la fiche. L'extension le centre —
   mesuré : le pied centré au-dessus d'une rangée d'agendas alignée à gauche
   donnait deux axes de lecture dans un bloc de six centimètres de haut. Seul
   l'en-tête reste centré, et c'est ce qui le fait ressortir. */
:is(.sb-rendezvous__zone,.booking-confirmation-page) .fcal_confirmation .fcal_booking_manage{
  border-top:1px solid color-mix(in srgb,currentColor 14%,transparent);
  color:inherit;font-size:.95rem;text-align:left;
  margin-top:clamp(1.25rem,3vw,1.75rem);
  padding:clamp(1rem,3vw,1.25rem) 0 0;
}
:is(.sb-rendezvous__zone,.booking-confirmation-page) .fcal_confirmation .fcal_booking_manage a{
  color:var(--sb-accent-text);text-underline-offset:.2em;
}
/* L'extension centre la rangée et neutralise sa marge par un !important : on
   ne la lui dispute pas, l'écart vient du padding. Le reste du bloc est aligné
   à gauche, cette rangée aussi. `flex-wrap` : sept services d'agenda tiennent
   mal sur une colonne de téléphone. */
:is(.sb-rendezvous__zone,.booking-confirmation-page) .fcal_confirmation .fcal_to_calendars{
  justify-content:flex-start;flex-wrap:wrap;row-gap:.6rem;
  padding-top:clamp(1rem,3vw,1.25rem);
}
:is(.sb-rendezvous__zone,.booking-confirmation-page) .fcal_confirmation .fcal_to_calendars > span{
  font-family:var(--sb-font-mono,ui-monospace,SFMono-Regular,Menlo,monospace);
  font-size:max(.72rem,11px);letter-spacing:.14em;text-transform:uppercase;
  opacity:.6;
}
:is(.sb-rendezvous__zone,.booking-confirmation-page) .fcal_confirmation .fcal_to_calendars .fcal_cal_items a{
  border:1px solid color-mix(in srgb,currentColor 18%,transparent);
  border-radius:calc(var(--sb-radius,1rem) / 2);
  padding:.45rem;
}

/* Le formulaire d'annulation partage cet écran : sans ces règles, le lien
   « Annuler » mène à une page à moitié habillée — et c'est le moment où le
   visiteur a le plus besoin de reconnaître le site. Champ et boutons reprennent
   exactement ce qui est posé plus haut pour Fluent Forms, plancher de 16 px
   compris : sous cette taille, Safari iOS zoome de force à la prise de focus. */
:is(.sb-rendezvous__zone,.booking-confirmation-page) .fcal_confirmation .fcal_cancellation_wrap textarea{
  width:100%;font:inherit;font-size:max(1rem,16px);
  color:inherit;background:color-mix(in srgb,currentColor 6%,transparent);
  border:1px solid color-mix(in srgb,currentColor 22%,transparent);
  border-radius:var(--sb-radius,.75rem);padding:.85rem 1rem;
}
:is(.sb-rendezvous__zone,.booking-confirmation-page) .fcal_confirmation .fcal_form_actions{
  display:flex;flex-wrap:wrap;gap:.75rem;margin-top:1rem;
}
:is(.sb-rendezvous__zone,.booking-confirmation-page) .fcal_confirmation .fcal_cancellation_wrap .fcal_btn{
  font:inherit;font-weight:600;border-radius:999px;padding:.7em 1.6em;
  background:transparent;color:inherit;
  border:1px solid color-mix(in srgb,currentColor 30%,transparent);
}
:is(.sb-rendezvous__zone,.booking-confirmation-page) .fcal_confirmation .fcal_cancellation_wrap .fcal_btn.fcal_btn_primary{
  background:var(--sb-accent);color:var(--sb-accent-fg);border-color:transparent;
}

/* Le message de diagnostic de WP Armour est réservé aux administrateurs, mais
   il s'affiche EN PAGE et casse la mise en forme. On le sort du flux : il reste
   lisible dans l'inspecteur, il ne déforme plus la section. */
.sb-formulaire__zone .wpa_hidden_notice,
.sb-formulaire__zone [class*="wpa-"]:has(> :is(b,strong):first-child){
  position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);
}


/* Les compteurs prolongent la liste de marques : le prototype les montre d'un
   seul bloc, sur un seul fond. Deux sections HTML restent nécessaires — le
   client doit pouvoir retirer l'une sans l'autre — mais elles ne doivent pas
   se lire comme deux. On supprime donc l'espace qui les sépare. */
.sb-marques + .sb-chiffres{padding-block-start:0}
.sb-marques:has(+ .sb-chiffres){padding-block-end:clamp(1.5rem,3vw,2.5rem)}


/* L'enveloppe d'un appel à l'action du hero, quand elle est vide.

   patterns/hero.html porte ses boutons dans une liste répétable. Le lien lui-
   même n'est pas rendu si son libellé est vide — Compiler::transformField()
   l'entoure de `if ( ! empty( $row['lien']['title'] ) )`, ce qui évite le
   `<a href="#"></a>` qu'axe-core refuse (link-name). Mais l'ENVELOPPE de la
   ligne, elle, est toujours émise : le `foreach` du gabarit tourne une fois par
   ligne saisie, libellé ou pas.

   Mesuré sur le gabarit produit avec une ligne sans libellé : 2 `<span>` émis
   pour 1 seul `<a>`. Le span restant est un élément flex de largeur nulle dans
   `.sb-hero__actions`, qui applique `gap:14px`.

   Mesuré au navigateur (Chromium 1280 px, habillage yhb, un bouton + une
   enveloppe vide) : rangée de 141,2 px pour un bouton de 127,2 px sans cette
   règle, soit 14,0 px de blanc que rien n'explique à qui relit la page ; 127,2
   px avec, soit 0,0 px. Le compte est le même quel que soit le nombre
   d'enveloppes vides.

   L'enveloppe PLEINE, elle, ne coûte rien : mesurée à l'identique de l'ancienne
   forme où les `<a>` étaient enfants directs — mêmes largeurs, mêmes hauteurs,
   mêmes positions au dixième de pixel sur les quatre boutons de /hello/. C'est
   ce qui autorisait à l'introduire.

   La règle vit ici et non dans l'habillage d'une marque : elle répare une
   conséquence du MOTEUR de répétition, pas un choix esthétique, et elle vaudra
   pour toute marque qui reprendra ce pattern. */
.sb-hero__actions > *:empty{display:none}


/* ══ Ruptures — GÉNÉRÉ par lib/ruptures.php, ne pas éditer ══════════════════
   Les nombres des media queries ci-dessous et ceux des tokens viennent de la
   MÊME source. Les saisir à la main ici les désynchroniserait en silence :
   var() ne se lit pas dans une condition de media query. */
:root{
  --sb-plancher:20rem;
  --sb-plein:72rem;
  --sb-mesure-texte:65ch;
  --sb-mesure-prose:62ch;
  --sb-rupture-duo:44rem;
  --sb-rupture-nav:48rem;
  --sb-rupture-rail:56rem;
}

/* La mesure de ligne, prise sur les tokens plutôt qu'en dur. */
p{max-width:var(--sb-mesure-texte)}
.sb-prose{max-width:var(--sb-mesure-prose)}

/* ── rupture « duo » : 44em ─────────────────────────────────────────────
   La navigation horizontale. En dessous, elle ne tient pas sur une ligne et
   doit devenir un bouton — ce basculement n'appartient pas à ce fichier, mais
   le NOMBRE qui le déclenche, si. Le chantier du menu mobile consomme
   `--sb-rupture-duo` pour ses tokens et cette valeur pour sa media query. */

/* ── rupture « rail » : 56em ───────────────────────────────────────────
   La colonne latérale. Une seule colonne en dessous : à 56em, ce qui reste
   à la colonne principale tient encore la mesure. patterns.css la fait
   apparaître à 48rem, où la colonne principale tombe à 48 caractères ; la règle
   ci-dessous la ramène au bon endroit par la cascade, sans toucher au fichier
   partagé. */
.sb-wrap--split{grid-template-columns:1fr}
@media(min-width:56em){
  .sb-wrap--split{grid-template-columns:minmax(0,18rem) 1fr;align-items:start}
}

/* ══ Ce que le système ne couvrait pas ═════════════════════════════════════ */

/* 1. LES MOTS LONGS. Une URL collée dans un champ, un nom composé allemand, un
      identifiant de commande : un seul mot plus large que sa colonne pousse
      toute la mise en page et fait déborder la page.

      `anywhere` et non `break-word` : les deux coupent le mot, mais seul
      `anywhere` fait entrer les points de coupure dans le calcul de la taille
      min-content. Or c'est min-content qui décide jusqu'où un enfant de grille
      ou de flex accepte de rétrécir — avec `break-word`, l'enfant refuse de
      passer sous la largeur du mot entier et la page déborde quand même.

      `min-width:0` sur les enfants pour la même raison : un enfant de flex vaut
      `min-width:auto` par défaut, ce qui l'empêche de descendre sous son
      contenu. Sans lui, la règle ci-dessus ne sert à rien dans le header. */
body{overflow-wrap:anywhere}
.sb-grid>*,.sb-steps>*,.sb-figures>*,.sb-wrap--split>*,
.sb-header .sb-wrap>*,.sb-footer .sb-wrap>*{min-width:0}
/* La césure demande la langue, que header.php pose déjà via language_attributes(). */
.sb-prose,.sb-legal{hyphens:auto}
/* Un titre coupé au milieu d'un mot est illisible : on le laisse dépasser du
   rythme plutôt que de le hacher. text-wrap:balance est déjà posé plus haut. */
h1,h2,h3{overflow-wrap:break-word;hyphens:manual}

/* 2. LES TABLEAUX ET TOUT CE QUI NE SE PLIE PAS. WCAG 1.4.10 dispense
      explicitement les tableaux de données du reflow — ils ont le droit de
      demander deux dimensions. Ils n'ont pas le droit de faire déborder LA
      PAGE : ils défilent dans leur propre boîte.

      `tabindex` et `role=region` ne sont pas décoratifs : une zone qui défile
      et qu'on ne peut pas atteindre au clavier échoue à WCAG 2.1.1. Le client
      n'écrit pas ce markup — c'est le rendu qui l'enveloppe, voir le bloc
      d'intégration. */
.sb-echappe{overflow-x:auto;overscroll-behavior-x:contain;
  scrollbar-gutter:stable;max-width:100%}
.sb-echappe:focus-visible{outline:3px solid var(--sb-accent);outline-offset:2px}
.sb-echappe>table{min-width:max-content}
table{border-collapse:collapse;max-width:100%}
pre,code,kbd,samp{overflow-wrap:normal}
pre{overflow-x:auto;max-width:100%}

/* 3. LES MÉDIAS LARGES DANS DU TEXTE. patterns.css borne déjà `img`. Il reste
      tout ce qu'un champ WYSIWYG peut produire, et que rien ne bornait : une
      vidéo, un iframe de carte, un SVG à dimensions intrinsèques. */
img,svg,video,canvas,audio,iframe,embed,object{max-width:100%}
img,video,canvas{height:auto}
iframe,embed,object{border:0}
/* .sb-embed défile déjà (patterns.css) : il lui manquait d'être atteignable. */
.sb-embed{overscroll-behavior-x:contain}

/* 4. LES GRILLES PLUS ÉTROITES QUE LEUR PROPRE MINIMUM. `minmax(15rem,1fr)`
      déborde dès que le conteneur fait moins de 15rem — ce qui arrive dans le
      rail, et à 320 px si une marque élargit --sb-gap. `min(15rem,100%)` rend
      le minimum conditionnel : c'est l'idiome qui remplace la media query
      qu'on aurait sinon dû écrire. */
.sb-grid{grid-template-columns:repeat(auto-fit,minmax(min(15rem,100%),1fr))}
.sb-figures{grid-template-columns:repeat(auto-fit,minmax(min(9rem,100%),1fr))}

/* 5. LE PLANCHER. Rien de tout ceci ne vaut si la page elle-même impose une
      largeur. Aucun overflow-x:hidden sur body ici : il MASQUE le débordement
      au lieu de le corriger, et canaux/check-mobile.mjs le signale comme tel. */
html{-webkit-text-size-adjust:100%}


/* ══ Navigation ═══════════════════════════════════════════════════════════════
   Généré par lib/navigation.php. Le panneau mobile est un élément `popover` :
   il vit dans la couche supérieure du navigateur, et le bouton le bascule sans
   script. Tout ce qui suit n'est que sa mise en forme.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Le bouton n'apparaît que dans le bloc @supports/@media plus bas. Un
   navigateur qui ignore l'un ou l'autre n'affiche donc PAS de bouton mort à
   côté d'un menu qu'il rend déjà en clair. */
.sb-nav-bascule{display:none}

/* Même raison que le bouton ci-dessus, et une de plus : ce lien est le DOUBLON
   de celui de la barre. Caché par défaut, il n'est montré que dans le bloc
   @supports/@media — donc jamais en même temps que l'original, et jamais du
   tout sur un navigateur sans popover natif, où le menu reste en clair.
   `.sb-header .sb-nav-cta` et non `.sb-nav-cta` : `.sb-btn` de patterns.css
   pose `display:inline-block` à poids égal (0,1,0), et un doublon visible sur
   un simple accident d'ordre de concaténation serait le pire des défauts. */
.sb-header .sb-nav-cta{display:none}

/* ── Au-dessus du point de rupture : une barre de liens ordinaire ───────────
   L'attribut `popover` reste sur le <nav> à toutes les largeurs — on ne peut
   pas le retirer en CSS. Or sa feuille navigateur pose, sur TOUT porteur de
   l'attribut, `display:none` tant qu'il n'est pas ouvert, plus position:fixed,
   inset:0, une bordure, un padding et un fond opaque. Ces déclarations la
   défont : une feuille d'auteur l'emporte toujours sur celle du navigateur,
   quelle que soit la spécificité. Elles reprennent les propriétés PHYSIQUES de
   cette feuille, une par une, pour qu'on puisse les relire côte à côte. */
@media (width >= 48rem){
  .sb-nav{display:block;position:static;inset:auto;width:auto;height:auto;
    max-width:none;max-height:none;margin:0;border:0;padding:0;
    overflow:visible;background:none;color:inherit}
}

/* ── En dessous : un panneau ────────────────────────────────────────────────
   Tout est sous `@supports selector(:popover-open)`. Sans popover natif, rien
   de ce bloc ne s'applique : ni bouton, ni panneau — le menu reste la liste en
   clair. C'est le repli, pas une panne. */
@supports selector(:popover-open){
@media (width < 48rem){

  /* ── le bouton ─────────────────────────────────────────────────────────── */

  .sb-nav-bascule{
    display:inline-flex;align-items:center;gap:.6rem;
    margin-inline-start:auto;              /* au bord, loin du logo */
    min-block-size:2.75rem;                /* 44px : la cible tactile minimale */
    padding:.5rem .75rem;
    border:0;border-radius:var(--sb-radius,.25rem);
    background:transparent;color:inherit;cursor:pointer;
    font-family:inherit;font-size:1rem;font-weight:600;line-height:1;
    -webkit-tap-highlight-color:transparent;
  }

  /* Le pictogramme : une barre réelle et deux pseudo-éléments. Il est
     aria-hidden — le nom accessible du bouton vient du mot à côté, pas de lui. */
  .sb-nav-bascule__barres{
    position:relative;display:block;inline-size:1.25rem;block-size:2px;
    background:currentColor;border-radius:2px;
    transition:background var(--sb-motion,.2s) ease;
  }
  .sb-nav-bascule__barres::before,
  .sb-nav-bascule__barres::after{
    content:"";position:absolute;inset-inline:0;block-size:2px;
    background:currentColor;border-radius:2px;
    transition:translate var(--sb-motion,.2s) ease, rotate var(--sb-motion,.2s) ease;
  }
  .sb-nav-bascule__barres::before{translate:0 -.375rem}
  .sb-nav-bascule__barres::after{translate:0 .375rem}

  /* Ouvert, les trois barres deviennent une croix. L'état vient de
     `:popover-open`, jamais d'une classe posée par le script : le dessin ne
     PEUT pas mentir sur l'état réel du panneau, même sans script. */
  .sb-header:has(.sb-nav:popover-open) .sb-nav-bascule__barres{background:transparent}
  .sb-header:has(.sb-nav:popover-open) .sb-nav-bascule__barres::before{translate:0 0;rotate:45deg}
  .sb-header:has(.sb-nav:popover-open) .sb-nav-bascule__barres::after{translate:0 0;rotate:-45deg}

  /* ── le panneau ────────────────────────────────────────────────────────── */

  /* Le bandeau, PENDANT l'ouverture. Une seule règle, deux problèmes distincts.
     Sa spécificité — 0,3,0, `:has()` prenant celle de son argument le plus
     spécifique — dépasse le `.sb-header` d'un habillage de marque (0,1,0) :
     elle gagne quel que soit l'ordre de concaténation, et sans !important, donc
     sans rien retirer à la marque en dehors de l'ouverture.

     1. LE PIÈGE, et il n'est pas théorique : brands/yhb/style.css pose
        `backdrop-filter:saturate(140%) blur(14px)` sur .sb-header. Un ancêtre
        portant backdrop-filter, filter, transform, perspective ou will-change
        devient le bloc conteneur de ses descendants en position:fixed — le
        panneau se replierait dans la hauteur du bandeau au lieu de couvrir
        l'écran. La vraie parade est ailleurs : le panneau est un `popover`,
        donc rendu dans la COUCHE SUPÉRIEURE, où la position se résout sur la
        fenêtre et non sur un ancêtre. Mesuré : en retirant ces déclarations et
        en forçant le flou en !important, Chromium ne piège pas le panneau. Elles
        restent quand même, pour les moteurs qu'on n'a pas pu essayer ici et
        pour le `transform` qu'une marque ajoutera un jour. Coût du filet :
        aucun flou pendant les deux secondes où un menu est ouvert.
        Une marque qui en veut un le pose sur `.sb-nav::backdrop` — un
        pseudo-élément n'est l'ancêtre de personne.

     2. Le panneau part de la fenêtre, le bandeau suivait le défilement : sur un
        bandeau non collant, les deux se décollaient dès qu'on ouvrait le menu
        ailleurs qu'en haut de page — un trou par lequel on voyait le contenu, et
        un bouton de fermeture à moitié sorti de l'écran. Épingler le bandeau
        rend la géométrie indépendante du défilement ET du choix de la marque.
        `inset-inline:0` n'est pas décoratif : un élément en position:fixed sans
        calage horizontal se rétracte sur son contenu. Le saut de contenu que
        provoque la sortie du flux ne se voit pas — bandeau et panneau couvrent
        ensemble toute la hauteur, et la page ne défile plus. */
  .sb-header:has(.sb-nav:popover-open){
    position:fixed;inset-block-start:0;inset-inline:0;
    backdrop-filter:none;-webkit-backdrop-filter:none;
    filter:none;transform:none;perspective:none;will-change:auto;
  }

  .sb-nav{
    position:fixed;
    /* Pile sous le bandeau, jusqu'en bas. Le bandeau reste donc visible et
       cliquable : c'est lui qui porte le bouton de fermeture. La hauteur est
       MESURÉE par le script ; la valeur de repli sert quand il ne tourne pas. */
    inset:var(--sb-nav-bandeau,5.25rem) 0 0;
    width:auto;height:auto;max-width:none;max-height:none;margin:0;border:0;
    border-block-start:1px solid color-mix(in srgb,currentColor 14%,transparent);
    padding:1.25rem var(--sb-gap) 2.5rem;
    /* Un panneau doit être OPAQUE : on ne lit pas un menu au travers de la page.
       sb_nav_verifier() alerte si le token n'est pas une couleur opaque. */
    background:var(--sb-ink-bg);color:var(--sb-ink-fg);
    overflow-y:auto;
    /* Empêche le défilement de se propager à la page derrière, sans script. */
    overscroll-behavior:contain;
  }

  /* Le panneau reçoit le focus à l'ouverture, pour que le lecteur d'écran
     annonce la région. Il n'est jamais atteint au clavier (tabindex="-1") :
     un liseré autour de tout l'écran n'apprendrait rien à personne. */
  .sb-nav:focus{outline:none}

  /* La page ne défile plus derrière le panneau ouvert. Sans script, ni classe :
     l'état est lu là où il vit. */
  html:has(.sb-nav:popover-open){overflow:hidden}

  /* ── la liste ──────────────────────────────────────────────────────────── */

  /* Ces règles sont volontairement scopées sous `:popover-open`, et ce n'est pas
     de la décoration. patterns.css pose déjà `.sb-header ul{display:flex}`, et
     un habillage de marque va plus loin (brands/yhb : `.sb-header nav ul` en
     flex, `.sb-header nav a` en 14,5px). Une liste en colonne à cibles de 44px
     doit peser plus lourd que ces règles-là. `.sb-nav:popover-open .sb-nav__liste`
     vaut 0,3,0 contre 0,1,2 : elle gagne quel que soit l'ordre de concaténation,
     et sans !important — que la marque ne pourrait plus défaire. */
  .sb-nav:popover-open .sb-nav__liste{
    display:grid;gap:.25rem;margin:0;padding:0;list-style:none;
  }
  /* Les marges des entrées sont remises à zéro. Une barre horizontale se règle
     souvent à coups de marge automatique — brands/yhb pousse son dernier lien à
     droite avec `margin-left:auto` — et la même déclaration, dans une colonne,
     envoie l'entrée se coller au bord opposé. Ce n'est pas la mise en forme de
     la marque qu'on défait, c'est une consigne écrite pour un autre axe. */
  .sb-nav:popover-open .sb-nav__liste > li{margin:0}
  .sb-nav:popover-open .sb-nav__liste a{
    display:flex;align-items:center;
    min-block-size:2.75rem;padding:.5rem 0 .5rem .75rem;
    border-inline-start:3px solid transparent;
    font-family:var(--sb-font-title);font-size:1.25rem;
    color:inherit;text-decoration:none;
  }
  /* La page courante se signale par la graisse et un filet, jamais par la
     couleur : l'accent de la marque est calibré sur fond CLAIR — le token
     --sb-accent-text en porte le nom — et virerait au contraste insuffisant
     sur le fond encre du panneau. Un filet, lui, n'est pas du texte. */
  .sb-nav:popover-open .sb-nav__liste .current-menu-item > a,
  .sb-nav:popover-open .sb-nav__liste a[aria-current="page"]{
    border-inline-start-color:var(--sb-accent);font-weight:700;
  }

  /* ── L'APPEL À L'ACTION, EN TÊTE DU PANNEAU ───────────────────────────────
     Demandé le 20.08 : « en responsive le bouton premier échange doit être dans
     le menu », comme yanhaltey.com. Relevé sur la référence à 375 px : 54 px de
     haut, pleine largeur, 18 px avant la première entrée, et un filet entre.

     ── LES DEUX RÈGLES SONT UN COUPLE, NE PAS EN APPLIQUER UNE SEULE ──
     La première montre le doublon, la seconde retire l'original. Sans la
     seconde, le bouton de la barre reste MESURABLEMENT là — à (190, 84) sur un
     écran de 375, sous le filet du bandeau et par-dessus le titre du héros,
     parce que `patterns.css` fait passer `.sb-header .sb-wrap` à la ligne
     pendant que la feuille de marque en fige la hauteur à 68 px. Il restait
     aussi dans le parcours clavier, EN DERNIER, derrière un panneau opaque —
     WCAG 2.4.11, un arrêt de focus sur un lien invisible.

     AUCUNE COULEUR ÉCRITE ICI. Le fond vient de `--sb-accent-fond`, avec
     `--sb-accent` en repli pour une marque qui n'en déclare pas ; le texte de
     `--sb-accent-fg`, le jeton fait pour ce cas. Aucune contrepartie `.sb-ink`
     n'est nécessaire : le bouton porte son propre fond, il ne suppose donc pas
     la tonalité du panneau. Le filet, lui, part de `currentColor` — l'encre du
     panneau — et suit la tonalité tout seul. */
  .sb-nav:popover-open .sb-nav-cta{
    display:flex;align-items:center;justify-content:center;gap:.625rem;
    inline-size:100%;
    /* 3.375rem = 54 px, la mesure de la référence, et bien au-dessus des 44 px
       de cible tactile. */
    min-block-size:3.375rem;
    margin:0 0 1.125rem;
    padding:.875rem 1.5rem;
    border-radius:999px;
    background:var(--sb-accent-fond,var(--sb-accent));color:var(--sb-accent-fg);
    box-shadow:0 8px 24px -12px color-mix(in srgb,var(--sb-accent) 70%,transparent);
    font-family:inherit;font-size:.9375rem;font-weight:600;line-height:1.5;
    text-align:center;text-decoration:none;
  }
  /* Le filet qui sépare l'appel des entrées. Sélecteur ADJACENT et non une
     bordure sur le bouton : la marge de 18 px vit entre les deux, un filet posé
     sur le bouton lui collerait à la pastille. `wp_nav_menu` sort le <ul> sans
     conteneur — `'container' => false`, plus bas — donc les deux sont voisins. */
  .sb-nav:popover-open .sb-nav-cta + .sb-nav__liste{
    border-block-start:1px solid color-mix(in srgb,currentColor 14%,transparent);
    padding-block-start:.75rem;
  }

  /* ── ET LA BARRE S'EN SÉPARE ──────────────────────────────────────────────
     (0,3,0) : au-dessus du `.sb-header .sb-entete-cta` (0,2,0) qu'écrit la
     marque, donc sans dépendre de l'ordre de concaténation et sans !important.
     `display:none` et pas un masquage visuel : il faut que ce lien sorte AUSSI
     du parcours de tabulation et de l'arbre d'accessibilité, sinon le même nom
     et la même adresse sont annoncés deux fois. */
  .sb-header .sb-entete-outils .sb-entete-cta{display:none}
  /* Quand l'appel est le SEUL outil déclaré — c'est le cas de yhb, `langue` ne
     rend rien sans Polylang et `tonalite` est à false — la barre garderait un
     conteneur vide qui consomme quand même une gouttière de flex. Un sélecteur
     de langue ou un bouton de tonalité présents gardent leur place : la règle
     ne replie que le conteneur devenu inutile. */
  .sb-header .sb-entete-outils:not(:has(> :not(.sb-entete-cta))){display:none}
}
}
/* ══ En-tête de marque — GÉNÉRÉ par lib/entete.php, ne pas éditer ════════════
   Les outils vivent à DROITE du menu, dans l'ordre où l'œil les cherche :
   langue, présentation, action. */
.sb-entete-outils{display:flex;align-items:center;gap:.75rem;margin-inline-start:auto}

.sb-langues ul{display:flex;gap:.5rem;list-style:none;margin:0;padding:0}
.sb-langues a{font-size:.8125rem;letter-spacing:.06em;text-transform:uppercase;
  text-decoration:none;color:inherit;opacity:.65;padding:.25rem}
.sb-langues a:hover,.sb-langues a[aria-current]{opacity:1}

/* 2.25rem = 36 px : au-dessus des 24 px de WCAG 2.2 SC 2.5.8, et c'est une
   cible qu'on vise au pouce en haut d'écran — la plus difficile à atteindre. */
.sb-tonalite{display:inline-flex;align-items:center;justify-content:center;
  width:2.25rem;height:2.25rem;padding:0;
  border:1px solid currentColor;border-radius:99px;background:none;color:inherit;
  cursor:pointer;opacity:.65}
/* Le libellé N'ENTRE PAS dans le bouton : il l'élargirait, et le bandeau est
   une barre de repères de taille stable. L'état est dit par l'infobulle native
   (`title`, posée par le script) et reste lu par les lecteurs d'écran — d'où
   le masquage visuel plutôt qu'un `display:none`, qui l'aurait retiré à tout
   le monde.

   L'icône, elle, porte déjà l'état à l'œil : disque mi-plein, vide, ou plein. */
.sb-tonalite__etat{position:absolute;width:1px;height:1px;overflow:hidden;
  clip-path:inset(50%);white-space:nowrap}
.sb-tonalite:hover,.sb-tonalite:focus-visible{opacity:1}
/* ── R81 · TROIS ICÔNES, ET LE SURVOL MONTRE LA SUIVANTE ──────────────────────
   Lune pour la nuit, soleil pour le jour, disque mi-plein pour le mixte.
   Demandé par Yan, icônes reprises de ses captures.

   Dessinées en `mask-image` et non en `<img>` ni en `<svg>` dans le HTML :
   le masque prend `currentColor`, donc l'icône suit la tonalité du bandeau
   sans qu'on ait à la recolorier à chaque présentation. Un SVG en dur
   redeviendrait invisible en mode jour, ce qui est précisément le défaut que
   cette feuille passe son temps à corriger ailleurs.

   Le survol montre l'état SUIVANT — un aperçu de ce que le clic va faire, pas
   un rappel de ce qu'on est. D'où `[data-suivant]:hover`, que le script tient
   à jour en même temps que `[data-etat]`. Le nom accessible, lui, annonce
   toujours l'action : voir le commentaire du script. */
.sb-tonalite__icone{width:1rem;height:1rem;
  background:currentColor;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-position:center;mask-position:center;
  -webkit-mask-size:contain;mask-size:contain;
  transition:opacity .12s linear}
@media (prefers-reduced-motion:reduce){.sb-tonalite__icone{transition:none}}

/* Les trois masques. `%3E` et non `>` : une URL de données ne tolère pas les
   chevrons nus dans une feuille servie en ligne. */
.sb-tonalite[data-etat="mixte"] .sb-tonalite__icone,
.sb-tonalite[data-suivant="mixte"]:hover .sb-tonalite__icone,
.sb-tonalite[data-suivant="mixte"]:focus-visible .sb-tonalite__icone{
  -webkit-mask-image:var(--sb-ico-mixte);mask-image:var(--sb-ico-mixte)}
.sb-tonalite[data-etat="sombre"] .sb-tonalite__icone,
.sb-tonalite[data-suivant="sombre"]:hover .sb-tonalite__icone,
.sb-tonalite[data-suivant="sombre"]:focus-visible .sb-tonalite__icone{
  -webkit-mask-image:var(--sb-ico-lune);mask-image:var(--sb-ico-lune)}
.sb-tonalite[data-etat="clair"] .sb-tonalite__icone,
.sb-tonalite[data-suivant="clair"]:hover .sb-tonalite__icone,
.sb-tonalite[data-suivant="clair"]:focus-visible .sb-tonalite__icone{
  -webkit-mask-image:var(--sb-ico-soleil);mask-image:var(--sb-ico-soleil)}

/* ⚠️ L'ORDRE COMPTE. Les règles de survol arrivent APRÈS celles de l'état
   courant dans chaque bloc, mais à spécificité égale entre blocs c'est le
   dernier qui gagne. Ces trois-ci rétablissent la priorité du survol quel que
   soit l'état de départ — sans elles, survoler en « clair » vers « mixte »
   laissait le soleil, parce que sa règle est écrite plus bas. */
.sb-tonalite[data-suivant="mixte"]:hover .sb-tonalite__icone{
  -webkit-mask-image:var(--sb-ico-mixte)!important;mask-image:var(--sb-ico-mixte)!important}
.sb-tonalite[data-suivant="lune"]:hover .sb-tonalite__icone,
.sb-tonalite[data-suivant="sombre"]:hover .sb-tonalite__icone{
  -webkit-mask-image:var(--sb-ico-lune)!important;mask-image:var(--sb-ico-lune)!important}
.sb-tonalite[data-suivant="clair"]:hover .sb-tonalite__icone{
  -webkit-mask-image:var(--sb-ico-soleil)!important;mask-image:var(--sb-ico-soleil)!important}

:root{
  --sb-ico-lune:url('data:image/svg+xml;utf8,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"%3E%3Cpath fill="black" d="M20.7 14.6A8.6 8.6 0 0 1 9.4 3.3a1 1 0 0 0-1.3-1.2 10.6 10.6 0 1 0 13.8 13.8 1 1 0 0 0-1.2-1.3z"/%3E%3C/svg%3E');
  --sb-ico-soleil:url('data:image/svg+xml;utf8,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"%3E%3Cg fill="none" stroke="black" stroke-width="1.9" stroke-linecap="round"%3E%3Ccircle cx="12" cy="12" r="4.1"/%3E%3Cpath d="M12 2.2v2.3M12 19.5v2.3M4.2 12H1.9M22.1 12h-2.3M6.5 6.5 4.9 4.9M19.1 19.1l-1.6-1.6M17.5 6.5l1.6-1.6M4.9 19.1l1.6-1.6"/%3E%3C/g%3E%3C/svg%3E');
  --sb-ico-mixte:url('data:image/svg+xml;utf8,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"%3E%3Ccircle cx="12" cy="12" r="9" fill="none" stroke="black" stroke-width="1.9"/%3E%3Cpath fill="black" d="M12 3.1a8.9 8.9 0 0 0 0 17.8z"/%3E%3C/svg%3E');
}

/* ── Les trois présentations ──────────────────────────────────────────────────
   « mixte » ne pose RIEN : c'est l'alternance par position que le compilateur
   produit déjà. Les deux autres la surchargent, et doivent donc peser plus
   lourd qu'une classe simple — d'où le sélecteur sur :root. */
/* On REMAPPE LES TOKENS, on ne surcharge pas les fonds — et c'est toute la
   différence. Surcharger `.sb-ink{background:…}` ne touchait que la section :
   ses ENFANTS gardaient leur couleur, et un bouton pensé pour un fond sombre
   devenait invisible sur fond clair. Constaté à l'écran.

   En remappant les variables, tout ce qui les consomme suit — boutons, filets,
   liens, ombres — sans qu'on ait à les énumérer. Ce qui ne suit pas, ce sont
   les couleurs écrites en dur dans une feuille de marque ; c'est une raison de
   plus de ne pas en écrire. */
:root[data-sb-presentation="clair"]{
  --sb-ink-bg:var(--sb-paper-bg);
  --sb-ink-fg:var(--sb-paper-fg);

  /* Le paragraphe ci-dessus prévenait que « ce qui ne suit pas, ce sont les
     couleurs écrites en dur dans une feuille de marque ». C'est arrivé : la
     feuille yhb définit `--yhb-ink`, un noir plus profond que l'encre commune,
     et l'emploie comme fond du bandeau, du pied et de `.sb-ink` — la classe de
     TOUTES les sections sombres — tout en prenant leur couleur de texte à
     `--sb-ink-fg`, qui, lui, est remappé ici.
     En mode jour, le texte passait donc au sombre sur un fond resté noir : les
     entrées de menu disparaissaient, seul le soulignement de la page courante
     restait. Le jeton de marque est neutralisé ici plutôt qu'aux cinq endroits
     qui le consomment ; il garde sa profondeur dans les deux autres tonalités. */
  --yhb-ink:var(--sb-paper-bg);

  /* Même raison que `--yhb-ink` ci-dessus : la bande d'appel de plan-accès
     prend son fond à une prune figée de la feuille de marque pendant que son
     titre et sa ligne de réassurance prennent leur couleur à `--sb-ink-fg`,
     remappé ici. En mode jour le texte passait au sombre sur un fond resté
     prune — 1,04:1 MESURÉ, invisible. Le crème est ce que sont déjà les sept
     autres bandes d'appel du site : on ne fait que la rejoindre. */
  --yhb-fond-appel:var(--sb-paper-bg);
}
:root[data-sb-presentation="sombre"]{
  --sb-paper-bg:var(--sb-ink-bg);
  --sb-paper-fg:var(--sb-ink-fg);
}
:root[data-sb-presentation="clair"] .sb-fond-etoile{display:none}

/* ── Fond décoratif ───────────────────────────────────────────────────────────
   `position:fixed` et non `absolute` : le décor ne défile pas avec la page,
   sinon il faudrait le dessiner sur toute la hauteur du document.
   Le dégradé est posé en CSS et reste seul quand le script ne tourne pas —
   c'est le repli, et il se suffit. */
.sb-fond-etoile{position:fixed;inset:0;width:100%;height:100%;z-index:-1;
  pointer-events:none;
  background:radial-gradient(ellipse 80% 60% at 50% 0%,
    color-mix(in srgb,var(--sb-accent) 18%,transparent),transparent 70%)}

/* ── Laisser VOIR le fond ─────────────────────────────────────────────────────
   Le canvas est en z-index:-1, donc derrière tout. Les sections encre ayant un
   fond OPAQUE, il ne s'en voyait pas un pixel — le décor existait, dessiné, et
   invisible. Constaté au navigateur, pas déduit.

   La couleur encre remonte donc sur le <body>, et les sections encre
   deviennent transparentes : elles laissent passer le décor tout en gardant
   leur couleur de texte. Les sections crème, elles, restent OPAQUES — c'est
   ce qui fait l'alternance, et un décor visible au travers la brouillerait. */
body:has(.sb-fond-etoile){background:var(--sb-ink-bg);color:var(--sb-ink-fg)}
body:has(.sb-fond-etoile) .sb-ink{background:transparent}
body:has(.sb-fond-etoile) .sb-paper{background:var(--sb-paper-bg)}

/* En présentation « sombre », tout devient encre : le décor doit se voir
   partout, y compris sous ce qui était crème. */
:root[data-sb-presentation="sombre"] body:has(.sb-fond-etoile) .sb-paper{background:transparent}

@media (prefers-reduced-motion:reduce){
  /* Le canvas reste, vide : seul le dégradé s'affiche. */
  .sb-fond-etoile{animation:none}
}
/* ══ Pied de page — GÉNÉRÉ par lib/pied.php, ne pas éditer ═══════════════════
   Les nombres viennent du prototype ; ceux qui en diffèrent sont annotés, et
   chacun l'est pour une raison mesurable. */

/* Ce que patterns.css impose au pied d'une seule ligne, et qui doit tomber. */
.sb-footer .sb-wrap{display:block}

/* ⚠️ `.sb-footer ul{display:block}` A ÉTÉ RETIRÉ D'ICI.
   C'était l'antidote d'un poison qui n'existe plus : patterns.css portait
   `.sb-header ul,.sb-footer ul{display:flex}`, et cette ligne le neutralisait.
   Le `.sb-footer ul` a été retiré de patterns.css — l'antidote est alors devenu
   le problème. Avec sa spécificité (0,1,1) il battait les règles nommées du
   pied, toutes en (0,1,0) : `.sb-pied__reseaux{display:flex}` retombait en
   block et les icônes s'empilaient, `.sb-pied__liste` ne pouvait plus rien
   décider. Mesuré à l'écran : liens en ligne, icônes empilées — l'inverse exact
   des deux règles, sans qu'aucune soit fausse.

   La leçon vaut au-delà du pied : une règle en nappe et sa contre-règle forment
   une paire. On ne retire jamais l'une sans l'autre. */

.sb-footer{padding-block:clamp(2.5rem,6vw,4.375rem) 2.5rem}

/* 1.7fr pour la marque : la baseline fait 34ch et le formulaire deux champs.
   `auto-fit` plutôt qu'un nombre de colonnes : le module ne sait pas combien il
   y en aura, et une marque à deux colonnes ne doit pas hériter d'un trou. */
/* ⚠️ PAS de `1.7fr repeat(auto-fit,…)`.
   `auto-fit` et `auto-fill` ne peuvent pas cohabiter avec une piste fixe dans
   la même déclaration : le navigateur juge TOUTE la déclaration invalide et
   l'ignore. La grille retombait alors sur une colonne unique, et le pied
   s'empilait — pris pour « un pied en double ».

   `grid-auto-flow:column` fait le travail sans cette restriction, et il
   s'adapte au nombre de colonnes déclarées, quel qu'il soit. */
.sb-pied__grille{display:grid;gap:clamp(2rem,4vw,3rem);align-items:start;
  grid-template-columns:minmax(0,1.7fr);
  grid-auto-columns:minmax(9rem,1fr);grid-auto-flow:column}
@media (max-width:56.25rem){
  /* Sous 900 px, on repasse en flux normal : les colonnes s'empilent, et
     c'est voulu. `auto-flow:column` est neutralisé en le remettant à `row`. */
  .sb-pied__grille{grid-template-columns:repeat(auto-fit,minmax(11rem,1fr));
    grid-auto-flow:row}
  .sb-pied__marque{grid-column:1/-1}
}

/* ── Colonne de marque ────────────────────────────────────────────────────── */
/* 34ch : la mesure de ligne du prototype. Au-delà, l'œil perd la ligne suivante. */
.sb-pied__baseline{max-width:34ch;margin:0;font-size:.9375rem;line-height:1.6;opacity:.75}
/* Sur écran tactile, la baseline repasse à 16 px : c'est du texte courant, et
   15 px lu à bout de bras sur un téléphone n'est pas la même chose que 15 px
   lu à 50 cm sur un écran. Le canal multi-largeurs le relevait à chaque page. */
@media (pointer:coarse){ .sb-pied__baseline{font-size:1rem} }

.sb-pied__reseaux{display:flex;gap:.75rem;margin:1.25rem 0 0;padding:0;list-style:none}
/* 2.5rem = 40 px, comme le prototype, et très au-dessus des 24 px de
   WCAG 2.2 SC 2.5.8 — une icône sans texte n'a aucune marge d'erreur au doigt. */
.sb-pied__reseaux a{display:grid;place-items:center;width:2.5rem;height:2.5rem;
  border-radius:50%;border:1px solid color-mix(in srgb,currentColor 22%,transparent);
  color:inherit;opacity:.85;text-decoration:none;
  transition:opacity var(--sb-motion,.2s),border-color var(--sb-motion,.2s),transform var(--sb-motion,.2s)}
.sb-pied__reseaux a:hover,.sb-pied__reseaux a:focus-visible{opacity:1;
  border-color:var(--sb-accent);transform:translateY(-2px);text-decoration:none}
.sb-pied__reseaux svg{width:1.125rem;height:1.125rem}
@media (prefers-reduced-motion:reduce){
  .sb-pied__reseaux a{transition:none}
  .sb-pied__reseaux a:hover,.sb-pied__reseaux a:focus-visible{transform:none}
}

/* ── Colonnes de menus ────────────────────────────────────────────────────── */
/* .75rem = 12 px PILE : le plancher du projet. Le prototype écrit 11 px, ce qui
   passe sous la limite — c'est le seul écart délibéré avec lui, et il n'est pas
   négociable. L'interlettrage compense la perte d'empattement optique. */
.sb-pied__titre{margin:0 0 1rem;font-family:var(--sb-font-body);font-size:.75rem;
  font-weight:500;letter-spacing:.14em;text-transform:uppercase;opacity:.7}

.sb-pied__liste{display:block;margin:0;padding:0;list-style:none}
.sb-pied__liste li{margin:0}
/* .375rem de rembourrage vertical : 14 px × 1.4 + 12 px ≈ 32 px de haut. La
   cible dépasse les 24 px sans qu'on ait à poser une hauteur, qui casserait au
   passage à la ligne d'un libellé long. */
.sb-pied__liste a{display:block;padding-block:.375rem;font-size:.875rem;
  color:inherit;text-decoration:none;opacity:.8;transition:opacity var(--sb-motion,.2s)}
/* PAS DE TRAIT AU SURVOL — demandé le 20.08. Le relevé d'origine tient
   toujours : `--sb-accent` sur le fond encre ne fait pas les 4,5:1, et
   `--sb-accent-text` est calibré pour le CRÈME. On ne peut donc pas simplement
   passer le lien à l'accent.
   L'opacité seule (0,8 → 1) serait un changement trop faible pour se remarquer.
   On la double d'un éclaircissement vers le blanc du fond encre : le mouvement
   se voit, et le texte ne perd pas de contraste — il en gagne. */
.sb-pied__liste a:hover,.sb-pied__liste a:focus-visible{
  opacity:1;color:color-mix(in oklab,currentColor 70%,#FFF)}
.sb-pied__liste .current-menu-item > a{opacity:1}

/* ── Inscription newsletter ───────────────────────────────────────────────── */
/* Fluent Forms charge sa feuille APRÈS celle du thème : à spécificité égale
   c'est elle qui gagne. On ne lui dispute pas le terrain, on lui PASSE les
   tokens par ses propres variables — même tactique que .sb-formulaire__zone. */
.sb-pied__newsletter{margin-top:1.125rem;max-width:32.5rem;
  --fluentform-primary:var(--sb-accent);
  --fluentform-border-radius:100px}
/* Clé introuvable, extension retirée : le gabarit rend un conteneur vide. Pas
   de gouttière, et la mention qui le suit disparaît avec lui — sinon
   « L'actualité de notre newsletter. » flotte sans rien à quoi se rattacher. */
.sb-pied__newsletter:empty{display:none}
.sb-pied__newsletter:empty + .sb-pied__mention{display:none}

.sb-pied__newsletter label,
.sb-pied__newsletter .ff-el-input--label label,
.sb-pied__newsletter .ff-el-help-message{color:inherit}
.sb-pied__newsletter .ff-el-help-message{opacity:.75}
/* 1rem = 16 px, et c'est un minimum matériel : sous 16 px, Safari iOS zoome de
   force à la mise au point du champ et décadre la page. Fluent Forms descend
   plus bas par défaut, d'où la reprise. */
/* ⚠️ Le sélecteur vise le TYPE de champ, pas la classe de l'extension.
   `.ff-el-form-control` est la classe attendue, et elle n'est pas toujours là :
   mesuré sur ce site, les champs de la newsletter sortent avec `class=""` et
   retombent donc à 13,3 px — la valeur par défaut du navigateur, sous le seuil
   des 16 px, exactement le défaut que la règle prétendait corriger.
   Miser sur la classe d'une extension, c'est miser sur une version. Le type du
   champ, lui, ne change pas : c'est du HTML, pas du Fluent Forms. */
.sb-pied__newsletter .fluentform .ff-el-form-control,
.sb-pied__newsletter .fluentform input:is([type="email"],[type="text"],[type="tel"],[type="url"]),
.sb-pied__newsletter .fluentform textarea,
.sb-pied__newsletter .fluentform select{font-size:1rem;
  padding:.8125rem 1.125rem;border-radius:100px;
  border:1px solid color-mix(in srgb,currentColor 22%,transparent);
  background:transparent;color:inherit}
.sb-pied__newsletter .fluentform .ff-el-form-control::placeholder{color:inherit;opacity:.6}
.sb-pied__newsletter .fluentform .ff-btn-submit{font-size:1rem;padding:.8125rem 1.375rem;
  border:0;border-radius:100px;background:var(--sb-accent)}
/* Fluent Forms écrit la couleur du texte du bouton EN DUR, dans un <style> posé
   au fil de la page : aucune variable à lui passer. Seul !important du module,
   et il évite un blanc littéral sur un accent clair. */
.sb-pied__newsletter .ff-btn-submit{color:var(--sb-accent-fg)!important}
/* Le libellé du bouton ne se coupe pas : « S'inscrire » sur deux lignes
   désaligne la ligne entière, et la cible perd sa hauteur. */
.sb-pied__newsletter .ff-btn-submit{white-space:nowrap}

/* ⚠️ `.ff-t-cell` A ÉTÉ RETIRÉ D'ICI, et il faut dire pourquoi.
   Ce bloc portait `@media (min-width:26rem){ … .ff-t-cell{padding-inline:0} }`
   sous le commentaire « champ et bouton sur une ligne ». La règle ne pouvait pas
   le faire : `.ff-t-cell` est la classe des colonnes d'un CONTENEUR Fluent Forms,
   et un formulaire d'un seul champ n'en produit aucune. Relevé sur le markup
   servi : zéro occurrence de `ff-t-cell` dans la page, une seule dans toute la
   feuille publique de l'extension. Le commentaire promettait donc une mise en
   page que le sélecteur ne touchait jamais — champ et bouton restaient empilés.

   Ce que Fluent Forms 6.2.12 rend réellement pour un champ + un envoi :

       div.fluentform > form.frm-fluent-form > fieldset
           ├ legend.ff_screen_reader_title      (masquée par l'extension)
           ├ input[type=hidden] × 3             (hors flux)
           ├ div.ff-el-group                    → le champ
           └ div.ff-el-group.ff_submit_btn_wrapper → le bouton

   C'est donc le `fieldset` qu'il faut mettre en ligne, et rien d'autre. */
@media (min-width:26rem){
  .sb-pied__newsletter .frm-fluent-form > fieldset{display:flex;flex-wrap:wrap;
    align-items:flex-end;gap:.625rem}
  /* Par DÉFAUT, tout enfant direct prend sa propre ligne. C'est l'inverse de ce
     qu'on écrirait spontanément, et c'est voulu : Fluent Forms injecte au fil de
     la vie du formulaire des blocs qu'on ne contrôle pas (pile d'erreurs
     `.ff-errors-in-stack`, message de succès, case de consentement). Sans cette
     règle ils s'inséreraient DANS la ligne du champ et la casseraient à la
     première erreur de saisie — c'est-à-dire au pire moment. */
  .sb-pied__newsletter .frm-fluent-form > fieldset > *{flex:1 1 100%}
  /* 11rem de base : sous cette largeur le champ ne peut plus montrer une adresse
     entière, et il vaut mieux qu'il passe à la ligne que qu'il la tronque. */
  .sb-pied__newsletter .frm-fluent-form > fieldset > .ff-el-group{flex:1 1 11rem;
    margin-bottom:0}
  /* Après la règle précédente, jamais avant : le bouton porte AUSSI `.ff-el-group`
     (même spécificité, c'est l'ordre qui tranche). Il ne s'étire pas — un bouton
     de la largeur du pied ne ressemble plus à un bouton. */
  .sb-pied__newsletter .frm-fluent-form > fieldset > .ff_submit_btn_wrapper{flex:0 0 auto}
}
/* Sous 26rem (416 px), aucune de ces règles ne s'applique : on laisse Fluent
   Forms empiler champ puis bouton, chacun sur toute la laisse. C'est le repli du
   prototype, et sur un téléphone c'est aussi la bonne réponse — deux cibles
   pleine largeur valent mieux que deux cibles à l'étroit. */

/* La note de l'éditeur, rendue par `sb_formulaire_avis()` quand la clé ne se
   résout pas. Elle n'est stylée NULLE PART ailleurs : le CSS du pattern
   formulaire (.sb-formulaire__avis) n'a jamais été reporté dans patterns.css —
   vérifié, zéro occurrence. Sur le fond encre du pied, sans cette règle, elle
   sort en corps de texte plein, plus voyante que le pied lui-même. */
.sb-pied__newsletter .sb-formulaire__avis{margin:0;font-size:.8125rem;
  font-style:italic;opacity:.8;padding:.75em 1em;
  border:1px dashed color-mix(in srgb,currentColor 40%,transparent);
  border-radius:.5rem}

/* .75rem = 12 px, le plancher, comme les titres de colonne. */
.sb-pied__mention{margin:.75rem 0 0;font-size:.75rem;letter-spacing:.04em;opacity:.7}

/* ── Barre du bas ─────────────────────────────────────────────────────────── */
/* Le copyright à GAUCHE, tout le reste groupé à DROITE.
   `space-between` répartissait les trois blocs à intervalles égaux : le
   copyright collait à gauche, les mentions flottaient au milieu et « Cookies »
   partait seul à l'autre bout — trois îlots au lieu de deux groupes.
   `flex-start` + une marge automatique sur le PREMIER bloc qui suit le
   copyright suffisent : tout ce qui vient après le suit, sans avoir à les
   envelopper dans un conteneur de plus. */
.sb-pied__bas{display:flex;flex-wrap:wrap;align-items:center;
  justify-content:flex-start;gap:.75rem 1.5rem;
  margin-top:3rem;padding-top:1.375rem;
  border-top:1px solid color-mix(in srgb,currentColor 18%,transparent);
  font-size:.8125rem;opacity:.75}
.sb-pied__copyright{margin:0}
.sb-pied__copyright + *{margin-inline-start:auto}

.sb-pied__legal ul,.sb-pied__legal-liste{display:flex;flex-wrap:wrap;
  align-items:center;gap:.25rem 1.125rem;margin:0;padding:0;list-style:none}
/* Rembourrage vertical : la barre du bas est en 13 px, et sans lui les liens
   légaux tomberaient à 18 px de haut — sous les 24 px exigés. */
.sb-pied__legal a{display:inline-block;padding-block:.3125rem;
  color:inherit;text-decoration:none}
.sb-pied__legal a:hover,.sb-pied__legal a:focus-visible{
  color:color-mix(in oklab,currentColor 70%,#FFF)}

/* ⚠️ `.sb-pied__bas > :last-child{margin-inline-start:auto}` A ÉTÉ RETIRÉ.
   Deux marges automatiques dans un même flex se partagent l'espace libre : la
   première poussait les mentions à droite, la seconde poussait « Cookies »
   encore plus loin, et le résultat était trois îlots au lieu de deux groupes.
   Mesuré : copyright à 78 px, mentions à 561, « Cookies » à 1154.
   Une seule marge automatique, posée sur le premier bloc qui suit le
   copyright, suffit — tout ce qui vient après la suit. */
@media (max-width:48rem){
  .sb-pied__copyright + *{margin-inline-start:0}

  /* ── Les liens légaux s'empilent, comme les colonnes au-dessus ────────────
     Sur téléphone, la barre du bas se lisait en deux temps : « CGU · Mentions
     légales · Données personnelles » serrés sur une ligne, puis « Cookies »
     retombé seul en dessous. Un orphelin — et une ligne trop dense juste
     au-dessus de lui.

     La cause n'est pas un manque de place, c'est la FORME : cette barre reste
     une rangée jusqu'en bas, alors que tout le reste du pied est déjà en
     colonnes à cette largeur. « Origine », « Trajectoire » s'empilent ;
     les mentions légales, non. Mesuré à 390 px : 3 liens sur une ligne, le
     quatrième 45 px plus bas, barre de 132,6 px de haut.

     Et « Cookies » n'est pas dans le menu légal — c'est une COMMANDE posée par
     lib/consentement.php, sœur du <nav> et non fille de sa liste (voir
     sb_pied_bas_html plus haut). Empiler la seule <ul> laisserait donc encore
     l'orphelin. Il faut mettre la BARRE en colonne, puis la liste, puis rendre
     les deux familles de liens identiques — c'est ce que font les trois règles
     qui suivent, dans cet ordre.

     `align-items` DOIT être repris : la barre le pose à `center` pour centrer
     verticalement une rangée ; en colonne, le même mot centre horizontalement,
     et les quatre liens partiraient au milieu.

     Les gouttières tombent à zéro et les liens prennent le `padding-block` de
     `.sb-pied__liste a` : les entrées se touchent, comme dans les colonnes,
     mais chacune garde une cible de 24 px et plus de haut — WCAG 2.2 SC 2.5.8.

     Le copyright, lui, garde 1rem sous lui, et ce nombre n'est pas choisi ici :
     c'est celui de `.sb-pied__titre`, l'écart entre un titre de colonne et sa
     liste. La barre a désormais exactement cette forme — un intitulé, puis des
     liens empilés —, elle doit donc en avoir le rythme. Repris à .75rem (la
     gouttière d'avant), le titulaire se lisait comme la première entrée de la
     liste : mesuré à 390 px, 27,5 px du copyright à « CGU » contre 34,4 px
     entre deux liens, soit un écart PLUS SERRÉ à l'endroit où la coupure doit
     se voir.

     ⚠️ `.sb-pied__bas .sb-pied__copyright` et non `.sb-pied__copyright` seul :
     une marque peut poser une nappe `.sb-footer p{margin:0}` — l'habillage yhb
     le fait (brands/yhb/style.css:712), et il pèse (0,1,1) contre les (0,1,0)
     du module. Écrite simplement, la règle était MUETTE : mesuré, la marge
     sortait à 0 px et le titulaire collait à « CGU ». La règle de base
     `.sb-pied__copyright{margin:0}` perd déjà le même duel depuis toujours,
     sans que personne le voie — les deux posent la même valeur.

     48rem, et pas une valeur de plus : c'est déjà la rupture de cette barre,
     trois lignes plus haut. Au-dessus, les quatre liens tiennent sur une ligne
     et y restent. */
  .sb-pied__bas{flex-direction:column;align-items:flex-start;gap:0}
  .sb-pied__bas .sb-pied__copyright{margin-bottom:1rem}
  .sb-pied__legal ul,.sb-pied__legal-liste{flex-direction:column;
    align-items:flex-start;gap:0}
  /* `.sb-pied__bas > a` vise le lien de consentement sans nommer sa classe :
     elle appartient à un autre module, et une marque peut poser là un lien
     qui n'en vient pas. */
  .sb-pied__legal a,.sb-pied__bas > a{display:block;padding-block:.375rem}
}
/* ══ Sélecteur de trajectoire — GÉNÉRÉ par lib/selecteur.php, ne pas éditer ══

   LE REPLI EST DANS CE FICHIER, pas dans le script. Les 28 réponses sont dans
   la page, masquées ; seule celle qui est visée s'affiche. Sans script, c'est
   `:target` qui décide ; avec, c'est la classe posée par le script — et la
   classe `est-pilote` désarme alors `:target`, sinon une ancre restée dans
   l'adresse ouvrirait une seconde réponse par-dessus. */

/* Le garde-fou du prototype. Un `display:` d'auteur bat l'attribut `hidden` du
   navigateur : c'est ainsi que la roulette d'attente tournait à l'arrivée sur la
   page, avant tout clic. Porté à la seule section — il ne s'agit pas d'imposer
   cette règle au reste du site. */
.sb-selecteur [hidden]{display:none!important}

/* ── L'accent, tenable dans les deux tonalités ──────────────────────────────
   La tonalité vient du RANG de la section : le pattern ne sait pas où il
   tombera, il doit tenir aux deux. Or patterns.css remappe `--sb-accent-text`
   sur l'accent brut en tonalité encre, et l'accent d'YHB y tombe à 2,8:1 sur le
   fond de section, 2,4:1 sur la plaque des marques — loin des 4,5:1 qu'exige un
   texte de 14 px. Mesuré à l'écran, pas déduit.

   On éclaircit donc vers l'ivoire, comme le fait déjà le pattern « chiffres ».
   Lui s'arrête à 75 % parce que ses chiffres sont énormes et n'ont besoin que
   de 3:1 ; ici ce sont des liens de 14 px. Le seuil mesuré est 70 % — on prend
   65 %, une marche en dessous, et la teinte reste celle de la marque. */
/* L'ACCENT DE LA MARQUE, PAS SON REPLI — aligné le 20.08 sur la section
   « portes ». `--sb-accent-text` est un accent assombri POUR LE TEXTE, utile
   là où l'accent brut ne passe pas le seuil. Sur le crème, il passe : #A02597
   mesure 5,81:1 et #B7006F 5,74. Le repli était donc moins conforme à la
   charte ET très légèrement moins lisible.
   Surtout, il faisait diverger deux pages : l'accueil peignait #A02597 et
   /trajectoires/ #B7006F pour exactement le même rôle. */
.sb-selecteur{--sb-selecteur-accent:var(--sb-accent)}
.sb-selecteur.sb-ink{--sb-selecteur-accent:color-mix(in oklab,var(--sb-accent) 65%,var(--sb-ink-fg))}

/* Le cadre du sélecteur ne porte plus que la marge : le choisisseur qu'il
   contient a la sienne, et deux marges empilées faisaient un trou de 3,75rem
   sous le chapô là où l'accueil en a 1,75. */
.sb-selecteur__cadre{margin-top:0}

/* ══ LA BASCULE — UNE SEULE, POUR LES DEUX PAGES ════════════════════════════
   L'habillage est celui de /trajectoires/, retenu le 21.08 : une gélule à filet
   fin, l'onglet actif rempli du DÉGRADÉ de la marque et son texte en blanc,
   l'onglet inactif transparent en encre douce. L'accueil peignait un aplat de
   `--sb-accent`, ce qui « donnait un bouton violet là où l'œil attend du
   magenta » — le défaut que `--sb-accent-fond` a justement été créé pour
   régler, et que tokens.css décrit comme « le FOND d'un bouton plein, et lui
   seul ».
   Les MOTS, eux, sont ceux de l'accueil : « Je veux… » / « Je suis… ».

   Des LIENS, pas des boutons : sans script un bouton ne fait rien, un lien mène
   à l'ancre et `:target` bascule l'axe. Ils restent focalisables et activables
   à l'Entrée. */
.sb-portes__bascule{display:inline-flex;flex-wrap:wrap;gap:.25rem;padding:.25rem;
  border:1px solid color-mix(in srgb,currentColor 22%,transparent);
  border-radius:99px;margin:0 0 1.5rem}
.sb-portes__onglet{display:inline-flex;align-items:center;justify-content:center;
  min-height:2.75rem;padding:.5rem 1.25rem;border-radius:99px;
  font:500 .9375rem/1.2 var(--sb-font-body);color:inherit;text-decoration:none;
  background:transparent;border:0;cursor:pointer;opacity:.7;
  transition:background .18s ease,color .18s ease,opacity .18s ease}
.sb-portes__onglet:hover{opacity:1}
/* 🔴 IL N'Y A PLUS DE RÈGLE D'ANNEAU ICI, ET C'EST LE CORRECTIF.
   Elle disait `outline:2px solid currentColor;outline-offset:2px`. Mesuré au
   focus clavier réel, par rastérisation : sur l'onglet ACTIF en présentation
   claire — l'état par défaut au chargement — l'encre courante est
   `--sb-accent-fg`, donc un ivoire #FAF8F3, posé à 2 px À L'EXTÉRIEUR de la
   gélule, c'est-à-dire sur le crème #F4F1EA de la section : 1,063:1. WCAG 1.4.11
   demande 3:1 pour un indicateur de focus ; celui-là n'existait pas.

   ⚠️ LA CAUSE EST DANS LE RACCOURCI, PAS DANS LA VALEUR. `outline:` réécrit
   AUSSI `outline-color`. Or la feuille de marque pose déjà
   `:focus-visible{outline-color:var(--yhb-accent-section,var(--sb-accent))}` —
   le jeton qui résout la matrice tonalité × présentation. À (0,2,0) contre
   (0,1,0), cette règle retirait les onglets du mécanisme que la marque a
   construit exprès pour ça. Écrire une couleur « mieux choisie » ici aurait
   reproduit la faute d'un cran plus haut : le bon geste est de rendre la main.

   Mesuré après retrait : actif 5,81:1 et inactif 4,22:1 en clair, 5,41:1 et
   3,78:1 en sombre — et l'anneau reprend les 3 px qu'il a partout ailleurs.

   `opacity:1` RESTE, et il ne concerne pas la couleur : l'onglet inactif vit à
   .7, ce qui affadissait l'anneau autant que le libellé. `:hover` remonte déjà
   à 1 — le focus n'avait aucune raison d'en faire moins. */
/* ⚠️ DEUX `[data-axe]` DANS LE SÉLECTEUR, ET C'EST POUR LE POIDS — compté deux
   fois avant de tomber juste. Les règles d'onglet ÉTEINT plus bas s'écrivent
   `.sb-portes__cadre[data-axe="…"] .sb-portes__onglet[data-axe="…"]`, soit
   (0,4,0), et posent `opacity:.7`. Une règle de focus en (0,2,0) puis en (0,4,0)
   a donc été lue puis annulée deux fois de suite — mesuré, l'anneau de l'onglet
   inactif restait à 4,64:1 sous WebKit au lieu de 5,36. (0,5,0) tranche. */
.sb-portes__cadre[data-axe] .sb-portes__onglet[data-axe]:focus-visible{opacity:1}
/* 🔴 B20 — LA PAIRE, ET NON DEUX FOIS LE MÊME AXE.
   Cette liste portait « intentions » DEUX FOIS ; le second sélecteur devait
   dire « publics ». Un renommage d'axe passé trop large avait recopié le
   premier à la place du second, et « Mon profil » se retrouvait donc SANS
   aucune règle d'onglet actif — sans qu'aucune règle n'échoue.

   MESURÉ (vrai clic Playwright, pas `elementFromPoint`) : après clic sur
   le second onglet, le cadre passe bien à `data-axe="publics"` et l'onglet
   reçoit bien `aria-current="true"` — le clic atteint donc sa cible — mais
   `background-image:none`, `font-weight:500`, seule l'opacité monte à 1.
   Les DEUX onglets sortent alors en texte ordinaire : c'est exactement la
   capture de Yan.

   Et ce n'était PAS un défaut de tonalité claire : `--sb-accent-fond` résout
   `linear-gradient(112deg,#7A2CA6,#A02597,#E6008A)` à l'identique en clair,
   en sombre et en mixte — mesuré sur `:root` dans les trois.

   ⚠️ `est-actif` EST DANS LA LISTE. Le serveur pose la classe sur le premier
   onglet et le script la déplace ; sans elle, le tout premier rendu — avant que
   le script ne tourne — n'allumait aucun onglet. La règle du cadre reste, elle,
   la source de vérité une fois le script en place. */
.sb-portes__cadre[data-axe="intentions"] .sb-portes__onglet[data-axe="intentions"],
.sb-portes__cadre[data-axe="publics"]    .sb-portes__onglet[data-axe="publics"],
.sb-portes__onglet.est-actif{
  background:var(--sb-accent-fond,var(--sb-accent));color:var(--sb-accent-fg);
  font-weight:600;opacity:1}
/* L'onglet ÉTEINT reprend le dessus quand le cadre désigne l'autre axe : sans
   cette règle, `est-actif` posée au rendu resterait allumée après la première
   bascule du script — deux onglets pleins à la fois. */
.sb-portes__cadre[data-axe="intentions"] .sb-portes__onglet[data-axe="publics"],
.sb-portes__cadre[data-axe="publics"]    .sb-portes__onglet[data-axe="intentions"]{
  background:transparent;color:inherit;font-weight:500;opacity:.7}

/* ── La grille de choix ─────────────────────────────────────────────────────
   HAUTEURS ÉGALES, SANS EXCEPTION : c'est la grille qui étire, pas le contenu
   qui décide. 3 colonnes pour les 12 profils, 4 pour les 16 besoins — la
   décision est ici, et elle se prend depuis l'axe, jamais depuis un décompte.

   🔴 LES DEUX BASCULES SONT CELLES DU PROTOTYPE, ET ELLES SONT EN PIXELS.

   L'état précédent partait d'une seule bascule à 60rem (960 px) et retombait,
   en dessous, sur `repeat(auto-fill,minmax(13rem,1fr))`. Relevé au balayage,
   côte à côte, sur les deux pages :

       viewport   prototype        WordPress (avant)
       1024 px    3 col / 4 col    3 col / 4 col     concordant
       1000 px    2 col / 2 col    3 col / 4 col     ← l'écart vu à l'écran
        960 px    2 col / 2 col    3 col / 4 col
        900 px    2 col / 2 col    3 col / 3 col
        768 px    2 col / 2 col    3 col / 3 col
        600 px    1 col / 1 col    2 col / 2 col
        480 px    1 col / 1 col    2 col / 2 col

   C'est là que naissaient les « 4 colonnes » : entre 960 et 1000 px, WordPress
   tenait encore les 4 colonnes des besoins quand le prototype était déjà retombé
   à 2. Et `auto-fill` n'a jamais reproduit la cascade du prototype : il compte
   des cases de 13rem, le prototype compte des paliers.

   On reprend donc les paliers tels quels : 1 colonne jusqu'à 600 px, 2 colonnes
   jusqu'à 1000 px, la grille pleine au-delà (3 profils / 4 besoins).

   EN PIXELS ET NON EN REM, délibérément : le prototype les exprime en pixels, et
   une conversion en rem déplacerait la bascule dès que la taille de base change
   — l'écart reviendrait sans que personne sache pourquoi. La transposition est
   légitime parce que le CONTENANT mesure la même largeur des deux côtés à chaque
   palier : 1124 px à 1280, 968 px à 1024, 944 px à 1000, 844 px à 900. Un seuil
   posé sur le viewport y vaut donc la même chose ici et là.

   La règle `auto-fill` reste en base : elle ne sert plus de repli responsive,
   seulement de filet si un axe apparaissait un jour sans règle à lui. */
.sb-selecteur__choix{display:grid;gap:.5rem;list-style:none;margin:0;padding:0;
  grid-template-columns:repeat(auto-fill,minmax(13rem,1fr))}
/* Même renommage trop large qu'aux onglets : ces six sélecteurs disaient tous
   « intentions ». Conséquence mesurée — la règle à 4 colonnes, écrite en
   second, écrasait celle à 3 pour le MÊME axe, et l'axe « publics » ne
   recevait plus aucune des trois : ses 12 profils retombaient sur le
   `auto-fill` de base, celui-là même dont le relevé ci-dessus dit qu'il n'a
   « jamais reproduit la cascade du prototype ».
   Vérifié au prototype : `.carte-liste[data-axe="publics"]` sert bien
   `368px 368px 368px` à 1280 px, soit 3 colonnes. */
   ⚠️ LES COMPTES DE COLONNES NE SONT PLUS ICI. Ils sont posés une seule fois,
   sur `.sb-portes__mots[data-axe]`, dans le bloc du choisisseur plus bas — le
   même <ul> porte les deux classes, et les deux pages servent le même. Écrits
   ici EN PLUS, ils gagnaient par leur spécificité (0,3,0 contre 0,2,0) et
   rendaient les règles du choisisseur inopérantes sans que rien ne le dise. */
.sb-selecteur__choix > li{display:flex}

.sb-selecteur__opt{display:flex;align-items:center;gap:.6875rem;width:100%;
  min-height:3.25rem;padding:.6875rem .875rem;
  /* La tuile n'avait aucun fond : elle se confondait avec la page en mode clair.
     Repli = transparent, donc une marque qui ne déclare pas --sb-plaque garde
     exactement le rendu d'avant. */
  background:var(--sb-plaque,transparent);
  border:1px solid var(--sb-plaque-bord,color-mix(in srgb,currentColor 18%,transparent));
  border-radius:var(--sb-radius,.25rem);
  font-size:.9375rem;font-weight:500;line-height:1.35;
  color:inherit;text-decoration:none}
.sb-selecteur__opt:hover{border-color:var(--sb-selecteur-accent)}
.sb-selecteur__opt.est-choisi,
.sb-selecteur__opt[aria-current]{border-color:var(--sb-selecteur-accent);
  color:var(--sb-selecteur-accent);font-weight:600}

.sb-selecteur__ic{display:grid;place-items:center;flex:0 0 1.8125rem;
  width:1.8125rem;height:1.8125rem;border-radius:50%;
  background:color-mix(in srgb,currentColor 10%,transparent)}
.sb-selecteur__ic svg{width:1rem;height:1rem;fill:none;stroke:currentColor;
  stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
.sb-selecteur__l{flex:1;min-width:0}

/* ── Les réponses ───────────────────────────────────────────────────────────
   MASQUÉES PAR DÉFAUT. C'est toute la différence entre un repli conçu et une
   page qui déballe : aucune marque ne s'affiche avant un choix. */
/* Le TITRE est masqué en propre, et pas seulement par son conteneur. Ce n'est
   pas une précaution de style : `getComputedStyle` interrogé sur un titre rend
   « display:block » même quand un parent est masqué, et tout relevé qui compte
   les titres visibles élément par élément — celui du canal structure en est un —
   verrait ici 28 titres pour un seul affiché. La règle rend la mesure vraie. */
.sb-reponse,.sb-reponse .sb-reponse__t{display:none}
.sb-reponse{margin-top:2rem}
.sb-selecteur__cadre:not(.est-pilote) .sb-reponse:target,
.sb-selecteur__cadre:not(.est-pilote) .sb-reponse:target .sb-reponse__t{display:block}
.sb-selecteur__cadre.est-pilote .sb-reponse.est-ouverte,
.sb-selecteur__cadre.est-pilote .sb-reponse.est-ouverte .sb-reponse__t{display:block}
.sb-reponse__t{margin-bottom:.35em}
.sb-reponse__t:focus-visible{outline:3px solid var(--sb-accent);outline-offset:4px}
/* Et il s'éteint quand le geste était un doigt ou une souris. Le focus reste
   posé — le lecteur d'écran a besoin qu'il le soit — seul l'anneau s'efface. */
.sb-reponse__t[data-geste="pointeur"]:focus-visible{outline:none}
.sb-reponse__texte{max-width:var(--sb-mesure-prose,68ch);opacity:.9}

/* ── Le panneau de réponse : un CADRE, pas du texte nu ──────────────────────
   Le titre et le paragraphe posés à même le fond de section, c'est ce qu'on
   voyait ici. Le prototype en fait une carte. Relevé sur `.carte-pan` à 1280 px :

       fond            #FBF9F4  (la plaque, sur un fond de zone à #F4F1EA)
       filet           1px solid rgba(11,11,14,.2)   soit 20 % d'encre
       coins           18px
       marges internes 30px en haut et en bas, 34px à gauche et à droite
       filet du haut   2px, #E6008A, sur toute la largeur, sous les coins

   ── ce qui est repris tel quel, et ce qui ne peut pas l'être ──

   LES MARGES INTERNES sont les valeurs relevées, converties à la base de 16 px :
   30px = 1.875rem, 34px = 2.125rem.

   LE FOND ne peut PAS être #FBF9F4. La tonalité vient du rang de la section, et
   le pattern ne sait pas où il tombera : une plaque crème écrite en dur ferait
   un pavé blanc dès que la section passe en tonalité encre. On reprend donc la
   recette déjà employée par le contenant des marques juste en dessous —
   `currentColor` à 6 % — qui donne le même écart au fond dans les deux tonalités.
   Les deux cadres se répondent, ce qui est exactement ce que fait le prototype
   avec ses deux `--surface`.

   LE FILET DU CADRE suit la même logique : 20 % d'encre relevés → 20 % de
   `currentColor`. C'est un cran au-dessus des 18 % des cases de la grille, et
   c'est voulu : le prototype aussi distingue `--border-strong` de `--border`.

   LES COINS prennent `--sb-radius` (14px sur ce site) et non les 18px relevés.
   Un rayon est un jeton de marque, pas une mesure de composant : le prototype
   pose 18px sur le panneau et 20px sur les marques, soit deux valeurs quasi
   égales. Reprendre le jeton ici garde ce même rapport — panneau et marques au
   même rayon — alors qu'un 18px écrit en dur les aurait désaccordés.

   LE FILET DU HAUT prend `var(--sb-accent)` brut, et non le `--sb-selecteur-accent`
   éclairci défini plus haut : cet éclaircissement existe pour tenir 4,5:1 sur du
   TEXTE de 14px. Un trait décoratif de 2px n'a pas de texte à rendre lisible, et
   l'affadir lui ferait perdre la seule chose qu'il apporte — l'accent de la
   marque, franc, en haut de la carte.

   `overflow:hidden` est indispensable : sans lui le filet dépasserait des coins
   arrondis et laisserait deux pointes carrées en haut de la carte. */
.sb-reponse__panneau{position:relative;overflow:hidden;
  padding:1.875rem 2.125rem;
  border:1px solid color-mix(in srgb,currentColor 20%,transparent);
  border-radius:var(--sb-radius,.25rem);
  background:var(--sb-plaque,color-mix(in srgb,currentColor 6%,transparent))}
.sb-reponse__panneau::before{content:"";position:absolute;inset:0 0 auto;
  height:2px;background:var(--sb-accent)}
/* Le paragraphe porte une marge basse qui, dans un cadre, s'ajoute aux 30px de
   marge interne et déséquilibre la carte. Le prototype ne l'a pas : `.pan-texte`
   y est à `margin:10px 0 0`. */
.sb-reponse__panneau > :last-child{margin-bottom:0}

/* L'axe suit la réponse visée : on ne laisse pas la grille des profils
   au-dessus d'une réponse de besoin. La spécificité de `:has()` est celle de
   son argument le plus fort — l'identifiant l'emporte donc sur `[data-axe]`. */
/* 🔴 B20, second effet du même renommage — ET IL SE VOYAIT ENCORE PLUS.
   Les trois règles ci-dessous ne parlaient que d'« intentions ». La deuxième
   et la troisième étaient donc SÉLECTEUR POUR SÉLECTEUR IDENTIQUES : à
   spécificité égale la dernière gagne, et `display:none` l'emportait sur
   `display:block`. La vue « Mes besoins » était masquée EN PERMANENCE, y
   compris quand son onglet était l'onglet actif ; et la vue « Mon profil »,
   privée de sa règle de masquage de base, restait affichée EN PERMANENCE.

   MESURÉ au chargement, dans les trois tonalités : onglet actif =
   « Mes besoins » en magenta, et sous lui les 12 cases de « Mon profil »
   — `vue[intentions] display=none (16 cases)`, `vue[publics] display=block
   (12 cases)`. La pastille magenta désignait donc une grille qui n'était pas
   celle qu'on lisait.

   La base masque l'axe SECOND (`publics`) ; l'axe par défaut (`intentions`)
   n'a pas de règle de masquage, c'est ce qui le rend visible sans script. */
.sb-portes__vue[data-axe="publics"]{display:none}
.sb-portes__cadre[data-axe="publics"] .sb-portes__vue[data-axe="publics"]{display:block}
.sb-portes__cadre[data-axe="publics"] .sb-portes__vue[data-axe="intentions"]{display:none}
.sb-portes__cadre:not(.est-pilote):has(#sb-axe-publics:target) .sb-portes__vue[data-axe="publics"],
.sb-selecteur__cadre:not(.est-pilote):has(.sb-reponse[data-axe="publics"]:target) .sb-portes__vue[data-axe="publics"]{display:block}
.sb-portes__cadre:not(.est-pilote):has(#sb-axe-publics:target) .sb-portes__vue[data-axe="intentions"],
.sb-selecteur__cadre:not(.est-pilote):has(.sb-reponse[data-axe="publics"]:target) .sb-portes__vue[data-axe="intentions"]{display:none}
/* Le repli SANS SCRIPT, atteint par `:target` — corrompu de la même façon.
   Les deux blocs étaient sélecteur pour sélecteur identiques : le second,
   qui ÉTEINT l'onglet, effaçait le premier, qui l'ALLUME. Sans script, viser
   `#sb-axe-publics` n'allumait donc rien du tout.
   Le premier bloc allume l'onglet VISÉ (`publics`), le second éteint l'AUTRE
   (`intentions`), que la règle par défaut du cadre a peint plus haut. */
.sb-portes__cadre:not(.est-pilote):has(#sb-axe-publics:target) .sb-portes__onglet[data-axe="publics"],
.sb-selecteur__cadre:not(.est-pilote):has(.sb-reponse[data-axe="publics"]:target) .sb-portes__onglet[data-axe="publics"]{
  /* `--sb-accent-fond` et non `--sb-accent` : un onglet actif EST un bouton
     plein, et tokens.css dit de ce jeton qu'il est « le FOND d'un bouton
     plein, et lui seul » — créé précisément parce qu'aplatir le dégradé
     « donnait un bouton violet là où l'œil attend du magenta ». C'est le
     défaut relevé à l'écran sur « Découvrez nos solutions ».
     Le texte reste centré sur l'arrêt milieu du dégradé : 6,17:1, inchangé. */
  background:var(--sb-accent-fond,var(--sb-accent));color:var(--sb-accent-fg);font-weight:600;opacity:1}
.sb-portes__cadre:not(.est-pilote):has(#sb-axe-publics:target) .sb-portes__onglet[data-axe="intentions"],
.sb-selecteur__cadre:not(.est-pilote):has(.sb-reponse[data-axe="publics"]:target) .sb-portes__onglet[data-axe="intentions"]{
  background:none;color:inherit;font-weight:500;opacity:.7}

/* ── Le contenant des marques ───────────────────────────────────────────────
   UN SEUL contenant arrondi pour tout le bloc, pas une boîte par marque. */
.sb-reponse__marques{margin-top:1.5rem;padding:1.25rem var(--sb-gap);
  border-radius:var(--sb-radius,.25rem);
  background:var(--sb-plaque,color-mix(in srgb,currentColor 6%,transparent))}
.sb-reponse__lab{font-size:.75rem;letter-spacing:.14em;text-transform:uppercase;
  opacity:.7;margin:0 0 .75rem}

.sb-rangs{list-style:none;margin:0;padding:0}
.sb-rang{display:flex;align-items:flex-start;gap:1.25rem;padding:1.25rem 0;
  border-top:1px solid color-mix(in srgb,currentColor 15%,transparent)}
.sb-rang:first-child{border-top:0;padding-top:0}
.sb-rang__t{flex:1;min-width:0;display:flex;flex-direction:column;gap:.35rem}
.sb-rang__t b{font-size:1rem;line-height:1.35}
.sb-rang__d{font-size:.9375rem;opacity:.8}
.sb-rang__l{font-size:.875rem;color:var(--sb-selecteur-accent);
  text-decoration:none;font-weight:600;
  /* Une cible tactile ne descend pas sous 24 px, et un lien de fin de rangée
     est exactement celui qu'on rate au pouce. */
  min-height:1.5rem;display:inline-flex;align-items:center;align-self:flex-start}
/* LE SURVOL CHANGE LA COULEUR, IL NE SOULIGNE PLUS — demandé le 20.08.
   Sans risque ici : ce lien est SEUL en fin de rangée, pas noyé dans un
   paragraphe. C'est le cas de figure que WCAG 1.4.1 vise — un lien qu'on ne
   distingue du texte autour que par sa couleur — et il ne se pose pas. */
.sb-rang__l:hover,.sb-rang__l:focus-visible{
  color:color-mix(in oklab,var(--sb-selecteur-accent) 72%,var(--sb-fg))}

/* ── Les logos ──────────────────────────────────────────────────────────────
   PLAQUE CLAIRE, FIGÉE. La tonalité d'une section vient de sa position et on ne
   la fige pas — mais un logo est un fichier figé, et sa variante encre devient
   invisible dès que la section tombe en tonalité sombre. La plaque rend la
   lisibilité indépendante de la position.

   `filter:none` est écrit, et il n'est pas superflu : une feuille de marque a
   déjà repeint les logos d'un ruban avec `brightness(0) invert(1)`, ce qui les
   effaçait sur les plaques claires. La source était juste depuis le début.

   LA PLAQUE GARDE UNE TAILLE POSÉE — pas déduite du contenu : une image sans
   dimension intrinsèque et en chargement différé a une largeur nulle, n'entre
   jamais dans le champ, et n'est jamais chargée.

   ── LA TAILLE DU DESSIN VIENT DU RENDU, PAS D'ICI ───────────────────────────
   `--sb-rang-h` (sur la ligne) et `--sb-rang-hm` (en colonne) sont écrites par
   `sb_selecteur_rang()`, logo par logo, pour que tous PEIGNENT LA MÊME SURFACE
   — voir le raisonnement là-bas. Le CSS ne pose QUE la hauteur : la largeur suit
   le rapport intrinsèque de l'image, donc aucun dessin n'est déformé. Les replis
   des `var()` sont les valeurs d'avant, pour un logo dont le rapport n'a pas pu
   être lu. */
/* PAS DE PLAQUE EN TONALITÉ CLAIRE. La section est déjà claire : le fond ne
   faisait que dessiner un rectangle autour de chaque logo, sans rien résoudre.
   ⚠️ ELLE RESTE INDISPENSABLE EN ENCRE. Presque tous ces logos sont dessinés
   pour un fond BLANC — la plaque le leur rend. La retirer partout ferait
   disparaître les logos sombres sur le fond sombre.
   Les jetons employés sont ceux que la marque déclare déjà pour les plaques du
   nuage : une seule paire pour un seul problème, plutôt qu'une deuxième qui
   dériverait. */
.sb-rang__logo{flex:0 0 8.25rem;width:8.25rem;height:2.75rem;
  display:grid;place-items:center;padding:.125rem .5rem;
  border-radius:calc(var(--sb-radius,.25rem) / 2);
  background:transparent;color:inherit}
.sb-ink .sb-rang__logo{
  background:var(--sb-nuage-plaque-bg-sombre,#FFFFFF);
  color:var(--sb-nuage-plaque-fg-sombre,#0B0B0E)}
/* `object-fit:contain` n'est PAS décoratif ici : c'est le filet qui empêche la
   déformation le jour où `max-width` mord — la boîte se réduit, le dessin s'y
   inscrit sans s'écraser. */
.sb-rang__img{width:var(--sb-rang-l,auto);height:var(--sb-rang-h,2.25rem);
  max-width:100%;max-height:100%;object-fit:contain;filter:none}
.sb-rang__mono{display:grid;place-items:center;width:2.25rem;height:2.25rem;
  border-radius:calc(var(--sb-radius,.25rem) / 2);font-weight:700;font-size:.875rem;
  background:color-mix(in srgb,var(--sb-paper-fg) 12%,transparent)}

/* ── Le repli du vivier ─────────────────────────────────────────────────────
   `details`/`summary` : clavier et lecteur d'écran, sans une ligne de script.
   Le nombre est calculé au rendu, jamais écrit. */
.sb-reponse__aussi{margin-top:1.5rem;padding-top:1rem;
  border-top:1px solid color-mix(in srgb,currentColor 15%,transparent)}
/* ⚠️ CE REPLI N'AVAIT AUCUNE AFFORDANCE, ET C'ÉTAIT UN EFFET DE BORD.
   `display:flex` sur un <summary> SUPPRIME le marqueur du navigateur — le
   petit triangle. Posé ici pour aligner le compte, il a emporté le seul signe
   qui disait « ça s'ouvre ». Restait du texte gris à 0,85 d'opacité, que rien
   ne distinguait d'une légende.
   On redonne donc les trois signaux d'un contrôle : la COULEUR d'accent, la
   GRAISSE, et un CHEVRON qui tourne à l'ouverture — dessiné en bordures plutôt
   qu'écrit en caractère, parce qu'un caractère ne tourne pas proprement et
   change de dessin d'une police à l'autre.
   `list-style:none` et le pseudo `-webkit-` couvrent les deux façons dont les
   navigateurs posent ce marqueur ; sans les deux, un triangle réapparaît à
   côté du chevron sur Firefox. */
.sb-reponse__aussi summary{cursor:pointer;font-size:.9375rem;font-weight:600;
  color:var(--sb-selecteur-accent);
  min-height:2.75rem;display:flex;align-items:center;gap:.5rem;
  list-style:none;transition:color .18s ease}
.sb-reponse__aussi summary::-webkit-details-marker{display:none}
.sb-reponse__aussi summary::after{content:"";flex:0 0 auto;
  width:.45rem;height:.45rem;margin:-.15em 0 0 .1rem;
  border-right:2px solid currentColor;border-bottom:2px solid currentColor;
  transform:rotate(45deg);transition:transform .18s ease}
.sb-reponse__aussi[open] summary::after{transform:rotate(-135deg);margin-top:.1em}
.sb-reponse__aussi summary:hover,.sb-reponse__aussi summary:focus-visible{
  color:color-mix(in oklab,var(--sb-selecteur-accent) 70%,var(--sb-fg))}
/* Le compte reste dans l'encre du texte : c'est une information, pas le
   contrôle. Tout passer à l'accent aurait dilué le signal qu'on vient de poser. */
.sb-reponse__n{opacity:.7;font-weight:400;color:var(--sb-fg)}
@media (prefers-reduced-motion:reduce){
  .sb-reponse__aussi summary,.sb-reponse__aussi summary::after{transition:none}
}
.sb-rangs--discret .sb-rang{padding:.875rem 0}
.sb-rangs--discret .sb-rang__logo{flex-basis:6.5rem;width:6.5rem;height:2.25rem}
/* UN FACTEUR, PAS UN PLAFOND. `max-height:1.75rem` rognait les logos hauts et
   laissait les larges intacts : l'égalisation de surface était défaite pour la
   moitié de la liste. Multiplier garde le rapport entre eux — la hiérarchie
   visuelle du vivier reste, sans redevenir arbitraire. */
.sb-rangs--discret .sb-rang__img{width:calc(var(--sb-rang-l,5.5rem) * .8);
  height:calc(var(--sb-rang-h,2.25rem) * .8)}
.sb-rangs--discret .sb-rang__t b{font-size:.9375rem;font-weight:600}

/* ── L'appel final ──────────────────────────────────────────────────────────
   Centré, et HORS du contenant des marques : il ne fait pas partie de la carte.
   C'était la fuite la plus coûteuse relevée par l'audit — sans lui, le parcours
   qualifié ne se terminait nulle part. */
.sb-reponse__fin{display:flex;flex-direction:column;align-items:center;gap:.75rem;
  text-align:center;margin:2rem 0 0}
/* ── L'APPEL EST UNE BANDE, PAS UN PARAGRAPHE ────────────────────────────
   Posé en simple paragraphe sous la réponse, il se lisait comme une phrase de
   plus dans le même souffle. Le prototype traite l'appel final en SECTION, avec
   son fond : c'est ce qui le détache et le fait voir comme une invitation, pas
   comme une conclusion.
   Le fond est tiré de l'encre courante et non d'une valeur grise écrite en dur :
   la même règle sert alors en présentation claire ET sombre, où un gris fixe
   serait soit invisible soit criard. */
.sb-reponse__fin{
  margin:2.5rem 0 0;padding:2.5rem 1.5rem;border-radius:1.25rem;text-align:center;
  background:color-mix(in srgb,currentColor 5%,transparent);
  border:1px solid color-mix(in srgb,currentColor 9%,transparent)}
.sb-reponse__fin .sb-wrap{display:grid;gap:1.1rem;justify-items:center;padding:0}
.sb-reponse__fin-t{display:block;margin:0;line-height:1.5;
  font-size:clamp(1.15rem,2.4vw,1.5rem);font-weight:700;text-wrap:balance}
/* La promesse passe sous l'écho, en retrait de graisse : deux lignes de même
   force se disputeraient l'attention. */
.sb-reponse__fin-s{display:block;font-weight:400;
  color:color-mix(in srgb,currentColor 74%,transparent)}
/* La mention, sous le bouton, dans la fonte technique — c'est une donnée, pas
   une phrase : durée, engagement, délai. */
.sb-reponse__fin-m{display:block;margin-top:.75rem;
  font:400 max(.75rem,12px)/1.4 var(--sb-font-mono,ui-monospace,monospace);
  letter-spacing:.06em;color:color-mix(in srgb,currentColor 62%,transparent)}

.sb-selecteur-note{margin:0 0 1rem}

/* ── La roulette d'attente ──────────────────────────────────────────────────
   Le prototype ne montre pas la réponse au clic : il fait patienter, puis la
   révèle. Le module s'en tenait au dévoilement immédiat, et il manquait donc
   « la petite planète sablier qui tourne pendant que ça charge ».

   ELLE N'EST PAS DÉCORATIVE, et c'est pour cela qu'elle se remet : le prototype
   la documente comme ce qui « marque le passage de la question à la réponse, et
   laisse le temps à l'œil de suivre la descente ». La réponse s'ouvre à plusieurs
   centaines de pixels sous la grille ; sans ce temps, elle apparaît sans qu'on
   ait vu d'où elle venait.

   Relevé sur `.carte-spin` du prototype, chargement en cours, à 1280 px :

       disposition     flex, aligné au centre, 14px entre le dessin et le texte
       marges internes 30px au-dessus, 6px en dessous, rien sur les côtés
       hauteur totale  82px  (30 + 46 + 6)
       texte           15px, en gris adouci
       dessin          46 × 46, non rétrécissable
       orbite          r = 16.5, trait de 1px à 20 % d'encre, pointillé 3 5,
                       un tour en 9s, linear, à l'infini
       planète         r = 7.5, remplie d'accent, opacité .9
       satellite       r = 2.9 posé à cx = 39.5, encre pleine,
                       un tour en 1.5s, linear, à l'infini

   Les deux rotations partagent la même image-clé et ne diffèrent que par leur
   durée : 9s pour l'orbite, 1.5s pour le satellite. C'est ce décalage — un
   rapport de 6 — qui fait lire un mouvement d'astre et non un chargeur générique.

   LES COULEURS PASSENT PAR LES JETONS, comme partout dans ce module. L'orbite et
   le satellite prennent `currentColor` (à 20 % pour l'orbite, plein pour le
   satellite) : le prototype y met `--border-strong` et `--text`, qui sont
   exactement l'encre de la section à deux intensités, donc la roulette tient
   dans les deux tonalités sans être écrite deux fois.

   LA PLANÈTE, ELLE, PREND `--sb-selecteur-accent` ET NON `--sb-accent` BRUT.
   C'est le seul endroit où l'on s'écarte du prototype, et pour la raison déjà
   mesurée en tête de cette feuille : en tonalité encre, l'accent d'YHB tombe à
   2,8:1 sur le fond de section. Un filet décoratif peut se le permettre — un
   indicateur de chargement, non : c'est la seule chose à l'écran pendant deux
   secondes, et s'il disparaît le visiteur croit la page bloquée.

   ⚠️ CONSÉQUENCE ASSUMÉE, à connaître avant de « corriger » : la planète et le
   filet du panneau ne sont donc PAS exactement de la même teinte sur ce site —
   relevé #B7006F pour l'une, #A02597 pour l'autre, là où le prototype met
   #E6008A partout. Les deux ne sont jamais à l'écran en même temps (la roulette
   s'efface au moment où le panneau paraît), mais l'œil peut noter le décalage à
   deux secondes d'intervalle. Si on veut l'unité stricte, c'est `--sb-accent`
   qu'il faut mettre ici — au prix du contraste en tonalité encre. Le choix est
   posé, il n'est pas subi.

   `[hidden]` est déjà neutralisé en tête de cette feuille par
   `.sb-selecteur [hidden]{display:none!important}` — la roulette est dans la
   section, elle en bénéficie. C'est ce garde-fou, et lui seul, qui l'empêche de
   tourner à l'arrivée sur la page : sans lui, le `display:flex` d'auteur bat
   l'attribut du navigateur. Le prototype porte la même règle, pour la même
   raison, apprise de la même façon. */
.sb-selecteur__attente{display:flex;align-items:center;gap:.875rem;
  padding:1.875rem 0 .375rem;font-size:.9375rem;opacity:.8}
.sb-selecteur__attente svg{flex:0 0 2.875rem;width:2.875rem;height:2.875rem}
.sb-selecteur__orbite{fill:none;stroke:color-mix(in srgb,currentColor 20%,transparent);
  stroke-width:1;stroke-dasharray:3 5;
  transform-origin:50% 50%;animation:sb-selecteur-tourne 9s linear infinite}
.sb-selecteur__planete{fill:var(--sb-selecteur-accent);opacity:.9}
.sb-selecteur__satellite{transform-origin:50% 50%;
  animation:sb-selecteur-tourne 1.5s linear infinite}
.sb-selecteur__satellite circle{fill:currentColor}
@keyframes sb-selecteur-tourne{to{transform:rotate(360deg)}}

/* ══ EN COLONNE — SOUS 40rem ═══════════════════════════════════════════════
   🔴 LE PIÈGE QUI A CRÉÉ LE VIDE, ET IL EST DANS LE MODÈLE FLEX.
   `.sb-rang__logo` porte `flex:0 0 8.25rem`. La rangée passant ici en colonne,
   l'AXE PRINCIPAL devient vertical : `flex-basis` cesse d'être une largeur et
   devient une HAUTEUR, qui bat le `height:2.75rem` écrit deux lignes plus haut.
   MESURÉ à 390 px : réserve de 132 × 132 px pour un dessin de 116 × 15
   (Clairvidence) — 8,8 % de remplissage, 109 px de vide sous un trait.
   Il ne suffit donc PAS d'agrandir le dessin : il faut rendre sa base au
   `flex-basis`, sinon la réserve reste carrée quoi qu'on peigne dedans.

   LA PLAQUE ÉPOUSE LE DESSIN. `width:max-content` : plus de colonne à tenir
   droite ici — la plaque est seule sur sa ligne, au-dessus du texte. Le vide
   intérieur tombe donc à ce que la marge laisse, et rien de plus. C'est le
   « respirer sans flotter » : la marge est la respiration, la plaque au plus
   près est ce qui empêche de flotter. */
@media (max-width:40rem){
  .sb-rang{flex-direction:column;gap:.75rem}
  .sb-rang__logo{flex:0 0 auto;max-width:100%;justify-items:start;padding:.375rem .5rem;
    width:calc(var(--sb-rang-lm,6rem) + 1rem);
    height:calc(var(--sb-rang-hm,2.75rem) + .75rem)}
  .sb-rang__img{width:var(--sb-rang-lm,auto);height:var(--sb-rang-hm,2.75rem);max-height:none}
  .sb-rangs--discret .sb-rang__logo{flex-basis:auto;
    width:calc(var(--sb-rang-lm,6rem) * .8 + 1rem);
    height:calc(var(--sb-rang-hm,2.75rem) * .8 + .75rem)}
  .sb-rangs--discret .sb-rang__img{width:calc(var(--sb-rang-lm,6rem) * .8);
    height:calc(var(--sb-rang-hm,2.75rem) * .8)}
  /* Le monogramme de repli n'a pas de `viewBox` à lire — il grandit du même
     rapport que les autres, à la main, pour ne pas rester seul minuscule. */
  .sb-rang__mono{width:3rem;height:3rem;font-size:1.0625rem}
  .sb-portes__bascule{display:flex;width:100%}
  .sb-portes__onglet{flex:1}
}

/* `prefers-reduced-motion` arrête TOUTE animation — patterns.css le fait déjà
   pour les transitions ; il reste le défilement, que le script lit lui aussi,
   et les deux rotations de la roulette.

   L'ATTENTE, ELLE, EST CONSERVÉE : on arrête le mouvement, pas le délai. Le
   prototype fait le même partage, et il a raison — `prefers-reduced-motion`
   demande de ne pas faire bouger l'écran, pas de brûler l'étape qui explique
   d'où sort la réponse. Le texte « Nous composons votre carte… » et sa zone
   `aria-live` restent, eux, entièrement lisibles. */
@media (prefers-reduced-motion:reduce){
  .sb-selecteur__cadre{scroll-behavior:auto}
  .sb-selecteur__orbite,.sb-selecteur__satellite{animation:none}
}

/* ══════════════════════════════════════════════════════════════════════════════
   LES PORTES DE L'ACCUEIL
   ══════════════════════════════════════════════════════════════════════════════

   ⚠️ LA PREMIÈRE RÈGLE EST UN GARDE-FOU, PAS UNE COMMODITÉ. Sans
   `.sb-portes [hidden]{display:none!important}`, la seconde liste s'affiche EN
   MÊME TEMPS que la première : l'attribut `hidden` ne pose qu'un `display:none`
   de feuille utilisateur, et n'importe quelle règle d'auteur qui déclare un
   display le bat — ici le `display:flex` des pastilles. La panne a déjà été
   constatée sur le prototype, où l'on lisait « Je veux un particulier ».
   Le sélecteur porte la même règle, pour la même raison ; elle est SCOPÉE par
   section, donc en poser une ne dispense pas de poser l'autre.

   La planète de la roulette lit `--sb-selecteur-accent`, déclaré plus haut sur
   `.sb-selecteur` seulement. On le redéclare ici à l'identique — même repli en
   tonalité encre, où l'accent brut d'YHB tombe à 2,8:1. */
.sb-portes [hidden]{display:none!important}
/* ⚠️ L'ACCENT DE LA MARQUE, PAS SON REPLI. Le sélecteur prend
   `--sb-accent-text` — un accent assombri pour le texte — et c'était recopié
   ici sans le vérifier. Sur le crème de cette section, les deux passent :
   #A02597 mesure 5,81:1 et #B7006F 5,74. Le repli était donc à la fois moins
   conforme à la marque ET très légèrement moins lisible : il ne réglait aucun
   problème qui se pose ici.
   En tonalité ENCRE, l'accent brut tombe à 2,8:1 — là, le mélange reste
   indispensable. La distinction se fait donc par tonalité, pas par module. */
.sb-portes{--sb-selecteur-accent:var(--sb-accent)}
.sb-portes.sb-ink{--sb-selecteur-accent:color-mix(in oklab,var(--sb-accent) 65%,var(--sb-ink-fg))}

.sb-portes__cadre{display:grid;justify-items:center;margin-top:1.75rem}
/* ⚠️ L'HABILLAGE DE LA BASCULE N'EST PLUS ICI — il est monté dans le bloc du
   sélecteur, en une seule place pour les deux pages. Le laisser ici l'aurait
   emporté par l'ORDRE : la feuille est concaténée dans l'ordre du fichier, et à
   spécificité égale la dernière écrite gagne. L'aplat d'accent aurait donc
   continué de battre le dégradé qu'on vient de choisir, sans qu'aucune règle
   n'échoue. C'est le même piège que la feuille de marque, un cran plus bas. */

/* La phrase EST le titre visuel de la section : c'est elle qu'on lit en
   arrivant, et c'est elle qui change. Le h2 reste au-dessus pour le plan du
   document et pour l'ancre de section. */
/* ⚠️ DEUX LIGNES, TOUJOURS — ET C'EST CE QUI TIENT LA PAGE IMMOBILE.
   `max-width:24ch` coupait « créer ou optimiser une société » en deux alors que
   la section fait 64rem : la phrase passait de deux à trois lignes selon le mot
   survolé, et TOUT ce qui suit descendait de 65 px, à chaque survol.
   On lève donc la bride — la largeur du cadre suffit — et on RÉSERVE la hauteur
   de deux lignes. Le `min-height` n'est pas une ceinture de sécurité : c'est lui
   qui garantit que la boîte ne bouge pas, même si un libellé venait un jour à
   passer à la ligne sur un écran étroit.
   `text-wrap:balance` a sauté : il répartit les mots entre les lignes, donc il
   FABRIQUE des retours à la ligne là où on n'en veut aucun. */
.sb-portes__phrase{font:300 clamp(1.7rem,4.4vw,3rem)/1.35 var(--sb-font-title,var(--sb-font-body));
  letter-spacing:-.015em;max-width:none;margin:0 auto 2rem;
  /* 1,35em et non 2,7 : la tête ayant quitté la vue, la phrase ne fait plus
     qu'UNE ligne. Le figeage reste — c'est lui qui empêche la page de sauter
     quand on passe d'une forme courte à une forme longue — mais sur la bonne
     hauteur. Le laisser à 2,7 aurait creusé un blanc de la taille exacte du
     texte qu'on vient de retirer. */
  min-height:1.35em;
  /* La section n'aligne rien — `text-align` y vaut `start`, hérité du corps —
     et la phrase n'était centrée que comme BOÎTE par `justify-items`. La tête
     se posait donc au bord gauche, au-dessus d'un mot plus long qu'elle. */
  text-align:center}
/* LA TÊTE SUR SA PROPRE LIGNE, et pour deux raisons qui vont dans le même sens.
   À l'œil : la phrase se réagençait à chaque survol — « Je veux réaliser / un
   projet » puis « Je veux faire vivre un / lieu ou un réseau » — et la partie
   FIXE bougeait avec la variable. Figée sur sa ligne, seule la forme change.
   À la mesure : deux `<span>` en ligne dans un paragraphe qui passe à la ligne
   ont des rectangles qui SE CONTIENNENT — celui de la forme couvre les deux
   lignes, donc celui de la tête — et le canal 6 y voit deux boîtes de texte
   superposées à 100 %. Un bloc et un en-ligne ne se recouvrent plus. */
/* ⚠️ LA TÊTE EST LUE, PAS VUE — signalé le 21.08 : « le "je veux" au milieu est
   un peu redondant avec le bouton ». Il l'est, et ce n'est pas un accident de
   rédaction : la bascule dit déjà « Je veux… », avec des points de suspension
   qui PROMETTENT la suite. La phrase répétait donc l'amorce avant de la
   compléter, à deux centimètres d'écart.
   La bascule et la phrase sont UNE SEULE phrase, coupée en deux morceaux :
   « Je veux… » / « optimiser une société ». Le lecteur voyant la reconstitue
   sans effort — c'est même ce que les points de suspension lui demandent.
   ⚠️ MAIS ON NE LA SUPPRIME PAS DU DOCUMENT. Qui écoute la page n'a pas les
   deux morceaux sous les yeux en même temps : à la lecture d'écran, « optimiser
   une société » seul est un fragment sans sujet. La tête reste donc dans le
   texte, retirée de la seule vue.
   ET PAS `display:none` : il sortirait aussi de la restitution vocale. C'est le
   masquage visuel classique — la boîte d'un pixel, en dehors du flux. */
.sb-portes__tete{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0;
}
.sb-portes__trou{font-weight:700;color:var(--sb-selecteur-accent);
  border-bottom:2px solid color-mix(in srgb,var(--sb-selecteur-accent) 55%,transparent)}

/* LA GRILLE DE /trajectoires/, AUX MÊMES COMPTES DE COLONNES. Quatre pour les
   seize besoins, trois pour les douze profils : les deux tombent juste, donc
   aucune ligne ne finit sur une carte solitaire. C'est la STRUCTURE qui règle
   l'orpheline, pas un rattrapage au script.
   Le dessin de la carte vient de `.sb-selecteur__opt`, porté par le même
   élément : on n'en reprend ici que la HAUTEUR. */
.sb-portes__vue{width:100%;max-width:64rem}
.sb-portes__mots{gap:.5rem}
.sb-portes__mots[data-axe="intentions"]{grid-template-columns:repeat(4,1fr)}
.sb-portes__mots[data-axe="publics"]{grid-template-columns:repeat(3,1fr)}
@media (max-width:1000px){
  .sb-portes__mots[data-axe="intentions"],
  .sb-portes__mots[data-axe="publics"]{grid-template-columns:repeat(2,1fr)}
}
@media (max-width:600px){
  .sb-portes__mots[data-axe="intentions"],
  .sb-portes__mots[data-axe="publics"]{grid-template-columns:1fr}

}
/* PLUS BASSES QU'AILLEURS, ET C'EST VOULU. Sur /trajectoires/ la grille EST la
   page : elle peut respirer. Ici elle est une section parmi sept, sous une
   phrase de 3 rem — seize cartes à 3,25 rem poussaient le reste de l'accueil
   hors de l'écran. On descend à 2,75 rem, ce qui laisse la cible tactile bien
   au-dessus du plancher de 44 px. */
/* ⚠️ DEUX CLASSES, ET C'EST OBLIGATOIRE. La feuille de MARQUE est concaténée
   APRÈS celle des modules, et elle pose `.sb-selecteur__opt{min-height:3.875rem}`
   — 62 px. À spécificité égale, la dernière écrite gagne : une règle en une
   seule classe était donc lue, puis annulée, et la carte restait à 62 px.
   `.sb-portes__mot.sb-selecteur__opt` passe à (0,2,0) et tranche pour de bon.
   Écrire `!important` aurait marché aussi, et aurait empêché la marque de
   reprendre la main — ce qui est exactement ce à quoi sert sa feuille. */
/* UNE HAUTEUR, PAS UN PLANCHER. Avec `min-height`, une carte dont le libellé
   passe à la ligne — « Créer ou optimiser une société » — devient plus haute que
   ses voisines, la RANGÉE entière s'aligne sur elle, et la grille change de
   hauteur selon l'axe affiché. D'où le saut au basculement.
   3,75rem tient deux lignes de libellé (2 × 20,25 px), le rembourrement et la
   bordure. Toutes les cartes font donc exactement la même hauteur, les seize
   besoins comme les douze profils — et comme les deux axes tiennent en quatre
   rangées, les deux grilles ont la même hauteur au pixel près.
   Le libellé est borné à deux lignes : au-delà il déborderait de la boîte, et
   une boîte fixe ne pardonne pas. */
.sb-portes__mot.sb-selecteur__opt{height:3.75rem;min-height:0;padding:.5rem .75rem;
  transition:border-color .18s ease,background .18s ease}
.sb-portes__mot .sb-selecteur__l{display:-webkit-box;-webkit-box-orient:vertical;
  -webkit-line-clamp:2;line-clamp:2;overflow:hidden}
.sb-portes__mot .sb-selecteur__ic{flex:0 0 1.625rem;width:1.625rem;height:1.625rem}
/* ⚠️ CE BLOC EST APRÈS LA RÈGLE DE BASE, ET CE N'EST PAS UN HASARD. Écrit plus
   haut — dans le `@media` qui règle les colonnes — il portait la MÊME
   spécificité (0,2,0) que `.sb-portes__mot.sb-selecteur__opt` et se faisait
   annuler par l'ordre : mesuré, la carte restait à 60 px et 8 px de
   rembourrage, exactement comme avant. Une media query ne pèse rien dans la
   cascade ; seul l'ordre départage. */
@media (max-width:600px){  /* ══ LES CARTES EMPILÉES MANGENT L'ÉCRAN — corrigé le 21.08 ══════════════
     MESURÉ à 390 px, avant : carte de 60 px, gouttière de 10, seize cartes
     = 1 110 px de liste, soit 1,3 écran d'iPhone 14 rien que pour choisir.
     Le client demande moins de rembourrage haut et bas, et « très légèrement »
     moins d'écart — « tout en restant joli ».

     LE PLANCHER EST LE POINT DUR : une cible tactile ne descend pas sous
     44 px de haut. On s'arrête à 3rem = 48 px, quatre pixels AU-DESSUS, et pas
     un de moins : le rembourrage tombe de 11 à 7 px, la gouttière de 10 à 8.
     L'icône ronde reste à 26 px, sous les 32 px de hauteur utile de la carte —
     elle ne peut donc pas devenir plus haute que sa boîte.

     ⚠️ `min-height` ET NON `height`, ICI SEULEMENT. Sur la ligne, une hauteur
     FIXE est ce qui garantit que toutes les cartes se valent et que la grille
     ne saute pas au changement d'axe. En colonne il n'y a plus de rangée à
     égaliser, et une hauteur fixe deviendrait un piège : un libellé qui passe
     à deux lignes sur un écran étroit déborderait de son cadre. Le plancher
     donne la même carte à tout le monde et laisse la rare exception grandir.

     ⚠️ DEUX CLASSES, ET C'EST OBLIGATOIRE — même piège qu'au-dessus. La feuille
     de MARQUE est concaténée APRÈS celle des modules et pose
     `.sb-selecteur__opt{min-height:3.875rem}` puis `.sb-selecteur__choix{gap:.625rem}`.
     À spécificité égale la dernière écrite gagne : une règle en une seule
     classe serait lue puis annulée, en silence. */
  .sb-portes__mot.sb-selecteur__opt{height:auto;min-height:3rem;padding:.4375rem .75rem}
  .sb-portes__mots.sb-selecteur__choix{gap:.5rem}
}
.sb-portes__mot:hover{background:color-mix(in srgb,var(--sb-selecteur-accent) 7%,transparent)}
.sb-portes__mot:focus-visible{outline:2px solid var(--sb-selecteur-accent);outline-offset:2px}
/* LA ROULETTE NE POUSSE PLUS RIEN. Elle s'insérait sous la grille au clic :
   la section grandissait d'un coup, et la page sautait juste avant de partir —
   le dernier mouvement qu'on voit d'une page est celui dont on se souvient.
   Posée en couche par-dessus le cadre, elle n'occupe aucune place dans le flux.
   Le voile reprend le fond de la section plutôt qu'un blanc ou un noir écrits
   en dur : elle reste juste dans les deux tonalités. */
.sb-portes__cadre{position:relative}
.sb-portes__attente{position:absolute;inset:0;z-index:2;padding:0;
  justify-content:center;align-items:center;opacity:1;
  background:color-mix(in srgb,var(--sb-bg,#F4F1EA) 84%,transparent)}
.sb-portes__dessous{margin:1.5rem 0 0;font-size:.92rem}
.sb-portes__dessous a{color:var(--sb-selecteur-accent);font-weight:600}

@media (prefers-reduced-motion:reduce){ .sb-portes__mot{transition:none} }
@media (max-width:40rem){ .sb-portes__phrase{margin-bottom:1.5rem} }


/* ══ Constellation — GÉNÉRÉ par lib/constellation.php, ne pas éditer ════════

   LE REPLI EST DANS CE FICHIER, pas dans le script. Toutes les marques sont
   dans la page, groupées et titrées ; seul le groupe visé reste affiché quand
   une ancre est active. Sans script, c'est `:target` qui décide ; avec, c'est
   l'attribut `hidden` posé par le script — et la classe `est-pilote` désarme
   alors `:target`, sinon une ancre restée dans l'adresse filtrerait par-dessus.

   L'état par défaut des deux mécanismes est LE MÊME — tout est affiché — donc
   il n'y a pas d'éclair possible au chargement, quelle que soit l'ancre. */

/* Le garde-fou. Un `display:` d'auteur bat l'attribut `hidden` du navigateur :
   c'est ainsi qu'une roulette d'attente tournait à l'arrivée sur la page, dans
   une autre section. Porté à la seule constellation — il ne s'agit pas
   d'imposer cette règle au reste du site. */
.sb-const [hidden]{display:none!important}

/* ── Deux accents, et ce n'est pas un doublon ───────────────────────────────

   L'accent de SECTION : la tonalité vient du rang de la section, le pattern ne
   sait pas où il tombera, il doit tenir aux deux. patterns.css remappe
   `--sb-accent-text` sur l'accent brut en tonalité encre, et l'accent d'YHB y
   tombe sous les 4,5:1 qu'exige un texte de 14 px. On éclaircit vers l'ivoire,
   comme le font déjà « chiffres » et « selecteur », et avec la même valeur que
   le sélecteur : 65 %, une marche sous le seuil mesuré à l'écran pour des liens
   de cette taille.
   ⚠️ Cette valeur est REPRISE d'une mesure faite sur le sélecteur, elle n'a pas
   été remesurée sur cette section. Les deux cas sont les mêmes — texte d'accent
   de 14 px sur le fond de section — mais la mesure reste à refaire.

   L'accent de PLAQUE : les cartes sont claires en permanence, quelle que soit la
   tonalité de la section. Leur lien doit donc garder l'accent LISIBLE SUR CRÈME,
   celui que les tokens appellent --sb-accent-text. Il est capté ici, à :root,
   AVANT tout remappage : une propriété personnalisée est substituée sur
   l'élément qui la déclare, donc `.sb-ink` ne peut plus la reprendre ensuite.
   C'est le procédé qu'emploie déjà --sb-chiffres-magenta dans patterns.css. */
:root{--sb-const-plaque-accent:var(--sb-accent-text,var(--sb-accent))}
.sb-constellation{--sb-const-accent:var(--sb-accent-text,var(--sb-accent))}
.sb-constellation.sb-ink{--sb-const-accent:color-mix(in oklab,var(--sb-accent) 65%,var(--sb-ink-fg))}

/* .sb-eyebrow n'a AUCUNE règle dans patterns.css, et « chiffres » a posé la
   sienne portée à sa seule section, en disant pourquoi : décider pour tous les
   patterns d'un coup ne lui appartenait pas. Même retenue ici. */
.sb-constellation .sb-eyebrow{
  font-family:var(--sb-font-mono,ui-monospace,SFMono-Regular,Menlo,monospace);
  font-size:max(.75rem,12px);letter-spacing:.14em;text-transform:uppercase;
  color:var(--sb-const-accent);margin:0 0 .75rem}

.sb-const{margin-top:2rem}

/* ── La barre de filtres ────────────────────────────────────────────────────
   Des LIENS et non des boutons : le repli en dépend. Un bouton sans script ne
   fait rien, un lien mène à l'ancre. Il reste focalisable, activable à l'Entrée,
   annoncé, et il part dans l'index. */
.sb-const__filtres{display:flex;flex-wrap:wrap;gap:.4375rem;margin:0 0 1.75rem}
.sb-const__f{display:inline-flex;align-items:center;gap:.5rem;
  min-height:2.75rem;padding:.5rem 1rem;border-radius:99px;
  border:1px solid color-mix(in srgb,currentColor 20%,transparent);
  font-size:max(.875rem,14px);line-height:1.3;font-weight:500;
  color:inherit;text-decoration:none;opacity:.78;
  transition:opacity var(--sb-motion,.2s) ease,border-color var(--sb-motion,.2s) ease}
.sb-const__f:hover{opacity:1;border-color:color-mix(in srgb,currentColor 45%,transparent)}

/* LE COMPTEUR. Il est calculé au rendu, jamais écrit — voir le module.
   Pas de pastille colorée derrière le nombre, contrairement à la référence :
   un fond teinté déplace le contraste du chiffre d'une valeur qui n'a pas été
   mesurée, et ce chiffre est le plus petit texte de la section. En accent nu
   sur le fond de section, il retombe exactement sur le cas déjà mesuré.
   Chasse fixe : le nombre change au fil des ajouts, la pastille ne doit pas
   changer de largeur sous le curseur. */
.sb-const__n{font-size:max(.75rem,12px);font-weight:600;
  font-variant-numeric:tabular-nums;color:var(--sb-const-accent)}

/* L'état actif : le liseré d'accent sous la pastille, comme la référence.
   Il vient EN PLUS de la graisse et de la bordure — un liseré seul serait la
   couleur pour seul signal, et la couleur ne se voit pas de partout. */
.sb-const__f.est-actif,
.sb-const:not(.est-pilote):not(:has(.sb-const__cible:target)) .sb-const__f[data-f=""],
.sb-const:not(.est-pilote):has(#sb-const-analyser:target) .sb-const__f[data-f="analyser"],
.sb-const:not(.est-pilote):has(#sb-const-realisation:target) .sb-const__f[data-f="realisation"],
.sb-const:not(.est-pilote):has(#sb-const-communaute:target) .sb-const__f[data-f="communaute"],
.sb-const:not(.est-pilote):has(#sb-const-marques:target) .sb-const__f[data-f="marques"],
.sb-const:not(.est-pilote):has(#sb-const-medias:target) .sb-const__f[data-f="medias"],
.sb-const:not(.est-pilote):has(#sb-const-autres:target) .sb-const__f[data-f="autres"]{
  opacity:1;font-weight:600;
  border-color:var(--sb-const-accent);
  box-shadow:inset 0 -2px 0 var(--sb-const-accent)}

/* ── Les familles, mêlées ───────────────────────────────────────────────────
   PLUS DE BANDE PAR FAMILLE, PLUS DE TITRE. La grille rangeait les marques par
   famille, une bande titrée par famille ; demandé le 20.08, les cartes sont
   maintenant entrelacées dans une grille unique — l'œil voit un ensemble, pas
   un classement. Les familles n'ont pas disparu pour autant : elles vivent sur
   chaque carte (`data-f`) et restent le fondement des filtres.
   Le point d'ancrage ne se voit pas : il n'a ni taille ni contenu, il n'existe
   que pour recevoir `:target`. */
.sb-const__cible{display:block;height:0;overflow:hidden}

/* Sans script : les cartes de la famille visée, et elles seules. « Tout voir »
   vise le conteneur, aucun point n'est alors :target, et rien ne se masque. */
.sb-const:not(.est-pilote) #sb-const-analyser:target ~ .sb-const__grille > .sb-const__c:not([data-f="analyser"]),
.sb-const:not(.est-pilote) #sb-const-realisation:target ~ .sb-const__grille > .sb-const__c:not([data-f="realisation"]),
.sb-const:not(.est-pilote) #sb-const-communaute:target ~ .sb-const__grille > .sb-const__c:not([data-f="communaute"]),
.sb-const:not(.est-pilote) #sb-const-marques:target ~ .sb-const__grille > .sb-const__c:not([data-f="marques"]),
.sb-const:not(.est-pilote) #sb-const-medias:target ~ .sb-const__grille > .sb-const__c:not([data-f="medias"]),
.sb-const:not(.est-pilote) #sb-const-autres:target ~ .sb-const__grille > .sb-const__c:not([data-f="autres"]){display:none}

.sb-const__grille{display:grid;gap:var(--sb-gap);
  grid-template-columns:repeat(auto-fill,minmax(15rem,1fr));
  list-style:none;padding:0;margin:0}

/* ── La carte : une PLAQUE CLAIRE, FIGÉE ────────────────────────────────────
   La tonalité de la section vient de sa position et on ne la fige pas. Mais un
   logo est un fichier figé : sa variante encre devient invisible dès que la
   section tombe en tonalité sombre — constaté, blackformance-light.svg sur
   rgb(20,20,26). La plaque rend la lisibilité indépendante de la position.
   C'est la même règle que `.sb-marques .sb-card` dans patterns.css. */
/* CENTRÉE DE BOUT EN BOUT — demandé le 20.08. Le logo, le nom, la phrase et le
   lien étaient calés à gauche : sur une grille de vingt cartes de largeurs de
   contenu très inégales, l'alignement à gauche fait ressortir la longueur de
   chaque phrase au lieu de la marque qu'elle décrit.
   ⚠️ `align-items` NE SUFFIT PAS : il centre les BOÎTES, pas le texte qui est
   dedans. Une phrase sur deux lignes reste alors fer à gauche dans une boîte
   centrée — ce qui se voit plus qu'un alignement franc. Il faut les deux. */
.sb-const__c{position:relative;display:flex;flex-direction:column;
  align-items:center;text-align:center;gap:.5rem;
  background:var(--sb-paper-bg);color:var(--sb-paper-fg);
  border:1px solid color-mix(in srgb,var(--sb-paper-fg) 12%,transparent);
  border-radius:var(--sb-radius,.25rem);padding:1.5rem;
  transition:border-color var(--sb-motion,.2s) ease}
.sb-const__c:hover{border-color:var(--sb-const-plaque-accent)}

/* La plaque du logo porte une hauteur réservée : une image différée sans
   dimension intrinsèque a une largeur nulle, n'entre jamais dans le champ, et
   n'est jamais chargée. `filter:none` est explicite — une feuille de marque a
   déjà repeint des logos avec brightness(0) invert(1), et six diagnostics
   erronés ont suivi. */
/* R83. LA PLAQUE PORTE UN FOND CLAIR, PARCE QUE LES LOGOS EN VIENNENT.
   Demandé par Yan, capture à l'appui : sur `/univers/`, Blackformance,
   Clairvidence et Tiré à Part étaient INVISIBLES — logos noirs sur carte
   sombre. Ce n'est pas un défaut de contraste du texte, c'est un défaut de
   support : un logo est fourni pour un fond, et presque tous le sont pour du
   blanc.

   La correction n'est pas de repeindre les logos — `filter:invert()` a déjà
   coûté six diagnostics erronés, le commentaire ci-dessus le rappelle. C'est de
   leur RENDRE LE FOND qu'ils attendent. Le carrousel de l'accueil le fait déjà :
   ses tuiles sont claires, et tous les logos s'y voient.

   `--sb-paper-bg` et non un blanc littéral : la plaque suit alors la tonalité
   du site, et reste correcte en présentation « jour » comme en « nuit ». */
/* ⚠️ `justify-content:center` N'EST PAS UN DOUBLON DE `align-items` DE LA CARTE.
   La carte centre la PLAQUE ; la plaque doit centrer le LOGO. Sans cette
   ligne, un logo plafonné par `max-width` dans une plaque plus large que lui
   se range au départ de l'axe principal, c'est-à-dire à GAUCHE — le
   désalignement rapporté sur téléphone. `max-width:100%` la borne à la boîte
   de contenu de la carte : la plaque ne peut alors plus déborder, quelle que
   soit la largeur de colonne. */
/* ⚠️ LA BANDE DU LOGO GARDE UNE HAUTEUR CONSTANTE, ET C'EST UNE CONSÉQUENCE
   DIRECTE DE LA BOÎTE EXACTE. Tant que les vingt boîtes faisaient 56 px de
   haut, la plaque en faisait 73,6 partout et le nom de marque tombait à la
   même hauteur sur les vingt cartes. Une boîte qui épouse le dessin est plus
   basse pour un logo-mot — 21,9 px pour Clairvidence — et le nom remontait
   alors de 17,6 px sur ces cartes-là : mesuré, quatre lignes de nom
   désalignées dans la grille.
   La bande réserve donc toujours `--sb-const-logo-h` de hauteur de dessin,
   plus son propre rembourrage. Les deux valeurs ne sont écrites qu'UNE fois,
   ici, et le plafond du logo les relit — les désaccorder ferait réapparaître
   exactement ce désalignement. */
.sb-const__plaque{--sb-const-logo-h:3.5rem;--sb-const-plaque-p:.55rem;
  display:flex;align-items:center;justify-content:center;
  min-height:calc(var(--sb-const-logo-h) + 2 * var(--sb-const-plaque-p));
  max-width:100%;margin-bottom:.25rem;
  background:var(--sb-paper-bg);border-radius:var(--sb-radius,.25rem);
  padding:var(--sb-const-plaque-p) .7rem;box-sizing:border-box}
/* LA BOÎTE EST DITE PAR LE SERVEUR, PLUS DEVINÉE PAR LE MOTEUR.
   `--lw` et `--lh` sont posés sur la plaque par sb_constellation_carte(),
   d'après le `viewBox` du fichier — la seule mesure honnête pour un SVG, que
   la médiathèque ne fait pas. Le raisonnement complet est dans
   sb_constellation_boite() ; l'essentiel tient en deux mesures :
     · sans eux, Chromium rendait les VINGT boîtes à 170 × 56, quel que soit le
       dessin — jusqu'à 131 px de vide autour d'un logo en hauteur, et une
       plaque de 192,4 px pour tout le monde ;
     · sans eux, WebKit — le moteur de l'iPhone — résolvait la largeur à ZÉRO :
       19 logos sur 20 n'étaient pas peints du tout.
   Les valeurs de repli sont les anciens plafonds : une plaque qui n'aurait pas
   reçu ses jetons retombe exactement sur le comportement d'avant.
   `max-width:100%` est le garde-fou qui rend le débordement IMPOSSIBLE : si la
   colonne devenait plus étroite que la boîte, c'est la boîte qui cède, et
   `object-fit:contain` garde le dessin entier et centré dedans. */
.sb-const__logo{width:var(--lw,170px);height:var(--lh,var(--sb-const-logo-h,3.5rem));
  max-width:100%;max-height:var(--sb-const-logo-h,3.5rem);
  /* `center` et non `left` : un logo `contain` se pose dans sa boîte selon
     cette position, et une boîte centrée dont le contenu reste à gauche donne
     exactement le désalignement qu'on vient de corriger au-dessus. */
  object-fit:contain;object-position:center;filter:none}
.sb-const__mono{display:grid;place-items:center;width:3rem;height:3rem;
  border-radius:var(--sb-radius,.25rem);
  background:color-mix(in srgb,var(--sb-paper-fg) 8%,transparent);
  font-family:var(--sb-font-title);font-weight:700;font-size:max(.875rem,14px)}

.sb-const__nom{font-family:var(--sb-font-title);font-size:1.0625rem;
  font-weight:700;line-height:1.25}
.sb-const__p{font-size:max(.875rem,14px);line-height:1.55;margin:0;
  color:color-mix(in srgb,var(--sb-paper-fg) 78%,var(--sb-paper-bg))}

/* LE LIEN ÉTIRÉ. Il est enfant de la carte, jamais son enveloppe : une
   enveloppe avalerait le nom et la phrase dans son nom accessible. Le
   pseudo-élément étend sa surface à toute la carte, et le nom accessible porte
   la marque — vingt liens qui s'annoncent tous « Voir le site » sont
   indistinguables dans une liste de liens. */
.sb-const__l{margin-top:auto;padding-top:.25rem;
  font-size:max(.875rem,14px);font-weight:500;
  color:var(--sb-const-plaque-accent);text-decoration:none}
.sb-const__l::after{content:"";position:absolute;inset:0;border-radius:inherit}
.sb-const__l:hover,.sb-const__l:focus-visible{
  color:color-mix(in oklab,var(--sb-const-plaque-accent) 72%,currentColor)}
/* Le contour de focus se pose sur la carte entière, puisque c'est elle que le
   lien couvre : un contour dessiné autour du seul libellé désignerait une cible
   qui n'est pas celle qu'on active. */
.sb-const__c:focus-within{outline:3px solid var(--sb-accent);outline-offset:2px}
.sb-const__l:focus-visible{outline:none}


/* ══ Les deux nuages de l'accueil ═══════════════════════════════════════════

   Trois formes pour UNE section, choisies par le modificateur de la page :
   sans modificateur le carrousel, `nuage-carte` la carte mentale,
   `nuage-bulles` l'amas de plaques rondes. Le HTML des deux nuages est rendu
   par le module (voir lib/constellation.php, sb_nuage_rendu) ; ici, la forme.

   AUCUNE COULEUR EN DUR, comme dans tout ce fichier : les teintes de famille
   sont dérivées de l'accent juste au-dessus, et tout le reste passe par les
   tokens de tonalité. Aucun texte sous 12 px. */

/* Le support est VIDE dans la forme carrousel — le rendu retourne une chaîne
   vide. `display:contents` lui retire sa boîte : la section sort alors avec la
   même géométrie qu'avant que ce mécanisme existe. */
.sb-marques__nuage{display:contents}

/* Un nuage REMPLACE le carrousel, il ne s'y ajoute pas. Le `<ul>` reste dans le
   document — il porte le contenu du copy deck, et c'est lui qui revient dès
   qu'on retire le modificateur. */
/* ⚠️ LA CLASSE DE BLOC EST RÉPÉTÉE, ET CE N'EST PAS UNE COQUETTERIE.
   `.sb-marques--nuage-carte .sb-grid` pèse (0,2,0). La feuille de marque pose
   `.sb-marques .sb-grid{display:flex}` — (0,2,0) elle aussi, mais CONCATÉNÉE
   APRÈS le module. À poids égal, la dernière gagne : le carrousel restait
   visible SOUS le nuage, mesuré au navigateur, et le nuage s'ajoutait au lieu
   de remplacer.

   `.sb-marques.sb-marques--nuage-carte` pèse (0,3,0) et tranche sans `!important`.
   C'est la même parade que `.sb-hero.sb-hero--legal` (B11) : dans cette chaîne,
   un module est TOUJOURS écrit avant la feuille de marque, donc tout ce qu'il
   veut imposer doit peser un cran de plus. */
.sb-marques.sb-marques--nuage-carte .sb-grid,
.sb-marques.sb-marques--nuage-bulles .sb-grid{display:none}

.sb-nuage{--sb-nuage-f:var(--sb-accent);margin-top:2rem}
.sb-nuage [data-f="analyser"]{--sb-nuage-f:var(--sb-nuage-f-analyser,oklch(from var(--sb-accent) l c calc(h + 0.0)))}
.sb-nuage [data-f="realisation"]{--sb-nuage-f:var(--sb-nuage-f-realisation,oklch(from var(--sb-accent) l c calc(h + 72.0)))}
.sb-nuage [data-f="communaute"]{--sb-nuage-f:var(--sb-nuage-f-communaute,oklch(from var(--sb-accent) l c calc(h + 144.0)))}
.sb-nuage [data-f="marques"]{--sb-nuage-f:var(--sb-nuage-f-marques,oklch(from var(--sb-accent) l c calc(h + 216.0)))}
.sb-nuage [data-f="medias"]{--sb-nuage-f:var(--sb-nuage-f-medias,oklch(from var(--sb-accent) l c calc(h + 288.0)))}


/* ── FORME « carte » : un noyau, les familles autour ────────────────────── */

.sb-nuage--carte .sb-nuage__plan{
  position:relative;width:100%;max-width:1000px;aspect-ratio:1000/560;margin-inline:auto;
  --sb-nuage-eteint:.24}
.sb-nuage__svg{position:absolute;inset:0;width:100%;height:100%}
/* Les filets d'une famille sont DANS sa grappe : ils héritent de sa teinte par
   `data-f`, et surtout de sa dérive et de son recentrage sans qu'on écrive une
   seule transformée pour eux. Une transformée animée sur un nœud SVG ne part pas
   sur le compositeur — cinq animations en moins, c'est la moitié du coût par
   image de cette forme. */
.sb-nuage__gr .sb-nuage__svg line{stroke:color-mix(in srgb,var(--sb-nuage-f) 42%,transparent);stroke-width:1}
/* Les filets du MOYEU sont sur une couche à part : ils disent « le noyau tient
   les familles », ce qui n'a plus d'objet quand l'une d'elles EST le centre. Ils
   s'effacent alors avec lui. */
.sb-nuage__hub{transition:opacity .34s ease}
.sb-nuage__hub line{stroke:color-mix(in srgb,var(--sb-fg) 13%,transparent);
  stroke-width:1;stroke-dasharray:3 6}

.sb-nuage__coeur{
  position:absolute;left:50%;top:50%;margin:0;transform:translate(-50%,-50%);
  display:grid;place-items:center;width:7.4rem;height:7.4rem;border-radius:50%;
  border:1px solid color-mix(in srgb,var(--sb-fg) 16%,transparent);
  background:radial-gradient(circle at 34% 30%,
    color-mix(in srgb,var(--sb-accent) 30%,transparent),transparent 72%);
  font-family:var(--sb-font-mono,ui-monospace,SFMono-Regular,Menlo,monospace);
  font-size:max(.75rem,12px);letter-spacing:.12em;text-transform:uppercase;
  text-align:center;line-height:1.5;
  color:color-mix(in srgb,var(--sb-fg) 84%,var(--sb-bg));
  transition:opacity .34s ease,transform .34s ease}

/* La grappe est en `inset:0` : SA BOÎTE EST LE PLAN. Un pourcentage de
   translation sur elle vaut donc le même pourcentage du plan, et amener une
   famille au centre s'écrit « 50 % − sa position ». Les positions n'existent
   qu'une fois, dans le balisage.
   `translate:` et `scale:` et non `transform:` — `transform` est déjà pris par
   la dérive lente, et les propriétés indépendantes se composent AVANT elle sans
   la toucher : la grappe continue de dériver pendant qu'elle rejoint le centre.
   Cinq nœuds d'enveloppe économisés. */
.sb-nuage__gr{
  position:absolute;inset:0;pointer-events:none;
  --dx:calc(50% - var(--fx));--dy:calc(50% - var(--fy));
  transform-origin:var(--fx) var(--fy);
  animation:sb-nuage-derive var(--dur,47s) ease-in-out infinite alternate;
  animation-delay:var(--dec,0s);
  transition:opacity .3s ease,translate .44s ease,scale .44s ease}
@keyframes sb-nuage-derive{from{transform:translate(-6px,4px)}to{transform:translate(6px,-4px)}}

.sb-nuage__fam{position:absolute;left:var(--fx);top:var(--fy);
  transform:translate(-50%,-50%);margin:0}
.sb-nuage__fam a{
  display:inline-flex;align-items:center;gap:.5rem;pointer-events:auto;
  padding:.4rem .8rem;border-radius:999px;white-space:nowrap;text-decoration:none;
  border:1px solid var(--sb-nuage-f);
  background:var(--sb-bg);color:var(--sb-fg);
  font-family:var(--sb-font-mono,ui-monospace,SFMono-Regular,Menlo,monospace);
  font-size:max(.75rem,12px);letter-spacing:.12em;text-transform:uppercase;
  transition:background .22s ease,color .22s ease,border-color .22s ease}
.sb-nuage__n{
  border-radius:999px;padding:.05rem .45rem;font-weight:700;font-size:max(.75rem,12px);
  background:var(--sb-nuage-f);color:var(--sb-bg)}

/* La pilule d'une marque est OPAQUE, et ce n'est pas cosmétique : deux
   étiquettes qui se croisent doivent s'occulter proprement. Un mot lisible à
   moitié caché vaut mieux que deux mots superposés illisibles. */
.sb-nuage__tag{
  position:absolute;left:var(--x);top:var(--y);transform:translate(-50%,-50%);
  pointer-events:auto;white-space:nowrap;text-decoration:none;
  padding:.34rem .7rem;border-radius:999px;font-size:max(.8125rem,13px);
  background:var(--sb-bg);color:var(--sb-fg);
  border:1px solid color-mix(in srgb,var(--sb-nuage-f) 46%,transparent);
  transition:background .22s ease,color .22s ease,border-color .22s ease}
a.sb-nuage__tag:hover,.sb-nuage__fam a:hover{
  background:var(--sb-accent);color:var(--sb-accent-fg);border-color:transparent}

/* L'ALLUMAGE PAR FAMILLE N'A BESOIN NI DE `:has()` NI DE SCRIPT : les grappes
   sont en `pointer-events:none`, seules leurs pilules reprennent la main, donc
   `:hover` sur une grappe ne se déclenche que par l'une d'elles. On éteint tout
   dès qu'une grappe est touchée, puis on rallume celle-là. C'EST LE SOCLE, et
   il fonctionne script coupé. */
.sb-nuage__plan:hover .sb-nuage__gr,
.sb-nuage__plan:focus-within .sb-nuage__gr{opacity:var(--sb-nuage-eteint)}
.sb-nuage__plan .sb-nuage__gr:hover,
.sb-nuage__plan .sb-nuage__gr:focus-within{opacity:1}

/* LE VERROU, armé par le script seulement (voir sb_nuage_js : `:hover` ne peut
   pas produire un état qui survit au départ du pointeur). Sans `.pilote`, aucune
   des quatre règles ci-dessous ne s'applique et la carte reste exactement dans
   l'état ci-dessus — le repli est entier, pas dégradé.
   On éteint PLUS FORT qu'au simple survol : les autres grappes passent SOUS
   celle qui est centrée, et à .24 elles la brouillaient. */
.sb-nuage__plan.pilote.vise .sb-nuage__gr{opacity:.17}
.sb-nuage__plan.pilote .sb-nuage__gr[data-vise]{
  opacity:1;z-index:4;translate:var(--dx) var(--dy);scale:1.12}
/* Le noyau RECULE au lieu de disparaître : il rétrécit en s'effaçant, donc on
   comprend qu'il est derrière et non supprimé. C'est la place qu'il cède. */
.sb-nuage__plan.pilote.vise .sb-nuage__coeur{opacity:0;transform:translate(-50%,-50%) scale(.78)}
.sb-nuage__plan.pilote.vise .sb-nuage__hub{opacity:0}
.sb-nuage__plan.pilote .sb-nuage__gr[data-vise] .sb-nuage__fam a{
  background:var(--sb-accent);color:var(--sb-accent-fg);border-color:transparent}
.sb-nuage__plan.pilote .sb-nuage__gr[data-vise] .sb-nuage__n{
  background:color-mix(in srgb,var(--sb-accent-fg) 26%,transparent);color:var(--sb-accent-fg)}

.sb-nuage__leg{
  display:flex;flex-wrap:wrap;gap:.5rem 1.4rem;list-style:none;margin:1.25rem 0 0;padding:0;
  font-family:var(--sb-font-mono,ui-monospace,SFMono-Regular,Menlo,monospace);
  font-size:max(.75rem,12px);letter-spacing:.11em;text-transform:uppercase;
  color:color-mix(in srgb,var(--sb-fg) 76%,var(--sb-bg))}
.sb-nuage__leg li{display:flex;align-items:center;gap:.5rem}
.sb-nuage__leg b{color:var(--sb-fg);font-weight:400}
.sb-nuage__pas{width:.5625rem;height:.5625rem;border-radius:50%;
  display:inline-block;background:var(--sb-nuage-f)}

/* ── Le repli de la carte ────────────────────────────────────────────────
   Un placement absolu ne se réagence pas : sous 880 px les étiquettes se
   chevauchent quoi qu'on règle. On ne comprime donc pas la carte, on la REND AU
   FLUX — et ce repli ne coûte AUCUN nœud : ce sont les mêmes grappes, les mêmes
   étiquettes, remises en ligne. Il garde même ce que le plan apportait : les
   marques restent groupées sous leur famille. */
@media (max-width:880px){
  .sb-nuage--carte .sb-nuage__plan{aspect-ratio:auto;display:grid;gap:1rem}
  .sb-nuage--carte .sb-nuage__svg,
  .sb-nuage--carte .sb-nuage__coeur{display:none}
  .sb-nuage--carte .sb-nuage__gr{
    position:static;inset:auto;pointer-events:auto;
    animation:none;translate:none;scale:none;opacity:1;
    display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:.5rem}
  .sb-nuage--carte .sb-nuage__fam,
  .sb-nuage--carte .sb-nuage__tag{position:static;left:auto;top:auto;transform:none}
}

/* ── FORME « bulles » : les logos sur une sphère, sur fond clair ────────── */

/* LA PAIRE DE TONALITÉ, DÉCLARÉE ENTIÈRE — c'est le cas d'école de la règle.
   Cette forme bascule la section en CLAIR alors que l'accueil est sombre, et
   elle le fait pour une raison mesurée : le suffixe `-light` d'un fichier de
   logo désigne le FOND, donc une encre SOMBRE. Sur l'encre ces fichiers sont
   invisibles ; sur le papier, ce sont exactement les bons.
   Déclarer le fond sans le texte laisserait le titre de la section en ivoire sur
   crème — c'est la septième rupture de paire relevée sur ce site, et on ne la
   refait pas. Les deux jetons sont donc réécrits ENSEMBLE, et non un blanc
   littéral : la section reste juste si la marque repeint ses tonalités.
   Deux fois `.sb-marques` pour passer devant `.sb-ink`/`.sb-paper` de
   patterns.css, qui valent (0,1,0) : la forme doit gagner quelle que soit la
   position de la section.

   ⚠️ ET CE MODULE NE PEUT PAS GAGNER PLUS QUE ÇA — c'est sa place dans la
   chaîne, pas une faiblesse : il est concaténé AVANT l'habillage de marque,
   qu'il « précise sans empêcher de tout reprendre ». Une feuille de marque qui
   peint le fond de cette section sur DEUX classes — `.sb-marques.sb-ink`, ce que
   fait brands/yhb/style.css §13 — vaut (0,2,0) comme la règle ci-dessous et
   passe après elle : le fond sombre revient pendant que le texte, lui, passe en
   encre. Fond sombre sur texte sombre, par la porte de derrière.
   La marque doit alors redéclarer la paire de son côté, sur trois classes. C'est
   fait pour YHB, avec le raisonnement complet, dans brands/yhb/style.css — au
   même endroit que ses teintes de famille. Toute marque qui reprend le fond de
   cette section doit faire de même. */
/* ⚠️ ARBITRAGE RENVERSÉ, 19.08.2026. Cette forme basculait TOUTE la section en
   clair, pour que les logos — fournis pour du blanc — restent lisibles. La
   demande est maintenant l'inverse : fond sombre, logos clairs. Or les deux ne
   s'opposent pas, parce que ce n'est pas la SECTION qui doit être claire, c'est
   la PLAQUE. Elle l'est déjà, explicitement, en jetons `--sb-paper-*` : chaque
   disque porte son propre fond et son propre texte.
   La section garde donc la tonalité de la page, et le raisonnement d'origine
   tient toujours — un logo pour fond blanc est posé sur du blanc. Il l'est
   simplement sur 5,75 rem de disque au lieu de toute la largeur.
   La règle jumelle de brands/yhb/style.css §14 est retirée dans le même geste :
   laissée seule, elle rendrait le fond clair par la porte de derrière. */

/* L'INCLINAISON DE L'AXE — le seul réglage de cette forme, et il est LIBRE.
   N'importe quelle valeur : 0deg rend le carrousel vu de face, 90deg fait
   basculer l'orbite à la verticale, 230deg la renverse. Une marque la repose
   dans sa feuille (`.sb-nuage--bulles{--sb-nuage-incl:20deg}`), le script la
   déplace au glissement vertical. */
/* ── LA PLAQUE NE SUIT PAS LA TONALITÉ, ET C'EST TOUT L'ENJEU ────────────
   Les plaques portaient `--sb-paper-bg`. En présentation « claire » ce jeton
   vaut une crème, et tout allait. En présentation SOMBRE il vaut #14141A : les
   disques devenaient noirs sur fond noir, et les logos — fournis pour du blanc,
   c'est la raison d'être de cette forme — disparaissaient purement et
   simplement. Mesuré en ligne : fond de plaque rgb(20,20,26).
   Une plaque est une FEUILLE, et une feuille ne change pas de couleur quand on
   éteint la lumière. Elle a donc sa propre paire, qui reste claire dans les
   deux présentations.
   Les valeurs sombres sont surchargeables par la marque
   (`--sb-nuage-plaque-bg-sombre`) : le module ne connaît pas la crème de
   chacune, il pose un blanc neutre et laisse la marque dire le sien. */
.sb-nuage--bulles{
  margin-top:1.5rem;--sb-nuage-incl:45deg;position:relative;
  --sb-nuage-plaque-bg:var(--sb-paper-bg);
  --sb-nuage-plaque-fg:var(--sb-paper-fg)}
[data-sb-presentation="sombre"] .sb-nuage--bulles{
  --sb-nuage-plaque-bg:var(--sb-nuage-plaque-bg-sombre,#FFFFFF);
  --sb-nuage-plaque-fg:var(--sb-nuage-plaque-fg-sombre,#111111)}
/* ⚠️ LA SCÈNE DÉBORDE SA PROPRE BOÎTE, ET C'EST LA PERSPECTIVE QUI LE FAIT.
   Mesuré à 320 px : le cadre fait 264 px de large, la scène 413. Sa boîte de
   MISE EN PAGE fait bien 264 — c'est son étendue VISUELLE qui enfle, parce que
   `preserve-3d` rend l'union des enfants transformés, et que les plaques les
   plus proches de l'œil sont projetées plus loin du centre qu'elles ne le sont
   dans l'espace. Rétrécir le rayon ne réglerait le problème qu'à une largeur
   donnée : c'est la projection qu'il faudrait borner, pas la sphère.
   On CLIPPE donc, au niveau du cadre. `clip` et non `hidden` : `hidden` crée un
   conteneur de défilement, ce qui casserait le `position:sticky` d'un ancêtre
   et changerait le comportement des ancres. `clip` ne fait que couper.
   Ce qui est coupé, c'est le bord d'une plaque au moment où elle passe le plus
   près de l'œil — invisible à l'usage, et sans commune mesure avec les 56 px de
   défilement horizontal que la page gagnait sur toutes ses sections. */
.sb-nuage__cadre{position:relative;height:clamp(27.5rem,60vw,35rem);perspective:1150px}
/* ⚠️ LE CLIP NE VA PAS SUR LE CADRE, ET C'EST UNE RÈGLE DU MODÈLE 3D, PAS UN
   GOÛT. Posé là le 20.08 pour arrêter le débordement horizontal, il a fait
   DISPARAÎTRE TOUS LES LOGOS sur téléphone — plaques visibles, logos absents.
   La cause : une valeur d'`overflow` autre que `visible` force
   `transform-style: flat` sur l'élément. Le cadre porte la `perspective`, donc
   il ouvre le contexte 3D ; l'aplatir casse les contre-rotations que chaque
   plaque applique pour que son logo reste face à l'œil. Les logos se
   retrouvaient de profil, c'est-à-dire nulle part.
   On clippe donc UN CRAN AU-DESSUS, sur `.sb-nuage`, qui n'appartient pas au
   contexte 3D : le débordement est arrêté et la scène garde son relief.
   ⚠️ Ne jamais redescendre cette ligne sur `__cadre` ni sur `__scene`. */
.sb-nuage{overflow-x:clip}
.sb-nuage__scene{position:absolute;inset:0;display:grid;place-items:center;
  transform-style:preserve-3d;--r:clamp(9.125rem,24vw,14.75rem);
  /* L'AXE DE ROTATION EST INCLINÉ, PAS LA SPHÈRE. Le basculement porte sur la
     scène : la sphère continue de tourner autour de son propre axe vertical,
     mais cet axe penche. C'est ce qui donne une orbite oblique plutôt qu'un
     carrousel vu de face. Chaque plaque défait l'inclinaison de son côté, sinon
     les logos arriveraient couchés.
     ⚠️ L'ANGLE EST DÉCLARÉ PLUS HAUT, SUR `.sb-nuage--bulles`, ET C'EST LA
     CONDITION POUR QU'IL SOIT RÉGLABLE. Écrit ici, il gagnait contre toute
     valeur venue du dessus : ni la feuille de marque ni le script ne pouvaient
     le changer, et 45° devenait une constante déguisée en variable. */
  transform:rotateZ(var(--sb-nuage-incl,45deg))}
.sb-nuage__sph{
  position:relative;width:0;height:0;list-style:none;margin:0;padding:0;
  transform-style:preserve-3d;
  --dur:78s;                   /* un tour complet ; ~19 s pour traverser la face */
  animation:sb-nuage-tour var(--dur) linear infinite}
@keyframes sb-nuage-tour{to{transform:rotateY(360deg)}}

/* LES TROIS NIVEAUX. `__t` place sur la sphère, `__pl` défait le placement,
   `__c` défait la rotation — le logo reste face au lecteur pendant tout le tour.
   ⚠️ Les trois portent `transform-origin:0 0` : sans cela chacun pivoterait
   autour du centre de SA plaque, et la sphère se disloquerait d'une demi-largeur
   à chaque niveau. */
.sb-nuage__t{
  position:absolute;left:0;top:0;transform-origin:0 0;transform-style:preserve-3d;
  transform:rotateY(var(--az)) rotateX(var(--el)) translateZ(var(--r));
  transition:transform .4s ease}
.sb-nuage__pl{display:block;transform-origin:0 0;transform-style:preserve-3d;
  transform:rotateX(calc(-1 * var(--el))) rotateY(calc(-1 * var(--az)))}
/* DEUX animations sur DEUX propriétés disjointes : la contre-rotation prend
   `transform`, la profondeur ne prend que `opacity` et la propriété INDÉPENDANTE
   `scale`. Écrites toutes deux sur `transform`, la seconde aurait effacé la
   première et la plaque se serait remise à tourner.
   ⚠️ ET LA PROFONDEUR EST AUSSI ÉCRITE AU REPOS, en dur, par `cos(--az)` :
   patterns.css coupe TOUTES les animations en mouvement réduit
   (`*{animation:none!important}`). Sans ce calcul statique, les vingt plaques
   se retrouveraient à pleine opacité et à pleine taille, superposées — la
   préférence d'accessibilité aurait rendu la section illisible. Là, elle
   s'immobilise sur une sphère JUSTE, correctement dégradée. */
.sb-nuage__c{
  display:block;transform-origin:0 0;
  /* `--tour` est écrit par le script ; absent, il vaut 0 et la formule
     retrouve exactement son comportement d'origine. C'est ce qui permet de
     supprimer l'animation de profondeur en mode piloté sans rien perdre. */
  --sb-nuage-prof:calc((cos(var(--az) + var(--tour,0deg)) + 1) / 2);
  /* ⚠️ LE PLANCHER EST PASSÉ DE .2 À .55 le 22.08.2026, ET C'EST UNE CONTRAINTE
     DE LISIBILITÉ, PAS UN GOÛT. À .2, une plaque du fond de sphère porte son
     monogramme à **2,19:1** — mesuré par axe-core, qui compose correctement
     l'opacité de cet ancêtre : l'encre #0B0B0E et la plaque #F4F1EA se
     retrouvent toutes deux fondues vers le fond, à #484748 sur #7c7b7b. Le seuil
     AA est 4,5:1, et la porte qualité refusait la livraison.
     .55 est le minimum qui passe, trouvé en itérant et en MESURANT : .45 rendait
     encore 3,79:1, .55 lève la violation. Ce n’est pas une marge confortable : la profondeur
     reste lisible — le rapport d'échelle (.72 → 1.02) continue de porter le
     relief, et c'est lui qui fait l'essentiel de l'effet.
     Même arbitrage que le dégradé du bouton, assombri de #E6008A à #d80082 pour
     tenir le même seuil : dans cette maison le contraste se corrige, il ne
     s'excuse pas. */
  opacity:calc(.55 + var(--sb-nuage-prof) * .45);
  scale:calc(.72 + var(--sb-nuage-prof) * .3);
  animation:sb-nuage-contre var(--dur,78s) linear infinite,
            sb-nuage-prof   var(--dur,78s) linear infinite;
  animation-delay:0s,calc(var(--azn) * var(--dur,78s) * -1)}
@keyframes sb-nuage-contre{
  from{transform:rotateY(0deg) rotateZ(calc(-1 * var(--sb-nuage-incl,45deg)))}
  to{transform:rotateY(-360deg) rotateZ(calc(-1 * var(--sb-nuage-incl,45deg)))}}
@keyframes sb-nuage-prof{
  0%{opacity:1;scale:1.02}
  12.5%{opacity:.86;scale:.973}
  25%{opacity:.539;scale:.86}
  37.5%{opacity:.252;scale:.747}
  50%{opacity:.16;scale:.7}
  62.5%{opacity:.252;scale:.747}
  75%{opacity:.539;scale:.86}
  87.5%{opacity:.86;scale:.973}
  100%{opacity:1;scale:1.02}}

/* R83, ET C'EST LA RAISON D'ÊTRE DE CETTE FORME. Un logo est fourni pour un
   FOND, et presque tous le sont pour du blanc. La plaque le lui rend, en jetons
   et non en blanc littéral, donc elle reste correcte en présentation « jour »
   comme en « nuit ». `filter:none` est explicite : une feuille de marque a déjà
   repeint des logos avec `brightness(0) invert(1)`, et six diagnostics erronés
   ont suivi. */
.sb-nuage__b{
  position:relative;box-sizing:border-box;
  width:5.75rem;height:5.75rem;border-radius:50%;padding:.625rem;
  display:flex;align-items:center;justify-content:center;
  transform:translate(-50%,-50%);text-decoration:none;
  background:var(--sb-nuage-plaque-bg);color:var(--sb-nuage-plaque-fg);
  border:1px solid color-mix(in srgb,var(--sb-nuage-plaque-fg) 12%,transparent);
  box-shadow:0 .5rem 1.5rem -.75rem color-mix(in srgb,var(--sb-nuage-plaque-fg) 42%,transparent);
  overflow:hidden}
/* LE CARRÉ NE TIENT PAS DANS LE ROND, ET LE MOT NE TIENT PAS DANS LE CARRÉ.
   `object-fit:contain` ajuste le logo à sa BOÎTE. Une boîte carrée de 70 %
   convient à un monogramme — c'est le plus grand carré inscrit dans un cercle,
   à 70,7 % du diamètre. Elle étrangle un logo-mot : à 7,69:1, il n'occupe plus
   que 6 px de haut sur 49 de large.
   Un logo-mot reçoit donc une boîte LARGE ET BASSE. 88 % × 44 % : la diagonale
   vaut √(0,88² + 0,44²) = 0,98 du diamètre, donc les coins restent DANS le
   disque même si un fichier remplissait exactement la boîte. Et comme `contain`
   ne remplit jamais les deux axes à la fois, un mot de 5:1 y est peint 88 % ×
   17,6 % — bien au large, et 80 % plus grand qu'avant.
   Le seuil de 2,2 est posé côté serveur : lui seul connaît le rapport du
   fichier. `overflow:hidden` sur la plaque reste le dernier garde-fou. */
/* ⚠️ ET LA BOÎTE EST DITE, PAS DEVINÉE — c'est ce qui manquait, et c'est ce qui
   vidait les plaques sur iPhone. `max-width`/`max-height` ne sont que des
   BORNES : entre zéro et elles, c'est le moteur qui choisit la largeur d'une
   image sans dimension intrinsèque, et WordPress n'en donne aucune à un SVG.
   Mesuré le 21.08.2026 : sous WebKit, 19 plaques sur 20 peignaient leur logo
   sur 0 px de large — un disque nu, que la profondeur assombrit jusqu'à 20 %
   d'opacité, donc un rond gris. Sous Chromium le même défaut passait inaperçu
   parce que la taille d'objet par défaut donne, par chance, une boîte non
   nulle. `--lw` et `--lh` viennent maintenant de sb_constellation_boite(), en
   pourcents de plaque : le rectangle exact du rapport du fichier, inscrit dans
   les bornes ci-dessous. Elles restent, en garde-fou et en repli. */
.sb-nuage__logo{width:var(--lw,70%);height:var(--lh,70%);
  max-width:88%;max-height:70%;object-fit:contain;filter:none}
.sb-nuage__logo--mot{width:var(--lw,88%);height:var(--lh,44%);max-height:44%}
.sb-nuage__mono{font-family:var(--sb-font-title);font-weight:700;font-size:max(.875rem,14px);
  /* LE FOND EST REDÉCLARÉ ICI, ET IL EST DÉJÀ CELUI DE LA PLAQUE. Ce n'est pas
     une correction de couleur : mesuré au navigateur, le monogramme est à
     17,4:1 sur sa plaque, très au-dessus du seuil.
     C'est le CONTRÔLE qui ne sait pas le voir. axe-core retrouve le fond d'un
     texte en remontant la géométrie ; la plaque est posée sur une sphère en
     transformation 3D, la remontée échoue, et il retombe sur le fond de la
     section — sombre — d'où un 1,05 rapporté et un blocage.
     Poser le fond SUR le texte lui-même le rend lisible au contrôle sans rien
     changer à l'écran : c'est exactement la couleur qui est déjà dessous.

     ⚠️ ET LA COULEUR AVEC, depuis le 22.08.2026 — le fond seul ne suffisait pas.
     Déclarer le fond sans l'encre laissait axe comparer une plaque CLAIRE
     (`--sb-paper-bg`, #F4F1EA) à une couleur HÉRITÉE de la section sombre : il
     rapportait toujours un manquement, et il avait raison sur ce qu'il lisait.
     La paire se déclare donc entière, exactement comme `.sb-nuage__nom` le fait
     six lignes plus bas. Rien ne change à l'écran : c'est déjà cette encre-là. */
  background:var(--sb-nuage-plaque-bg);color:var(--sb-nuage-plaque-fg)}

/* LE NOM NE DISPARAÎT PAS, IL SE DÉPLACE. Plusieurs logos sont des monogrammes
   muets — un « H », une plante : le visiteur voit une marque et ne sait pas
   laquelle. Le nom reste donc DANS le lien (c'est lui que lit un lecteur d'écran
   et que suit la tabulation) et revient à l'écran sous la plaque, au survol
   comme au focus. En `position:absolute` : la plaque ne change pas de taille,
   donc la sphère ne se réagence pas quand on la parcourt.
   `visibility:hidden` ET `opacity:0` au repos, pas seulement l'opacité : c'est
   ce qui le rend invisible AU CANAL MOBILE, qui mesure les recouvrements sur la
   page au repos. Aucune couche ne couvre donc jamais une commande, et ce nuage
   n'a rien à congédier. */
.sb-nuage__nom{
  position:absolute;left:50%;top:calc(100% + .375rem);transform:translateX(-50%);
  white-space:nowrap;font-size:max(.75rem,12px);font-weight:600;
  padding:.125rem .625rem;border-radius:999px;pointer-events:none;
  background:var(--sb-nuage-plaque-bg);color:var(--sb-nuage-plaque-fg);
  visibility:hidden;opacity:0;transition:opacity .16s ease}
.sb-nuage__b:hover .sb-nuage__nom,
.sb-nuage__b:focus-visible .sb-nuage__nom{visibility:visible;opacity:1}

/* ── LA ROTATION PILOTÉE ─────────────────────────────────────────────────
   La sphère ne s'arrête PLUS au survol : approcher la souris l'accélère au
   contraire, dans le sens du geste. L'ancienne pause était un choix inverse,
   assumé alors ; il est révoqué.
   Ces deux règles n'existent que sous `pilote`, posée par le script. Sans lui —
   et en mouvement réduit, où le script s'abstient — l'animation d'origine
   reprend la main, inclinaison comprise. Le repli est entier, pas dégradé. */
/* DEUX AXES, ET C'EST LA CONDITION POUR CENTRER. Une plaque fait face au
   lecteur quand son azimut ET son élévation sont annulés. Avec la seule
   rotation en Y, `--tour` amenait la plaque sur le méridien de face mais la
   laissait à sa hauteur : une marque du haut de la sphère restait en haut, et
   « ramener au centre » ne pouvait pas marcher. `--incX` corrige l'élévation. */
.sb-nuage--bulles.pilote .sb-nuage__sph{
  animation:none;
  transform:rotateX(var(--incX,0deg)) rotateY(var(--tour,0deg))}

/* ── ON ATTRAPE LA SPHÈRE ────────────────────────────────────────────────
   Le curseur annonce le geste. `touch-action:pan-y` laisse le DÉFILEMENT
   vertical au doigt et ne prend que l'horizontal : sans lui, la page se
   bloquerait dès qu'on pose le doigt sur une section qui occupe la moitié
   de l'écran. `user-select:none` évite qu'un glissement sélectionne les noms. */
.sb-nuage--bulles.pilote .sb-nuage__cadre{
  cursor:grab;touch-action:pan-y;user-select:none;-webkit-user-select:none}
.sb-nuage--bulles.pilote .sb-nuage__cadre:active{cursor:grabbing}

/* La plaque visée sort de son orbite, comme au focus : c'est le même geste de
   mise en avant, et la fiche parle d'elle. */
.sb-nuage--bulles.pilote .sb-nuage__t[data-vise]{
  transform:rotateY(var(--az)) rotateX(var(--el)) translateZ(calc(var(--r) + 2.75rem));
  z-index:5}
.sb-nuage--bulles.pilote .sb-nuage__t[data-vise] .sb-nuage__c{opacity:1!important;scale:1.2!important}
.sb-nuage--bulles.pilote.fiche-ouverte .sb-nuage__t:not([data-vise]) .sb-nuage__c{opacity:.18!important}
/* Pendant la lecture, les autres plaques ne captent plus le clic : viser la
   bulle ne doit pas ouvrir la fiche d'une voisine passée dessous. */
.sb-nuage--bulles.pilote.fiche-ouverte .sb-nuage__t:not([data-vise]){pointer-events:none}

/* ── LA FICHE ────────────────────────────────────────────────────────────
   Posée SOUS la sphère et non par-dessus : un panneau flottant recouvrirait
   des plaques, donc des commandes, ce que le canal mobile refuse. */
/* UNE BULLE, PAS UN PANNEAU — ET HORS DU CONTEXTE 3D.
   Elle se pose au centre, sous la plaque qu'on vient d'y amener, pour se lire
   comme SA légende et non comme un encart de bas de section.
   ⚠️ ELLE EST SŒUR DU CADRE, PAS SA FILLE, et c'est la condition pour qu'on la
   VOIE. Le cadre porte `perspective`, donc tout enfant y est placé en
   profondeur : les plaques avancent jusqu'à 236 px vers le lecteur par leur
   `translateZ`, la bulle resterait à zéro, et dans un contexte 3D `z-index` ne
   tranche pas la profondeur. Elle se dessinait derrière la sphère, invisible.
   Elle chevauche des plaques, ce que la porte mobile refuse — mais seulement
   AU REPOS, et au repos elle est `hidden`, donc absente de la mesure. C'est le
   même raisonnement que pour le nom sous une plaque. */
.sb-nuage__fiche{
  position:relative;margin:.25rem auto 0;
  width:min(26rem,92%);text-align:center;
  padding:.9rem 2.2rem .9rem 1.1rem;border-radius:1rem;
  background:var(--sb-nuage-plaque-bg);color:var(--sb-nuage-plaque-fg);
  border:1px solid color-mix(in srgb,var(--sb-nuage-plaque-fg) 12%,transparent);
  box-shadow:0 1rem 2.5rem -1rem color-mix(in srgb,var(--sb-nuage-plaque-fg) 38%,transparent)}
.sb-nuage__fiche[hidden]{display:none}
.sb-nuage__fiche-nom{margin:0 0 .25rem;font-size:1.05rem}
.sb-nuage__fiche-txt{margin:0;font-size:max(.875rem,14px)}
.sb-nuage__fermer{
  position:absolute;top:.5rem;right:.5rem;width:2rem;height:2rem;
  display:flex;align-items:center;justify-content:center;
  border:0;border-radius:50%;background:transparent;cursor:pointer;
  font-size:1.4rem;line-height:1;color:inherit}
.sb-nuage__fermer:hover{background:color-mix(in srgb,currentColor 10%,transparent)}
/* L'ordre inverse, sinon le logo arrive de travers : ce qui est appliqué en
   dernier doit être défait en premier. */
.sb-nuage--bulles.pilote .sb-nuage__c{
  animation:none;
  transform:rotateY(calc(-1 * var(--tour,0deg)))
            rotateX(calc(-1 * var(--incX,0deg)))
            rotateZ(calc(-1 * var(--sb-nuage-incl,45deg)))}
/* La plaque focalisée sort de son orbite — un `translateZ` de plus, donc valable
   quelle que soit la rotation en cours — et les autres tombent, si bien
   qu'aucune plaque opaque ne peut plus la masquer. Les `!important` ne sont pas
   une facilité : une déclaration d'auteur normale PERD contre une animation. */
.sb-nuage__t:focus-within{
  transform:rotateY(var(--az)) rotateX(var(--el)) translateZ(calc(var(--r) + 2.75rem))}
.sb-nuage__t:focus-within .sb-nuage__c{opacity:1!important;scale:1.14!important}
.sb-nuage__sph:has(.sb-nuage__t:focus-within) .sb-nuage__t:not(:focus-within) .sb-nuage__c{
  opacity:.12!important}

/* ── Le repli de l'amas ──────────────────────────────────────────────────
   Vingt libellés ne tiennent pas sur une sphère de 292 px de diamètre — aucun
   réglage de rayon ne rattrape ça. On neutralise les trois transformées, les
   trois animations et la perspective, et le `<ul>` redevient une grille de
   tuiles : les mêmes nœuds, remis dans le flux, et le nom enfin toujours
   visible — ce que le survol donnait, et que le tactile n'a pas. */
@media (max-width:700px){
  /* ⚠️ ARBITRAGE RENVERSÉ. Cette requête METTAIT LA SPHÈRE À PLAT : perspective
     coupée, plaques remises dans le flux, vingt tuiles empilées. Le raisonnement
     tenait tant que la sphère était décorative — le tactile n'a pas de survol,
     donc les noms n'apparaissaient jamais. Il ne tient plus : la sphère se
     manipule maintenant au doigt, et le nom vient dans la fiche au toucher.
     Mesuré à 390 px avant correction : une colonne de 2062 px de haut, avec des
     disques de 334 px. Ce n'était pas un repli, c'était une autre page.
     La sphère RESTE donc une sphère, à l'échelle de l'écran. */
  .sb-nuage--bulles .sb-nuage__cadre{height:clamp(20rem,86vw,26rem)}
  .sb-nuage--bulles .sb-nuage__scene{--r:clamp(6.5rem,33vw,9.5rem)}
  /* La plaque rétrécit AVEC le rayon, sinon vingt disques de 5,75 rem sur une
     sphère deux fois plus petite se recouvrent au lieu de s'ordonner. */
  .sb-nuage--bulles .sb-nuage__b{width:4rem;height:4rem;padding:.4rem}
  /* Le nom au repos resterait posé sur les plaques voisines, et la porte
     mobile mesure les recouvrements page au repos : il continue de n'aparaître
     qu'au survol ou au focus, et la fiche le porte au toucher. */
  .sb-nuage__fiche{width:min(20rem,94%);padding:.8rem 2rem .8rem 1rem}
  .sb-nuage__fiche-nom{font-size:1rem}
}


/* ══ LE REPLI EN MOUVEMENT RÉDUIT — une grille, pas une sphère figée ═══════
 R83. Le script sort dès `prefers-reduced-motion: reduce` en disant « le repli
 statique de la feuille suffit ». CE REPLI N'EXISTAIT PAS. Ce qui restait
 était la sphère elle-même, immobile : vingt plaques posées en 3D, dont
 plusieurs recouvertes à plus de 95 % par celles de devant — et comme rien ne
 tourne plus, elles n'en ressortent jamais.

 MESURÉ le 20.08.2026 par le canal mobile, qui s'exécute précisément en
 mouvement réduit : NEUF marques définitivement inatteignables, à 71, 69, 65,
 85, 53, 77 et 83 % de recouvrement. Ce n'était pas un faux positif du canal —
 c'était un vrai défaut, et le canal a mis quatre mois à être cru.

 Une sphère immobile n'est plus une sphère, c'est un tas. On rend donc ce que
 la sphère voulait dire — les vingt marques, toutes visibles, toutes
 cliquables — sous la forme qui n'a pas besoin de mouvement : une grille.

 ⚠️ IL FAUT DÉFAIRE LES TROIS NIVEAUX, pas seulement l'animation. `__t` place
 sur la sphère, `__pl` défait le placement, `__c` défait la rotation : laisser
 un seul des trois transforms disloque la grille. Et `perspective` doit tomber
 avec eux, sinon les plaques gardent leur profondeur et leur échelle.

 ⚠️ ET L'OPACITÉ AUSSI. La profondeur est écrite EN DUR par `cos(--az)` pour
 survivre au `*{animation:none!important}` de patterns.css — donc sans cette
 remise à 1, la moitié des marques resterait à demi transparente dans une
 grille où plus rien ne justifie qu'elles s'effacent. */
@media (prefers-reduced-motion: reduce){
.sb-nuage--bulles .sb-nuage__cadre{
  /* `!important` : la requête `max-width:700px` plus haut repose
     `height:clamp(20rem,86vw,26rem)` à poids égal. Sans lui, la grille se
     retrouvait dans une boîte de 335 px et débordait sur les témoignages. */
  height:auto !important;min-height:0;perspective:none;
}
.sb-nuage--bulles .sb-nuage__scene{
  position:static;inset:auto;display:block;transform:none;transform-style:flat;
}
.sb-nuage--bulles .sb-nuage__sph{
  /* ⚠️ `100%` ET NON `auto` — mesuré, pas supposé. La règle d'origine pose
     `width:0` sur cette liste ; `auto` la rouvre bien, mais en largeur
     ADAPTÉE AU CONTENU : 92 px, soit UNE colonne, et les vingt marques en
     bande verticale de 2436 px. Avec `100%` la grille reprend les 1124 px du
     conteneur, se range en neuf colonnes et tient en 396 px. */
  position:static;width:100%;height:auto;transform:none;transform-style:flat;
  animation:none;
  display:grid;grid-template-columns:repeat(auto-fit,minmax(5.5rem,1fr));
  gap:clamp(1rem,3vw,1.75rem);place-items:center;
  padding:clamp(1rem,3vw,2rem) 0;
}
.sb-nuage--bulles .sb-nuage__t,
.sb-nuage--bulles .sb-nuage__pl,
.sb-nuage--bulles .sb-nuage__c{
  position:static;transform:none;transform-style:flat;
  opacity:1;scale:1;transition:none;
}
/* ⚠️ IL Y A UN QUATRIÈME TRANSFORM, ET IL MANQUAIT. La plaque elle-même porte
   `translate(-50%,-50%)` — c'est ce qui la CENTRE sur son point d'ancrage tant
   qu'elle est posée sur la sphère. Dans la grille il n'y a plus de point
   d'ancrage : la cellule est déjà la place de la plaque, et ce décalage la
   sort d'un demi-disque vers le haut et vers la gauche.
   MESURÉ le 21.08.2026 à 390 px : la colonne de gauche dépassait de 13,7 px
   hors de la section, donc RECOUPÉE par `overflow-x:clip` — Blackformance,
   Je suis moi, Lina Hadid et Renaissance 69 amputées de leur moitié gauche,
   logo compris, et la première rangée coupée par le haut. Le repli montrait
   ainsi des logos tronqués, ce qu'il existe justement pour éviter. */
.sb-nuage--bulles .sb-nuage__b{transform:none}
}

/* ══ Ruban de marques — GÉNÉRÉ par lib/ruban.php, ne pas éditer ══════════════
   Le mouvement est écrit dans le script : il anime la POSITION DE DÉFILEMENT,
   pour que le défilement manuel — molette, doigt, flèches — continue de
   fonctionner pendant. Le CSS ne fait donc ici que deux choses : garantir une
   rangée qui déborde, et écarter ce qui se battrait avec cette position.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Étage bas : le repli utile ────────────────────────────────────────────
   Une rangée qui déborde, rien de plus. Toute marque qui a une opinion la
   surcharge avec un sélecteur plus lourd — brands/yhb le fait déjà. */
[data-sb-ruban]{display:flex;flex-wrap:nowrap;overflow-x:auto}
[data-sb-ruban] > *{flex:0 0 auto}

/* ── Étage haut : la bande défile pour de bon ──────────────────────────────
   Posé par le script, et seulement quand il défile réellement : ni sous
   `prefers-reduced-motion`, ni quand la bande tient déjà dans sa largeur. */
.sb-marques .sb-grid[data-sb-ruban][data-sb-ruban-actif]{
  /* L'accroche au défilement ramènerait la bande sur la plaque la plus proche
     à chaque arrêt du mouvement : un ruban qui bégaie. Elle a tout son sens
     quand la bande est manuelle — c'est pourquoi on la retire ICI seulement. */
  scroll-snap-type:none;
  /* Un `scroll-behavior:smooth` hérité lisserait CHAQUE écriture de position :
     le ruban se traînerait à une fraction de sa vitesse, sans que rien ne le
     dise. Le script écrit soixante fois par seconde, il ne veut pas d'aide. */
  scroll-behavior:auto;
  /* La bande atteint sa fin en permanence. Sans cela, la chaîne de défilement
     remonte à la page — ou déclenche le geste « page précédente » du
     navigateur, ce qui coûte la visite. */
  overscroll-behavior-x:contain;
}
.sb-marques .sb-grid[data-sb-ruban][data-sb-ruban-actif] > *{scroll-snap-align:none}

/* ── Le fondu des bords ────────────────────────────────────────────────────
   Il dit « ça continue » là où le rognage dirait « ça s'arrête ». Levé dès que
   le focus entre : le masque s'applique au dessin de l'élément, liseré de
   focus compris, et un anneau de focus mangé sur ses deux bords n'apprend plus
   rien à personne. Le ruban étant AUSSI en pause à ce moment-là, l'ensemble se
   lit comme une seule décision : au clavier, la bande s'arrête et se montre
   entière. */
.sb-marques .sb-grid[data-sb-ruban][data-sb-ruban-actif]:not(:focus-within){
  -webkit-mask:linear-gradient(90deg,transparent 0,#000 7%,
    #000 calc(100% - 7%),transparent 100%);
  mask:linear-gradient(90deg,transparent 0,#000 7%,
    #000 calc(100% - 7%),transparent 100%);
}

/* ── Mouvement réduit ──────────────────────────────────────────────────────
   Le script ne pose JAMAIS l'attribut actif dans ce cas, et retire la copie
   s'il l'avait déjà posée : sous ce réglage, la bande est celle d'aujourd'hui,
   au nœud près. Ces règles sont la ceinture — si l'attribut arrivait par un
   autre chemin, rien ne bougerait et l'accroche reviendrait.
   (patterns.css coupe déjà toute `animation` ici ; on n'en émet aucune, et
   c'est bien pour cela que le réglage devait être traité dans le script.) */
@media (prefers-reduced-motion:reduce){
  .sb-marques .sb-grid[data-sb-ruban][data-sb-ruban-actif]{
    scroll-snap-type:x proximity;scroll-behavior:auto;
  }
  .sb-marques .sb-grid[data-sb-ruban][data-sb-ruban-actif]:not(:focus-within){
    -webkit-mask:none;mask:none;
  }
}

/* ── habillage de la marque — brands/yhb/style.css ── */
/* ═══════════════════════════════════════════════════════════════════════════
   Yan Haltey Brands — habillage de marque
   ═══════════════════════════════════════════════════════════════════════════

   Lu par lib/build.php, concaténé APRÈS patterns.css : ce fichier surcharge.

   Toutes les valeurs viennent de proto-v2/assets/site.css, relevées une par une.
   Rien n'est inventé ici : c'est un PORTAGE de la mise en page du prototype sur
   le markup .sb-* de la chaîne, pas un nouveau design.

   Ce que ce fichier ne peut pas faire, et qui restera différent :
     — le fond étoilé animé (canvas + JS, hors thème) ;
     — le mode mixte à trois états (bouton d'en-tête + JS) ;
     — l'interaction du calculateur.
   ═══════════════════════════════════════════════════════════════════════════ */

:root{
  /* Le monogramme, nommé : l'en-tête ET la carte de visite le consomment.
     Deux copies du même data-URI finiraient par diverger. */
  --sb-monogramme:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 800 800'%3E%3Cpolygon fill='%23FAF8F3' points='471.06,281.97 471.06,396.1 328.94,396.1 328.94,281.97 186.83,186.83 186.83,325.39 186.83,396.1 186.83,412.24 186.83,538.21 186.83,609.27 328.94,609.27 328.94,538.21 471.06,538.21 471.06,609.27 613.17,609.27 613.17,538.21 613.17,412.24 613.17,396.1 613.17,325.39 613.17,186.83'/%3E%3C/svg%3E");

  --yhb-maxw:1180px;

  /* Le RELAIS du dégradé de marque : son arrêt intermédiaire, nommé parce que,
     lui aussi, sert À PLAT hors de tout dégradé — le rail vertical de la frise
     « Notre histoire » et le filet de survol des cartes ADN s'y éteignent, et le
     halo du héros en est un voile à 34 %. Trois recopies du même `#5B1A8C`, donc
     trois occasions de le voir dériver.
     Comme pour `--sb-accent-fond-amorce` dans tokens.css, le dégradé le CONSOMME
     au lieu de le redire : une seule écriture, plus de divergence possible.

     « relais » et non « violet » : ce qui le définit est sa fonction dans la
     rampe — il tient le milieu entre l'amorce presque noire et le magenta — et
     c'est à ce titre qu'on le reprend pour terminer un filet.

     MESURÉ, comme l'amorce, il ne lit que sur le clair : 9,44:1 sur le crème,
     1,72:1 sur l'encre commune, 1,86:1 sur l'encre profonde. Ses trois emplois
     sont DÉCORATIFS — un rail de 2 px, un filet de survol, un voile — donc hors
     du seuil de 4,5:1, qui ne vise que le texte. C'est pourquoi il reste figé
     ici : voir le rapport, où le remappage possible est décrit sans être posé. */
  --yhb-grad-relais:#5B1A8C;
  --yhb-grad:linear-gradient(112deg,#3A0E63 0%,var(--yhb-grad-relais) 28%,#E6008A 100%);
  /* ⚠️ LE DÉGRADÉ EST ILLISIBLE SUR L'ENCRE, ET C'EST MESURÉ. Ses deux premiers
     arrêts tombent à 1,24 et 1,72 sur le fond #14141A — le seuil des grands
     textes est de 3. Concrètement, le DÉBUT de chaque mot peint au dégradé
     s'efface : « Depuis toujours », « 2021 », et la seconde ligne des héros.
     Le troisième arrêt, lui, tient (4,13) — d'où l'impression que « ça se voit
     quand même », qui vient de la fin des mots.
     On relève donc les deux premiers, et EUX SEULS, de 32 % et 25 % vers le
     blanc en oklab — l'espace qui garde le mieux la chroma, vérifié contre srgb
     et oklch. Les valeurs sont calculées ICI, pas recopiées en dur : le jour où
     la marque change son violet, le rattrapage suit.
     ⚠️ SUR FOND CLAIR, ON NE TOUCHE À RIEN. Le violet profond y passe
     largement, et l'éclaircir le ferait au contraire disparaître. La correction
     appartient à la TONALITÉ, pas au dégradé. */
  --yhb-ease:cubic-bezier(.22,.61,.36,1);

  /* Les gros chiffres reprennent le dégradé de la marque, et non celui que le
     pattern calcule par `color-mix`.
     Relevé sur le prototype (`.val`, 68 px) : `linear-gradient(112deg,
     #3A0E63 0%, #5B1A8C 28%, #E6008A 100%)` — il part d'un violet presque noir.
     Le calcul du pattern partait d'un violet clair, si proche du magenta final
     que la transition ne se lisait plus : les chiffres paraissaient plats, et
     « 1 », trop court pour montrer une transition, paraissait uni.
     C'est `--yhb-grad`, déjà déclaré deux lignes plus haut.

     Le jeton est `--sb-degrade` et non `--sb-chiffres-degrade` : le pattern lit
     `var(--sb-degrade, <calcul par défaut>)`, l'auteur y a laissé le crochet
     exprès. Surcharger `--sb-chiffres-degrade` depuis `:root` ne servait à
     rien — le pattern le redéclare sur `.sb-chiffres`, qui gagne. */
  --sb-degrade:var(--yhb-grad);
  --yhb-ink:#0A0910;                 /* le fond réel du prototype, plus profond
                                        que --sb-ink-bg : relevé sur
                                        :root[data-theme="dark"] body */
  --yhb-surface:#FBF9F4;
  /* MÊME VALEUR que --yhb-surface, et ce n'est pas une redite : c'est un rôle
     différent. --yhb-surface est une plaque de MISE EN PAGE, elle suit la
     tonalité (voir le bloc de remappage plus bas) ; celle-ci est la plaque sur
     laquelle on pose un LOGO, un fichier image figé qui n'a pas de variante
     sombre. Elle ne doit donc jamais bouger, dans aucune présentation.
     Deux rôles qui divergent ont besoin de deux noms, sinon l'un des deux sera
     fatalement faux le jour où l'autre change. */
  --yhb-plaque-logo:#FBF9F4;

  /* ── Le fond SUR LEQUEL se détachent ces plaques ───────────────────────────
     Les trois prunes `#1A1226`, `#0C0B12`, `#070709` étaient écrites en dur au
     §13, dans le `radial-gradient` de la section des marques. On les nomme —
     mais d'un SEUL nom, parce qu'elles n'ont qu'un rôle à elles trois : elles
     ne sont pas trois couleurs, elles sont un fond.

     ⚠️ CELUI-CI RESTE FIGÉ, ET C'EST LA BONNE RÉPONSE, PAS UN RENONCEMENT.
     C'est le fond qui porte les plaques de logo, et une plaque de logo NE PEUT
     PAS suivre la tonalité : `--yhb-plaque-logo`, deux lignes plus haut, dit
     pourquoi — un logo est un fichier image figé, sans variante sombre. Si ce
     fond-ci suivait le sélecteur pendant que la plaque, elle, ne le peut pas,
     la tuile ne se détacherait plus de sa propre section.
     MESURÉ, plaque #FBF9F4 contre son fond :
       sur la prune (#0C0B12, l'arrêt médian)      18,61:1   la tuile se lit
       sur le crème (--sb-paper-bg, #F4F1EA)        1,07:1   la tuile disparaît
     Faire suivre la tonalité à ce fond, c'est donc effacer les tuiles en mode
     jour pour corriger un contraste que personne ne mesure — il n'y a AUCUN
     texte dessus : `h3` et `p` sont masqués au §13, il ne reste que l'image.
     Deux jetons figés qui se répondent valent mieux qu'un seul qui bouge.

     ⓘ Ce fond ne s'affiche PAS aujourd'hui, et ce n'est pas de son fait :
     `lib/entete.php` pose `body:has(.sb-fond-etoile) .sb-ink{background:transparent}`,
     qui pèse (0,2,1) contre les (0,2,0) de `.sb-marques.sb-ink` et gagne donc
     quelle que soit la cascade. Vérifié au navigateur en neutralisant le décor
     étoilé : la prune réapparaît aussitôt. Voir le rapport. */
  --yhb-fond-plaques:radial-gradient(130% 100% at 80% -10%,#1A1226 0%,#0C0B12 45%,#070709 100%);

  /* ── Le fond de la bande d'appel prune ─────────────────────────────────────
     `#130D23`, écrit en dur au §23 pour la clôture de plan-accès. Celui-ci, à
     l'inverse du précédent, DOIT suivre la tonalité : il ne porte aucune image
     figée, seulement un titre et une ligne de réassurance, dont la couleur
     vient de `--sb-ink-fg` — que le sélecteur remappe.
     MESURÉ sur ce fond resté prune :
       texte d'encre (#FAF8F3) dessus, tonalités neutre et sombre   17,83:1
       texte de crème (#0B0B0E) dessus, ce que produit le mode jour  1,04:1
     Soit exactement le défaut que `lib/entete.php` décrit déjà mot pour mot :
     « le texte passait au sombre et le fond restait noir ».
     Le remappage lui-même n'est pas ici — il vit dans `lib/entete.php`, avec
     les autres ; les lignes à y poser sont dans le rapport.

     ⓘ CE JETON N'A PLUS AUCUN PORTEUR, et c'est réglé plutôt que constaté.
     Il était consommé par `.sb-integre + .sb-cta.sb-band`, une combinaison de
     sections qui n'existe sur AUCUNE des 12 pages : plan-accès enchaîne
     hero → lieu → cta-final. Cette règle a été supprimée le 18.08.2026 avec
     onze autres du même genre — vérifié en les réinjectant en fin de `<head>`,
     donc avec priorité de dernier mot : zéro pixel de différence sur les 12
     pages. Une règle à qui l'on donne le dernier mot et qui ne change rien ne
     s'appliquait à rien.

     Le jeton est gardé : il nomme une intention — le fond de la bande d'appel
     — qui resservira le jour où un copy deck enchaînera ces deux sections. Un
     jeton sans porteur ne coûte rien ; une règle sans cible trompe. */
  --yhb-fond-appel:#130D23;

  --yhb-border:rgba(11,11,14,.12);
  --yhb-border-dark:rgba(244,241,234,.13);
  --yhb-soft:#ECE7DD;

  /* ── Les deux gris sourds : DÉRIVÉS, plus figés ────────────────────────────
     Ils valaient #C8C4BB et #56544F, écrits en dur. Ces deux valeurs sont
     justes — ce sont celles du prototype — mais une valeur figée ne suit pas le
     sélecteur de tonalité, et `lib/entete.php` prévient déjà, mot pour mot, que
     « ce qui ne suit pas, ce sont les couleurs écrites en dur dans une feuille
     de marque ». C'est exactement ce qui arrivait, et ce n'était pas un
     détail : le chapô de héros, le pied, le menu et les mentions gardaient leur
     gris CLAIR quand la présentation passait au clair, soit #C8C4BB sur crème —
     1,54:1 MESURÉ, illisible, sur les huit pages. Symétriquement #56544F
     tombait à 2,43:1 sur l'encre en présentation sombre.

     On les redéfinit donc comme ce qu'ils sont vraiment : la couleur de texte
     de la famille, tirée vers son fond. Les pourcentages ne sont pas choisis,
     ils sont RETROUVÉS depuis les valeurs relevées — 77 % redonne #C8C4BB sur
     l'encre, 68 % redonne #56544F sur le crème, à un point près sur chaque
     canal. En tonalité neutre le rendu est donc INCHANGÉ ; il ne change que là
     où il était faux. C'est le procédé de `temoignages.html`, qui retrouve ses
     propres teintes de la même façon et pour la même raison.

     Contrastes MESURÉS après dérivation : 7,06:1 en encre, 5,832:1 en papier —
     AA des deux côtés, dans les trois tonalités. */
  --yhb-text-soft-dark:color-mix(in srgb,var(--sb-ink-fg) 77%,var(--sb-ink-bg));
  --yhb-text-soft:color-mix(in srgb,var(--sb-paper-fg) 68%,var(--sb-paper-bg));
}

/* ── La plaque claire doit suivre la tonalité, comme l'encre ────────────────
   `--yhb-surface` est le fond des plaques posées SUR une zone crème : cartes de
   marques, cartes ADN, portes, encadré de formulaire. Leur texte vient de
   `--sb-paper-fg`, qui est remappé par le sélecteur de présentation ; la plaque,
   elle, était figée à l'ivoire. En présentation sombre le texte passait donc au
   clair sur une plaque restée claire — MESURÉ à 1,01:1, c'est-à-dire invisible,
   sur les portes de l'accueil, les cartes ADN du noyau et le plan d'accès.

   C'est mot pour mot le défaut que `lib/entete.php` a déjà rencontré sur
   `--yhb-ink` et corrigé au même endroit : « Les deux jetons doivent venir de la
   même famille, sans quoi le bandeau ne peut être juste que dans une tonalité
   sur deux. » On applique le même remède au jeton symétrique.

   La valeur sombre n'est pas inventée : c'est la recette de plaque que la
   feuille emploie déjà partout en tonalité encre — 5 % d'ivoire — ici résolue
   sur le fond d'encre pour rester opaque (une plaque translucide laisserait
   passer le décor étoilé, ce que `lib/entete.php` réserve aux sections).
   Contraste MESURÉ après remappage : 14,2:1. */
:root[data-sb-presentation="sombre"]{
  --yhb-surface:color-mix(in srgb,var(--sb-ink-fg) 5%,var(--sb-ink-bg));

  /* La plaque étant passée au sombre, l'accent POSÉ DESSUS doit passer avec
     elle : `--sb-accent-text` est par construction « l'accent lisible sur fond
     CLAIR » (tokens.css), et il tombe à 2,83:1 sur la plaque sombre — MESURÉ
     sur les liens de porte de l'accueil. Son pendant sombre est justement le
     jeton ajouté à tokens.css. C'est le même remappage que `patterns.css` pose
     sur `.sb-ink`, à ceci près qu'il y renvoyait vers `--sb-accent` — le violet
     du milieu de dégradé, à 2,8:1 — ce qui ne réglait rien. */
  --sb-accent-text:var(--sb-accent-encre);
}

/* ── socle ─────────────────────────────────────────────────────────────── */

body{
  font-family:var(--sb-font-body);
  background:var(--yhb-ink);
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
}

/* ── L'OUVERTURE DE PAGE ───────────────────────────────────────────────────
   Sur `main#contenu` et NON sur `body` : le bandeau et le pied vivent hors de
   `main`, ils peignent tout de suite. MESURÉ sur sept chargements par cas — le
   premier rendu est identique au témoin (444 ms contre 444) ; posé sur `body`,
   il passait à 860 ms. Le fondu se voit, il ne retarde pas le premier pixel.

   CE QU'IL COÛTE, PUISQU'IL COÛTE : le titre du héros cesse d'être compté comme
   plus grand rendu tant qu'il est transparent. Plus grand rendu sur l'accueil :
   492 → 520 ms à pleine vitesse, 692 → 956 ms à quatre fois ralenti. On reste
   très loin du seuil de 2,5 s. Rallonger ne coûte rien de plus (400 ms mesuré :
   même valeur) — c'est la promotion en couche qui coûte, pas la durée.

   🔴 L'OPACITÉ NULLE N'EXISTE QUE DANS LA KEYFRAME, ET C'EST VITAL.
   `patterns/patterns.css` coupe TOUTES les animations sous mouvement réduit
   (`*{animation:none!important}`). Un `main{opacity:0}` statique survivrait à
   cette coupe et rendrait la page BLANCHE pour qui a activé le réglage. C'est
   la panne que le module de constellation documente déjà pour la sphère.
   D'où la double garde : le média `no-preference` n'arme la règle que si le
   visiteur ne demande rien, et même armée de force elle retombe sur opacité 1.

   La courbe et la durée sont des JETONS, pas des nombres. */
@keyframes sb-ouverture{from{opacity:0}to{opacity:1}}
@media (prefers-reduced-motion:no-preference){
  main#contenu{animation:sb-ouverture var(--sb-motion,.2s) var(--yhb-ease) both}
}

.sb-wrap{max-width:var(--yhb-maxw);margin:0 auto;padding:0 28px}

h1,h2,h3{font-weight:600;line-height:1.1;letter-spacing:-.02em;margin:0}

/* ── en-tête : UNE seule ligne, comme le prototype ─────────────────────── */
/* Le gabarit généré produit  header.sb-header > .sb-wrap > a.sb-logo + nav.
   Sans mise en ligne, le menu se déroule en liste à puces sur deux lignes. */

/* ── Ancres : s'arrêter SOUS le bandeau, pas dessous ──────────────────────────
   Le bandeau est `sticky` : il flotte au-dessus du contenu. Une ancre amenait
   donc la section pile en haut de la fenêtre, c'est-à-dire DERRIÈRE le bandeau,
   et les 69 premiers pixels de la section — son surtitre, souvent — restaient
   cachés. Le propriétaire l'a relevé sur le bouton d'appel de Trajectoire.

   `scroll-margin-top` est fait pour ça : il ne déplace rien, il dit seulement
   où le défilement doit s'arrêter. La valeur suit la hauteur réelle du bandeau,
   plus une respiration — la coder en dur la rendrait fausse au premier
   changement de hauteur, et c'est exactement ce qui vient d'arriver à ce
   bandeau.

   La variable vit sur `:root` et non sur `.sb-header` : une propriété
   personnalisée descend dans l'arbre, elle ne le traverse pas — déclarée sur le
   bandeau, elle serait restée invisible à `main`, qui n'en est pas un
   descendant. */
:root{--sb-entete-hauteur:68px}
:where(main [id], main [name]){scroll-margin-top:calc(var(--sb-entete-hauteur,68px) + 1.5rem)}

/* ── ET LE TRAJET SE VOIT ──────────────────────────────────────────────────
   `scroll-margin-top` dit OÙ s'arrêter ; il ne dit rien du chemin. Un saut sec
   ne montre pas qu'on est descendu DANS la même page — le visiteur croit avoir
   changé de page. Demandé le 20.08 sur « Découvrir nos solutions ».

   ⚠️ LE DÉSARMEMENT N'EST PAS UNE POLITESSE. `prefers-reduced-motion` est un
   réglage système posé par des gens à qui le défilement animé donne la nausée
   ou déclenche un vertige. C'est eux qui décident : sous ce réglage on revient
   au saut d'aujourd'hui, au pixel près.

   Les deux scripts qui écrivent une position — le ruban, qui écrit `scrollLeft`
   soixante fois par seconde, et le sélecteur, dont les `scrollIntoView` portent
   un `behavior` explicite qui gagne sur le CSS — sont DÉJÀ gardés contre un
   `smooth` hérité. Vérifié règle par règle, pas supposé.

   Effet de bord assumé : Chromium lisse aussi l'arrivée sur une URL à fragment.
   `/univers/#constellation` descendra donc au lieu d'arriver en bas. C'est le
   seul lien inter-pages à fragment du site, et c'est cohérent. */
html{scroll-behavior:smooth}
@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}}

.sb-header{
  position:sticky;top:0;z-index:80;
  /* `patterns.css` pose `padding-block:1.25rem` sur `.sb-header,.sb-footer`.
     Le pied a le sien qui l'écrase ; le bandeau, non — il héritait donc de
     40 px que le prototype n'a pas. Relevé par `lib/mesures.mjs` : 109 px de
     haut contre 69. C'est ce qui faisait paraître le bouton « Premier échange »
     énorme alors qu'aucune de ses largeurs n'était fausse : il occupait la
     moitié d'un bandeau une fois et demie trop haut.
     La hauteur vient de `.sb-header .sb-wrap{height:68px}`, et d'elle seule. */
  padding-block:0;
  /* OPAQUE, et non 82 %. Au-dessus d'une section claire, la transparence
     donnait un bandeau gris — ni encre ni crème, et le logo blanc y perdait
     son contraste. Un bandeau est un repère : il ne change pas de couleur
     selon ce qui passe dessous.

     🔴 `--sb-ink-bg` et non `--yhb-ink` : le premier est REMAPPÉ par le
     sélecteur de tonalité, le second est figé. Le bandeau prenait donc son fond
     à une source que le mode jour ne touche pas, pendant que ses liens
     prenaient la leur — `--sb-ink-fg` — à une source qu'il remappe. En clair,
     le texte passait au sombre et le fond restait noir : les entrées de menu
     devenaient invisibles, seul le soulignement magenta de la page courante
     restait visible. Les deux jetons doivent venir de la même famille, sans
     quoi le bandeau ne peut être juste que dans une tonalité sur deux. */
  background:var(--sb-ink-bg);
  backdrop-filter:saturate(140%) blur(14px);
  -webkit-backdrop-filter:saturate(140%) blur(14px);
  border-bottom:1px solid var(--yhb-border-dark);
}
.sb-header .sb-wrap{
  display:flex;align-items:center;gap:28px;
  min-height:52px;padding-top:0;padding-bottom:0;
}
.sb-header .sb-logo{
  font-weight:700;letter-spacing:.02em;font-size:18px;
  color:var(--sb-ink-fg);text-decoration:none;
  display:flex;align-items:center;gap:9px;flex:0 0 auto;
}
/* Le monogramme. Inline en data-URI : build.php ne recopie pas assets/ du
   prototype dans le thème, et la médiathèque ne sert que les images citées
   dans le contenu — un logo d'en-tête n'en est pas. */
.sb-header .sb-logo::before{content:"";width:46px;height:46px;flex:0 0 auto;
  background:var(--sb-monogramme) center/contain no-repeat}
.sb-header nav{flex:1 1 auto;min-width:0}
.sb-header nav ul{
  list-style:none;margin:0;padding:0;
  display:flex;flex-wrap:wrap;align-items:center;gap:4px 22px;
}
.sb-header nav a{
  color:var(--yhb-text-soft-dark);text-decoration:none;
  /* `padding:10px 13px` relevé sur le prototype. Ici il valait `6px 0` : aucune
     marge latérale, donc les entrées se touchaient presque et seule la
     gouttière les séparait. Ce sont les 13 px de chaque côté qui font l'air
     entre les mots, pas le `gap` — et c'est aussi ce qui donne à chaque entrée
     une cible cliquable digne de ce nom. */
  /* ⚠️ INTERLETTRAGE POSITIF, comme le site en ligne — signalé le 20.08 :
     « la police du menu n'est pas la même que sur le site en ligne ». Mesuré
     côte à côte : même fonte (Manrope), même graisse (500), même corps (14 px).
     Le seul écart était l'interlettrage — −0,14 px ici contre +0,08 px en
     ligne, soit 1,6 px de largeur perdue sur « Origine ». Un centième de
     cadratin en négatif suffit à changer la texture d'un menu : les mots se
     serrent et la barre paraît plus dense.
     `.005em` est la valeur du corps de texte (`body{letter-spacing:.005em}`),
     et c'est celle du site en ligne. On la reprend au lieu de la contredire. */
  font-size:14px;font-weight:500;letter-spacing:.005em;
  padding:10px 13px;display:inline-block;
  transition:color .18s var(--yhb-ease);
}
.sb-header nav a:hover{color:var(--sb-ink-fg)}
/* PAS DE TRAIT AU SURVOL — demandé le 20.08. `patterns/patterns.css` pose
   `.sb-header a:hover,.sb-footer a:hover{text-decoration:underline}` pour
   TOUTES les marques ; on le reprend ici, pour celle-ci seulement, plutôt que
   de changer la base commune.
   Le signal ne disparaît pas : la ligne juste au-dessus, et son équivalent au
   pied, font déjà passer le lien à l'encre pleine. Le trait était le second
   signal, pas le seul. */
.sb-header a:hover,.sb-header a:focus-visible,
.sb-footer a:hover,.sb-footer a:focus-visible{text-decoration:none}
/* La page courante : le TEXTE à l'accent, et le filet avec lui — demandé le
   20.08, capture à l'appui. Le filet seul était le signal ; il devient le
   second signal, ce qui garde la règle « jamais la couleur seule ».

   🔴 UNE VARIABLE LOCALE, ET UNE SEULE ÉCRITURE. Le texte et le filet doivent
   être le MÊME rose : deux jetons à 2 px d'écart divergeraient au premier
   changement. `--sb-entete-actif` est la couleur du BANDEAU, pas de la marque —
   elle bascule avec la tonalité.

   ── POURQUOI PAS LE ROSE D'AUJOURD'HUI, MESURÉ EN CANVAS ───────────────────
   `--yhb-accent` (#E6008A) est le rose du filet actuel. En TEXTE il échoue des
   DEUX côtés : 4,13:1 sur l'encre du bandeau, 3,93:1 sur le crème. Un filet de
   2 px n'est pas du texte et pouvait s'en contenter ; un mot, non.
   Aucune couleur unique ne tient les deux fonds. D'où la PAIRE, qui existe :
     encre  #FF2EA6  --sb-accent-encre   5,41:1 sur #14141A   ✓ AA
     crème  #B7006F  --sb-accent-text    5,74:1 sur #F4F1EA   ✓ AA
   ⚠️ PAS `--sb-accent-text` SEUL : il n'est remappé vers l'encre qu'en
   présentation « sombre ». En présentation par défaut il vaut #B7006F, soit
   2,83:1 sur le bandeau encre — et le bandeau est encre dans deux tonalités
   sur trois.

   `a[aria-current="page"]` en plus de `.current-menu-item` : WordPress pose la
   classe sur le <li> pour une page du menu, l'attribut sur le <a>. Un lien
   personnalisé n'a que le second. */
.sb-header{--sb-entete-actif:var(--sb-accent-encre)}
:root[data-sb-presentation="clair"] .sb-header{--sb-entete-actif:var(--sb-accent-text)}
.sb-header nav .current-menu-item > a,
.sb-header nav a[aria-current="page"]{
  color:var(--sb-entete-actif);
  box-shadow:inset 0 -2px 0 0 var(--sb-entete-actif);
}
/* ET LE PANNEAU MOBILE SUIT. Le module de navigation refuse délibérément la
   couleur sur l'entrée active du panneau — « l'accent de la marque est calibré
   sur fond clair ». L'argument était juste avant que `--sb-accent-encre`
   existe ; il ne l'est plus. La parité se pose ICI, dans la feuille de marque,
   et non dans le module partagé par toutes les marques. */
.sb-nav:popover-open .sb-nav__liste .current-menu-item > a,
.sb-nav:popover-open .sb-nav__liste a[aria-current="page"]{
  color:var(--sb-accent-encre);
}
/* L'appel à l'action du bandeau est déclaré par la clé `entete` de la
   passation, et porte la classe `.sb-entete-cta`. Il N'EST PLUS la dernière
   entrée du menu.

   ⚠️ Ces règles ciblaient `li:last-child`, ce qui était juste tant que
   « Premier échange » était une entrée de menu. Le jour où elle en est sortie,
   c'est « Noyau » qui a hérité de la pilule — et le vrai appel s'est retrouvé
   en double à côté. Un sélecteur POSITIONNEL désigne ce qui se trouve là, pas
   ce qu'on voulait désigner : il devient faux dès que la liste bouge. */
/* `.sb-header .sb-entete-cta` et non `.sb-entete-cta` : le bouton porte
   `class="sb-btn sb-entete-cta"`, et `.sb-btn` — déclarée PLUS BAS dans ce même
   fichier avec `padding:15px 30px` — gagnait à spécificité égale, par simple
   ordre. La règle ci-dessous existait déjà et n'avait jamais rien fait.
   Deux classes de même poids sur un même élément : celle qu'on veut voir gagner
   doit peser plus, pas seulement venir après. */
.sb-header .sb-entete-cta{
  /* Dimensions RELEVÉES sur le prototype par `bin/sbwp-mesures`, pas estimées :
     38 px de haut, 18 px de marge horizontale, aucune marge verticale — le
     prototype tire sa hauteur de l'interligne. La règle posait 9 px de marge
     verticale et donnait 54 px, soit une fois et demie la référence dans un
     bandeau qui, lui aussi, était trop haut de 40 px. D'où un bouton qui
     paraissait énorme sans qu'aucune de ses largeurs ne soit fausse. */
  background:var(--sb-accent-fond);
  background-size:200% 200%;background-position:68% 50%;
  /* ARBITRAGE, car deux sources se contredisaient ici.
     Le commentaire précédent gardait `#fff` au motif qu'il est « relevé sur le
     prototype » et que l'écart avec #FAF8F3 « est de 12 sur un canal, invisible
     à l'œil ». Mais `tokens.css` ouvre sur une règle de la maison sans
     exception : « AUCUN blanc pur. L'ivoire est #FAF8F3. » Un relevé du
     prototype ne prime pas sur les jetons — c'est justement à ça qu'ils
     servent, et le prototype a lui-même de vieilles scories.
     C'est `--sb-accent-fg` : le jeton fait EXACTEMENT ce cas, le texte posé sur
     un fond d'accent. Contraste mesuré sur le milieu du dégradé (#A02597) :
     6,50:1 avec #fff, 6,17:1 avec le jeton. AA large dans les deux cas. */
  color:var(--sb-accent-fg);border-radius:999px;font-weight:600;
  padding:0 18px;min-height:38px;font-size:15px;
  box-shadow:none;
}
.sb-entete-cta:hover{box-shadow:0 10px 26px -12px color-mix(in srgb,var(--yhb-accent) 65%,transparent)}

/* ── sections : la tonalité alterne, on ne la fige pas ─────────────────── */

main > section{padding:clamp(56px,8vh,104px) 0}
.sb-ink{background:var(--yhb-ink);color:var(--sb-ink-fg)}

/* ── LE MESSAGE DE CONFIRMATION D'UN FORMULAIRE ─────────────────────────────
   Fluent Forms peint ses messages avec SA palette, calibrée pour un fond blanc :
   un gris sombre sur un cadre clair. Dans le pied — fond #0A0910 — ça donne du
   gris sur du noir, relevé illisible sur téléphone après l'inscription à la
   newsletter. Le pire moment pour ne pas se faire lire : celui où l'on vient
   de confier son adresse et où l'on attend une confirmation.
   On reprend donc les couleurs à la SECTION, par `currentColor` : le message
   tient sur le crème comme sur l'encre, sans qu'on écrive deux fois la règle.
   Le fond très légèrement teinté et le filet suffisent à le détacher — c'est
   une confirmation, pas une alerte, elle n'a pas à crier. */
.ff-message-success,
.ff_submit_success,
.frm-fluent-form .ff-message-success{
  color:currentColor;
  background:color-mix(in srgb,currentColor 7%,transparent);
  border:1px solid color-mix(in srgb,currentColor 18%,transparent);
  border-radius:var(--sb-radius,.5rem);
  padding:1rem 1.125rem;line-height:1.55;
}
/* L'erreur garde un signal propre : la couleur ne suffit pas à la distinguer
   d'une confirmation, mais elle y contribue, et le texte dit le reste. */
.ff-errors-in-stack,.frm-fluent-form .error{
  color:currentColor;
  background:color-mix(in srgb,#E4572E 14%,transparent);
  border:1px solid color-mix(in srgb,#E4572E 42%,transparent);
  border-radius:var(--sb-radius,.5rem);padding:.75rem 1rem;
}
/* LE DÉGRADÉ, RELEVÉ POUR L'ENCRE. Voir la note en tête de feuille, au jeton :
   ses deux premiers arrêts tombaient à 1,24 et 1,72 sur ce fond, contre un
   seuil de 3 pour les grands textes. On les relève de 32 % et 25 % vers le
   blanc en oklab — l'espace qui garde le mieux la chroma.
   La redéclaration porte sur la TONALITÉ, pas sur le dégradé : les sections
   claires gardent le violet profond, qui y passe largement et que l'éclaircir
   ferait au contraire disparaître. */
.sb-ink{
  --yhb-grad-relais:color-mix(in oklab,#5B1A8C 75%,white);
  --yhb-grad:linear-gradient(112deg,
    color-mix(in oklab,#3A0E63 68%,white) 0%,
    var(--yhb-grad-relais) 28%,
    #E6008A 100%);
}
.sb-paper{background:var(--sb-paper-bg);color:var(--sb-paper-fg)}

/* ── LA PLAQUE ────────────────────────────────────────────────────────────────
   Une plaque est une surface posée SUR une section : tuile de choix, panneau de
   réponse, ligne de carte de visite. Elle doit se DÉTACHER du fond, donc aller
   dans la direction opposée à lui.

   Ce que faisait la chaîne : `color-mix(in srgb, currentColor 6%, transparent)`,
   c'est-à-dire un voile de la couleur du TEXTE. Sur une section encre le texte
   est clair, le voile éclaircit — juste. Sur une section crème le texte est de
   l'encre, et le voile ASSOMBRIT : la carte devenait plus foncée que la page au
   lieu de s'en détacher. Mesuré en mode clair : tuiles sans fond, panneau et
   bloc du bas à `#0B0B0E` 6 % sur un fond `#F4F1EA`.

   Une plaque ne se déduit pas du texte. Elle se déclare par tonalité.

   ⚠️ AUCUNE COULEUR NOUVELLE ICI. `--yhb-surface` (#FBF9F4) est le blanc cassé
   déjà déclaré par la marque pour les plaques sur crème. Le 4 % et le 13 % du
   versant encre sont RELEVÉS sur https://yanhaltey.com/yan, et le 244,241,234
   du bord n'est pas un choix : c'est `--sb-paper-bg`. */
.sb-paper{
  --sb-plaque:var(--yhb-surface);
  --sb-plaque-bord:color-mix(in srgb,var(--sb-paper-fg) 10%,transparent);
}
.sb-ink{
  --sb-plaque:color-mix(in srgb,var(--sb-ink-fg) 4%,transparent);
  --sb-plaque-bord:color-mix(in srgb,var(--sb-paper-bg) 13%,transparent);
}

/* ── héros : centré, titre à grande échelle ────────────────────────────── */

.sb-hero{padding-top:clamp(72px,11vh,132px);padding-bottom:clamp(72px,11vh,132px);
  text-align:center;position:relative;overflow:hidden}
/* le halo violet du prototype, en dégradé radial — pas le champ d'étoiles,
   qui demande un canvas et du script */
.sb-hero::before{
  content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background:
    /* `rgba(91,26,140,.34)` était le RELAIS du dégradé recopié en décimal —
       troisième écriture de `#5B1A8C` dans cette feuille, et la moins
       trouvable. Le voile garde exactement la même alpha. */
    radial-gradient(46% 40% at 18% 12%, color-mix(in srgb,var(--yhb-grad-relais) 34%,transparent), transparent 70%),
    radial-gradient(40% 34% at 84% 82%, color-mix(in srgb,var(--yhb-accent) 14%,transparent), transparent 72%);
}
.sb-hero > .sb-wrap{position:relative;z-index:1}
/* 26ch, pas 19 : à 19 le titre d'accueil tombait sur TROIS lignes quand le
   prototype en fait deux. Vérifié par capture, côte à côte. */
.sb-hero__title,.sb-hero h1{
  /* PLAFOND À 68 PX, ET NON 72 — demandé le 20.08. « nous clarifions par où
     commencer. » passait à la ligne de justesse : 33 signes dans les 1124 px du
     conteneur demandent 68 px, pas 72. Quatre pixels, soit 5,5 % du plus grand
     corps du site — imperceptible à l'œil, et ça évite une troisième ligne au
     héros de /hello/.
     ⚠️ Ça ne suffit PAS pour le titre de /trajectoires/, qui demanderait 61 px,
     soit 15 % : celui-là reste sur deux lignes, et c'est le bon arbitrage. */
  font-size:clamp(38px,6vw,68px);font-weight:700;letter-spacing:-.025em;
}
/* ⚠️ LE HÉROS DE /trajectoires/ EST PLUS PETIT, ET C'EST MESURÉ.
   « Vous cherchez quelque chose de précis ? » demande 1293 px à 68 px, et le
   conteneur n'en offre que 1124 — il passait donc à la ligne, en coupant la
   phrase là où le hasard voulait. À 60 px il lui manque encore 11 px ; à 59 px
   elle tient d'une seule pièce.
   ⚠️ POURQUOI PAS PARTOUT : 59 px, c'est 13 % de moins que le plafond du site.
   Les autres héros n'en ont pas besoin — celui de l'accueil tient à 68 — et
   rétrécir le plus grand corps du site pour un titre serait payer partout ce
   qui se règle à un seul endroit.
   L'ancrage passe par CE QUE LA PAGE CONTIENT, jamais par un `body.page-id-NN` :
   les identifiants changent à chaque reconstruction. `.sb-selecteur` n'existe
   que sur /trajectoires/ — c'est la règle d'ancrage posée en tête de la passe
   de parité, quelques centaines de lignes plus haut. */
/* ⚠️ PLUS AUCUNE EXCEPTION DE CORPS ICI, et c'est le bon état.
   Trois essais ont cherché la taille qui ferait tenir « Vous cherchez quelque
   chose de précis ? » : une bride en `ch` qui rétrécissait avec la police, un
   corps fixe qui donnait trois lignes quand même, puis un corps proportionnel
   mesuré qui tenait en deux lignes — à 23 px sur un iPhone, contre 38 pour tous
   les autres titres du site.
   Aucun des trois ne réglait le vrai problème : la phrase faisait 38 signes.
   Elle en fait 19 depuis le 21.08, et le corps commun suffit — une ligne à 68 px
   sur bureau, deux sur téléphone.
   Ne reste que la levée de la bride : `26ch` était la cause du premier échec, et
   rien ne la justifiait. */
body:has(.sb-selecteur) .sb-hero__title,
body:has(.sb-selecteur) .sb-hero h1{max-width:none}
/* Le dégradé de la seconde phrase — la signature visuelle de l'accueil.
   Suppose que le champ titre accepte du HTML (voir patterns/hero.html). */
.sb-hero h1 .grad,.sb-hero__title .grad{
  background:var(--yhb-grad);-webkit-background-clip:text;background-clip:text;
  color:transparent;
}
/* ⚠️ RÈGLE DU JEU — quatrième fois que ce motif casse quelque chose.
   Un sélecteur qui vise « le chapô » en écrivant `p` attrape TOUS les
   paragraphes de la section : le surtitre, la mention du bouton, le rôle
   d'une carte. Ils ont chacun leur classe et leur taille, et se font écraser
   par la spécificité de structure — `main > section.sb-hero > .sb-wrap > :is(…)`
   pèse (0,3,2) et bat `.sb-hero .sb-eyebrow` (0,2,0). Mesuré : le surtitre de
   la page ambassadeur sortait à 21px contre 13px sur les trois autres pages.

   DONC : toute règle d'ici qui vise `p` sous `.sb-wrap` porte cette liste.
   On nomme ce qu'on exclut plutôt que de surenchérir en spécificité — un
   contrepoids ne se voit pas, une exclusion se lit. Une nouvelle classe de
   paragraphe nommé s'ajoute ICI, aux quatre endroits d'un coup. */
.sb-hero__lead,.sb-hero .sb-lead,.sb-hero p:not(.sb-eyebrow,.sb-cta__mention,.sb-profil__role,.sb-profil__intro,.sb-profil__pied){
  font-size:clamp(17px,1.7vw,21px);font-weight:300;line-height:1.55;
  color:var(--yhb-text-soft-dark);
  max-width:62ch;margin:22px auto 0;opacity:1;
}
.sb-hero .sb-btn{margin-top:30px}

/* ── boutons : la pilule à dégradé du prototype ────────────────────────── */

/* Le dégradé du prototype va du violet très sombre au magenta sur TOUTE la
   largeur de la page. Appliqué tel quel à un bouton de 200px, seul le violet
   sombre apparaît — c'est ce que la première capture montrait. On repart donc
   du milieu du dégradé, là où le bouton du prototype se situe réellement. */
.sb-btn{
  display:inline-flex;align-items:center;gap:9px;
  padding:15px 30px;border-radius:999px;
  /* Angle, taille et POSITION repris de site.css:183 sans les réinterpréter.
     Le dégradé était déjà juste ; c'est `background-position:0 50%` sur une
     taille de 180 % qui donnait à voir son extrémité VIOLETTE. Le prototype
     cadre à 68 %, donc sur le magenta. Un bouton violet là où l'œil attend du
     magenta, relevé deux fois à l'écran, tenait à cette seule valeur. */
  background:var(--sb-accent-fond);
  background-size:200% 200%;background-position:68% 50%;
  color:var(--sb-ink-fg);border:1px solid transparent;
  font-weight:600;font-size:15.5px;letter-spacing:-.01em;text-decoration:none;
  box-shadow:0 10px 30px -14px color-mix(in srgb,var(--yhb-accent) 55%,transparent);
  transition:background-position .5s var(--yhb-ease),
             transform .18s var(--yhb-ease), box-shadow .18s var(--yhb-ease);
}
.sb-btn:hover{
  background-position:100% 50%;transform:translateY(-2px);
  box-shadow:0 16px 36px -12px color-mix(in srgb,var(--yhb-accent) 70%,transparent);opacity:1;
}
/* Sur la bande d'accent, le dégradé disparaîtrait : on inverse.
   `#FAF8F3` → `--sb-ink-fg` (l'ivoire, même valeur) ; `#7A2CA6` est le premier
   arrêt de `--sb-accent-fond`, pas une couleur à part — il a maintenant un nom
   à lui, `--sb-accent-fond-amorce` dans tokens.css, et c'est lui qu'on prend.
   Au survol le fond passait à `#fff` — blanc pur, interdit par tokens.css, et
   pour un gain invisible : l'ivoire est déjà la couleur la plus claire de la
   marque. On ne change donc plus le fond, on garde la seule translation, qui
   est ce que l'œil lit.

   ⚠️ CETTE RÈGLE NE MORD PAS, ET IL NE FAUT PAS LA RÉVEILLER TELLE QUELLE.
   Vérifié au navigateur sur les 3 pages qui portent une bande : la couleur
   servie est `rgb(250,248,243)`, pas l'amorce — c'est le §9 qui gagne, avec
   `.sb-cta.sb-band > .sb-wrap > .sb-btn` en (0,3,2) contre les (0,2,0) d'ici.
   Le jour où on la rétablirait, l'amorce serait le mauvais choix : en
   présentation claire `--sb-ink-fg` devient l'encre, et l'amorce sur ce fond
   sombre tombe à 2,39:1 MESURÉ. Ce n'est pas au jeton de suivre la tonalité —
   c'est à la règle de demander « l'accent lisible en texte ici », rôle que
   `--sb-accent-text` et `--sb-accent-encre` remplissent déjà. */
.sb-band .sb-btn{background:var(--sb-ink-fg);color:var(--sb-accent-fond-amorce);box-shadow:none}
.sb-band .sb-btn:hover{background:var(--sb-ink-fg);transform:translateY(-2px)}

/* ── titres de section : centrés, avec leur respiration ────────────────── */

main > section > .sb-wrap > h2{
  font-size:clamp(28px,3.1vw,42px);letter-spacing:-.025em;
  max-width:22ch;margin:0 auto;text-align:center;
}
main > section > .sb-wrap > .sb-lead,
main > section > .sb-wrap > p{
  text-align:center;max-width:62ch;margin:16px auto 0;
  font-size:17px;line-height:1.6;font-weight:300;opacity:1;
}
.sb-ink .sb-lead,.sb-ink > .sb-wrap > p{color:var(--yhb-text-soft-dark)}
.sb-paper .sb-lead,.sb-paper > .sb-wrap > p{color:var(--yhb-text-soft)}

/* ── cartes : plaque, filet, rayon 18 — les valeurs du prototype ───────── */

.sb-grid{gap:22px;margin-top:clamp(34px,5vh,54px)}
.sb-card{
  background:var(--yhb-surface);color:var(--sb-paper-fg);
  border:1px solid var(--yhb-border);border-radius:18px;padding:28px;
  box-shadow:0 1px 2px color-mix(in srgb,var(--sb-paper-fg) 3%,transparent), 0 16px 40px -26px color-mix(in srgb,var(--sb-paper-fg) 22%,transparent);
  transition:transform .2s var(--yhb-ease), box-shadow .2s var(--yhb-ease);
}
.sb-card:hover{transform:translateY(-3px);
  box-shadow:0 1px 2px color-mix(in srgb,var(--sb-paper-fg) 4%,transparent), 0 22px 50px -26px color-mix(in srgb,var(--sb-paper-fg) 30%,transparent)}
.sb-card h3{font-size:19px;letter-spacing:-.015em;margin:0 0 8px}
.sb-card p{font-size:15px;line-height:1.55;color:var(--yhb-text-soft);opacity:1}

/* ── texte long : les pages juridiques et les récits ───────────────────── */

.sb-prose,.sb-recit .sb-wrap > div{max-width:72ch;margin-inline:auto;text-align:left}
.sb-prose h2{font-size:clamp(22px,2.2vw,30px);margin:2em 0 .6em;text-align:left;max-width:none}
.sb-prose h3{font-size:19px;margin:1.6em 0 .5em}
.sb-prose p,.sb-prose li{font-size:16px;line-height:1.68;color:var(--yhb-text-soft)}
.sb-ink .sb-prose p,.sb-ink .sb-prose li{color:var(--yhb-text-soft-dark)}
.sb-legal .sb-wrap > h2{text-align:left;margin-inline:0;max-width:none}

/* ── équipe : portraits ronds, comme le prototype ──────────────────────── */

.sb-equipe .sb-grid{grid-template-columns:repeat(auto-fit,minmax(11rem,1fr))}
.sb-equipe .sb-card{background:transparent;border:0;box-shadow:none;
  padding:0;text-align:center}
.sb-equipe .sb-card:hover{transform:none;box-shadow:none}
.sb-equipe .sb-portrait{
  width:clamp(96px,11vw,150px);aspect-ratio:1;border-radius:50%;
  object-fit:cover;display:block;margin:0 auto 14px;
}
.sb-equipe .sb-card h3{margin:0 0 2px;font-size:17px}
.sb-paper .sb-equipe .sb-card p{color:var(--yhb-text-soft)}

/* ── bande d'accent du CTA final ───────────────────────────────────────── */

.sb-band,.sb-cta{text-align:center}
.sb-band > .sb-wrap > h2{color:inherit}

/* ── pied de page ──────────────────────────────────────────────────────── */

.sb-footer{
  background:var(--yhb-ink);color:var(--yhb-text-soft-dark);
  border-top:1px solid var(--yhb-border-dark);padding:56px 0 48px;
}
/* ⚠️ NE PAS remettre `display:flex` sur `.sb-footer .sb-wrap`.
   Cette règle décrivait un pied d'UNE SEULE LIGNE — copyright à gauche, liens
   à droite. Le pied a maintenant trois colonnes, posées en grille par
   lib/pied.php ; un flex ici les remettait toutes en colonne, empilées. C'est
   ce qui donnait l'impression d'un pied « en double ». */
.sb-footer .sb-bas{display:flex;flex-wrap:wrap;align-items:center;
  justify-content:space-between;gap:18px}
.sb-footer p{margin:0;font-size:14px}
/* ⚠️ TROISIÈME règle de la même famille, et la seule que le navigateur écoutait.
   `.sb-footer nav ul` pèse (0,1,2) : elle battait `.sb-pied__liste` (0,1,0) sans
   que ni patterns.css ni le contre-poison de pied.php n'y changent quoi que ce
   soit. Deux corrections successives sont restées sans effet à cause d'elle, et
   c'est en demandant au navigateur QUELLE règle gagne — plutôt qu'en cherchant
   dans les sources — qu'elle est apparue.

   Elle vise la rangée légale du bas, qui se pose bien en ligne. Elle est donc
   restreinte à celle-ci ; les colonnes de menu s'empilent, une entrée par ligne. */
.sb-footer .sb-pied__legal nav ul,
.sb-footer nav.sb-pied__legal-nav ul{list-style:none;margin:0;padding:0;
  display:flex;flex-wrap:wrap;gap:8px 20px}
.sb-footer nav ul{list-style:none;margin:0;padding:0}
.sb-footer nav a{color:var(--yhb-text-soft-dark);text-decoration:none;font-size:14px}
.sb-footer nav a:hover{color:var(--sb-ink-fg)}

/* ── téléphone ─────────────────────────────────────────────────────────── */

@media (max-width:720px){
  .sb-header .sb-wrap{flex-wrap:wrap;gap:10px 18px;padding-top:12px;padding-bottom:12px}
  .sb-header nav ul{gap:2px 16px}
  main > section{padding:clamp(44px,6vh,72px) 0}
}

/* ═══════════════════════════════════════════════════════════════════════════
   Synthèse de la comparaison page à page — 17.08.2026
   ───────────────────────────────────────────────────────────────────────────
   Produite par neuf agents ayant comparé chaque page WordPress à sa page du
   prototype, chaque valeur relevée dans proto-v2/assets/site.css avec son
   sélecteur d'origine. Vient APRÈS le bloc ci-dessus : en cas de conflit de
   spécificité égale, c'est cette synthèse qui gagne — elle est plus précise.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════════════
   YHB — passe de parité prototype → WordPress
   À CONCATÉNER À LA FIN de brands/yhb/style.css. Rien n'est réécrit au-dessus :
   tout ce qui suit surcharge par l'ordre de cascade ou par une spécificité
   supérieure, jamais par !important.

   Toutes les valeurs viennent des relevés de comparaison des 9 pages
   (sources citées bloc par bloc : proto-v2/assets/site.css et carte.css).

   CHOIX D'ANCRAGE : aucun `body.page-id-NN` ici. Les identifiants changent à
   chaque reconstruction. Chaque page est atteinte par ce qu'elle contient —
   `.sb-promesse` n'existe que sur ambassadeurs, `.sb-calculateur` que sur
   trajectoires, `.sb-integre + .sb-cta` que sur plan-accès, etc. Voir le
   tableau d'ancrage en fin de fichier.

   Reste hors de portée du CSS : le fond étoilé, le mode mixte à trois états,
   l'interaction du calculateur, le ruban défilant des marques.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 1. jetons complémentaires ─────────────────────────────────────────────
   Répare l'absence de police mono, de gris sourds et du dégradé de bouton :
   sans eux, surtitres et mentions tombaient en Manrope 16-17px de corps. */

:root{
  /* Était la pile recopiée caractère pour caractère. C'est EXACTEMENT
     `--sb-font-mono` de tokens.css, dont le commentaire dit qu'il a été créé
     pour que « les petits textes en monospace » cessent de retomber sur la
     police système. Deux copies d'une même valeur, c'est une divergence en
     attente — le même raisonnement, et le même geste, que `--yhb-grad-btn`
     quelques lignes plus bas. Le nom local reste pour les règles qui
     l'emploient ; il n'a simplement plus de valeur à lui. */
  --yhb-mono:var(--sb-font-mono);          /* site.css --mono */
  --yhb-accent:#E6008A;                    /* site.css --accent, zone claire */
  /* Était `#FF2EA6` écrit en dur ici. La valeur n'a pas changé — elle a
     simplement UN SEUL domicile désormais, `--sb-accent-encre` dans tokens.css,
     là où vit l'identité. Le nom local reste pour les règles qui l'emploient. */
  --yhb-accent-dark:var(--sb-accent-encre); /* site.css:37 --accent en sombre */
  /* ── Le gris sourd : ARBITRÉ, plus figé ───────────────────────────────────
     L'ARBITRAGE EST TRANCHÉ. Le commentaire qui tenait ici disait que corriger
     `#8A877F` demandait « un arbitrage de design, pas un remplacement
     mécanique », et renvoyait au rapport. Le propriétaire a tranché : ON
     ASSOMBRIT, en acceptant que le site paraisse un peu moins aérien que le
     prototype. Ce bloc est cet arbitrage, posé.

     ── CE QUI ÉTAIT FAUX, MESURÉ AU NAVIGATEUR ────────────────────────────────
     Deux échecs distincts, relevés sur les 8 pages × 3 tonalités :
       `#8A877F` sur le crème                              3,18:1
         — les fonctions sous les portraits de l'équipe du noyau (FONDATEUR,
           ENTREPRENEURIAT), en mono 12 px ; en tonalité neutre ET en clair.
       `--yhb-muted-dark` à 53 %, en présentation CLAIRE   3,95:1
         — les légendes de la frise « Notre histoire » (`.tl-cap`), 12 px : le
           jeton dérive de la famille ENCRE, que le mode jour remappe sur le
           crème, et 53 % du texte d'encre sur crème donne `#797775`.
     Les deux sont sous les 4,5:1 que WCAG AA demande pour du 11-12 px.

     ── POURQUOI UN SEUL POURCENTAGE POUR LES DEUX ─────────────────────────────
     AUCUNE couleur unique ne peut tenir 4,5:1 à la fois sur le crème et sur
     l'encre : il faudrait une luminance relative ≤ 0,154 côté crème et ≥ 0,204
     côté encre, deux intervalles qui ne se croisent pas. Ce n'est donc pas une
     COULEUR qu'on cherche, c'est un POURCENTAGE — la même distance parcourue
     depuis la couleur de texte vers son fond, dans chaque famille. C'est
     exactement le procédé de `patterns/temoignages.html`, qui a tranché ce même
     problème pour son propre gris (52 % → 62 %, « seule valeur qui passe des
     deux côtés »), et celui des gris sourds déclarés plus haut.

     ── LA VALEUR RETENUE, ET POURQUOI ELLE ET PAS UNE AUTRE ───────────────────
     57 %, BALAYÉ au navigateur de 40 à 85 % par pas de 1 sur le rendu servi.
     C'est le premier pourcentage qui passe, donc le gris le plus CLAIR qui
     tienne : on n'assombrit pas d'un point de plus que nécessaire, ce qui était
     la consigne. Le côté crème est celui qui contraint — le côté encre est déjà
     large bien avant.
       56 %  →  #72706F sur crème  4,366:1   REFUSÉ
       57 %  →  #6F6E6D sur crème  4,515:1   retenu
     Contrastes MESURÉS à 57 %, sur les trois fonds réels de la marque :
       papier  #6F6E6D sur le crème  --sb-paper-bg (#F4F1EA)   4,515:1
       encre   #979696 sur l'encre   --sb-ink-bg   (#14141A)   6,216:1
       encre   #979696 sur l'encre profonde --yhb-ink (#0A0910) 6,336:1
     La marge côté crème est mince — 0,015 au-dessus du seuil. C'est le prix
     demandé : la consigne était le gris le plus clair qui passe, pas le plus
     confortable. 58 % (#6D6C6A, 4,670:1) est le voisin immédiat si un jour on
     préfère de l'air sous le seuil plutôt qu'au-dessus du prototype.

     ── CE QUE ÇA CHANGE À L'ŒIL ───────────────────────────────────────────────
     En papier, `#8A877F` → `#6F6E6D` : le gris du prototype s'assombrit
     nettement, c'est l'arbitrage assumé. En encre, `#8E8D8D` → `#979696` : le
     gris s'ÉCLAIRCIT de neuf points par canal, donc rien ne se perd du côté
     sombre — la dérivation ne fait que cesser d'être fausse en mode jour. */
  --yhb-muted:color-mix(in srgb,var(--sb-paper-fg) 57%,var(--sb-paper-bg));
  --yhb-muted-dark:color-mix(in srgb,var(--sb-ink-fg) 57%,var(--sb-ink-bg));
  --yhb-border-strong-dark:rgba(244,241,234,.24); /* site.css:36 --border-strong */
  /* Était le dégradé recopié caractère pour caractère. C'est EXACTEMENT
     `--sb-accent-fond` de tokens.css — même angle, mêmes trois arrêts — dont le
     commentaire dit déjà qu'il est « le FOND d'un bouton plein, et lui seul ».
     Deux copies d'une même valeur, c'est une divergence en attente. */
  --yhb-grad-btn:var(--sb-accent-fond);    /* site.css:183 */
}

/* ── 2. polices — CONDITIONNEL ─────────────────────────────────────────────
   Répare la retombée générale en Helvetica : le thème ne déclare aucun
   @font-face (mesuré 18 fontes chargées sur le proto, 0 sous WordPress).
   PRÉREQUIS : build.php doit recopier proto-v2/assets/fonts/*.woff2 dans
   themes/sb-yhb/assets/fonts/. Sans les fichiers, ce bloc est inopérant mais
   inoffensif (font-display:swap garde la fonte de repli). */

/* ⚠️ MANROPE EST UNE POLICE VARIABLE, et le thème la traitait en statique.
   Vérifié le 20.08 : `Manrope-200/300/400/500/600-latin.woff2` sont CINQ COPIES
   BIT À BIT du même fichier (MD5 `101877a7a906…`), une variable d'axe
   `wght` 200→800. Le thème en déclarait trois sous trois URL différentes — donc
   trois téléchargements du même octet, 48 Ko jetés à chaque page.

   Et surtout : la graisse 400 n'était déclarée NULLE PART. Or c'est la plus
   employée du site (33 950 signes sur /trajectoires/). L'algorithme de
   correspondance de CSS, quand on demande 400, essaie 500 avant tout le reste :
   tout le texte courant du site était rendu en 500. Un cran trop gras, partout,
   depuis le début. Mesuré, pas déduit : « Hamburgefonstiv » faisait 814,7 px en
   400 comme en 500, contre 783,6 px en 300.

   Une seule déclaration par sous-ensemble, avec la PLAGE `200 800` : le
   navigateur pose alors l'axe sur la graisse demandée, quelle qu'elle soit, et
   ne télécharge qu'un fichier.

   ⚠️ Le nom de fichier ment — il dit « 300 » et contient toute la plage. Le
   renommer demanderait de toucher la recopie de `build.php` ; on préfère un
   commentaire à une migration.

   L'`unicode-range` ajoute le sous-ensemble latin-ext, présent sur le disque et
   déclaré nulle part : sans lui, un « ș » ou un « ő » retombait en Helvetica
   sans que rien ne le signale. Il ne se télécharge que si la page en contient. */
@font-face{font-family:"Manrope";src:url("assets/fonts/Manrope-300-latin.woff2") format("woff2");font-weight:200 800;font-style:normal;font-display:swap;unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:"Manrope";src:url("assets/fonts/Manrope-300-latin-ext.woff2") format("woff2");font-weight:200 800;font-style:normal;font-display:swap;unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
/* IBM Plex Mono, elle, est STATIQUE — vérifié : pas de table `fvar`. Chaque
   graisse est un fichier, et seule la 400 est employée par la marque. */
@font-face{font-family:"IBM Plex Mono";src:url("assets/fonts/IBMPlexMono-400-latin.woff2") format("woff2");font-weight:400;font-style:normal;font-display:swap;unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:"IBM Plex Mono";src:url("assets/fonts/IBMPlexMono-400-latin-ext.woff2") format("woff2");font-weight:400;font-style:normal;font-display:swap;unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}

/* ── 3. socle typographique ────────────────────────────────────────────────
   Répare l'interlettrage de corps absent (site.css:62 body{letter-spacing:.005em}). */

body{letter-spacing:.005em;line-height:1.6}

/* Répare les liens du corps de texte rendus en bleu navigateur #0000EE souligné
   (relevé sur mentions-légales) : site.css:63 a{color:inherit} + site.css:649
   .legal a{color:var(--accent)}, soit #B7006F en zone claire. */
a{color:inherit;text-decoration:none}
/* ⚠️ SOULIGNÉ AU REPOS, ET PAS SEULEMENT AU SURVOL — depuis le 22.08.2026.
   `link-in-text-block` refusait la livraison sur /cgu/, /donnees-personnelles/
   et /mentions-legales/ : un lien AU MILIEU D'UN PARAGRAPHE qui ne se distingue
   que par la couleur n'existe pas pour qui ne perçoit pas cette couleur — et il
   n'existe pas non plus pour qui n'a pas de survol, c'est-à-dire tout visiteur
   sur téléphone. C'étaient les adresses de contact des pages légales : les
   seules de tout le site qu'on vient chercher pour AGIR.
   Le soulignement est décalé et fin, pour ne pas hacher les jambages. Les liens
   hors prose — navigation, boutons, cartes — ne sont pas concernés : leur
   fonction se lit à leur forme, pas à leur couleur. */
.sb-prose a{color:var(--sb-accent-text);text-decoration:underline;
  text-underline-offset:.18em;text-decoration-thickness:.06em}
.sb-ink .sb-prose a{color:var(--yhb-accent-dark)}
.sb-prose a:hover,.sb-prose a:focus-visible{text-decoration-thickness:.12em}

/* ── 4. hygiène des champs vides ───────────────────────────────────────────
   Répare les blocs fantômes sortis par les gabarits et absents du prototype :
   pilule magenta vide (62×32) des héros légaux et de yan, 8 paragraphes vides
   du noyau, chapô vide des marques d'univers, carte vide du gabarit « lieu ».
   Ne masque QUE ce qui est réellement vide : sans effet dès que le contenu
   est renseigné. Le vrai correctif reste côté gabarit / contenu. */

main :is(p,blockquote):empty{display:none}
.sb-btn:empty{display:none}
.sb-lead:empty,.sb-hero__lead:empty,.sb-adresse:empty{display:none;margin:0}
.sb-carte:not(:has(img)){display:none}
/* Une carte de répéteur sans titre ni image est une entrée blanche, pas une carte. */
.sb-communaute .sb-card:not(:has(h3)){display:none}
.sb-communaute:not(:has(.sb-card h3)) .sb-grid{display:none}
/* Témoignages : le prototype ne pose aucune carte tant qu'il n'a rien à citer. */
.sb-temoignages .sb-card:not(:has(blockquote:not(:empty))){display:none}
.sb-temoignages:not(:has(blockquote:not(:empty))){display:none}

/* ── 5. en-tête ────────────────────────────────────────────────────────────
   Répare la barre à 60px et les liens à 14,5/22px : le prototype pose 68px de
   haut, 23px de gouttière, 14px de corps (site.css:103, 109, 110). */

.sb-header .sb-wrap{min-height:68px}
/* 4 px, et non 23. Le commentaire ci-dessus lisait « 23 px de gouttière » dans
   `site.css:109` ; le rendu calculé du prototype donne `gap: 4px` et
   `padding: 10px 13px` sur chaque lien — 30 px entre deux mots, dont 26 de
   marge. Cumuler 23 px de gouttière AVEC les 26 px de marge en aurait fait 49.
   Une valeur lue dans une feuille de style n'est pas la valeur qui s'applique :
   c'est le calculé qui fait foi. */
.sb-header nav ul{gap:4px}
.sb-header nav a{font-size:14px}

/* ── 6. héros ──────────────────────────────────────────────────────────────
   Répare le héros sans plancher (441 à 508px mesurés) : le prototype réserve
   70svh et centre verticalement (site.css:1774 et 2159, carte.css:93). */

.sb-hero{
  min-height:70svh;display:flex;flex-direction:column;justify-content:center;
  padding-top:clamp(56px,8vh,104px);padding-bottom:clamp(48px,7vh,96px);
}

/* Répare le h1 trop serré : -.025em/1.06 au lieu de -.02em/1.1, et une mesure
   plafonnée à 26ch qui coupe la phrase au mauvais endroit (site.css:68, 199). */
.sb-hero__title,.sb-hero h1{letter-spacing:-.02em;line-height:1.1;max-width:none}

/* ⚠️ PLUS D'EXCEPTION DE CORPS SUR /hello/ NON PLUS — même histoire qu'à côté.
   « Vous ne savez pas par où commencer ? » demandait 18 px pour une ligne et
   35 pour deux à 390 px : au corps commun de 38, elle en prenait trois. Un
   corps proportionnel l'avait ramenée à 34 px, soit 4 de moins que les autres
   titres du site.
   « Une question, un projet, une idée ? » demande 20 px pour une ligne et 41
   pour deux — mesuré. Au corps COMMUN, donc : une ligne à 68 px sur bureau,
   deux à partir de 360 px. L'exception n'a plus lieu d'être.
   ⚠️ ASSUMÉ : à 320 px — un iPhone SE de première génération — la question
   repasse à trois lignes. Descendre le plancher pour ce seul appareil
   rapetisserait tous les héros du site ; on ne le fait pas. */

/* Répare le chapô de héros écrasé à 17px/16px : la règle générique de section
   (0,1,3) battait .sb-hero__lead (0,1,0). On repasse devant par la structure.
   Valeurs : site.css:73 (.lede) et site.css:1745 (.hero .lede{max-width:64ch}). */
main > section.sb-hero > .sb-wrap > :is(.sb-hero__lead,.sb-lead,p):not(.sb-eyebrow,.sb-cta__mention,.sb-profil__role,.sb-profil__intro,.sb-profil__pied){
  font-size:clamp(17px,1.7vw,21px);font-weight:300;line-height:1.55;
  color:var(--yhb-text-soft-dark);max-width:64ch;margin:26px auto 0;text-align:center;
}
/* Répare l'écart bouton/chapô : 30px au lieu de 34 (site.css:195 .cta-row). */
.sb-hero .sb-btn{margin-top:34px}

/* Répare l'absence de césure entre le héros et la section suivante : filet +
   trait magenta dégradé de 560px centré (carte.css:85-92). */
.sb-hero + section{position:relative;border-top:1px solid color-mix(in srgb,var(--sb-paper-fg) 12%,transparent)}
.sb-hero + section::after{
  content:"";position:absolute;left:50%;top:-1px;width:min(560px,60%);height:1px;
  transform:translateX(-50%);opacity:.55;pointer-events:none;
  background:linear-gradient(90deg,transparent,var(--yhb-accent),transparent);
}

/* ── 7. sections : rythme et tête de section ───────────────────────────────
   Répare la respiration verticale (72px mesurés) : le prototype pose 90px à
   900px de fenêtre (site.css:92 section{padding:clamp(68px,10vh,140px) 0}). */

main > section{padding:clamp(68px,10vh,140px) 0}

/* Répare les h2 de section à 39,68px : le prototype les fige TOUS à 30px,
   sans clamp, précisément pour les aligner entre eux, dans un bloc de tête de
   680px et non de 22ch (carte.css:224-226 + site.css:84). */
main > section > .sb-wrap > h2{
  font-size:30px;font-weight:600;line-height:1.15;letter-spacing:-.02em;
  text-align:center;max-width:680px;margin:0 auto;
}

/* Répare les chapôs de section à 17px/16px/62ch : 21px, 26px de marge, 64ch. */
/* ⚠️ `:not(.sb-eyebrow)` : cette règle vise le CHAPÔ, et attrapait aussi le
   surtitre — un `p` enfant direct du wrap, lui aussi. Avec sa spécificité
   (0,1,3) elle battait `.sb-eyebrow` (0,1,0) sans appel.
   Mesuré sur la section « équipe » du noyau : surtitre à 21 px et collé au
   titre, contre 11 px et 18 px d'écart au prototype. Presque le double, et le
   mot « L'ÉQUIPE » pesait autant que le titre qu'il annonce.
   Un surtitre n'est pas un chapô : l'un situe, l'autre développe. */
/* ⚠️ CETTE LISTE EST LA VRAIE CORRECTION, et elle a mis trois passes à venir.
   Cette règle habille les CHAPÔS et attrapait tout `p` enfant direct du wrap.
   Elle a doublé la taille du surtitre, puis de la mention du CTA, qui ont été
   exclus l'un après l'autre — et `cta-final.html` a même dû répéter sa classe
   pour peser assez lourd. Trois contrepoids pour une cause unique.
   Un chapô développe un titre, un surtitre situe, une mention rassure, un rôle
   nomme : quatre intentions, un seul sélecteur. On les nomme ici plutôt que de
   surenchérir ailleurs. */
main > section > .sb-wrap > :is(.sb-lead,p):not(.sb-eyebrow,.sb-cta__mention,.sb-profil__role,.sb-profil__intro,.sb-profil__pied){
  font-size:clamp(17px,1.7vw,21px);font-weight:300;line-height:1.55;
  max-width:64ch;margin:26px auto 0;text-align:center;
}

/* Le surtitre de section : 13 px et non les 11 px du prototype — deux points
   de plus, demandés à l'écran, parce qu'un mono capitalisé interlettré perd en
   lisibilité bien avant un texte courant. Les 18 px de marge basse viennent du
   prototype tels quels : c'est cet écart qui empêche le surtitre de coller au
   titre et le fait lire comme une annonce plutôt que comme une première ligne. */
main > section > .sb-wrap > .sb-eyebrow{
  font-size:13px;margin:0 auto 18px;text-align:center;
}

/* Répare l'absence totale de style de surtitre — il tombait en paragraphe
   courant gris 16-17px, indistinguable du chapô (site.css:67 .eyebrow).
   .sb-eyebrow est posée d'avance : elle prendra effet dès que les gabarits
   émettront un champ surtitre. */
/* 12px et non 11 : c'est le plancher de lisibilité du canal 6. En capitales
   monospace très espacées, 11px se lit encore sur un écran de bureau — et plus
   du tout au bras tendu sur un téléphone. Relevé par la mesure, pas supposé. */
.sb-eyebrow,.sb-prose .eyebrow,.sb-prose .eye{
  font-family:var(--yhb-mono);font-size:12px;font-weight:400;line-height:1.6;
  letter-spacing:.18em;text-transform:uppercase;
  margin:0 0 18px;max-width:none;
}
.sb-paper :is(.sb-eyebrow,.eyebrow,.eye){color:var(--sb-accent-text)}
.sb-ink :is(.sb-eyebrow,.eyebrow,.eye){color:var(--yhb-accent-dark)}

/* Répare l'absence des deux filets dégradés qui détachent une zone crème du
   fond sombre (carte.css:302-306). */
main > section.sb-paper{position:relative}
main > section.sb-paper::before,
main > section.sb-paper::after{
  content:"";position:absolute;left:0;right:0;height:1px;pointer-events:none;
  background:linear-gradient(90deg,transparent,color-mix(in srgb,var(--sb-paper-fg) 16%,transparent),transparent);
}
main > section.sb-paper::before{top:0}
main > section.sb-paper::after{bottom:0}

/* ── 8. boutons ────────────────────────────────────────────────────────────
   Répare la pilule trop grosse et son dégradé aplati : 15px/30px, corps 15,5,
   dégradé 100deg cadré à 0% — le prototype pose 14px/25px, corps 15, gap 10,
   112deg avec arrêt médian à 50% cadré à 68% (site.css:180, 183-184). */

.sb-btn{
  gap:10px;padding:14px 25px;border-radius:100px;font-size:15px;
  background:var(--yhb-grad-btn);background-size:200% 200%;background-position:68% 50%;
  box-shadow:0 8px 24px -12px color-mix(in srgb,var(--yhb-accent) 70%,transparent);
}
.sb-btn:hover{
  background-position:12% 50%;transform:translateY(-2px);
  box-shadow:0 14px 32px -10px color-mix(in srgb,var(--yhb-accent) 85%,transparent);
}

/* Bouton fantôme du prototype (site.css:187 .btn-ghost + site.css:36
   --border-strong), pour les seconds CTA et les héros qui n'en veulent pas
   de plein. Posé d'avance pour le jour où le gabarit émet un second bouton. */
.sb-btn--ghost,.sb-btn--fantome,.sb-prose .btn-ghost{
  display:inline-flex;align-items:center;gap:10px;padding:14px 25px;
  border-radius:100px;font-size:15px;font-weight:600;text-decoration:none;
  background:transparent;box-shadow:none;
}
.sb-ink :is(.sb-btn--ghost,.sb-btn--fantome,.btn-ghost){color:var(--sb-ink-fg);border:1px solid var(--yhb-border-strong-dark)}
.sb-paper :is(.sb-btn--ghost,.sb-btn--fantome,.btn-ghost){color:var(--sb-paper-fg);border:1px solid color-mix(in srgb,var(--sb-paper-fg) 20%,transparent)}
.sb-btn--ghost,.sb-btn--fantome:hover,.sb-prose .btn-ghost:hover{
  background:transparent;transform:translateY(-2px);box-shadow:none;
  border-color:var(--yhb-accent-dark);color:var(--yhb-accent-dark);
}
.sb-paper :is(.sb-btn--ghost,.sb-btn--fantome,.btn-ghost):hover{border-color:var(--sb-accent-text);color:var(--sb-accent-text)}

/* Bouton d'encre — le TROISIÈME poids, demandé le 21.08.2026.
   Il ne remplace pas le fantôme : le fantôme est un bouton qui s'efface, celui-ci
   est un bouton PLEIN qui n'est simplement pas l'appel principal. Deux boutons
   pleins côte à côte, l'un en dégradé l'autre en encre, se lisent comme deux
   propositions de même rang dont une seule est prioritaire — ce que trois héros
   demandaient et qu'un fantôme ne savait pas dire.

   ⚠️ LA BORDURE N'EST PAS DÉCORATIVE. Le fond des héros est déjà de l'encre
   (`#0B0B0E` à `#14141A`) : un bouton noir sans bordure y serait un trou, pas un
   bouton. Le filet clair à 24 % le détoure — c'est le même que celui du fantôme,
   pour que les deux seconds rôles se ressemblent.
   Contraste du texte : `#FAF8F3` sur `#0B0B0E`, très au-dessus du plancher. */
.sb-btn--encre{
  display:inline-flex;align-items:center;gap:10px;padding:14px 25px;
  border-radius:100px;font-size:15px;font-weight:600;text-decoration:none;
  background:var(--sb-paper-fg);color:var(--sb-ink-fg);
  border:1px solid var(--yhb-border-strong-dark);
  box-shadow:0 8px 24px -12px rgb(0 0 0 / .75);
}
.sb-btn--encre:hover{
  background:var(--sb-paper-fg);transform:translateY(-2px);
  border-color:var(--sb-ink-fg);
  box-shadow:0 14px 32px -10px rgb(0 0 0 / .85);
}
/* Sur fond clair, le filet clair disparaîtrait : on le reprend à l'encre. */
.sb-paper .sb-btn--encre{border-color:color-mix(in srgb,var(--sb-paper-fg) 24%,transparent)}
.sb-paper .sb-btn--encre:hover{border-color:var(--sb-paper-fg)}

/* ── 9. CTA final : la clôture crème, pas une bande magenta ────────────────
   L'écart majeur commun à 8 pages sur 9. WordPress aplatit la section en
   #A02597 plein ; le prototype la pose sur le crème de la zone claire, texte
   d'encre, bordée de deux filets magenta à 20% (carte.css:294 et 346,
   site.css:2404-2406). Le bouton reprend donc son dégradé, et la ligne de
   réassurance repasse APRÈS lui, en mono 12,5px gris (site.css:2411, 2503). */

.sb-cta.sb-band{
  background:var(--sb-paper-bg);color:var(--sb-paper-fg);position:relative;
  border-top:1px solid color-mix(in srgb,var(--yhb-accent) 20%,transparent);
  border-bottom:1px solid color-mix(in srgb,var(--yhb-accent) 20%,transparent);
  padding:clamp(56px,8vh,92px) 0;
}
.sb-cta.sb-band > .sb-wrap{display:flex;flex-direction:column;align-items:center}
.sb-cta.sb-band > .sb-wrap > h2{
  order:1;color:var(--sb-paper-fg);font-size:30px;font-weight:600;line-height:1.15;
  letter-spacing:-.02em;max-width:22ch;margin:0 auto;text-align:center;
}
/* `#fff` → `--sb-accent-fg` : blanc pur interdit par tokens.css, et le jeton
   dit précisément « texte sur fond d'accent », ce qu'est ce bouton. */
.sb-cta.sb-band > .sb-wrap > .sb-btn{
  order:2;margin-top:30px;background:var(--yhb-grad-btn);
  background-size:200% 200%;background-position:68% 50%;color:var(--sb-accent-fg);
  box-shadow:0 8px 24px -12px color-mix(in srgb,var(--yhb-accent) 70%,transparent);
}
.sb-cta.sb-band > .sb-wrap > .sb-btn:hover{
  background:var(--yhb-grad-btn);background-size:200% 200%;background-position:12% 50%;
  color:var(--sb-accent-fg);box-shadow:0 14px 32px -10px color-mix(in srgb,var(--yhb-accent) 85%,transparent);transform:translateY(-2px);
}
.sb-cta.sb-band > .sb-wrap > p{
  order:3;margin:14px 0 0;max-width:none;
  font-family:var(--yhb-mono);font-size:12.5px;font-weight:400;line-height:1.6;
  letter-spacing:.03em;color:var(--yhb-muted);text-align:center;
}

/* ── 10. cartes ────────────────────────────────────────────────────────────
   Répare la plaque crème et l'ombre portée conservées sur fond sombre : en
   thème sombre le prototype rend les cartes translucides à 5% de blanc, sans
   ombre (site.css:49 et 223). Les marques rétablissent leur plaque plus bas :
   un logo est un fichier figé, il lui faut son fond clair. */

.sb-ink .sb-card{
  /* Le prototype écrit `rgba(255,255,255,.05)`, mais la plaque n'est pas « du
     blanc à 5 % » : c'est la couleur de texte de la section, posée à 5 % — et
     cette couleur est l'ivoire, pas le blanc. La différence se voit dès que la
     tonalité bascule. C'est le procédé déjà retenu par `temoignages.html` pour
     ses propres plaques, avec le même raisonnement. */
  background:color-mix(in srgb,var(--sb-ink-fg) 5%,transparent);color:var(--sb-ink-fg);
  border:1px solid var(--yhb-border-dark);box-shadow:none;
}
.sb-ink .sb-card:hover{box-shadow:none}
.sb-ink .sb-card p{color:var(--yhb-text-soft-dark)}

/* ── 11. segmentation (accueil) : des lignes, pas des blocs ────────────────
   Répare des cartes de 28px de rembourrage, rayon 18, gouttière 22 : le
   prototype pose des lignes horizontales 22/26, rayon 16, filet à 20%,
   gouttière 16 (carte.css:113, 115, 131, 132). */

.sb-segmentation .sb-grid{
  gap:16px;margin-top:30px;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));
}
.sb-segmentation .sb-card{
  padding:22px 26px;border-radius:16px;border:1px solid color-mix(in srgb,var(--sb-paper-fg) 20%,transparent);box-shadow:none;
}
.sb-segmentation .sb-card:hover{
  transform:translateY(-2px);border-color:var(--sb-accent-text);
  box-shadow:0 14px 34px -22px var(--yhb-accent);
}
.sb-segmentation .sb-card h3{font-size:18.5px;font-weight:600;letter-spacing:-.015em;margin:0 0 2px}
.sb-segmentation .sb-card p{font-size:14px;line-height:1.5;color:var(--yhb-text-soft)}

/* ── 12. sélecteur (trajectoires) — CE BLOC AVAIT PERDU SA CIBLE ───────────
   Il visait `.sb-calculateur`. Le pattern a été renommé `selecteur` (la table
   de correspondance en fin de feuille le note déjà) et plus aucun copy deck ne
   déclare `calculateur` : mesuré, 0 occurrence de `.sb-calculateur` sur les
   pages servies. Les valeurs relevées sur le prototype que ce bloc portait ont
   donc cessé de s'appliquer sans que rien ne le signale. C'est l'origine des
   deux écarts B8 « cases 52 px contre 62 » et « gouttières 8 contre 10 » :
   le module n'a jamais posé ces valeurs, c'est la marque qui les corrigeait.

   Ne restent ici que les déclarations qui divergent ENCORE. `lib/selecteur.php`
   pose déjà, et conformément au prototype, le flex, le rembourrage 11/14, le
   rayon et le corps à 15 px, ainsi que les colonnes PAR AXE — 3 pour les
   profils, 4 pour les besoins (carte.css:433-434) — que l'ancien bloc écrasait
   à 3 pour tout le monde. Les réécrire serait recréer la divergence.

   Aucune surenchère de spécificité : dans le thème compilé, le module est en
   l.1096 et l'habit de marque en l.1601. Un sélecteur de même poids l'emporte
   par l'ordre — c'est pourquoi tout ce qui suit tient en (0,1,0). */

/* Gouttière 10 px, mesurée 8 px (carte.css:432). */
.sb-selecteur__choix{gap:.625rem}
/* Tuile de 62 px, mesurée 52 px — le module pose 3.25rem (carte.css:435). */
.sb-selecteur__opt{min-height:3.875rem}
/* Titre de réponse à 30 px, mesuré 18,4 px (carte.css:391). Le module ne le
   dimensionne pas : il retombait sur le `h3{font-size:1.15rem}` du catalogue,
   qui pèse (0,0,1). La classe suffit donc à le nommer, sans contrepoids. */
.sb-reponse__t{font-size:1.875rem;font-weight:600;letter-spacing:-.02em;line-height:1.1}
/* Le contenant des marques n'avait AUCUN filet, mesuré `0px none`, là où le
   prototype en pose un (carte.css:503). Le module lui donne déjà le fond de
   plaque mais s'arrête là. Aucune couleur nouvelle : `--sb-plaque-bord` est le
   bord de plaque que la marque déclare par tonalité, et c'est déjà lui que le
   module prend pour le bord des tuiles de choix. */
.sb-reponse__marques{border:1px solid var(--sb-plaque-bord)}
/* La mesure de prose de la réponse sortait à 62ch — la valeur que
   `lib/ruptures.php` génère pour le TEXTE LONG (`.sb-prose`, `.sb-legal`).
   `lib/selecteur.php` lit `var(--sb-mesure-prose,68ch)` : le jeton étant
   toujours défini globalement, son repli de 68ch n'a jamais pu s'appliquer et
   le paragraphe de réponse héritait en silence de la mesure des pages
   juridiques. Le prototype le pose à 74ch (carte.css:174). On répond au jeton
   là où la question se pose, sur la section, plutôt que d'écraser la règle du
   module : la portée s'arrête au sélecteur, et `.sb-prose` garde ses 62ch. */
.sb-selecteur{--sb-mesure-prose:74ch}

/* ── 13. marques, variante SOMBRE (accueil, trajectoires) ──────────────────
   Répare une grille de fiches crème de 265×181 avec nom et description posée
   à plat sur l'encre : le prototype pose des plaques de logo SEULES de 190×96
   sur un fond radial prune bordé de violet (site.css:1933, 1969, 2127 ;
   carte.css .sur-clair .logo). Le défilement du ruban demande du JS. */

.sb-marques.sb-ink{
  /* Les trois prunes sont nommées d'un seul jeton en tête de feuille, avec la
     raison pour laquelle il reste FIGÉ : c'est le fond qui fait se détacher des
     plaques de logo qui, elles, ne peuvent pas suivre la tonalité. */
  background:var(--yhb-fond-plaques);
  /* Le filet reprend l'AMORCE du fond de bouton — c'était `rgba(122,44,166,.35)`,
     soit `#7A2CA6` recopié en décimal, donc invisible à toute recherche de la
     valeur. Le mélange vers `transparent` redonne exactement la même alpha. */
  border-top:1px solid color-mix(in srgb,var(--sb-accent-fond-amorce) 35%,transparent);
  padding-top:clamp(46px,6vh,62px);padding-bottom:clamp(48px,6vh,64px);
}
.sb-marques.sb-ink .sb-grid{grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:14px}
/* ⚠️ SEULE PLAQUE QUI NE SUIT PAS LA TONALITÉ, ET C'EST VOULU. Le commentaire
   du bloc le dit déjà : « un logo est un fichier figé, il lui faut son fond
   clair. » Un PNG de logo noir sur une plaque devenue sombre disparaîtrait.
   On prend donc `--yhb-plaque-logo`, dont c'est tout le rôle, plutôt que
   `--yhb-surface` que le bloc de tonalité remappe.
   `--sb-ink-fg` ne conviendrait PAS non plus, malgré la même valeur : la
   présentation « clair » le remappe sur l'encre, et la plaque virerait au noir.
   Aucun risque de contraste ici : `h3` et `p` sont masqués trois lignes plus
   bas, il ne reste que l'image. */
.sb-marques.sb-ink .sb-card{
  background:var(--yhb-plaque-logo);color:var(--sb-paper-fg);border:1px solid color-mix(in srgb,var(--sb-paper-fg) 8%,transparent);
  border-radius:14px;padding:16px 22px;min-height:96px;
  display:grid;place-items:center;box-shadow:none;
}
.sb-marques.sb-ink .sb-card:hover{transform:none;box-shadow:none}
.sb-marques.sb-ink .sb-logo{height:56px;max-height:none;max-width:150px;width:auto;object-fit:contain;margin:0}
.sb-marques.sb-ink .sb-card :is(h3,p){display:none}

/* ── 14. marques, variante CLAIRE (univers) : des tuiles lisibles ──────────
   Répare des tuiles invisibles — leur fond valait exactement le crème de la
   section (mesuré rgb(244,241,234)) : le prototype pose une plaque #FBF9F4,
   rayon 16, rembourrage 24/22, colonne alignée à GAUCHE, logo calé en haut
   à gauche à 56px (carte.css:33-34, 267-270, 427, 476, 488). */

.sb-marques.sb-paper .sb-grid{
  grid-template-columns:repeat(auto-fit,minmax(248px,1fr));gap:14px;
  margin-top:clamp(30px,4vh,52px);
}
.sb-marques.sb-paper .sb-card{
  background:var(--yhb-surface);color:var(--sb-paper-fg);border:1px solid color-mix(in srgb,var(--sb-paper-fg) 10%,transparent);
  border-radius:16px;padding:24px 22px;text-align:left;
  display:flex;flex-direction:column;gap:12px;
  box-shadow:0 18px 40px -30px rgba(0,0,0,.7);
}
.sb-marques.sb-paper .sb-card:hover{
  transform:none;border-color:var(--yhb-accent);box-shadow:0 20px 44px -26px rgba(0,0,0,.8);
}
.sb-marques.sb-paper .sb-logo{
  height:56px;max-height:none;max-width:170px;width:auto;object-fit:contain;
  align-self:flex-start;margin:0;
}
.sb-marques.sb-paper .sb-card h3{font-size:17px;color:var(--sb-paper-fg);margin:0}
.sb-marques.sb-paper .sb-card p{font-size:14px;line-height:1.55;color:var(--yhb-text-soft);margin:0}

/* ── 15. équipe (noyau) : une rangée, pas une grille ───────────────────────
   Répare une grille de 5 colonnes qui renvoie les 6 membres sur deux rangées :
   le prototype tient une seule ligne de 860px, portraits de 132px, nom 18/500
   et rôle en mono 10px capitales (site.css:355, 364, 365 ; carte.css:378-382,
   479-480). */

.sb-equipe .sb-grid{
  display:flex;flex-wrap:nowrap;align-items:flex-start;justify-content:center;
  /* 860 px pour six profils et cinq gouttières laissaient 130 px par colonne,
     soit moins que « RÉSEAU & CONNEXIONS » en mono 12 px interlettré à .08em :
     deux fonctions sur six passaient à la ligne. 1040 px donne 160 px, ce qui
     tient la plus longue sur une seule ligne. Les portraits ne changent pas de
     taille — c'est l'espace ENTRE eux qui manquait. */
  grid-template-columns:none;gap:16px;max-width:1040px;margin:44px auto 0;
}
.sb-equipe .sb-card{flex:1 1 0;min-width:0}
.sb-equipe .sb-portrait{
  width:min(132px,100%);max-width:132px;height:auto;margin:0 auto 14px;
  border:1px solid var(--yhb-border-strong-dark);
}
.sb-equipe .sb-card h3{margin:0 0 3px;font-size:18px;font-weight:500}
.sb-equipe .sb-card p,.sb-paper .sb-equipe .sb-card p{
  font-family:var(--yhb-mono);font-size:12px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--yhb-muted);
}

/* ── 16. prose : le bloc ADN du noyau ──────────────────────────────────────
   Répare une grille tombée en display:block, cartes sans fond ni filet ni
   rayon : aucune règle .dna-* n'existait dans le thème (bloc <style> en tête
   de proto-v2/noyau.html + site.css:222-226). */

.sb-prose .dna-grid{display:grid;grid-template-columns:1fr 1fr;gap:22px;margin-top:40px}
.sb-prose .dna-card{
  position:relative;overflow:hidden;margin:0;
  background:var(--yhb-surface);color:var(--sb-paper-fg);
  border:1px solid var(--yhb-border);border-radius:18px;padding:30px;
  box-shadow:0 1px 2px color-mix(in srgb,var(--sb-paper-fg) 3%,transparent),0 16px 40px -26px color-mix(in srgb,var(--sb-paper-fg) 22%,transparent);
  transition:transform .28s var(--yhb-ease),border-color .28s var(--yhb-ease),box-shadow .28s var(--yhb-ease);
}
.sb-prose .dna-card::after{
  content:"";position:absolute;left:0;right:0;top:0;height:2px;opacity:0;
  /* Le filet part de l'accent et s'éteint sur le RELAIS du dégradé de marque,
     nommé en tête de feuille : `#5B1A8C` n'était pas une couleur à part, c'est
     l'arrêt médian de `--yhb-grad` employé à plat. */
  background:linear-gradient(90deg,var(--yhb-accent),var(--yhb-grad-relais));
  transition:opacity .28s var(--yhb-ease);
}
.sb-prose .dna-card:hover{transform:translateY(-4px);box-shadow:0 22px 50px -30px color-mix(in srgb,var(--yhb-accent) 50%,transparent)}
.sb-prose .dna-card:hover::after{opacity:.9}
.sb-prose .dna-values{grid-column:1/-1}
.sb-prose .dna-top{display:flex;align-items:center;gap:13px;margin-bottom:15px}
.sb-prose .dna-ic{
  flex:none;width:48px;height:48px;border-radius:14px;display:grid;place-items:center;
  color:var(--yhb-accent);background:color-mix(in srgb,var(--yhb-accent) 12%,transparent);
  border:1px solid color-mix(in srgb,var(--yhb-accent) 28%,var(--yhb-border));
}
.sb-prose .dna-ic svg{width:25px;height:25px;fill:none;stroke:currentColor;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
/* `--sb-accent-text` et non `--yhb-accent` : à 12 px c'est du TEXTE, et
   tokens.css prévient que le magenta pur « ne passe pas AA en texte » —
   MESURÉ ici à 4,22:1 sur la plaque, sous les 4,5:1 requis. Le jeton de texte
   donne 5,74:1 sur crème, et suit la tonalité vers l'accent d'encre. Les icônes
   voisines (`.dna-ic`, `.pillar-mark`) gardent le magenta pur : ce sont des
   composants graphiques, leur seuil est 3:1, et 4,22 le passe. */
.sb-prose .dna-top .num{margin:0;font-family:var(--yhb-mono);font-size:12px;letter-spacing:.14em;color:var(--sb-accent-text)}
.sb-prose .dna-idx{margin-left:auto;font-family:var(--yhb-mono);font-size:12px;letter-spacing:.16em;color:var(--yhb-muted)}
.sb-prose .dna-card h3{margin:0 0 12px;font-size:20px;font-weight:600;color:var(--sb-paper-fg)}
.sb-prose .dna-card > p{margin:0;font-size:15px;font-weight:300;line-height:1.6;color:var(--yhb-text-soft)}

/* Répare les 5 piliers rendus en liste à puces, nom et description collés sur
   la même ligne : le prototype pose 5 cartes en grille auto-fit de 196px. */
.sb-prose .pillars{
  list-style:none;margin:20px 0 0;padding:0;display:grid;
  grid-template-columns:repeat(auto-fit,minmax(196px,1fr));gap:12px;
}
.sb-prose .pillars li{
  display:flex;gap:12px;align-items:flex-start;margin:0;padding:14px 16px;
  border:1px solid var(--yhb-border);border-radius:13px;
  background:color-mix(in srgb,var(--yhb-accent) 5%,transparent);
  transition:transform .22s var(--yhb-ease),border-color .22s var(--yhb-ease);
}
.sb-prose .pillars li:hover{transform:translateY(-2px);border-color:color-mix(in srgb,var(--yhb-accent) 42%,var(--yhb-border))}
.sb-prose .pillar-mark{flex:none;width:22px;height:22px;display:grid;place-items:center;color:var(--yhb-accent);margin-top:2px}
.sb-prose .pillar-mark svg{width:15px;height:15px;fill:currentColor}
.sb-prose .pillar-txt{display:flex;flex-direction:column;gap:4px;min-width:0}
.sb-prose .pillar-name{color:var(--sb-paper-fg);font-weight:600;font-size:15.5px;letter-spacing:.01em;line-height:1.3}
.sb-prose .pillar-desc{color:var(--yhb-text-soft);font-weight:300;font-size:14px;line-height:1.5}

/* ── 17. prose : la frise « Notre histoire » ───────────────────────────────
   Répare une liste numérotée brute où année, légende et description se
   retrouvaient sur une même ligne : le prototype pose une grille .9fr/1.6fr
   de 940px, rail vertical dégradé et pastilles roses. */

/* LE TITRE RESPIRE. Il n'y avait rien entre « Une histoire commencée bien
   avant. » et la frise : le h2 est HORS de `.sb-prose`, donc aucune des règles
   de la frise ne l'atteignait, et sa marge par défaut suffisait à peine.
   Posée sur la section qui CONTIENT une frise, pas sur tous les récits : les
   autres n'ont pas le même problème. */
.sb-recit:has(.histoire) .sb-wrap > h2{margin-bottom:clamp(1.75rem,3.5vw,2.75rem)}

/* LA COLONNE DE DROITE ÉTAIT TROP LARGE POUR CE QU'ELLE PORTE. À 1,6fr de
   940 px elle mesurait ~573 px, soit près de 75 signes par ligne — au-delà des
   65 à 70 où l'œil retrouve encore le début de la ligne suivante. Le texte y
   est court : il n'avait pas besoin de toute cette place, et le déséquilibre
   se voyait d'autant plus que la colonne de gauche, elle, est dense.
   1,25fr de 860 px donne ~474 px, soit une soixantaine de signes. */
.sb-prose .histoire{
  display:grid;grid-template-columns:.9fr 1.25fr;gap:44px;align-items:start;
  max-width:860px;margin:0 auto;
}
/* La phrase sous les portraits : une note, pas un chapô. Elle se lit après la
   grille, en retrait — même famille de gris que les rôles juste au-dessus. */
.sb-equipe__note{max-width:58ch;margin:clamp(2rem,4vw,3rem) auto 0;text-align:center;
  font-size:clamp(15px,1.5vw,17px);line-height:1.6;font-weight:300;
  color:color-mix(in srgb,currentColor 74%,transparent)}
.sb-equipe__note:empty{display:none}

.sb-prose .tl{list-style:none;margin:0;padding:0;position:relative}
.sb-prose .tl::before{
  content:"";position:absolute;left:5px;top:6px;bottom:6px;width:2px;
  /* Même rampe que le filet des cartes ADN, à la verticale : accent → relais. */
  background:linear-gradient(var(--yhb-accent),var(--yhb-grad-relais));border-radius:2px;
}
.sb-prose .tl-item{position:relative;margin:0;padding:0 0 30px 30px}
.sb-prose .tl-item:last-child{padding-bottom:0}
.sb-prose .tl-item::before{
  content:"";position:absolute;left:0;top:5px;width:12px;height:12px;border-radius:50%;
  background:var(--yhb-accent);box-shadow:0 0 14px -1px var(--yhb-accent);border:2px solid var(--yhb-ink);
}
.sb-prose .tl-year{
  display:block;font-weight:700;font-size:clamp(22px,2.6vw,30px);line-height:1;
  letter-spacing:-.02em;background:var(--yhb-grad);
  -webkit-background-clip:text;background-clip:text;color:transparent;

  /* ⚠️ LES DESCENDANTES ÉTAIENT COUPÉES — g, j, p, q, y rognés par le bas.
     `background-clip:text` ne découpe pas le DESSIN de la lettre : il découpe
     la boîte de ligne, et une `line-height` serrée la referme sous la ligne de
     base. Le dégradé s'arrête donc au milieu du jambage.
     On rend la hauteur au bloc sans écarter les lignes : un rembourrement bas,
     compensé par une marge négative de même valeur. */
  padding-bottom:.14em;margin-bottom:-.14em;}
.sb-prose .tl-cap{
  display:block;margin-top:8px;font-family:var(--yhb-mono);font-size:12px;
  letter-spacing:.13em;text-transform:uppercase;color:var(--yhb-muted-dark);
}
.sb-prose .tl-desc{display:block;margin-top:8px;font-size:14px;line-height:1.55;font-weight:300}
/* JUSTIFIÉ, ET AVEC CÉSURE — demandé le 20.08, après un passage au fer à gauche.
   ⚠️ `hyphens:auto` N'EST PAS UN ORNEMENT ICI, c'est ce qui rend la
   justification tenable. Sans césure, le navigateur ne peut étirer que les
   ESPACES pour remplir la ligne : dans une colonne de 474 px il en reste
   beaucoup à répartir, et les « rivières » blanches se creusent verticalement.
   Avec césure, il coupe les mots longs et l'étirement redevient discret.
   La langue est déclarée sur <html> par le thème — sans elle, `hyphens:auto`
   ne fait rien du tout, et en silence. */
.sb-prose .hist-text p{margin:0;font-size:16.5px;line-height:1.7;font-weight:300;
  text-align:justify;hyphens:auto;-webkit-hyphens:auto}
.sb-prose .hist-text p b{font-weight:600;color:var(--sb-ink-fg)}

/* Répare l'étranglement à 72ch (640px mesurés) : la grille ADN occupe toute la
   largeur du wrap et la frise 940px. Levé uniquement pour ces deux blocs, les
   pages juridiques gardant leur mesure de lecture. */
/* ⚠️ Le sélecteur passe par `.sb-recit .sb-wrap >` pour peser (0,3,0).
   En (0,2,0) il était à égalité avec `.sb-recit .sb-wrap > div{max-width:72ch}`,
   quelques lignes plus haut, qui attrape le même élément par un autre chemin —
   et à poids égal la dernière déclarée gagne. Mesuré : `.sb-prose` sortait à
   640,5 px (72ch) alors que sa règle propre disait `none`, et la grille de
   « Notre ADN » tenait dans 641 px au lieu des 1124 du prototype.
   Une mesure de ligne n'a de sens que pour du texte suivi : une grille de
   cartes ou une frise n'en est pas. */
.sb-recit .sb-wrap > .sb-prose:has(.dna-grid),
.sb-recit .sb-wrap > .sb-prose:has(.histoire),
.sb-prose:has(.dna-grid),.sb-prose:has(.histoire){max-width:none}
.sb-prose:has(.dna-grid) .section-head,
.sb-prose:has(.histoire) .section-head{max-width:none;margin:0 0 6px;text-align:left}

/* ── 18. prose : récit centré (accueil, ambassadeurs) ──────────────────────
   Répare un bloc livré en HTML brut que le thème n'habillait pas : surtitre
   en corps gris et « Découvrir le Noyau » en lien bleu #0000EE. Exclut les
   deux récits du noyau, qui sont des grilles pleine largeur. */

.sb-recit .sb-prose:not(:has(.dna-grid)):not(:has(.histoire)){
  max-width:680px;margin-inline:auto;text-align:center;
}
.sb-recit .sb-prose:not(:has(.dna-grid)):not(:has(.histoire)) p{
  font-size:clamp(17px,1.7vw,21px);font-weight:300;line-height:1.55;
}
.sb-prose .lede{font-size:clamp(17px,1.7vw,21px);font-weight:300;line-height:1.55}
.sb-prose .cta-row{display:flex;gap:14px;flex-wrap:wrap;justify-content:center;margin-top:34px}
.sb-prose .btn{
  display:inline-flex;align-items:center;gap:10px;padding:14px 25px;
  border-radius:100px;font-size:15px;font-weight:600;text-decoration:none;
  background:var(--yhb-grad-btn);background-size:200% 200%;background-position:68% 50%;
  /* `#fff` → `--sb-accent-fg` : blanc pur interdit par tokens.css. */
  color:var(--sb-accent-fg);box-shadow:0 8px 24px -12px color-mix(in srgb,var(--yhb-accent) 70%,transparent);
}

/* ── 19. ambassadeurs — LE FORÇAGE À L'ENCRE A ÉTÉ RETIRÉ le 20.08.2026 ─────
   Ce bloc rendait la page sombre de bout en bout, y compris les sections qui
   DÉCLARENT `tonalite: clair` dans le copy deck. Il le faisait par parité avec
   le prototype — « le prototype garde l'encre partout » — et c'était un choix
   défendable tant que personne ne demandait autre chose.
   Demandé le 20.08 : « Comment ça marche » et le formulaire de candidature
   doivent être CLAIRS. Le copy deck le déclarait déjà ; la règle l'écrasait.
   ⚠️ CE QUE ÇA APPREND, ET QUI VAUT AU-DELÀ D'ICI. Une déclaration de tonalité
   qu'une feuille peut annuler en silence n'est plus une déclaration : le
   diagnostic a coûté trois détours — vérifier la base, puis les classes rendues
   (`sb-paper`, correctes), avant de trouver que le FOND PEINT ne suivait pas.
   Une règle qui force une tonalité doit se voir depuis le copy deck, ou ne pas
   exister. Elle n'existe plus.
   Les sections restées en `sombre` — le héros, « Le cadre » — le sont parce
   qu'elles le DÉCLARENT, ce qui est vérifiable et modifiable sans toucher au
   CSS. */

/* Les trois gestes — CE BLOC VISAIT LA MAUVAISE SECTION.
   Son commentaire disait déjà ce qu'il répare : « les trois gestes », soit
   `.amb-orbit` / `.amb-geste` du prototype (ambassadeurs.html:77-87). Il était
   pourtant ancré sur `.sb-promesse`, qui ne porte pas les trois gestes mais les
   QUATRE mentions du cadre. Deux conséquences mesurées sur /ambassadeurs/ :
   les quatre mentions sortaient en cartes de 355×125 numérotées 01 à 04, et les
   trois gestes ne recevaient rien — liste verticale de 3 × 1124×87, pastille de
   32 px calée à gauche, là où le prototype pose trois colonnes et des pastilles
   de 74 px. C'est l'écart B8 « les quatre mentions sortent en cartes, pas en
   pastilles » : il ne vient pas d'une règle manquante mais d'une règle posée sur
   la section voisine.

   Ré-ancré sur `.sb-steps` / `.sb-step`, que SEUL `deroule` emploie (vérifié sur
   le catalogue) : la portée est donc exacte sans avoir à nommer un ancêtre, et
   les règles pèsent (0,1,0) comme celles du catalogue qu'elles précisent —
   l'ordre suffit, aucune surenchère.

   Le compteur `geste` disparaît : `patterns.css` pose déjà `counter-reset:sb`
   et `counter-increment:sb` sur ces deux classes. Deux compteurs pour une seule
   numérotation, c'était une divergence en attente. */
.sb-steps{
  grid-template-columns:repeat(3,1fr);gap:30px;margin-top:48px;position:relative;
}
.sb-steps::before{
  content:"";position:absolute;top:37px;left:16%;right:16%;height:1px;
  /* Le filet se tire de l'ENCRE DE LA SECTION, pas d'un fond écrit en dur :
     `--sb-paper-bg` donnait un trait crème, visible sur l'encre et invisible
     sur le crème. `currentColor` suit la tonalité sans qu'on écrive la règle
     deux fois. */
  background:linear-gradient(90deg,transparent,color-mix(in srgb,currentColor 24%,transparent) 20%,color-mix(in srgb,currentColor 24%,transparent) 80%,transparent);
}
/* `padding-left:3rem` du catalogue réservait la gouttière de la pastille calée
   à gauche : elle est ici au-dessus et centrée, la réserve n'a plus d'objet. */
.sb-step{padding-left:0;text-align:center;position:relative}
/* ⚠️ LA PASTILLE ET LE TITRE SUIVENT LA TONALITÉ. Ils étaient dessinés pour
   l'encre et l'encre seule — disque noir, accent sombre dedans, titre blanc.
   Sur le crème d'/ambassadeurs/, ça donnait trois ronds noirs qui ne
   raccordaient à rien et des titres blancs sur crème, illisibles.
   Le disque prend donc le CONTRAIRE de sa section : encre sur clair, clair sur
   encre. C'est ce qui lui donne son poids de numéro dans les deux cas — un
   disque de la couleur du fond ne serait plus un disque.
   Le chiffre, lui, garde l'accent : c'est le seul endroit coloré, et il est
   posé sur le disque, pas sur la section. */
.sb-step::before{
  content:"0" counter(sb);width:74px;height:74px;margin:0 auto 22px;
  position:relative;left:auto;top:auto;z-index:1;
  border-radius:50%;display:grid;place-items:center;
  font-family:var(--yhb-mono);font-size:21px;
  color:var(--yhb-accent-dark);
  /* ⚠️ CRÈME, PAS ENCRE — demandé le 20.08 : « les ronds sont noirs, c'est
     mieux en couleur crème ». Le raisonnement d'avant (« le disque prend le
     CONTRAIRE de sa section ») tenait sur le papier et pas à l'œil : trois
     disques noirs de 74 px sur le crème d'/ambassadeurs/ pèsent plus que les
     titres qu'ils numérotent, et le regard tombe dessus avant de lire.
     Le disque reste donc DANS la famille du fond — un crème d'un cran plus
     dense — et c'est le FILET qui le détache. Un disque se lit par son bord
     autant que par sa masse ; le chiffre garde l'accent, seul point coloré. */
  background:color-mix(in srgb,var(--sb-paper-fg) 6%,var(--sb-paper-bg));
  border:1px solid color-mix(in srgb,var(--sb-paper-fg) 16%,transparent);
}
.sb-ink .sb-step::before{
  color:var(--sb-accent-text,var(--sb-accent));
  background:var(--sb-paper-bg);
  border-color:color-mix(in srgb,var(--sb-paper-bg) 70%,transparent);
}
.sb-step h3{font-size:18px;font-weight:700;color:var(--sb-paper-fg);margin:0 0 9px}
.sb-ink .sb-step h3{color:var(--sb-ink-fg)}
/* ⚠️ LE GRIS SUIT LA TONALITÉ, il ne la suppose pas. Cette règle posait le gris
   d'ENCRE sans condition : tant que la page « ambassadeurs » était forcée en
   sombre, ça passait. Le forçage retiré, la section est devenue crème et le
   texte est tombé à 1,55 — gris clair sur crème.
   Un gris de tonalité écrit en dur est une bombe à retardement : il ne casse
   que le jour où quelqu'un change le fond, c'est-à-dire longtemps après. */
.sb-step p{
  font-size:14px;font-weight:300;line-height:1.6;
  color:var(--yhb-text-soft);margin:0 auto;max-width:30ch;
}
.sb-ink .sb-step p{color:var(--yhb-text-soft-dark)}

/* Le cadre — quatre mentions en PASTILLES, pas en cartes.
   Relevé sur `.amb-facts` (ambassadeurs.html:91-93) : rangée souple centrée,
   gouttière 10, marge haute 30 ; chaque mention en mono 12 px interlettré à
   .05em, filet de 1 px, coins pleinement arrondis, rembourrage 9/17. Les quatre
   mentions n'ont qu'un intitulé — le copy deck ne leur donne pas de texte — donc
   rien à cadrer : une pastille suffit, et c'est ce que le prototype en fait.

   Aucune couleur nouvelle : la section est à l'encre, et `--sb-plaque-bord` y
   vaut `--sb-paper-bg` à 13 % — exactement le `rgba(244,241,234,.13)` mesuré sur
   le filet des pastilles du prototype. Le jeton dit la même chose que le relevé. */
.sb-promesse .sb-grid{
  display:flex;flex-wrap:wrap;justify-content:center;gap:10px;margin-top:30px;
}
.sb-promesse .sb-card{
  background:none;box-shadow:none;padding:9px 17px;
  border:1px solid var(--sb-plaque-bord);border-radius:100px;
}
.sb-promesse .sb-card:hover{transform:none;box-shadow:none;border-color:var(--yhb-border-strong-dark)}
/* `line-height` explicite : l'intitulé est porté par un `h3`, qui hérite de
   l'interligne serré des titres et donnait une pastille de 33 px de haut contre
   39 au prototype. Là-bas c'est un `<span>` : il prend l'interligne du corps,
   1.6, que la feuille pose sur `body`. On redit donc la valeur du corps, parce
   que la balise, elle, ne peut pas être celle du prototype. */
.sb-promesse .sb-card h3{
  font-family:var(--yhb-mono);font-size:12px;font-weight:400;letter-spacing:.05em;
  line-height:1.6;color:var(--yhb-text-soft-dark);margin:0;
}

/* ── 20. gabarit « lieu » détourné (hello, ambassadeurs, yan) ──────────────
   Répare la colonne de 18rem et les 804px de vide laissés par la figure de
   carte absente : sans carte, ce n'est pas un lieu, on repasse en pleine
   largeur centrée (site.css:84, 1743). Le vrai correctif est de mapper ces
   trois sections sur le gabarit « formulaire », pas sur « lieu ». */

.sb-lieu .sb-wrap--split:has(> .sb-carte:not(:has(img))){display:block;text-align:center}
.sb-lieu .sb-wrap--split:has(> .sb-carte:not(:has(img))) > div > h2{
  font-size:30px;font-weight:600;line-height:1.15;letter-spacing:-.02em;
  text-align:center;max-width:680px;margin:0 auto;
}
.sb-lieu .sb-wrap--split:has(> .sb-carte:not(:has(img))) > div > :is(p,.sb-adresse){
  text-align:center;max-width:64ch;margin:16px auto 0;
  font-size:17px;line-height:1.6;font-weight:300;
}

/* ── 21. gabarit « intégré » : socle commun ────────────────────────────────
   Répare un conteneur sans mesure ni rapport d'image : le jour où l'iframe
   revient, elle s'étalerait sur toute la largeur (carte.css:484 .ik-vod +
   le padding-top:56.25% du prototype). */

.sb-integre .sb-embed{max-width:880px;margin:26px auto 0;border-radius:16px;overflow:hidden}
.sb-integre .sb-embed iframe{width:100%;height:auto;aspect-ratio:16/9;display:block;border:0}
/* Le lecteur vidéo du noyau est verrouillé à 820px (seule page où « intégré »
   précède l'équipe). */
.sb-integre:has(+ .sb-equipe) .sb-embed{max-width:820px}

/* ── 22. RETIRÉ — visait « intégré suivi de lieu » et « héros suivi de intégré
   puis lieu », la suite de sections de l'ancien hello. Mesuré le 2026-08-18 sur
   les 12 pages servies : `.sb-integre` n'existe que sur /noyau/ (suivi de
   `.sb-equipe`), `.sb-lieu` que sur /plan-acces/ — la combinaison n'est nulle
   part, les 5 règles correspondaient à 0 élément. hello sert désormais
   hero → rendezvous → formulaire → cta. */

/* ── 23. plan d'accès : la section du lieu reste sombre ────────────────────
   Répare une tonalité inversée et l'absence de plaque : le prototype garde
   l'encre teintée de blanc à 2,7% et pose une carte de 960px, filet 1px,
   rayon 18, fond blanc 3%, rembourrage 30/28. Le titre y est un titre de
   CARTE (19px, à gauche), pas un titre de section, et le surtitre passe
   AVANT lui.
   ⚠️ Ancré sur `.sb-lieu` et non `.sb-integre` : le copy deck de plan-accès
   enchaîne hero → LIEU → cta-final. Ces règles visaient `.sb-integre`, donc
   une section que cette page ne rend pas — tout ce bloc était du code mort,
   et le plan n'avait ni plaque, ni filet, ni hauteur.

   ⛔ CE BLOC EST À MOITIÉ APPLIQUÉ, ET IL CASSE LA PAGE. Relevé le 2026-08-18
   sur /plan-acces/ servi, depuis que R60 a réveillé l'adjacence :

     · `color:var(--sb-ink-fg)` ci-dessous, spécificité (0,2,0), BAT le
       `.sb-paper{color:var(--sb-paper-fg)}` (0,1,0) de la tonalité ;
     · le `background` de la même règle, lui, PERD contre
       `body:has(.sb-fond-etoile) .sb-paper{background:var(--sb-paper-bg)}`
       (0,3,0), relevé via CSS.getMatchedStylesForNode.

   Résultat mesuré : texte ivoire #FAF8F3 sur fond crème #F4F1EA, soit
   **1,06:1** — le h2 « Nos bureaux sont à Lausanne… », l'adresse, les deux h3
   et le corps des deux cartes sont INVISIBLES. Seuls les surtitres magenta
   (5,74:1) et le bouton survivent. Vérifié à la capture.

   La section porte `sb-lieu sb-paper` : le compilateur la veut CLAIRE, ce bloc
   la veut SOMBRE, et seule la moitié « couleur du texte » passe. Trancher —
   soit la section est déclarée sombre côté compilateur (comme `hero.html`
   porte `sb-ink`), soit on retire le remappage de tonalité d'ici. Non corrigé :
   c'est une décision d'apparence, pas un nettoyage.

   ⚠️ Les 6 règles `> .sb-wrap > h2`, `> .sb-wrap > .sb-lead` et `.sb-embed*`
   plus bas ne correspondent à RIEN (0 sur les 12 pages) : le pattern rend
   `.sb-wrap > article.sb-lieu__principal > h2` et pas de `.sb-embed` du tout
   (la carte est un `.sb-lieu__plan-interactif > .sb-embed__cadre`). Gardées :
   leur combinaison de sections, elle, existe — c'est la forme interne du
   pattern qui a changé, pas la page. À réancrer en même temps que ci-dessus. */

/* ⚠️ CETTE RÈGLE NE FORCE PLUS LA TONALITÉ, ET C'EST LA CORRECTION.
   Elle posait `color:var(--sb-ink-fg)` — du texte clair — ET un fond d'encre.
   La couleur passait (0,2,0 bat `.sb-paper` en 0,1,0) ; le fond PERDAIT contre
   `body:has(.sb-fond-etoile) .sb-paper{background:var(--sb-paper-bg)}` en
   (0,3,0). Résultat servi sur /plan-acces/ : ivoire #FAF8F3 sur crème #F4F1EA,
   soit **1,06:1**. Le titre, l'adresse, les deux sous-titres et le corps des
   deux cartes étaient INVISIBLES. Régression réveillée par R60.

   Une tonalité se déclare par sa PAIRE ou pas du tout — troisième fois que ce
   défaut se présente (voir R61b sur `.sb-profil.sb-ink`). Et elle se déclare
   dans le copy deck (`tonalite: sombre`), là où l'intention se lit, pas par un
   sélecteur d'adjacence qui doit gagner une course de spécificité pour être
   vrai. Surenchérir ici aurait été un quatrième contrepoids.

   Ne reste donc que la GÉOMÉTRIE, qui elle dépend bien du voisinage : cette
   section touche l'appel final, elle respire donc moins en haut et plus en bas. */
.sb-lieu:has(+ .sb-cta){
  padding-top:clamp(20px,3vh,36px);padding-bottom:clamp(64px,9vh,120px);
}
.sb-lieu:has(+ .sb-cta)::before,.sb-lieu:has(+ .sb-cta)::after{display:none}
.sb-lieu:has(+ .sb-cta) > .sb-wrap{max-width:1016px;display:flex;flex-direction:column;align-items:stretch}
/* RETIRÉ le 20.08.2026 — SIX règles, ZÉRO correspondance. Vérifié au
   navigateur en parcourant `document.styleSheets` sur /plan-acces/ servi :
     `> .sb-wrap > h2` et `> .sb-wrap > .sb-lead` → le gabarit rend
        `.sb-wrap > article.sb-lieu__principal > h2`, jamais un enfant direct ;
     les quatre `.sb-embed*` → le champ `carte` porte `[gneiz_carte]`, qui rend
        `<figure class="sb-carte"><img>`. Il n'y a plus d'iframe, donc plus de
        `.sb-embed` nulle part sur cette page.
   Trois d'entre elles étaient de surcroît des couleurs d'ENCRE sans
   contrepartie claire — dont `--yhb-accent-dark` (#FF2EA6), qui mesure 3,0:1
   sur le crème, sous AA. Mortes, elles ne se voyaient pas ; ranimées par un
   retour de l'iframe, elles auraient peint de l'encre sur une section devenue
   claire — le jour même où on la fait passer au clair. Mieux valait les retirer
   que les documenter une fois de plus.
   L'habillage utile du plan est ailleurs et vit : `.sb-carte img` et
   `.sb-carte__pied`, plus bas dans cette feuille. */
/* RETIRÉ — visait « héros suivi de intégré puis appel » et « intégré suivi de
   l'appel » (le CTA prune à halo magenta de l'ancien plan-accès). Mesuré le
   2026-08-18 : aucune page ne fait suivre un héros d'un `.sb-integre`, et le
   seul `.sb-integre` du site (/noyau/) est suivi de `.sb-equipe`, pas d'un
   `.sb-cta` — les 4 règles correspondaient à 0 élément. plan-accès sert
   hero → lieu → cta. */

/* ── 24. RETIRÉ — visait « héros suivi du calculateur », l'ancienne page
   trajectoires. Mesuré le 2026-08-18 sur les 12 pages servies : la classe
   `.sb-calculateur` n'existe sur AUCUNE — les 3 règles correspondaient à 0
   élément. trajectoires sert hero → `.sb-selecteur` → cta.
   ⚠️ Les règles `.sb-calculateur .sb-grid` des media queries du bloc 28 sont
   mortes pour la même raison ; laissées en place, hors du périmètre de cette
   revue d'adjacence. */

/* ── 25. pages juridiques ──────────────────────────────────────────────────
   Répare une colonne centrée de 641px et un corps à 16px/400 : le prototype
   pose 760px collés à gauche du wrap, 15,5px/300/1,7, intertitres à 23px avec
   38px de blanc, et la date de mise à jour en mono 11px capitales
   (site.css:640-650). */

.sb-legal{padding-top:clamp(20px,3vh,36px);padding-bottom:clamp(64px,9vh,120px)}
.sb-legal > .sb-wrap > h2{
  font-size:23px;font-weight:600;line-height:1.1;letter-spacing:-.02em;
  max-width:760px;margin:0 0 12px;text-align:left;
}
.sb-legal .sb-prose{max-width:760px;margin-inline:0;text-align:left}
.sb-legal .sb-prose :is(p,li){font-size:15.5px;font-weight:300;line-height:1.7;max-width:none}
.sb-legal .sb-prose p{margin:0 0 14px}
.sb-legal .sb-prose li{margin:0 0 6px}
.sb-legal .sb-prose ul{margin:0 0 16px;padding-left:20px}
.sb-legal .sb-prose h2{
  font-size:23px;font-weight:600;line-height:1.1;letter-spacing:-.02em;
  margin:38px 0 12px;text-align:left;max-width:none;
}
.sb-legal .sb-prose h2:first-of-type{margin-top:0}
.sb-legal .sb-prose h3{font-size:17px;font-weight:600;line-height:1.1;margin:24px 0 8px}
.sb-legal .sb-prose .upd{
  font-family:var(--yhb-mono);font-size:12px;font-weight:400;letter-spacing:.1em;
  text-transform:uppercase;margin:0 0 30px;
}
/* Répare le surtitre « Légal » rendu APRÈS le h1 en chapô gris 17px : le
   prototype le pose AVANT, en mono 11px capitales magenta (site.css:67).

   ⚠️ Réveillée par R60, cette règle n'applique que la MOITIÉ de ce qu'elle
   écrit. Relevé le 2026-08-18 sur /cgu/ (identique sur les 3 autres) :
   `order:1` passe — le surtitre remonte bien AVANT le h1, l'intention est
   tenue — mais `font-size:12px` et `color:var(--yhb-accent-dark)` sont battus
   par la règle générique du chapô de héros, plus spécifique :
   `main > section.sb-hero > .sb-wrap > :is(…):not(…)` = (0,9,2) contre (0,4,0)
   ici. Rendu réel : « LÉGAL » en mono 21px GRIS, pas 12px magenta.
   Lisible, mais ce n'est pas ce qui est écrit. Pour que la couleur et le corps
   passent, il faut égaler la structure de l'ancre générique, pas surenchérir —
   laissé en l'état, c'est un choix d'apparence à valider. */
/* R80. LE HÉROS COMPACT NE FAIT QUE SA HAUTEUR — contenu et marges comprises.
   Demandé par Yan pour /plan-acces/ et /ambassadeurs/ ; mesuré 447 px de
   contenu réel sous un `min-height:70svh` qui en imposait 717 sur son écran.

   ── L'HISTORIQUE, PARCE QU'IL EXPLIQUE LA FORME D'AUJOURD'HUI ──────────────

   Le PROTOTYPE déclarait déjà ses héros compacts :

     plan-acces.html   class="hero legal-hero"
     hello.html        class="hero compact"
     cgu, cookies, données, mentions   class="hero legal-hero"
     index, univers, noyau, trajectoires, ambassadeurs   class="hero"

   et sa feuille disait `.hero:not(.compact):not(.legal-hero){min-height:70svh}`.
   La chaîne PERDAIT ces deux modificateurs : tout héros sortait en `sb-hero`
   nu. Faute de prise, ces règles nommaient les sections SUIVANTES —
   `.sb-hero:has(+ .sb-legal)`, `:has(+ .sb-lieu)`, `:has(+ .sb-deroule)`.

   Ce rattrapage a coûté ce qu'on lui prédisait, et plus tôt que prévu : il
   couvrait les pages légales (`.sb-legal` suit), plan-accès (`.sb-lieu`) et
   ambassadeurs (`.sb-deroule`), mais PAS /hello/, dont le second bloc est
   `.sb-rendezvous`. Cette page est restée haute alors que son prototype la
   déclarait `compact`, et rien — ni build, ni canal, ni journal — ne pouvait le
   dire. Une section trop haute n'est une erreur pour personne.

   ── CE QUI LES REMPLACE (B11) ─────────────────────────────────────────────

   Le modificateur traverse maintenant la chaîne : le copy deck écrit
   `modificateur: compact`, et la section sort en `sb-hero--compact`. Voir
   lib/compiler.php (champModificateur) et lib/build.php (sb_modificateurs).
   Les règles ci-dessous ne regardent donc plus que le héros lui-même. Une page
   qui change de second bloc ne change plus de hauteur.

     compact   /hello/, /plan-acces/, /ambassadeurs/
     legal     /cgu/, /cookies/, /donnees-personnelles/, /mentions-legales/

   `legal` est `compact` PLUS la remontée du chapô au-dessus du titre : ces
   quatre pages écrivent leur surtitre « Légal » dans `chapo`, faute d'un champ
   `surtitre` dans leur copy deck. C'est la seule raison pour laquelle
   /plan-acces/ est ici en `compact` alors que le prototype l'écrit
   `legal-hero` : elle a un vrai surtitre ET un vrai chapô, et la remontée
   retournerait sa phrase d'accueil en capitales de 12 px. Elle garde donc la
   hauteur seule — ce que le site rendait déjà avant B11, `:has(+ .sb-lieu)`
   ne posant que `min-height:0`. Écart au prototype connu et sans effet visible
   à ce jour : le `padding-bottom` réduit de `legal-hero` ne s'y applique pas.

   ── ⚠️ ÉCART ASSUMÉ ───────────────────────────────────────────────────────

   `/ambassadeurs/` est déclaré `hero` SIMPLE dans le prototype, donc haut. Yan
   le veut compact. On s'écarte du prototype sciemment — et l'écart a changé de
   place : il ne se lit plus dans un sélecteur d'adjacence qui ne nommait aucune
   page, mais dans brands/yhb/content/ambassadeurs.md, sur la section
   concernée, où il est commenté. Voir aussi ECARTS-ASSUMES.md.

   ── POURQUOI `.sb-hero.sb-hero--legal` ET NON `.sb-hero--legal` SEUL ───────

   Pour garder la spécificité à l'identique. Les anciens sélecteurs pesaient
   (0,2,0) et (0,4,0) — `:has()` compte son argument le plus spécifique — et
   le commentaire de la section 25 documente un arbitrage déjà réglé à ce
   poids-là : la remontée du chapô gagne sur `order`, perd sur `font-size` et
   `color` face à l'ancre générique en (0,4,2). Écrire la classe seule ferait
   perdre un cran à toutes ces règles d'un coup, pour un gain nul. On rejoue le
   même poids, et le rendu ne bouge pas. */
.sb-hero.sb-hero--compact,
.sb-hero.sb-hero--legal{min-height:0}

/* ET LE REMBOURRAGE SUIT LE PLANCHER, LUI AUSSI.
   Le `min-height:70svh` avait été retiré du héros compact — c'est fait, et
   mesuré : `min-height` calculé vaut bien 0. Il restait pourtant une part de
   FENÊTRE dans la hauteur, et personne ne l'avait vue : le rembourrage de
   `.sb-hero` est `clamp(56px,8vh,104px)` / `clamp(48px,7vh,96px)`. Un héros
   compact grandissait donc encore avec l'écran.

   MESURÉ, même contenu, deux fenêtres :
     1280 × 900   419 px de héros, rembourrage 72 / 63
     1512 × 1400  484 px de héros, rembourrage 104 / 96
   +65 px que rien ne remplit. « Compact » disait « ne fait que sa hauteur,
   contenu ET marges comprises » : la moitié « marges » manquait.

   Les valeurs ne sont pas choisies, ce sont les PLANCHERS du clamp — 56 et 48.
   La part `vh` disparaît, le rendu sur petit écran ne bouge pas d'un pixel.

   ⚠️ PORTÉE : les TROIS pages compactes, arbitré le 20.08. /plan-acces/,
   /hello/ et /ambassadeurs/. Le modificateur nomme une FORME, et « ne fait que
   sa hauteur » est cette forme — le corriger sur une seule page laisserait les
   deux autres mentir sur leur propre nom. */
.sb-hero.sb-hero--compact{padding-top:56px;padding-bottom:48px}

/* ── /hello/ RETROUVE LA HAUTEUR STANDARD SUR BUREAU ───────────────────────
   Demandé le 21.08. MESURÉ avant : 463 px dans une fenêtre de 900, soit 51 %,
   contre 70 % pour les héros de l'accueil et de /trajectoires/. La page
   d'entrée en contact était la plus basse du site, alors que c'est celle où
   l'on veut de l'air avant la première question.

   ⚠️ SUR BUREAU SEULEMENT, et c'est délibéré. Le modificateur `compact` a été
   posé sur cette page pour une raison qui tient toujours en petite largeur :
   sous 768 px, un héros à 70 % de la fenêtre repousse le formulaire de
   réservation sous la ligne de flottaison, et le visiteur ne voit pas ce pour
   quoi il est venu. Le compact reste donc là où il sert.

   `70svh` et non `70vh` : sur un téléphone, `vh` compte la barre d'adresse qui
   se rétracte au défilement, donc la hauteur change en cours de route. Le
   `svh` prend la petite hauteur, celle qui ne bouge pas. Ici la règle ne mord
   qu'au-dessus de 768 px, mais on écrit la même unité que le héros standard —
   deux unités pour une même intention, c'est une divergence en attente. */
@media (min-width:48rem){
  body.page-hello .sb-hero.sb-hero--compact{
    min-height:70svh;
    padding-top:clamp(72px,11vh,132px);
    padding-bottom:clamp(72px,11vh,132px);
  }
}
.sb-hero.sb-hero--legal{padding-bottom:clamp(30px,4.5vh,58px)}
.sb-hero.sb-hero--legal > .sb-wrap{display:flex;flex-direction:column;align-items:center}
.sb-hero.sb-hero--legal > .sb-wrap > .sb-hero__lead{
  order:1;font-family:var(--yhb-mono);font-size:12px;font-weight:400;line-height:1.6;
  letter-spacing:.18em;text-transform:uppercase;color:var(--yhb-accent-dark);
  max-width:none;margin:0 0 18px;
}
.sb-hero.sb-hero--legal > .sb-wrap > .sb-hero__title{order:2}
/* Aucun héros légal ne porte de bouton aujourd'hui (0 correspondance sur les 4
   pages) : gardée, car c'est le 3e terme d'un ordre — la retirer laisserait un
   bouton ajouté demain se placer avant le titre. */
.sb-hero.sb-hero--legal > .sb-wrap > .sb-btn{order:3}

/* ── 26. yan ── RETIRÉ. Son ancre était
   `body:has(main > .sb-hero + .sb-lieu:last-child)` : la page servait
   `hero`+`communaute`+`lieu`, elle sert maintenant un seul `profil`.
   Le sélecteur ne peut plus mordre — voir la section 27. */

/* ── 27. pied de page ──────────────────────────────────────────────────────
   Répare 56/48px de respiration et un filet à 13% : le prototype pose 70/60
   et un filet à 8%. */

.sb-footer{padding:70px 0 60px;border-top:1px solid color-mix(in srgb,var(--sb-paper-bg) 8%,transparent)}

/* ── 28. téléphone ─────────────────────────────────────────────────────────
   Répare les grilles fixes posées plus haut, qui débordent sous 1000px. */

@media (max-width:1000px){
  .sb-calculateur .sb-grid{grid-template-columns:repeat(2,1fr)}
  .sb-prose .histoire{grid-template-columns:1fr;gap:26px}
}
@media (max-width:820px){
  .sb-equipe .sb-grid{flex-wrap:wrap}
}
@media (max-width:760px){
  .sb-prose .dna-grid{grid-template-columns:1fr}
}
@media (max-width:720px){
  main > section{padding:clamp(52px,7vh,88px) 0}
  /* Ré-ancrés en même temps que le bloc des trois gestes : ils le suivaient et
     visaient eux aussi `.sb-promesse`. Le repli d'une colonne et la disparition
     du filet sont ceux du prototype (ambassadeurs.html:87). La ligne
     `.sb-calculateur` qui les précédait est retirée : le pattern n'existe plus.
     Les quatre mentions n'ont pas de repli à déclarer — une rangée souple
     enveloppe d'elle-même. */
  .sb-steps{grid-template-columns:1fr;gap:36px}
  .sb-steps::before{display:none}
}

/* ═══════════════════════════════════════════════════════════════════════════
   TABLEAU D'ANCRAGE — quelle page est visée par quel sélecteur, et pourquoi
   il tient à travers une reconstruction :

     accueil       .sb-segmentation, .sb-temoignages, .sb-marques.sb-ink
     trajectoires  .sb-selecteur                    (était .sb-calculateur)
     univers       .sb-marques.sb-paper, .sb-communaute
     noyau         .sb-equipe, .sb-prose:has(.dna-grid|.histoire),
                   .sb-integre:has(+ .sb-equipe)
     hello         .sb-rendezvous, .sb-formulaire   (était .sb-integre + .sb-lieu)
     ambassadeurs  body:has(.sb-promesse), .sb-steps/.sb-step (les trois gestes,
                   ré-ancrés le 18.08 — ils visaient .sb-promesse, qui porte les
                   quatre mentions du cadre et non les gestes), .sb-promesse
                   .sb-card (les quatre mentions, en pastilles)
     plan-acces    .sb-lieu:has(+ .sb-cta)
     pages légales .sb-legal, .sb-hero--legal   (était .sb-hero:has(+ .sb-legal))
     yan           .sb-profil                       (était .sb-hero + .sb-lieu)

   La ligne des pages légales est la première de ce tableau à ne PLUS dépendre
   de la suite des sections : depuis B11, la page DÉCLARE la forme de son héros
   dans son copy deck et la chaîne la porte jusqu'à une classe. C'est la sortie
   qu'on cherche pour les autres lignes — tant qu'un sélecteur décrit une
   VOISINE plutôt que la section elle-même, il tient par un fait de mise en
   page, pas par une intention.

   Suite des sections relevée le 2026-08-18 sur le site servi — c'est elle qui
   fait vivre ou mourir un sélecteur d'adjacence, et elle seule :

     accueil       hero → portes → marques → chiffres → recit → temoignages → cta
     noyau         hero → recit → integre → equipe → recit → cta
     trajectoires  hero → selecteur → cta
     univers       hero → constellation → communaute → cta
     hello         hero → rendezvous → formulaire → cta
     ambassadeurs  hero → deroule → promesse → formulaire → cta
     yan           profil                                    (section unique)
     plan-acces    hero → lieu → cta
     4 pages légales  hero → legal → cta

   La tonalité .sb-ink / .sb-paper vient de l'alternance par rang de
   lib/build.php:177 — c'est elle qui sépare les deux traitements de
   « marques ». Si l'ordre des sections d'une page change dans son .md, les
   blocs 13, 14 et 19 sont à revérifier — et les blocs d'adjacence 21, 23, 25
   avec eux : une section insérée ou retirée les allume ou les éteint sans
   qu'aucun fichier CSS ne bouge.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ═══ champs ajoutés au pattern hero — surtitre, second appel ═══════════════
   Un champ vide ne doit rien laisser derrière lui : sans ces règles :empty, une
   page sans surtitre garderait un <p> vide qui pousse le titre vers le bas, et
   un bouton fantôme sans libellé resterait cliquable et muet. */

.sb-hero .sb-eyebrow{
  font-family:var(--yhb-mono);
  font-size:12px;letter-spacing:.18em;text-transform:uppercase;
  /* Était `#C77BB8` : un mauve délavé sans jeton, sans relevé et sans raison
     écrite — une couleur inventée, arrivée là pour tenir le contraste sur
     l'encre. C'est le rôle exact de `--sb-accent-encre`.
     Mesuré sur le fond réel du héros (--yhb-ink #0A0910) : #C77BB8 donnait
     6,89:1, le jeton donne 5,85:1. Les deux passent AA très au large pour du
     12 px, et la teinte redevient celle de la marque au lieu d'être un rose
     gris que rien ne rattache à elle. */
  color:var(--sb-accent-encre);margin:0 0 18px;text-align:center;
}
.sb-hero .sb-eyebrow:empty{display:none}

/* LE LIEN VIDÉO RESPIRE. Il n'avait aucune règle d'espacement : il héritait de
   la gouttière du héros et se collait sous les deux boutons, au point de se
   lire comme un troisième bouton mal aligné plutôt que comme une porte de côté.
   Le retrait qu'on lui donne est ce qui dit qu'il est secondaire — c'est de
   l'espace, pas une couleur ni une taille, donc ça tient dans les deux
   tonalités et à toutes les largeurs. */
.sb-hero__video-ligne{margin-top:clamp(1.5rem,3vw,2.25rem)}

/* ⚠️ LES 34 px DE `.sb-hero .sb-btn` (§6, plus haut) NE VALENT QUE POUR LE
   BOUTON SEUL DU HÉROS, et ils empoisonnaient cette rangée.

   Ce sélecteur attrape AUSSI les boutons portés ici, qui ont déjà leur écart au
   chapô par le `margin-top` du conteneur. Sur UNE ligne, les 34 px décalent les
   quatre boutons du même montant : invisible, donc jamais relevé. Dès que la
   rangée passe à la ligne — c'est-à-dire sur tout téléphone — cette marge
   s'AJOUTE au `gap` et devient l'écart VERTICAL entre chaque bouton.

   Mesuré sur /hello/, 390 px, les quatre boutons empilés : 48 px entre deux
   boutons hauts de 54 px (14 de gouttière + 34 de marge), pour un bloc de
   394 px. Quatre variantes d'un même geste — nous joindre — lues comme quatre
   décisions séparées, parce qu'un bouton est presque aussi haut que le vide qui
   le suit.

   LA MARGE REVIENT AU CONTENEUR : 30 + 34 = 64 px, la valeur exacte que le
   chapô voyait déjà. La première rangée ne bouge donc pas d'un pixel — ni sur
   grand écran, ni ailleurs — et l'écart entre boutons empilés redevient le
   `gap` seul, 14 px. Rien d'inventé : les deux nombres étaient déjà là.
   Après : bloc de 258 px (4 × 54 + 3 × 14), soit 136 px rendus à l'écran.

   Les 14 px restants sont un choix, pas un reste : la cible garde ses 54 px de
   haut (WCAG 2.2 SC 2.5.8 en demande 24, SC 2.5.5 en demande 44) et deux
   boutons ne se touchent jamais. */
.sb-hero__actions{
  display:flex;gap:14px;flex-wrap:wrap;justify-content:center;margin-top:64px;
}
.sb-hero__actions .sb-btn{margin-top:0}
.sb-hero__actions:not(:has(a[href]:not([href="#"]))){display:none}

/* Le second appel : contour, pas de fond — il ne doit pas concurrencer le premier. */
.sb-btn--fantome{
  background:transparent;color:var(--sb-ink-fg);
  border:1px solid color-mix(in srgb,var(--sb-paper-bg) 22%,transparent);box-shadow:none;
}
.sb-btn--fantome:hover{
  background:transparent;border-color:var(--yhb-accent);color:var(--sb-ink-fg);
  transform:translateY(-2px);box-shadow:none;
}
.sb-paper .sb-btn--fantome{color:var(--sb-paper-fg);border-color:color-mix(in srgb,var(--sb-paper-fg) 20%,transparent)}
.sb-paper .sb-btn--fantome:hover{border-color:var(--sb-accent-text);color:var(--sb-accent-text)}
.sb-btn--fantome:empty,.sb-btn:empty,a.sb-btn[href="#"]:empty{display:none}

/* Un champ « html » passe par wpautop, qui enveloppe tout en <p>. Sur un titre,
   cela donne <h1><p>…</p></h1> : le <p> apporte ses marges et sa taille, et le
   titre se retrouve décalé et rapetissé. On le rend transparent plutôt que de
   toucher au compilateur. Vaut pour tout titre portant un champ html. */
.sb-hero h1 > p,.sb-hero__title > p,
main > section > .sb-wrap > h2 > p{
  margin:0;padding:0;font:inherit;color:inherit;letter-spacing:inherit;
  line-height:inherit;display:block;
}
/* Surtitre absent : ni marge, ni place. `:empty` ne suffit pas quand le champ
   rend une espace ou un <p> vide. */
.sb-hero .sb-eyebrow:empty,
.sb-hero .sb-eyebrow:not(:has(*)):blank{display:none}
.sb-hero .sb-eyebrow > p:empty{display:none}

/* ═══ titre en deux temps ════════════════════════════════════════════════════
   La seconde partie passe à la ligne et porte le dégradé — c'est la signature
   du prototype. En bloc plutôt qu'avec un <br> saisi : le retour appartient à
   la mise en forme, pas au contenu, et il disparaît proprement sur mobile. */
.sb-hero__title .sb-accent{
  display:block;
  background:var(--yhb-grad);
  -webkit-background-clip:text;background-clip:text;color:transparent;

  /* ⚠️ LES DESCENDANTES ÉTAIENT COUPÉES — g, j, p, q, y rognés par le bas.
     `background-clip:text` ne découpe pas le DESSIN de la lettre : il découpe
     la boîte de ligne, et une `line-height` serrée la referme sous la ligne de
     base. Le dégradé s'arrête donc au milieu du jambage.
     On rend la hauteur au bloc sans écarter les lignes : un rembourrement bas,
     compensé par une marge négative de même valeur. */
  padding-bottom:.14em;margin-bottom:-.14em;}
.sb-hero__title .sb-accent:empty{display:none}

/* Le contraste de la ligne de réassurance sur la bande d'appel.
   La synthèse a fait passer cette bande au crème sans que la couleur du chapô
   suive : texte ivoire sur crème, 1,2:1, refusé par axe-core à juste titre. */
.sb-cta > .sb-wrap > p,.sb-cta.sb-band > .sb-wrap > p{color:var(--yhb-text-soft)}
.sb-cta.sb-ink > .sb-wrap > p{color:var(--yhb-text-soft-dark)}

/* ═══ portes ═══════════════════════════════════════════════════════════════
   L'HABILLAGE DES DEUX CARTES A ÉTÉ RETIRÉ le 20.08.2026. Le pattern ne produit
   plus `.sb-portes__grille` ni `.sb-porte` : il rend une bascule et une grille
   de vingt-sept cartes, habillée par `lib/selecteur.php` (`.sb-portes__*`).
   Ces règles ne s'appliquaient donc plus à rien — et du CSS qui décrit un
   markup disparu est un piège pour qui lira cette feuille ensuite. */
/* ═══ marques — un défilement horizontal, pas une grille ════════════════════
   Le prototype fait défiler un ruban. Le markup généré est une <ul> en grille :
   on la remet en ligne, avec accroche au défilement et barre masquée. Le
   glissement est réel — molette, doigt, clavier. Un défilement AUTOMATIQUE et
   sans couture demanderait de dupliquer la liste, ce que seul du script ou une
   déclaration de comportement peut faire : voir §14 du RETEX. */
.sb-marques .sb-grid{
  display:flex;flex-wrap:nowrap;gap:14px;
  overflow-x:auto;scroll-snap-type:x proximity;
  scrollbar-width:none;-ms-overflow-style:none;
  padding-bottom:6px;
  /* Pas de plein-cadre : `100vw` inclut la barre de défilement, d'où 8 px de
     débordement horizontal mesurés. Le ruban reste dans la colonne. */
}
.sb-marques .sb-grid::-webkit-scrollbar{display:none}
.sb-marques .sb-card{
  flex:0 0 auto;width:190px;min-height:96px;scroll-snap-align:center;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
}
/* Le nom sous le logo reste lisible mais s'efface : le logo dit déjà la marque. */
.sb-marques .sb-card h3{font-size:13px;margin:8px 0 0;color:var(--yhb-text-soft)}
.sb-marques .sb-card p{display:none}

/* ── Les cinq teintes de famille des NUAGES ──────────────────────────────────
   La section « marques » a deux autres formes, choisies par le modificateur de
   la page — `nuage-carte` et `nuage-bulles`, rendues par lib/constellation.php.
   Leur module donne à chaque famille l'accent de la marque TOURNÉ EN TEINTE d'un
   cran régulier : c'est un défaut qui marche pour n'importe quelle marque, et
   c'est tout ce qu'un fichier partagé a le droit de décider.

   YHB, elle, a ses teintes, et elles ne sont pas choisies à côté du dégradé :
   elles sont PRISES DESSUS. Deux arrêts purs (#E6008A, #A02597), l'accent du
   thème nuit (#FF2EA6), et les deux violets du départ éclaircis vers le papier —
   posés tels quels sur l'encre, #3A0E63 et #5B1A8C donnent 1,2:1 et 1,9:1, soit
   indiscernables du fond ; éclaircis de 34 % et 22 %, ils se voient.

   ⚠️ CES TEINTES NE PORTENT JAMAIS DE TEXTE, et c'est ce qui autorise des
   contrastes aussi bas : elles ne servent qu'à des pastilles, des bordures et
   des filets. Le texte des deux nuages reste sur la paire de tonalité de la
   section. La seule exception est la pastille de compte, qui pose le fond ET
   son texte ensemble (`--sb-bg` sur `--sb-nuage-f`) — à surveiller si l'une de
   ces cinq valeurs est un jour éclaircie davantage.

   Les identifiants sont ceux de la clé `groupes` de proto/assets/carte.json. Une
   famille renommée là-bas rend simplement sa ligne muette ici, et la teinte
   dérivée reprend la main : rien ne casse, la carte perd juste sa couleur. */
.sb-marques--nuage-carte,
.sb-marques--nuage-bulles{
  --sb-nuage-f-analyser:#E6008A;
  --sb-nuage-f-realisation:#A02597;
  --sb-nuage-f-communaute:#7D49A1;   /* #5B1A8C éclairci de 22 % vers le papier */
  --sb-nuage-f-marques:#FF2EA6;
  --sb-nuage-f-medias:#795B91;       /* #3A0E63 éclairci de 34 % vers le papier */
}

/* ── L'amas de bulles bascule la section en CLAIR, et il faut le dire ICI ────
   🔴 SANS CETTE RÈGLE, LA FORME « bulles » EST ILLISIBLE, et la cause n'est pas
   dans le module : c'est un conflit d'ORDRE, pas de logique.
   `sb_constellation_css()` pose bien la paire de tonalité sur
   `.sb-marques.sb-marques--nuage-bulles`, soit (0,2,0) — mais la règle §13
   ci-dessus, `.sb-marques.sb-ink`, vaut EXACTEMENT (0,2,0) elle aussi, et cette
   feuille est concaténée APRÈS le module. À spécificité égale, la dernière
   gagne : le fond prune revenait, pendant que le texte de la section, lui,
   passait bien en encre sombre. Fond sombre, texte sombre — la huitième rupture
   de paire, et elle serait arrivée par la porte de derrière.
   Trois classes ici, donc (0,3,0), et la question ne se pose plus quel que soit
   l'ordre de concaténation.

   Le fond ET le texte, ensemble, en jetons : la section reste juste si la marque
   repeint ses tonalités, ce qu'un crème littéral ne permettrait pas.
   Le filet violet du haut part avec le fond sombre — posé sur du crème, il
   dessinait une barre qui n'appartient à rien.

   ⚠️ LA FORME « carte », ELLE, GARDE LE FOND PRUNE, et ce n'est pas un oubli :
   ses étiquettes portent des NOMS, pas des logos. Elle n'a aucune raison de
   changer de tonalité, et le fond radial de §13 lui sert de cosmos. */
/* ── LA PLAQUE DE LA SPHÈRE EN PRÉSENTATION SOMBRE ──────────────────────────
   Le module pose un blanc neutre : il ne connaît pas la crème de chaque marque.
   YHB dit la sienne, celle de ses jetons « papier » en présentation claire. Sans
   ces deux lignes les disques seraient blancs purs au lieu d'ivoire — lisible,
   mais pas la marque. */
[data-sb-presentation="sombre"] .sb-nuage--bulles{
  --sb-nuage-plaque-bg-sombre:#F4F1EA;
  --sb-nuage-plaque-fg-sombre:#0B0B0E;
}

/* ⚠️ RETIRÉE LE 19.08.2026 — la section reste SOMBRE, seuls les disques sont
   clairs. Cette règle existait pour rendre les logos lisibles ; ils le sont
   désormais sur leur propre plaque, qui porte les jetons « papier ». La
   rétablir ramènerait le fond clair et annulerait la demande. */

/* ═══ noyau — les cartes ADN ════════════════════════════════════════════════
   Les icônes SVG ont été supprimées par data-sb-ignore : leur conteneur restait,
   vide, et s'affichait en pastille rose. On le retire plutôt que de le remplir
   d'un pictogramme qui n'existe plus. */
.dna-ic:empty{display:none}
.dna-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(300px,100%),1fr));
  gap:18px;margin-top:34px;text-align:left}
.dna-card{background:var(--yhb-surface);border:1px solid var(--yhb-border);
  border-radius:18px;padding:26px}
.dna-top{display:flex;align-items:baseline;gap:10px;margin-bottom:14px}
.dna-top .num{font-family:var(--yhb-mono);
  font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--sb-accent-text)}
.dna-top .dna-idx{margin-left:auto;font-family:var(--yhb-mono);
  font-size:11px;color:var(--yhb-muted)}
.dna-card h3{font-size:19px;margin:0 0 8px}
.dna-card > p{font-size:15px;line-height:1.6;color:var(--yhb-text-soft);margin:0}
.dna-values{grid-column:1/-1}
.pillars{list-style:none;margin:14px 0 0;padding:0;
  display:grid;grid-template-columns:repeat(auto-fit,minmax(min(240px,100%),1fr));gap:12px}
.pillars li{display:flex;gap:10px;align-items:flex-start;
  background:color-mix(in srgb,var(--yhb-accent) 4%,transparent);border-radius:12px;padding:14px 16px}
.pillar-mark:empty{display:none}
.pillar-name{display:block;font-weight:600;font-size:15px;margin-bottom:2px}
.pillar-desc{display:block;font-size:13.5px;line-height:1.5;color:var(--yhb-text-soft)}

/* La zone défilante prend le focus : il doit se voir. */
.sb-marques .sb-grid:focus-visible{outline:2px solid var(--yhb-accent);outline-offset:4px;border-radius:14px}
/* Cible tactile : 22 px de haut, sous le plancher de 24. Le lien porte déjà
   toute la carte via ::after, mais le contrôle mesure la boîte du lien
   lui-même. On lui donne sa hauteur, sans changer le rendu. */
.sb-porte__l{min-height:24px;line-height:24px}

/* ═══ lieu — venir sur place ════════════════════════════════════════════════
   Une carte large pour le plan et l'adresse, puis deux cartes de même hauteur.
   Les valeurs viennent de .acc-card du prototype. */
.sb-lieu > .sb-wrap{display:flex;flex-direction:column;gap:18px}
.sb-lieu__principal,.sb-lieu__carte{
  background:color-mix(in srgb,var(--sb-ink-fg) 3%,transparent);border:1px solid var(--yhb-border-dark);
  border-radius:18px;padding:26px 28px;text-align:left;
}
.sb-paper .sb-lieu__principal,.sb-paper .sb-lieu__carte{
  background:var(--yhb-surface);border-color:var(--yhb-border);
}
.sb-lieu .sb-eyebrow{
  font-family:var(--yhb-mono);
  font-size:11px;letter-spacing:.18em;text-transform:uppercase;
  /* Surtitre en 11 px : du texte, donc `--sb-accent-text` et non le magenta
     pur, MESURÉ à 4,22:1 sur la plaque — sous AA. Voir `.dna-top .num`. */
  color:var(--sb-accent-text);margin:0 0 12px;text-align:left;
}
.sb-lieu h2{font-size:clamp(20px,2.1vw,26px);letter-spacing:-.02em;
  margin:0 0 18px;text-align:left;max-width:none}
.sb-lieu h3{font-size:19px;margin:0 0 14px}

.sb-lieu__plan{
  width:100%;height:auto;max-height:280px;object-fit:cover;
  border-radius:14px;display:block;margin:0 0 18px;
}
.sb-lieu__plan:not([src]),.sb-lieu__plan[src=""]{display:none}

/* ── LE PIED DU PLAN — crédit de source ────────────────────────────────────
   L'extension de carte porte bien un habillage, mais posé par un `wp_head`
   conditionné à `has_shortcode( post_content )` : le code court vit dans un
   champ ACF, jamais dans le contenu du billet, donc la règle ne part JAMAIS.
   Vérifié sur le HTML servi — une seule occurrence de `sb-carte__pied`, celle
   du markup, aucune dans le <head>. Sans elle, « © swisstopo » sort en 16 px
   pleine encre, du poids d'une ligne de texte courant, juste sous le plan.
   Le correctif est ici et non dans l'extension : le module ne connaît pas la
   marque, et il ne doit pas — c'est ce que dit son propre commentaire.
   La couleur se déclare par sa PAIRE : le plan tombe sur le crème aujourd'hui,
   il retomberait sur l'encre le jour où la section change de tonalité. */
.sb-carte__pied{
  display:flex;flex-wrap:wrap;gap:.5rem 1rem;align-items:baseline;
  justify-content:space-between;margin-top:10px;font-size:12px;
}
.sb-carte__pied p{margin:0}
.sb-paper .sb-carte__pied{color:var(--yhb-text-soft)}
.sb-ink   .sb-carte__pied{color:var(--yhb-text-soft-dark)}

.sb-lieu__adresse{font-size:15.5px;line-height:1.6;margin:0 0 18px}
.sb-lieu__adresse strong{font-weight:600}

.sb-lieu__paire{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(300px,100%),1fr));gap:18px}

.sb-lieu__liste{list-style:none;margin:0;padding:0;display:flex;
  flex-direction:column;gap:12px}
.sb-lieu__liste li{display:flex;gap:12px;align-items:baseline}
/* L'étiquette en chasse fixe, encadrée : c'est la signature du prototype pour
   « Bus 7 », « Métro M1 », « Gare CFF ». */
.sb-lieu__etiquette{
  flex:0 0 auto;min-width:82px;text-align:center;
  font-family:var(--yhb-mono);font-size:12px;
  padding:5px 10px;border-radius:99px;
  border:1px solid var(--yhb-border-dark);color:var(--yhb-text-soft-dark);
}
.sb-paper .sb-lieu__etiquette{border-color:var(--yhb-border);color:var(--yhb-text-soft)}
.sb-lieu__detail{font-size:14.5px;line-height:1.55;color:var(--yhb-text-soft-dark)}
.sb-paper .sb-lieu__detail{color:var(--yhb-text-soft)}
.sb-lieu__texte p{font-size:14.5px;line-height:1.6;color:var(--yhb-text-soft-dark);margin:0 0 10px}
.sb-paper .sb-lieu__texte p{color:var(--yhb-text-soft)}
.sb-lieu__texte p:last-child{margin-bottom:0}

/* E2 — hauteur FERME, pas plancher : le prototype pose .nav{height:68px}. */
.sb-header .sb-wrap{height:68px;min-height:0;gap:26px}

/* ═══ consentement — un bandeau, pas un mur ═════════════════════════════════
   Le module se présentait en panneau plein écran : à l'arrivée, le contenu
   était inatteignable derrière. Un mur de consentement fait chuter la lecture
   et n'est pas exigé. Le prototype pose un bandeau bas, compact, qui laisse
   la page lisible pendant qu'on décide.

   🔴 CE QUI NE DOIT PAS BOUGER : « Tout refuser » garde exactement le même
   poids visuel que « Tout accepter » — même taille, même couleur, même rang.
   Refuser doit être aussi facile qu'accepter. Ce n'est pas une question de
   goût, et un habillage qui rendrait le refus plus discret serait fautif. */
.sbwp-cnst{
  position:fixed;inset:auto 0 0 0;z-index:9000;
  /* ⚠️ CE BANDEAU EST SOMBRE DANS LES TROIS TONALITÉS, ET PAS DE NOTRE FAIT.
     `lib/consentement.php` émet sa propre feuille EN LIGNE, dans le <head>,
     donc APRÈS le <link> du thème : à spécificité égale c'est elle qui gagne,
     et elle y fige `background:#14141A` et `color:#FAF8F3`. Vérifié sur le HTML
     servi, pas supposé.
     Conséquence : tout ce qui vit dans ce bandeau doit rester dans la famille
     ENCRE, quelle que soit la présentation. Le gris sourd est donc REDONNÉ ici
     en valeur d'encre — sans quoi, la dérivation posée en tête de feuille le
     ferait virer au gris foncé (#414041 MESURÉ) sur un fond resté noir en
     présentation claire, soit 1,6:1 : illisible.
     À RETIRER le jour où `lib/consentement.php` cessera de figer sa palette et
     laissera le bandeau suivre la tonalité — c'est le vrai correctif, il est
     hors de cette feuille. */
  --yhb-text-soft-dark:color-mix(in srgb,var(--sb-accent-fg) 77%,#14141A);
  /* `#0A0910`, c'est `--yhb-ink` — l'encre profonde du prototype, déjà nommée
     en tête de cette feuille. */
  background:color-mix(in srgb,var(--yhb-ink) 94%,transparent);
  backdrop-filter:saturate(140%) blur(16px);
  -webkit-backdrop-filter:saturate(140%) blur(16px);
  border-top:1px solid color-mix(in srgb,var(--yhb-accent) 35%,transparent);
  box-shadow:0 -18px 50px -30px rgba(0,0,0,.9);
  max-height:min(62vh,560px);overflow-y:auto;
}
.sbwp-cnst-boite{
  max-width:var(--yhb-maxw);margin:0 auto;padding:20px 28px 22px;
  display:grid;gap:12px;
}
.sbwp-cnst-titre{font-size:17px;font-weight:600;letter-spacing:-.015em;margin:0}
.sbwp-cnst-texte{font-size:13.5px;line-height:1.55;color:var(--yhb-text-soft-dark);
  margin:0;max-width:88ch}
/* lib/consentement.php pose 16 px sur le bandeau, et dit pourquoi : sous 16,
   Safari iOS zoome de force la page à la prise de focus, le bandeau saute à
   l'écran et le visiteur perd sa place. Cette feuille de marque ramenait le
   texte à 13,5 px et défaisait donc la décision sans le savoir.
   Le 13,5 px reste — c'est un choix de composition qui tient au bureau — mais
   il ne s'applique plus là où il fait du tort. */
@media (pointer:coarse){ .sbwp-cnst-texte{font-size:1rem} }
.sbwp-cnst-legende{font-size:12.5px;letter-spacing:.14em;text-transform:uppercase;
  font-family:var(--yhb-mono);margin:2px 0 0;
  /* #E6008A sur #0A0910 donne 3,63:1 — sous le seuil AA de 4,5. Un accent de
     marque est presque toujours trop sombre pour du petit texte sur fond
     sombre : c'est le pendant de `accent_text` côté clair.
     Ce raisonnement était juste ; sa conclusion, `#FF9BD4`, était une couleur
     inventée pour l'occasion. La marque avait déjà la sienne pour ce cas
     exact — c'est désormais `--sb-accent-encre`.
     Mesuré sur le fond du bandeau (#0A0910 à 94 %) : #FF9BD4 donnait 9,51:1,
     le jeton donne 5,85:1. On perd de la marge, pas la conformité — 5,85 pour
     du 12,5 px là où AA en demande 4,5 — et on gagne que le bandeau de
     consentement parle enfin la même langue que le reste du site. */
  color:var(--sb-accent-encre)}
/* Les catégories en ligne : elles restent visibles — le visiteur choisit
   catégorie par catégorie — mais elles n'occupent plus tout l'écran. */
.sbwp-cnst-liste{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(min(220px,100%),1fr));
  gap:10px;margin:0;padding:0;list-style:none;
}
.sbwp-cnst-choix{
  border:1px solid var(--yhb-border-dark);border-radius:12px;padding:12px 14px;
  background:color-mix(in srgb,var(--sb-ink-fg) 3%,transparent);
}
.sbwp-cnst-choix-titre{font-size:13.5px;font-weight:600;display:block}
.sbwp-cnst-choix-texte{font-size:12.5px;line-height:1.45;color:var(--yhb-text-soft-dark)}
.sbwp-cnst-actions{display:flex;flex-wrap:wrap;gap:10px;align-items:center}
.sbwp-cnst-btn{
  padding:11px 22px;border-radius:999px;font-size:14px;font-weight:600;
  border:1px solid transparent;cursor:pointer;
  background:var(--sb-accent-fond);
  color:var(--sb-ink-fg);
}
/* « Enregistrer mes choix » est l'action secondaire — pas le refus. */
.sbwp-cnst-btn--discret{
  background:transparent;color:var(--sb-ink-fg);
  border-color:var(--yhb-border-dark);
}
.sbwp-cnst-btn:focus-visible{outline:2px solid var(--sb-ink-fg);outline-offset:3px}
@media (max-width:640px){
  .sbwp-cnst-boite{padding:16px 20px 18px}
  .sbwp-cnst-actions .sbwp-cnst-btn{flex:1 1 auto;text-align:center}
}

/* ═══ 27. l'accent sur fond ENCRE — une seule teinte, plus quatre ═══════════
   ── LE DÉFAUT, TEL QU'IL A ÉTÉ MESURÉ ────────────────────────────────────
   Trois patterns ont, chacun de son côté, rencontré le même problème : en
   tonalité encre, `patterns.css` remappe `--sb-accent-text` sur `--sb-accent`
   (patterns.css:73), et l'accent d'YHB tombe alors à 2,8:1 sur le fond sombre —
   sous AA, sous même les 3:1 d'un composant. Chacun a inventé son remède, et
   tous ont pris le même chemin : éclaircir l'accent VERS L'IVOIRE par un
   `color-mix` en oklab. Relevé au navigateur, sur le site servi :

     témoignages   color-mix(--sb-accent 72%, --sb-ink-fg)  →  #BD68B2
     sélecteur     color-mix(--sb-accent 65%, --sb-ink-fg)  →  #C376B8
     chiffres      color-mix(--sb-accent 75%, --sb-ink-fg)  →  #BA62AF

   Trois roses grisés, tous différents, aucun de la marque. Le procédé est
   fautif à la racine : `--sb-accent` est le MILIEU d'un dégradé violet-magenta
   (tokens.css le dit), et l'éclaircir vers un ivoire chaud le désature vers le
   mauve au lieu de le rapprocher du magenta. Plus on monte en luminosité, plus
   on s'éloigne de la teinte — c'est exactement le « magenta délavé » relevé à
   l'œil sur les témoignages, et l'onglet « qui tire sur le violet » du
   sélecteur.

   ── LE REMÈDE ────────────────────────────────────────────────────────────
   La marque A une couleur pour ce cas : celle que le prototype pose lui-même
   en thème sombre, `#FF2EA6`, désormais `--sb-accent-encre` dans tokens.css.
   Rien n'est inventé ici : on remplace trois calculs par la valeur relevée.

   ── POURQUOI ICI, ET PAS DANS LES PATTERNS ───────────────────────────────
   Les trois jetons sont des PROPRIÉTÉS PERSONNALISÉES, et leurs auteurs les ont
   posées pour ça — `temoignages.html` écrit noir sur blanc qu'« une marque peut
   donc la changer depuis sa feuille, sans toucher au pattern ». C'est le point
   d'extension prévu ; on l'emprunte au lieu d'aller éditer des fichiers de la
   chaîne, qui valent pour toutes les marques.

   ⚠️ `:root` EN TÊTE DE SÉLECTEUR, ET CE N'EST PAS DÉCORATIF. Le <style> de
      `temoignages.html` est sérialisé DANS <main>, donc APRÈS le <link> du
      thème : à spécificité égale (0,2,0) c'est lui qui gagne, et l'écrasement
      serait resté sans effet. `:root .sb-temoignages.sb-ink` pèse (0,3,0) et
      passe devant quel que soit l'ordre. Vérifié au navigateur, pas déduit.
      Le sélecteur, lui, est concaténé AVANT cette feuille (l. 946 de la
      feuille servie contre 1500 ici) : la spécificité simple suffirait, mais on
      la garde alignée pour que les trois règles se lisent ensemble.

   Contrastes MESURÉS après remplacement, sur le fond réel de chaque emploi :
     sur la plaque des cartes témoignage (#1F1F24)   4,84:1   AA
     sur le fond de section encre     (#14141A)      5,41:1   AA
     sur le fond de page              (#0A0910)      5,85:1   AA
   Les valeurs remplacées donnaient 5,06 / 5,77 / 4,76 : on reste dans le même
   ordre de grandeur, au-dessus d'AA partout, et la teinte redevient juste. */
/* ⚠️ `.sb-ink` NE VEUT PAS DIRE « SOMBRE » DANS LES TROIS PRÉSENTATIONS.
   En présentation « clair », `lib/entete.php` remappe `--sb-ink-bg` sur le
   crème : une section `.sb-ink` y devient CLAIRE tout en gardant sa classe.
   Poser l'accent d'encre sans condition y donnait #FF2EA6 sur une plaque
   crème — 2,80:1 MESURÉ, sous AA. C'est le défaut qu'on vient de corriger
   ailleurs, retrouvé ici par la même mesure.
   On nomme donc « l'accent de texte de cette section », qui suit la
   présentation, et les trois patterns le consomment sans avoir à savoir. */
.sb-ink{--yhb-accent-section:var(--sb-accent-encre)}
:root[data-sb-presentation="clair"] .sb-ink{--yhb-accent-section:var(--sb-accent-text)}

/* ── L'INDICATEUR DE FOCUS PREND LE MÊME CHEMIN ─────────────────────────────
   `patterns/patterns.css:70` pose `:focus-visible{outline:3px solid
   var(--sb-accent)}`. C'est juste sur le crème et faux sur l'encre, pour la
   raison que tout ce §27 décrit : l'accent d'YHB est le MILIEU d'un dégradé
   violet-magenta, trop sombre pour se détacher d'un fond sombre.
   MESURÉ au navigateur, en tabulant pour de vrai sur les neuf pages du site :

     #A02597 sur le fond de section encre  (#14141A)   2,80:1
     #A02597 sur la plaque du profil       (#141319)   2,82:1
     #A02597 sur le fond de page du pied   (#0A0910)   3,03:1

   WCAG 1.4.11 demande 3:1 d'un indicateur de focus — la première ligne échoue,
   la troisième passe d'un cheveu. Dix-neuf combinaisons relevées sous le seuil.
   Avec `--yhb-accent-section` : 5,41 / 5,45 / 5,85. Trois lignes plus haut, la
   même valeur corrigeait déjà le même défaut pour les témoignages, le sélecteur
   et les compteurs ; l'indicateur de focus est le quatrième cas, pas un cas à
   part. On ne redit donc PAS `--sb-accent-encre` ici : on emprunte le jeton qui
   sait déjà suivre la présentation, sans quoi le mode jour rendrait #FF2EA6 sur
   une plaque crème — 3,01:1, le seuil frôlé par accident.

   ⚠️ LA CONTREPARTIE EST ICI ET NON DANS `patterns/patterns.css`, où vit la
   règle d'origine : cette feuille est concaténée APRÈS le catalogue (voir
   l'en-tête du fichier), donc `outline-color` seul, à spécificité (0,1,0)
   égale, repasse devant. Le reste du raccourci — `3px solid`, `outline-offset`
   — n'est pas retouché : il est déjà juste, et le redire l'exposerait à
   diverger.

   ⚠️ LE BANDEAU ET LE PIED sont de la famille ENCRE sans porter `.sb-ink` —
   `patterns.css` leur donne `background:var(--sb-ink-bg)`, et cette feuille
   pose le pied sur `--yhb-ink`. Ils n'héritaient donc de rien et gardaient
   l'accent plat : 2,80:1 sur le bandeau, 3,03:1 sur le pied, MESURÉS. Ils
   prennent le jeton par la même porte, et le remappage du mode jour avec. */
.sb-header,.sb-footer{--yhb-accent-section:var(--sb-accent-encre)}
:root[data-sb-presentation="clair"] :is(.sb-header,.sb-footer){
  --yhb-accent-section:var(--sb-accent-text)}
/* En présentation « sombre », `lib/entete.php` remappe `--sb-paper-bg` sur
   l'encre : TOUTE section devient sombre, y compris celles qui ne portent pas
   `.sb-ink` et n'ont donc aucune règle ci-dessus. Le jeton est posé sur la
   racine pour les couvrir toutes ; `.sb-ink`, qui déclare le sien, garde la
   main sur son propre sous-arbre. */
:root[data-sb-presentation="sombre"]{--yhb-accent-section:var(--sb-accent-encre)}

/* Le repli `var(…, var(--sb-accent))` n'est pas une précaution de style : hors
   d'une section qui déclare le jeton — une page nue, un écran d'administration
   — la propriété n'est pas définie, et sans repli `outline-color` retomberait
   sur `currentColor`, c'est-à-dire la couleur du TEXTE. L'indicateur prendrait
   alors la teinte de ce qu'il entoure : le pire des cas. */
:focus-visible{outline-color:var(--yhb-accent-section,var(--sb-accent))}

:root .sb-temoignages.sb-ink{--sb-temoins-accent:var(--yhb-accent-section)}
:root .sb-selecteur.sb-ink{--sb-selecteur-accent:var(--yhb-accent-section)}
:root .sb-chiffres.sb-ink{
  /* Les deux bornes du dégradé des gros chiffres. Elles doivent rester
     DISTINCTES — c'est ce qui fait lire la transition — mais toutes deux dans
     la famille magenta : on garde le violet du début, éclairci vers le magenta
     d'encre plutôt que vers l'ivoire, et le magenta d'encre pur en fin. */
  --sb-chiffres-debut:color-mix(in oklab,var(--sb-accent) 55%,var(--yhb-accent-section));
  --sb-chiffres-fin:var(--yhb-accent-section);
  --sb-chiffres-surtitre:var(--yhb-accent-section);
}

/* ── 27. carte de visite ────────────────────────────────────────────────
   Le fond est l'encre PROFONDE (#0A0910) et non l'encre commune : c'est
   celle de la page de référence.
   ⚠️ Le dégradé du monogramme est PORTÉ par la section, jamais posé sur
   `:root` — chiffres.html a mesuré que ce départ presque noir éteint les
   compteurs sur une section encre. */
/* ⚠️ LES DEUX, et c'est là qu'était le défaut. Ne remapper que `--sb-bg` laissait
   `--sb-fg` à l'encre du mode clair (#0B0B0E) : la plaque des lignes, calculée en
   `color-mix(--sb-fg 4%, transparent)`, sortait NOIRE à 4 % sur un fond noir —
   donc invisible. Mesuré `srgb 0.043 0.043 0.055 / 0.04` là où la référence sert
   `rgba(255,255,255,.04)`. Une tonalité se déclare par sa paire, jamais à moitié. */
.sb-profil.sb-ink{--sb-bg:var(--yhb-ink);--sb-fg:var(--sb-ink-fg)}

/* La page carte de visite n'est pas une page du site : elle se suffit. La
   référence https://yanhaltey.com/yan ne sert ni en-tête ni pied — seul le
   bouton de tonalité subsiste, et il est déjà posé par `sb_entete`. */
body.page-yan :is(.sb-header,.sb-footer){display:none}
body.page-yan{background:var(--yhb-ink)}
.sb-profil{--sb-degrade:linear-gradient(112deg,#3A0E63 0%,#5B1A8C 28%,#E6008A 100%)}


/* ── LA SECTION DU FORMULAIRE RESPIRE ───────────────────────────────────────
   Le chapô touchait le premier libellé du formulaire, et le formulaire touchait
   le bord bas de la section : trois blocs collés, sans hiérarchie visible entre
   « ce qu'on vous dit » et « ce qu'on vous demande ».
   L'espace est ce qui les sépare — pas un filet, pas un encadré. */
/* ⚠️ `main > section` DANS LE SÉLECTEUR, ET CE N'EST PAS DU REMPLISSAGE.
   Le catalogue pose `main > section > .sb-wrap > :is(.sb-lead,p):not(…)` avec
   `margin: 26px auto 0px` — un RACCOURCI, qui remet donc le bas à zéro. Il pèse
   (0,3,2) ; une règle en trois classes ne pèse que (0,3,0) et perdait en
   silence. On repasse devant par la structure, exactement comme la feuille le
   fait déjà pour `.sb-hero__lead` quelques centaines de lignes plus haut. */
main > section.sb-formulaire > .sb-wrap > .sb-lead{margin-bottom:clamp(2rem,4vw,3rem)}
/* Le formulaire porte déjà 38 px sous lui — mesuré — mais rien ne le garantit :
   c'est une marge d'extension, qui bougera à la prochaine mise à jour. */
.sb-formulaire .frm-fluent-form{margin-bottom:clamp(1.5rem,3vw,2.5rem)}


/* ── CSS levé des patterns — voir Compiler::renderTemplate() ── */

/* patterns/communaute.html */
/* ══ Les jetons locaux, dérivés et jamais inventés ═══════════════════════
     Même construction que profil.html : le pattern SUIT la marque au lieu de la
     doubler, et s'adapte seul à une section claire.

     Le gris de la note : la référence sert #C8C4BB, soit `--text-soft`. L'écrire
     tel quel figerait une couleur d'YHB dans le catalogue partagé. On le DÉRIVE
     à 77 % de la tonalité courante — la formule que brands/yhb/style.css emploie
     déjà pour `--yhb-text-soft-dark`. Mesuré : la dérivation donne rgb(195,193,191)
     sur le sombre #0A0910 contre rgb(200,196,187) relevé, et le chapô de la
     section servie sort DÉJÀ à cette dérivation — l'écart est celui que la chaîne
     accepte partout ailleurs, pas un écart de plus. Contraste 6,1:1 sur l'encre :
     les 4,5:1 de WCAG AA sont tenus pour du 11,5 px. */
  .sb-communaute{
    --sb-communaute-doux:color-mix(in srgb,var(--sb-fg) 77%,var(--sb-bg));
    --sb-communaute-duree:var(--sb-motion,.2s);
    --sb-communaute-courbe:var(--sb-ease,cubic-bezier(.22,.61,.36,1));
  }
  /* L'accent du survol, et il DÉPEND DE LA TONALITÉ.
     patterns.css:73 réécrit `--sb-accent-text` vers `--sb-accent` dans `.sb-ink`,
     et l'accent plat d'YHB (#A02597) tombe à 2,8:1 sur le sombre — sous les 3:1
     que WCAG demande à un élément d'interface. `--sb-accent-encre` est justement
     le jeton que la marque a nommé pour ce cas, et la référence mesure #FF2EA6,
     qui EST sa valeur.
     Il est posé sur `.sb-ink` SEULEMENT, et non en défaut : sur une section
     claire, #FF2EA6 tombe à 3,3:1 sur le papier là où `--sb-accent-text` est
     précisément le jeton lisible sur fond clair. La tonalité alterne selon la
     position de la section — ce pattern ne la fige pas —, donc le jeton ne peut
     pas être choisi une fois pour toutes. */
  .sb-communaute{--sb-communaute-accent:var(--sb-accent-text,var(--sb-accent))}
  .sb-communaute.sb-ink{--sb-communaute-accent:var(--sb-accent-encre,var(--sb-accent-text,var(--sb-accent)))}

  /* ══ L'icône ═════════════════════════════════════════════════════════════
     22 × 22, relevé. Aucune couleur : le <svg> peint en `currentColor` et hérite
     donc de la couleur de la pastille — c'est ce qui fait passer l'icône à
     l'accent au survol sans qu'une seconde règle ait à le dire.
     `flex:none` : sans lui l'icône se comprime dans la pastille dès que le
     libellé du lien est long, et les trois dessins n'alignent plus.

     ⚠️ `flex:none` NE CHANGE PAS QUAND LE PORTEUR PASSE DE `span` À `div`, et
     ce n'était pas évident. Un élément en ligne placé dans un conteneur flex
     est BLOQUIFIÉ par la mise en page flex elle-même : `display:inline` n'a
     jamais atteint cet élément. MESURÉ au navigateur, avant et après le
     changement de balise, à 390 px et à 1280 px, sur les trois pastilles :
     `display: block`, `flex-grow: 0`, `flex-shrink: 0`, `flex-basis: auto`,
     `width: 22px`, `height: 22px` — identiques ; boîte 22 × 22 et décalage de
     16 / 16 px dans la pastille — identiques ; abscisses 114 / 184 / 254 en
     mobile et 559 / 629 / 699 en bureau — identiques. Zéro pixel de dérive.
     La bloquification n'opère toutefois QUE tant que `.sb-card` est flex,
     c'est-à-dire en forme `pastille` : en forme `carte`, `.sb-card` est un
     bloc ordinaire, et là un `span` aurait ignoré `width`/`height` là où le
     `div` les honore. Le `div` est donc aussi le seul des deux qui tienne dans
     les DEUX formes. */
  .sb-communaute__ic{flex:none;width:22px;height:22px}
  /* ⚠️ DES PIXELS, PAS DES POURCENTAGES — et le `<p>` neutralisé.
     Un champ `data-sb-type="html"` voit sa valeur enveloppée dans un `<p>` par
     la chaîne (même mécanisme que R62). Ce `<p>` s'intercale entre le porteur
     et le SVG, et il est en hauteur AUTOMATIQUE : `height:100%` ne se résout
     que contre un parent de hauteur définie, donc il rendait **0**. Mesuré :
     `svg boîte : 0×0` — l'icône était là, colorée, centrée, et ne dessinait
     rien. Trois pastilles vides.
     `display:contents` sort le `<p>` de l'arbre des boîtes sans le retirer du
     DOM : la chaîne peut continuer à l'écrire, il ne s'interpose plus.

     CETTE RÈGLE RESTE, ET ELLE N'EST PLUS UN AVEU. Tant que le porteur était
     un `<span>`, elle masquait un document invalide : html-validate relevait
     « <p> element is not permitted as content under <span> » TROIS fois sur
     /univers/, une par pastille. Le porteur est maintenant un `<div>`, le `<p>`
     y est du contenu licite, et les trois relevés ont disparu (9 erreurs → 6,
     les 6 restantes étrangères à ce pattern). `display:contents` ne répare donc
     plus une faute de structure : il ne règle plus que la géométrie, ce qui est
     son seul emploi légitime. */
  .sb-communaute__ic p{display:contents}
  .sb-communaute__ic svg{width:22px;height:22px;display:block}
  /* Champ non rempli : le conteneur resterait et vaudrait 22 px pour rien. Même
     correctif que `.sb-profil__ic:empty`, posé pour exactement ce symptôme —
     trois pastilles vides sur /noyau/. */
  .sb-communaute__ic:empty{display:none}

  /* ══ La note sous la rangée ══════════════════════════════════════════════
     Relevé : mono 11,5 px / 400 / .345 px d'interlettrage (soit .03em) / 18,4 px
     d'interligne (1,6) / 56ch de mesure / 18 px au-dessus / centrée.

     La mesure vaut 386,4 px sur la référence. `ch` d'IBM Plex Mono vaut .6em,
     donc 6,9 px à 11,5 px, et 386,4 / 6,9 = 56 tout rond : c'est bien 56ch, et
     la mesure suivra la police au lieu de figer un nombre de pixels.

     ⚠️ LE SÉLECTEUR EST SCOPÉ À LA SECTION, ET IL FAUT SAVOIR POURQUOI.
     brands/yhb/style.css:772 pose
        main > section > .sb-wrap > :is(.sb-lead,p):not(.sb-eyebrow,.sb-cta__mention,
                                                        .sb-profil__role,…)
     pour habiller les CHAPÔS, et cette règle attrape TOUT <p> enfant direct du
     wrap — donc cette note, qui n'en est pas un. Elle pèse (0,3,2) : `:is()` et
     `:not()` prennent chacun la spécificité de leur argument le plus fort.
     Ajouter la classe de SECTION amène ce sélecteur à (0,3,2) lui aussi ; à
     égalité, l'ordre tranche, et ce bloc est concaténé après la feuille de marque
     (lib/build.php:323).
     Ce n'est PAS une classe doublée pour gagner du poids — `.sb-communaute` est
     la section à laquelle la règle appartient, elle a sa raison d'être seule. La
     correction durable reste d'exclure `.sb-communaute__note` de la règle de
     chapô, comme il a été fait pour `.sb-eyebrow` puis pour les trois classes de
     profil : elle est écrite dans le rapport. Ce jour-là, ce sélecteur cesse
     d'être à égalité et redevient un simple scope. */
  main > section.sb-communaute > .sb-wrap > .sb-communaute__note{
    margin:18px auto 0;max-width:56ch;
    font-family:var(--sb-font-mono,ui-monospace,SFMono-Regular,Menlo,monospace);
    /* 12px : plancher absolu du canal 6. 11.5 passait inaperçu à l'œil sur un
       écran d'ordinateur et devenait illisible sur téléphone, où ce texte porte
       pourtant une mention légale. */
    font-size:12px;font-weight:400;letter-spacing:.03em;line-height:1.6;
    color:var(--sb-communaute-doux);text-align:center;
  }

  /* ══════════════════════════════════════════════════════════════════════════
     LA FORME « PASTILLE » — tout ce qui suit est conditionné à la variante
     ══════════════════════════════════════════════════════════════════════════
     Chaque règle lit `:has(.sb-communaute__pastille)`, c'est-à-dire la classe
     que la VARIANTE a posée. Aucune ne compte les éléments, aucune ne regarde un
     rang. Un copy deck qui ne déclare rien ne déclenche pas une seule de ces
     règles, et la forme `carte` sort inchangée. */

  /* ── La rangée ────────────────────────────────────────────────────────────
     Relevé : flex, 16 px de gouttière, centrée, 28 px sous le chapô.
     `.sb-grid` vaut `display:grid` + `gap:22px` + `margin-top:clamp(34px,5vh,54px)`
     (patterns.css:33 et brands/yhb/style.css:469) — soit 45 px à 1280×900, contre
     28 relevés. Le sélecteur pèse (0,3,0) contre (0,1,0) : il l'emporte par la
     spécificité, sans dépendre de l'ordre de la cascade.
     `flex-wrap` : trois pastilles de 54 px tiennent partout, mais rien ne dit
     qu'il y en aura toujours trois — une rangée qui déborde vaut mieux qu'une
     rangée qui pousse la page horizontalement. */
  .sb-communaute .sb-grid:has(.sb-communaute__pastille){
    display:flex;flex-wrap:wrap;justify-content:center;
    gap:16px;margin-top:28px;
  }

  /* ── La pastille ──────────────────────────────────────────────────────────
     Relevé : 54 × 54, rayon 50 %, fond transparent, filet d'un pixel, contenu
     centré. Le sélecteur pèse (0,3,0) et bat `.sb-ink .sb-card` (0,2,0), qui pose
     la plaque ivoire à 5 % et le rembourrage de 28 px des cartes.
     Ni `border-color` ni `color` ne sont écrits : la marque les donne déjà, et le
     relevé dit qu'elle les donne justes (voir la note du relevé plus haut).
     `flex:none` : sans lui la pastille s'étire pour remplir la rangée. */
  .sb-communaute .sb-card:has(.sb-communaute__pastille){
    position:relative;flex:none;
    width:54px;height:54px;padding:0;
    border-radius:50%;
    display:flex;align-items:center;justify-content:center;
    background:none;box-shadow:none;
    /* La référence transitionne TOUT en .2s cubic-bezier(.22,.61,.36,1) ;
       `.sb-card` ne transitionne que `transform` et `box-shadow`. Le filet et la
       couleur d'icône, qui sont ce qui change au survol, sautaient donc d'un
       coup. On nomme les trois propriétés réellement animées. */
    transition:transform var(--sb-communaute-duree) var(--sb-communaute-courbe),
               border-color var(--sb-communaute-duree) var(--sb-communaute-courbe),
               color var(--sb-communaute-duree) var(--sb-communaute-courbe);
  }

  /* Le titre et la description ne sont pas SUPPRIMÉS du gabarit, ils sont
     retirés du rendu — et c'est voulu à deux titres.

     D'abord parce que la référence ne les montre pas : la pastille ne porte
     qu'un dessin.

     Ensuite parce que B9 le demandait. `display:none` sort le <h3> de l'arbre
     d'accessibilité : les trois canaux de /univers/ cessent d'annoncer trois
     titres de section à un lecteur d'écran, qui construit sa navigation dessus.
     Le backlog mesurait « 5 h3 de filtres + 3 h3 de canaux » sur cette page pour
     une table des matières qui n'existe pas ; les trois derniers tombent ici.

     ⚠️ Le <h3> reste dans le DOM, et c'est NÉCESSAIRE :
     `brands/yhb/style.css:690` masque toute carte sans <h3>
     (`.sb-communaute .sb-card:not(:has(h3)){display:none}`) et 691 masque la
     grille entière si aucune carte n'en a. `:has()` interroge le DOM, pas le
     rendu : un <h3> en `display:none` y répond encore. Le vider dans le copy
     deck, en revanche, ferait disparaître la rangée entière — sans un mot. */
  /* ⚠️ L'EXCLUSION EST NOMMÉE, et elle n'est pas facultative.
     Sans elle, cette règle attrapait AUSSI le `<p>` que la chaîne injecte
     autour de la valeur d'un champ `data-sb-type="html"` — celui de l'icône.
     Mesuré : `P.=none` dans la chaîne d'ancêtres du SVG, qui calculait pourtant
     22×22 et ne dessinait rien. Trois pastilles vides, sans qu'aucune règle
     n'échoue ni qu'aucun sélecteur ne soit faux.
     C'est la troisième fois que ce `<p>` injecté surprend (R62 sur l'adresse de
     la carte de visite, puis la hauteur du SVG ici) : un champ `html` n'est
     jamais nu, et toute règle qui vise `p` dans un pattern doit se demander
     lequel elle attrape. */
  .sb-communaute .sb-card:has(.sb-communaute__pastille) :is(h3,p):not(.sb-communaute__ic p){display:none}

  /* ── Le lien, étiré sur toute la pastille ─────────────────────────────────
     Le lien est SŒUR de l'icône (le compilateur refuse un `link` qui contient
     d'autres champs) : pour que la pastille entière soit cliquable, c'est lui qui
     s'étend, pas elle qui délègue.

     ⚠️ SON LIBELLÉ N'EST PAS MONTRÉ, ET C'EST UNE DÉCISION, PAS UN OUBLI.
     profil.html pose en principe qu'on ne masque pas le libellé d'un lien, et il
     a raison DANS SON CAS : sa ligne montre un intitulé, le cacher le rendrait
     illisible pour tout le monde. Ici la référence ne montre aucun libellé — elle
     ne sert qu'un dessin, et donne le nom accessible par `aria-label`. Le nom
     doit donc exister sans s'afficher.
     Le procédé retenu est le retrait par indentation, et non `font-size:0` : le
     texte garde une taille, une police et une couleur, il est simplement poussé
     hors de la boîte que `overflow:hidden` referme. C'est le seul des deux qui
     laisse le libellé lisible à un lecteur d'écran ET sélectionnable par les
     outils qui lisent le texte rendu.
     `aria-label` aurait été plus proche de la référence, mais la chaîne ne sait
     pas transporter un attribut de champ — et un libellé vrai vaut mieux qu'un
     attribut : c'est le même texte que le client édite, il ne peut pas diverger.

     La boîte fait 54 × 54, très au-dessus du plancher de 24 px des cibles
     tactiles — c'est la boîte propre du <a> qui est mesurée, et ici elle couvre
     la pastille entière. */
  .sb-communaute__pastille{
    position:absolute;inset:0;border-radius:inherit;
    text-indent:100%;white-space:nowrap;overflow:hidden;
    color:inherit;text-decoration:none;
  }

  /* Le survol : filet et icône passent à l'accent, la pastille monte de 3 px.
     Le voyage vient de `.sb-card:hover` et vaut déjà -3 px, relevé identique :
     rien à réécrire. Seule la couleur manquait. Elle est posée sur la PASTILLE et
     non sur l'icône — le <svg> peint en `currentColor`, il suit. */
  .sb-communaute .sb-card:has(.sb-communaute__pastille):hover{
    border-color:var(--sb-communaute-accent);
    color:var(--sb-communaute-accent);
  }

  /* L'anneau de focus appartient à la PASTILLE, pas au lien : le lien est étiré,
     mais son libellé est indenté hors de la boîte, et `outline` se dessinerait
     autour d'un texte qui n'est nulle part. Le visiteur au clavier ne verrait
     rien. Même montage que `.sb-profil__ligne:has(… :focus-visible)`. */
  .sb-communaute .sb-card:has(.sb-communaute__pastille:focus-visible){
    outline:2px solid var(--sb-communaute-accent);outline-offset:2px;
  }

  /* Le voyage de 3 px est du confort. Qui demande le calme n'a pas à le subir ;
     le changement de filet et de couleur, lui, reste — c'est LUI qui dit « cette
     pastille est visée ». */
  @media (prefers-reduced-motion:reduce){
    .sb-communaute .sb-card:has(.sb-communaute__pastille){transition:none}
    .sb-communaute .sb-card:has(.sb-communaute__pastille):hover{transform:none}
  }

  /* En contraste forcé, une icône en `currentColor` et un filet dérivé de la
     tonalité perdent toute couleur. Le filet redevient alors la seule chose qui
     dessine la pastille : on le rend au système. */
  @media (forced-colors:active){
    .sb-communaute .sb-card:has(.sb-communaute__pastille){border-color:CanvasText}
  }

/* patterns/cta-final.html */
/* ── La MENTION n'est pas un chapô ────────────────────────────────────────
     « 15 min · sans engagement · réponse rapide » sortait à 21 px, contre
     12,5 px au prototype — plus gros que le bouton qu'elle accompagne.

     La cause était dans `brands/yhb/style.css` : la règle qui habille les
     CHAPÔS attrapait tout `p` enfant direct du wrap. ELLE EST CORRIGÉE À LA
     SOURCE — `.sb-cta__mention` est désormais dans son `:not()`, avec le
     surtitre et les trois classes de la carte de visite. Ce pattern ne se bat
     donc plus contre rien : il ne fait que POSER sa taille.

     Un chapô développe le titre ; une mention rassure sous le bouton. Elles ne
     se lisent pas de la même façon — leur donner le même corps était une
     confusion de rôle, pas un réglage à corriger.
 */
  main > section > .sb-wrap > .sb-cta__mention{
    font-size:12.5px;
    letter-spacing:.03em;
    margin:18px auto 0;
    max-width:none;
    /* ⚠️ PAS d'`opacity`. Elle y était à .72, et le canal 3 l'a refusée : un
       texte translucide sur la bande d'accent tombe sous le seuil de contraste,
       et la page devient non livrable. Une opacité n'atténue pas seulement la
       couleur — elle atténue le RAPPORT entre le texte et son fond, ce qu'aucun
       jeton ne peut rattraper ensuite.
       La discrétion vient de la taille et de la casse, pas de la transparence. */
  }

/* patterns/hero.html */
/* ── LE LIEN DÉCLENCHEUR ────────────────────────────────────────────────────
     Une LIGNE À PART, sous `.sb-hero__actions`, et non un troisième bouton dans
     la rangée. Ce n'est pas un arbitrage graphique : la rangée est une liste
     répétable dont chaque entrée est un appel à l'action de la page, et le
     lecteur vidéo n'en est pas un — il ne mène nulle part, il montre. L'y
     glisser aurait aussi demandé au copy deck de distinguer, dans une même
     liste, les lignes qui naviguent de celle qui ouvre une couche.

     ⚠️ L'enveloppe est un <div> et NON un <p>. `brands/yhb/style.css` habille en
     chapô tout `p` enfant direct de `.sb-hero > .sb-wrap` — le sélecteur porte
     un `:not()` qui liste les exceptions une à une, et une classe de plus à y
     inscrire serait une feuille de marque à corriger pour un pattern partagé.
     Un <div> n'est pas visé par la règle : il n'y a rien à corriger nulle part. */
  .sb-hero__video-ligne{margin-top:20px;text-align:center}

  /* Champ vide : le compilateur retire le <a> (wrapInCondition sur le titre) et
     laisse l'enveloppe. Sans cette règle, six pages gagneraient 20 px de blanc
     que personne n'a demandés.

     ⚠️ `:not(:has(a))` ET NON `:empty`. Le réflexe était `:empty`, il ne mord
     pas ici : le gabarit produit met le `<?php if … ?>` sur SA PROPRE LIGNE,
     donc l'enveloppe garde un nœud de texte — un retour à la ligne et six
     espaces — même quand le lien n'est pas rendu. Vérifié sur le gabarit
     compilé, caractère par caractère. `:empty` est sensible à cette espace dans
     les navigateurs livrés aujourd'hui ; la règle n'aurait jamais mordu, et
     personne ne l'aurait su avant de voir le blanc.

     `:has()` est déjà la façon de faire de la maison pour cette question exacte
     — `brands/yhb/style.css` masque la rangée d'actions par
     `.sb-hero__actions:not(:has(a[href]:not([href="#"])))`, quinze lignes plus
     haut dans la même feuille. */
  .sb-hero__video-ligne:not(:has(a)){display:none}

  .sb-hero__video{
    display:inline-flex;align-items:center;gap:9px;
    font-family:var(--sb-font-mono);font-size:12.5px;letter-spacing:.08em;
    text-transform:uppercase;color:var(--sb-ink-fg);text-decoration:none;
    border-bottom:1px solid color-mix(in srgb,var(--sb-ink-fg) 28%,transparent);
    padding-bottom:3px;
  }
  .sb-hero__video:hover{color:var(--sb-accent-encre);border-bottom-color:var(--sb-accent-encre)}

  /* Le triangle est DESSINÉ, pas écrit. Un « ▶ » — qu'il vienne du copy deck ou
     d'un `content` — est un caractère : plusieurs lecteurs d'écran annoncent le
     contenu généré, et celui-là s'annonce « triangle noir pointant vers la
     droite » au milieu du libellé. Des bordures ne portent aucun texte, donc
     rien à annoncer et rien à masquer. `content:""` reste nécessaire pour que la
     pseudo-classe existe. */
  .sb-hero__video::before{
    content:"";width:0;height:0;border-style:solid;
    border-width:5px 0 5px 8px;
    border-color:transparent transparent transparent currentColor;
  }

  /* ── LA COUCHE ──────────────────────────────────────────────────────────────
     Elle est construite par le script et jamais servie par le gabarit : voir le
     bloc du <script> pour le raisonnement. Ces règles l'attendent.

     Le z-index est SOUS celui du bandeau de consentement (2147483000) et sous sa
     modale (2147483001). Ce n'est pas une politesse : le bandeau est ce qui
     permet de refuser, et une vidéo qui le recouvrirait retirerait au visiteur
     la commande dont il a le plus besoin — très exactement le défaut que le
     canal 6d relève. On se range donc dessous, délibérément. */
  .sb-videobox{
    position:fixed;inset:0;z-index:2147482990;display:none;
    align-items:center;justify-content:center;padding:24px;
  }
  .sb-videobox[data-sb-ouverte]{display:flex}

  /* Le voile est un <div> muet, pas un lien de fermeture : un <a> vide plein
     écran exigerait un `aria-hidden` sur un élément atteignable, ce que le
     canal 3 refuse à juste titre. Le clic-pour-fermer lui est rendu par le
     script — qui est de toute façon la condition d'existence de cette couche. */
  .sb-videobox__voile{position:absolute;inset:0;background:rgba(10,10,14,.82);backdrop-filter:blur(4px)}

  .sb-videobox__boite{
    position:relative;width:100%;max-width:min(1100px,92vw);
    border-radius:calc(var(--sb-radius) + 6px);overflow:hidden;
    background:#000;box-shadow:0 50px 100px -34px rgba(0,0,0,.6);
  }

  /* Le RATIO tient la place avant que le lecteur réponde : sans lui la boîte
     naît plate puis se déplie, et le voile saute sous les yeux de qui vient de
     cliquer. Même raison que le `aspect-ratio` de `sb_embed_cadre()`. */
  .sb-videobox__cadre{position:relative;width:100%;aspect-ratio:16 / 9;background:#000}
  .sb-videobox__cadre iframe{display:block;width:100%;height:100%;border:0}

  /* ── LA COULEUR DE LA COUCHE — SON IVOIRE, ET POURQUOI PAS `--sb-ink-fg` ──
     Cette couche est SOMBRE DANS LES TROIS PRÉSENTATIONS, et pas par oubli :
     son voile, sa boîte et son cadre sont des noirs FIXES, posés dix lignes
     plus haut, parce qu'ils encadrent une vidéo — une image dont on ne connaît
     ni la luminosité ni la tonalité, et qui n'a aucune raison de suivre celle
     du site.

     Conséquence : ce qui s'écrit dessus doit rester dans la famille ENCRE quoi
     qu'il arrive. `--sb-ink-fg` serait donc FAUX ici — `lib/entete.php` le
     remappe sur l'encre claire en présentation « jour », et la croix passerait
     au noir sur sa plaque restée noire (#0B0B0E sur rgba(10,10,14,.72) :
     1,05:1 MESURÉ, invisible). C'est mot pour mot le piège que
     `brands/yhb/style.css` décrit sur le bandeau de consentement, qui est dans
     le même cas et s'en sort de la même façon.

     `--sb-accent-fg` est l'ivoire de la maison — celui qui se pose SUR une
     couleur pleine — et aucune des trois présentations ne le remappe : c'est le
     seul jeton du thème qui tienne la valeur sans la faire bouger. Il rend
     exactement le #FAF8F3 d'avant, à ceci près qu'une marque qui changerait son
     ivoire l'emmènerait désormais avec elle.

     La croix est POSÉE SUR la vidéo, pas au-dessus d'elle : une barre de titre
     mangerait de la hauteur sur un portable couché, là où il en reste le moins.
     Le fond opaque lui garde son contraste quelle que soit l'image dessous —
     une croix blanche sur une vidéo claire ne se voit pas, et c'est la seule
     commande de la couche.
     MESURÉ sur sa plaque (rgba(10,10,14,.72) composée sur le voile) : 15,7:1. */
  .sb-videobox{--sb-videobox-fg:var(--sb-accent-fg,#FAF8F3)}
  .sb-videobox__fermer{
    position:absolute;top:10px;right:10px;z-index:1;
    width:40px;height:40px;border:0;border-radius:999px;cursor:pointer;
    background:rgba(10,10,14,.72);color:var(--sb-videobox-fg);
    font-size:22px;line-height:1;
  }
  .sb-videobox__fermer:hover{background:rgba(10,10,14,.92)}
  /* Le liseré de focus prend le MÊME ivoire, et pour la même raison : il se
     dessine sur la boîte noire, jamais sur le fond de la page. Il ne suit donc
     pas `:focus-visible` du catalogue, qui suit la tonalité — ici elle ment. */
  .sb-videobox :focus-visible{outline:3px solid var(--sb-videobox-fg);outline-offset:2px}

  @media (prefers-reduced-motion:no-preference){
    .sb-videobox[data-sb-ouverte] .sb-videobox__boite{animation:sb-videobox-pose .2s ease-out}
  }
  @keyframes sb-videobox-pose{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}

/* patterns/lieu.html */
/* ══ Lieu — le plan, et ses deux états ═══════════════════════════════════════
   Toutes les valeurs sont RELEVÉES sur le prototype servi, jamais estimées : le
   relevé complet est en tête de patterns/lieu.html. Ce bloc ne fait que trois
   choses — donner au plan la bande de 180 px du prototype, empêcher le cadre de
   creuser un trou tant que le visiteur n'a rien accepté, et effacer
   l'emplacement quand il est vide. Le reste appartient à la feuille de marque.
   ═════════════════════════════════════════════════════════════════════════ */

/* 20 px sous le plan, relevés entre le plan et l'adresse. */
.sb-lieu__plan-interactif{margin:0 0 1.25rem}

/* L'emplacement est un <div>, et le compilateur le rend TOUJOURS : il n'entoure
   de condition que les <p> et les titres. Vide, il vaudrait une gouttière pour
   rien. Pour qui peut éditer la page il porte la note du module, donc il n'est
   pas vide et reste visible — ce qui est exactement l'effet voulu. */
.sb-lieu__plan-interactif:empty{display:none}

/* ── ACCORDÉ : la bande de 180 px du prototype ───────────────────────────────
   `height` et NON `aspect-ratio` : le module d'intégration pose le ratio en
   style EN LIGNE sur le cadre, qu'aucune règle de feuille ne bat. Inutile de
   lutter — `aspect-ratio` ne gouverne qu'une dimension automatique, donc une
   hauteur définie suffit à le rendre sans effet, sans `!important`.
   Mesuré à 960 px de large : 720 px de haut sans cette ligne, 180 px avec. */
.sb-lieu__plan-interactif .sb-embed__cadre{height:11.25rem}

/* Le rayon est porté par l'IFRAME, et non par un `overflow:hidden` sur le
   cadre. Deux raisons, la seconde étant la vraie : une iframe arrondie rogne
   déjà son propre contenu, cela suffit ; et surtout, le jour où `:has()`
   manquerait, la règle suivante ne s'appliquerait pas et un `overflow:hidden`
   ROGNERAIT le remplaçant — donc le bouton qui autorise la carte. Un
   débordement se lit et se corrige ; un bouton coupé, le visiteur le subit.

   ÉCART ASSUMÉ, mesuré : le prototype arrondit le plan à 16 px, et le jeton
   --sb-radius d'YHB vaut 14. Le pattern prend le JETON, pas les 16 px : il ne
   connaît pas la marque où il tombera, et une géométrie de marque écrite en dur
   dans le catalogue est une dette qu'on paie à la marque suivante. Les 2 px se
   rattrapent d'une ligne dans brands/yhb/style.css, qui pose déjà 16 px sur le
   plan là où il vit aujourd'hui (section 23, `.sb-integre:has(+ .sb-cta)`). */
.sb-lieu__plan-interactif .sb-embed__cadre iframe{border-radius:var(--sb-radius,1rem)}

/* ── EN ATTENTE D'ACCORD : l'état de toute première visite ───────────────────
   Le module de consentement insère son remplaçant DANS le cadre, juste avant
   l'iframe, et pose `hidden` sur celle-ci. Le cadre garderait donc ses 180 px
   (ou les 720 de son ratio) autour d'un encart bien plus court : relevé 720 px
   de boîte pour 51 px de contenu, un trou juste au-dessus du bouton qui
   réparerait tout. `display:contents` efface la boîte du cadre ; le remplaçant
   se dimensionne alors seul.
   L'iframe vidée n'a pas à être masquée ici : vérifié au navigateur, la règle
   `[hidden]{display:none !important}` de la feuille du navigateur l'emporte sur
   le `display:block` en ligne du module. */
.sb-lieu__plan-interactif .sb-embed__cadre:has(.sbwp-cnst-remplacant){display:contents}

/* patterns/profil.html */
/* ── La page est une CARTE, pas une section de site ────────────────────────
     `main > section` vaut clamp(68px,10vh,140px) de padding vertical dans
     brands/yhb/style.css:696, et .sb-wrap y vaut 1180 px de large : appliqués
     tels quels, la carte de 432 px se retrouvait dans une colonne de 1180
     alignée à gauche, avec 140 px de vide au-dessus.
     Le sélecteur porte une classe de plus que celui qu'il bat — (0,1,2) contre
     (0,0,2) — donc il l'emporte quel que soit l'ordre de la cascade. */
  main > section.sb-profil{
    padding:clamp(28px,7vh,72px) 0 40px;
    /* La carte occupe la hauteur d'écran : c'est ELLE qui donne sa couleur au
       document, et non le fond de <body>. Sans cela, la tonalité de la section
       s'arrête sous le pied et le fond du thème réapparaît en bas d'écran. */
    min-height:100dvh;
  }

  /* 472 et non 432 : la colonne de la référence fait 432 px de CONTENU, avec
     20 px de marge de chaque côté portés par <body>. En border-box, 432 + 20
     + 20 = 472. Écrire 432 ici donnait 392 px de texte — 40 px trop étroit sur
     chaque ligne, ce que le canal géométrie relève sur les six lignes. */
  .sb-profil > .sb-wrap{max-width:472px;padding-inline:20px;text-align:center}

  /* ── Le monogramme ────────────────────────────────────────────────────────
     Le dessin n'est PAS recopié ici. Il vit déjà en data-URI dans
     brands/yhb/style.css (.sb-header .sb-logo::before) ; une seconde écriture
     serait une divergence en attente — le reproche que tokens.css fait déjà à
     --yhb-grad-btn. La marque le sert par `--sb-monogramme`, et les deux
     emplois consomment alors la MÊME écriture. Les règles exactes à poser dans
     la feuille de marque sont dans le rapport de passation.
     Sans ce jeton la plaque sort nue : c'est visible, donc corrigible. Inventer
     un glyphe de repli dans le catalogue partagé le rendrait invisible. */
  .sb-profil__monogramme{
    width:76px;height:76px;margin-inline:auto;border-radius:20px;
    background-image:var(--sb-monogramme,none),
      var(--sb-degrade,var(--sb-accent-fond,var(--sb-accent)));
    background-repeat:no-repeat;
    background-position:center;
    /* 52 px de dessin dans 76 px de plaque — relevé, pas centré « à peu près ». */
    background-size:52px 52px,cover;
    /* La lueur de la marque, au pixel : --sb-lueur vaut déjà
       `0 14px 40px -18px #FF2EA6`, qui EST l'ombre relevée sur cette page. */
    box-shadow:var(--sb-lueur,none);
  }

  /* ── Identité ─────────────────────────────────────────────────────────────
     Le <h1> du thème vaut clamp(2rem,5vw,3.5rem) dans patterns.css et la marque
     le pousse plus haut encore : 54 px mesurés sur cette page avant correction,
     contre 32 attendus. */
  .sb-profil__nom{
    margin:20px 0 0;font-size:clamp(26px,7vw,32px);font-weight:700;
    letter-spacing:-.02em;line-height:1.05;max-width:none;
  }

  /* ── ⚠️ LA CLASSE EST RÉPÉTÉE SUR LES TROIS PARAGRAPHES, ET C'EST DÉLIBÉRÉ ──
     brands/yhb/style.css:714 pose
        main > section > .sb-wrap > :is(.sb-lead,p):not(.sb-eyebrow)
     pour habiller les CHAPÔS, et cette règle attrape TOUT <p> enfant direct du
     wrap — donc le rôle, l'intro et le pied, qui n'en sont pas. Elle pèse
     (0,3,2) : `:is()` prend la spécificité de son argument le plus fort
     (.sb-lead), et `:not(.sb-eyebrow)` en ajoute encore.
     Le même sélecteur avec une seule classe pèse (0,2,2) et ne mord pas — c'est
     exactement ce qui est arrivé à la mention de cta-final.html, sortie à 21 px
     au lieu de 12,5. Doubler la classe amène à ÉGALITÉ (0,3,2) ; l'ordre
     tranche alors, et ce bloc étant concaténé après la feuille de marque
     (lib/build.php:323), il l'emporte.

     C'est un contrepoids, pas une solution. La vraie correction est d'exclure
     ces trois classes de la règle de chapô dans brands/yhb/style.css, comme il
     a été fait pour .sb-eyebrow. Elle est écrite dans le rapport. */
  main > section > .sb-wrap > .sb-profil__role.sb-profil__role{
    margin:9px 0 0;max-width:none;
    font-family:var(--sb-font-mono,ui-monospace,SFMono-Regular,Menlo,monospace);
    font-size:11px;font-weight:400;letter-spacing:.16em;text-transform:uppercase;
    color:var(--sb-profil-muted);
  }
  main > section > .sb-wrap > .sb-profil__intro.sb-profil__intro{
    margin:16px auto 0;max-width:34ch;
    font-size:14.5px;font-weight:300;line-height:1.6;letter-spacing:normal;
    color:var(--sb-profil-doux);
  }
  main > section > .sb-wrap > .sb-profil__pied,
  main > section > .sb-wrap > .sb-profil__pied > p{
    margin:28px 0 0;max-width:none;
    font-family:var(--sb-font-mono,ui-monospace,SFMono-Regular,Menlo,monospace);
    /* 12 px et non 10,5 depuis le 22.08.2026. C'est le plancher de lisibilité du
       canal 6, et il refusait la livraison — à juste titre : cette ligne porte la
       raison sociale et l'adresse postale, c'est-à-dire les seules mentions que
       quelqu'un vient réellement y CHERCHER. Une adresse qu'on plisse les yeux
       pour lire n'est pas une adresse.
       Le pas est de 1,5 px : la ligne tient toujours sur une seule à 320 px,
       vérifié, et le gris et l'interlettrage sont conservés. */
    font-size:12px;font-weight:400;letter-spacing:.06em;line-height:1.6;
    color:var(--sb-profil-muted);text-align:center;
  }
  /* Le <p> que la chaîne injecte dans un champ `html` ne doit rien ajouter. */
  main > section > .sb-wrap > .sb-profil__pied > p{margin:0}

  /* ── Les deux gris, dérivés et non inventés ───────────────────────────────
     La page de référence sert --muted:#928E86 et --text-soft:#C8C4BB en
     sombre. Les écrire tels quels figerait deux couleurs d'YHB dans le
     catalogue partagé. On les DÉRIVE de la tonalité courante, avec la formule
     que brands/yhb/style.css emploie déjà pour --yhb-muted-dark (57 %) et
     --yhb-text-soft-dark (77 %) : le pattern suit la marque au lieu de la
     doubler, et s'adapte seul à une section claire.
     MESURÉ sur le sombre profond #0A0910 : le gris à 57 % donne 6,08:1, et
     5,62:1 sur l'encre commune #14141A. Le sous-titre et le pied sont à
     10,5 px, donc soumis aux 4,5:1 de WCAG AA — les deux passent. */
  .sb-profil{
    --sb-profil-muted:color-mix(in srgb,var(--sb-fg) 57%,var(--sb-bg));
    --sb-profil-doux:color-mix(in srgb,var(--sb-fg) 77%,var(--sb-bg));
    /* La plaque d'une ligne : un voile clair sur la tonalité, jamais une
       couleur. Relevé rgba(255,255,255,.04) en sombre. */
    --sb-profil-plaque:var(--sb-plaque,color-mix(in srgb,var(--sb-fg) 4%,transparent));
    --sb-profil-bord:var(--sb-plaque-bord,color-mix(in srgb,var(--sb-fg) 13%,transparent));
    /* L'accent des icônes. --sb-accent-encre EN PREMIER : patterns.css:73
       réécrit --sb-accent-text vers --sb-accent dans .sb-ink, et l'accent plat
       d'YHB (#A02597) tombe à 2,8:1 sur le sombre. #FF2EA6, lui, est justement
       le jeton que la marque a nommé pour ce cas. */
    --sb-profil-accent:var(--sb-accent-encre,var(--sb-accent-text,var(--sb-accent)));
    --sb-profil-duree:var(--sb-motion,.22s);
    --sb-profil-courbe:var(--sb-ease,cubic-bezier(.22,.61,.36,1));
  }

  /* ── La liste ─────────────────────────────────────────────────────────────
     Une colonne, pas une grille : les lignes ne se répartissent jamais sur
     deux colonnes, quel que soit leur nombre. */
  .sb-profil__liste{margin:30px 0 0;display:flex;flex-direction:column;gap:11px}

  .sb-profil__ligne{
    position:relative;display:flex;align-items:center;gap:14px;
    padding:15px 18px;text-align:left;
    border:1px solid var(--sb-profil-bord);
    border-radius:var(--sb-radius,14px);
    background:var(--sb-profil-plaque);
    transition:transform var(--sb-profil-duree) var(--sb-profil-courbe),
               border-color var(--sb-profil-duree) var(--sb-profil-courbe);
  }
  .sb-profil__ligne:hover{transform:translateY(-2px);border-color:var(--sb-profil-accent)}

  /* Une ligne dont l'intitulé n'est pas rempli n'a PAS de <a> — le compilateur
     l'a retiré par condition — et ne serait plus qu'une icône et une flèche
     autour de rien. On la retire entière. Même geste que
     `.sb-communaute .sb-card:not(:has(h3))` dans la feuille de marque. */
  .sb-profil__ligne:not(:has(.sb-profil__lien)){display:none}

  /* L'anneau de focus appartient à la LIGNE, pas au lien : le lien est étiré
     par ::after, mais `outline` se dessine autour de la boîte propre du <a>,
     c'est-à-dire du seul intitulé. Le visiteur au clavier verrait un cadre
     autour de trois mots au lieu de la cible qu'il va activer. */
  .sb-profil__ligne:has(.sb-profil__lien:focus-visible){
    outline:2px solid var(--sb-profil-accent);outline-offset:2px;
  }

  /* Le voyage de 2 px et celui de la flèche sont du confort. Qui demande le
     calme n'a pas à les subir ; le changement de bord et de couleur, lui,
     reste — c'est LUI qui dit « cette ligne est visée ». */
  @media (prefers-reduced-motion:reduce){
    .sb-profil__ligne,.sb-profil__fleche{transition:none}
    .sb-profil__ligne:hover{transform:none}
    .sb-profil__ligne:hover .sb-profil__fleche{transform:none}
  }

  /* ── L'icône ──────────────────────────────────────────────────────────────
     `flex:none` : sans lui l'icône se comprime dès qu'un sous-titre est long,
     et les six lignes n'alignent plus leurs dessins. */
  /* ⚠️ LA PASTILLE DU CHAMP EST UN `div`, PAS UN `span` — et c'est nécessaire.
     Le champ `icone` porte `data-sb-type="html"`, donc `wpautop` enveloppe son
     SVG dans un `<p>`. Or un `<p>` n'est pas du contenu de phrasé : dans un
     `<span>`, le balisage est invalide, et le validateur le relevait SEPT fois
     sur /yan/ — une par ligne du répéteur.
     Les deux autres pastilles de ce gabarit restent des `span` : leur SVG est
     écrit en dur ici, il ne passe jamais par `wpautop`.
     Aucun effet visuel : la pastille est un enfant de conteneur flex, donc
     déjà bloquifiée par la mise en page — mesuré identique avant/après. */
  .sb-profil__ic{flex:none;width:22px;height:22px;color:var(--sb-profil-accent)}
  .sb-profil__ic svg{width:100%;height:100%;display:block}
  /* Champ non rempli : le conteneur resterait et vaudrait 22 px de gouttière
     pour rien. Même correctif que `.dna-ic:empty` dans la feuille de marque,
     posé pour exactement ce symptôme — trois pastilles vides sur /noyau/. */
  .sb-profil__ic:empty{display:none}

  /* ── L'intitulé et son lien ───────────────────────────────────────────────
     `min-width:0` sur le bloc de texte : sans lui, un élément de flex refuse de
     descendre sous la largeur de son contenu, `text-overflow:ellipsis` du
     sous-titre ne se déclenche jamais et une adresse longue pousse la flèche
     hors de la ligne. */
  .sb-profil__tx{flex:1;min-width:0}
  .sb-profil__t{display:block;font-size:15px;font-weight:600;letter-spacing:-.01em}
  /* Le <b> survit à un lien retiré par condition : il serait une ligne de 24 px
     de vide. La ligne entière est déjà masquée plus haut ; cette règle est la
     ceinture de la bretelle, et elle protège aussi l'emploi hors répéteur. */
  .sb-profil__t:empty{display:none}

  .sb-profil__lien{
    display:block;color:inherit;text-decoration:none;
    /* 1,6 × 15 px = 24 px : la boîte propre du lien atteint tout juste le
       plancher de 24 px des cibles tactiles. Le contrôle mesure cette
       boîte-là, pas la surface étirée — c'est ce qui avait fait tomber
       `.sb-porte__l` à 22 px. À ne pas descendre. */
    line-height:1.6;
  }
  /* La surface cliquable devient la ligne entière. Le nom accessible reste
     celui du <a>, donc l'intitulé, et rien d'autre n'est annoncé. */
  .sb-profil__lien::after{content:"";position:absolute;inset:0;border-radius:inherit}
  .sb-profil__ligne:hover .sb-profil__lien{text-decoration:none}

  .sb-profil__sous{
    display:block;margin-top:1px;
    font-family:var(--sb-font-mono,ui-monospace,SFMono-Regular,Menlo,monospace);
    /* ⚠️ 12 PX EST UN PLANCHER, PAS UNE PRÉFÉRENCE. Ce texte était à 10,5 px —
       relevé par le canal mobile aux DIX largeurs, sur six libellés à la fois
       (« yanhaltey.com », « /in/yanhaltey », « Fiche vCard · téléchargement »…).
       Personne ne l'avait vu parce que la porte qualité ne regardait qu'une
       page, et jamais celle-ci.
       Le gain de place était illusoire : ces libellés portent `text-overflow:
       ellipsis`, donc ils se coupent déjà quand ils dépassent. Les rapetisser ne
       faisait pas tenir plus de texte, seulement moins lisible celui qui tient.
       Et c'est une mono à 0,04 em d'interlettrage, la moins pardonnante des
       fontes pour un petit corps. */
    font-size:12px;font-weight:400;letter-spacing:.04em;line-height:1.6;
    color:var(--sb-profil-muted);
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  }

  .sb-profil__fleche{
    flex:none;font-size:17px;line-height:1;color:var(--sb-profil-muted);
    transition:transform var(--sb-profil-duree) var(--sb-profil-courbe),
               color var(--sb-profil-duree) var(--sb-profil-courbe);
  }
  .sb-profil__ligne:hover .sb-profil__fleche{
    color:var(--sb-profil-accent);transform:translateX(3px);
  }

  /* ── La ligne mise en avant ───────────────────────────────────────────────
     La variante pose sa classe sur le LIEN — c'est lui qui porte le champ —
     mais c'est la LIGNE qu'elle doit repeindre. `:has()` fait le pont. La
     feuille de marque emploie déjà ce sélecteur pour ancrer des pages
     entières ; il n'introduit rien de nouveau ici.
     Relevé sur la ligne vCard de la référence : fond du dégradé, bord
     transparent, texte et flèche à #FBF9F4, sous-titre à 72 % d'opacité, et
     une ombre de `0 14px 38px -20px` — VOISINE de --sb-lueur sans lui être
     égale (40/-18 contre 38/-20). L'écart est de 2 px sur deux valeurs d'une
     ombre floue : il n'est pas perceptible, et prendre le jeton évite une
     troisième écriture de la même lueur. */
  /* MESURÉ sur la référence, pas supposé : le rang primaire garde EXACTEMENT la
     même plaque que les autres — `rgba(255,255,255,.04)`, relevé — et se
     distingue par une barre dégradée sous lui, pas par un aplat.
     Le remplir du dégradé le rendait plus voyant que le nom de la personne, et
     obligeait à repeindre l'icône, la flèche et le sous-titre en couleur
     d'accent pour rester lisibles. Une accentuation, pas une inversion. */
  .sb-profil__ligne:has(.sb-profil__lien--primaire){
    border-color:transparent;
    box-shadow:var(--sb-lueur,none);
  }
  .sb-profil__ligne:has(.sb-profil__lien--primaire)::after{
    content:"";position:absolute;inset:auto 0 0;height:2px;
    background:var(--sb-degrade,var(--sb-accent-fond,var(--sb-accent)));
    border-radius:inherit;pointer-events:none;
  }
  .sb-profil__ligne:has(.sb-profil__lien--primaire):hover{border-color:transparent}

  /* Un texte découpé sur un dégradé et des icônes en `currentColor` perdent
     toute couleur en contraste forcé. On rend au système ce qu'il sait
     repeindre, et le bord — invisible en temps normal — redevient la seule
     chose qui dessine la ligne. */
  @media (forced-colors:active){
    .sb-profil__ligne{border-color:CanvasText}
    .sb-profil__ligne:has(.sb-profil__lien--primaire){background:none}
    .sb-profil__monogramme{box-shadow:none;border:1px solid CanvasText}
  }

/* patterns/temoignages.html */
/* ══ Témoignages — le carousel. GÉNÉRÉ À LA MAIN depuis le prototype ═════════
   Toutes les valeurs sont RELEVÉES au navigateur sur localhost:8765 (voir le
   relevé complet en tête de patterns/temoignages.html) ; aucune n'est estimée.
   Le mouvement n'est PAS écrit ici : il est dans le script, parce qu'il anime
   la position de défilement et non une transformation. Le CSS ne fait que
   poser la rangée, la carte, et écarter ce qui se battrait avec cette position.
   ══════════════════════════════════════════════════════════════════════════ */

.sb-temoignages{
  /* Géométrie relevée : carte 340, gouttière 20 — donc un pas de 360 px et une
     période de 360 × (nombre de témoignages). */
  --sb-temoins-carte:340px;
  --sb-temoins-espace:20px;
  /* Le battement vertical de 4 px du prototype (`.tcard{margin:4px 0}`), porté
     en rembourrage de la PISTE et non en marge de carte : c'est la piste qui
     rogne maintenant, et un soulèvement de survol de 3 px doit tenir dedans. */
  --sb-temoins-battement:4px;
  --sb-temoins-fondu:7%;      /* le fondu des deux bords, relevé à 7 %/93 % */
  --sb-temoins-rayon:16px;    /* et non les 18 px des autres cartes de la marque */
  /* La VITESSE, en px/s, et non une durée : une durée fixe fait dépendre
     l'allure du nombre de témoignages et de la largeur de la fenêtre — c'est le
     défaut n° 1 du prototype, mesuré à 17 % d'écart entre grand écran et
     téléphone. Relevée à 34,02 px/s. Lue par le script en propriété calculée :
     une marque peut donc la changer depuis sa feuille, sans toucher au pattern. */
  --sb-temoins-vitesse:34;

  /* ── Les couleurs, dérivées de la TONALITÉ et non écrites en dur ──────────
     La tonalité d'une section vient de son rang ou du copy deck : le pattern ne
     sait pas où il tombera, il doit tenir en encre comme en papier. Chaque
     teinte est donc un mélange de la couleur de texte de la section, qui
     s'inverse toute seule.
     Les pourcentages ne sont pas choisis : ils sont RETROUVÉS depuis les
     couleurs relevées sur le prototype sombre, texte #FAF8F3 sur plaque ≈#1F1F24 —
       plaque  rgba(255,255,255,.03)  →  3 %
       filet   rgba(244,241,234,.13)  → 13 %   (= --yhb-border-dark, au poil)
       citation #C8C4BB               → 77 %, arrondi à 78
     Et en tonalité papier les mêmes règles retombent sur les jetons que la
     marque s'était donnés : 13 % d'encre sur crème, c'est --yhb-border.

     ⚠️ `var(--sb-fg)` et NON `currentColor`. Un mélange à `currentColor` est
        résolu sur l'élément qui l'EMPLOIE, pas sur celui qui le déclare : la
        pastille de marque, dont la couleur de texte est déjà le mélange à 62 %,
        prenait donc 13 % de ce 62 % pour son filet. Mesuré au navigateur :
        alpha 0,0806 au lieu de 0,13 attendus — un contour deux fois trop pâle,
        sur le seul élément qui en a un à lui. `--sb-fg` est posé par
        patterns.css sur `.sb-ink` et `.sb-paper` : il vaut la couleur de texte
        de la SECTION, où qu'on le lise. */
  --sb-temoins-plaque:color-mix(in srgb,var(--sb-fg,currentColor) 3%,transparent);
  --sb-temoins-filet:color-mix(in srgb,var(--sb-fg,currentColor) 13%,transparent);
  --sb-temoins-doux:color-mix(in srgb,var(--sb-fg,currentColor) 78%,transparent);
  /* L'auteur et la pastille : le prototype pose #928E86, soit 52 % du texte sur
     la plaque sombre. 52 % tiendrait en encre (5,03:1) mais tomberait à 3,82:1
     en papier — sous les 4,5:1 de WCAG AA, pour du texte de 10,5 à 12,5 px. On
     monte à 62 %, seule valeur qui passe DES DEUX CÔTÉS — 7,25:1 en encre et
     5,18:1 en papier, mesurés sur le rendu compilé. */
  --sb-temoins-muted:color-mix(in srgb,var(--sb-fg,currentColor) 62%,transparent);
  /* L'accent porte le guillemet et les mots mis en valeur. */
  --sb-temoins-accent:var(--sb-accent-text,var(--sb-accent));
}

/* patterns.css remappe `--sb-accent-text` sur `--sb-accent` dans `.sb-ink` :
   l'accent d'YHB y tombe à 2,8:1 sur l'encre, et un guillemet déjà posé à 35 %
   d'opacité s'y éteint tout à fait. On l'éclaircit vers la couleur du texte
   encre, exactement comme `.sb-chiffres.sb-ink` le fait pour son dégradé —
   même problème, même remède, pour qu'il n'y ait qu'une chose à réapprendre. */
.sb-temoignages.sb-ink{
  --sb-temoins-accent:color-mix(in oklab,var(--sb-accent) 72%,var(--sb-ink-fg));
}

@media (max-width:35rem){          /* 560 px : la bascule RELEVÉE du prototype */
  .sb-temoignages{--sb-temoins-carte:280px}
}

/* ── La piste : c'est ELLE la boîte de défilement ──────────────────────────
   Et non un cadre qui rognerait une piste translatée. C'est ce qui garde la
   molette, le doigt et les flèches opérants PENDANT que le ruban tourne, et ce
   qui donne un repli honnête sous mouvement réduit. `tabindex="0"` et
   `role="list"` sont sur le markup : le premier parce qu'axe-core exige qu'une
   zone défilante soit atteignable au clavier (scrollable-region-focusable), le
   second parce que retirer les puces retire aussi la sémantique de liste dans
   Safari. Ni l'un ni l'autre n'est décoratif. */
.sb-temoignages .sb-temoins{
  display:flex;flex-wrap:nowrap;align-items:stretch;
  gap:var(--sb-temoins-espace);
  list-style:none;
  /* 30 px, et non 56. La feuille du prototype pose 26 px sous la coiffe de
     titre ET 30 px au-dessus du carousel : LUES, elles s'additionnent ; MESURÉES
     au navigateur, elles se réduisent à 30 — deux marges verticales de frères
     fusionnent. Le relevé fait foi : 30 px du bas du h2 au haut du cadre, 34 px
     jusqu'au haut de la carte, le battement de 4 px compris. Ici l'habillage de
     marque pose `margin:0 auto` sur le h2 de section : il n'y a rien à fusionner,
     et ces 30 px plus les 4 px de rembourrage redonnent les 34 px du prototype. */
  margin:30px 0 0;
  padding:var(--sb-temoins-battement) 0;
  overflow-x:auto;
  /* Rogné verticalement, sinon `overflow-x` seul rendrait aussi l'axe Y
     défilable et la page gagnerait une barre parasite au moindre débordement. */
  overflow-y:hidden;
  /* L'accroche sert quand la rangée est manuelle — au doigt, et surtout sous
     mouvement réduit où c'est le seul mode. Elle est retirée plus bas dès que
     le script défile vraiment : sinon le ruban bégaie à chaque arrêt. */
  scroll-snap-type:x proximity;
  /* La rangée est en permanence à sa fin. Sans cela, la chaîne de défilement
     remonte à la page — ou déclenche le geste « page précédente » du
     navigateur, ce qui coûte la visite. */
  overscroll-behavior-x:contain;
  /* Barre masquée, comme la bande de marques de la marque (style.css:1136).
     Elle revient sous mouvement réduit, plus bas : là, elle est la seule chose
     qui dise qu'il reste des témoignages à droite. */
  scrollbar-width:none;-ms-overflow-style:none;
  text-align:left;                 /* le titre reste centré, pas les cartes */
}
.sb-temoignages .sb-temoins::-webkit-scrollbar{display:none}

/* ── L'état ACTIF : posé par le script, et seulement quand il défile pour de
   bon — ni sous mouvement réduit, ni quand la rangée tient déjà en largeur ── */
.sb-temoignages .sb-temoins[data-sb-temoins-actif]{
  scroll-snap-type:none;
  /* Un `scroll-behavior:smooth` hérité lisserait CHAQUE écriture de position :
     le ruban se traînerait à une fraction de sa vitesse sans que rien ne le
     dise. Le script écrit soixante fois par seconde, il ne veut pas d'aide. */
  scroll-behavior:auto;
}
.sb-temoignages .sb-temoins[data-sb-temoins-actif] > *{scroll-snap-align:none}

/* Le fondu dit « ça continue » là où le rognage dirait « ça s'arrête ».
   Levé dès que le focus entre : le masque s'applique au dessin de l'élément,
   liseré de focus compris, et un anneau mangé sur ses deux bords n'apprend plus
   rien à personne. Le ruban étant AUSSI en pause à ce moment-là, l'ensemble se
   lit comme une seule décision — au clavier, la rangée s'arrête et se montre
   entière. Même arbitrage que lib/ruban.php. */
.sb-temoignages .sb-temoins[data-sb-temoins-actif]:not(:focus-within){
  -webkit-mask:linear-gradient(90deg,transparent 0,#000 var(--sb-temoins-fondu),
    #000 calc(100% - var(--sb-temoins-fondu)),transparent 100%);
  mask:linear-gradient(90deg,transparent 0,#000 var(--sb-temoins-fondu),
    #000 calc(100% - var(--sb-temoins-fondu)),transparent 100%);
}

/* ── La carte ──────────────────────────────────────────────────────────────
   Trois classes, et ce n'est pas de la coquetterie : `.sb-ink .sb-card` de
   l'habillage de marque pèse (0,2,0) et pose une plaque de 5 %, un rayon de 18
   et 28 px de rembourrage. On repasse devant par la STRUCTURE (0,3,0) et jamais
   par `!important`, que la marque ne pourrait plus défaire. */
.sb-temoignages .sb-temoins .sb-temoin{
  position:relative;                          /* le guillemet est posé dessus */
  flex:0 0 auto;width:var(--sb-temoins-carte);
  display:flex;flex-direction:column;align-items:flex-start;gap:12px;
  padding:26px 30px;
  border:1px solid var(--sb-temoins-filet);
  border-radius:var(--sb-temoins-rayon);
  background:var(--sb-temoins-plaque);
  color:inherit;
  scroll-snap-align:center;
  /* L'ombre du prototype est rognée par la boîte elle-même — mesuré : cadre de
     225,17 px pour des cartes de 217,17, soit 4 px de battement. On ne recopie
     pas une ombre que personne ne peut voir. */
  box-shadow:none;
}
.sb-temoignages .sb-temoins .sb-temoin:hover{
  transform:translateY(-3px);                 /* relevé, site.css:2549 */
  border-color:var(--sb-temoins-accent);
  box-shadow:none;
}

/* Le gros guillemet ouvrant. Décoratif : il est en pseudo-élément, donc jamais
   annoncé, et la citation garde son propre `<blockquote>`. Georgia parce que
   c'est le dessin du prototype et qu'aucune police de la marque n'a ce galbe. */
.sb-temoignages .sb-temoin::before{
  content:"\201C";
  position:absolute;top:6px;left:18px;
  font-family:Georgia,"Times New Roman",serif;
  font-size:56px;line-height:56px;
  color:var(--sb-temoins-accent);opacity:.35;
  pointer-events:none;
}

.sb-temoignages .sb-temoin__citation{
  margin:6px 0 0;padding-left:4px;
  font-size:16px;line-height:1.6;font-weight:400;
  color:var(--sb-temoins-doux);
  /* La citation vient d'un champ texte : elle est échappée, donc sans balise.
     Ces deux lignes servent le jour où le champ passera en `html` et où les
     `**mots**` du prototype reviendront en gras — voir INTÉGRATION, point 4. */
}
.sb-temoignages .sb-temoin__citation :is(b,strong){
  font-weight:700;color:var(--sb-temoins-accent);
}

/* La ligne du bas, poussée EN BAS : deux cartes voisines n'ont pas la même
   longueur de citation, et sans cela leurs auteurs ne s'alignent pas. */
.sb-temoignages .sb-temoin__meta{
  width:100%;margin-top:auto;padding-top:16px;
  display:flex;align-items:center;justify-content:space-between;
  gap:14px;flex-wrap:nowrap;
}

.sb-temoignages .sb-temoin .sb-temoin__auteur{
  margin:0;flex:1 1 auto;min-width:0;
  font-family:var(--sb-font-mono,ui-monospace,SFMono-Regular,Menlo,monospace);
  font-size:12.5px;line-height:1.6;letter-spacing:.06em;text-transform:uppercase;
  color:var(--sb-temoins-muted);opacity:1;
  /* Un nom long — « Stéphanie B. · créatrice de bougies » — pousserait la
     pastille hors de la carte. Il est coupé, la pastille ne bouge pas. */
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}

/* La pastille bordée. `margin-inline-start:auto` en plus du `space-between` :
   si l'auteur n'est pas renseigné, le compilateur ne rend pas son `<p>` du tout
   et la pastille se retrouverait à gauche. */
.sb-temoignages .sb-temoin .sb-temoin__marque{
  margin:0 0 0 auto;flex:0 0 auto;
  font-family:var(--sb-font-mono,ui-monospace,SFMono-Regular,Menlo,monospace);
  /* 12px et non 10.5 : plancher absolu du canal 6, toutes largeurs confondues.
     Une pastille en capitales espacées PARAÎT plus grande qu'elle n'est — c'est
     l'illusion qui avait fait descendre à 10.5. Le canal, lui, mesure. Le
     `letter-spacing` fait le travail que la petite taille croyait faire. */
  font-size:12px;line-height:1.6;letter-spacing:.07em;text-transform:uppercase;
  color:var(--sb-temoins-muted);opacity:1;
  border:1px solid var(--sb-temoins-filet);border-radius:999px;
  padding:4px 11px;white-space:nowrap;
}

/* ── Mouvement réduit : NON NÉGOCIABLE ─────────────────────────────────────
   Le script ne pose jamais l'attribut actif dans ce cas, et retire la copie
   s'il l'avait déjà posée — c'est lui la vraie garantie, parce qu'aucune
   `animation` n'est émise ici et que la coupure globale de patterns.css (l. 71)
   n'aurait donc rien à couper. Ces règles sont la CEINTURE : si l'attribut
   arrivait par un autre chemin, rien ne bougerait, l'accroche reviendrait, le
   fondu s'effacerait et la barre de défilement reparaîtrait — elle est alors la
   seule chose qui dise qu'il reste des témoignages à droite. */
@media (prefers-reduced-motion:reduce){
  .sb-temoignages .sb-temoins,
  .sb-temoignages .sb-temoins[data-sb-temoins-actif]{
    scroll-snap-type:x proximity;scroll-behavior:auto;
    scrollbar-width:auto;-ms-overflow-style:auto;
  }
  .sb-temoignages .sb-temoins::-webkit-scrollbar{display:block;height:8px}
  .sb-temoignages .sb-temoins[data-sb-temoins-actif]:not(:focus-within){
    -webkit-mask:none;mask:none;
  }
  .sb-temoignages .sb-temoins .sb-temoin:hover{transform:none}
}

/* En contraste forcé, les mélanges à `currentColor` sont remplacés par le
   système et la carte perd sa plaque comme son filet : on lui rend un contour
   que le système sait peindre, sinon dix cartes deviennent un seul bloc. */
@media (forced-colors:active){
  .sb-temoignages .sb-temoins .sb-temoin{border-color:CanvasText}
  .sb-temoignages .sb-temoin .sb-temoin__marque{border-color:CanvasText}
}
