/* =============================================================================
   blocks-document.css — LE RESET DE DOCUMENT du site généré
   -----------------------------------------------------------------------------
   Ces règles habillent le DOCUMENT : le corps de la page, et le comportement par
   défaut des médias et des liens. Elles sont sorties de `blocks-base.css` le
   2026-08-11, et la raison est mesurée, pas théorique.

   `blocks-base.css` est importé par `blocks-editor.css`, donc par l'application
   de bureau, pour que les blocs TipTap ressemblent au site. Il emmenait ces
   règles avec lui, dans un document qui n'est pas le sien et où aucune variable
   `--tw-*` n'existe : les replis en dur s'appliquaient donc au CHROME de l'app.

   Mesuré avant le découpage : `body` peint #fafafa / #222 — EN THÈME SOMBRE
   AUSSI — en 17 px, avec la police du site. Invisible, parce que `#root` couvre
   le corps.

   ⚠️ Les deux autres règles ne faisaient AUCUN dégât, et il faut le dire, sinon
   la prochaine personne surestimera le risque : le preflight de Tailwind v4 pose
   déjà `img,svg,video,iframe,… { display:block }` et `a { color:inherit }`. Les
   38 icônes de l'application que j'ai comptées en `display:block` le sont par
   TAILWIND — j'avais mesuré l'effet et présumé la cause. Elles restent en
   `display:block` après le découpage, et c'est normal.

   Le découpage vaut quand même, et pour une raison qui n'est pas le dégât
   d'aujourd'hui : il fait passer la feuille de « inoffensive parce qu'une autre
   feuille dit la même chose » à « inoffensive par construction ». La règle
   suivante ajoutée ici n'aurait eu personne pour la doubler.

   > Une feuille qui habille un DOCUMENT ne doit jamais être importée dans un
   > AUTRE document. Une contre-règle posée après l'import corrige le symptôme
   > et laisse le canal ouvert : la règle suivante fuira pareil, et personne ne
   > saura d'où elle vient.

   `box-sizing` reste dans `blocks-base.css` : il vaut partout où une brique est
   rendue, y compris dans l'éditeur, et Tailwind pose exactement la même chose —
   il ne peut donc rien casser en fuyant.

   ⚠️ Ce fichier ne doit JAMAIS être importé par l'application. Un seul
   consommateur : le site généré, via `copySharedCSS` → `BaseLayout.astro`.
   ============================================================================= */

/* Le site ne charge pas Tailwind : c'est ici, et nulle part ailleurs, qu'il
   reçoit `box-sizing`. Il vivait dans `blocks-base.css` — que l'APPLICATION
   importe pour l'aperçu des blocs — d'où un sélecteur universel dans le graphe
   du chrome, signalé par `verifie-socle`. Le déplacer le rend inaccessible à
   l'app sans rien retirer au site. */
*, *::before, *::after { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--tw-page-bg, #fafafa);
  color: var(--tw-text, #222);
  font-family: var(--tw-font-body, system-ui, sans-serif);
  font-size: var(--tw-size-base, 1.0625rem);
  line-height: var(--tw-leading-body, 1.6);
  -webkit-font-smoothing: antialiased;
}
img, svg, video, iframe { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }

/* « Afficher sur » (#479) — un bloc ou une section masqué sur un type d'écran.
   Les seuils de la grille (`blocks-base.css`) : mobile < 640 px, tablette
   640 à 1023 px, ordinateur ≥ 1024 px. Ici, dans la feuille du SITE : l'éditeur
   importe blocks-base.css, et une règle posée là masquerait le bloc dans le
   canevas selon la largeur de la fenêtre. L'aperçu par appareil, lui, est le
   site : il masque pour de vrai. */
@media (max-width: 639px) { .tw-masquer-mobile { display: none !important; } }
@media (min-width: 640px) and (max-width: 1023px) { .tw-masquer-tablette { display: none !important; } }
@media (min-width: 1024px) { .tw-masquer-ordinateur { display: none !important; } }

/* =============================================================================
   blocks-base.css — LES 12 BRIQUES (couche neutre, DNA-agnostique)
   -----------------------------------------------------------------------------
   C'est la première livraison de P1 (cf DNA-BRICK-DOM-SPEC.md). Chaque propriété
   visuelle = var(--tw-*) + fallback NEUTRE. AUCUN --theme-*, AUCUNE couleur/font/
   radius/shadow en dur ici. Le DNA (dna-c.css) re-pointe les --tw-* + skinne.
   Réutilisable à l'identique par les 15 DNAs.
   Cascade : blocks-base.css → dna-{x}.css → user-overrides. Pas de !important.
   ============================================================================= */

/* ═══ LISIBILITÉ DÉRIVÉE — les deux jetons « auto » (B16, #223) ═════════════
   Décision Fable 5.1 (2026-09-08), déléguée par Yann.

   Un DNA promet que « le contraste fond/texte est garanti par construction ».
   Mesuré sur les 96 combinaisons DNA × mode : 33 couples porteurs de texte
   passaient sous 4,5:1, dont un libellé de bouton à 1,95:1.

   > Une promesse qui n'est pas mesurée est une intention.

   Ces deux jetons calculent une couleur de TEXTE à partir de la couleur qu'elle
   recouvre. Ils sont posés sur `[data-dna]` — le MÊME élément que celui où le
   DNA déclare `--tw-btn-bg` — sans quoi `var()` ne verrait pas sa valeur.

   ⚠️ ILS NE SONT PAS APPLIQUÉS D'OFFICE. Un DNA les adopte en pointant son
   `--tw-btn-text` dessus. Les couples délibérés qui passent déjà — or sur
   marine, crème sur terracotta — gardent leur nuance : les remplacer par du
   noir et blanc uniformiserait 48 identités sur un détail où elles n'ont rien
   fait de mal.

   ⚠️ ET ILS NE SUFFISENT PAS TOUJOURS. Il existe une bande de clarté où NI le
   noir NI le blanc n'atteignent 4,5:1 sur un fond saturé — mesurée à
   L ∈ [0,475 ; 0,635] pour C = 0,30, et large de 0,011 même sur un gris neutre
   (noir dès L = 0,568, blanc jusqu'à L = 0,557 : ils ne se recouvrent PAS).
   Deux fonds de bouton étaient dans cette bande ; ils ont été déplacés à la
   main, en gardant leur teinte et leur saturation. La sonde `contraste-dna`
   refuse tout nouveau fond qui y retomberait.

   Le noir reste à L = 0,12 : à L = 0,17 le pire cas repasse sous 4,5. */
[data-dna]{
  /* ══ LE PIVOT EST LA LUMINANCE WCAG, PAS LA CLARTÉ D'OKLAB (#225) ═══════
     Décision Fable 5.1 (2026-09-09), déléguée par Yann.

     La première version pivotait sur `l`. C'était le défaut lui-même : le L
     d'OKLab est PERCEPTUEL, la luminance de WCAG ne l'est pas. Tourner la
     teinte à L constant — ce que font les curseurs de tonalité — changeait
     donc le contraste, et 67 couples passaient sous 4,5:1 sur le balayage.

     > On ne choisit pas une couleur de texte sur l'échelle qui décrit ce que
     > l'œil PERÇOIT, mais sur celle qui décrit ce que la norme MESURE.

     Y (la luminance relative de WCAG) est exactement le Y de XYZ, et il se lit
     depuis `l c h` par la ligne Y de M1⁻¹ d'OKLab — un polynôme cubique, sans
     décodage gamma. `pow()`, `cos()` et `sin()` existent en CSS (Values 4) et
     sont PLUS ANCIENS que `oklch(from …)` que ces feuilles exigent déjà : le
     plancher de compatibilité ne bouge pas.

     ⚠️ Vérifié : la formule rend la luminance WCAG à 3·10⁻⁵ près, et le pivot
     0,1791 garantit **4,58:1 au pire** sur 4 000 fonds tirés au hasard — c'est
     le point où blanc et noir font le même contraste. Le blanc et le noir sont
     PURS (chroma 0) : le blanc teinté d'hier coûtait 0,08 de marge pour une
     nuance invisible. */
  /* ⚠️ EN OKLAB, PAS EN OKLCH — ET CE N'EST PAS UN GOÛT DE NOTATION.
     La première écriture lisait `l c h` et repassait en cartésien avec
     `cos(h * 1deg)` / `sin(h * 1deg)`. **WebKit ignore l'unité et traite
     l'argument comme des RADIANS** : mesuré le 2026-09-09 dans WKWebView (le
     moteur de Safari sur cette machine), le cosinus de 35,2 degrés y rend
     −0,806 au lieu de 0,817 — la valeur qu'on obtient en radians. Le jeton
     restait valide et rendait du noir ou du blanc PUR : rien ne le signalait,
     sauf 8 fonds sur 300 qui recevaient la mauvaise polarité, jusqu'à 3,77:1.

     `oklab(from …)` donne `a` et `b` directement. Plus de trigonométrie, donc
     plus de dépendance à une primitive que les deux moteurs n'interprètent pas
     pareil — et la formule est plus courte. Vérifié : 4,58:1 au pire sur les
     mêmes 300 fonds, dans Chromium ET dans WebKit.

     > Une formule juste peut être fausse dans un moteur sans jamais devenir
     > invalide. C'est le pire des deux mondes : elle répond, et elle ment. */
  --tw-y-origine: (
      -0.0405801784 * pow(l + 0.3963377774 * a + 0.2158037573 * b, 3)
    +  1.1122568696 * pow(l - 0.1055613458 * a - 0.0638541728 * b, 3)
    -  0.0716766787 * pow(l - 0.0894841775 * a - 1.2914855480 * b, 3)
  );
  --tw-blanc-ou-noir: clamp(0, (0.1791 - var(--tw-y-origine)) * 1000000, 1);
  /* ══ IL N'Y A PAS DE JETON « COULEUR PRÊTE À L'EMPLOI », ET C'EST VOULU ══
     Ce qui suit est une POLARITÉ (0 = noir, 1 = blanc), pas une couleur. Elle
     se consomme DANS un `oklab(from <le fond> var(--tw-sur-fond) 0 0 / 1)`,
     écrit dans le MÊME bloc que le fond dont il parle.

     ⚠️ C'est la seule forme qui marche, et le contraire est un piège muet.
     Une propriété personnalisée est substituée sur l'élément qui la DÉCLARE,
     et c'est le RÉSULTAT qui est hérité. Un jeton « couleur de texte du
     bouton » déclaré ici, sur `[data-dna]`, se fige donc avec le fond de
     bouton de la RACINE ; une palette de section qui repeint `--tw-btn-bg`
     hérite d'une couleur calculée pour un AUTRE fond. Mesuré le 2026-09-09 :
     bouton jaune, texte blanc, 13 constats « INVISIBLE » au gate `dna-smoke`
     — pendant que la sonde statique, qui MODÉLISE le jeton au lieu de le
     résoudre, était verte.

     > Un jeton dérivé n'est pas une valeur, c'est un calcul. Le ranger dans
     > une variable, c'est l'exécuter trop tôt.

     ⚠️ Et JAMAIS sur un fond `transparent` : la syntaxe de couleur relative
     hérite l'ALPHA de l'origine, donc `oklch(from transparent …)` rend un
     texte transparent — invisible, sur un chemin qu'aucune mesure de contraste
     ne signale (elle mesurerait un couple qui n'existe pas). Le `/ 1` final
     ferme cette porte pour un fond seulement SEMI-transparent ; il ne peut
     rien pour un fond entièrement transparent, dont la teinte n'existe pas.
     Un bouton fantôme garde un texte DÉCLARÉ, pas dérivé. */
  --tw-sur-fond: calc(1 * var(--tw-blanc-ou-noir));
  /* Sur-titre : on GARDE la teinte de la marque et on PINCE sa clarté. Un DNA
     clair prend la borne basse, un DNA sombre la borne haute — la polarité est
     connue du DNA, qui a déjà ses deux blocs d'origines.

     ⚠️ DÉRIVÉ DE `--tw-accent`, PAS DE `--theme-primary`, et c'est la garde
     `jetons-legacy` qui me l'a appris en devenant rouge : ce fichier est la
     couche NEUTRE, il ne consomme aucun `--theme-*`. Les 48 DNAs déclarent
     `--tw-accent` dans leur bloc de base — la brique a donc son propre jeton,
     et il vaut `var(--theme-primary)` chez eux. */
  --tw-eyebrow-color-auto-clair: var(--tw-accent);
  --tw-eyebrow-color-auto-sombre:var(--tw-accent);
  /* ⚠️ LE JETON À UTILISER EST CELUI-CI, ET IL SUIT LE MODE TOUT SEUL.
     Ma première version obligeait chaque DNA à choisir `-clair` ou `-sombre`
     dans le bon bloc — six l'ont adopté dans leur bloc de BASE, que le bloc
     sombre HÉRITE : la clarté pincée vers le bas se retrouvait sur un fond
     sombre. Le gate `dna-smoke` l'a vu (aurore/dark, 1,09:1, « INVISIBLE ») ;
     ma sonde statique, non — elle ne résolvait pas ses propres jetons.

     > Un jeton qui oblige l'appelant à connaître le mode finira par être posé
     > dans le mauvais bloc. Celui-ci le sait pour lui. */
  --tw-eyebrow-color-auto: var(--tw-eyebrow-color-auto-clair);
  /* Le LIEN du texte courant : la teinte de la marque, clarté pincée par mode comme
     le sur-titre. L'accent brut sortait sous 4,5:1 sur le fond clair d'une vingtaine de
     DNA (corail de TROPIC 2,6:1, or de COSMOS, terracotta d'ATELIER 2,4:1 — audit
     du 2026-09-28). Un DNA dont l'accent est lisible le déclare lui-même ; la bande
     `bold` le re-pointe sur son texte (plus bas). */
  --tw-prose-link: var(--tw-eyebrow-color-auto);
}
@supports (color: oklch(from red l c h)){
  [data-dna]{
  --tw-eyebrow-color-auto-clair: oklch(from var(--tw-accent) min(l, 0.40) c h);
  --tw-eyebrow-color-auto-sombre:oklch(from var(--tw-accent) max(l, 0.80) c h);
  }
}

[data-dna][data-theme="dark"]{
  --tw-eyebrow-color-auto: var(--tw-eyebrow-color-auto-sombre);
}

/* --- 0. Reset minimal + primitives de layout ----------------------------- */
/* Seul `box-sizing` reste ici : il vaut partout où une brique est rendue —
   éditeur compris — et Tailwind pose exactement la même chose, donc il ne peut
   rien casser en atteignant le chrome de l'application.

   Le reste du reset (`body`, `img/svg/video/iframe`, `a`) est parti dans
   `blocks-document.css` : ce fichier-ci est importé par `blocks-editor.css`,
   donc par l'application, et ces trois règles habillaient un document qui n'est
   pas le leur. Le dégât MESURÉ se limitait au corps (peint clair en thème
   sombre, en 17 px) : le preflight de Tailwind double déjà les deux autres
   règles côté application. Ce n'est pas le dégât qui justifie le découpage,
   c'est qu'une règle d'élément nu ajoutée ici n'aurait personne pour la doubler.

   ⚠️ NE RIEN AJOUTER ICI qui vise un sélecteur d'ÉLÉMENT NU. Tout ce qui n'est
   pas préfixé par une classe fuit dans l'application. */
/* `box-sizing` est parti dans `blocks-document.css` : c'est un sélecteur
   UNIVERSEL, donc du socle, et cette feuille-ci est importée par l'application
   pour l'aperçu des blocs. Dans l'app, Tailwind le pose déjà — il ne manquait
   donc à personne ici, et il manquait au SITE, qui ne charge pas Tailwind.
   Dernier sélecteur de racine de ce fichier : il n'en reste aucun. */

/* La BANDE — setter de palette (cf DNA-BLOCKS-SPEC §1). Ne consomme pas de
   couleur : la palette (dna-c.css [data-palette]) re-pointe les --tw-* contextuels. */
.content-section {
  position: relative;
  background: var(--tw-bg, transparent);
  color: var(--tw-text, inherit);
  padding-block: calc(var(--tw-section-py, 6rem) * var(--tw-section-density, 1));
}
.content-section__container {
  max-width: var(--tw-container, 1200px);
  margin-inline: auto;
  padding-inline: var(--tw-container-pad, clamp(1.5rem, 4vw, 3rem));
}
.content-section[data-container="narrow"] .content-section__container { max-width: 760px; }
.content-section[data-container="wide"]   .content-section__container { max-width: 1400px; }
.content-section[data-container="full"]   .content-section__container { max-width: none; }

/* Rythme vertical des enfants = --tw-gap (remplace les marges StackPattern) */
.tw-stack > * + * { margin-top: var(--tw-gap, 1.5rem); }
/* colonne (columnsBlock) : MÊME rythme vertical que .tw-stack. Sans ça, .tw-lead{margin:0}
   colle le lead au bloc suivant dans la story (manque d'air SOUS le sous-titre, cf #134). */
.column > * + * { margin-top: var(--tw-gap, 1.5rem); }

/* Grille de collection (features/products/posts/team/…) */
.tw-grid {
  display: grid;
  gap: var(--tw-gap, 1.5rem);
  grid-template-columns: repeat(var(--cols, 1), minmax(0, 1fr));
}
@media (min-width: 1024px){ .tw-grid[data-cols-desktop="2"]{--cols:2} .tw-grid[data-cols-desktop="3"]{--cols:3} .tw-grid[data-cols-desktop="4"]{--cols:4} .tw-grid[data-cols-desktop="5"]{--cols:5} .tw-grid[data-cols-desktop="6"]{--cols:6} }
@media (min-width:640px) and (max-width:1023px){ .tw-grid[data-cols-tablet="2"]{--cols:2} .tw-grid[data-cols-tablet="3"]{--cols:3} .tw-grid[data-cols-tablet="4"]{--cols:4} }
@media (max-width:639px){ .tw-grid[data-cols-mobile="2"]{--cols:2} }

/* ============================================================================
   TAILLE DES TITRES (Thème › Formes et rythme, Yann 2026-10-11)
   ============================================================================
   Le réglage est un facteur sur l'ÉCART entre un titre et le texte courant :
     taille = texte + (taille du style − texte) × --tw-heading-scale
   (absent = 1 : le style tel quel). Formule unique : `tailleDeTitreCss`,
   shared/bricks/dna-shape.ts — ce bloc la reprend mot pour mot (garde :
   taille-des-titres-reglage.test.ts).

   ⚠️ POURQUOI DEUX RÈGLES. Chaque DNA pose ses `--tw-size-h1…h6` (et
   `--tw-size-display`) sur l'élément `[data-dna]`. Les redéfinir AU MÊME endroit
   à partir d'eux-mêmes est un cycle (une variable qui se lit elle-même est
   invalide). Alors :
     1. sur `[data-dna]`, on RECOPIE la valeur du style sous un autre nom
        (`--tw-size-h1-dna`) ;
     2. sur ses ENFANTS DIRECTS, on redéfinit `--tw-size-h1` par la formule, à
        partir de la copie héritée. Tout le site (les briques, les titres libres,
        l'échelle de lecture des articles et des fiches produit) hérite de là.
   Les 50 feuilles de style restent intactes, et rien d'autre ne change de nom.
   Un DNA sans `--tw-size-display` (Revue) : la copie prend le H1 du style, le
   repli que les règles utilisaient déjà (sonde `dna-jetons` : pas de lecture
   sans repli).
   Le facteur (`--tw-heading-scale`) et le texte (`--tw-size-base`, « Taille du
   texte » comprise) sont lus sur l'élément `[data-dna]` : éditeur (style en
   ligne), Aperçu et site publié (theme.css) suivent le même chemin. */
[data-dna]{
  --tw-size-h1-dna: var(--tw-size-h1);
  --tw-size-h2-dna: var(--tw-size-h2);
  --tw-size-h3-dna: var(--tw-size-h3);
  --tw-size-h4-dna: var(--tw-size-h4);
  --tw-size-h5-dna: var(--tw-size-h5);
  --tw-size-h6-dna: var(--tw-size-h6);
  --tw-size-display-dna: var(--tw-size-display, var(--tw-size-h1)); /* un DNA sans « display » (Revue) : le H1, repli des règles */
}
[data-dna] > *{
  --tw-size-h1: calc(min(var(--tw-size-h1-dna), var(--tw-size-base, 1rem)) + max(var(--tw-size-h1-dna) - var(--tw-size-base, 1rem), 0px) * var(--tw-heading-scale, 1));
  --tw-size-h2: calc(min(var(--tw-size-h2-dna), var(--tw-size-base, 1rem)) + max(var(--tw-size-h2-dna) - var(--tw-size-base, 1rem), 0px) * var(--tw-heading-scale, 1));
  --tw-size-h3: calc(min(var(--tw-size-h3-dna), var(--tw-size-base, 1rem)) + max(var(--tw-size-h3-dna) - var(--tw-size-base, 1rem), 0px) * var(--tw-heading-scale, 1));
  --tw-size-h4: calc(min(var(--tw-size-h4-dna), var(--tw-size-base, 1rem)) + max(var(--tw-size-h4-dna) - var(--tw-size-base, 1rem), 0px) * var(--tw-heading-scale, 1));
  --tw-size-h5: calc(min(var(--tw-size-h5-dna), var(--tw-size-base, 1rem)) + max(var(--tw-size-h5-dna) - var(--tw-size-base, 1rem), 0px) * var(--tw-heading-scale, 1));
  --tw-size-h6: calc(min(var(--tw-size-h6-dna), var(--tw-size-base, 1rem)) + max(var(--tw-size-h6-dna) - var(--tw-size-base, 1rem), 0px) * var(--tw-heading-scale, 1));
  --tw-size-display: calc(min(var(--tw-size-display-dna), var(--tw-size-base, 1rem)) + max(var(--tw-size-display-dna) - var(--tw-size-base, 1rem), 0px) * var(--tw-heading-scale, 1));
}

/* ============================================================================
   BRIQUE 1 — TYPO (h1-h6, lead, body, meta, eyebrow, numeral, list, quote-em)
   ============================================================================ */
.tw-h1,.tw-h2,.tw-h3,.tw-h4,.tw-h5,.tw-h6{
  font-family: var(--tw-font-heading, Georgia, serif);
  font-weight: var(--tw-weight-heading, 700);
  letter-spacing: var(--tw-tracking-heading, -0.01em);
  line-height: var(--tw-leading-heading, 1.15);
  color: var(--tw-text-heading, inherit);
  text-transform: var(--tw-case-display, none);
  text-shadow: var(--tw-text-shadow, none);
  margin: 0;
}
/* ÉCHELLE DE GRAISSE — dérivée, JAMAIS en dur. h1 = --tw-weight-heading (= la
   graisse de base choisie par l'user) ; chaque niveau descend de --tw-weight-step
   (pas DNA, défaut 0 → graisse uniforme). clamp [400,900] = garde-fou. */
.tw-h1{ font-size: var(--tw-size-h1, clamp(3rem,6.5vw,5.75rem)); line-height: var(--tw-leading-heading-tight, 1.05); }

/* ═══ LE DISPLAY EST UN RÔLE, PAS UN H1 PLUS GROS ═══════════════════════════
   Le titre de héros n'obéit pas à l'échelle des titres : il est plus grand, plus
   serré, souvent plus léger. Le vocabulaire ne savait pas le dire, alors les
   feuilles l'ont contourné — 32 des 46 DNAs écrivaient la MÊME règle, mot pour
   mot :

       [data-dna="x"] .content-section:has(.hero-block) .tw-h1{ font-size … }

   Quand les deux tiers d'un catalogue contournent le système au même endroit, ce
   ne sont pas trente-deux caprices, c'est un jeton manquant. Les voici.

   ⚠️ LES REPLIS SONT COPIÉS SUR LA RÈGLE GÉNÉRIQUE, PAS CHOISIS. Un DNA qui ne
   pose aucun jeton doit rendre au pixel près ce qu'il rendait — d'où
   `--tw-tracking-heading, -0.01em` et non `normal` : ma première écriture mettait
   `normal`, et onze DNAs ont bougé.

   10 feuilles gardent leur règle, chacune pour une propriété qu'aucun jeton
   ne porte : b, i, l, neon, p, q, r, u, v, y — ombre portée, casse forcée, axes variables,
   texte en dégradé, couleur de l'em. Ce sont les jetons SUIVANTS à écrire. */
.content-section:has(.hero-block) .tw-h1{
  font-size:      var(--tw-size-display,     var(--tw-size-h1, clamp(3rem,6.5vw,5.75rem)));
  line-height:    var(--tw-leading-display,  var(--tw-leading-heading-tight, 1.05));
  letter-spacing: var(--tw-tracking-display, var(--tw-tracking-heading, -0.01em));
  font-weight:    var(--tw-weight-display,   var(--tw-weight-heading, 700));
}
/* ⚠️ À CÔTÉ D'UNE IMAGE, LE TITRE N'A QU'UNE COLONNE. La taille display se règle
   sur la FENÊTRE (vw) : pensée pour un héros pleine largeur, elle ne tient pas
   dans une demi-colonne dès qu'un mot est long — « d'information » sortait du
   cadre, coupé à « d'informatior » (page TOMail de lauyan.com, 25/09, #317).
   La colonne de texte devient un conteneur, et le titre se plafonne AUSSI à sa
   largeur : `min()` ne réduit que ce qui déborderait, un titre court garde la
   taille voulue par le DNA. 10cqi ≈ un mot de 14 lettres par ligne. */
.hero-block--layout-text-image-right > .hero-block__text,
.hero-block--layout-text-image-left > .hero-block__text,
.columns-block .column > .hero-block--layout-text-only > .hero-block__text{ container-type: inline-size; }
/* (0,5,0) : bat la règle display des DNAs, `[data-dna] .content-section:has(.hero-block) .tw-h1`
   (0,4,0), qui se charge APRÈS cette feuille. */
/* Même cas quand le héros « texte seul » est posé DANS UNE COLONNE (à côté d'une
   animation, d'une image dans la colonne voisine) : la taille réglée sur la
   fenêtre donnait un titre de pleine page dans une demi-page — « vraiment trop
   gros » (Yann, staging de lauyan.com, 2026-09-28). */
.content-section .hero-block.hero-block--layout-text-image-right > .hero-block__text > .tw-h1,
.content-section .hero-block.hero-block--layout-text-image-left > .hero-block__text > .tw-h1,
.content-section .column > .hero-block.hero-block--layout-text-only > .hero-block__text > .tw-h1{
  font-size: min(var(--tw-size-display, var(--tw-size-h1, clamp(3rem,6.5vw,5.75rem))), 10cqi);
  overflow-wrap: break-word;
}
.tw-h2{ font-size: var(--tw-size-h2, clamp(2rem,3.5vw,3rem)); font-weight: clamp(400, calc(var(--tw-weight-heading,700) - var(--tw-weight-step,0) * 1), 900); }
.tw-h3{ font-size: var(--tw-size-h3, 1.625rem); font-weight: clamp(400, calc(var(--tw-weight-heading,700) - var(--tw-weight-step,0) * 2), 900); }
.tw-h4{ font-size: var(--tw-size-h4, 1.3rem); font-weight: clamp(400, calc(var(--tw-weight-heading,700) - var(--tw-weight-step,0) * 2), 900); }
.tw-h5{ font-size: var(--tw-size-h5, 1.1rem); font-weight: clamp(400, calc(var(--tw-weight-heading,700) - var(--tw-weight-step,0) * 3), 900); }
.tw-h6{ font-size: var(--tw-size-h6, 1rem); font-weight: clamp(400, calc(var(--tw-weight-heading,700) - var(--tw-weight-step,0) * 3), 900); }

/* Interligne : celui du style × le réglage du Thème (`--tw-leading-scale`, absent = 1),
   comme le texte libre (shared/theme/themeElementRules.ts). */
.tw-body{ font-family: var(--tw-font-body, system-ui, sans-serif); font-size: var(--tw-size-base,1.0625rem); line-height: calc(var(--tw-leading-body,1.65) * var(--tw-leading-scale, 1)); color: var(--tw-text, inherit); margin: 0; }
.tw-lead{ font-family: var(--tw-font-body, system-ui, sans-serif); font-size: var(--tw-size-lead,1.25rem); line-height: calc(var(--tw-leading-lead,1.5) * var(--tw-leading-scale, 1)); color: var(--tw-text-muted, inherit); margin: 0; }
.tw-meta{ font-family: var(--tw-font-body, system-ui, sans-serif); font-size: var(--tw-size-meta,0.875rem); color: var(--tw-text-muted, #777); }
.tw-eyebrow{ font-family: var(--tw-font-body, system-ui, sans-serif); font-size: var(--tw-size-eyebrow,0.75rem); font-weight: 500; letter-spacing: var(--tw-tracking-eyebrow,0.16em); text-transform: uppercase; color: var(--tw-eyebrow-color, var(--tw-accent, #888)); display:inline-block; }

/* em mid-phrase = âme de plusieurs DNAs (font-swap accent + couleur).
   `:is(em, i)` : les champs in-place (hero…) produisent <i> via execCommand natif
   AVANT que le commit ne le normalise en <em> (inlineHtml.ts) — on couvre les DEUX
   pour que l'accent s'affiche dès la frappe, pas seulement après le blur. */
.tw-body :is(em, i), .tw-lead :is(em, i), .tw-h1 :is(em, i), .tw-h2 :is(em, i), .tw-h3 :is(em, i), .tw-h4 :is(em, i){
  font-family: var(--tw-font-accent, inherit);
  font-style: var(--tw-em-style, italic);
  color: var(--tw-em-color, inherit);
}
.tw-body :is(em, i), .tw-lead :is(em, i){ font-weight: var(--tw-em-weight, inherit); }
.tw-h1 :is(em, i), .tw-h2 :is(em, i), .tw-h3 :is(em, i), .tw-h4 :is(em, i){ font-weight: inherit; }  /* l'accent suit la graisse du titre (échelle) */
/* TRAITEMENT des mots accentués (em) des TITRES — token-driven, 3 modes, inerte par défaut :
   • COULEUR (défaut)   : --tw-em-color/-fill ; pas de fond ; clip:text.
   • DÉGRADÉ-TEXTE      : --tw-em-gradient:<gradient> + --tw-em-fill:transparent (clip:text).
   • PLAQUE / MARQUEUR  : --tw-em-gradient:linear-gradient(c,c) + --tw-em-clip:border-box +
       --tw-em-bg-size/-pos (bande peinte sur la SEULE hauteur des glyphes → AUCUN débordement
       sur un masthead à interligne serré, cf G #G-accent-overlap) + --tw-em-fill:<contraste>
       + --tw-em-pad. Posé sur `.tw-h1` (hero only) ou sur le DNA. C'est la recette des aperçus
       à plaque (style-G/.accent-mark, style-FORGE/.box…) ENFIN réutilisée par le moteur. */
.tw-h1 :is(em, i), .tw-h2 :is(em, i), .tw-h3 :is(em, i), .tw-h4 :is(em, i){
  background-image: var(--tw-em-gradient, none);
  background-repeat: no-repeat;
  background-size: var(--tw-em-bg-size, auto);
  background-position: var(--tw-em-bg-pos, 0 0);
  -webkit-background-clip: var(--tw-em-clip, text); background-clip: var(--tw-em-clip, text);
  -webkit-text-fill-color: var(--tw-em-fill, var(--tw-em-color, currentColor));
  padding: var(--tw-em-pad, 0);
  /* Défaut SLICE (revue 2026-07-02) : un `clone` inconditionnel faisait redémarrer le
     dégradé-texte à chaque ligne sur un em multi-lignes (régression dna-k/dna-t). Seules
     les DNAs à PLAQUE (G/FORGE) posent --tw-em-decoration-break: clone (plaque par ligne). */
  -webkit-box-decoration-break: var(--tw-em-decoration-break, slice);
  box-decoration-break: var(--tw-em-decoration-break, slice);
}

.tw-numeral{ font-family: var(--tw-price-font, var(--tw-font-heading, inherit)); font-variant-numeric: tabular-nums; }
.tw-numeral[data-affix]{ font-size: .65em; color: var(--tw-text-muted, #888); font-family: var(--tw-font-body, sans-serif); }

.tw-list{ list-style: none; margin: 0; padding: 0; }
.tw-list.tw-list--bullet li{ padding-inline-start: 1.4em; position: relative; }
.tw-list.tw-list--bullet li::before{ content:""; position:absolute; inset-inline-start:.3em; top:.65em; width:.4em; height:.4em; border-radius:50%; background: var(--tw-icon-color, var(--tw-accent, currentColor)); } /* token C → visible sur bold (cf §6) */

.tw-body--cols-2{ column-count: 2; column-gap: var(--tw-gap, 2rem); }
/* Lettrine éditoriale — OFF par défaut, ENTIÈREMENT gardée par des tokens `--tw-dropcap-cap-*`
   que SEUL un DNA éditorial pose (≠ `--tw-dropcap-size/color/font`, que ~40 DNAs définissent
   globalement pour le mécanisme de section `content-section--text-dropcap`). Sans opt-in :
   font/size/float/margin/color héritent ⇒ 1ʳᵉ lettre strictement normale (zéro fuite). Un DNA
   éditorial active via --tw-dropcap-cap-float:left (+ cap-size/cap-color/cap-font). Émise par
   richHome sur le 1er paragraphe de la story (blockStyle:'dropcap' → .tw-dropcap) ; option
   justification du corps via --tw-dropcap-align:justify. Token-driven (cf #118, contrat DNA). */
.tw-dropcap{ text-align: var(--tw-dropcap-align, inherit); hyphens: var(--tw-dropcap-hyphens, manual); -webkit-hyphens: var(--tw-dropcap-hyphens, manual); }
.tw-dropcap::first-letter{ font-family: var(--tw-dropcap-cap-font, inherit); font-size: var(--tw-dropcap-cap-size, 1em); line-height: .8; float: var(--tw-dropcap-cap-float, none); margin: var(--tw-dropcap-cap-margin, 0); color: var(--tw-dropcap-cap-color, inherit); }

/* ============================================================================
   BRIQUE 9 — QUOTE
   ============================================================================ */
.tw-quote{ margin: 0; color: var(--tw-text-heading, inherit); font-family: var(--tw-font-body, inherit); }
.tw-quote p{ font-size: var(--tw-size-lead, 1.25rem); line-height: 1.5; margin: 0; }
.tw-quote cite{ display:block; margin-top: .8em; font-style: normal; color: var(--tw-text-muted, #888); font-size: var(--tw-size-meta, .875rem); }
/* guillemets décoratifs de la brique (DNA-BRICK-DOM-SPEC §9 ; un DNA peut les masquer via content:none) */
.tw-quote p::before{ content: open-quote; }
.tw-quote p::after{ content: close-quote; }

/* ============================================================================
   BRIQUE 2 — SURFACE (carte / panel)
   ============================================================================ */
.tw-surface{
  background: var(--tw-surface, #fff);
  color: var(--tw-on-surface, inherit);
  border: var(--tw-border-width, 1px) solid var(--tw-border, rgba(0,0,0,.1));
  border-radius: var(--tw-radius-card, 16px);
  box-shadow: var(--tw-shadow-card, none);
  margin: 0; /* surface = îlot grille/flex : jamais de marge externe (sécurise l'usage <figure>) */
  padding: var(--tw-card-padding, 1.75rem);
  transition: transform .18s ease, box-shadow .18s ease;
}
.tw-surface[data-surface="bare"]{ background: transparent; border: 0; box-shadow: none; padding: 0; }
.tw-surface[data-surface="raised"]{ background: var(--tw-surface-raised, var(--tw-surface, #fff)); }
/* Une surface MATÉRIALISÉE (card/raised) est un « îlot » : son texte intérieur se RE-LOCALISE
   sur son propre on-surface, sinon une carte claire sur une bande flood (bold) hérite du
   text-muted de la bande (crème) et devient invisible. (bare = transparent → hérite la bande.) */
.tw-surface[data-surface="card"], .tw-surface[data-surface="raised"]{
  --tw-text: var(--tw-on-surface);
  --tw-text-heading: var(--tw-on-surface);
  /* 86 % du texte de la carte, mêlé à SA surface (plus 76 % translucide) : la
     fonction d'un témoignage ou la légende d'une carte tombaient à 3-4:1 sur les
     cartes des bandes colorées (axe-core, audit des 48 DNA, 2026-09-28). */
  --tw-text-muted: color-mix(in oklab, var(--tw-on-surface) 86%, var(--tw-surface, transparent));
}
/* Sur une bande `bold` (flood mi-ton : rouge, magenta, émeraude…), même 86 % ne
   suffit plus : la fonction d'un témoignage, la légende d'une carte ou d'un compte
   à rebours y restaient à 3,3-4,4:1 (axe-core, audit des 48 DNA, 2026-09-28).
   L'atténué y prend le texte plein de la carte ; la hiérarchie tient à la taille. */
[data-palette="bold"] :is(.tw-surface[data-surface="card"], .tw-surface[data-surface="raised"], .cards-block__card, .countdown-block--cards .countdown-block__unit, .feedback-block__item){
  --tw-text-muted: var(--tw-on-surface);
  /* Le PRIX aussi : la couleur de prix de la bande (or, sceau, rouge) est réglée
     pour la bande, pas pour la carte claire posée dessus — 1,5 à 3:1 chez NOUVEAU,
     SUMI, CONSTRUCT (axe-core, 2026-09-28). Dans la carte, il prend son encre ; un
     DNA qui a réglé le prix de ses cartes de bande le déclare et garde la main. */
  --tw-price-color: var(--tw-on-surface);
}
/* accent : bordure COMPLÈTE (pas seulement border-color) → reste visible même sur
   un data-surface="bare" qui met border:0 (ex. plan highlighted en variant minimal). */
/* La carte MISE EN AVANT (tarif « populaire ») : la surface DE LA BANDE teintée
   d'accent — plus `--tw-accent-soft`, teinte globale claire. Sur une bande
   sombre (l'`accent` vert de BAZAR, l'`accent` noir de SUISSE), la carte restait
   pâle sous le texte clair de la bande : prix, nom et avantages invisibles
   (audit des 48 DNA, 2026-09-28). */
.tw-surface--accent{ background: color-mix(in oklab, var(--tw-accent, currentColor) 14%, var(--tw-surface, transparent)); border: var(--tw-border-width, 1px) solid var(--tw-accent, currentColor); }
/* Sa teinte assombrit (ou éclaircit) la carte : le texte ATTÉNUÉ, réglé pour la
   carte nue, y tombait sous 4,5:1 — la période d'un tarif « populaire » à 4,3:1
   chez NORDIQUE et PASTEL sur bande forte (axe-core, 2026-09-28). Il y prend le
   texte plein de la carte ; la hiérarchie tient à la taille. */
.tw-surface--accent .tw-meta{ color: var(--tw-on-surface, var(--tw-text, currentColor)); }
.tw-surface--glass{ background: var(--tw-surface-translucent, var(--tw-surface, #fff)); backdrop-filter: blur(var(--tw-blur, 0)); }
.tw-surface--hover-lift:hover{ transform: translateY(var(--tw-hover-lift, 0)); box-shadow: var(--tw-shadow-card-hover, var(--tw-shadow-card, none)); }

/* ============================================================================
   BRIQUE 3 — MEDIA (image/vidéo/iframe/logo/avatar)
   ============================================================================ */
.tw-media{
  position: relative;
  margin: 0;
  border-radius: var(--tw-radius-media, 12px);
  box-shadow: var(--tw-shadow-media, none);
  overflow: hidden;
  background: var(--tw-media-placeholder, #e9e3d8);
  filter: var(--tw-media-filter, none);   /* p.ex. DNA-N pose sepia() (photos d'époque) ; défaut none */
}
.tw-media > img, .tw-media > video, .tw-media > iframe{ width: 100%; height: 100%; object-fit: cover; }
.tw-media[data-aspect="16:9"]{ aspect-ratio: 16/9; } .tw-media[data-aspect="4:3"]{ aspect-ratio: 4/3; }
.tw-media[data-aspect="1:1"]{ aspect-ratio: 1/1; } .tw-media[data-aspect="3:2"]{ aspect-ratio: 3/2; }
.tw-media[data-aspect="4:5"]{ aspect-ratio: 4/5; } .tw-media[data-aspect="3:4"]{ aspect-ratio: 3/4; }
.tw-media[data-shape="circular"], .tw-media--avatar{ border-radius: 9999px; aspect-ratio: 1/1; }
.tw-media--avatar{ width: var(--avatar-size, 48px); height: var(--avatar-size, 48px); flex: none; }
.tw-media--logo{ background: transparent; box-shadow: none; border-radius: 0; overflow: visible; }
/* HELO 75068 : une image détourée (PNG, SVG) laisse voir ce qu'il y a derrière — le fond
   d'attente du cadre, peint à la couleur du site, se posait en carré derrière un logo rond. */
.tw-media.tw-media--transparent{ --tw-media-placeholder: transparent; background: transparent !important; }
.tw-media--logo > img{ object-fit: contain; }
.tw-media--embed > iframe{ aspect-ratio: 16/9; }
.tw-media__caption{ font-size: var(--tw-size-meta, .875rem); color: var(--tw-text-muted, #888); padding-top: .6rem; }
.tw-media__caption--overlay{ position: absolute; inset-inline: 0; bottom: 0; padding: .8rem; color:#fff; background: var(--tw-overlay, rgba(0,0,0,.4)); }
/* frame presets (skin DNA peut surcharger) */
.tw-media--border{ border: var(--tw-border-width,1px) solid var(--tw-border, rgba(0,0,0,.15)); }
.tw-media--polaroid{ background:#fff; padding: .6rem .6rem 2.2rem; border-radius: var(--tw-radius-card,8px); box-shadow: var(--tw-shadow-media, 0 6px 18px rgba(0,0,0,.15)); }
/* DNA media filter (--tw-media-filter) sur les images PUBLIÉES qui ne sont pas
   enveloppées dans .tw-media : cartes blog/shop, images vedette article/produit,
   et les vignettes du bloc Galerie. Sans ça le filtre N&B (DNA-E) / sépia (DNA-N)
   ne s'appliquait qu'à l'accueil/About (qui utilisent .tw-media), pas au blog /
   boutique / galerie. (L'imageBlock le reçoit déjà via son wrapper .tw-media —
   filtre seul, pas de sizing, pour ne pas recadrer ces images au format natif de
   leur carte. Un photoFilter utilisateur, plus spécifique sur l'img, l'emporte.) */
.blog-card__image,
.product-card__image,
.post-featured-image,
.toweb-product__gallery img,
.gallery-block__item img{ filter: var(--tw-media-filter, none); }

/* ============================================================================
   BRIQUE 4 — BUTTON (rempli/outline/soft)
   ============================================================================ */
.tw-button{
  display: inline-flex; align-items: center; gap: .5em; justify-content: center;
  background: var(--tw-btn-bg, #222);
  color: var(--tw-btn-text, #fff);
  border: var(--tw-border-width,1px) solid var(--tw-btn-border, transparent);
  border-radius: var(--tw-radius-button, 8px);
  box-shadow: var(--tw-shadow-button, none);
  padding: var(--btn-py, .9rem) var(--btn-px, 1.8rem);
  font-family: var(--tw-font-body, sans-serif);
  font-weight: var(--tw-btn-weight, 600);
  letter-spacing: .01em; line-height: 1; white-space: nowrap; cursor: pointer;
  text-decoration: none;
  transition: transform .18s ease, box-shadow .18s ease, background .18s ease, color .18s ease;
  max-width: 100%;
}
/* Un libellé LONG (« RETROUVEZ NOTRE ACTUALITE ET LES NOUVEAUTES SUR FACEBOOK »,
   site client V14 importé) sortait du téléphone en `nowrap`, rogné des deux
   côtés — et sur ORDINATEUR aussi dans une colonne étroite (« Download the free
   edition of TOWeb version 14.10 for Windows », lauyan.com, 24/09). Le bouton ne
   dépasse plus son conteneur et son libellé passe à la ligne, équilibré, s'il ne
   tient pas ; un libellé qui tient n'en voit rien. `flex-shrink: 0` : dans une
   rangée de boutons, c'est la RANGÉE qui passe à la ligne, un bouton ne se tasse
   pas pour y rester. */
.tw-button{ white-space: normal; text-wrap: balance; text-align: center; flex-shrink: 0; }
@media (max-width: 767px){
  .tw-button{ line-height: 1.25; }
}
.tw-button[data-size="small"]{ --btn-py:.55rem; --btn-px:1.15rem; font-size: var(--tw-size-meta,.875rem); }
.tw-button[data-size="large"]{ --btn-py:1.1rem; --btn-px:2.4rem; }
.tw-button[data-variant="outline"]{ background: transparent; color: var(--tw-btn-outline-text, var(--tw-accent, currentColor)); border-color: var(--tw-btn-outline-border, var(--tw-accent, currentColor)); box-shadow: none; }
/* Bouton SECONDAIRE : un voile d'accent TRANSLUCIDE sur la bande, pas la teinte
   globale `--tw-accent-soft` (claire) — son libellé prend le texte de la bande,
   il sortait blanc sur pâle sur toute bande sombre (audit du 2026-09-28). Sur la
   bande claire par défaut, le rendu est celui d'avant, à un souffle près. */
.tw-button[data-variant="secondary"], .tw-button--soft{ background: color-mix(in oklab, var(--tw-accent, currentColor) 15%, transparent); color: var(--tw-text-heading, inherit); box-shadow: none; }
.tw-button:hover{ transform: translateY(var(--tw-btn-hover-lift, -1px)); background: var(--tw-btn-bg-hover, var(--tw-btn-bg, #222)); color: var(--tw-btn-text-hover, var(--tw-btn-text, #fff)); box-shadow: var(--tw-shadow-button-hover, var(--tw-shadow-button, none)); }
/* soft/secondary : garde son fond atténué au survol (sinon le :hover générique impose le fond primaire) */
.tw-button[data-variant="secondary"]:hover, .tw-button--soft:hover{ background: color-mix(in oklab, var(--tw-accent, currentColor) 24%, transparent); color: var(--tw-text-heading, inherit); box-shadow: none; }

/* ============================================================================
   BRIQUE 5 — CTA-LINK (lien souligné)
   ============================================================================ */
.tw-cta-link{
  display: inline-flex; align-items: center; gap: .4em;
  font-family: var(--tw-font-body, sans-serif); font-weight: 500;
  color: var(--tw-cta-color, currentColor);
  text-decoration-line: underline;
  text-decoration-style: var(--tw-cta-underline-style, solid);
  text-decoration-color: var(--tw-cta-underline-color, currentColor);
  text-decoration-thickness: var(--tw-cta-underline-thickness, 1.5px);
  text-underline-offset: var(--tw-cta-underline-offset, .35em);
  transition: text-decoration-color .2s ease, color .2s ease;
}
.tw-cta-link:hover{ text-decoration-color: var(--tw-cta-underline-hover, var(--tw-accent-2, var(--tw-accent, currentColor))); }
.tw-cta-link__arrow{ transition: transform .2s ease; }
.tw-cta-link:hover .tw-cta-link__arrow{ transform: translateX(3px); }

/* Filet de sécurité §6 (la classe « cta-link invisible sur bande bold », attrapée par
   dna-smoke sur c/e/k/l/n/o) : sur la bande `bold` (flood/sombre, texte flippé en on-accent),
   AUCUN DNA ne re-pointe --tw-cta-color → le lien-CTA garde la couleur du root (≈ le flood) =
   invisible. On le re-pointe ici par DÉFAUT sur on-accent (toujours lisible sur un flood) ;
   un DNA peut surcharger via `[data-dna][data-palette="bold"]` (spécificité supérieure). */
/* Un lien DANS un titre a la couleur du titre (Shopify, Squarespace) : l'accent y
   était illisible sur la bande `accent` de rainbowio, dont chaque titre est un lien
   vers sa rubrique. `color: currentColor` vaut `inherit`. */
:is(h1, h2, h3, h4, h5, h6) a{ --tw-prose-link: currentColor; }
/* Un DNA sombre IMPOSE son mode (`themeImposeParLeDna`) : le bouton clair/sombre
   de l'en-tête n'a plus rien à basculer. Posé sur <html> au site, sur le canevas
   dans l'éditeur. */
[data-theme-impose] .site-header-block__theme{ display: none; }
/* Le texte ATTÉNUÉ d'une bande décalée (`subtle`, `accent`) : le gris du DNA est
   réglé pour le fond de PAGE (la garde `contraste-dna` le vérifie là), et la bande
   est plus foncée ou teintée — le chapeau, les méta et les chiffres de liste
   d'ATELIER tombaient à 3,4:1 sur son sable (axe-core sur la démo, 2026-09-28).
   Ici il se calcule SUR la bande : le texte de la bande, mêlé à son fond, à
   78 % (4,5:1 dès que le texte fait 7:1). Un DNA qui a réglé l'atténué de sa
   bande le déclare dans `[data-dna] [data-palette]` et garde la main. */
:is([data-palette="subtle"], [data-palette="accent"]){
  --tw-text-muted: color-mix(in oklab, var(--tw-text, currentColor) 78%, var(--tw-bg, transparent));
}
[data-palette="bold"]{
  /* Même classe pour un lien de TEXTE (un titre-lien de bande colorée) : il prenait
     --tw-accent, soit le flood. Un DNA qui a un accent lisible sur sa bande le dit
     par `[data-dna][data-palette="bold"]{ --tw-prose-link: … }`. */
  --tw-prose-link: var(--tw-text, var(--tw-on-accent, currentColor));
  --tw-cta-color: var(--tw-text, var(--tw-on-accent, currentColor));
  /* Le fond ATTÉNUÉ (bouton secondaire, encadrés) suit la bande : `--tw-accent-soft`
     est global, donc la teinte claire de la racine ; posée sous le texte clair de la
     bande, elle rendait « Learn more » ou « Choose » blanc sur pâle (audit des 48
     DNA, 2026-09-28). Un DNA qui a sa teinte de bande la déclare. */
  --tw-accent-soft: color-mix(in oklab, var(--tw-text, #fff) 16%, transparent);
  /* Le sur-titre d'une bande flood : l'accent y est le flood, ou sa variante
     « claire » calculée pour la PAGE (sombre, donc) — bleu nuit sur bleu chez
     BRUTAL, violet sur violet chez LIQUID (audit des 48 DNA). Un DNA qui a une
     couleur lisible sur sa bande la déclare dans `[data-dna] [data-palette="bold"]`. */
  --tw-eyebrow-color: var(--tw-text, currentColor);
  /* …et la variante « auto », que plusieurs DNA recopient dans leur bande `bold` :
     résolue à la RACINE, elle y valait la couleur calculée pour la page. Posée
     ici, sur la bande, elle y vaut son texte. */
  --tw-eyebrow-color-auto: var(--tw-text, currentColor);
  --tw-cta-underline-color: var(--tw-text, var(--tw-on-accent, currentColor));
}

/* Lien de NAVIGATION (header / menu / footer) — distinct du cta-link.
   PAS de soulignement permanent (le permanent est réservé au cta-link inline
   "action →"). Sobre par défaut ; le DNA décide l'effet de SURVOL (skin) —
   p.ex. DNA-C met son ondulation au hover seulement (sinon = correcteur ortho). */
.tw-nav-link{
  display:inline-flex; align-items:center; gap:.4em;
  font-family: var(--tw-font-body, sans-serif); font-weight: 500;
  color: var(--tw-nav-color, var(--tw-text, currentColor));
  text-decoration: none;
  transition: color .18s ease, text-decoration-color .18s ease;
}
.tw-nav-link:hover, .tw-nav-link[aria-current="page"], .tw-nav-link--active{
  color: var(--tw-nav-color-hover, var(--tw-accent, currentColor));
}

/* ============================================================================
   BRIQUE 6 — BADGE / TAG
   ============================================================================ */
.tw-badge, .tw-tag{
  display: inline-flex; align-items: center; gap: .35em;
  background: var(--tw-badge-bg, rgba(0,0,0,.06));
  color: var(--tw-badge-text, inherit);
  border: var(--tw-border-width, 1px) solid var(--tw-badge-border, transparent);
  border-radius: var(--tw-radius-badge, 999px);
  padding: .25em .7em;
  font-family: var(--tw-font-body, sans-serif);
  font-size: var(--tw-size-meta, .8rem); font-weight: 500; line-height: 1;
}
.tw-badge--chip{ cursor: pointer; }
.tw-badge--status::before{ content:""; width:.5em; height:.5em; border-radius:50%; background: var(--tw-accent, currentColor); }

/* ============================================================================
   BRIQUE 7 — PRICE + STICKER
   ============================================================================ */
.tw-price{ color: var(--tw-price-color, inherit); font-family: var(--tw-price-font, inherit); font-weight: 700; }
.tw-price--sale{ color: var(--tw-accent, inherit); }
.tw-price__compare{ text-decoration: line-through; color: var(--tw-text-muted, #999); font-weight: 400; margin-left: .4em; }
/* sticker : caché par défaut, le DNA l'affiche */
.tw-price-sticker{ display: none; }

/* ============================================================================
   BRIQUE 8 — RULE (divider)
   ============================================================================ */
.tw-rule{ display: flex; align-items: center; justify-content: center; gap: .6em; padding-block: var(--tw-rule-space, 1rem); }
.tw-rule--left{ justify-content: flex-start; } .tw-rule--right{ justify-content: flex-end; }
.tw-rule__line{ flex: 1 1 auto; border: 0; border-top: var(--rule-th, 1px) solid var(--tw-rule-strong, var(--tw-rule, rgba(0,0,0,.2))); }
.tw-rule--w-75 .tw-rule__line{ max-width: 75%; } .tw-rule--w-50 .tw-rule__line{ max-width: 50%; } .tw-rule--w-25 .tw-rule__line{ max-width: 25%; }
.tw-rule--thin .tw-rule__line{ --rule-th: 1px; } .tw-rule--medium .tw-rule__line{ --rule-th: 2px; }
.tw-rule--thick .tw-rule__line{ --rule-th: 4px; border-color: var(--tw-rule-strong, var(--tw-rule, currentColor)); }
.tw-rule[data-variant="dashed"] .tw-rule__line{ border-top-style: dashed; }
.tw-rule[data-variant="dotted"] .tw-rule__line{ border-top-style: dotted; }
.tw-rule[data-variant="double"] .tw-rule__line{ border-top-style: double; border-top-width: 3px; }
/* variantes dégradées : la ligne devient un fond (les bords s'estompent) */
.tw-rule[data-variant="gradient"] .tw-rule__line{ border: 0; height: var(--rule-th, 2px); background: linear-gradient(90deg, transparent, var(--tw-rule-strong, var(--tw-accent, currentColor)), transparent); }
.tw-rule[data-variant="fade"] .tw-rule__line{ border: 0; height: var(--rule-th, 1px); background: linear-gradient(90deg, transparent, var(--tw-rule, currentColor), transparent); }
/* ornament : glyphe centré (✦) entre deux traits — couleur = token accent/glyph DNA. */
.tw-rule__glyph{ flex: none; color: var(--tw-rule-glyph-color, var(--tw-accent, currentColor)); font-size: 1.15em; line-height: 1; }
/* espacement vertical (la valeur par défaut = medium = 1rem sur .tw-rule) */
.tw-rule[data-spacing="small"]{ --tw-rule-space: .5rem; }
.tw-rule[data-spacing="large"]{ --tw-rule-space: 2.5rem; }
.tw-rule__glyph{ flex: none; color: var(--tw-rule-glyph-color, var(--tw-accent, currentColor)); font-size: 1.1em; }
/* ornement : flourish SVG porté par le DNA */
.tw-rule[data-variant="ornament"] .tw-rule__line{ display: none; }

/* ============================================================================
   BRIQUE 11 — ICON
   ============================================================================ */
.tw-icon{ display: inline-flex; align-items: center; justify-content: center; width: var(--tw-icon-size, 1.5em); height: var(--tw-icon-size, 1.5em); color: var(--tw-icon-color, var(--tw-accent, currentColor)); }
/* Inline-SVG icons (social/team links, brand-coloured icons) read --tw-icon-color
   DIRECTLY into `fill` — NOT via the svg's inherited `color`/`currentColor`. The
   currentColor chain is fragile on <a> wrappers (any `.content-section a`/`*` rule
   that touches the svg's `color` silently broke the fill → social icons stuck on
   the accent regardless of the palette re-point or the colour-style override). By
   reading the brick's own token here, the icon is legible on every palette (cream
   on a `bold` flood) and the Brand/Mono/White/Black styles (which set
   --tw-icon-color) work. Mask icons ([data-glyph]/[data-icon-svg]) have no child
   <svg> and keep their currentColor mask below. */
.tw-icon > svg{ width: 100%; height: 100%; fill: var(--tw-icon-color, var(--tw-accent, currentColor)); }
/* HELO 75087 : site web, e-mail et téléphone sont dessinés AU TRAIT (`fill="none"`,
   `stroke`). La règle ci-dessus l'emporte sur l'attribut : le globe devenait un
   disque plein. Ceux-là gardent un fond vide, et leur trait lit le même jeton. */
.tw-icon > svg[fill="none"]{ fill: none; stroke: var(--tw-icon-color, var(--tw-accent, currentColor)); }
/* mask-span = forme canonique de l'icône (DNA-BRICK-DOM-SPEC §11) : data-glyph =
   glyphe UI nommé ; data-icon-svg = SVG arbitraire (le markup pose --glyph = le
   data-URI). Les deux se peignent en currentColor → l'accent/DNA s'applique. */
.tw-icon[data-glyph], .tw-icon[data-icon-svg]{ background: currentColor; -webkit-mask: var(--glyph) center/contain no-repeat; mask: var(--glyph) center/contain no-repeat; }
/* well = pastille autour de l'icône : on GARDE la taille définie (--tw-icon-size)
   et on étend le padding vers l'extérieur (content-box). width/height:auto
   cassait les <svg> en width:100% (pas de référence → ballonnent). */
.tw-icon--well{ background: var(--tw-accent-soft, rgba(0,0,0,.06)); border-radius: var(--tw-radius-button, 10px); padding: .6em; box-sizing: content-box; }

/* ============================================================================
   BRIQUE 12 — FIELD (input/textarea/select)
   ============================================================================ */
.tw-field{
  width: 100%;
  background: var(--tw-field-bg, var(--tw-surface, #fff));
  color: var(--tw-on-surface, inherit);
  border: var(--tw-border-width, 1px) solid var(--tw-field-border, var(--tw-border, rgba(0,0,0,.18)));
  border-radius: var(--tw-radius-field, var(--tw-radius-button, 8px));
  padding: .7rem .9rem;
  font: inherit;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.tw-field::placeholder{ color: var(--tw-text-muted, #999); }
.tw-field:focus{ outline: none; border-color: var(--tw-accent, #888); box-shadow: 0 0 0 3px var(--tw-accent-soft, rgba(0,0,0,.08)); }
.tw-field--area{ min-height: 6em; resize: vertical; }

/* ============================================================================
   BRIQUE 10 — CODE (toujours sombre — pilotée uniquement par --tw-code-*)
   ============================================================================ */
.tw-code{
  background: var(--tw-code-bg, #1e1e1e);
  color: var(--tw-code-text, #e6e6e6);
  border-radius: var(--tw-radius-card, 12px);
  font-family: var(--tw-font-mono, ui-monospace, monospace);
  overflow: hidden;
}
.tw-code__chrome{ display: flex; align-items: center; gap: .6em; padding: .6em .9em; border-bottom: 1px solid var(--tw-code-chrome-rule, rgba(255,255,255,.1)); }
.tw-code__dots{ display:inline-flex; gap:.4em; }
.tw-code__dots::before, .tw-code__dots::after{ content:""; width:.7em; height:.7em; border-radius:50%; background: rgba(255,255,255,.25); }
.tw-code__lang{ font-size:.7rem; text-transform: uppercase; letter-spacing:.1em; color: var(--tw-code-prompt, #9cdcfe); }
.tw-code__file{ font-size:.75rem; color: rgba(255,255,255,.55); }
.tw-code__copy{ margin-left:auto; background:none; border:0; color: rgba(255,255,255,.5); cursor:pointer; }
.tw-code__pre{ margin:0; padding: 1rem 1.1rem; overflow-x:auto; font-size:.85rem; line-height:1.6; }
.code-token--keyword{ color: var(--tw-code-keyword, #c586c0); }
.code-token--string{ color: var(--tw-code-string, #ce9178); }
.code-token--comment{ color: var(--tw-code-comment, #6a9955); font-style: italic; }
.code-token--number{ color: var(--tw-code-number, #b5cea8); }
.code-token--function{ color: var(--tw-code-function, #dcdcaa); }

/* ============================================================================
   AUX — backdrop (atmosphère), rating (étoiles)
   ============================================================================ */
.tw-backdrop{ position: fixed; inset: 0; z-index: -1; background: var(--tw-backdrop, none); pointer-events: none; }
.tw-backdrop--section{ position: absolute; inset: 0; z-index: 0; background: var(--tw-section-backdrop, none); pointer-events: none; }
/* The section that owns a backdrop must be a positioning context, and its content
   must sit above the (z-index:0) atmosphere layer. Container class differs editor
   (.content-section__container) vs publish (.toweb-container). DNA-agnostic: any DNA
   that sets --tw-section-backdrop gets the atmosphere; none = the div paints nothing. */
.content-section:has(> .tw-backdrop--section){ position: relative; }
.content-section:has(> .tw-backdrop--section) > :is(.content-section__container, .toweb-container){ position: relative; z-index: 1; }
/* A user media background (image/video) supersedes the DNA atmosphere — applies to ALL DNAs. */
.content-section--has-bg-image > .tw-backdrop--section,
.content-section--has-bg-video > .tw-backdrop--section{ display: none; }

/* AMBIANCES ANIMÉES (`data-atmosphere`, choisies par section) — le fond VIVANT
   des sites premium : une aurore qui dérive, une lueur qui respire, une grille
   ou une trame qui s'estompe vers les bords. Tout vient de la palette de la
   bande (--tw-accent, --tw-text, --tw-bg) : l'ambiance suit le DNA, la palette
   de la section et le mode sombre, sans une couleur en dur. CSS pur, animé par
   `transform`/`opacity` seulement (composité, sans repeindre), figé quand le
   visiteur demande moins de mouvement. Elle REMPLACE l'atmosphère du DNA dans
   cette section ; une image ou une vidéo de fond la remplace à son tour
   (règle ci-dessus). */
.content-section[data-atmosphere] > .tw-backdrop--section{
  overflow: hidden;
  background: none;
  --atm-a: color-mix(in oklab, var(--tw-accent, #6366f1) 42%, transparent);
  --atm-b: color-mix(in oklab, var(--tw-accent, #6366f1) 26%, var(--tw-text, #111) 6%);
}
@supports (color: oklch(from red l c h)){
  .content-section[data-atmosphere] > .tw-backdrop--section{
    --atm-b: var(--tw-accent, #6366f1);
  }
  @supports (color: oklch(from red l c h)){
    .content-section[data-atmosphere] > .tw-backdrop--section{
    --atm-b: oklch(from var(--tw-accent, #6366f1) l c calc(h + 55) / 0.34);
    }
  }
}
.content-section[data-atmosphere] > .tw-backdrop--section::before,
.content-section[data-atmosphere] > .tw-backdrop--section::after{
  content: ""; position: absolute; pointer-events: none;
}
/* Mise en pause par le visiteur (commande de la page, pause-animations.ts,
   WCAG 2.2.2) : l'ambiance s'arrête là où elle en est. Plus spécifique que les
   règles qui animent (l'attribut en plus) : `animation` en raccourci y remet
   `animation-play-state` à `running`. */
.content-section[data-atmosphere][data-tw-pause="on"] > .tw-backdrop--section::before,
.content-section[data-atmosphere][data-tw-pause="on"] > .tw-backdrop--section::after{ animation-play-state: paused; }
/* Aurore : deux nappes de couleur qui dérivent lentement, en sens contraires. */
.content-section[data-atmosphere="aurora"] > .tw-backdrop--section::before{
  inset: -35% -20%;
  background:
    radial-gradient(38% 42% at 22% 30%, var(--atm-a), transparent 70%),
    radial-gradient(34% 40% at 78% 62%, var(--atm-b), transparent 70%);
  animation: tw-atm-derive 22s ease-in-out infinite alternate;
}
.content-section[data-atmosphere="aurora"] > .tw-backdrop--section::after{
  inset: -30% -25%;
  background: radial-gradient(30% 36% at 60% 20%, var(--atm-b), transparent 70%);
  animation: tw-atm-derive 28s ease-in-out -9s infinite alternate-reverse;
  opacity: .8;
}
/* Lueur : un halo de la couleur d'accent au-dessus du contenu, qui respire. */
.content-section[data-atmosphere="glow"] > .tw-backdrop--section::before{
  left: 50%; top: -30%; width: min(1100px, 130%); aspect-ratio: 2 / 1;
  translate: -50% 0;
  background: radial-gradient(closest-side, var(--atm-a), transparent);
  animation: tw-atm-respire 9s ease-in-out infinite alternate;
}
/* Grille et trame : un motif fin, estompé vers les bords, qui glisse à peine. */
.content-section[data-atmosphere="grid"] > .tw-backdrop--section::before,
.content-section[data-atmosphere="dots"] > .tw-backdrop--section::before{
  inset: -64px 0 0 0;
  --atm-trait: color-mix(in oklab, var(--tw-text, #111) 9%, transparent);
  -webkit-mask-image: radial-gradient(ellipse 75% 70% at 50% 35%, #000 30%, transparent 100%);
          mask-image: radial-gradient(ellipse 75% 70% at 50% 35%, #000 30%, transparent 100%);
  animation: tw-atm-glisse 14s linear infinite;
}
.content-section[data-atmosphere="grid"] > .tw-backdrop--section::before{
  background:
    linear-gradient(var(--atm-trait) 1px, transparent 1px) 0 0 / 64px 64px,
    linear-gradient(90deg, var(--atm-trait) 1px, transparent 1px) 0 0 / 64px 64px;
}
.content-section[data-atmosphere="dots"] > .tw-backdrop--section::before{
  --atm-trait: color-mix(in oklab, var(--tw-text, #111) 16%, transparent);
  background: radial-gradient(var(--atm-trait) 1.2px, transparent 1.6px) 0 0 / 22px 22px;
  animation-duration: 8s;
}
.content-section[data-atmosphere="grid"] > .tw-backdrop--section::after,
.content-section[data-atmosphere="dots"] > .tw-backdrop--section::after{
  left: 50%; top: -20%; width: min(900px, 110%); aspect-ratio: 2 / 1; translate: -50% 0;
  background: radial-gradient(closest-side, var(--atm-a), transparent);
  opacity: .55;
}
@keyframes tw-atm-derive{
  0%   { transform: translate3d(-4%, -2%, 0) rotate(0deg) scale(1); }
  100% { transform: translate3d(5%, 4%, 0) rotate(8deg) scale(1.12); }
}
@keyframes tw-atm-respire{
  0%   { opacity: .65; transform: scale(.94); }
  100% { opacity: 1;   transform: scale(1.06); }
}
/* Glisse d'exactement UNE maille : la boucle est invisible. */
@keyframes tw-atm-glisse{
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(0, 64px, 0); }
}
.content-section[data-atmosphere="dots"] > .tw-backdrop--section::before{ animation-name: tw-atm-glisse-trame; }
@keyframes tw-atm-glisse-trame{
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(0, 22px, 0); }
}
@media (prefers-reduced-motion: reduce){
  .content-section[data-atmosphere] > .tw-backdrop--section::before,
  .content-section[data-atmosphere] > .tw-backdrop--section::after{ animation: none; }
}
[data-animations="off"] .content-section[data-atmosphere] > .tw-backdrop--section::before,
[data-animations="off"] .content-section[data-atmosphere] > .tw-backdrop--section::after{ animation: none; }
/* ÉTOILES DE NOTATION — un élément graphique porteur de sens : 3:1 contre le fond
   (WCAG 1.4.11). Audit du 2026-09-28 : l'or des DNA (AURORE #f7b733, 1,8:1 sur
   blanc) et les étoiles vides en `--tw-rating-off` (filet, 1,2 à 2,9:1) passaient
   sous le seuil dans une vingtaine de DNA. Sans toucher à la couleur du DNA :
   - l'étoile PLEINE garde sa teinte et reçoit un CONTOUR, la même teinte mêlée à
     l'encre du texte — lisible sur clair comme sur sombre (le contour d'or foncé
     des étoiles d'Amazon ou de Google) ;
   - l'étoile VIDE est un contour à l'encre atténuée (`--tw-text-muted`, que chaque
     DNA et chaque palette tiennent à 4,5:1). Plein ou vide se lit par la FORME, pas
     seulement par la couleur (1.4.1). `--tw-rating-off` n'est plus lu ici. */
.tw-rating{ display:inline-flex; gap:.1em; color: var(--tw-rating-on, var(--tw-accent, #e0a030)); letter-spacing:.05em; }
.tw-rating > span,
.feedback-block__item-star.is-filled{
  -webkit-text-stroke: .07em color-mix(in oklab, var(--tw-rating-on, var(--tw-accent, #e0a030)) 30%, var(--tw-text, #1a1a1a));
  paint-order: stroke fill;
}
.tw-rating > [data-empty]{ color: var(--tw-text-muted, currentColor); -webkit-text-stroke: 0; }

/* ============================================================================
   LAYOUT DE BLOCS (DNA-agnostique — le bloc porte le layout, pas la couleur)
   ============================================================================ */
.hero-block{ display: grid; gap: var(--tw-gap, 3rem); align-items: center; }
.hero-block--split{ grid-template-columns: 7fr 5fr; }
.hero-block__text{ display:flex; flex-direction:column; min-width:0; }
/* Masthead → lead : .tw-lead{margin:0} (brique typo) ANNULE le rythme .tw-stack
   sur cette paire → le titre et le sous-titre se touchent (gap réel = 0). Avec un
   masthead à line-height serré + un em italique à jambages (DNA-D : leading .95,
   « pièces choisies »), les descendantes mordent la ligne de lead. On rétablit un
   gap réel, robuste aux leadings serrés : le rythme propre du DNA (--tw-gap), avec
   un plancher em (suit la taille du lead) si un DNA réduit son gap. (0,2,0) bat
   .tw-lead{margin:0} (0,1,0). DNA-agnostique, pur layout. */
.hero-block__text.tw-stack > .tw-lead{ margin-top: max(var(--tw-gap, 1.5rem), 0.5em); }
.hero-block__media{ min-width:0; }
.hero-block__media > .tw-media, .hero-block__media img{ display:block; width:100%; height:auto; }
.hero-block__ctas{ display:flex; gap: var(--tw-gap, 1.5rem); align-items:center; flex-wrap:wrap; margin-top: 1.5rem; }
@media (max-width: 880px){ .hero-block--split{ grid-template-columns: 1fr; } }
/* layout-variants (contentLayout TOWeb — DNA-BLOCKS-SPEC §3.3) : slot layout pur. */
.hero-block--layout-text-only{ grid-template-columns: 1fr; }
.hero-block--layout-text-image-right{ grid-template-columns: 1.1fr 1fr; }
.hero-block--layout-text-image-left{ grid-template-columns: 1fr 1.1fr; }
.hero-block--layout-text-image-left .hero-block__text{ grid-column: 2; }
.hero-block--layout-text-image-left .hero-block__media{ grid-column: 1; grid-row: 1; }
/* text-image-background : la photo passe en FOND plein-cadre, le texte vit dans une
   CARTE = la recette de surface du DNA (IDENTIQUE à .tw-surface[data-surface="card"]).
   → AUCUNE valeur de style en dur : fond / bordure / rayon / ombre / padding + couleurs
   de texte viennent TOUS des tokens DNA (--tw-surface, --tw-border(-width), --tw-radius-card,
   --tw-shadow-card, --tw-card-padding, --tw-on-surface), eux-mêmes dérivés des variables
   utilisateur. Donc chaque DNA personnalise la carte du hero comme ses autres cartes,
   sans toucher aux 12 DNAs. Les libellés/accents/boutons gardent leurs couleurs natives →
   lisibles sur N'IMPORTE quel DNA. La photo reste audacieuse autour de la carte ; son rendu
   est gouverné par le DNA via --tw-media-filter (pas de voile en dur). Seule la GÉOMÉTRIE
   (position, grille, z-index, min-height, max-width, object-fit, justify-self) reste
   littérale = structure du bloc, pas un style.
   Scopé via :has(> .hero-block__media) : sans image, retombe sur un hero texte.
   Mobile (<=760px) : on dé-empile (texte sur la surface de section PUIS photo en bannière). */
.hero-block--layout-text-image-background{ grid-template-columns: 1fr; }
/* PLEINE LARGEUR (décision Yann 2026-08-06). Une photo de hero contenue dans la colonne
   de texte se lit comme une vignette encadrée, pas comme une ouverture — c'est le seul
   endroit du site où l'image doit filer bord à bord. On neutralise donc la contrainte
   du CONTENEUR pour ce layout uniquement, et le texte reprend la largeur de contenu
   sur sa carte (voir `.hero-block__text` plus bas).

   Pas de `width: 100vw; margin-inline: calc(50% - 50vw)` : `100vw` INCLUT la barre de
   défilement verticale, donc cette recette classique déborde de sa largeur sur desktop
   et fait apparaître une barre horizontale. On laisse plutôt le conteneur cesser de
   contraindre — aucune unité `vw`, donc rien à compenser.

   Sélecteur préfixé `.toweb-section` : le thème généré émet `.toweb-section
   .toweb-container { max-width: … }` (0,2,0) APRÈS cette feuille ; à spécificité égale
   c'est lui qui gagnerait. Le préfixe porte la règle au-dessus.

   `:has()` NON IMBRIQUÉ : `:has(> .hero-block--…:has(> .hero-block__media))` est invalide
   et fait jeter la RÈGLE ENTIÈRE par le navigateur — silencieusement. La sélection se fait
   donc en une seule passe, avec le chemin complet à l'intérieur du `:has()`.

   Bornée à >760px : en dessous, le layout se dé-empile (texte PUIS photo en bannière) et
   le conteneur doit garder sa gouttière — sans cette borne, le texte du hero viendrait
   coller aux deux bords de l'écran sur téléphone. */
@media (min-width: 761px){
  .toweb-section .toweb-container:has(> .hero-block--layout-text-image-background > .hero-block__media){
    max-width: none;
    padding-inline: 0;
  }
  /* Et le visuel colle sous le menu (décision Yann 2026-08-08). Une image qui déborde sur
     les côtés mais flotte sous 6rem de fond clair a l'air à moitié finie : l'ouverture de
     page doit occuper le haut, pas y flotter.
     `:first-child` : SEULEMENT quand ce hero ouvre la page. Un même bloc placé en milieu de
     page a besoin de son espace au-dessus, sinon il vient buter contre la section
     précédente. Le padding du BAS reste dans les deux cas — c'est lui qui sépare le hero
     de la suite. */
  /* ⚠️ SAUF QUAND LE MENU FLOTTE. « Coller sous le menu » suppose une BARRE sous
     laquelle se glisser. DNA-T (Aurora Glass) n'en a pas : son menu est une
     pilule de verre à marges et coins ronds, posée sur le fond de page. Mesuré
     le 2026-08-25 : bas de la pilule y=70, haut du hero y=70 — elles se touchent,
     et l'ombre portée de la pilule tombe sur la carte. Sur les 45 autres DNAs le
     menu est une barre pleine largeur et la valeur 0 reste la bonne.

     > Un objet qui flotte a besoin d'air dessous. Une barre, non. */
  .toweb-section.content-section:first-child:has(.hero-block--layout-text-image-background > .hero-block__media){
    padding-top: var(--tw-hero-flush-top, 0px);
  }
  /* ⚠️ UN PLANCHER D'AIR SOUS LE MENU, ET IL EST MESURÉ — PAS CHOISI.
     Depuis que la section colle au menu, la seule séparation restante est le
     `padding-block` du hero, qui suit `--tw-gap` : il varie du simple au triple
     selon le DNA. Relevé le 2026-08-25 sur dix DNAs, écart menu → carte du hero :

         t    0 px      a   20 px      cosmos 38   c/p/e 40
         couture 48     solar 53       aqua  57    u    65

     Zéro et vingt ne sont pas des choix de rythme, ce sont des accidents : la
     carte vient buter sous la barre. Le plancher vaut 2.5rem = 40 px, la valeur
     que quatre DNAs atteignent déjà d'eux-mêmes — il ne touche donc QUE les deux
     accidents et ne redessine le rythme d'aucun autre.

     Borné à >760px comme la règle du dessus : sous cette largeur la section
     garde son padding normal, un plancher s'y ajouterait au lieu de remplacer. */
  .toweb-section.content-section:first-child .hero-block--layout-text-image-background:has(> .hero-block__media){
    padding-block-start: max(var(--tw-gap, 1.5rem), 2.5rem);
  }
}
.hero-block--layout-text-image-background:has(> .hero-block__media){
  position: relative; isolation: isolate;
  min-height: clamp(24rem, 58vh, 38rem);
  display: grid; align-items: center;
  /* La photo file bord à bord, mais la CARTE doit rester alignée sur la colonne de
     contenu du reste de la page — sinon elle vient coller au bord de l'écran. On rend
     donc au bloc les gouttières que le conteneur ne pose plus : au moins la gouttière
     habituelle, et davantage dès que la fenêtre dépasse la largeur de contenu.
     `--tw-content-max` / `--tw-content-pad` sont émis par le thème sur le conteneur
     (cf. generateGlobalCss) : ce sont les valeurs RÉELLEMENT appliquées au site. */
  padding-block: var(--tw-gap, 1.5rem);
  padding-inline: max(
    var(--tw-content-pad, 2rem),
    calc((100% - var(--tw-content-max, 1200px)) / 2 + var(--tw-content-pad, 2rem))
  );
  /* Plus d'arrondi : un visuel bord à bord dont les angles sont rognés laisse quatre
     encoches de fond sur les côtés de la page. */
  border-radius: 0;
  overflow: hidden;
}
.hero-block--layout-text-image-background:has(> .hero-block__media) > .hero-block__media{
  position: absolute; inset: 0; z-index: 0; margin: 0;
}
.hero-block--layout-text-image-background:has(> .hero-block__media) > .hero-block__media > .tw-media,
.hero-block--layout-text-image-background:has(> .hero-block__media) > .hero-block__media img{
  width: 100%; height: 100%; object-fit: cover;
  /* ⚠️ ET L'ARRONDI VA JUSQU'À L'IMAGE — le `border-radius: 0` posé plus haut
     s'arrêtait au BLOC. Or la balise porte la classe `.tw-media`, qui applique
     `--tw-radius-media` : les quatre encoches que ce commentaire disait avoir
     supprimées étaient toujours là, sur les 46 DNAs. Les deux du HAUT touchent
     le menu et se lisent comme un défaut d'assemblage (Yann, 2026-08-25).

     > Une règle qui vise le parent d'un élément stylé ne le déshabille pas.
     > Le commentaire décrivait l'intention ; la mesure décrit le résultat. */
  border-radius: 0;
}
/* LA CARTE = recette surface DNA (cf .tw-surface[data-surface="card"]) : zéro valeur en dur.
   z-index 1 > média 0 (PAS de z-index négatif : un -1 retombe derrière le fond opaque de
   .content-section quand .toweb-container pose un z-index:1). */
.hero-block--layout-text-image-background:has(> .hero-block__media) > .hero-block__text{
  position: relative; z-index: 1;
  /* Panneau LÉGÈREMENT translucide par défaut (retour Yann 2026-07-04) : la photo
     de fond respire à travers la carte (92% + blur 6px). Tokens --tw-hero-panel-*
     pour qu'un DNA ajuste (A: 90%/6px, T: verre 22px — leurs overrides priment). */
  background: var(--tw-hero-panel-bg, color-mix(in srgb, var(--tw-surface, #fff) 92%, transparent));
  -webkit-backdrop-filter: blur(var(--tw-hero-panel-blur, 6px));
  backdrop-filter: blur(var(--tw-hero-panel-blur, 6px));
  border: var(--tw-border-width, 1px) solid var(--tw-border, rgba(0,0,0,.1));
  border-radius: var(--tw-radius-card, 16px);
  box-shadow: var(--tw-shadow-card, none);
  padding: var(--tw-card-padding, 1.75rem);
  --tw-text: var(--tw-on-surface);
  --tw-text-heading: var(--tw-on-surface);
  --tw-text-muted: color-mix(in oklab, var(--tw-on-surface) 86%, var(--tw-surface, transparent));
  color: var(--tw-on-surface, inherit);
  max-width: min(40rem, 100%);
  justify-self: start;
}
.hero-block--layout-text-image-background.hero-block--align-center:has(> .hero-block__media) > .hero-block__text{ justify-self: center; }
.hero-block--layout-text-image-background.hero-block--align-right:has(> .hero-block__media) > .hero-block__text{ justify-self: end; }
@media (max-width: 760px){
  .hero-block--layout-text-image-background:has(> .hero-block__media){
    display: block; min-height: 0; padding: 0; overflow: visible; isolation: auto; border-radius: 0;
  }
  .hero-block--layout-text-image-background:has(> .hero-block__media) > .hero-block__media{
    position: relative; inset: auto; z-index: auto; width: 100%; aspect-ratio: 16 / 10;
    margin-top: var(--tw-gap, 1.5rem); border-radius: var(--tw-radius-media, 12px); overflow: hidden;
  }
  .hero-block--layout-text-image-background:has(> .hero-block__media) > .hero-block__text{
    background: transparent; border: 0; box-shadow: none; padding: 0; max-width: none;
    --tw-text: inherit; --tw-text-heading: inherit; --tw-text-muted: inherit; color: inherit;
  }
}
/* alignement du texte (hero-block--align-{align}) */
.hero-block--align-center{ text-align: center; }
.hero-block--align-center .hero-block__text{ align-items: center; }
.hero-block--align-center .hero-block__ctas{ justify-content: center; }
.hero-block--align-right{ text-align: right; }
.hero-block--align-right .hero-block__text{ align-items: flex-end; }
.hero-block--align-right .hero-block__ctas{ justify-content: flex-end; }
@media (max-width: 760px){
  .hero-block--layout-text-image-right, .hero-block--layout-text-image-left{ grid-template-columns: 1fr; }
  .hero-block--layout-text-image-left .hero-block__text,
  .hero-block--layout-text-image-left .hero-block__media{ grid-column: 1; grid-row: auto; }
}
/* (Le placeholder des champs éditables vides est une affordance ÉDITEUR : il vit
   dans blocks-editor.css, PAS ici — blocks-base.css = briques nues + layout, le
   DNA décide du reste.) */

.cta-block{ display:flex; gap: var(--tw-gap,1rem); align-items:center; flex-wrap:wrap; }
.cta-block--align-center{ justify-content:center; }
.cta-block--banner{ flex-direction:column; text-align:center; }

.feature-item{ display:flex; flex-direction:column; gap:.75rem; }
.feature-item--list{ flex-direction:row; align-items:flex-start; }
.stat-item{ display:flex; flex-direction:column; gap:.25rem; }
.stat-item__value{ font-size: clamp(2.2rem,4vw,3.2rem); color: var(--tw-accent, inherit); }

.product-card{ display:flex; flex-direction:column; }
.product-card__link{ display:flex; flex-direction:column; gap:.85rem; }
.product-card__image-wrapper{ position:relative; }
.product-card__index{ position:absolute; bottom:.85rem; inset-inline-start:1rem; z-index:1; font-family: var(--tw-font-mono, monospace); font-size: var(--tw-size-meta,.8rem); letter-spacing:.06em; color: color-mix(in srgb, #fff 82%, transparent); }
.product-card__body{ display:flex; flex-direction:column; gap:.3rem; }
.product-card__tagline{ font-style: italic; color: var(--tw-accent-2, var(--tw-text-muted, inherit)); }
.product-card__cta{ margin-top:.4rem; }

.schedule-row{ display:grid; grid-template-columns: auto 1fr auto auto; gap: 1.5rem; align-items:center; padding-block: 1.25rem; }
@media (max-width:680px){ .schedule-row{ grid-template-columns: 1fr; gap:.5rem; } }

/* ----------------------------------------------------------------------------
   BLOCS SUPPLÉMENTAIRES (DOM canonique DNA-BLOCKS-SPEC). Tous DNA-agnostiques :
   uniquement du layout, consomment les briques .tw-* et le moteur .tw-grid.
   Variantes différentes de celles de DNA-C → validation de l'usage des briques
   par des blocs variés (même couche, autre composition).
   ---------------------------------------------------------------------------- */
/* hero — variante centered (text-only) en plus de split */
.hero-block--centered{ grid-template-columns: 1fr; justify-items: center; text-align: center; max-width: 820px; margin-inline: auto; }
.hero-block--centered .hero-block__ctas{ justify-content: center; }

/* logos-block — bande de logos partenaires */
.logos-block{ display:flex; flex-wrap:wrap; align-items:center; justify-content:center; gap: clamp(1.5rem, 5vw, 3.5rem); }
.logos-block__item{ opacity:.6; transition: opacity .2s; }
.logos-block__item:hover{ opacity:1; }

/* features-block — grille de features (colonnes via .tw-grid en markup ; le
   conteneur porte `features-block features-block--{variant} tw-grid`). DNA-BLOCKS-SPEC §3.4.
   item = .tw-surface (card pour `cards`, bare pour icons/list/centered). */
/* ⚠️ PAS DE `height:100%` SUR UN ÉLÉMENT DE GRILLE (lauyan.com#380, iPad iOS 17) :
   WebKit 17 résolvait ce pourcentage sur bien plus que la rangée — les cartes
   doublaient de hauteur, débordaient de leur section et passaient sous la
   suivante. Un élément de grille ou de flex s'étire déjà à la hauteur de sa
   rangée (`align-self: stretch`) : même rendu partout, sans pourcentage. */
.features-block .tw-surface{ display:flex; flex-direction:column; gap:.7rem; align-self:stretch; }
.features-block .tw-icon{ --tw-icon-size: 2rem; }       /* icône proéminente */
.features-block .tw-media{ aspect-ratio: 16/10; }       /* item à image */
.features-block--icons{ text-align:center; }
.features-block--icons .tw-surface{ align-items:center; }
.features-block--centered{ text-align:center; }
.features-block--centered .tw-surface{ align-items:center; }
/* list = icône à gauche, titre+texte à droite (grille : l'icône couvre 2 lignes,
   sans wrapper de contenu — DOM canonique icon+h4+p direct dans la surface). */
.features-block--list .tw-surface{ display:grid; grid-template-columns:auto 1fr; gap:.25rem 1rem; align-items:start; align-content:start; }
.features-block--list .tw-surface > .tw-icon{ grid-row: 1 / span 2; }
/* features-block — prix optionnel (.tw-numeral) + lien optionnel "label →" (.tw-cta-link).
   Tout optionnel : sans ces éléments, rendu identique. Le lien est aligné à gauche et
   poussé en bas de la carte (margin-top:auto) pour aligner les liens entre cartes de
   hauteurs différentes. Modelé sur cards-block__price / cards-block__button. */
.features-block__price{ margin-top:.1rem; }
.features-block__link{ margin-top:auto; align-self:flex-start; padding-top:.35rem; }
/* list = pas de poussée verticale (la grille auto/1fr ne définit pas de hauteur). */
.features-block--list .features-block__link{ margin-top:0; }

/* stats-block — rangée de métriques */
.stats-block .tw-surface{ display:flex; flex-direction:column; gap:.3rem; }
.stats-block--centered{ text-align:center; }
.stats-block--centered .tw-surface{ align-items:center; }
.stats-block--horizontal .tw-surface{ flex-direction:row; align-items:center; gap:1rem; }
/* La grande taille est sur le WRAPPER : la valeur (.tw-numeral) l'hérite, mais
   l'affixe (.tw-numeral[data-affix]{font-size:.65em}) reste proportionnel et petit
   (sinon il s'écrase à la taille de la valeur — même spécificité, source tardive). */
.stats-block__value-wrapper{ display:flex; align-items:baseline; gap:.08em; flex-wrap:wrap; font-size: clamp(2.4rem, 5vw, 3.6rem); line-height:1; }
/* #491 — le nombre se règle sur la largeur de SA carte, pas sur celle de l'écran.
   La taille du wrapper (base ci-dessus, ou celle du DNA) reste la taille VOULUE ;
   le nombre la prend tant qu'il tient, et se réduit sinon : largeur de la carte
   (container query) / longueur du plus long chiffre du bloc (`--stats-len`, posée
   par le rendu, shared/blocks/statsLongueur.ts) / .66em par chiffre (gras compris).
   Cinq valeurs à cinq chiffres dans un article de 800 px ne se chevauchent plus,
   dans l'éditeur comme sur le site. L'affixe suit dans la même proportion. */
.stats-block > .tw-surface{ container-type: inline-size; min-width: 0; }
.stats-block{ --stats-fit: calc(100cqi / (var(--stats-len, 4) * .66)); }
.stats-block__value-wrapper{ max-width: 100%; }
.stats-block__value-wrapper > .tw-numeral:not([data-affix]){ font-size: min(1em, var(--stats-fit)); }
.stats-block__value-wrapper > .tw-numeral[data-affix]{ font-size: min(.65em, var(--stats-fit) * .65); }
/* Dernier recours (police très large, valeur hors norme) : couper plutôt que déborder. */
.stats-block__value-wrapper > .tw-numeral{ overflow-wrap: anywhere; min-width: 0; }
.stats-block--horizontal .stats-block__value-wrapper{ min-width: 0; }
/* Colonnes plafonnées par la place : le nombre choisi est un MAXIMUM. Une grille
   trop étroite pour ses colonnes (article, colonne d'un bloc Colonnes) passe à la
   ligne au lieu d'écraser ses cartes sous ~7,5rem. Avec la place, la piste vaut
   exactement 1/--cols (le −1px absorbe l'arrondi) : rien ne change. */
.stats-block.tw-grid{
  grid-template-columns: repeat(auto-fit, minmax(min(100%, max(7.5rem, (100% - (var(--cols, 1) - 1) * var(--tw-gap, 1.5rem) - 1px) / var(--cols, 1))), 1fr));
}
/* caption optionnelle : sous-ligne meta sous le label, plus petite/discrète (hérite .tw-meta) */
/* ⚠️ Pas d'opacité sur du texte déjà atténué (`.tw-meta` = `--tw-text-muted`) : la
   double atténuation le faisait passer sous 4,5:1 (axe-core sur la démo, 2026-09-28). */
.stats-block__caption{ font-size:.8em; }

/* specs-block — rangées LABEL : VALUE (métadonnées projet / fiche technique).
   Layout-only : la typo/couleur vient des briques .tw-eyebrow (label) + .tw-body
   (value). DNA-BLOCKS-SPEC §3. */
.specs-block{ display:flex; flex-direction:column; gap:.85rem; }
.specs-block__row{ display:grid; grid-template-columns:minmax(6rem,.4fr) 1fr; gap:.2rem 1.5rem; align-items:start; }
.specs-block--stacked .specs-block__row{ grid-template-columns:1fr; gap:.15rem; }
.specs-block__value{ white-space:pre-line; }

/* story-block — récit « collant » façon Apple (Yann, 2026-09-26).
   Deux colonnes : les étapes défilent, la scène reste collée et fond d'une image
   à l'autre (`motion.ts` → initRecits pose `is-active` et --sb-progress).
   Layout-only : typo/couleur par .tw-eyebrow / .tw-h3 / .tw-body + jetons --tw-*.
   ⚠️ Les étapes ne pâlissent QUE sous `.story-block--vivant`, que le script pose :
   sans lui (éditeur, JS absent, moins de mouvement) tout reste lisible.
   Variables internes en --sb-* : un --tw-* lu sans repli devrait exister dans
   les 48 DNA. */
.story-block{ --sb-progress: 1; --sb-radius: var(--tw-radius-media, 14px); display:grid; grid-template-columns:1fr; gap:2rem; }
.story-block__steps{ display:flex; flex-direction:column; gap:2.5rem; min-width:0; }
.story-block__step{ display:flex; flex-direction:column; gap:1rem; }
.story-block__step-body{ display:flex; flex-direction:column; gap:.6rem; max-width:34rem; }
.story-block__title, .story-block__text, .story-block__eyebrow{ margin:0; }
.story-block__step-media{ border-radius:var(--sb-radius); overflow:hidden; aspect-ratio:4/3; }
.story-block__stage{ display:none; }
/* Cadre de la scène (et, en mobile, de l'image de chaque étape). */
.story-block--frame-rounded :is(.story-block__frame, .story-block__step-media){ border-radius:var(--sb-radius); box-shadow:0 1px 2px rgba(0,0,0,.06), 0 24px 60px -18px rgba(0,0,0,.28); }
.story-block--frame-none :is(.story-block__frame, .story-block__step-media){ border-radius:0; box-shadow:none; }
/* « Navigateur » : même habillage que .image-block--frame-browser (blocks-shared.css). */
.story-block--frame-browser :is(.story-block__frame, .story-block__step-media){
  box-sizing:border-box; padding-top:2rem;
  border:1px solid color-mix(in oklab, var(--tw-text, #111) 12%, transparent);
  border-radius:12px;
  background:
    radial-gradient(circle 5px at 18px 1rem, #ff5f57 97%, transparent 100%) no-repeat,
    radial-gradient(circle 5px at 36px 1rem, #febc2e 97%, transparent 100%) no-repeat,
    radial-gradient(circle 5px at 54px 1rem, #28c840 97%, transparent 100%) no-repeat,
    linear-gradient(color-mix(in oklab, var(--tw-text, #111) 6%, var(--tw-surface, #fff)), color-mix(in oklab, var(--tw-text, #111) 6%, var(--tw-surface, #fff))) top / 100% 2rem no-repeat,
    var(--tw-surface, #fff);
  box-shadow:0 1px 2px rgba(0,0,0,.06), 0 24px 60px -12px rgba(0,0,0,.28);
}
.story-block--frame-browser .story-block__step-media{ aspect-ratio:auto; }
.story-block--frame-browser .story-block__step-media > img{ height:auto; aspect-ratio:4/3; }
@media (min-width: 900px){
  .story-block:not(.story-block--sans-scene){ grid-template-columns:minmax(0,.8fr) minmax(0,1.45fr); gap:clamp(2rem,5vw,5rem); align-items:start; }
  .story-block--media-left:not(.story-block--sans-scene){ grid-template-columns:minmax(0,1.45fr) minmax(0,.8fr); }
  .story-block--media-left .story-block__stage{ order:-1; }
  .story-block:not(.story-block--sans-scene) .story-block__steps{ gap:0; }
  .story-block:not(.story-block--sans-scene) .story-block__step{ min-height:70vh; justify-content:center; }
  /* La PREMIÈRE étape et la scène partent du haut : centrées dans 70vh, elles
     laissaient un écran presque blanc avant la première image (Yann, 2026-09-26). */
  .story-block:not(.story-block--sans-scene) .story-block__step:first-child{ justify-content:flex-start; padding-top:clamp(1rem, 6vh, 3.5rem); }
  .story-block:not(.story-block--sans-scene) .story-block__step-media{ display:none; }
  /* La scène a le FORMAT DES IMAGES (16:10, celui d'une capture d'écran),
     centrée dans la hauteur collante : une scène de 76vh sur une demi-largeur
     était un portrait, et rognait les captures sur les deux côtés. */
  .story-block__stage{ display:flex; flex-direction:column; justify-content:flex-start; gap:1rem; position:sticky; top:max(12vh, calc(var(--mv-entete-h, 0px) + 1.5rem)); height:76vh; container-type:inline-size; }
  .story-block__frame{ position:relative; flex:none; width:100%; aspect-ratio:16/10; max-height:calc(76vh - 2rem); overflow:hidden; }
  .story-block--frame-browser .story-block__frame{ aspect-ratio:auto; height:calc((100cqw) * 0.625 + 2rem); }
  .story-block__media{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; object-position:top center; opacity:0; transform:scale(1.04); transition:opacity .7s ease, transform 1.2s cubic-bezier(.19,1,.22,1); }
  .story-block--frame-browser .story-block__media{ top:2rem; height:calc(100% - 2rem); border-radius:0 0 11px 11px; }
  .story-block__media.is-active{ opacity:1; transform:none; }
  .story-block__progress{ height:2px; border-radius:2px; background:color-mix(in oklab, var(--tw-text, #111) 12%, transparent); overflow:hidden; }
  .story-block__progress-bar{ display:block; height:100%; background:var(--tw-accent, currentColor); transform-origin:0 50%; transform:scaleX(var(--sb-progress)); transition:transform .6s cubic-bezier(.19,1,.22,1); }
  [dir="rtl"] .story-block__progress-bar{ transform-origin:100% 50%; }
  /* L'étape inactive s'estompe par sa COULEUR (le gris atténué du DNA, calculé
     pour tenir 4,5:1 sur sa bande), jamais par opacité : à .35, les étapes
     passées ou à venir tombaient sous le seuil de contraste (axe, lauyan.com). */
  .story-block--vivant .story-block__step:not(.is-active),
  .story-block--vivant .story-block__step:not(.is-active) *{ color:var(--tw-text-muted, #595959); transition:color .5s ease; }
}
@media (prefers-reduced-motion: reduce){
  .story-block__media, .story-block__progress-bar, .story-block--vivant .story-block__step, .story-block--vivant .story-block__step *{ transition:none; }
  .story-block__media{ transform:none; }
}

/* countdown-block — compte à rebours jusqu'à une date (#343). Chiffres en police
   de TITRE du DNA, alignés (tabular-nums) pour que la ligne ne tremble pas à
   chaque seconde. `cards` : une tuile par unité (surface/bordure/rayon du DNA) ;
   `plain` : les chiffres seuls, séparés par un filet. Centré, comme le veut le
   genre (lancements, soldes, événements). */
.countdown-block{ display:flex; flex-direction:column; align-items:center; gap:.9rem; text-align:center; }
.countdown-block__title{ margin:0; font-family:var(--tw-font-heading, inherit); font-weight:var(--tw-weight-heading, 700); font-size:clamp(1.1rem, 2.2vw, 1.5rem); color:var(--tw-text-heading, inherit); }
.countdown-block__units{ display:flex; flex-wrap:wrap; justify-content:center; gap:clamp(.5rem, 2vw, 1rem); }
.countdown-block__units[hidden]{ display:none; }
.countdown-block__unit{ display:flex; flex-direction:column; align-items:center; gap:.35rem; min-width:4.2em; }
.countdown-block__value{ font-family:var(--tw-font-heading, inherit); font-weight:800; line-height:1; font-variant-numeric:lining-nums tabular-nums; letter-spacing:-.02em; color:var(--tw-text-heading, inherit); }
.countdown-block__label{ font-size:var(--tw-size-meta, .8rem); text-transform:uppercase; letter-spacing:.08em; color:var(--tw-text-muted, inherit); }
.countdown-block--medium .countdown-block__value{ font-size:clamp(1.8rem, 5vw, 2.8rem); }
.countdown-block--large .countdown-block__value{ font-size:clamp(2.4rem, 8vw, 4.6rem); }
.countdown-block--cards .countdown-block__unit{
  padding:clamp(.7rem, 2vw, 1.1rem) clamp(.8rem, 2.4vw, 1.4rem);
  background:var(--tw-surface, transparent); color:var(--tw-on-surface, inherit);
  border:var(--tw-border-width, 1px) solid var(--tw-border, var(--tw-rule, rgba(0,0,0,.12)));
  border-radius:var(--tw-radius-card, 12px); box-shadow:var(--tw-shadow-card, none);
}
.countdown-block--plain .countdown-block__unit + .countdown-block__unit{ border-inline-start:1px solid var(--tw-rule, rgba(0,0,0,.15)); padding-inline-start:clamp(.5rem, 2vw, 1rem); }
.countdown-block__end{ margin:0; font-family:var(--tw-font-heading, inherit); font-weight:700; font-size:clamp(1.3rem, 3.4vw, 2rem); color:var(--tw-accent, inherit); }
.countdown-block__end[hidden]{ display:none; }
/* #497 — alignement à gauche / à droite (centré par défaut). */
.countdown-block--align-left{ align-items:flex-start; text-align:start; }
.countdown-block--align-left .countdown-block__units{ justify-content:flex-start; }
.countdown-block--align-right{ align-items:flex-end; text-align:end; }
.countdown-block--align-right .countdown-block__units{ justify-content:flex-end; }
.countdown-block__placeholder{ padding:1.2rem; border:1px dashed var(--tw-rule, rgba(0,0,0,.2)); border-radius:10px; color:var(--tw-text-muted, inherit); font-size:.9rem; }

/* feedback-block — avis et commentaires (M-FEEDBACK). Il n'avait AUCUNE règle,
   ni commune ni dans un DNA (audit des 48 DNA, 2026-09-28) : libellés collés aux
   champs du dessus, étoiles de saisie en boutons radio natifs, étoiles pleines et
   vides identiques dans la liste. Tout en jetons : les 48 DNA l'habillent d'un
   coup. Garde : `scripts/ui-harness/dna-matrice.mjs` (règle « bloc-nu »). */
.feedback-block{ display:flex; flex-direction:column; gap:clamp(1rem, 2.5vw, 1.5rem); width:100%; max-width:48rem; }
.feedback-block__header{ display:flex; flex-wrap:wrap; align-items:baseline; justify-content:space-between; gap:.4rem 1rem; }
.feedback-block__title{ margin:0; font-family:var(--tw-font-heading, inherit); font-weight:var(--tw-weight-heading, 700); font-size:var(--tw-size-h4, 1.25rem); line-height:var(--tw-leading-heading, 1.2); color:var(--tw-text-heading, inherit); }
.feedback-block__average{ display:inline-flex; align-items:baseline; gap:.3em; color:var(--tw-text-muted, inherit); font-size:var(--tw-size-meta, .875rem); }
.feedback-block__average-score{ font-family:var(--tw-font-heading, inherit); font-weight:700; font-size:1.6rem; line-height:1; color:var(--tw-text-heading, inherit); font-variant-numeric:tabular-nums; }
.feedback-block__list{ display:flex; flex-direction:column; gap:.75rem; }
.feedback-block__item{
  padding:var(--tw-card-padding, 1.25rem);
  background:var(--tw-surface, transparent); color:var(--tw-on-surface, inherit);
  border:var(--tw-border-width, 1px) solid var(--tw-border, rgba(0,0,0,.12));
  border-radius:var(--tw-radius-card, 12px);
}
.feedback-block__item-header{ display:flex; flex-wrap:wrap; align-items:center; gap:.35rem .75rem; margin-bottom:.45rem; }
.feedback-block__item-date{ margin-inline-start:auto; font-size:var(--tw-size-meta, .8rem); color:var(--tw-text-muted, inherit); }
.feedback-block__item-content{ margin:0; white-space:pre-line; }
.feedback-block__item-stars{ display:inline-flex; gap:.08em; }
/* Étoile vide d'un avis : un CONTOUR à l'encre atténuée (voir `.tw-rating`). */
.feedback-block__item-star{ color: transparent; -webkit-text-stroke: .07em var(--tw-text-muted, currentColor); }
.feedback-block__item-star.is-filled{ color:var(--tw-rating-on, var(--tw-accent, currentColor)); }
.feedback-block__empty{ margin:0; color:var(--tw-text-muted, inherit); }
.feedback-block__form{ display:flex; flex-direction:column; gap:1rem; }
.feedback-block__fields{ position:relative; display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:1rem; }
.feedback-block__field{ display:flex; flex-direction:column; gap:.375rem; font-size:.875rem; font-weight:500; color:var(--tw-text, inherit); }
.feedback-block__field--full, .feedback-block__rating, .feedback-block__captcha{ grid-column:1 / -1; }
@media (max-width: 640px){ .feedback-block__fields{ grid-template-columns:minmax(0, 1fr); } }
.feedback-block__rating{ display:flex; flex-wrap:wrap; align-items:center; gap:.4rem 1rem; font-size:.875rem; font-weight:500; color:var(--tw-text, inherit); }
.feedback-block__rating[hidden], .feedback-block [x-cloak]{ display:none; }
/* Étoiles de SAISIE : la radio native reste dans l'arbre (clavier, lecteur
   d'écran) mais invisible ; l'étoile choisie et celles qui la précèdent
   s'allument, au survol comme à la sélection. Ordre du DOM : juste en RTL aussi. */
.feedback-block__rating-stars{ display:inline-flex; gap:.1em; }
.feedback-block__star{ position:relative; display:inline-grid; place-items:center; font-size:1.6rem; line-height:1; cursor:pointer; color:var(--tw-rating-on, var(--tw-accent, currentColor)); }
.feedback-block__star input{ position:absolute; inset:0; margin:0; opacity:0; cursor:pointer; }
.feedback-block__rating-stars:not(:has(input:checked)) .feedback-block__star,
.feedback-block__star:has(input:checked) ~ .feedback-block__star{ color:var(--tw-rating-off, var(--tw-rule, rgba(0,0,0,.25))); }
.feedback-block__rating-stars:hover .feedback-block__star{ color:var(--tw-rating-on, var(--tw-accent, currentColor)); }
.feedback-block__rating-stars .feedback-block__star:hover ~ .feedback-block__star{ color:var(--tw-rating-off, var(--tw-rule, rgba(0,0,0,.25))); }
.feedback-block__star:has(input:focus-visible){ outline:2px solid var(--tw-accent, currentColor); outline-offset:2px; border-radius:4px; }
.feedback-block__submit{ align-self:flex-start; }
.feedback-block__submit:disabled{ cursor:progress; opacity:.6; }
.feedback-block__success, .feedback-block__error{
  margin:0; padding:.75rem 1rem; font-size:.9375rem; color:var(--tw-text, inherit);
  border-radius:var(--tw-radius-field, var(--tw-radius-button, 8px));
}
.feedback-block__success{ background:var(--tw-accent-soft, rgba(0,0,0,.06)); }
.feedback-block__error{ background:color-mix(in srgb, var(--tw-surface, #fff) 82%, #dc2626); border-inline-start:3px solid #dc2626; }

/* Une SURFACE peinte par un BLOC (hors brique `.tw-surface`) est un îlot, comme la
   brique : son texte se re-localise sur `--tw-on-surface`. Sans ça, une carte
   claire posée sur une bande `bold` héritait du texte clair de la bande — cartes,
   tuiles du compte à rebours et en-têtes de tableau BLANC SUR CRÈME chez ATELIER
   (audit des 48 DNA, 2026-09-28). */
:is(.cards-block__card, .countdown-block--cards .countdown-block__unit, .feedback-block__item, .product-card, .blog-card, .booking-block--card, .booking-block__summary-card, .tw-prose th, .content-section table th, .content-section table tbody tr:nth-child(even)){
  --tw-text: var(--tw-on-surface);
  --tw-text-heading: var(--tw-on-surface);
  --tw-text-muted: color-mix(in oklab, var(--tw-on-surface) 86%, var(--tw-surface, transparent));
  color: var(--tw-on-surface);
}

/* marquee-block — bandeau de texte qui défile sans fin (Yann, 2026-09-26).
   CSS seul : la piste porte deux groupes identiques et glisse de 0 à -50 %.
   Typo d'affiche = police de TITRE du DNA ; couleur = celle du texte de la bande
   (currentColor : le contour `outline` suit aussi le mode sombre). */
.marquee-block{
  --mq-duree: 35s; --mq-sens: normal;
  position:relative; overflow:hidden; padding-block:.15em;
  font-family:var(--tw-font-heading, inherit); font-weight:700; line-height:1.1; letter-spacing:-.01em;
  -webkit-mask-image:linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
  mask-image:linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.marquee-block--medium{ font-size:clamp(1.5rem, 3.2vw, 2.4rem); }
.marquee-block--large{ font-size:clamp(2.2rem, 6vw, 4.5rem); }
.marquee-block--huge{ font-size:clamp(3rem, 10vw, 8rem); letter-spacing:-.02em; }
.marquee-block[data-speed="slow"]{ --mq-duree: 60s; }
.marquee-block[data-speed="fast"]{ --mq-duree: 20s; }
.marquee-block[data-direction="right"]{ --mq-sens: reverse; }
.marquee-block__track{ display:flex; width:max-content; animation:mq-defile var(--mq-duree) linear infinite; animation-direction:var(--mq-sens); }
.marquee-block:hover .marquee-block__track,
.marquee-block[data-tw-pause="on"] .marquee-block__track{ animation-play-state:paused; }
.marquee-block__group{ display:flex; align-items:center; flex-shrink:0; }
.marquee-block__item{ white-space:nowrap; padding-inline:.45em; }
.marquee-block__sep{ padding-inline:.35em; color:var(--tw-accent, currentColor); font-size:.6em; }
.marquee-block{ --mq-trait: var(--tw-text, #111); }
/* ⚠️ LA PISTE NE DOIT PAS DONNER SA LARGEUR AU BLOC. Sans `contain: inline-size`,
   la largeur min-content du bloc est celle de sa piste (6 500 px mesurés) : dans
   l'éditeur, elle remontait jusqu'à la colonne du canevas, qui passait SOUS le
   panneau de droite (Yann, 2026-09-26). `overflow: hidden` ne suffit pas : il
   coupe l'affichage, pas la contribution à la largeur des parents. */
.marquee-block{ contain: inline-size; min-width: 0; max-width: 100%; }
/* ⚠️ PAS `currentColor` : sur un texte transparent, currentColor EST transparent,
   et le contour disparaissait avec le remplissage (bandeau vide, 2026-09-26). */
.marquee-block--outline .marquee-block__item{ color:transparent; -webkit-text-stroke:1.5px var(--mq-trait); }
@supports not (-webkit-text-stroke: 1px currentColor){ .marquee-block--outline .marquee-block__item{ color:inherit; } }
@keyframes mq-defile{ from{ transform:translate3d(0,0,0); } to{ transform:translate3d(-50%,0,0); } }
html[data-animations="off"] .marquee-block__track{ animation:none; }
@media (prefers-reduced-motion: reduce){
  .marquee-block{ -webkit-mask-image:none; mask-image:none; }
  .marquee-block__track{ animation:none; width:auto; justify-content:center; }
  .marquee-block__group{ flex-wrap:wrap; justify-content:center; flex-shrink:1; }
  .marquee-block__group[aria-hidden="true"]{ display:none; }
  .marquee-block__item{ white-space:normal; text-align:center; }
}
html[data-animations="off"] .marquee-block{ -webkit-mask-image:none; mask-image:none; }
html[data-animations="off"] .marquee-block__track{ width:auto; justify-content:center; }
html[data-animations="off"] .marquee-block__group{ flex-wrap:wrap; justify-content:center; flex-shrink:1; }
html[data-animations="off"] .marquee-block__group[aria-hidden="true"]{ display:none; }

/* pricelist-block — liste de prix / tarifs : entrées catégorie + items (nom … filet … prix).
   Layout-only : la typo/couleur vient des briques .tw-h4 (catégorie) / .tw-body (nom)
   / .tw-numeral (prix) / .tw-meta (description) ; les pointillés du filet réutilisent
   le token --tw-rule (défini par le DNA). DNA-BLOCKS-SPEC §3. */
.pricelist-block{ display:flex; flex-direction:column; gap:.5rem; }
.pricelist-block__category{ margin-top:1.25rem; }
.pricelist-block__entry:first-child.pricelist-block__category{ margin-top:0; }
.pricelist-block__item{ display:flex; flex-direction:column; gap:.15rem; }
.pricelist-block__line{ display:flex; align-items:baseline; gap:.6rem; }
.pricelist-block__name{ flex:0 1 auto; }
.pricelist-block__leader{ flex:1 1 auto; min-width:1.5rem; align-self:center; border-bottom:1px dotted var(--tw-rule, currentColor); opacity:.7; }
.pricelist-block__price{ flex:0 0 auto; white-space:nowrap; }

/* list-block — liste verticale d'entrées (annuaire / index / programme / line-up / registre).
   Layout-only : la typo/couleur vient des briques .tw-h4 (titre) / .tw-meta (meta, trailing,
   plain lead) / .tw-numeral (numéro, jour de date) / .tw-eyebrow (statut) + .tw-button (cta) ;
   le filet entre lignes réutilise le token --tw-rule (défini par le DNA). leadStyle pilote la
   colonne de gauche ; showRule => --ruled. DNA-BLOCKS-SPEC §3. */
.list-block{ display:flex; flex-direction:column; }
.list-block__row{ display:flex; align-items:baseline; gap:1rem; padding:.85rem 0; }
.list-block__row:first-child{ padding-top:0; }
.list-block--ruled .list-block__row + .list-block__row{ border-top:1px solid var(--tw-rule, rgba(0,0,0,.08)); }
.list-block__lead{ flex:0 0 auto; min-width:2.25rem; }
.list-block--lead-number .list-block__lead{ text-align:end; }
.list-block__lead-num{ font-size:1.35em; line-height:1; color:var(--tw-text-muted, inherit); font-variant-numeric:tabular-nums; }   /* couleur atténuée, plus d'opacité : 3:1 à 55 % (axe, démo 2026-09-28) */
.list-block__lead-date{ display:flex; flex-direction:column; align-items:center; line-height:1; }
.list-block__lead-day{ font-size:1.3em; font-variant-numeric:tabular-nums; }
.list-block__lead-month{ font-size:.7em; text-transform:uppercase; letter-spacing:.08em; color:var(--tw-text-muted, inherit); }
.list-block__lead-plain{ white-space:nowrap; }
.list-block__body{ flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:.15rem; }
.list-block__head{ display:flex; align-items:baseline; gap:.6rem; flex-wrap:wrap; }
.list-block__title{ margin:0; }
.list-block__title-link{ display:inline-flex; align-items:baseline; gap:.3em; }
.list-block__arrow{ transition:transform .15s ease; }
.list-block__title-link:hover .list-block__arrow{ transform:translateX(.2em); }
.list-block__status{ flex:0 0 auto; padding:.1em .55em; border:1px solid var(--tw-rule, currentColor); border-radius:999px; line-height:1.5; }
.list-block__meta{ display:block; }
.list-block__trailing{ flex:0 0 auto; margin-inline-start:auto; display:flex; flex-direction:column; align-items:flex-end; gap:.35rem; text-align:end; white-space:nowrap; }
@media (max-width:560px){
  .list-block__row{ flex-wrap:wrap; }
  .list-block__trailing{ margin-inline-start:0; flex-basis:100%; flex-direction:row; align-items:baseline; justify-content:flex-start; text-align:start; }
  .list-block--lead-number .list-block__lead,
  .list-block--lead-plain .list-block__lead{ min-width:1.75rem; }
}

/* cards-block — grille de cartes riches (image + titre + texte + meta + prix + bouton).
   Colonnes via .tw-grid + data-cols-* en markup. La carte est un conteneur TOKEN-DRIVEN
   (PAS la brique .tw-surface dont le padding casserait l'image plein cadre — même choix
   que les product/blog cards) : surface/bordure/rayon/ombre via les tokens --tw-* du DNA.
   L'aspect de l'image est géré par .tw-media[data-aspect]. DNA-BLOCKS-SPEC §3. */
/* dense packing : une carte large (--span2) laisse un trou d'une colonne sur sa
   rangée ; grid-auto-flow:dense fait remonter une carte suivante pour le combler
   (no-op quand toutes les cartes font 1 colonne → rendu identique sans span2). */
.cards-block{ grid-auto-flow: dense; }
.cards-block__card{ position:relative; display:flex; flex-direction:column; overflow:hidden; background:var(--tw-surface, #fff); border:var(--tw-border-width,1px) solid var(--tw-border, rgba(0,0,0,.08)); border-radius:var(--tw-radius-card, 12px); box-shadow:var(--tw-shadow-card, none); }
/* Carte cliquable EN ENTIER (lien sans libellé de bouton — arbitrage Fable
   2026-09-18) : le lien étiré de la carte d'article. Le `::after` du lien couvre
   la carte ; le survol reprend les jetons de `.tw-surface--hover-lift`, donc
   chaque DNA décide de l'élévation (0 pour les styles plats). Le focus se voit
   sur la CARTE, pas sur le seul titre. */
.cards-block__link{ color:inherit; text-decoration:none; }
.cards-block__link::after{ content:''; position:absolute; inset:0; z-index:0; }
.cards-block__card[data-linked]{ cursor:pointer; transition: transform .2s ease, box-shadow .2s ease; }
.cards-block__card[data-linked]:hover{ transform:translateY(var(--tw-hover-lift, 0)); box-shadow:var(--tw-shadow-card-hover, var(--tw-shadow-card, none)); }
.cards-block__card:has(.cards-block__link:focus-visible){ outline:2px solid var(--tw-accent, currentColor); outline-offset:2px; }
.cards-block__card:has(.cards-block__link:focus-visible) .cards-block__link:focus-visible{ outline:none; }
@media (prefers-reduced-motion: reduce){ .cards-block__card[data-linked]{ transition:none; } .cards-block__card[data-linked]:hover{ transform:none; } }
.cards-block__media{ width:100%; }
.cards-block__body{ display:flex; flex-direction:column; gap:.35rem; padding:1.05rem 1.15rem 1.25rem; }
.cards-block__text{ margin-top:.1rem; }
.cards-block__foot{ display:flex; align-items:baseline; justify-content:space-between; gap:.75rem; margin-top:.35rem; }
.cards-block__button{ margin-top:.7rem; align-self:flex-start; }
/* HELO 74942 — « Alignement du texte : Centré » (réglage du bloc). Gauche
   n'ajoute aucune classe : les grilles existantes ne bougent pas. Le pied
   (méta + prix) se resserre au centre au lieu de s'écarter aux deux bords. */
.cards-block--align-center .cards-block__body{ text-align:center; }
.cards-block--align-center .cards-block__foot{ justify-content:center; flex-wrap:wrap; }
.cards-block--align-center .cards-block__button{ align-self:center; }
/* cards-block — éditorial/masonry : colSpan (carte large 2 colonnes), aspect image
   par carte, et badge d'angle. Tout optionnel : sans ces classes, rendu identique.
   --span2 n'est honoré que si la grille déclare ≥2 colonnes au breakpoint (clamp :
   jamais plus large que la grille ; sur mobile tout est 1-col → span ignoré). */
@media (min-width:1024px){
  .tw-grid[data-cols-desktop="2"] > .cards-block__card--span2,
  .tw-grid[data-cols-desktop="3"] > .cards-block__card--span2,
  .tw-grid[data-cols-desktop="4"] > .cards-block__card--span2,
  .tw-grid[data-cols-desktop="5"] > .cards-block__card--span2,
  .tw-grid[data-cols-desktop="6"] > .cards-block__card--span2{ grid-column: span 2; }
}
@media (min-width:640px) and (max-width:1023px){
  .tw-grid[data-cols-tablet="2"] > .cards-block__card--span2,
  .tw-grid[data-cols-tablet="3"] > .cards-block__card--span2,
  .tw-grid[data-cols-tablet="4"] > .cards-block__card--span2{ grid-column: span 2; }
}
/* aspect par carte — override du data-aspect du bloc (double classe pour gagner en
   spécificité face à .tw-media[data-aspect]) ; width/height/object-fit:cover hérités de
   la règle de base .tw-media > img (le remplissage en PUBLIÉ est ré-asserté une seule
   fois pour TOUT .tw-media dans astro-preview.css, plus de hack par-carte ici). */
.cards-block__media.cards-block__media--portrait{ aspect-ratio: 3/4; }
.cards-block__media.cards-block__media--landscape{ aspect-ratio: 16/10; }
.cards-block__media.cards-block__media--square{ aspect-ratio: 1/1; }
/* badge d'angle — la brique .tw-badge (BRIQUE 6) porte le look (fond/texte/rayon via
   les tokens DNA) ; cette règle ne fait que le positionner dans le coin de la carte
   (même principe que .pricing-block__badge qui ne fait que positionner sa brique). */
.cards-block__badge{ position:absolute; top:.6rem; inset-inline-end:.6rem; z-index:1; pointer-events:none; }

/* pricing-block — cartes de plans (colonnes via .tw-grid en markup) */
.pricing-block{ align-items: stretch; }
.pricing-block__plan{ display:flex; flex-direction:column; gap:1rem; align-self:stretch; } /* pas de height:100% : cf. features-block */
.pricing-block__price{ display:flex; align-items:baseline; gap:.3rem; flex-wrap: wrap; }
/* Le MONTANT ne se coupe jamais (« 59 » d'un côté, « € » de l'autre) : c'est la
   période qui passe dessous quand la carte est étroite — quatre éditions de
   front, mesuré sur lauyan.com le 25/09 (#317). */
.pricing-block__price .tw-numeral{ white-space: nowrap; }
.pricing-block__price .tw-numeral{ font-size: clamp(2.2rem, 4vw, 3rem); line-height:1; }
/* Prix barré (plan.compareAtPrice) — la convention Stripe / Shopify : le prix
   normal, plus petit, en atténué (.tw-meta = --tw-text-muted, que chaque DNA tient
   à 4,5:1), barré d'un trait fin qui ne coupe pas la lecture des chiffres. Il ne se
   coupe pas plus que le montant. */
.pricing-block__compare{ font-size: 1.05rem; white-space: nowrap; text-decoration: line-through; text-decoration-thickness: .08em; text-decoration-color: currentColor; margin-inline-start: .15rem; font-variant-numeric: tabular-nums; }
/* « au lieu de » : lu par les lecteurs d'écran (qui n'annoncent pas <del>), pas vu.
   Le site n'a pas d'utilitaire .sr-only global : la règle est portée ici, comme
   pour .tw-rotation. */
.pricing-block__compare > .sr-only{ position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0; }
/* Note sous le prix (plan.priceNote) : une ligne courte collée au prix — le
   `gap` de la carte l'en éloignerait autant que du nom. */
.pricing-block__price-note{ margin: -.6rem 0 0; line-height: 1.4; }
/* La LISTE d'un plan se lit plus petit que le corps, et serrée — la convention des
   pages de prix (Stripe, Linear, Shopify : ≈ 0,9375 du corps, interligne ~1,45).
   Au corps entier (.tw-body), la carte Studio de lauyan.com débordait d'un écran
   de MacBook 13" et ses boutons passaient sous la ligne de flottaison (Yann,
   18/10). La taille est posée sur le POINT et héritée par son texte : l'icône
   (en em) se cale ainsi sur la même ligne. Plancher à 14 px (lisibilité mobile). */
.pricing-block__features{ list-style:none; padding:0; margin:.4rem 0 0; display:flex; flex-direction:column; gap:.4rem; }
.pricing-block__feature{ display:flex; align-items:flex-start; gap:.5rem; font-size: max(.875rem, calc(var(--tw-size-base, 1.0625rem) * .9375)); line-height: calc(1.45 * var(--tw-leading-scale, 1)); }
.pricing-block__feature-text.tw-body{ font-size: inherit; line-height: inherit; }
/* La phrase « pour qui » du plan à la même taille que la liste : au corps entier,
   elle prenait 4 lignes dans une carte de 297 px (lauyan.com, MacBook 13"). */
.pricing-block__description.tw-body{ font-size: max(.875rem, calc(var(--tw-size-base, 1.0625rem) * .9375)); line-height: calc(1.45 * var(--tw-leading-scale, 1)); }
/* Lien contextuel d'un point (`features[].link`) : sans `linkLabel`, le texte du
   point est le lien (souligné fin, comme un lien du texte courant) ; avec, un
   petit lien discret APRÈS le texte — « … · Voir en démo → » —, souligné au
   survol. Couleur : le lien du texte (`--tw-prose-link`, accent au contraste
   tenu, re-pointé par la bande `bold`). `inline-block` sur le séparateur et le
   libellé : le trait d'un point NON compris ne les barre pas. */
.pricing-block__feature-link{ color: var(--tw-prose-link, var(--tw-accent, currentColor)); text-decoration-line: underline; text-decoration-thickness: 1px; text-underline-offset: .2em; text-decoration-color: color-mix(in oklab, currentColor 45%, transparent); transition: color .15s ease, text-decoration-color .15s ease; }
.pricing-block__feature-link:hover{ text-decoration-color: currentColor; }
.pricing-block__feature-sep{ display: inline-block; margin-inline: .4em; color: var(--tw-text-muted, currentColor); }
.pricing-block__feature-link--after{ display: inline-block; font-weight: 500; white-space: nowrap; text-decoration-line: none; }
/* La flèche est décorative (texte de remplacement vide) ; un navigateur qui ne lit
   pas `/ ""` garde la première déclaration. Sens de lecture inversé : ←. */
.pricing-block__feature-link--after::after{ content: "\2192"; content: "\2192" / ""; display: inline-block; margin-inline-start: .3em; transition: transform .2s ease; }
.pricing-block__feature-link--after:dir(rtl)::after{ content: "\2190"; content: "\2190" / ""; }
.pricing-block__feature-link--after:hover{ text-decoration-line: underline; }
.pricing-block__feature-link--after:hover::after{ transform: translateX(2px); }
.pricing-block__feature-link--after:dir(rtl):hover::after{ transform: translateX(-2px); }
.pricing-block__feature-link:focus-visible{ outline: 2px solid currentColor; outline-offset: 2px; border-radius: 2px; }
/* Avantage NON compris : barré, en atténué — plus une opacité de 50 % qui le rendait
   illisible (3:1, axe-core, audit du 2026-09-28) ; seule l'icône s'efface. */
.pricing-block__feature--excluded .pricing-block__feature-text{ color: var(--tw-text-muted, inherit); }
.pricing-block__feature--excluded .pricing-block__feature-icon{ opacity:.5; }
.pricing-block__cta{ margin-top:auto; }
/* Bouton SOUS le prix (`ctaPosition: 'top'`) — la convention des pages de prix
   SaaS (Linear, Vercel, Notion, Framer) : l'appel reste à l'écran même quand la
   liste est longue. Le DOM regroupe nom, prix, note et description dans
   `.pricing-block__head` ; la carte devient une sous-grille de TROIS rangées
   (en-tête, bouton, liste) qui partage les rangées de la grille du bloc : l'en-tête
   prend la hauteur du plus haut de la rangée de cartes, et les boutons de toutes
   les cartes tombent sur la même ligne, quelle que soit la longueur des
   descriptions. Sans subgrid (navigateurs anciens), la carte reste une colonne
   flex : bouton sous la description, simplement pas aligné d'une carte à l'autre. */
.pricing-block--cta-top .pricing-block__head{ display:flex; flex-direction:column; gap:1rem; }
.pricing-block--cta-top .pricing-block__cta{ margin-top:0; }
@supports (grid-template-rows: subgrid){
  .pricing-block--cta-top > .pricing-block__plan{ display:grid; grid-row: span 3; grid-template-rows: subgrid; row-gap:1rem; align-content:start; }
}
/* pricing-block — additions DOM brique (DNA-BLOCKS-SPEC §3.6) */
.pricing-block__plan{ position: relative; }
.pricing-block__badge{ position: absolute; top: 0; left: 50%; transform: translate(-50%, -50%); white-space: nowrap; }
/* Sur une bande FORTE, l'étiquette « Populaire » prend l'encre de SA carte en
   fond et la surface de la carte en texte : les couleurs d'étiquette de la bande
   y tombaient à 1,7-3,7:1 chez ATELIER, BOTANIQUE, CROQUIS, NORDIQUE, NOUVEAU
   (axe-core, 2026-09-28). Le fond est l'encre de la carte (toujours opaque) et le
   texte, blanc ou noir selon ce fond (`--tw-sur-fond`) : pas la surface de la
   carte, translucide chez AQUA, AURORE ou LIQUID. C'est la pastille « Most
   popular » de Stripe ou Linear. */
[data-palette="bold"] .pricing-block__badge.tw-badge{ background: var(--tw-on-surface, var(--tw-text, #111)); color: oklab(from var(--tw-on-surface, var(--tw-text, #111)) var(--tw-sur-fond) 0 0 / 1); border-color: transparent; }
/* Icône centrée sur la PREMIÈRE ligne : (interligne 1,45em − icône 1,1em) / 2. */
.pricing-block__feature .tw-icon{ --tw-icon-size: 1.1em; flex: none; margin-top: .175em; }
.pricing-block__feature--excluded .pricing-block__feature-text{ text-decoration: line-through; }
.pricing-block__feature--excluded .tw-icon{ --tw-icon-color: var(--tw-text-muted, currentColor); }
.pricing-block--comparison .pricing-block__features{ border-top: var(--tw-border-width,1px) solid var(--tw-rule); padding-top: 1rem; margin-top: .4rem; }

/* testimonial-block — preuve sociale (colonnes via .tw-grid ; conteneur
   `testimonial-block testimonial-block--{variant} tw-grid`). DNA-BLOCKS-SPEC §3.5.
   item = .tw-surface (card pour `cards`, bare sinon ; carte non cliquable →
   --tw-hover-lift:0 inline sur l'item). */
.testimonial-block .tw-surface{ display:flex; flex-direction:column; gap:1rem; align-self:stretch; } /* pas de height:100% : cf. features-block */
.testimonial-block__rating{ margin-bottom:.25rem; }
.testimonial-block__quote{ flex:1; margin:0; }
.testimonial-block__author-info{ display:flex; align-items:center; gap:.8rem; margin-top:auto; }
.testimonial-block__author-details{ display:flex; flex-direction:column; gap:.1rem; }
.testimonial-block__photo.tw-media--avatar{ width:3rem; height:3rem; flex:none; }
.testimonial-block__photo.tw-media--avatar img{ width:100%; height:100%; object-fit:cover; }
.testimonial-block__photo--initials{ display:flex; align-items:center; justify-content:center; font-weight:600; background:var(--tw-accent-soft, rgba(128,128,128,.12)); color:var(--tw-on-surface, inherit); }
.testimonial-block--no-rating .testimonial-block__rating{ display:none; }
.testimonial-block--no-photos .testimonial-block__photo{ display:none; }
.testimonial-block--centered{ text-align:center; }
.testimonial-block--centered .tw-surface{ align-items:center; }
.testimonial-block--centered .testimonial-block__rating,
.testimonial-block--centered .testimonial-block__author-info{ justify-content:center; }
.testimonial-block--centered .testimonial-block__author-info{ flex-direction:column; align-items:center; }
.testimonial-block--minimal{ grid-template-columns: 1fr; }
.testimonial-block--minimal .tw-surface + .tw-surface{ border-top:var(--tw-border-width,1px) solid var(--tw-rule); padding-top:var(--tw-gap, 1.5rem); }

/* logo-cloud-block — bande de logos (DNA-BLOCKS-SPEC §3.8). DNA-agnostique : layout + briques.
   image = brique .tw-media tw-media--logo ; filtre grayscale via le token --tw-media-filter. */
.logo-cloud-block{ display:block; width:100%; }
.logo-cloud-block__grid{ display:grid; grid-template-columns: repeat(var(--logo-cols-desktop, 5), 1fr); align-items:center; gap: var(--tw-gap, 1.5rem); }
@media (max-width:1024px){ .logo-cloud-block__grid{ grid-template-columns: repeat(var(--logo-cols-tablet, 4), 1fr); } }
@media (max-width:640px){  .logo-cloud-block__grid{ grid-template-columns: repeat(var(--logo-cols-mobile, 3), 1fr); } }
.logo-cloud-block--align-left  .logo-cloud-block__grid{ justify-items:start; }
.logo-cloud-block--align-center .logo-cloud-block__grid,
.logo-cloud-block--align-right  .logo-cloud-block__grid{ display:flex; flex-wrap:wrap; }
.logo-cloud-block--align-center .logo-cloud-block__grid{ justify-content:center; }
.logo-cloud-block--align-right  .logo-cloud-block__grid{ justify-content:flex-end; }
.logo-cloud-block--align-center .logo-cloud-block__item,
.logo-cloud-block--align-right  .logo-cloud-block__item{ flex: 0 0 calc((100% - (var(--logo-cols-desktop, 5) - 1) * var(--tw-gap, 1.5rem)) / var(--logo-cols-desktop, 5)); max-width: calc((100% - (var(--logo-cols-desktop, 5) - 1) * var(--tw-gap, 1.5rem)) / var(--logo-cols-desktop, 5)); }
@media (max-width:1024px){
  .logo-cloud-block--align-center .logo-cloud-block__item,
  .logo-cloud-block--align-right  .logo-cloud-block__item{ flex: 0 0 calc((100% - (var(--logo-cols-tablet, 4) - 1) * var(--tw-gap, 1.5rem)) / var(--logo-cols-tablet, 4)); max-width: calc((100% - (var(--logo-cols-tablet, 4) - 1) * var(--tw-gap, 1.5rem)) / var(--logo-cols-tablet, 4)); }
}
@media (max-width:640px){
  .logo-cloud-block--align-center .logo-cloud-block__item,
  .logo-cloud-block--align-right  .logo-cloud-block__item{ flex: 0 0 calc((100% - (var(--logo-cols-mobile, 3) - 1) * var(--tw-gap, 1.5rem)) / var(--logo-cols-mobile, 3)); max-width: calc((100% - (var(--logo-cols-mobile, 3) - 1) * var(--tw-gap, 1.5rem)) / var(--logo-cols-mobile, 3)); }
}
.logo-cloud-block__item{ display:flex; align-items:center; justify-content:center; }
.logo-cloud-block__image{ max-width:100%; width:auto; height:auto; min-height:20px; object-fit:contain; display:block; }
/* N&B : filtre piloté par un token C (le DNA calcule la nuance selon la bande) — sur le flood
   bold, dna-c re-pointe --tw-logo-mono-filter en silhouette claire (sinon gris-sur-terracotta
   se fond). Défaut = grayscale(1) (logos gris foncé sur fond clair). */
.logo-cloud-block--grayscale .logo-cloud-block__image{ --tw-media-filter: var(--tw-logo-mono-filter, grayscale(1)); opacity:.75; }
.logo-cloud-block--grayscale-hover .logo-cloud-block__image{ --tw-media-filter: var(--tw-logo-mono-filter, grayscale(1)); opacity:.75; transition: filter .3s ease, opacity .3s ease; }
.logo-cloud-block--grayscale-hover .logo-cloud-block__item:hover .logo-cloud-block__image{ --tw-media-filter: none; opacity:1; }
.logo-cloud-block--color .logo-cloud-block__image{ --tw-media-filter: none; opacity:1; }
/* #497 — un logo sans image s'écrit par son nom, en police de titre, comme une
   marque : atténué comme les logos N&B, plein au survol. */
.logo-cloud-block__name{ font-family:var(--tw-font-heading, inherit); font-weight:var(--tw-weight-heading, 700); font-size:clamp(1rem, 1.8vw, 1.3rem); line-height:1.15; letter-spacing:-.01em; text-align:center; color:var(--tw-text-heading, inherit); overflow-wrap:anywhere; }
.logo-cloud-block--grayscale .logo-cloud-block__name,
.logo-cloud-block--grayscale-hover .logo-cloud-block__name{ opacity:.7; transition: opacity .3s ease; }
.logo-cloud-block--grayscale-hover .logo-cloud-block__item:hover .logo-cloud-block__name{ opacity:1; }

/* ============================================================================
   social-block — DNA-BLOCKS-SPEC §3.8 (brique .tw-icon, gap=--tw-gap).
   Toutes les valeurs CSS utilisent UNIQUEMENT des tokens --tw-*.
   Les couleurs de la colorStyle "brand" sont injectées via --tw-icon-color inline.
   ============================================================================ */
.social-block{ display:block; width:100%; }
.social-block__links{ display:flex; flex-wrap:wrap; align-items:center; gap:var(--tw-gap,1rem); }

/* Alignment — applied to the flex links container */
.social-block--align-left  .social-block__links{ justify-content:flex-start; }
.social-block--align-center .social-block__links{ justify-content:center; }
.social-block--align-right  .social-block__links{ justify-content:flex-end; }

/* Icon size — sets --tw-icon-size so .tw-icon reads it (no inline px) */
.social-block--size-small  .tw-icon{ --tw-icon-size: 2rem; }   /* 32px */
.social-block--size-medium .tw-icon{ --tw-icon-size: 2.5rem; } /* 40px */
.social-block--size-large  .tw-icon{ --tw-icon-size: 3.25rem; }/* 52px */

/* Color styles — override --tw-icon-color; brand is set inline via style attr */
/* color-inherit = la couleur d'icône de la PALETTE (--tw-icon-color, token C re-pointé par
   [data-palette] → cream sur bold). PAS de var(--tw-accent) (G, jamais re-pointé → terracotta
   sur le flood terracotta = invisible). La brique .tw-icon consomme déjà --tw-icon-color → on
   ne l'override pas (contrat §6 « Règle dérivée »). */
.social-block--color-monochrome .tw-icon{ --tw-icon-color: var(--tw-text-muted, currentColor); opacity:.6; }
.social-block--color-white      .tw-icon{ --tw-icon-color: #ffffff; }
.social-block--color-black      .tw-icon{ --tw-icon-color: #000000; }

/* Link interaction */
.social-block__link{ text-decoration:none; transition:opacity .2s, transform .2s; }
.social-block__link:hover{ opacity:.8; transform:scale(1.1); }

/* faq-block — accordéon natif <details>/<summary> + variantes list/cards.
   DNA-BRICKS (2026-06-08): question=.tw-h4, answer=.tw-body,
   indicateur=.tw-icon[data-glyph], dividers=<hr class="tw-rule"> (non-accordion)
   ou border-top (accordion). Cards variant root=.tw-grid. --tw-* ONLY. */
.faq-block{ display:flex; flex-direction:column; }
/* cards variant uses .tw-grid co-applied on root; .tw-grid already sets display:grid */

/* item */
.faq-block__item{ padding-block: var(--tw-faq-item-py, 1rem); }
/* accordion dividers: border-top between consecutive items */
.faq-block--accordion.faq-block--dividers .faq-block__item + .faq-block__item{ border-top: var(--tw-border-width,1px) solid var(--tw-rule, rgba(0,0,0,.12)); }
/* non-accordion dividers: <hr class="tw-rule"> in the DOM */
.faq-block:not(.faq-block--accordion) hr.tw-rule{ display:block; height:0; border:0; border-top: var(--tw-border-width,1px) solid var(--tw-rule, rgba(0,0,0,.12)); margin:0; padding:0; }
/* cards: item as .tw-surface (card) handled by .tw-surface rules */

/* question row (.tw-h4 brick + .tw-icon indicator) */
.faq-block__question{ display:flex; justify-content:space-between; align-items:center; gap: var(--tw-faq-q-gap,.75rem); cursor:pointer; list-style:none; user-select:none; }
.faq-block__question::-webkit-details-marker{ display:none; }

/* indicator: .tw-icon[data-glyph] brick painted in currentColor (accent) */
.faq-block__indicator{ flex:none; transition: transform var(--tw-faq-speed,.2s) ease; }
.faq-block--icon-chevron details.faq-block__item[open] .faq-block__indicator{ transform:rotate(90deg); }
.faq-block--icon-plus details.faq-block__item[open] .faq-block__indicator{ transform:rotate(45deg); }

/* answer */
.faq-block__answer{ padding-top: var(--tw-faq-answer-pt,.6rem); max-width:70ch; }
/* .tw-body class on inner div handles font/color/line-height */

/* CSS-grid expand animation for <details> open/close (no JS, no height:auto flash).
   The inner .tw-body div is the overflow guard (min-height:0 = 0fr collapses it). */
.faq-block--accordion .faq-block__answer{ display:grid; grid-template-rows:0fr; transition:grid-template-rows var(--tw-faq-speed,.3s) ease; }
.faq-block--accordion details.faq-block__item[open] .faq-block__answer{ grid-template-rows:1fr; }
.faq-block--accordion .faq-block__answer > *{ overflow:hidden; min-height:0; }

/* article-block — corps long-form, option MULTI-COLONNES (presse/letterpress).
   column-rule + justify/hyphens token-driven, DNA-agnostique. */
.article-block{ max-width: 70ch; margin-inline: auto; }
.article-block--multicol{ max-width: 960px; column-count: var(--tw-article-cols, 2); column-gap: var(--tw-gap, 2.5rem); column-rule: 1px solid var(--tw-rule); }
.article-block--multicol > *{ break-inside: avoid; }
.article-block--justify .tw-body{ text-align: justify; hyphens: auto; -webkit-hyphens: auto; }
@media (max-width: 760px){ .article-block--multicol{ column-count: 1; } }
.article-block--justify :is(p, li){ text-align: justify; hyphens: auto; -webkit-hyphens: auto; }
.article-block--dropcap > :first-child::first-letter{ font-family: var(--tw-dropcap-font, var(--tw-font-heading, serif)); font-size: var(--tw-dropcap-size, 3.2em); line-height:.8; float:left; margin:.05em .12em 0 0; color: var(--tw-dropcap-color, var(--tw-accent, inherit)); }

/* pricelist-block — liste tarifaire tabulaire à POINTS DE CONDUITE (specimen ancien).
   Différent de pricing-block (cartes). nom … prix, via un trait pointillé extensible. */
.pricelist-block{ display:flex; flex-direction:column; gap:.8rem; }
.pricelist-block__row{ display:grid; grid-template-columns: auto 1fr auto; align-items:baseline; gap:.4rem; border-bottom: var(--tw-border-width,1px) solid var(--tw-rule); padding-bottom:.5rem; }
.pricelist-block__dots{ border-bottom: 1px dotted var(--tw-text-muted, currentColor); height: 1.05em; min-width: 24px; align-self: end; margin: 0 .4em .35em; }
.pricelist-block__price{ white-space: nowrap; font-variant-numeric: tabular-nums; }

/* index-list — liste d'archive/index tabulaire (meta │ titre │ flèche), filets pleins
   entre lignes (Swiss/éditorial). Différent de pricelist (points de conduite). */
.index-list{ display:flex; flex-direction:column; }
.index-list__row{ display:grid; grid-template-columns: auto 1fr auto; align-items:baseline; gap: clamp(1rem,3vw,2rem); padding-block: 1.1rem; border-top: var(--tw-border-width,1px) solid var(--tw-rule); }
.index-list__row:last-child{ border-bottom: var(--tw-border-width,1px) solid var(--tw-rule); }
.index-list__meta{ font-variant-numeric: tabular-nums; color: var(--tw-text-muted, inherit); white-space: nowrap; }

/* numbered-list — étapes/process à GRAND chiffre tabulaire (01. 02. …) + texte. */
.numbered-list{ display:flex; flex-direction:column; gap: var(--tw-gap, 2rem); }
.numbered-list__item{ display:grid; grid-template-columns: auto 1fr; gap: clamp(1rem,2.5vw,2rem); align-items:baseline; }
.numbered-list__num{ font-variant-numeric: tabular-nums; font-family: var(--tw-font-heading, inherit); font-weight: var(--tw-weight-heading, 700); font-size: clamp(2rem,4vw,3rem); line-height: 1; }

/* ============================================================================
   BLOCK — TIMELINE
   DNA-BLOCKS-SPEC §3.7. Trois variantes de rail (vertical/alternating/horizontal).
   RÈGLE D'OR : couleurs via --tw-* uniquement. PAS de var(--theme-*).
   Connecteur = §8 escape-hatch (geometry pur ; couleur = --tw-rule/--tw-rule-strong).
   Marker = .tw-icon brick (well pour dot, mask pour SVG icône).
   ============================================================================ */

/* Base ─────────────────────────────────────────────────────────────────────── */
.timeline-block {
  position: relative;
  width: 100%;
}

/* ── VERTICAL (default) : rail gauche ─────────────────────────────────────── */
.timeline-block--vertical {
  display: flex;
  flex-direction: column;
  gap: var(--tw-gap, 1.5rem);
}

.timeline-block--vertical .timeline-block__item {
  display: flex;
  align-items: flex-start;
  gap: 1rem;
  position: relative;
  /* room for marker (2.5rem) + gap (1rem) = 3.5rem */
  padding-inline-start: 3.5rem;
}

/* Marker sits at absolute left, centered on connector line */
.timeline-block--vertical .timeline-block__marker {
  position: absolute;
  inset-inline-start: 0;
  top: 0;
}

/* Connector: vertical line between markers.
   Color = --tw-rule (§8 sanctioned escape-hatch: geometry is plain, color is token). */
.timeline-block--vertical.timeline-block--with-connector::before {
  content: '';
  position: absolute;
  /* center line through 2.5rem-wide marker */
  inset-inline-start: calc(1.25rem - 1px);
  top: 1.25rem;
  bottom: 1.25rem;
  width: 2px;
  background: var(--tw-rule-strong, var(--tw-rule, rgba(0,0,0,.18)));
}

/* ── ALTERNATING : rail centre, zig-zag ──────────────────────────────────── */
.timeline-block--alternating {
  display: flex;
  flex-direction: column;
  position: relative;
  gap: var(--tw-gap, 1.5rem);
}

.timeline-block--alternating .timeline-block__item {
  display: flex;
  align-items: flex-start;
  gap: 1rem;
  position: relative;
  width: calc(50% - 1.25rem);
}

/* Odd: left side, right-aligned text */
.timeline-block--alternating .timeline-block__item:nth-child(odd) {
  align-self: flex-start;
  text-align: end;
  flex-direction: row-reverse;
  padding-inline-end: 2rem;
}

/* Even: right side, left-aligned text */
.timeline-block--alternating .timeline-block__item:nth-child(even) {
  align-self: flex-end;
  padding-inline-start: 2rem;
}

.timeline-block--alternating .timeline-block__item:nth-child(odd) .timeline-block__marker {
  position: absolute;
  inset-inline-end: calc(-1.25rem - 1.25rem);
}

.timeline-block--alternating .timeline-block__item:nth-child(even) .timeline-block__marker {
  position: absolute;
  inset-inline-start: calc(-1.25rem - 1.25rem);
}

/* Connector: centered vertical line */
.timeline-block--alternating.timeline-block--with-connector::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 1.25rem;
  bottom: 1.25rem;
  width: 2px;
  background: var(--tw-rule-strong, var(--tw-rule, rgba(0,0,0,.18)));
  transform: translateX(-50%);
}

/* Mobile: collapse alternating → single left-rail column */
@media (max-width: 767px) {
  .timeline-block--alternating .timeline-block__item {
    width: 100%;
    padding-inline-start: 3.5rem;
    padding-inline-end: 0;
    text-align: start;
    flex-direction: row;
  }
  /* ⚠️ Impair ET pair, comme sur bureau : sinon la règle du bureau (0,3,0) gardait
     le texte aligné à droite (impair) et l'ancien retrait de 2rem (pair) — la date
     recouvrait l'icône sur un téléphone (audit des 48 DNA, 2026-09-28). */
  .timeline-block--alternating .timeline-block__item:nth-child(odd),
  .timeline-block--alternating .timeline-block__item:nth-child(even) {
    align-self: flex-start;
    flex-direction: row;
    text-align: start;
    padding-inline-start: 3.5rem;
    padding-inline-end: 0;
  }
  .timeline-block--alternating .timeline-block__item:nth-child(odd) .timeline-block__marker,
  .timeline-block--alternating .timeline-block__item:nth-child(even) .timeline-block__marker {
    position: absolute;
    inset-inline-start: 0;
    inset-inline-end: auto;
  }
  .timeline-block--alternating.timeline-block--with-connector::before {
    inset-inline-start: calc(1.25rem - 1px);
    transform: none;
  }
}

/* ── HORIZONTAL : rail haut, scroll ─────────────────────────────────────── */
.timeline-block--horizontal {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--tw-gap, 1.5rem);
}

.timeline-block--horizontal .timeline-block__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  flex: 0 1 200px;
  min-width: 150px;
  max-width: 250px;
  position: relative;
}

/* Horizontal connector disabled (doesn't work with flex-wrap) */
.timeline-block--horizontal.timeline-block--with-connector::before {
  display: none;
}

.timeline-block--horizontal .timeline-block__marker {
  margin-bottom: 0.75rem;
}

.timeline-block--horizontal .timeline-block__content {
  width: 100%;
}

/* ── MARKER sizing (overrides .tw-icon default em-based sizing) ─────────── */
/* Marker = pastille de FOND (couleur de la bande) qui MASQUE le connecteur qui
   passerait derrière (sinon le trait traverse l'icône) + icône/point centré.
   z-index au-dessus du connecteur. L'icône est l'enfant masqué (.tw-icon), pas le
   marker lui-même → le marker peut porter un fond opaque. */
.timeline-block__marker {
  width: 2.5rem;
  height: 2.5rem;
  flex-shrink: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--tw-bg, var(--tw-page-bg, #fafafa));
  border-radius: 50%;
}
/* Point (pas d'icône) : petit disque accent. */
.timeline-block__marker-dot {
  width: 0.7rem;
  height: 0.7rem;
  /* token C (re-pointé par [data-palette]) — PAS --tw-accent (G, jamais re-pointé →
     accent-sur-flood = invisible sur bold). Contrat §4-O / §6 « Règle dérivée ». */
  background: var(--tw-icon-color, var(--tw-accent, currentColor));
  border-radius: 50%;
}
/* Icône SVG : c'est CE span qui porte le mask (.tw-icon[data-icon-svg]). */
.timeline-block__marker-icon {
  --tw-icon-size: 1.4rem;
  color: var(--tw-icon-color, var(--tw-accent, currentColor));
}

/* ── CONTENT ─────────────────────────────────────────────────────────────── */
.timeline-block__content {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
/* date (.tw-meta) · title (.tw-h4) · description (.tw-body) — all brick-driven */

/* ============================================================================
   team-block — grille d'équipe (DNA-BLOCKS-SPEC §3.5). Colonnes via .tw-grid sur
   .team-block__grid ; conteneur = team-block--{variant} ; item = article.tw-surface.
   --tw-* ONLY. Gap = --tw-gap (DNA-owned).
   ============================================================================ */
/* tw-media--square / --rounded : variantes radius du wrapper photo (circle = tw-media--avatar). */
.tw-media--square { border-radius: 0; }
.tw-media--rounded { border-radius: var(--tw-radius-card, 16px); }

/* === 4 VARIANTS distincts (cards/minimal/portrait/row), bâtis sur les bricks === */
/* Base : photo avatar 1:1 (cards/minimal/row), membre = colonne centrée. */
.team-block__photo-wrapper.tw-media { width: var(--team-avatar-size, 6.5rem); height: var(--team-avatar-size, 6.5rem); flex: none; }
.team-block__photo-wrapper.tw-media img { width: 100%; height: 100%; object-fit: cover; }
/* Taille de la photo (#19) : les grands médaillons des cartes de visite V14
   (150 à 360 px) devenaient des avatars. Jamais plus large que la colonne. */
.team-block--size-small .team-block__photo-wrapper.tw-media { --team-avatar-size: 4.5rem; }
.team-block--size-large .team-block__photo-wrapper.tw-media { --team-avatar-size: 10rem; }
.team-block--size-xlarge .team-block__photo-wrapper.tw-media { --team-avatar-size: 14rem; }
.team-block:is(.team-block--size-small, .team-block--size-large, .team-block--size-xlarge) .team-block__photo-wrapper.tw-media { width: min(var(--team-avatar-size), 100%); height: auto; aspect-ratio: 1 / 1; }
.team-block__photo--initials { display: flex; align-items: center; justify-content: center; font-weight: 600; background: var(--tw-accent-soft, rgba(128,128,128,.12)); color: var(--tw-on-surface, inherit); }

.team-block__member { display: flex; flex-direction: column; align-items: center; text-align: center; gap: var(--tw-gap, 1rem); align-self: stretch; } /* pas de height:100% : cf. features-block */
.team-block__info { display: flex; flex-direction: column; gap: .3rem; }
.team-block__socials { display: flex; gap: .5rem; justify-content: center; margin-top: .5rem; }
.team-block__social-link.tw-icon { --tw-icon-size: 1.4rem; }
.team-block--no-bio .team-block__bio { display: none; }
.team-block--no-socials .team-block__socials { display: none; }

/* PORTRAIT : grande photo RECTANGULAIRE 4:5 pleine largeur (image dominante, éditorial). */
.team-block--portrait .team-block__photo-wrapper.tw-media { width: 100%; height: auto; aspect-ratio: 4 / 5; }
.team-block--portrait .team-block__member { gap: var(--tw-gap, 1.1rem); }

/* ROW : photo à gauche + texte à droite (horizontal, bios longues). */
.team-block--row .team-block__member { flex-direction: row; align-items: center; text-align: left; }
.team-block--row .team-block__photo-wrapper.tw-media { --team-avatar-size: 5rem; }
.team-block--row .team-block__info { align-items: flex-start; }
.team-block--row .team-block__socials { justify-content: flex-start; }

/* photoFilter → variante token-driven de .tw-media (filtre posé sur le wrapper).
   Set partagé avec le picker SidebarPickers (gallery/image). */
.team-block--filter-grayscale .team-block__photo-wrapper { --tw-media-filter: grayscale(1); }
.team-block--filter-inkwell   .team-block__photo-wrapper { --tw-media-filter: sepia(.3) contrast(1.1) brightness(1.1) grayscale(1); }
.team-block--filter-matte     .team-block__photo-wrapper { --tw-media-filter: contrast(.85) brightness(1.1) saturate(.7); }
.team-block--filter-vivid     .team-block__photo-wrapper { --tw-media-filter: contrast(1.15) saturate(1.5) brightness(1.05); }
.team-block--filter-reyes     .team-block__photo-wrapper { --tw-media-filter: sepia(.22) brightness(1.1) contrast(.85) saturate(.75); }
.team-block--filter-willow    .team-block__photo-wrapper { --tw-media-filter: grayscale(.5) contrast(.95) brightness(.9); }
.team-block--filter-moon      .team-block__photo-wrapper { --tw-media-filter: grayscale(1) contrast(1.1) brightness(1.1); }
.team-block--filter-lofi      .team-block__photo-wrapper { --tw-media-filter: contrast(1.5) saturate(1.1); }
.team-block--filter-clarendon .team-block__photo-wrapper { --tw-media-filter: contrast(1.2) saturate(1.35); }
.team-block--filter-1977      .team-block__photo-wrapper { --tw-media-filter: contrast(1.1) brightness(1.1) saturate(1.3) sepia(.1); }
.team-block--filter-aden      .team-block__photo-wrapper { --tw-media-filter: hue-rotate(-20deg) contrast(.9) saturate(.85) brightness(1.2); }
.team-block--filter-brannan   .team-block__photo-wrapper { --tw-media-filter: sepia(.5) contrast(1.4); }
.team-block--filter-brooklyn  .team-block__photo-wrapper { --tw-media-filter: contrast(.9) brightness(1.1) sepia(.05); }
.team-block--filter-gingham   .team-block__photo-wrapper { --tw-media-filter: brightness(1.05) hue-rotate(-10deg) saturate(.9); }
.team-block--filter-hudson    .team-block__photo-wrapper { --tw-media-filter: brightness(1.2) contrast(.9) saturate(1.1) hue-rotate(5deg); }
.team-block--filter-maven     .team-block__photo-wrapper { --tw-media-filter: sepia(.25) brightness(.95) contrast(.95) saturate(1.5) hue-rotate(-10deg); }
.team-block--filter-nashville .team-block__photo-wrapper { --tw-media-filter: sepia(.25) contrast(1.2) brightness(1.05) saturate(1.2) hue-rotate(-5deg); }
.team-block--filter-rise      .team-block__photo-wrapper { --tw-media-filter: brightness(1.05) sepia(.2) contrast(.9) saturate(.9); }
.team-block--filter-valencia  .team-block__photo-wrapper { --tw-media-filter: contrast(1.08) brightness(1.08) sepia(.15) saturate(1.1); }
.team-block--filter-walden    .team-block__photo-wrapper { --tw-media-filter: brightness(1.1) hue-rotate(-10deg) sepia(.3) saturate(1.6); }
.team-block--filter-duo-cyan   .team-block__photo-wrapper { --tw-media-filter: grayscale(1) sepia(1) hue-rotate(150deg) saturate(2.5) contrast(1.1); }
.team-block--filter-duo-rose   .team-block__photo-wrapper { --tw-media-filter: grayscale(1) sepia(1) hue-rotate(310deg) saturate(1.8) brightness(1.05); }
.team-block--filter-duo-amber  .team-block__photo-wrapper { --tw-media-filter: grayscale(1) sepia(1) hue-rotate(15deg) saturate(2) contrast(1.05); }
.team-block--filter-duo-violet .team-block__photo-wrapper { --tw-media-filter: grayscale(1) sepia(1) hue-rotate(230deg) saturate(2) brightness(.95); }

/* ============================================================================
   columns-block — layout 2 à 4 colonnes (DNA-BLOCKS-SPEC §3.1). PAS de brique :
   compose les briques de ses enfants. GRILLE : nombre via data-cols="2|3|4" ; le
   ratio 2-col (50-50/33-66/66-33) via data-layout (seulement à data-cols="2", les
   3/4 colonnes sont toujours égales). gap = --tw-gap ; valign top(défaut)/center/
   bottom/stretch ; reverseOnMobile. --tw-* ONLY.
   ============================================================================ */
.columns-block{ display:grid; gap: var(--tw-gap, 1.5rem); grid-template-columns: repeat(2, minmax(0,1fr)); align-items: start; }
.columns-block[data-cols="3"]{ grid-template-columns: repeat(3, minmax(0,1fr)); }
.columns-block[data-cols="4"]{ grid-template-columns: repeat(4, minmax(0,1fr)); }
/* 2-col ratios (only meaningful at data-cols="2") */
.columns-block[data-cols="2"][data-layout="33-66"]{ grid-template-columns: 1fr 2fr; }
.columns-block[data-cols="2"][data-layout="66-33"]{ grid-template-columns: 2fr 1fr; }
.columns-block--valign-center{ align-items:center; }
.columns-block--valign-bottom{ align-items:end; }
.columns-block--valign-stretch{ align-items:stretch; }
.column{ min-width:0; }
/* responsive: 4→2 on tablet, everything→1 stacked on mobile */
@media (max-width: 1100px){ .columns-block[data-cols="4"]{ grid-template-columns: repeat(2, minmax(0,1fr)); } }
@media (max-width: 760px){
  .columns-block{ grid-template-columns: 1fr !important; }
  .columns-block--reverse-mobile{ direction: rtl; }
  .columns-block--reverse-mobile > .column{ direction: ltr; }
}

/* ============================================================================
   MOUVEMENT PREMIUM (`astro-runtime/src/scripts/motion.ts`) — Yann, 2026-09-26.
   Chaque règle ne s'applique qu'aux classes que le SCRIPT pose (`tw-defile`,
   `tw-titre-anime`, `tw-halo`…) : dans l'éditeur, qui ne l'exécute pas, et pour
   un visiteur qui demande moins de mouvement, le contenu reste à son état final.
   ============================================================================ */

/* Fonds génératifs : le canvas remplit la couche d'atmosphère. */
.cta-block[data-os-mode="only"]:has(> [data-os="macos"]) > .tw-button[data-os]:not([data-os="macos"]):is(html[data-os="macos"] *),
.cta-block[data-os-mode="only"]:has(> [data-os="windows"]) > .tw-button[data-os]:not([data-os="windows"]):is(html[data-os="windows"] *),
.cta-block[data-os-mode="only"]:has(> [data-os="linux"]) > .tw-button[data-os]:not([data-os="linux"]):is(html[data-os="linux"] *) { display: none; }
/* La ligne « aussi pour… » : seulement les systèmes dont le bouton est caché. */
.cta-block__autres-os { display: none; flex-basis: 100%; margin: .25rem 0 0; gap: .35rem 1.25rem; flex-wrap: wrap; font-size: .9em; }
.cta-block--align-center > .cta-block__autres-os { justify-content: center; }
.cta-block--align-right > .cta-block__autres-os { justify-content: flex-end; }
.cta-block__autres-os > .tw-cta-link { display: none; }
html:is([data-os="macos"], [data-os="windows"], [data-os="linux"]) .cta-block[data-os-mode="only"] > .cta-block__autres-os:has(> [data-os]) { display: flex; }
html[data-os="macos"] .cta-block__autres-os > [data-os]:not([data-os="macos"]),
html[data-os="windows"] .cta-block__autres-os > [data-os]:not([data-os="windows"]),
html[data-os="linux"] .cta-block__autres-os > [data-os]:not([data-os="linux"]) { display: inline; }
/* Aucun bouton du système du visiteur (Linux sur un groupe Windows/macOS) : tous
   les boutons restent affichés, la ligne n'a rien à ajouter. */
html[data-os="linux"] .cta-block[data-os-mode="only"]:not(:has(> .tw-button[data-os="linux"])) > .cta-block__autres-os,
html[data-os="macos"] .cta-block[data-os-mode="only"]:not(:has(> .tw-button[data-os="macos"])) > .cta-block__autres-os,
html[data-os="windows"] .cta-block[data-os-mode="only"]:not(:has(> .tw-button[data-os="windows"])) > .cta-block__autres-os { display: none; }
.tw-atm-canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.content-section[data-atmosphere="mesh"] > .tw-backdrop--section .tw-atm-canvas { filter: blur(24px) saturate(1.15); transform: scale(1.1); }

/* Titres : chaque mot sort d'une fente (rise) ou d'un flou (blur). */
.tw-titre-anime .tw-mot { display: inline-block; vertical-align: top; }
/* ⚠️ L'AILE DES GLYPHES PASSE PAR UN PADDING, PAS PAR `overflow-clip-margin` SEUL :
   Safari ne connaît pas cette propriété (2026-09) et tranchait à la boîte du mot le
   débord droit d'un italique (« chosen », « write to ») et même le « : » de
   « TOWeb 15 : » (captures de Yann, staging et démo). Le padding élargit la
   fenêtre sur les quatre côtés ; la marge négative rend la place prise. */
.tw-titre-anime--rise .tw-mot {
  overflow: clip; overflow-clip-margin: 0.12em;
  padding: 0 0.14em 0.08em; margin: 0 -0.14em -0.08em;
}
.tw-titre-anime .tw-mot__dedans { display: inline-block; will-change: transform; }
.tw-titre-anime--rise .tw-mot__dedans {
  transform: translate3d(0, 108%, 0) rotate(4deg);
  transition: transform 1s cubic-bezier(0.19, 1, 0.22, 1) calc(var(--mv-i, 0) * 75ms + 80ms);
}
.tw-titre-anime--blur .tw-mot__dedans {
  opacity: 0; filter: blur(14px); transform: translate3d(0, 0.35em, 0) scale(1.04);
  transition: opacity 0.9s ease calc(var(--mv-i, 0) * 70ms + 80ms),
              filter 0.9s ease calc(var(--mv-i, 0) * 70ms + 80ms),
              transform 1.1s cubic-bezier(0.19, 1, 0.22, 1) calc(var(--mv-i, 0) * 70ms + 80ms);
}
.tw-titre-anime--parti .tw-mot__dedans { transform: none; opacity: 1; filter: none; }
/* ⚠️ LE SOUS-TITRE N'EST JAMAIS INVISIBLE (Lighthouse, 2026-09-29) : c'est lui, le
   plus grand texte du héros sur téléphone, donc l'élément LCP. Parti de
   `opacity: 0`, il n'existait pour le navigateur qu'une fois le fondu commencé
   (0,55 s après le titre) : le LCP attendait l'animation. Il part d'un texte
   lisible, atténué, et le fondu le porte à pleine intensité. */
.tw-titre-anime .tw-titre-anime__suite { opacity: 0.5; transform: translate3d(0, 14px, 0); transition: opacity 0.9s ease 0.55s, transform 1s cubic-bezier(0.19, 1, 0.22, 1) 0.55s; }
.tw-titre-anime--parti .tw-titre-anime__suite { opacity: 1; transform: none; }

/* Mot d'accent qui tourne : une piste verticale, le plus long mot réserve la place. */
/* ⚠️ LA FENÊTRE DÉBORDE LA LIGNE DE `--mv-rot-marge` EN HAUT ET EN BAS (padding-block
   compensé par une marge négative : la ligne ne bouge pas), et `overflow-clip-margin`
   y ajoute l'aile de l'italique, comme `.mv-titre-section .tw-mot`. Pourquoi : une
   fenêtre de 1lh tranchait les jambages (p, g) d'une serif italique à interligne
   serré pendant le défilement (démo « Maison Kafé », 2026-09-26). Les mots de la
   piste sont espacés de DEUX marges : le voisin reste hors de la fenêtre agrandie. */
/* ⚠️ `vertical-align: BASELINE`, pas `bottom` : aligner le BAS de la fenêtre sur le
   bas de la ligne ne pose le mot sur la ligne de base du titre que si les deux
   polices ont les mêmes métriques. L'italique d'accent tombait 7 à 12 px trop bas
   (vitrines, 2026-09-28). La ligne de base d'une grille en ligne est celle de son
   premier élément, le gabarit : la même que celle du mot affiché, qui occupe la
   même place dans la fenêtre.
   ⚠️ `box-sizing: CONTENT-BOX` : motion.ts pose en `width` la largeur MESURÉE du
   mot. En border-box, les ailes (padding) s'y retranchaient : « PC » perdait sa
   dernière lettre, tout mot ses 0,28 em de fin.
   ⚠️ PADDING `!important` : c'est la FENÊTRE du mécanisme, pas un habillage.
   `.tw-h1 :is(em, i){ padding: var(--tw-em-pad, 0) }` (0,1,1) l'annulait en
   silence — et les DNA à plaque posent le leur, plus spécifique encore
   (confetti). Sans padding, restaient les marges négatives : la fenêtre, amputée
   de 0,4 em, rognait jambages et ailes, et descendait sous la ligne (vitrines,
   2026-09-28). Une plaque garde son fond : l'aile (0,14 em) vaut son padding. */
.tw-rotation {
  --mv-rot-marge: 0.2em;
  --mv-rot-aile: 0.14em;
  display: inline-grid; vertical-align: baseline; height: 1lh; box-sizing: content-box;
  padding-block: var(--mv-rot-marge) !important; margin-block: calc(-1 * var(--mv-rot-marge));
  overflow: hidden; overflow: clip; overflow-clip-margin: 0.12em;
  /* Même aile en largeur, par padding compensé (Safari ignore overflow-clip-margin) :
     l'italique garde ses deux ailes, la ligne n'en est pas élargie. */
  padding-inline: var(--mv-rot-aile) !important; margin-inline: calc(-1 * var(--mv-rot-aile));
}
.tw-rotation > * { grid-area: 1 / 1; }
/* Le mot lu par les lecteurs d'écran (gabarit et piste sont `aria-hidden`). */
.tw-rotation > .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0; }
.tw-rotation__gabarit { visibility: hidden; white-space: nowrap; }
.tw-rotation__piste { display: flex; flex-direction: column; gap: calc(2 * var(--mv-rot-marge)); white-space: nowrap; transform: translate3d(0, calc(var(--mv-k, 0) * -1 * (1lh + 2 * var(--mv-rot-marge))), 0); transition: transform 0.75s cubic-bezier(0.7, 0, 0.2, 1); }
.tw-rotation__piste > span { display: block; width: max-content; height: 1lh; }
.tw-rotation--mesuree { justify-items: start; transition: width 0.75s cubic-bezier(0.7, 0, 0.2, 1); }
.tw-rotation--mesuree > .tw-rotation__gabarit { width: 0; }
.tw-titre-anime .tw-rotation { opacity: 0; transform: translate3d(0, 0.4em, 0); transition: opacity 0.8s ease 0.35s, transform 1s cubic-bezier(0.19, 1, 0.22, 1) 0.35s, width 0.75s cubic-bezier(0.7, 0, 0.2, 1); }
.tw-titre-anime--parti .tw-rotation { opacity: 1; transform: none; }
/* Le mot d'accent porte souvent un DÉGRADÉ découpé dans le texte (background-clip)
   : ses mots sont maintenant dans des enfants, qui doivent en hériter. */
.tw-rotation__piste, .tw-rotation__piste > span { background: inherit; -webkit-background-clip: text; background-clip: text; }
/* Même chose pour les mots découpés d'un titre animé (effets rise, blur) : un
   mot d'accent en dégradé DISPARAISSAIT, ses morceaux n'héritant pas du fond. */
:is(.tw-titre-anime, .mv-titre-section) em :is(.tw-mot, .tw-mot__dedans) { background: inherit; -webkit-background-clip: text; background-clip: text; }

/* Images pilotées par le défilement : --mv-p va de 0 (l'image entre) à 1 (elle est au centre). */
.tw-defile { --mv-p: 1; --mv-q: 0.5; }
.tw-defile :is(img, video) { will-change: transform; }
.tw-defile[data-scroll-effect="tilt"] { perspective: 1600px; }
.tw-defile[data-scroll-effect="tilt"] :is(img, video) {
  transform-origin: 50% 0%;
  transform: rotateX(calc((1 - var(--mv-p)) * 32deg)) scale(calc(0.86 + var(--mv-p) * 0.14)) translate3d(0, calc((1 - var(--mv-p)) * 40px), 0);
}
.tw-defile[data-scroll-effect="zoom"] :is(img, video) { transform: scale(calc(0.78 + var(--mv-p) * 0.22)); opacity: calc(0.35 + var(--mv-p) * 0.65); }
.tw-defile[data-scroll-effect="parallax"] { overflow: hidden; border-radius: var(--tw-radius-media, 12px); }
.tw-defile[data-scroll-effect="parallax"] :is(img, video) { transform: scale(1.18) translate3d(0, calc((var(--mv-q) - 0.5) * -14%), 0); }
/* Dévoilement : le CADRE entier (barre de navigateur comprise) s'ouvre du haut
   vers le bas comme un rideau, pendant que l'image, un peu agrandie, se pose.
   L'ancien découpage rognait l'image DANS son cadre de navigateur — « il faut une
   plus belle animation » (Yann, 2026-09-26). Il part d'un tiers visible : jamais
   de trou blanc à l'entrée. Le « tout voir » de l'écran d'édition n'est pas
   concerné : sans le script, `--mv-p` vaut 1 et rien n'est découpé. */
.tw-defile[data-scroll-effect="reveal"] { --lx-rideau: max(0%, calc((0.75 - var(--mv-p)) * 88%)); }
.tw-defile[data-scroll-effect="reveal"] .image-block__media,
.tw-defile[data-scroll-effect="reveal"]:not(:has(.image-block__media)) :is(img, video) {
  clip-path: inset(0 0 var(--lx-rideau) 0 round var(--tw-radius-media, 12px));
  transform: translate3d(0, max(0px, calc((0.75 - var(--mv-p)) * 48px)), 0);
}
/* Sans cadre peint, l'image se pose aussi (légère réduction) ; un cadre de
   navigateur ou de téléphone est peint SUR l'image, on ne le déforme pas. */
.tw-defile[data-scroll-effect="reveal"]:not(.image-block--frame-browser, .image-block--frame-phone) .image-block__media :is(img, video) {
  transform-origin: 50% 0%;
  transform: scale(calc(1.08 - var(--mv-p) * 0.08));
}

/* Cartes : halo qui suit le pointeur, ou inclinaison vers lui. */
[data-hover-effect] :is(.tw-surface, .cards-block__card):has(> .tw-halo) { position: relative; isolation: isolate; }
.tw-halo {
  position: absolute; inset: 0; z-index: -1; border-radius: inherit; pointer-events: none;
  background: radial-gradient(420px circle at var(--mv-mx, 50%) var(--mv-my, 50%), color-mix(in oklab, var(--tw-accent, #6366f1) 18%, transparent), transparent 62%);
  opacity: 0; transition: opacity 0.35s ease;
}
[data-hover-effect] :is(.tw-surface, .cards-block__card):hover > .tw-halo { opacity: 1; }
[data-hover-effect="tilt"] :is(.tw-surface, .cards-block__card) {
  transform: perspective(900px) rotateX(var(--mv-rx, 0deg)) rotateY(var(--mv-ry, 0deg));
  transition: transform 0.35s cubic-bezier(0.2, 0.8, 0.2, 1);
  transform-style: preserve-3d;
}
@media (prefers-reduced-motion: reduce) {
  .tw-halo { display: none; }
  [data-hover-effect="tilt"] :is(.tw-surface, .cards-block__card) { transform: none; }
}

/* ============================================================================
   VARIANTES PREMIUM DE MISE EN PAGE (Yann, 2026-09-26) — des sections qui ne
   se ressemblent plus toutes. Bento et liste numérotée (featuresBlock), rail
   horizontal (cardsBlock `layout: scroll`), mur de témoignages (testimonialBlock
   `wall`), sections empilées (contentSection `data-stack`).
   Les tuiles du bento sont des CLASSES calculées à la génération
   (shared/blocks/premiumVariants.ts), pas des :nth-child : une règle qui dépend
   du nombre d'éléments se trompe dès qu'on en ajoute un.
   Propriétés internes en --lx-* (jamais --tw-* : la sonde exige que chaque DNA
   définisse tout --tw-* lu sans repli). Chaque --tw-* lu a son repli.
   Spécificité : les DNAs visent `[data-dna] .features-block > .tw-surface`
   (0,3,0) et se chargent APRÈS cette feuille — les règles de STRUCTURE d'une
   variante montent donc à (0,5,0) ; la PEAU (fonds, couleurs) reste au DNA.
   ============================================================================ */

/* ── featuresBlock « bento » ─────────────────────────────────────────────── */
.features-block.features-block--bento.tw-grid{
  --lx-bento-gap: max(var(--tw-gap, 1.25rem), 0.875rem);
  grid-template-columns: minmax(0, 1fr);
  grid-auto-flow: row dense;
  grid-auto-rows: minmax(11rem, auto);
  gap: var(--lx-bento-gap);
}
@media (min-width: 640px){ .features-block.features-block--bento.tw-grid{ grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1024px){ .features-block.features-block--bento.tw-grid{ grid-template-columns: repeat(4, minmax(0, 1fr)); } }

.features-block.features-block--bento.tw-grid > .tw-surface.features-block__tile{
  position: relative; isolation: isolate; overflow: hidden;
  height: auto;
  justify-content: flex-start;
  /* Composition de la variante : alignée à gauche même chez un DNA qui centre ses features. */
  align-items: flex-start; text-align: start;
  /* Une TUILE garde son retrait et son rayon, même chez un DNA qui aplatit ses
     features en liste (`padding: 1.25rem 0 0; border-radius: 0`) : le texte
     touchait le bord de la tuile chez SUISSE, REVUE, GAZETTE… (audit des 48 DNA,
     2026-09-28). Les valeurs restent celles du DNA (ses jetons). */
  padding: var(--tw-card-padding, 1.5rem);
  border-radius: var(--tw-radius-card, 16px);
  /* Lueur intérieure : un halo d'accent dans le coin haut, sous le contenu. */
  background-image: radial-gradient(120% 90% at 0% 0%, color-mix(in oklab, var(--tw-accent, #6366f1) 11%, transparent), transparent 62%);
}
/* Liseré en dégradé (1 px) : accent mêlé à la bordure du DNA, qui s'efface vers
   le bas. Posé PAR-DESSUS la bordure du DNA (qui reste la sienne). */
.features-block.features-block--bento.tw-grid > .tw-surface.features-block__tile::after{
  content: ''; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  /* Neutralise ce qu'un DNA pose sur le ::after de ses cartes (coins, tailles). */
  width: auto; height: auto; margin: 0; border: 0;
  border-radius: inherit; padding: 1px;
  background: linear-gradient(150deg,
    color-mix(in oklab, var(--tw-accent, #6366f1) 70%, var(--tw-border, transparent)) 0%,
    color-mix(in oklab, var(--tw-accent, #6366f1) 22%, var(--tw-border, transparent)) 38%,
    transparent 70%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
  opacity: .75; transition: opacity .35s ease;
}
.features-block.features-block--bento.tw-grid > .tw-surface.features-block__tile:hover::after{ opacity: 1; }
@media (min-width: 640px){
  .features-block.features-block--bento.tw-grid > .features-block__tile--hero,
  .features-block.features-block--bento.tw-grid > .features-block__tile--wide,
  .features-block.features-block--bento.tw-grid > .features-block__tile--full{ grid-column: span 2; }
  .features-block.features-block--bento.tw-grid > .features-block__tile--hero{ min-height: 18rem; }
}
@media (min-width: 1024px){
  .features-block.features-block--bento.tw-grid > .features-block__tile--hero{ grid-column: span 2; grid-row: span 2; min-height: 24rem; }
  .features-block.features-block--bento.tw-grid > .features-block__tile--tall{ grid-row: span 2; }
  .features-block.features-block--bento.tw-grid > .features-block__tile--full{ grid-column: 1 / -1; }
}
/* Puits d'icône teinté d'accent. */
.features-block__well{
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  align-self: flex-start; width: 2.75rem; height: 2.75rem;
  border-radius: var(--tw-radius-button, 12px);
  background: color-mix(in oklab, var(--tw-accent, #6366f1) 13%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--tw-accent, #6366f1) 28%, transparent);
}
.features-block.features-block--bento .features-block__well > .tw-icon{ --tw-icon-size: 1.35rem; }
/* Tuile vedette : icône en haut, texte en bas, titre à l'échelle h3 (et plus). */
.features-block.features-block--bento.tw-grid > .features-block__tile--hero > .features-block__well{ width: 3.5rem; height: 3.5rem; margin-bottom: auto; }
.features-block.features-block--bento.tw-grid > .features-block__tile--hero > .features-block__well > .tw-icon{ --tw-icon-size: 1.75rem; }
.features-block.features-block--bento.tw-grid > .features-block__tile--hero .tw-h4{
  font-size: clamp(var(--tw-size-h4, 1.3rem), 2.6vw, calc(var(--tw-size-h3, 1.625rem) * 1.25));
  line-height: var(--tw-leading-heading, 1.15);
}
.features-block.features-block--bento.tw-grid > .features-block__tile--hero .tw-body{ max-width: 44ch; }
/* Vedette SANS image : l'icône devient l'illustration — un emblème au centre du
   vide, entouré d'ondes, au lieu d'une petite pastille perdue en haut d'une
   grande carte blanche (« un grand vide dans la carte de gauche », Yann 2026-09-26). */
@media (min-width: 640px){
  .features-block.features-block--bento.tw-grid > .features-block__tile--hero:not(:has(> img.tw-media)) > .features-block__well{
    position: relative; align-self: center; margin-block: auto;
    width: clamp(5.5rem, 9vw, 7.5rem); height: clamp(5.5rem, 9vw, 7.5rem); border-radius: 999px;
    background: radial-gradient(circle at 35% 30%, color-mix(in oklab, var(--tw-accent, #6366f1) 30%, var(--tw-bg, #fff)), color-mix(in oklab, var(--tw-accent, #6366f1) 12%, var(--tw-bg, #fff)) 70%);
    box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--tw-accent, #6366f1) 30%, transparent),
      0 18px 50px -12px color-mix(in oklab, var(--tw-accent, #6366f1) 45%, transparent);
  }
  .features-block.features-block--bento.tw-grid > .features-block__tile--hero:not(:has(> img.tw-media)) > .features-block__well > .tw-icon{ --tw-icon-size: clamp(2.25rem, 3.6vw, 3rem); }
  .features-block.features-block--bento.tw-grid > .features-block__tile--hero:not(:has(> img.tw-media)) > .features-block__well::before,
  .features-block.features-block--bento.tw-grid > .features-block__tile--hero:not(:has(> img.tw-media)) > .features-block__well::after{
    content: ""; position: absolute; inset: -1.6rem; border-radius: inherit; pointer-events: none;
    border: 1px solid color-mix(in oklab, var(--tw-accent, #6366f1) 22%, transparent);
    /* DEUX ondes, puis le calme : une boucle sans fin de plus de 5 s devrait
       pouvoir s'arrêter (WCAG 2.2.2) — un décor ne mérite pas un bouton de pause,
       il s'arrête de lui-même avant (3,6 s + 1,2 s de décalage = 4,8 s). */
    animation: lx-bento-onde 3.6s cubic-bezier(.2,.7,.2,1) 1 both;
  }
  .features-block.features-block--bento.tw-grid > .features-block__tile--hero:not(:has(> img.tw-media)) > .features-block__well::after{ animation-delay: 1.2s; }
}
@keyframes lx-bento-onde{ from{ transform: scale(.72); opacity: 0; } 25%{ opacity: 1; } to{ transform: scale(1.55); opacity: 0; } }
/* ⚠️ MÊME SPÉCIFICITÉ QUE LA RÈGLE QUI ANIME (`:not(:has(> img.tw-media))` compris).
   Sans le `:not(:has())`, la garde perdait : les ondes tournaient en boucle chez
   un visiteur qui a demandé moins de mouvement, dans les 48 DNA (audit du
   2026-09-28, `document.getAnimations()` sous `reducedMotion: reduce`). */
@media (prefers-reduced-motion: reduce){
  .features-block.features-block--bento.tw-grid > .features-block__tile--hero:not(:has(> img.tw-media)) > .features-block__well::before,
  .features-block.features-block--bento.tw-grid > .features-block__tile--hero:not(:has(> img.tw-media)) > .features-block__well::after{ animation: none; opacity: .6; transform: none; }
  .features-block.features-block--bento.tw-grid > .features-block__tile--hero:not(:has(> img.tw-media)) > .features-block__well::after{ inset: -3rem; opacity: .35; }
}
html[data-animations="off"] .features-block.features-block--bento.tw-grid > .features-block__tile--hero:not(:has(> img.tw-media)) > .features-block__well::before,
html[data-animations="off"] .features-block.features-block--bento.tw-grid > .features-block__tile--hero:not(:has(> img.tw-media)) > .features-block__well::after{ animation: none; }
/* Vedette À IMAGE : l'image remplit la tuile et s'efface vers le bas, où le texte
   repose sur la couleur de surface du DNA — lisible sans voile en dur. */
.features-block.features-block--bento.tw-grid > .features-block__tile--hero:has(> img.tw-media){ justify-content: flex-end; }
.features-block.features-block--bento.tw-grid > .features-block__tile--hero > img.tw-media{
  position: absolute; inset: 0; z-index: -1; width: 100%; height: 100%;
  aspect-ratio: auto; object-fit: cover; border-radius: 0; box-shadow: none;
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 28%, transparent 72%);
  mask-image: linear-gradient(to bottom, #000 0%, #000 28%, transparent 72%);
}
@media (max-width: 639px){
  .features-block.features-block--bento.tw-grid > .features-block__tile--hero:has(> img.tw-media){ min-height: 22rem; }
}

/* ── featuresBlock « numbered » : liste éditoriale ───────────────────────── */
.features-block.features-block--numbered.tw-grid{
  --lx-index-size: clamp(3rem, 10vw, 4rem);
  --lx-index-col: 0px;
  grid-template-columns: minmax(0, 1fr); gap: 0;
}
.features-block.features-block--numbered.tw-grid > .tw-surface.features-block__step{
  position: relative; height: auto; gap: .5rem;
  align-items: flex-start; text-align: start;
  background: transparent; box-shadow: none; border: 0; border-radius: 0;
  border-top: 1px solid var(--tw-rule, var(--tw-border, rgba(0,0,0,.12)));
  padding: clamp(1.4rem, 3vw, 2.25rem) 0 clamp(1.4rem, 3vw, 2.25rem) var(--lx-index-col);
}
.features-block.features-block--numbered.tw-grid > .tw-surface.features-block__step:last-child{
  border-bottom: 1px solid var(--tw-rule, var(--tw-border, rgba(0,0,0,.12)));
}
/* Le numéro EST le repère : les pastilles décoratives d'un DNA et l'icône se retirent. */
.features-block.features-block--numbered.tw-grid > .tw-surface.features-block__step::before{ content: none; }
.features-block.features-block--numbered.tw-grid > .features-block__step > .tw-icon{ display: none; }
.features-block.features-block--numbered.tw-grid > .features-block__step > img.tw-media{ max-width: 20rem; }
.features-block__index{
  --lx-index-stroke: color-mix(in oklab, var(--tw-accent, #555) 62%, var(--tw-text-heading, var(--tw-text, #111)));
  display: block;
  font-family: var(--tw-font-heading, Georgia, serif);
  font-weight: var(--tw-weight-heading, 700);
  font-size: var(--lx-index-size); line-height: .9; letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums lining-nums;
  /* Détouré SANS les chevauchements internes des glyphes (le « 2 » d'une police
     variable dessine sa jonction) : trait double, peint SOUS un remplissage de la
     couleur du fond — seule la moitié extérieure du trait reste visible. */
  color: var(--tw-bg, transparent);
  -webkit-text-stroke: 3px var(--lx-index-stroke);
  paint-order: stroke fill;
  transition: color .35s ease;
  user-select: none;
}
@supports not (-webkit-text-stroke: 1px #000){
  .features-block__index{ color: var(--tw-accent, currentColor); }
}
.features-block.features-block--numbered.tw-grid > .features-block__step:hover > .features-block__index{
  color: color-mix(in oklab, var(--tw-accent, #555) 16%, var(--tw-bg, transparent));
}
.features-block.features-block--numbered.tw-grid > .features-block__step .tw-h4{ font-size: var(--tw-size-h3, 1.625rem); }
.features-block.features-block--numbered.tw-grid > .features-block__step .tw-body{ max-width: 62ch; }
@media (min-width: 768px){
  .features-block.features-block--numbered.tw-grid{
    --lx-index-size: clamp(4rem, 7vw, 6.5rem);
    --lx-index-col: calc(var(--lx-index-size) * 1.7 + clamp(1.5rem, 4vw, 3rem));
  }
  .features-block.features-block--numbered.tw-grid > .tw-surface.features-block__step{
    min-height: calc(var(--lx-index-size) * .9 + clamp(1.4rem, 3vw, 2.25rem) * 2);
    justify-content: center;
  }
  .features-block.features-block--numbered.tw-grid > .features-block__step > .features-block__index{
    position: absolute; left: 0; top: clamp(1.4rem, 3vw, 2.25rem);
  }
}

/* ── cardsBlock « scroll » : rail horizontal ─────────────────────────────── */
.cards-block.cards-block--scroll{
  --lx-rail-gap: max(var(--tw-gap, 1.5rem), 1rem);
  --lx-rail-card: min(80vw, 21rem);
  display: flex; gap: var(--lx-rail-gap);
  overflow-x: auto; overflow-y: hidden;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  /* Même garde que le bandeau : la piste ne donne pas sa largeur aux parents
     (le canevas de l'éditeur passait sous le panneau de droite). */
  contain: inline-size; min-width: 0;
  /* De l'air pour l'ombre et le soulèvement des cartes (rendu par la marge sur
     le site, où l'enveloppe le permet — voir `.cards-scroll >` plus bas). */
  padding-block: .5rem 1rem;
}
.cards-block.cards-block--scroll::-webkit-scrollbar{ display: none; }
@media (min-width: 640px){ .cards-block.cards-block--scroll{ --lx-rail-card: clamp(17rem, 40vw, 22rem); } }
@media (min-width: 1024px){ .cards-block.cards-block--scroll{ --lx-rail-card: clamp(18rem, 27vw, 24rem); } }
.cards-block.cards-block--scroll > .cards-block__card{ flex: 0 0 var(--lx-rail-card); scroll-snap-align: start; }
.cards-block.cards-block--scroll img{ -webkit-user-drag: none; }
.cards-block.cards-block--scroll:focus-visible{ outline: 2px solid var(--tw-accent, currentColor); outline-offset: 4px; }
@media (pointer: fine){ .cards-scroll[data-pret] > .cards-block--scroll{ cursor: grab; } }
.cards-block.cards-block--scroll.is-glisse{ cursor: grabbing; scroll-snap-type: none; user-select: none; }
/* Sur le site (enveloppe `.cards-scroll`) : la piste déborde jusqu'au bord DROIT de
   l'écran ; le rembourrage final ramène la dernière carte sur le bord du contenu.
   `.page-content` rogne en x le demi-ascenseur que 100vw compte en trop. */
.cards-scroll{ position: relative; }
.cards-scroll > .cards-block.cards-block--scroll{
  margin-block: -.5rem -1rem;
  margin-inline-end: calc(50% - 50vw);
  padding-inline-end: calc(50vw - 50%);
}
.cards-scroll__ui{ display: flex; align-items: center; gap: .75rem; margin-top: clamp(1rem, 2vw, 1.5rem); }
.cards-scroll:not([data-pret]) .cards-scroll__ui,
.cards-scroll[data-deborde="false"] .cards-scroll__ui{ display: none; }
.cards-scroll__progress{
  flex: 1 1 auto; height: 2px; border-radius: 2px; overflow: hidden;
  background: var(--tw-rule, color-mix(in oklab, var(--tw-text, #000) 14%, transparent));
}
.cards-scroll__bar{
  display: block; height: 100%;
  background: var(--tw-accent, currentColor);
  transform-origin: 0 50%; transform: scaleX(var(--lx-progress, .25));
  transition: transform .25s ease-out;
}
.cards-scroll__nav{
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 2.75rem; height: 2.75rem; padding: 0; border-radius: 999px;
  border: var(--tw-border-width, 1px) solid var(--tw-border, rgba(0,0,0,.15));
  background: var(--tw-surface, #fff);
  color: var(--tw-text-heading, var(--tw-text, inherit));
  font: inherit; font-size: 1.1rem; line-height: 1; cursor: pointer;
  transition: background-color .2s ease, color .2s ease, border-color .2s ease, opacity .2s ease;
}
.cards-scroll__nav:hover:not(:disabled){ background: var(--tw-accent, #111); color: var(--tw-on-accent, #fff); border-color: var(--tw-accent, #111); }
.cards-scroll__nav:disabled{ opacity: .35; cursor: default; }
.cards-scroll__nav:focus-visible{ outline: 2px solid var(--tw-accent, currentColor); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce){ .cards-scroll__bar, .cards-scroll__nav{ transition: none; } }
html[data-animations="off"] .cards-scroll__bar{ transition: none; }

/* ── testimonialBlock « wall » : maçonnerie (colonnes CSS) ───────────────── */
.testimonial-block.testimonial-block--wall{
  --lx-wall-gap: max(var(--tw-gap, 1.5rem), 1rem);
  display: block; column-count: 1; column-gap: var(--lx-wall-gap);
}
@media (min-width: 640px){ .testimonial-block.testimonial-block--wall:not([data-cols-tablet="1"]){ column-count: 2; } }
@media (min-width: 1024px){
  .testimonial-block.testimonial-block--wall[data-cols-desktop="1"]{ column-count: 1; }
  .testimonial-block.testimonial-block--wall[data-cols-desktop="2"]{ column-count: 2; }
  .testimonial-block.testimonial-block--wall[data-cols-desktop="3"]{ column-count: 3; }
  .testimonial-block.testimonial-block--wall[data-cols-desktop="4"]{ column-count: 4; }
}
.testimonial-block.testimonial-block--wall > .testimonial-block__item{
  height: auto; width: 100%;
  margin: 0 0 var(--lx-wall-gap);
  break-inside: avoid; -webkit-column-break-inside: avoid; page-break-inside: avoid;
}
.testimonial-block.testimonial-block--wall > .testimonial-block__item .testimonial-block__quote{ flex: none; }
/* Une carte sur cinq, légèrement teintée d'accent : le mur respire. */
.testimonial-block.testimonial-block--wall > .testimonial-block__item[data-surface="card"]:nth-child(5n+2){
  background-color: color-mix(in oklab, var(--tw-accent, #6366f1) 7%, var(--tw-surface, #fff));
}
/* Citation courte = composée grand, dans la police des titres. */
/* (0,5,1) : bat les `[data-dna] .testimonial-block .tw-quote p` ; plancher 1,5rem pour
   rester au-dessus de la citation ordinaire d'un DNA qui grossit déjà les siennes. */
.testimonial-block.testimonial-block--wall > .tw-surface.testimonial-block__item--short .tw-quote p{
  font-family: var(--tw-font-heading, inherit);
  font-size: clamp(1.5rem, 2.4vw, 1.9rem);
  letter-spacing: var(--tw-tracking-heading, -0.01em);
  line-height: 1.3;
}

/* ── contentSection « stack » : sections empilées (paquet de cartes) ─────── */
.content-section[data-stack]{
  --lx-stack-radius: clamp(1.25rem, 3vw, 2rem);
  border-radius: var(--lx-stack-radius) var(--lx-stack-radius) 0 0;
  /* Fond opaque : la section suivante glisse PAR-DESSUS, rien ne doit transparaître. */
  background-color: var(--tw-bg, var(--tw-page-bg, #fff));
  box-shadow:
    0 -1px 0 color-mix(in oklab, var(--tw-text, #000) 7%, transparent),
    0 -26px 50px -30px color-mix(in oklab, var(--tw-text, #000) 45%, transparent);
}
/* Le collage n'existe qu'une fois la section MESURÉE par premium-variantes.ts
   (`data-stack-pret`, `--lx-stack-top` négatif pour une section plus haute que
   l'écran : elle défile jusqu'à son bas avant d'être recouverte). Sans z-index :
   la section d'en-tête (z-index 50, blocks-shared.css) et ses menus restent
   au-dessus ; chaque section suivante, plus loin dans le flux, passe devant. */
@media (min-width: 700px) and (min-height: 560px) and (prefers-reduced-motion: no-preference){
  .content-section[data-stack][data-stack-pret]{ position: sticky; top: var(--lx-stack-top, 0px); }
}
html[data-animations="off"] .content-section[data-stack][data-stack-pret]{ position: relative; top: auto; }

/* ── animation-block — animation de motion design écrite en code (2026-09-26) ──
   Un cadre ISOLÉ (iframe sandbox + CSP sans réseau, cf. shared/blocks/animationBlock.ts)
   dans une scène au ratio fixé par `data-ratio`. Le fond reste transparent : la
   section se voit derrière. `animation-block.ts` (runtime) envoie la palette au cadre.
   « Moins de mouvement » : l'affiche, si elle existe, remplace le cadre. */
/* Bloc Équipe : la page d'un membre (HELO 74815) — le nom garde son allure de
   titre, souligné au survol ; la photo reste un bloc. */
.team-block__name-link{ color: inherit; text-decoration: none; }
.team-block__name-link:hover, .team-block__name-link:focus-visible{ text-decoration: underline; text-underline-offset: .2em; }
/* HELO 74842 : le lien est un conteneur FLEX — en bloc, le médaillon (un <span>)
   redevenait EN LIGNE, sa largeur ne comptait plus et la photo prenait toute la
   colonne (« logo pleine largeur dans un grand cadre blanc »). */
.team-block__photo-link{ display: flex; justify-content: center; flex: none; max-width: 100%; }
.team-block--row .team-block__photo-link{ justify-content: flex-start; }

/* Bloc « Intégrer » (embedBlock) : une adresse dans un cadre, ou le code d'un
   service écrit tel quel. Le cadre prend la largeur du texte, sa hauteur est
   réglée dans le bloc ; le code HTML garde sa propre mise en page. */
.embed-block{ width: 100%; margin: 0 auto; }
.embed-block__frame{ display: block; width: 100%; max-width: 100%; border: 0; border-radius: var(--tw-radius-media, 12px); background: color-mix(in srgb, currentColor 4%, transparent); }
.embed-block--html{ overflow-x: auto; }
/* En attente du consentement du visiteur (catégorie réglée dans le bloc) : le
   message tient la place, avec le lien qui rouvre la bannière ; le réveil
   (/_tw/consentement.js) le retire. Un cadre sans adresse garde sa hauteur. */
/* #420 (beta.15) : une vidéo ou une carte Google en attente du consentement.
   Le message tient la place du cadre (même hauteur), avec « Afficher la vidéo »
   qui ne charge QUE ce bloc. */
.tw-consentement{ display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.75rem; padding: 1.25rem; text-align: center; font-size: 0.92em; background: var(--tw-surface, color-mix(in srgb, currentColor 6%, transparent)); color: var(--tw-text, inherit); }
:is(.video-block__wrapper, .map-block) > .tw-consentement{ position: absolute; inset: 0; z-index: 1; }
.tw-consentement p{ margin: 0; max-width: 34rem; }
.tw-consentement__actions{ display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 0.5rem 1rem; }
.tw-consentement__afficher{ cursor: pointer; font: inherit; padding: 0.55rem 1.1rem; border: 0; border-radius: var(--tw-radius-button, 8px); background: var(--tw-btn-bg, var(--tw-accent, #111)); color: var(--tw-btn-text, #fff); }
.tw-consentement__afficher:focus-visible{ outline: 2px solid currentColor; outline-offset: 2px; }
.tw-consentement a{ color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.embed-block__consentement{ margin: 0; padding: 1rem 1.25rem; border: 1px dashed color-mix(in srgb, currentColor 30%, transparent); border-radius: var(--tw-radius-media, 12px); font-size: 0.9em; text-align: center; }
.embed-block__consentement a{ text-decoration: underline; text-underline-offset: 2px; }
.embed-block--url .embed-block__consentement{ margin-block-start: 0.5rem; }
/* Le jeton d'un code PHP (#382) : visible dans l'APERÇU seulement — la passe
   après le build le remplace par le code et publie la page en index.php. */
tw-php{ display: block; padding: 1rem 1.25rem; border: 1px dashed color-mix(in srgb, currentColor 30%, transparent); border-radius: var(--tw-radius-media, 12px); font: 0.85em/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; }
.animation-block{
  margin: 0 auto;
  width: 100%;
}
.animation-block--full{ max-width: 100%; }
.animation-block--large{ max-width: min(960px, 100%); }
.animation-block--medium{ max-width: min(640px, 100%); }
.animation-block__stage{
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  overflow: hidden;
}
.animation-block[data-ratio="4:3"] .animation-block__stage{ aspect-ratio: 4 / 3; }
.animation-block[data-ratio="1:1"] .animation-block__stage{ aspect-ratio: 1 / 1; }
.animation-block[data-ratio="21:9"] .animation-block__stage{ aspect-ratio: 21 / 9; }
.animation-block[data-ratio="3:4"] .animation-block__stage{ aspect-ratio: 3 / 4; }
/* Vertical : la hauteur ne dépasse pas l'écran — c'est la largeur qui cède. */
.animation-block[data-ratio="9:16"] .animation-block__stage{ aspect-ratio: 9 / 16; width: min(100%, calc(90vh * 9 / 16)); margin: 0 auto; }
.animation-block--rounded .animation-block__stage{
  border-radius: var(--tw-radius-media, 1rem);
}
/* L'image nommée (`role="img"`) qui porte le cadre et l'affiche : elle remplit la
   scène (la mise en pause, pause-animations.ts, vise la figure). */
.animation-block__image{ position: absolute; inset: 0; }
.animation-block__frame{
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
  background: transparent;
  color-scheme: normal;
}
.animation-block__poster{
  position: absolute;
  inset: 0;
  display: none;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
@media (prefers-reduced-motion: reduce){
  .animation-block--has-poster .animation-block__frame{ display: none; }
  .animation-block--has-poster .animation-block__poster{ display: block; }
}
html[data-animations="off"] .animation-block--has-poster .animation-block__frame{ display: none; }
html[data-animations="off"] .animation-block--has-poster .animation-block__poster{ display: block; }
/* Éditeur seulement : un voile au-dessus du cadre, pour qu'un clic SÉLECTIONNE
   le bloc au lieu d'entrer dans l'animation ; et l'emplacement d'un bloc vide. */
.animation-block__overlay{
  position: absolute;
  inset: 0;
  z-index: 1;
  cursor: pointer;
  background: transparent;
}
.animation-block__placeholder{
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 3rem;
  text-align: center;
  font-size: 1rem;
  color: var(--tw-text, rgba(128, 128, 128, 0.6));
  opacity: 0.6;
  border: 2px dashed var(--tw-border, rgba(128, 128, 128, 0.25));
  border-radius: 0.5rem;
}

/* Ambiances GÉNÉRATIVES sans leur script (éditeur, ou avant le chargement de
   motion.ts) : un rendu fixe qui en donne l'idée. L'éditeur n'exécute pas le
   script du site, et l'ambiance y était invisible (Yann, 2026-09-26). Dès que le
   canvas est posé, il prend le relais. */
.content-section:is([data-atmosphere="liquid"], [data-atmosphere="mesh"]) > .tw-backdrop--section:not(:has(canvas)){
  background:
    radial-gradient(40% 55% at 20% 30%, color-mix(in oklab, var(--tw-accent, #6366f1) 38%, transparent), transparent 70%),
    radial-gradient(38% 50% at 80% 65%, color-mix(in oklab, var(--tw-accent-2, var(--tw-accent, #a855f7)) 34%, transparent), transparent 70%),
    radial-gradient(30% 40% at 60% 15%, color-mix(in oklab, var(--tw-accent, #6366f1) 22%, transparent), transparent 70%);
}
.content-section[data-atmosphere="particles"] > .tw-backdrop--section:not(:has(canvas)){
  background: radial-gradient(color-mix(in oklab, var(--tw-accent, #6366f1) 45%, transparent) 1.5px, transparent 2px) 0 0 / 46px 46px;
  -webkit-mask-image: radial-gradient(ellipse 80% 70% at 50% 40%, #000 20%, transparent 100%);
          mask-image: radial-gradient(ellipse 80% 70% at 50% 40%, #000 20%, transparent 100%);
}
.content-section[data-atmosphere="waves"] > .tw-backdrop--section:not(:has(canvas)){
  background: repeating-radial-gradient(ellipse 140% 60% at 50% 120%, transparent 0 22px, color-mix(in oklab, var(--tw-accent, #6366f1) 22%, transparent) 22px 23px);
}

/* DEUX SECTIONS VOISINES DU MÊME FOND n'additionnent plus leurs marges : sans
   changement de couleur, rien ne justifie le double vide (~16 rem chez Cobalt) —
   « je vois souvent de gros espaces blancs » (Yann, 2026-09-26). La seconde
   réduit sa marge du haut ; une bande de couleur différente garde les siennes.
   Une ambiance qui s'estompe vers le bas (grille, maillage, particules) laisse le
   fond de la page : elle compte comme le même fond. Les vagues et le liquide
   occupent le bas de leur section, eux gardent l'écart. */
.content-section:is([data-palette="default"], :not([data-palette])):not(.content-section--system):not([data-atmosphere="liquid"], [data-atmosphere="waves"]):not(.content-section--has-bg-image)
  + .content-section:is([data-palette="default"], :not([data-palette])):not(.content-section--system):not([data-atmosphere]):not(.content-section--has-bg-image){
  padding-top: calc((var(--tw-section-py, 6rem) * var(--tw-section-density, 1)) * 0.3);
}
.content-section[data-palette="subtle"] + .content-section[data-palette="subtle"],
.content-section[data-palette="accent"] + .content-section[data-palette="accent"],
.content-section[data-palette="bold"] + .content-section[data-palette="bold"]{
  padding-top: calc((var(--tw-section-py, 6rem) * var(--tw-section-density, 1)) * 0.3);
}

/* ── compare-block — comparaison AVANT / APRÈS (Yann, 2026-09-26) ──────────
   L'image « après » est dans le flux (en ratio `auto`, c'est elle qui donne les
   proportions) ; l'image « avant » est posée par-dessus et découpée par
   `clip-path` à `--cp-pos`. Un <input type="range"> INVISIBLE couvre la scène :
   clavier, souris et doigt marchent sans script ; `comparaison.ts` recopie sa
   valeur dans `--cp-pos` (sans script, le style en ligne garde `start`).
   Le curseur déborde de la moitié de son pouce de chaque côté : le centre du
   pouce tombe ainsi EXACTEMENT sous le séparateur, de 0 à 100 %.
   Survoler n'est pas glisser : rien ne bouge sans appui.
   Variables internes en --cp-* ; les couleurs viennent des jetons --tw-*. */
.compare-block{
  --cp-pos: 50%;
  --cp-radius: var(--tw-radius-media, 14px);
  --cp-thumb: 2.75rem;
  --cp-line: var(--tw-surface, #fff);
  margin: 0;
  width: 100%;
}
.compare-block__stage{
  position: relative;
  box-sizing: content-box;
  overflow: hidden;
  isolation: isolate;
  line-height: 0;
  -webkit-user-select: none;
  user-select: none;
}
.compare-block__stage img{ display: block; width: 100%; max-width: none; margin: 0; border-radius: 0; }
.compare-block__after{ height: auto; }
.compare-block__before{
  position: absolute;
  inset: 0;
  height: 100%;
  object-fit: cover;
  clip-path: inset(0 calc(100% - var(--cp-pos)) 0 0);
}
.compare-block--vertical .compare-block__before{ clip-path: inset(0 0 calc(100% - var(--cp-pos)) 0); }
/* Proportions imposées : les deux images remplissent la scène. */
.compare-block[data-ratio="16:9"] .compare-block__stage{ aspect-ratio: 16 / 9; }
.compare-block[data-ratio="4:3"] .compare-block__stage{ aspect-ratio: 4 / 3; }
.compare-block[data-ratio="3:2"] .compare-block__stage{ aspect-ratio: 3 / 2; }
.compare-block[data-ratio="1:1"] .compare-block__stage{ aspect-ratio: 1 / 1; }
.compare-block:is([data-ratio="16:9"], [data-ratio="4:3"], [data-ratio="3:2"], [data-ratio="1:1"]) .compare-block__after{
  position: absolute;
  inset: 0;
  height: 100%;
  object-fit: cover;
}
/* Étiquettes : petites pastilles dans les coins (typo du surtitre du DNA). */
.compare-block__label{
  position: absolute;
  z-index: 1;
  top: .75rem;
  pointer-events: none;
  line-height: 1.2;
  white-space: nowrap;
  font-family: var(--tw-font-body, system-ui, sans-serif);
  font-size: var(--tw-size-eyebrow, .75rem);
  font-weight: 600;
  letter-spacing: var(--tw-tracking-eyebrow, .12em);
  text-transform: uppercase;
  color: var(--tw-text, #111);
  background: color-mix(in oklab, var(--tw-surface, #fff) 82%, transparent);
  padding: .4em .8em;
  border-radius: 999px;
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}
.compare-block__label--before{ left: .75rem; }
.compare-block__label--after{ right: .75rem; }
.compare-block--vertical .compare-block__label--after{ top: auto; right: auto; bottom: .75rem; left: .75rem; }
/* Le curseur natif, invisible, sur toute la scène. */
.compare-block__range{
  position: absolute;
  z-index: 2;
  top: 0;
  left: calc(var(--cp-thumb) / -2);
  width: calc(100% + var(--cp-thumb));
  height: 100%;
  margin: 0;
  padding: 0;
  opacity: 0;
  cursor: ew-resize;
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
  touch-action: pan-y;
}
.compare-block__range::-webkit-slider-thumb{ -webkit-appearance: none; appearance: none; width: var(--cp-thumb); height: var(--cp-thumb); border: 0; background: transparent; }
.compare-block__range::-moz-range-thumb{ width: var(--cp-thumb); height: var(--cp-thumb); border: 0; background: transparent; }
.compare-block--vertical .compare-block__range{
  top: calc(var(--cp-thumb) / -2);
  left: 0;
  width: 100%;
  height: calc(100% + var(--cp-thumb));
  writing-mode: vertical-lr;
  direction: ltr;
  cursor: ns-resize;
  touch-action: pan-x;
}
/* La poignée : un trait et une pastille ronde à deux chevrons. */
.compare-block__handle{
  position: absolute;
  z-index: 3;
  top: 0;
  bottom: 0;
  left: var(--cp-pos);
  width: 0;
  pointer-events: none;
}
.compare-block__handle::before{
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: -1px;
  width: 2px;
  background: var(--cp-line);
  box-shadow: 0 0 6px rgba(0, 0, 0, .25);
}
.compare-block__grip{
  position: absolute;
  top: 50%;
  left: 0;
  width: var(--cp-thumb);
  height: var(--cp-thumb);
  display: grid;
  place-items: center;
  border-radius: 999px;
  background: var(--tw-surface, #fff);
  color: var(--tw-text, #111);
  box-shadow: 0 2px 10px rgba(0, 0, 0, .22), 0 0 0 1px color-mix(in oklab, var(--tw-text, #111) 10%, transparent);
  transform: translate(-50%, -50%);
  transition: transform .2s ease;
}
.compare-block__grip svg{ display: block; }
.compare-block--vertical .compare-block__handle{ top: var(--cp-pos); right: 0; bottom: auto; left: 0; width: auto; height: 0; }
.compare-block--vertical .compare-block__handle::before{ top: -1px; right: 0; bottom: auto; left: 0; width: auto; height: 2px; }
.compare-block--vertical .compare-block__grip{ top: 0; left: 50%; }
.compare-block--vertical .compare-block__grip svg{ transform: rotate(90deg); }
.compare-block__range:focus-visible ~ .compare-block__handle .compare-block__grip{
  outline: 2px solid var(--tw-accent, currentColor);
  outline-offset: 3px;
}
.compare-block__range:active ~ .compare-block__handle .compare-block__grip{ transform: translate(-50%, -50%) scale(1.08); }
@media (hover: hover) and (pointer: fine){
  .compare-block__stage:hover .compare-block__grip{ transform: translate(-50%, -50%) scale(1.06); }
}
@media (prefers-reduced-motion: reduce){
  .compare-block__grip{ transition: none; }
}
/* Cadres. « Navigateur » : même habillage que .image-block--frame-browser
   (blocks-shared.css) — la barre est une bordure haute TRANSPARENTE sur
   laquelle le fond peint les trois points : la scène (et donc les images, la
   poignée, le curseur) commence juste en dessous, sans élément de plus. */
.compare-block--frame-rounded .compare-block__stage{
  border-radius: var(--cp-radius);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .06), 0 24px 60px -18px rgba(0, 0, 0, .28);
}
.compare-block--frame-browser .compare-block__stage{
  border-top: 2rem solid transparent;
  border-radius: 12px;
  background:
    radial-gradient(circle 5px at 18px 1rem, #ff5f57 97%, transparent 100%) no-repeat,
    radial-gradient(circle 5px at 36px 1rem, #febc2e 97%, transparent 100%) no-repeat,
    radial-gradient(circle 5px at 54px 1rem, #28c840 97%, transparent 100%) no-repeat,
    linear-gradient(color-mix(in oklab, var(--tw-text, #111) 6%, var(--tw-surface, #fff)), color-mix(in oklab, var(--tw-text, #111) 6%, var(--tw-surface, #fff))) top / 100% 2rem no-repeat,
    var(--tw-surface, #fff);
  background-origin: border-box;
  box-shadow:
    0 0 0 1px color-mix(in oklab, var(--tw-text, #111) 12%, transparent),
    0 1px 2px rgba(0, 0, 0, .06),
    0 24px 60px -12px rgba(0, 0, 0, .28);
}
/* Éditeur seulement : l'emplacement d'une comparaison sans ses deux images. */
.compare-block__placeholder{
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 3rem;
  text-align: center;
  font-size: 1rem;
  color: var(--tw-text, rgba(128, 128, 128, 0.6));
  opacity: 0.6;
  border: 2px dashed var(--tw-border, rgba(128, 128, 128, 0.25));
  border-radius: 0.5rem;
}

/* ── callout-block — l'ENCADRÉ : note / astuce / attention / important (#335) ─
   L'« admonition » de GitBook et Docusaurus, le callout de Notion. Un fond
   TEINTÉ (≈ 9 % de la couleur de sa nature, posé sur la surface de la section
   quelle qu'elle soit : on mélange avec `transparent`, pas avec une surface
   supposée), un filet de 3 px à gauche, une icône Lucide en ligne et un titre
   facultatif en demi-gras. Le contenu est du texte riche ordinaire : il garde la
   typographie du DNA.
   Couleurs : note = l'accent du thème, astuce = vert, attention = ambre,
   important = rouge. L'icône est mêlée à la couleur du TEXTE : elle fonce sur
   une section claire et s'éclaircit sur une section sombre sans qu'on ait à
   savoir laquelle. Le thème sombre éclaircit en plus les quatre teintes.
   Propriétés internes en --lx-* ; chaque --tw-* lu a son repli. */
.callout-block{
  --lx-callout-c: var(--tw-accent, #2563eb);
  display: flex;
  align-items: flex-start;
  gap: .75rem;
  margin: 1.5rem 0;
  padding: 1rem 1.25rem;
  border-inline-start: 3px solid var(--lx-callout-c);
  border-radius: min(var(--tw-radius-media, 10px), 12px);
  background: color-mix(in oklab, var(--lx-callout-c) 9%, transparent);
}
.callout-block--tip{ --lx-callout-c: #16a34a; }
.callout-block--warning{ --lx-callout-c: #d97706; }
.callout-block--danger{ --lx-callout-c: #dc2626; }
[data-theme="dark"] .callout-block{ background: color-mix(in oklab, var(--lx-callout-c) 14%, transparent); }
[data-theme="dark"] .callout-block--note{ --lx-callout-c: color-mix(in oklab, var(--tw-accent, #2563eb) 72%, #fff); }
[data-theme="dark"] .callout-block--tip{ --lx-callout-c: #4ade80; }
[data-theme="dark"] .callout-block--warning{ --lx-callout-c: #fbbf24; }
[data-theme="dark"] .callout-block--danger{ --lx-callout-c: #f87171; }
.callout-block__icon{
  flex: none;
  display: inline-flex;
  width: 1.25rem;
  height: 1.25rem;
  margin-top: .2em;
  color: color-mix(in oklab, var(--lx-callout-c) 78%, currentColor);
}
.callout-block__icon svg{ display: block; width: 100%; height: 100%; }
.callout-block__body{ flex: 1 1 auto; min-width: 0; }
.callout-block__title{
  margin: 0 0 .35rem;
  font-family: var(--tw-font-body, inherit);
  font-weight: 600;
  line-height: 1.45;
  color: var(--tw-text-heading, inherit);
}
/* Le premier et le dernier bloc du contenu collent au cadre. Deux profondeurs :
   la publication (enfants directs) et l'éditeur (la NodeView intercale son
   conteneur `[data-node-view-content-react]`). */
.callout-block__content > :first-child,
.callout-block__content > [data-node-view-content-react] > :first-child{ margin-top: 0; }
.callout-block__content > :last-child,
.callout-block__content > [data-node-view-content-react] > :last-child{ margin-bottom: 0; }
/* Éditeur seulement : le titre se tape en place, dans un champ qui a
   exactement l'allure du titre publié. */
.callout-block__title-input{
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: transparent;
  font: inherit;
  font-weight: 600;
  color: inherit;
  outline: none;
}
.callout-block__title-input::placeholder{ color: inherit; opacity: .45; }
button.callout-block__icon{ padding: 0; border: 0; background: none; cursor: pointer; }

/* ── Écritures de droite à gauche (2026-09-26) — cf. la section RTL de
   blocks-shared.css. Rien ici n'agit sans `[dir="rtl"]`. ── */
/* Les flèches « → » des liens pointent vers la FIN de la ligne. */
[dir="rtl"] :is(.tw-cta-link__arrow, .list-block__arrow){ display: inline-block; transform: scaleX(-1); }
[dir="rtl"] .tw-cta-link:hover .tw-cta-link__arrow{ transform: translateX(-3px) scaleX(-1); }
[dir="rtl"] .list-block__title-link:hover .list-block__arrow{ transform: translateX(-.2em) scaleX(-1); }
/* Avant / après : la poignée se place en `left: var(--cp-pos)`, calculé depuis
   la valeur du curseur natif — qui, en RTL, part de la DROITE. On garde le
   curseur de gauche à droite, pour que la poignée suive le doigt. */
[dir="rtl"] .compare-block__range{ direction: ltr; }

/**
 * blocks-shared.css - TipTap Block Rendering Styles (SHARED)
 *
 * SHARED between:
 * - TipTap editor preview (imported by blocks-editor.css)
 * - Astro preview (copied to public/styles/blocks.css)
 * - Published Astro site
 *
 * DO NOT put editor-only styles here (modals, focus rings, sidebar UI, .dark rules).
 * Those belong in blocks-editor.css.
 *
 * COLOURS = DNA BRICK TOKENS (--tw-*):
 * ====================================
 * All colours use the brick tokens (--tw-text, --tw-accent, --tw-surface,
 * --tw-border, --tw-btn-*…) derived by the active DNA (dna-{x}.css). A section
 * carries a [data-palette] attribute; the DNA re-points the contextual --tw-*
 * set per palette (default/subtle/accent/bold). No legacy --section-* injection.
 */
/* ═══════════════════════════════════════════════════════════════════════════
   SECTION BASE
   Section vertical rhythm + system-section reset.

   Colours flow from the DNA brick tokens (--tw-*); the palette is selected by
   the [data-palette] attribute emitted on the section via ContentSectionExtension.
   ═══════════════════════════════════════════════════════════════════════════ */

.content-section {
  /* Section vertical rhythm — owned by the DNA (--tw-section-py). The legacy
     per-section --section-* colour bridge is gone: a section's colours come
     from the DNA via the [data-palette] attribute (dna-{x}.css re-points the
     contextual --tw-* set). Blocks read --tw-* directly.
     × --tw-section-density : Thème › Densité (Yann, 2026-10-17), 1 = le style. */
  padding-top: calc(var(--tw-section-py, 4rem) * var(--tw-section-density, 1));
  padding-bottom: calc(var(--tw-section-py, 4rem) * var(--tw-section-density, 1));
}

/* System sections (header/footer) without an explicit palette paint nothing,
   so the child block (siteHeader/siteFooter) uses its own surface. */
.content-section--system:not([data-pattern-id]) {
  background: none;
}


/* ═══════════════════════════════════════════════════════════════════════════
   SECTION PALETTE BACKGROUND
   A section with a palette paints the DNA band colour.
   ═══════════════════════════════════════════════════════════════════════════ */

.content-section--has-pattern {
  /* DNA-first (contrat §1) : la bande suit la palette DNA via --tw-bg
     (re-pointé par dna-c.css [data-palette]). */
  background: var(--tw-bg);
}

/* ═══════════════════════════════════════════════════════════════════════════
   BACKGROUND IMAGE + BLUR
   Image backgrounds use inline styles for background: url(...).
   Blur requires a ::before pseudo-element so the content stays sharp.
   The blur value is passed via --section-bg-blur CSS variable.
   ═══════════════════════════════════════════════════════════════════════════ */

.content-section--has-bg-image {
  position: relative;
}

/* When blur is set: ::before inherits the background and applies filter */
.content-section--has-bg-blur {
  overflow: hidden;
}

.content-section--has-bg-blur::before {
  content: '';
  position: absolute;
  /* Extend beyond edges to prevent blur fringe artifacts */
  inset: -10px;
  background: inherit;
  filter: blur(var(--section-bg-blur, 0px));
  z-index: 0;
}

/* FOND FIXE (03/10) — l'image reste immobile pendant que le contenu défile.
   ⚠️ PAS `background-attachment: fixed` : iOS Safari l'ignore. Un calque
   `position: fixed` à la taille de l'écran, DÉCOUPÉ par la section
   (`clip-path: inset(0)`) : on ne voit l'image qu'à travers la section, et elle
   ne bouge pas. Rendu par le compositeur, sans script ; marche sur iPhone et
   iPad. Le fond de la section reste posé dessous : c'est le repli quand le
   calque ne peut pas être fixe (« moins de mouvement », ou un ancêtre
   transformé qui fait de `fixed` un simple `absolute`). Propriétés logiques :
   RTL inchangé. */
.content-section--fond-fixe {
  clip-path: inset(0);
  isolation: isolate;
}
.content-section__fond-fixe {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  height: 100vh;
  /* La grande hauteur d'écran (barre d'adresse repliée) : pas de bande vide en
     bas sur mobile quand la barre se replie au défilement. */
  height: 100lvh;
  z-index: 0;
  pointer-events: none;
  background-attachment: scroll;
}
.content-section--has-bg-blur > .content-section__fond-fixe {
  /* Déborde un peu pour que le flou n'éclaircisse pas les bords. */
  inset-inline: -10px;
  inset-block-start: -10px;
  height: calc(100vh + 20px);
  height: calc(100lvh + 20px);
  filter: blur(var(--section-bg-blur, 0px));
}
@media (prefers-reduced-motion: reduce) {
  .content-section__fond-fixe { display: none; }
}

/* Content must sit above the blurred background layer.
   Target both editor (.content-section__container) and Astro (.toweb-container) wrappers */
.content-section--has-bg-image > .content-section__container,
.content-section--has-bg-image > .toweb-container {
  position: relative;
  z-index: 1;
}

/* Video background — <video> positioned absolutely behind content */
.content-section--has-bg-video {
  position: relative;
  overflow: hidden;
}

.content-section__video {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  min-width: 100%;
  min-height: 100%;
  width: auto;
  height: auto;
  object-fit: cover;
  z-index: 0;
  pointer-events: none;
}

/* Overlay on top of video (semi-transparent color for text readability) */
.content-section--has-bg-video::after {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--section-video-overlay, transparent);
  z-index: 1;
  pointer-events: none;
}

/* Content sits above video + overlay */
.content-section--has-bg-video > .content-section__container,
.content-section--has-bg-video > .toweb-container,
/* #343 : la couche d'ambiance (`bricks-base.css`, `.content-section:has(> .tw-backdrop--section) > …`)
   ramène le contenu à z-index 1, AU NIVEAU du voile — peint après, le voile
   recouvrait le texte : un titre blanc sortait gris sur toute vidéo voilée. */
.content-section.content-section--has-bg-video:has(> .tw-backdrop--section) > :is(.content-section__container, .toweb-container) {
  position: relative;
  z-index: 2;
}

/* Reduced motion: hide video for users who prefer it. L'image d'attente reste en
   fond (style en ligne posé par le générateur) ; sans elle, un fond sombre neutre
   garde lisible un texte clair (`--text-light`) — il sortait blanc sur le fond
   clair du DNA (audit des 48 DNA, 2026-09-28). */
@media (prefers-reduced-motion: reduce) {
  .content-section__video {
    display: none;
  }
  .content-section--has-bg-video.content-section--text-light {
    background-color: #161616;
  }
}

/* Un MOT trop long pour la ligne (« Comportementale » dans un grand titre, site
   client V14 importé, 390 px) sortait de l'écran, rogné. Il se coupe au lieu de
   déborder ; `break-word` ne touche pas un mot qui tient. PAS de `hyphens: auto` :
   essayé, il coupait tous les mots d'un titre centré (« l'Asso-ciation »,
   « Lan-guedoc »), bien pire que le mal.
   `anywhere` et non `break-word` (audit WCAG de lauyan.com, 2026-09-29, 1.4.12) :
   `break-word` ne compte pas dans la largeur MINIMALE du titre. Dans une colonne
   flex (le texte du héros), le titre gardait la largeur de son plus long mot et
   débordait de son parent — « transformé », à 390 px, avec l'espacement de
   lettres qu'un visiteur impose, était coupé par le bord. Même coupure, seulement
   quand un mot ne tient pas. */
.content-section :is(h1, h2, h3, h4, h5, h6) { overflow-wrap: anywhere; }

/* Full viewport height sections (hero/landing style) */
.content-section--full-height {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.content-section--full-height > .content-section__container,
.content-section--full-height > .toweb-container {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* ── Text layout (PageXL editorial format) ─────────────────────────────────
   Newspaper multi-column flow + drop-cap + justify, applied to the section's
   prose. Targets BOTH wrappers (editor .content-section__container, publish
   .toweb-container) like the bg-image rules above. Token-only (--tw-rule / --tw-gap
   / --tw-dropcap-*). ADDITIVE + DEFAULT-OFF: a section without these classes is
   unaffected. */
/* The class content-section--text-cols-N is emitted by BOTH paths (editor renderHTML
   + publish tiptap-to-html), so the class selector alone covers editor and publish.
   NB: the editor forces .content-section__container to display:flex, which kills
   multicol — a display:block override sits next to that flex rule in styles.css.
   NO break-inside: prose (incl. a single long paragraph) must flow across columns. */
.content-section--text-cols-2 > .content-section__container,
.content-section--text-cols-2 > .toweb-container { column-count: 2; column-gap: var(--tw-gap, 2.5rem); column-rule: 1px solid var(--tw-rule); }
.content-section--text-cols-3 > .content-section__container,
.content-section--text-cols-3 > .toweb-container { column-count: 3; column-gap: var(--tw-gap, 2.5rem); column-rule: 1px solid var(--tw-rule); }
/* headings, images & non-prose (e.g. a stats block) span ALL columns (full-width,
   PageXL "About Us" style). */
.content-section--text-cols-2 > :is(.content-section__container,.toweb-container) > :not(p):not(ul):not(ol),
.content-section--text-cols-3 > :is(.content-section__container,.toweb-container) > :not(p):not(ul):not(ol) { column-span: all; }
/* A full-width BLOCK above the columns (stats / image / divider — NOT a heading) gets a
   bottom gap so the columns resume uniformly below it: its own bottom margin offsets
   every column equally, whereas a paragraph's top margin would push only column 1.
   Headings are EXCLUDED so they keep their small per-element bottom margin (0.5rem) and
   hug the body text. The container is display:block in BOTH editor and publish (text-cols
   override in styles.css + astro-preview.css), and the per-element heading/paragraph
   margins are aligned across both, so the whole section spaces identically editor↔publish.
   Prose (p/ul/ol) is left to those per-element rules — they already set margin-top:0
   (flush column tops) + a bottom margin, in both contexts. */
.content-section--text-cols-2 > :is(.content-section__container,.toweb-container) > :not(p):not(ul):not(ol):not(h1):not(h2):not(h3):not(h4):not(h5):not(h6),
.content-section--text-cols-3 > :is(.content-section__container,.toweb-container) > :not(p):not(ul):not(ol):not(h1):not(h2):not(h3):not(h4):not(h5):not(h6) { margin-bottom: var(--theme-block-gap, 1.5rem); }
/* drop-cap on the first paragraph */
.content-section--text-dropcap > :is(.content-section__container,.toweb-container) p:first-of-type::first-letter {
  font-family: var(--tw-dropcap-font, var(--tw-font-heading, serif)); font-size: var(--tw-dropcap-size, 3.2em);
  line-height:.8; float:left; margin:.05em .12em 0 0; color: var(--tw-dropcap-color, var(--tw-accent, inherit)); }
/* justify */
.content-section--text-justify > :is(.content-section__container,.toweb-container) :is(p,li) { text-align: justify; hyphens: auto; -webkit-hyphens: auto; }
/* responsive — collapse to 1 column on mobile */
@media (max-width: 760px){
  .content-section--text-cols-2 > :is(.content-section__container,.toweb-container),
  .content-section--text-cols-3 > :is(.content-section__container,.toweb-container) { column-count: 1; }
}

/* L'EN-TÊTE PASSE AU-DESSUS DE LA PAGE. Ses menus déroulants (langues, sous-menus)
   débordent sur la section suivante ; or une section qui porte une ambiance place
   son contenu en `z-index: 1`, et l'en-tête — contexte d'empilement propre depuis
   son `view-transition-name` — n'en avait aucun : la liste des langues s'ouvrait
   SOUS le héros (capture de Yann, 2026-09-26). */
.content-section--system:has(> .site-header-block) { z-index: 50; }

/* Défilement infini (defilement-infini.ts) : la pagination devient un simple
   repère de chargement ; les éléments ajoutés entrent en fondu. */
.pagination.pagination--infinie { visibility: hidden; height: 3rem; }
.pagination.pagination--infinie.is-chargement { visibility: visible; }
.pagination.pagination--infinie.is-chargement > * { display: none; }
.pagination.pagination--infinie.is-chargement::after { content: ""; display: block; margin: 0 auto; width: 1.6rem; height: 1.6rem; border-radius: 50%; border: 2px solid color-mix(in oklab, var(--tw-accent, #6366f1) 25%, transparent); border-top-color: var(--tw-accent, #6366f1); animation: tw-infini-tourne .8s linear infinite; }
.pagination.pagination--infinie.is-fin { display: none; }
@keyframes tw-infini-tourne { to { transform: rotate(1turn); } }
.tw-infini-arrive { animation: tw-infini-arrive .6s cubic-bezier(.19,1,.22,1) both; }
@keyframes tw-infini-arrive { from { opacity: 0; transform: translate3d(0, 24px, 0); } }
@media (prefers-reduced-motion: reduce) { .tw-infini-arrive { animation: none; } }

/* Un article de blog met son contenu (des sections) DANS la section de l'article :
   leurs marges s'additionnaient — un grand blanc entre les étiquettes et le texte
   (Yann, 2026-09-26). Dedans, une section ne garde qu'un espacement de paragraphe. */
.toweb-blog-post .post-content > .content-section { padding-block: clamp(0.75rem, 2vw, 1.5rem); }
.toweb-blog-post .post-content > .content-section:first-child { padding-top: clamp(1rem, 3vw, 2rem); }
.toweb-blog-post .post-content > .content-section > .toweb-container { padding-inline: 0; }
.toweb-blog-index { padding-top: clamp(2.5rem, 6vw, 4.5rem); }
.toweb-blog-index__titre { margin: 0 0 clamp(1rem, 2.5vw, 1.75rem); }
/* La description du blog (HELO 74857) : sous le titre, une mesure de lecture,
   la couleur atténuée du texte. Le titre garde sa marge quand elle est absente. */
.toweb-blog-index__titre:has(+ .toweb-blog-index__description) { margin-bottom: 0.75rem; }
.toweb-blog-index__description {
  max-width: 65ch;
  margin: 0 0 clamp(1.25rem, 3vw, 2rem);
  font-size: 1.125rem;
  line-height: 1.6;
  color: var(--tw-text-muted, inherit);
  text-wrap: pretty;
}

/* Texte-manifeste (`texte-defile.ts`) : un mot éteint est grisé, un mot allumé
   reprend la couleur du texte. Posé seulement quand le script a découpé le texte. */
.tw-texte-defile .tw-mot-defile { opacity: 0.18; transition: opacity 0.35s ease; }

/* (Les boutons « aimantés » ont été retirés le 2026-09-26 : le bouton suivait le
   pointeur de quelques pixels, et Yann l'a pris pour un défaut. Un bouton ne bouge
   pas sous le curseur ; le survol reste celui du DNA.) */
.content-section[data-atmosphere="liquid"] > .tw-backdrop--section .tw-atm-canvas { filter: blur(10px) saturate(1.08); transform: scale(1.06); }

/* Titres de section qui entrent (`headingEffect`, motion.ts) — posé par le script seulement. */
/* Aile des glyphes par padding : Safari ignore overflow-clip-margin (voir .tw-titre-anime--rise). */
.mv-titre-section .tw-mot { display: inline-block; vertical-align: top; overflow: clip; overflow-clip-margin: 0.12em; padding: 0 0.14em 0.08em; margin: 0 -0.14em -0.08em; }
.mv-titre-section .tw-mot__dedans { display: inline-block; transform: translate3d(0, 105%, 0); transition: transform 0.95s cubic-bezier(0.19, 1, 0.22, 1) calc(var(--mv-i, 0) * 60ms); }
.mv-titre-section--blur .tw-mot { overflow: visible; }
.mv-titre-section--blur .tw-mot__dedans { transform: translate3d(0, 0.3em, 0); opacity: 0; filter: blur(12px); transition: transform 0.9s cubic-bezier(0.19, 1, 0.22, 1) calc(var(--mv-i, 0) * 55ms), opacity 0.8s ease calc(var(--mv-i, 0) * 55ms), filter 0.8s ease calc(var(--mv-i, 0) * 55ms); }
.mv-titre-section.is-entre .tw-mot__dedans { transform: none; opacity: 1; filter: none; }
.tw-texte-defile .tw-mot-defile.is-allume { opacity: 1; }

/* POSITION DE L'EN-TÊTE (`data-entete`, réglage navigation.style.position).
   `--mv-entete` va de 0 (haut de page) à 1 (après ~120 px de défilement), posé
   par entete-defile.ts : le fond, le flou et l'ombre apparaissent peu à peu.
   - sticky / fixed : l'en-tête reste en haut, son fond se densifie au défilement ;
   - overlay : il FLOTTE au-dessus du héros, transparent, puis prend son fond. */
.content-section--system[data-entete="sticky"],
.content-section--system[data-entete="fixed"],
.content-section--system[data-entete="overlay"] {
  position: sticky;
  top: 0;
  z-index: 60;
  transition: box-shadow 0.3s ease;
}
.content-section--system:is([data-entete="sticky"], [data-entete="fixed"], [data-entete="overlay"]) > .site-header-block {
  background: color-mix(in oklab, var(--tw-bg, #fff) calc(55% + var(--mv-entete, 0) * 35%), transparent) !important;
  -webkit-backdrop-filter: saturate(1.6) blur(calc(var(--mv-entete, 0) * 16px));
          backdrop-filter: saturate(1.6) blur(calc(var(--mv-entete, 0) * 16px));
  box-shadow: 0 1px 0 color-mix(in oklab, var(--tw-text, #111) calc(var(--mv-entete, 0) * 10%), transparent),
              0 10px 30px -18px color-mix(in oklab, var(--tw-text, #111) calc(var(--mv-entete, 0) * 35%), transparent);
  transition: background-color 0.2s linear;
}
/* LE FOCUS N'EST JAMAIS SOUS L'EN-TÊTE COLLANT (WCAG 2.4.11, audit de lauyan.com
   du 2026-09-29). Au clavier, en remontant (Maj+Tab) ou en revenant au début
   après le bas de page, le navigateur amène l'élément au bord HAUT de l'écran —
   c'est-à-dire sous l'en-tête, qui le cachait entièrement (« Découvrir PageXL »,
   « Télécharger pour Windows » en 390 px). `scroll-padding-top` réserve la
   hauteur mesurée de l'en-tête (`--mv-entete-h`, entete-defile.ts). */
html:has(.content-section--system:is([data-entete="sticky"], [data-entete="fixed"], [data-entete="overlay"])) {
  scroll-padding-top: calc(var(--mv-entete-h, 72px) + 0.75rem);
}

/* Superposé, l'en-tête est TRANSLUCIDE (voile de 28 % en haut de page) : les liens
   du menu passent sur ce qu'il y a dessous. La couleur ATTÉNUÉE que la plupart des
   DNA donnent au menu (5,8:1 sur blanc) tombait à 3,5:1 sur le fond en dégradé du
   héros de lauyan.com (4,4:1 en sombre) — WCAG 1.4.3. Le menu y prend la couleur
   pleine du texte ; la version « texte clair sur photo » (plus bas) garde la main. */
.content-section--system[data-entete="overlay"] > .site-header-block .tw-nav-link {
  --tw-nav-color: var(--tw-text, #111);
}

/* Superposé : la section d'en-tête ne prend plus de place, le héros passe dessous. */
.content-section--system[data-entete="overlay"] {
  margin-bottom: calc(-1 * var(--mv-entete-h, 72px));
  background: transparent !important;
}
.content-section--system[data-entete="overlay"] > .site-header-block {
  background: color-mix(in oklab, var(--tw-bg, #fff) calc(28% + var(--mv-entete, 0) * 60%), transparent) !important;
  -webkit-backdrop-filter: saturate(1.4) blur(calc(6px + var(--mv-entete, 0) * 10px));
          backdrop-filter: saturate(1.4) blur(calc(6px + var(--mv-entete, 0) * 10px));
}
/* Le héros qui passe sous un en-tête superposé garde son titre lisible. */
.content-section--system[data-entete="overlay"] + * > .content-section:first-child,
.content-section--system[data-entete="overlay"] ~ main > .content-section:first-child {
  padding-top: calc((var(--tw-section-py, 6rem) * var(--tw-section-density, 1)) * 0.55 + var(--mv-entete-h, 72px));
}

/* Superposé sur un HÉROS À TEXTE CLAIR (photo ou vidéo sombre, `--text-light`) :
   tant que l'en-tête est encore transparent (`data-entete-haut`, posé par
   entete-defile.ts dans les 40 premiers pixels), il n'a pas de voile clair et son
   texte est clair lui aussi — l'« en-tête transparent sur image » de Squarespace
   ou Shopify. Dès qu'il prend son fond au défilement, il retrouve les couleurs du
   DNA. Sans ça, le menu gris d'un DNA clair se perdait sur la vidéo (audit des
   48 DNA, 2026-09-28). Le sous-menu garde son propre fond : on ne le touche pas. */
.content-section--system[data-entete="overlay"][data-entete-haut]:has(~ main > .content-section--text-light:first-child) > .site-header-block {
  background: transparent !important;
  -webkit-backdrop-filter: none;
          backdrop-filter: none;
  box-shadow: none;
  border-color: transparent;
  color: var(--tw-entete-texte, #ffffff);
}
.content-section--system[data-entete="overlay"][data-entete-haut]:has(~ main > .content-section--text-light:first-child) > .site-header-block :is(.site-header-block__link, .site-header-block__logo, .site-header-block__logo-text, .site-header-block__action):not(.site-header-block__dropdown *) {
  color: var(--tw-entete-texte, #ffffff);
  -webkit-text-fill-color: var(--tw-entete-texte, #ffffff);
  text-shadow: 0 1px 10px rgba(0, 0, 0, 0.35);
}
/* La couleur CHOISIE dans le panneau (« Couleur du texte », HELO 75064) vaut en
   haut de page sur n'importe quel héros, pas seulement sur une photo sombre :
   c'est le réglage « en-tête transparent » de Shopify (Dawn). Au défilement,
   l'en-tête reprend son fond et les couleurs du style. */
.content-section--system[data-entete="overlay"][data-entete-couleur][data-entete-haut] > .site-header-block :is(.tw-nav-link, .site-header-block__link, .site-header-block__logo, .site-header-block__logo-text, .site-header-block__action):not(.site-header-block__dropdown *) {
  --tw-nav-color: var(--tw-entete-texte, #ffffff);
  color: var(--tw-entete-texte, #ffffff);
  -webkit-text-fill-color: var(--tw-entete-texte, #ffffff);
}

/* #353 — UN HÉROS PLEINE HAUTEUR EN TÊTE DE PAGE (photo ou vidéo de fond) : l'en-tête
   « collant » ou « fixe » s'y SUPERPOSE, comme « superposé ». Laurent (accueil V14
   « Ristretto » importé) : l'en-tête occupait 82 px au-dessus d'une vidéo en pleine
   hauteur, qui débordait donc de l'écran de 82 px — « pleine hauteur » veut dire
   « le premier écran », et c'est l'en-tête transparent sur image de Shopify (Dawn),
   Squarespace ou Apple. Rien ne change ailleurs : une page qui commence par un
   texte garde son en-tête collant au-dessus. Dès le défilement, l'en-tête prend
   son fond (le comportement collant habituel). */
.content-section--system:is([data-entete="sticky"], [data-entete="fixed"]):has(~ main > .content-section--full-height:is(.content-section--has-bg-image, .content-section--has-bg-video):first-child) {
  margin-bottom: calc(-1 * var(--mv-entete-h, 72px));
  background: transparent !important;
}
.content-section--system:is([data-entete="sticky"], [data-entete="fixed"]):has(~ main > .content-section--full-height:is(.content-section--has-bg-image, .content-section--has-bg-video):first-child) > .site-header-block {
  background: color-mix(in oklab, var(--tw-bg, #fff) calc(28% + var(--mv-entete, 0) * 60%), transparent) !important;
}
.content-section--system:is([data-entete="sticky"], [data-entete="fixed"]) ~ main > .content-section--full-height:is(.content-section--has-bg-image, .content-section--has-bg-video):first-child {
  padding-top: calc((var(--tw-section-py, 6rem) * var(--tw-section-density, 1)) * 0.55 + var(--mv-entete-h, 72px));
}
/* Sur un héros SOMBRE (texte clair, ou palette « forte » — l'aplat sombre du DNA) :
   tant qu'on est en haut, en-tête transparent et texte blanc, comme « superposé ». */
.content-section--system:is([data-entete="sticky"], [data-entete="fixed"])[data-entete-haut]:has(~ main > .content-section--full-height:is(.content-section--has-bg-image, .content-section--has-bg-video):is(.content-section--text-light, [data-palette="bold"]):first-child) > .site-header-block {
  background: transparent !important;
  -webkit-backdrop-filter: none;
          backdrop-filter: none;
  box-shadow: none;
  border-color: transparent;
  color: #ffffff;
}
.content-section--system:is([data-entete="sticky"], [data-entete="fixed"])[data-entete-haut]:has(~ main > .content-section--full-height:is(.content-section--has-bg-image, .content-section--has-bg-video):is(.content-section--text-light, [data-palette="bold"]):first-child) > .site-header-block :is(.site-header-block__link, .site-header-block__logo, .site-header-block__logo-text, .site-header-block__action):not(.site-header-block__dropdown *) {
  color: #ffffff;
  -webkit-text-fill-color: #ffffff;
  text-shadow: 0 1px 10px rgba(0, 0, 0, 0.35);
}

/* HELO 74949 — L'IMAGE DE FOND DE L'EN-TÊTE ET DU PIED (`shared/fond-section-systeme.ts`).
   Posée sur le BLOC, par-dessus SA couleur : le bloc garde son fond et son texte
   (rendu transparent, un pied à texte clair devenait illisible sur une page claire).
   `!important` : c'est un choix explicite de l'utilisateur, il passe devant les
   habillages des DNA, qui repeignent ces blocs avec un `background` raccourci.
   L'en-tête « superposé » sur un héros pleine hauteur (plus haut) reste transparent. */
.content-section--system.content-section--has-bg-image :is(.site-header-block, .site-footer-block) {
  background-image: var(--tw-fond-systeme-image, none) !important;
  background-position: var(--tw-fond-systeme-position, center) !important;
  background-size: var(--tw-fond-systeme-taille, cover) !important;
  background-repeat: var(--tw-fond-systeme-repetition, no-repeat) !important;
}
/* Le verre dépoli de certains DNA (cobalt : un `::before` en `backdrop-filter`
   sous le contenu) floutait l'image elle-même : elle n'est pas un arrière-plan à
   dépolir, c'est le décor choisi. */
.content-section--system.content-section--has-bg-image :is(.site-header-block, .site-footer-block)::before {
  -webkit-backdrop-filter: none !important;
          backdrop-filter: none !important;
}
/* La frise du pied est une bande en bas : le pied lui réserve sa hauteur, le texte
   ne passe pas dessus. */
.content-section--system.content-section--frise .site-footer-block {
  padding-bottom: calc(0.75rem + 72px);
}
/* Texte clair sur l'image de l'en-tête (réglage « texte clair » de la section) :
   le même traitement que l'en-tête superposé sur un héros sombre. */
.content-section--system.content-section--has-bg-image.content-section--text-light > * .site-header-block :is(.site-header-block__link, .site-header-block__logo, .site-header-block__logo-text, .site-header-block__action, .site-header-block__group-label):not(.site-header-block__dropdown *),
.content-section--system.content-section--has-bg-image.content-section--text-light > .site-header-block :is(.site-header-block__link, .site-header-block__logo, .site-header-block__logo-text, .site-header-block__action, .site-header-block__group-label):not(.site-header-block__dropdown *) {
  color: #ffffff;
  -webkit-text-fill-color: #ffffff;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.75), 0 0 12px rgba(0, 0, 0, 0.55);
}

/* Manual text-on-background override (attrs.textColor). DNA-first: the palette
   ([data-palette]) already contrasts text vs background, so --text-dark is a
   no-op; --text-light forces light text for a dark photo/video background.
   ⚠️ LE JEU CONTEXTUEL COMPLET, PAS DEUX JETONS (audit des 48 DNA, 2026-09-28).
   Ne re-pointer que le texte et les titres, c'est le défaut que le contrat §6
   interdit aux palettes (« une palette qui ne re-pointe que le fond ») : sur une
   vidéo ou une photo voilée, le chapeau (`--tw-text-muted`), le sur-titre, le
   lien-CTA, les liens du texte et le bouton secondaire gardaient les couleurs
   d'un DNA CLAIR — gris ou bleu foncé sur fond sombre, dans les 48 DNA.
   La seconde forme (`[data-dna] …:not():not()`, 0,5,0) passe devant les palettes
   de section d'un DNA (0,2,0) et les couleurs reprises de V14 (0,4,0) : c'est un
   choix EXPLICITE de l'utilisateur pour CETTE section. Les cartes (`.tw-surface`
   matérialisée) se re-localisent sur leur on-surface, elles restent lisibles. */
.content-section.content-section--text-light,
[data-dna] .content-section.content-section--text-light:not(.content-section--text-dark):not(.content-section--system) {
  --tw-text: #f8fafc;
  --tw-text-heading: #ffffff;
  --tw-text-muted: rgba(248, 250, 252, 0.86);
  --tw-eyebrow-color: rgba(255, 255, 255, 0.9);
  --tw-cta-color: #ffffff;
  --tw-cta-underline-color: rgba(255, 255, 255, 0.6);
  --tw-cta-underline-hover: #ffffff;
  --tw-prose-link: #ffffff;
  --tw-btn-outline-text: #ffffff;
  --tw-btn-outline-border: rgba(255, 255, 255, 0.72);
  --tw-accent-soft: rgba(255, 255, 255, 0.16);
  --tw-border: rgba(255, 255, 255, 0.28);
  --tw-rule: rgba(255, 255, 255, 0.24);
  --tw-rule-strong: rgba(255, 255, 255, 0.6);
  color: var(--tw-text);
}
/* La flèche « Voir la suite » sur une image : claire, avec une ombre portée qui
   la détache d'une vidéo chargée. */
.content-section--text-light > .content-section__suite { color: #ffffff; filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.45)); }

/* ═══════════════════════════════════════════════════════════════════════════
   COLLECTION COMMON STYLES
   Shared gap/spacing system for all collection blocks (FAQ, Team, Gallery,
   Features, Testimonials, etc.)

   This unified system allows Theme settings to control spacing globally.
   Blocks apply these classes via data attributes or CSS class modifiers.

   CSS Variables (can be overridden in Theme):
   - --collection-gap-h-small, --collection-gap-h-medium, --collection-gap-h-large
   - --collection-gap-v-small, --collection-gap-v-medium, --collection-gap-v-large
   ═══════════════════════════════════════════════════════════════════════════ */

/* Default gap values - can be customized in Theme step */
:root {
  --collection-gap-h-small: 0.75rem;
  --collection-gap-h-medium: 1.5rem;
  --collection-gap-h-large: 2.5rem;
  --collection-gap-v-small: 0.75rem;
  --collection-gap-v-medium: 1.5rem;
  --collection-gap-v-large: 2.5rem;
}

/* R.6 strip (Yann 2026-06-05): the per-block gap classes are DEPRECATED — they
   now ALL defer to the DNA's --tw-gap. The block panels no longer
   expose a Gap control; the DNA owns the rhythm uniformly. The classes are kept
   as DNA-token aliases so any existing document that still carries them (and the
   editor NodeViews that still emit them) render with the DNA gap, matching the
   Astro side which no longer emits them at all. */
.collection--gap-h-none,
.collection--gap-h-small,
.collection--gap-h-medium,
.collection--gap-h-large { column-gap: var(--tw-gap, 1.5rem); }

.collection--gap-v-none,
.collection--gap-v-small,
.collection--gap-v-medium,
.collection--gap-v-large { row-gap: var(--tw-gap, 1.5rem); }

.collection--gap-small,
.collection--gap-medium,
.collection--gap-large {
  column-gap: var(--tw-gap, 1.5rem);
  row-gap: var(--tw-gap, 1.5rem);
}

/* ═══════════════════════════════════════════════════════════════════════════
   CTA BLOCK - Call to Action Buttons
   Per EDITOR-BLOCKS-CONTRACT.md §CTA
   ═══════════════════════════════════════════════════════════════════════════ */

.cta-block {
  display: flex;
  flex-wrap: wrap;
  width: 100%;
  min-height: 2.5rem; /* Ensure clickable area for selection */
  padding: 0.25rem 0;
  box-sizing: border-box;
  /* R.6 strip: button gap is DNA-owned (tighter than the card grid gap). */
  gap: var(--tw-gap, 0.75rem);
}

.cta-block--align-left { justify-content: flex-start; }
.cta-block--align-center { justify-content: center; }
.cta-block--align-right { justify-content: flex-end; }

/* R.6 strip: legacy gap classes neutralised → DNA-owned --theme-cta-gap. */
.cta-block--gap-small,
.cta-block--gap-medium,
.cta-block--gap-large { gap: var(--tw-gap, 0.75rem); }

/* CTA Button Base - M-THEME-SCR.5: Uses theme CSS variables */
.cta-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* M-THEME-SCR.5: Use theme button padding/radius/shadow/border */
  padding: 0.625rem 1.25rem;
  border-radius: var(--tw-radius-button, 8px);
  box-shadow: var(--tw-shadow-button, none);
  /* M-THEME-SCR.5 FIX: Explicitly inherit font from theme */
  font-family: var(--theme-font-body, inherit);
  font-size: 1rem;
  font-weight: var(--tw-btn-weight, 500);
  text-decoration: none;
  cursor: pointer;
  transition: transform 0.15s, opacity 0.15s, background-color 0.15s, border-color 0.15s, filter 0.15s, box-shadow 0.15s;
  /* M-THEME-BTN-BORDER: Use theme border width, default transparent for filled buttons */
  border-width: var(--tw-border-width, 0px);
  border-style: solid;
  border-color: transparent;
}

/* M-THEME-SCR.5: Base hover - no transform/filter here, handled by generateThemeCSS based on hoverEffect setting */
.cta-button:hover {
  /* Hover effect applied via generateThemeCSS based on buttons.hoverEffect */
}

/* CTA Button Variants — colours come from the DNA brick tokens (--tw-btn-*,
   re-pointed per section by dna-{x}.css [data-palette]). */
.cta-button--primary {
  background: var(--tw-btn-bg, var(--tw-accent));
  color: var(--tw-btn-text, var(--tw-on-accent));
  /* Bordure = la bordure-bouton du DNA (transparent pour la plupart : le fond +
     l'ombre portent le bouton). PAS currentColor (donnait un liseré texte). */
  border-color: var(--tw-btn-border, transparent);
}

/* Hover = la brique BOUTON (--tw-btn-bg-hover + ombre hover + lift), PAS l'accent
   brut : sinon un bouton noir (Brutalist) virait bleu au survol — retour Yann 2026-06-22. */
.cta-button--primary:hover {
  transform: translateY(var(--tw-btn-hover-lift, -1px));
  background: var(--tw-btn-bg-hover, var(--tw-btn-bg, var(--tw-accent)));
  color: var(--tw-btn-text-hover, var(--tw-btn-text, var(--tw-on-accent)));
  box-shadow: var(--tw-shadow-button-hover, var(--tw-shadow-button, none));
  border-color: var(--tw-btn-border, transparent);
}

.cta-button--secondary {
  background: var(--tw-accent-2);
  color: var(--tw-on-accent);
  /* Border = text colour, matches the DNA button brick */
  border-color: currentColor;
}

/* M-THEME-SCR.5: Secondary hover handled by generateThemeCSS (supports filled/outline modes) */

.cta-button--outline {
  background: transparent;
  border-color: var(--tw-accent, var(--tw-accent));
  color: var(--tw-accent, var(--tw-accent));
}

.cta-button--outline:hover {
  background: var(--tw-accent, var(--tw-accent));
  color: var(--tw-on-accent, var(--tw-on-accent));
  border-color: var(--tw-accent, var(--tw-accent));
}

.cta-button--link {
  background: transparent;
  color: var(--tw-accent, var(--tw-accent));
  text-decoration: none;
}

.cta-button--link:hover {
  text-decoration: underline;
}

/* CTA Button Sizes - M-THEME-SCR.5: Scale based on theme variables */
.cta-button--small {
  padding: calc(0.625rem * 0.6) calc(1.25rem * 0.6);
  font-size: 0.875rem;
}

.cta-button--medium {
  /* Uses default theme padding from .cta-button base */
  font-size: 1rem;
}

.cta-button--large {
  padding: calc(0.625rem * 1.2) calc(1.25rem * 1.2);
  font-size: 1.125rem;
}

/* Selection state for CTA block */
.cta-block.ProseMirror-selectednode {
  outline: 2px solid var(--tw-accent, #3b82f6);
  outline-offset: 4px;
  border-radius: 0.5rem;
}

/* ═══════════════════════════════════════════════════════════════════════════
   FAQ BLOCK — RETIRED (DNA-BRICKS migration 2026-06-08)
   Layout → blocks-base.css (--tw-* tokens only).
   Editor affordances (placeholder, selection ring) → blocks-editor.css.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════════════
   IMAGE BLOCK
   Per EDITOR-BLOCKS-CONTRACT.md §IMAGE
   Container is always full width, image constrained and aligned within
   ═══════════════════════════════════════════════════════════════════════════ */

.image-block {
  display: flex;
  flex-direction: column;
  width: 100%;
  color: inherit; /* Ensure border color inheritance from section */
}

.image-block img {
  /* sizing (width/height/object-fit) owned by the .tw-media brick (.tw-media > img) */
  display: block;
  color: inherit; /* Ensure border color inheritance from section */
}

/* Media wrapper: fit-content to match image dimensions for overlay effects.
   position/overflow/radius/shadow owned by the .tw-media brick. */
.image-block__media {
  display: block;
  width: fit-content;
  max-width: 100%;
}

/* Image sizes - apply to wrapper for proper overlay sizing, fallback to img for legacy.
   XS (150 px) : un logo ou un badge (HELO 74947). Un plafond, pas une largeur : une
   image plus petite garde sa taille, et `min(…, 100%)` la tient dans une colonne étroite.
   Même valeur sur téléphone : un logo ne grandit pas quand l'écran rétrécit. */
.image-block--xsmall .image-block__media { max-width: min(150px, 100%); }
.image-block--small .image-block__media { max-width: min(300px, 100%); }
.image-block--medium .image-block__media { max-width: min(600px, 100%); }
.image-block--large .image-block__media { max-width: min(900px, 100%); }
.image-block--full .image-block__media { max-width: 100%; width: 100%; }
/* Fallback for blocks without wrapper */
.image-block--xsmall img { max-width: min(150px, 100%); }
.image-block--small img { max-width: min(300px, 100%); }
.image-block--medium img { max-width: min(600px, 100%); }
.image-block--large img { max-width: min(900px, 100%); }
.image-block--full img { max-width: 100%; }
/* Image width owned by the .tw-media brick (.tw-media > img). */

/* Image alignment - uses flexbox alignment on container */
.image-block--align-left { align-items: flex-start; }
.image-block--align-center { align-items: center; }
.image-block--align-right { align-items: flex-end; }

/* Image options */
.image-block--rounded img { border-radius: var(--tw-radius-media, 8px); }
.image-block--shadow img { box-shadow: var(--tw-shadow-media, 0 4px 12px rgba(0, 0, 0, 0.15)); }

/* Image aspect ratios */
.image-block--aspect-16-9 img { aspect-ratio: 16/9; object-fit: cover; }
.image-block--aspect-4-3 img { aspect-ratio: 4/3; object-fit: cover; }
.image-block--aspect-1-1 img { aspect-ratio: 1/1; object-fit: cover; }
.image-block--aspect-3-2 img { aspect-ratio: 3/2; object-fit: cover; }
.image-block--aspect-2-3 img { aspect-ratio: 2/3; object-fit: cover; }

/* ─────────────────────────────────────────────────────────────────────────────
   IMAGE SHAPE STYLES
   Border-radius only - separate from frame effects to allow combinations
   ───────────────────────────────────────────────────────────────────────────── */

/* Shape: Rounded - rounded corners */
.image-block--shape-rounded img,
.image-block--shape-rounded video,
.image-block--shape-rounded .image-block__media {
  border-radius: 1rem;
}

/* Shape: Circular - circle crop */
.image-block--shape-circular img,
.image-block--shape-circular video,
.image-block--shape-circular .image-block__media {
  border-radius: 50%;
}

/* ─────────────────────────────────────────────────────────────────────────────
   IMAGE FRAME STYLES (M-SIDEBAR.4)
   Visual presets for image borders, shadows, transforms
   ───────────────────────────────────────────────────────────────────────────── */

/* Frame: Simple Floating - shadow effect with elevation */
.image-block--frame-simple-floating {
  padding: 1rem;
}
.image-block--frame-simple-floating img,
.image-block--frame-simple-floating video {
  box-shadow:
    0 10px 20px rgba(0, 0, 0, 0.19),
    0 6px 6px rgba(0, 0, 0, 0.23);
}

/* Frame: Border - simple thin border using section's text color */
.image-block--frame-border {
  padding: 1rem;
}
.image-block--frame-border img,
.image-block--frame-border video {
  border: 1px solid color-mix(in srgb, var(--tw-text, currentColor) 25%, transparent);
}

/* Frame: Inset - deep "hole" effect with overlay pseudo-element */
.image-block--frame-inset {
  padding: 1rem;
}
.image-block--frame-inset img,
.image-block--frame-inset video {
  display: block;
}
/* Media wrapper: needed for inset overlay to match image dimensions */
.image-block--frame-inset .image-block__media {
  position: relative;
  display: block;
}
/* Overlay pseudo-element for inset shadow (box-shadow inset doesn't work on img) */
.image-block--frame-inset .image-block__media::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  pointer-events: none;
  border-radius: inherit; /* Inherits from .image-block__media which has shape's border-radius */
  /* Strong multi-layer inner shadow for deep "hole" effect */
  box-shadow:
    inset 0 8px 20px rgba(0, 0, 0, 0.5),       /* Top shadow - strongest */
    inset 0 -4px 10px rgba(0, 0, 0, 0.2),      /* Bottom subtle lift */
    inset 8px 0 15px rgba(0, 0, 0, 0.3),       /* Left shadow */
    inset -8px 0 15px rgba(0, 0, 0, 0.3),      /* Right shadow */
    inset 0 2px 4px rgba(0, 0, 0, 0.4);        /* Edge definition */
  /* Dark border to define the "hole" edge */
  border: 2px solid rgba(0, 0, 0, 0.4);
}

/* Frame: Navigateur — la capture d'écran posée dans une fenêtre : une barre de
   titre aux trois pastilles, un bord fin, une ombre longue. Tout est peint sur
   l'image elle-même (padding + fonds), donc AUCUN élément de plus dans le HTML :
   éditeur et publication gardent le même DOM. Couleurs tirées des jetons. */
.image-block--frame-browser img,
.image-block--frame-browser video {
  box-sizing: border-box;
  padding-top: 2rem;
  border: 1px solid color-mix(in oklab, var(--tw-text, #111) 12%, transparent);
  border-radius: 12px;
  background:
    radial-gradient(circle 5px at 18px 1rem, #ff5f57 97%, transparent 100%) no-repeat,
    radial-gradient(circle 5px at 36px 1rem, #febc2e 97%, transparent 100%) no-repeat,
    radial-gradient(circle 5px at 54px 1rem, #28c840 97%, transparent 100%) no-repeat,
    linear-gradient(color-mix(in oklab, var(--tw-text, #111) 6%, var(--tw-surface, #fff)), color-mix(in oklab, var(--tw-text, #111) 6%, var(--tw-surface, #fff))) top / 100% 2rem no-repeat,
    var(--tw-surface, #fff);
  box-shadow:
    0 1px 2px rgba(0, 0, 0, 0.06),
    0 24px 60px -12px rgba(0, 0, 0, 0.28);
}

/* Frame: Téléphone — la capture mobile dans un appareil : bord sombre épais,
   grands arrondis, encoche peinte en dégradé radial sur le haut du bord. Comme
   le cadre navigateur, TOUT est peint sur l'image (aucun élément ajouté). La
   taille se règle par `size` ; on plafonne la largeur, un téléphone de 1 200 px
   ne ressemble plus à un téléphone. */
.image-block--frame-phone img,
.image-block--frame-phone video {
  box-sizing: border-box;
  max-width: min(100%, 340px);
  margin-inline: auto;
  display: block;
  border: 11px solid #0b0d12;
  border-radius: 44px;
  background: #0b0d12;
  box-shadow:
    0 0 0 1.5px color-mix(in oklab, #0b0d12 70%, #9ca3af),
    0 30px 60px -18px rgba(0, 0, 0, 0.45),
    0 12px 24px -12px rgba(0, 0, 0, 0.3);
}

/* Les cadres navigateur et téléphone sont PEINTS SUR L'IMAGE : leur enveloppe
   `.tw-media` ne doit rien ajouter. Son fond d'attente (`--tw-media-placeholder`,
   un dégradé chez Cobalt), son rayon et son ombre apparaissaient autour du cadre
   — et derrière l'image quand un effet de défilement la réduit. */
.image-block:is(.image-block--frame-browser, .image-block--frame-phone) .image-block__media,
.image-block[data-scroll-effect]:not([data-scroll-effect="parallax"]) .image-block__media {
  background: none;
  box-shadow: none;
  overflow: visible;
}
.image-block--frame-phone .image-block__media { border-radius: 0; max-width: min(100%, 340px); margin-inline: auto; }
.image-block--frame-browser .image-block__media { border-radius: 0; }

/* Frame: Photo - white border + shadow (like physical photo) */
.image-block--frame-photo {
  padding: 1rem;
}
.image-block--frame-photo img,
.image-block--frame-photo video {
  border: 0.5em solid rgba(255, 255, 255, 0.9);
  box-shadow:
    0 10px 20px rgba(0, 0, 0, 0.19),
    0 6px 6px rgba(0, 0, 0, 0.23);
}

/* Frame: Photo Tilt - photo with slight rotation */
.image-block--frame-photo-tilt {
  padding: 1rem;
}
.image-block--frame-photo-tilt img,
.image-block--frame-photo-tilt video {
  border: 0.5em solid rgba(255, 255, 255, 0.9);
  box-shadow:
    0 10px 20px rgba(0, 0, 0, 0.19),
    0 6px 6px rgba(0, 0, 0, 0.23);
}
/* Tilt direction based on alignment */
.image-block--frame-photo-tilt.image-block--align-left img,
.image-block--frame-photo-tilt.image-block--align-left video,
.image-block--frame-photo-tilt.image-block--align-center img,
.image-block--frame-photo-tilt.image-block--align-center video {
  transform: rotate(-1deg);
}
.image-block--frame-photo-tilt.image-block--align-right img,
.image-block--frame-photo-tilt.image-block--align-right video {
  transform: rotate(1deg);
}

/* Frame: Polaroid - instant photo style with extra bottom padding */
.image-block--frame-polaroid {
  padding: 1rem;
}
.image-block--frame-polaroid img,
.image-block--frame-polaroid video {
  border: 0.6em solid white;
  border-bottom-width: 2.5em;
  box-shadow:
    0 4px 6px rgba(0, 0, 0, 0.1),
    0 10px 20px rgba(0, 0, 0, 0.15);
  background: white;
}

/* Frame: Circular - border-radius 50% (deprecated, prefer shape-circular) */
.image-block--frame-circular {
  padding: 1rem;
}
.image-block--frame-circular img,
.image-block--frame-circular video {
  border-radius: 50%;
}

/* Frame: Simple - just padding, no effects */
.image-block--frame-simple {
  padding: 1rem;
}

/* Frame: Rounded - rounded corners (deprecated, prefer shape-rounded) */
.image-block--frame-rounded {
  padding: 1rem;
}
.image-block--frame-rounded img,
.image-block--frame-rounded video {
  border-radius: 1rem;
}

/* Frame: Perspective - 3D perspective effect */
.image-block--frame-perspective {
  padding: 2rem;
  perspective: 60em;
}
.image-block--frame-perspective.image-block--align-center {
  padding: 0 10% 2em 10%;
}
.image-block--frame-perspective.image-block--align-center img,
.image-block--frame-perspective.image-block--align-center video {
  transform: rotateX(25deg);
  box-shadow: 0 1em 2em rgba(0, 0, 0, 0.5);
}
.image-block--frame-perspective.image-block--align-left img,
.image-block--frame-perspective.image-block--align-left video {
  transform: rotateY(25deg);
  box-shadow: -1em 1em 2em rgba(0, 0, 0, 0.5);
}
.image-block--frame-perspective.image-block--align-right img,
.image-block--frame-perspective.image-block--align-right video {
  transform: rotateY(-25deg);
  box-shadow: 1em 1em 2em rgba(0, 0, 0, 0.5);
}

/* Frame: Vintage - sepia tone with slight contrast boost */
.image-block--frame-vintage {
  padding: 1rem;
}
.image-block--frame-vintage img,
.image-block--frame-vintage video {
  filter: sepia(0.3) contrast(1.1) saturate(0.9);
}

/* ─────────────────────────────────────────────────────────────────────────────
   IMAGE CAPTION STYLES (M-SIDEBAR.17)
   Shared pattern between /image and /gallery - keep in sync!
   ───────────────────────────────────────────────────────────────────────────── */

/* Caption Style: Below (default) - caption below the image */
.image-block__caption,
.image-block figcaption {
  margin-top: 0.5rem;
  padding: 0.25rem 0.5rem;
  text-align: center;
  /* × « Taille du texte » (Thème › Formes et rythme) : sans réglage, le facteur
     vaut 1 et le rendu est inchangé au pixel près (HELO 75232). */
  font-size: calc(0.875rem * var(--tw-text-scale, 1));
  /* l'atténué du DNA, pas une opacité (qui ne garantit aucun contraste) */
  color: var(--tw-text-muted, var(--tw-text));
  line-height: 1.4;
}

/* Caption Style: None - hide caption (for blocks with captionStyle='none') */
.image-block--caption-none figcaption,
.image-block--caption-none .image-block__caption {
  display: none;
}

/* Caption Style: Overlay - caption overlaid at bottom with gradient background */
/* The figcaption is INSIDE .image-block__media for overlay/hover styles */
.image-block--caption-overlay .image-block__media {
  position: relative;
  overflow: hidden;
}
.image-block--caption-overlay figcaption,
.image-block--caption-overlay .image-block__caption {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  margin: 0;
  padding: 2rem 1rem 0.75rem;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.7) 0%, rgba(0, 0, 0, 0.4) 60%, transparent 100%);
  color: white;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
  opacity: 1;
}

/* Caption Style: Hover - caption appears on hover with blur effect */
/* The figcaption is INSIDE .image-block__media for overlay/hover styles */
.image-block--caption-hover .image-block__media {
  position: relative;
  overflow: hidden;
}
.image-block--caption-hover img {
  transition: filter 0.3s ease, transform 0.3s ease;
}
.image-block--caption-hover figcaption,
.image-block--caption-hover .image-block__caption {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  margin: 0;
  padding: 1rem 1.5rem;
  background: rgba(0, 0, 0, 0.75);
  color: white;
  border-radius: 0.5rem;
  text-align: center;
  opacity: 0;
  transition: opacity 0.3s ease;
  max-width: 80%;
  font-size: calc(1rem * var(--tw-text-scale, 1)); /* suit « Taille du texte » (HELO 75232) */
  font-weight: 500;
  z-index: 1;
}
.image-block--caption-hover .image-block__media:hover img {
  filter: blur(2px) brightness(0.7);
}
.image-block--caption-hover .image-block__media:hover figcaption,
.image-block--caption-hover .image-block__media:hover .image-block__caption {
  opacity: 1;
}

/* Click Action: Zoom - cursor indicates image can be enlarged */
.image-block--zoomable {
  cursor: zoom-in;
}
.image-block--zoomable img {
  cursor: zoom-in;
}
.image-block--zoomable:hover img {
  transform: scale(1.01);
  transition: transform 0.2s ease;
}

/* Click Action: Link - cursor indicates image is clickable */
.image-block--linked {
  cursor: pointer;
}
.image-block--linked img {
  cursor: pointer;
}
/* #413 : le lien publié couvre toute l'image (lien étiré, la mise en page ne bouge pas). */
.image-block__lien {
  position: absolute;
  inset: 0;
  z-index: 2;
  border-radius: inherit;
}
.image-block__lien:focus-visible {
  outline: 2px solid var(--tw-accent, currentColor);
  outline-offset: -4px;
}

/* ─────────────────────────────────────────────────────────────────────────────
   IMAGE OVERLAY (magazine-cover / hero-card)
   Optional title + subtitle rendered OVER the image (closes DNA-G's "cover" gap).
   Independent from the below-image caption. Only present when overlayTitle/
   overlaySubtitle is set → `.image-block--has-overlay` gates everything below.
   Scrim is a fixed neutral dark→transparent gradient (legibility, NOT a DNA
   colour); the text is always light because it sits on the darkened image.
   ───────────────────────────────────────────────────────────────────────────── */

/* The image wrapper becomes the positioning context for the overlay layer. */
.image-block--has-overlay .image-block__media {
  position: relative;
  overflow: hidden;
}

/* Absolute overlay layer = scrim + positioned text stack. */
.image-block__overlay {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  padding: clamp(1rem, 4%, 2rem);
  pointer-events: none; /* let clicks reach the image (zoom/link) */
  /* Legibility scrim — deliberately a FIXED neutral dark, NOT the `--tw-overlay`
     token: that token is a subtle caption-bar veil (most DNAs set it to the
     foreground at 16–30% opacity), which over a LIGHT image would leave the white
     overlay text unreadable. This scrim must guarantee white-text contrast on any
     image, so it stays a strong neutral black (dense enough for a bright photo). */
  background: linear-gradient(rgba(0, 0, 0, 0) 35%, rgba(0, 0, 0, 0.72));
}

.image-block__overlay-title {
  font-family: var(--tw-font-heading, inherit);
  font-size: var(--tw-size-h3, 1.625rem);
  font-weight: clamp(400, calc(var(--tw-weight-heading, 700) - var(--tw-weight-step, 0) * 2), 900);
  line-height: 1.15;
  color: #fff;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.55);
  margin: 0;
}

.image-block__overlay-subtitle {
  font-family: var(--tw-font-body, system-ui, sans-serif);
  font-size: var(--tw-size-lead, 1.25rem);
  line-height: 1.35;
  color: rgba(255, 255, 255, 0.92);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
  margin: 0;
}

/* Overlay positions — anchor the text stack inside the image. */
.image-block--overlay-top-left .image-block__overlay {
  justify-content: flex-start;
  align-items: flex-start;
  text-align: left;
  /* Reverse the scrim so the darker end is at the top, under the text. */
  background: linear-gradient(rgba(0, 0, 0, 0.72), rgba(0, 0, 0, 0) 65%);
}
.image-block--overlay-center .image-block__overlay {
  justify-content: center;
  align-items: center;
  text-align: center;
  /* Centre text reads best over an even darkening. */
  background: rgba(0, 0, 0, 0.5);
}
.image-block--overlay-bottom-left .image-block__overlay {
  justify-content: flex-end;
  align-items: flex-start;
  text-align: left;
}
.image-block--overlay-bottom-center .image-block__overlay {
  justify-content: flex-end;
  align-items: center;
  text-align: center;
}

/* Responsive: scale overlay text down on small screens. */
@media (max-width: 640px) {
  .image-block__overlay {
    padding: clamp(0.75rem, 4%, 1.25rem);
  }
  .image-block__overlay-title {
    font-size: var(--tw-size-h4, 1.3rem);
  }
  .image-block__overlay-subtitle {
    font-size: var(--tw-size-base, 1.0625rem);
  }
}

/* In the editor the overlay text fields are inline-editable → re-enable
   pointer events on the spans (the layer itself stays click-through). */
.image-block__overlay-title[contenteditable],
.image-block__overlay-subtitle[contenteditable] {
  pointer-events: auto;
  outline: none;
}
.image-block__overlay-title[contenteditable]:empty::before,
.image-block__overlay-subtitle[contenteditable]:empty::before {
  content: attr(data-placeholder);
  opacity: 0.6;
}

/* ─────────────────────────────────────────────────────────────────────────────
   CAPTION OVERLAY + SHAPE COMBINATIONS (Image Block)
   Apply border-radius to container so caption clips properly within shape
   ───────────────────────────────────────────────────────────────────────────── */

/* Overlay caption needs relative positioning on the figure */
.image-block--caption-overlay {
  position: relative;
  overflow: hidden;
}

/* Rounded shape + overlay caption: apply radius to .image-block__media (contains img + figcaption) */
.image-block--caption-overlay.image-block--shape-rounded .image-block__media {
  border-radius: 1rem;
}
.image-block--caption-overlay.image-block--shape-rounded img {
  border-radius: 0; /* Container handles the radius */
}
.image-block--caption-overlay.image-block--shape-rounded figcaption {
  border-radius: 0 0 1rem 1rem;
}

/* Circular shape + overlay caption: apply radius to .image-block__media */
.image-block--caption-overlay.image-block--shape-circular .image-block__media {
  border-radius: 50%;
}
.image-block--caption-overlay.image-block--shape-circular img {
  border-radius: 0; /* Container handles the radius */
}
.image-block--caption-overlay.image-block--shape-circular figcaption {
  /* Clip caption to bottom of circle */
  border-radius: 0 0 50% 50% / 0 0 100% 100%;
  padding-top: 2rem;
  padding-bottom: 0.75rem;
}

/* Hover caption + Shape combinations: apply shape to .image-block__media */
.image-block--caption-hover.image-block--shape-rounded .image-block__media {
  border-radius: 1rem;
}
.image-block--caption-hover.image-block--shape-rounded img {
  border-radius: 0;
}

.image-block--caption-hover.image-block--shape-circular .image-block__media {
  border-radius: 50%;
}
.image-block--caption-hover.image-block--shape-circular img {
  border-radius: 0;
}

/* ─────────────────────────────────────────────────────────────────────────────
   CIRCULAR SHAPE + FRAME + CAPTION COMBINATIONS
   When using frame-photo/photo-tilt with circular shape AND overlay/hover captions,
   the border/shadow must be on the container (.image-block__media), not the img,
   so the caption clips properly within the circle.
   ───────────────────────────────────────────────────────────────────────────── */

/* Circular + Photo frame + Overlay/Hover caption */
.image-block--shape-circular.image-block--frame-photo.image-block--caption-overlay .image-block__media,
.image-block--shape-circular.image-block--frame-photo.image-block--caption-hover .image-block__media {
  border-radius: 50%;
  border: 0.5em solid rgba(255, 255, 255, 0.9);
  box-shadow:
    0 10px 20px rgba(0, 0, 0, 0.19),
    0 6px 6px rgba(0, 0, 0, 0.23);
  overflow: hidden;
}
.image-block--shape-circular.image-block--frame-photo.image-block--caption-overlay img,
.image-block--shape-circular.image-block--frame-photo.image-block--caption-hover img {
  border: none;
  border-radius: 0;
  box-shadow: none;
}

/* Circular + Photo Tilt frame + Overlay/Hover caption */
.image-block--shape-circular.image-block--frame-photo-tilt.image-block--caption-overlay .image-block__media,
.image-block--shape-circular.image-block--frame-photo-tilt.image-block--caption-hover .image-block__media {
  border-radius: 50%;
  border: 0.5em solid rgba(255, 255, 255, 0.9);
  box-shadow:
    0 10px 20px rgba(0, 0, 0, 0.19),
    0 6px 6px rgba(0, 0, 0, 0.23);
  overflow: hidden;
}
.image-block--shape-circular.image-block--frame-photo-tilt.image-block--caption-overlay img,
.image-block--shape-circular.image-block--frame-photo-tilt.image-block--caption-hover img {
  border: none;
  border-radius: 0;
  box-shadow: none;
}

/* Circular + Simple Floating frame + Overlay/Hover caption */
.image-block--shape-circular.image-block--frame-simple-floating.image-block--caption-overlay .image-block__media,
.image-block--shape-circular.image-block--frame-simple-floating.image-block--caption-hover .image-block__media {
  border-radius: 50%;
  box-shadow:
    0 10px 20px rgba(0, 0, 0, 0.19),
    0 6px 6px rgba(0, 0, 0, 0.23);
  overflow: hidden;
}
.image-block--shape-circular.image-block--frame-simple-floating.image-block--caption-overlay img,
.image-block--shape-circular.image-block--frame-simple-floating.image-block--caption-hover img {
  border-radius: 0;
  box-shadow: none;
}

/* Selection state for image block */
.image-block.ProseMirror-selectednode {
  outline: 2px solid var(--tw-accent, #3b82f6);
  outline-offset: 4px;
  border-radius: 0.5rem;
}

/* ═══════════════════════════════════════════════════════════════════════════
   VIDEO BLOCK
   Per EDITOR-BLOCKS-CONTRACT.md §VIDEO
   Container is full width, video constrained and aligned within
   ═══════════════════════════════════════════════════════════════════════════ */

.video-block {
  display: flex;
  flex-direction: column;
  width: 100%;
  color: inherit; /* Ensure border color inheritance from section */
}

.video-block__wrapper {
  position: relative;
  width: 100%;
  border-radius: 4px;
  overflow: hidden;
  color: inherit; /* Ensure border color inheritance from section */
}

.video-block__wrapper iframe,
.video-block__wrapper video {
  /* width/height owned by the .tw-media brick (.tw-media > iframe,video) */
  border: none;
  display: block;
  background: #000; /* letterbox bg on the media element */
}
/* Self-hosted <video> LETTERBOXES (contain), it must NOT be cropped by the
   .tw-media brick default (object-fit:cover). 0,2,1 > the brick's .tw-media>video
   (0,1,1) → wins quel que soit l'ordre de chargement. (iframe embed = 16:9, inchangé.) */
.video-block__wrapper.tw-media > video {
  object-fit: contain;
}

/* Video sizes */
.video-block--small .video-block__wrapper { max-width: min(400px, 100%); }
.video-block--medium .video-block__wrapper { max-width: min(640px, 100%); }
.video-block--large .video-block__wrapper { max-width: min(960px, 100%); }
.video-block--full .video-block__wrapper { max-width: 100%; }

/* Video alignment */
.video-block--left { align-items: flex-start; }
.video-block--center { align-items: center; }
.video-block--right { align-items: flex-end; }

/* Aspect ratios */
.video-block--ratio-16-9 .video-block__wrapper { aspect-ratio: 16/9; }
.video-block--ratio-4-3 .video-block__wrapper { aspect-ratio: 4/3; }
.video-block--ratio-1-1 .video-block__wrapper { aspect-ratio: 1/1; }
.video-block--ratio-21-9 .video-block__wrapper { aspect-ratio: 21/9; }

/* Lecteur AUDIO (SoundCloud/Spotify/Bandcamp) : hauteur fixe, pas de ratio 16:9.
   Classe posée à la résolution (resolveMediaEmbed kind='audio'), override les
   règles ratio ci-dessus. */
.video-block--audio .video-block__wrapper {
  aspect-ratio: auto;
  height: 166px;
}
/* #453 — un FICHIER son (podcast) : le lecteur natif `<audio>` porte sa propre
   hauteur, ni cadre 16:9 ni hauteur d'iframe. 0,3,0 > les règles de ratio (0,2,0). */
.video-block.video-block--audio .video-block__wrapper--audio {
  aspect-ratio: auto;
  height: auto;
  overflow: visible;
}
.video-block__wrapper--audio > audio {
  display: block;
  width: 100%;
}

/* Caption */
.video-block figcaption {
  margin-top: 0.5rem;
  text-align: center;
  font-size: 0.875rem;
  color: var(--tw-text-muted, #64748b);
  font-style: italic;
}

/* Selection state for video block (éditeur) */
.video-block.ProseMirror-selectednode {
  outline: 2px solid var(--tw-accent, #3b82f6);
  outline-offset: 4px;
  border-radius: 0.5rem;
}

/* ─────────────────────────────────────────────────────────────────────────────
   VIDEO SHAPE STYLES (M-SIDEBAR.16)
   Border-radius only - separate from frame effects to allow combinations
   ───────────────────────────────────────────────────────────────────────────── */

/* Shape: Rounded - rounded corners */
.video-block--shape-rounded .video-block__wrapper,
.video-block--shape-rounded .video-block__wrapper iframe,
.video-block--shape-rounded .video-block__wrapper video {
  border-radius: 1rem;
}

/* Shape: Circular - circle crop (rare for videos but supported) */
.video-block--shape-circular .video-block__wrapper,
.video-block--shape-circular .video-block__wrapper iframe,
.video-block--shape-circular .video-block__wrapper video {
  border-radius: 50%;
}

/* ─────────────────────────────────────────────────────────────────────────────
   VIDEO FRAME STYLES
   Frame styles for videos (native video + iframe embeds)
   ───────────────────────────────────────────────────────────────────────────── */

/* Frame: Simple - padding around video */
.video-block--frame-simple .video-block__wrapper {
  padding: 0.75rem;
  background: var(--tw-surface, rgba(128, 128, 128, 0.1));
}

/* Frame: Rounded - rounded corners on the wrapper and children */
.video-block--frame-rounded .video-block__wrapper {
  border-radius: 1rem;
  overflow: hidden;
}
.video-block--frame-rounded .video-block__wrapper iframe,
.video-block--frame-rounded .video-block__wrapper video {
  border-radius: 1rem;
}

/* Frame: Border - simple thin border using section's text color */
.video-block--frame-border .video-block__wrapper {
  border: 1px solid var(--tw-border, color-mix(in srgb, currentColor 25%, transparent));
  border-radius: 4px;
  overflow: hidden;
}

/* Frame: Simple Floating - subtle shadow */
.video-block--frame-simple-floating .video-block__wrapper {
  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
  border-radius: 4px;
  overflow: hidden;
}

/* Frame: Photo - white border like a printed photo */
.video-block--frame-photo .video-block__wrapper {
  padding: 0.5rem;
  background: white;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  border-radius: 2px;
}

/* Frame: Photo Tilt - photo frame with slight rotation */
.video-block--frame-photo-tilt .video-block__wrapper {
  padding: 0.5rem;
  background: white;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  border-radius: 2px;
}
.video-block--frame-photo-tilt.video-block--left .video-block__wrapper,
.video-block--frame-photo-tilt.video-block--center .video-block__wrapper {
  transform: rotate(-2deg);
}
.video-block--frame-photo-tilt.video-block--right .video-block__wrapper {
  transform: rotate(2deg);
}

/* Frame: Perspective - 3D tilt effect */
.video-block--frame-perspective {
  perspective: 1000px;
}
.video-block--frame-perspective.video-block--center .video-block__wrapper {
  transform: rotateY(0deg);
}
.video-block--frame-perspective.video-block--left .video-block__wrapper {
  transform: rotateY(8deg);
}
.video-block--frame-perspective.video-block--right .video-block__wrapper {
  transform: rotateY(-8deg);
}

/* Frame: Polaroid - thick bottom border like an instant photo */
.video-block--frame-polaroid .video-block__wrapper {
  padding: 0.75rem 0.75rem 2.5rem 0.75rem;
  background: white;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  border-radius: 2px;
}

/* Frame: Inset - deep "hole" effect with overlay pseudo-element */
.video-block--frame-inset .video-block__wrapper {
  position: relative;
}
.video-block--frame-inset .video-block__wrapper::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  pointer-events: none;
  border-radius: inherit;
  z-index: 1; /* Above iframe/video */
  /* Strong multi-layer inner shadow for deep "hole" effect */
  box-shadow:
    inset 0 8px 20px rgba(0, 0, 0, 0.5),
    inset 0 -4px 10px rgba(0, 0, 0, 0.2),
    inset 8px 0 15px rgba(0, 0, 0, 0.3),
    inset -8px 0 15px rgba(0, 0, 0, 0.3),
    inset 0 2px 4px rgba(0, 0, 0, 0.4);
  border: 2px solid rgba(0, 0, 0, 0.4);
}

/* ═══════════════════════════════════════════════════════════════════════════
   GALLERY BLOCK
   Per EDITOR-BLOCKS-CONTRACT.md §GALLERY
   Responsive multi-image gallery with grid, masonry, and carousel variants
   ═══════════════════════════════════════════════════════════════════════════ */

.gallery-block {
  display: grid;
  grid-template-columns: 1fr;
  width: 100%;
  /* Default gap - overridden by collection--gap-h-* and collection--gap-v-* classes */
  column-gap: var(--collection-gap-h-medium, 1.5rem);
  row-gap: var(--collection-gap-v-medium, 1.5rem);
}

/* ─────────────────────────────────────────────────────────────────────────────
   GALLERY GAP SUPPORT
   Uses unified collection gap classes from ATOM-BLOCK-CONTRACT.md §6.7
   ───────────────────────────────────────────────────────────────────────────── */
.gallery-block.collection--gap-h-none { column-gap: 0; }
.gallery-block.collection--gap-h-small { column-gap: var(--collection-gap-h-small, 0.75rem); }
.gallery-block.collection--gap-h-medium { column-gap: var(--collection-gap-h-medium, 1.5rem); }
.gallery-block.collection--gap-h-large { column-gap: var(--collection-gap-h-large, 2.5rem); }

.gallery-block.collection--gap-v-none { row-gap: 0; }
.gallery-block.collection--gap-v-small { row-gap: var(--collection-gap-v-small, 0.75rem); }
.gallery-block.collection--gap-v-medium { row-gap: var(--collection-gap-v-medium, 1.5rem); }
.gallery-block.collection--gap-v-large { row-gap: var(--collection-gap-v-large, 2.5rem); }

/* Responsive columns using data attributes */
/* Tablet columns (768-1023px) */
@media (min-width: 768px) {
  .gallery-block[data-cols-tablet="2"] { grid-template-columns: repeat(2, 1fr); }
  .gallery-block[data-cols-tablet="3"] { grid-template-columns: repeat(3, 1fr); }
  .gallery-block[data-cols-tablet="4"] { grid-template-columns: repeat(4, 1fr); }
}

/* Desktop columns (≥1024px) */
@media (min-width: 1024px) {
  .gallery-block[data-cols-desktop="2"] { grid-template-columns: repeat(2, 1fr); }
  .gallery-block[data-cols-desktop="3"] { grid-template-columns: repeat(3, 1fr); }
  .gallery-block[data-cols-desktop="4"] { grid-template-columns: repeat(4, 1fr); }
  .gallery-block[data-cols-desktop="5"] { grid-template-columns: repeat(5, 1fr); }
  .gallery-block[data-cols-desktop="6"] { grid-template-columns: repeat(6, 1fr); }
}

/* Mobile columns (<768px) */
@media (max-width: 767px) {
  .gallery-block[data-cols-mobile="1"] { grid-template-columns: 1fr; }
  .gallery-block[data-cols-mobile="2"] { grid-template-columns: repeat(2, 1fr); }
}

/* Gap variants - Legacy classes for backward compatibility
   New galleries use unified collection gap classes (collection--gap-h-*, collection--gap-v-*)
   See ATOM-BLOCK-CONTRACT.md §6.7 for unified gap system */
.gallery-block--gap-none { gap: 0; }
.gallery-block--gap-small { gap: 0.25rem; }
.gallery-block--gap-medium { gap: 0.5rem; }
.gallery-block--gap-large { gap: 1rem; }

/* Gallery item */
.gallery-block__item {
  position: relative;
  overflow: hidden;
  margin: 0; /* reset <figure> UA default margin (1em 40px) */
}

.gallery-block__item img {
  display: block;
  width: 100%;
  height: auto;
  object-fit: cover;
}

/* ⚠️ LA LÉGENDE « DESSOUS » ÉTAIT ROGNÉE. Le <figure> d'une galerie est
   `.tw-media` ET contient sa légende. Or `.tw-media > img{height:100%}` (la
   brique), réaffirmé en (0,2,1) par `astro-preview.css` à la publication, fait
   remplir le figure par l'image. L'`overflow:hidden` coupait alors la légende,
   présente dans le HTML mais invisible (vu sur la galerie de lauyan.com,
   2026-09-25). Seulement quand il y a une légende : sans elle, rien ne change. */
.gallery-block__item.tw-media:has(> .gallery-block__caption) > img { height: auto; }

/* Rounded corners */
.gallery-block--rounded .gallery-block__item img {
  border-radius: 8px;
}

/* Aspect ratio variants - applied to IMAGE not container to preserve caption space */
.gallery-block--ratio-1-1 .gallery-block__item img { aspect-ratio: 1/1; }
.gallery-block--ratio-4-3 .gallery-block__item img { aspect-ratio: 4/3; }
.gallery-block--ratio-16-9 .gallery-block__item img { aspect-ratio: 16/9; }
.gallery-block--ratio-3-2 .gallery-block__item img { aspect-ratio: 3/2; }

.gallery-block--ratio-1-1 .gallery-block__item img,
.gallery-block--ratio-4-3 .gallery-block__item img,
.gallery-block--ratio-16-9 .gallery-block__item img,
.gallery-block--ratio-3-2 .gallery-block__item img {
  width: 100%;
  height: auto;
  object-fit: cover;
}

/* Carrousel à UNE image par vue, au format libre : une image plus PETITE que la
   vue garde sa taille réelle, centrée, au lieu d'être étirée et floue — le
   diaporama de logos de partenaires (200 px) d'un site client V14 importé
   s'étalait sur toute la largeur de la page (24/09). Une grande image, elle,
   remplit toujours la vue (`max-width: 100%`). Un format imposé (ratio) garde
   son recadrage. */
.gallery-block--carousel[data-cols-desktop="1"]:not([class*="gallery-block--ratio-"]) .gallery-block__item img {
  width: auto;
  max-width: 100%;
  margin-inline: auto;
}

/* Legacy aspect ratio classes for compatibility */
.gallery-block--aspect-square .gallery-block__item img { aspect-ratio: 1/1; }
.gallery-block--aspect-video .gallery-block__item img { aspect-ratio: 16/9; }
.gallery-block--aspect-portrait .gallery-block__item img { aspect-ratio: 3/4; }
.gallery-block--aspect-auto .gallery-block__item img { aspect-ratio: auto; }

/* ─────────────────────────────────────────────────────────────────────────────
   GALLERY CAPTION STYLES (M-SIDEBAR.17)
   Mirrors image-block caption styles - keep in sync!
   ───────────────────────────────────────────────────────────────────────────── */

/* Caption Style: Below (default) - caption below the image */
/* Taille : × « Taille du texte » (Thème › Formes et rythme, `--tw-text-scale`,
   posé en ligne sur le canevas de l'éditeur et dans theme.css du site). Une taille
   fixe en rem laissait les légendes de galerie sourdes au réglage (HELO 75232) ;
   sans réglage, le facteur vaut 1 : rendu inchangé au pixel près.
   Garde : legende-taille-du-texte.test.ts. */
.gallery-block__caption {
  display: block;
  padding: 0.5rem;
  font-size: calc(0.75rem * var(--tw-text-scale, 1));
  color: var(--tw-text);
  text-align: center;
  line-height: 1.4;
}

/* Caption Style: None - hide captions */
.gallery-block--caption-none .gallery-block__caption {
  display: none;
}

/* Caption Style: Overlay - caption overlaid at bottom with gradient */
.gallery-block--caption-overlay .gallery-block__item {
  position: relative;
  overflow: hidden;
}
.gallery-block--caption-overlay .gallery-block__caption {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  padding: 1.5rem 0.75rem 0.5rem;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.7) 0%, rgba(0, 0, 0, 0.4) 60%, transparent 100%);
  color: white;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}

/* Overlay caption + Shape combinations: apply shape to container so caption clips to it */
.gallery-block--caption-overlay.gallery-block--shape-rounded .gallery-block__item {
  border-radius: 1rem;
}
.gallery-block--caption-overlay.gallery-block--shape-rounded .gallery-block__item img {
  border-radius: 0; /* Container handles the radius */
}
.gallery-block--caption-overlay.gallery-block--shape-rounded .gallery-block__caption {
  border-radius: 0 0 1rem 1rem;
}

.gallery-block--caption-overlay.gallery-block--shape-circular .gallery-block__item {
  border-radius: 50%;
}
.gallery-block--caption-overlay.gallery-block--shape-circular .gallery-block__item img {
  border-radius: 0; /* Container handles the radius */
}
.gallery-block--caption-overlay.gallery-block--shape-circular .gallery-block__caption {
  /* Clip caption to bottom of circle */
  border-radius: 0 0 50% 50% / 0 0 100% 100%;
  padding-top: 2rem;
  padding-bottom: 0.75rem;
}

/* Caption Style: Hover - caption appears on hover with blur effect */
.gallery-block--caption-hover .gallery-block__item {
  position: relative;
  overflow: hidden;
}
.gallery-block--caption-hover .gallery-block__item img {
  transition: filter 0.3s ease, transform 0.3s ease;
}
.gallery-block--caption-hover .gallery-block__caption {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  padding: 0.75rem 1rem;
  background: rgba(0, 0, 0, 0.8);
  color: white;
  border-radius: 0.25rem;
  text-align: center;
  opacity: 0;
  transition: opacity 0.3s ease;
  max-width: 90%;
  font-size: calc(0.8rem * var(--tw-text-scale, 1)); /* suit « Taille du texte » (HELO 75232) */
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Hover caption + Shape combinations: apply shape to container */
.gallery-block--caption-hover.gallery-block--shape-rounded .gallery-block__item {
  border-radius: 1rem;
}
.gallery-block--caption-hover.gallery-block--shape-rounded .gallery-block__item img {
  border-radius: 0;
}

.gallery-block--caption-hover.gallery-block--shape-circular .gallery-block__item {
  border-radius: 50%;
}
.gallery-block--caption-hover.gallery-block--shape-circular .gallery-block__item img {
  border-radius: 0;
}
.gallery-block--caption-hover .gallery-block__item:hover img {
  filter: blur(2px) brightness(0.6);
}
.gallery-block--caption-hover .gallery-block__item:hover .gallery-block__caption {
  opacity: 1;
}

/* Lightbox hover effect */
.gallery-block--lightbox .gallery-block__item:hover img {
  transform: scale(1.02);
  cursor: zoom-in;
}

/* ─────────────────────────────────────────────────────────────────────────────
   MASONRY VARIANT
   True CSS columns-based masonry. Shared between editor (Pages) and preview.
   See masonry rules below (Phase 4A section) for the actual layout.
   ───────────────────────────────────────────────────────────────────────────── */

/* ─────────────────────────────────────────────────────────────────────────────
   CAROUSEL VARIANT
   Horizontal filmstrip - Astro renders with navigation and swipe
   ───────────────────────────────────────────────────────────────────────────── */

/* Gallery carousel = horizontal scroll strip.
   Number of visible items controlled by data-cols-* attributes.
   1 column = full-width hero banner, N columns = multi-item strip. */
.gallery-block--carousel {
  --carousel-cols: 3; /* default, overridden by data-cols-* */
  --carousel-gap: var(--collection-gap-h-medium, 1.5rem);
  display: flex;
  flex-wrap: nowrap;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  gap: var(--carousel-gap);
  row-gap: 0;
}

.gallery-block--carousel::-webkit-scrollbar {
  display: none;
}

/* Carousel horizontal gap from collection classes */
.gallery-block--carousel.collection--gap-h-none { --carousel-gap: 0px; gap: 0; }
.gallery-block--carousel.collection--gap-h-small { --carousel-gap: var(--collection-gap-h-small, 0.75rem); gap: var(--carousel-gap); }
.gallery-block--carousel.collection--gap-h-medium { --carousel-gap: var(--collection-gap-h-medium, 1.5rem); gap: var(--carousel-gap); }
.gallery-block--carousel.collection--gap-h-large { --carousel-gap: var(--collection-gap-h-large, 2.5rem); gap: var(--carousel-gap); }

/* Item sizing: each item = 100%/cols minus gap distribution */
.gallery-block--carousel .gallery-block__item {
  flex: 0 0 calc((100% - (var(--carousel-cols) - 1) * var(--carousel-gap)) / var(--carousel-cols));
  scroll-snap-align: start;
}

/* 1 column = full-width slides, center-aligned snap */
.gallery-block--carousel[data-cols-desktop="1"] .gallery-block__item {
  scroll-snap-align: center;
}

/* Desktop columns (≥1024px) */
@media (min-width: 1024px) {
  .gallery-block--carousel[data-cols-desktop="1"] { --carousel-cols: 1; }
  .gallery-block--carousel[data-cols-desktop="2"] { --carousel-cols: 2; }
  .gallery-block--carousel[data-cols-desktop="3"] { --carousel-cols: 3; }
  .gallery-block--carousel[data-cols-desktop="4"] { --carousel-cols: 4; }
  .gallery-block--carousel[data-cols-desktop="5"] { --carousel-cols: 5; }
  .gallery-block--carousel[data-cols-desktop="6"] { --carousel-cols: 6; }
}

/* Tablet columns (768-1023px) */
@media (min-width: 768px) and (max-width: 1023px) {
  .gallery-block--carousel[data-cols-tablet="1"] { --carousel-cols: 1; }
  .gallery-block--carousel[data-cols-tablet="2"] { --carousel-cols: 2; }
  .gallery-block--carousel[data-cols-tablet="3"] { --carousel-cols: 3; }
  .gallery-block--carousel[data-cols-tablet="4"] { --carousel-cols: 4; }
}

/* Mobile columns (<768px) */
@media (max-width: 767px) {
  .gallery-block--carousel[data-cols-mobile="1"] { --carousel-cols: 1; }
  .gallery-block--carousel[data-cols-mobile="2"] { --carousel-cols: 2; }
}

.gallery-block--carousel .gallery-block__item img {
  width: 100%;
  height: auto;
  object-fit: cover;
  border-radius: inherit;
}
/* ⚠️ « AUTO » VEUT DIRE LE FORMAT DE L'IMAGE (#322). Il imposait du 4:3 recadré :
   les bannières larges du carrousel de lauyan.com perdaient ~20 % de chaque
   côté, alors qu'en « masonry » elles s'affichaient entières. Un format choisi
   (1:1, 4:3, 16:9, 3:2) recadre toujours, et c'est lui qu'on prend pour des
   vignettes toutes de même hauteur. */
.gallery-block--carousel:not([class*="gallery-block--ratio-"]) .gallery-block__item img {
  aspect-ratio: auto;
}

/* Carousel aspect ratio support */
.gallery-block--carousel.gallery-block--ratio-1-1 .gallery-block__item img { aspect-ratio: 1 / 1; }
.gallery-block--carousel.gallery-block--ratio-4-3 .gallery-block__item img { aspect-ratio: 4 / 3; }
.gallery-block--carousel.gallery-block--ratio-16-9 .gallery-block__item img { aspect-ratio: 16 / 9; }
.gallery-block--carousel.gallery-block--ratio-3-2 .gallery-block__item img { aspect-ratio: 3 / 2; }

/* Gallery carousel navigation wrapper (arrows outside scrollable area) */
.gallery-carousel-nav {
  position: relative;
}

.gallery-carousel-nav > .tw-carousel__prev,
.gallery-carousel-nav > .tw-carousel__next {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  border: none;
  background: rgba(255, 255, 255, 0.85);
  color: #1e293b;
  font-size: 1.25rem;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
  transition: opacity 0.15s, background 0.15s;
  opacity: 0.7;
}

.gallery-carousel-nav > .tw-carousel__prev:hover,
.gallery-carousel-nav > .tw-carousel__next:hover {
  opacity: 1;
  background: #fff;
}

.gallery-carousel-nav > .tw-carousel__prev { left: 0.5rem; }
.gallery-carousel-nav > .tw-carousel__next { right: 0.5rem; }

.gallery-carousel-nav > .tw-carousel__prev[disabled],
.gallery-carousel-nav > .tw-carousel__next[disabled] {
  opacity: 0;
  pointer-events: none;
}

/* Hide gallery carousel arrows in editor */
.ProseMirror .gallery-carousel-nav > .tw-carousel__prev,
.ProseMirror .gallery-carousel-nav > .tw-carousel__next {
  display: none;
}

/* ─────────────────────────────────────────────────────────────────────────────
   GALLERY SHAPE STYLES (M-SIDEBAR.16)
   Border-radius only - separate from frame effects to allow combinations
   ───────────────────────────────────────────────────────────────────────────── */

/* Shape: Rounded - rounded corners */
.gallery-block--shape-rounded .gallery-block__item img {
  border-radius: 1rem;
}

/* Shape: Circular - circle crop (best with 1:1 ratio) */
.gallery-block--shape-circular .gallery-block__item img {
  border-radius: 50%;
}

/* ─────────────────────────────────────────────────────────────────────────────
   GALLERY FRAME STYLES (M-SIDEBAR.17)
   Applied to gallery items, same styles as image-block but on __item level
   ───────────────────────────────────────────────────────────────────────────── */

/* Simple: just padding */
.gallery-block--frame-simple .gallery-block__item {
  padding: 0.5rem;
}

/* Rounded: rounded corners */
.gallery-block--frame-rounded .gallery-block__item img {
  border-radius: 0.75rem;
}

/* Simple Floating: shadow effect */
.gallery-block--frame-simple-floating .gallery-block__item img {
  box-shadow:
    0 10px 20px rgba(0, 0, 0, 0.19),
    0 6px 6px rgba(0, 0, 0, 0.23);
}

/* Photo: white border + shadow */
.gallery-block--frame-photo .gallery-block__item img {
  border: 0.35em solid rgba(255, 255, 255, 0.9);
  box-shadow:
    0 10px 20px rgba(0, 0, 0, 0.19),
    0 6px 6px rgba(0, 0, 0, 0.23);
}

/* Photo Tilt: white border + shadow + slight rotation */
.gallery-block--frame-photo-tilt .gallery-block__item {
  transform: rotate(-2deg);
}
.gallery-block--frame-photo-tilt .gallery-block__item:nth-child(even) {
  transform: rotate(2deg);
}
.gallery-block--frame-photo-tilt .gallery-block__item img {
  border: 0.35em solid rgba(255, 255, 255, 0.9);
  box-shadow:
    0 10px 20px rgba(0, 0, 0, 0.19),
    0 6px 6px rgba(0, 0, 0, 0.23);
}

/* Perspective: 3D tilt effect */
.gallery-block--frame-perspective .gallery-block__item {
  transform: perspective(800px) rotateY(-5deg);
}
.gallery-block--frame-perspective .gallery-block__item:nth-child(even) {
  transform: perspective(800px) rotateY(5deg);
}
.gallery-block--frame-perspective .gallery-block__item img {
  box-shadow:
    0 10px 30px rgba(0, 0, 0, 0.25);
}

/* Polaroid: white border with extra bottom padding */
.gallery-block--frame-polaroid .gallery-block__item {
  background: rgba(255, 255, 255, 0.95);
  padding: 0.5rem 0.5rem 1.5rem 0.5rem;
  box-shadow:
    0 10px 20px rgba(0, 0, 0, 0.19),
    0 6px 6px rgba(0, 0, 0, 0.23);
}

/* Border: simple thin border using section's text color */
.gallery-block--frame-border .gallery-block__item img {
  border: 1px solid color-mix(in srgb, var(--tw-text, currentColor) 25%, transparent);
}

/* Inset: deep "hole" effect with strong inner shadows */
/* Inset: deep "hole" effect with overlay pseudo-element */
.gallery-block--frame-inset .gallery-block__item {
  position: relative;
}
.gallery-block--frame-inset .gallery-block__item::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  pointer-events: none;
  border-radius: inherit;
  /* Strong multi-layer inner shadow for deep "hole" effect */
  box-shadow:
    inset 0 8px 20px rgba(0, 0, 0, 0.5),       /* Top shadow - strongest */
    inset 0 -4px 10px rgba(0, 0, 0, 0.2),      /* Bottom subtle lift */
    inset 8px 0 15px rgba(0, 0, 0, 0.3),       /* Left shadow */
    inset -8px 0 15px rgba(0, 0, 0, 0.3),      /* Right shadow */
    inset 0 2px 4px rgba(0, 0, 0, 0.4);        /* Edge definition */
  /* Dark border to define the "hole" edge */
  border: 2px solid rgba(0, 0, 0, 0.4);
}
/* Inset + Shape combinations for gallery */
.gallery-block--shape-rounded.gallery-block--frame-inset .gallery-block__item::after {
  border-radius: 0.5rem;
}
.gallery-block--shape-circular.gallery-block--frame-inset .gallery-block__item::after {
  border-radius: 50%;
}

/* Circular: circular crop (works best with square images) */
.gallery-block--frame-circular .gallery-block__item {
  padding: 0.5rem;
}
.gallery-block--frame-circular .gallery-block__item img {
  border-radius: 50%;
  aspect-ratio: 1/1;
  object-fit: cover;
}

/* Vintage: sepia tone with contrast (color filter) */
.gallery-block--frame-vintage .gallery-block__item img {
  filter: sepia(0.3) contrast(1.1) saturate(0.9);
}

/* ─────────────────────────────────────────────────────────────────────────────
   GALLERY SHADOW OVERFLOW FIX
   Frames with shadows need space to render without clipping
   ───────────────────────────────────────────────────────────────────────────── */

/* Add padding to gallery container for shadow frames */
.gallery-block--frame-simple-floating,
.gallery-block--frame-photo,
.gallery-block--frame-photo-tilt,
.gallery-block--frame-perspective,
.gallery-block--frame-polaroid {
  padding: 1.5rem;
}

/* Allow overflow visible on items for shadow frames (only when no shape applied) */
.gallery-block--frame-simple-floating:not(.gallery-block--shape-rounded):not(.gallery-block--shape-circular) .gallery-block__item,
.gallery-block--frame-photo:not(.gallery-block--shape-rounded):not(.gallery-block--shape-circular) .gallery-block__item,
.gallery-block--frame-photo-tilt:not(.gallery-block--shape-rounded):not(.gallery-block--shape-circular) .gallery-block__item,
.gallery-block--frame-perspective:not(.gallery-block--shape-rounded):not(.gallery-block--shape-circular) .gallery-block__item,
.gallery-block--frame-polaroid:not(.gallery-block--shape-rounded):not(.gallery-block--shape-circular) .gallery-block__item {
  overflow: visible;
}

/* ─────────────────────────────────────────────────────────────────────────────
   GALLERY SHAPE + CONTAINER FRAME COMBINATIONS
   When shapes are combined with container-based frames (polaroid),
   apply shape to container and clip image to fit
   ───────────────────────────────────────────────────────────────────────────── */

/* Rounded + Polaroid: subtle rounded frame (matching /image style) */
.gallery-block--shape-rounded.gallery-block--frame-polaroid .gallery-block__item {
  border-radius: 0.5rem 0.5rem 0.375rem 0.375rem;
}
.gallery-block--shape-rounded.gallery-block--frame-polaroid .gallery-block__item img {
  /* Top corners slightly rounded, bottom straight (polaroid style) */
  border-radius: 0.25rem 0.25rem 0 0;
}

/* Circular + Polaroid: polaroid frame with circular image inside */
.gallery-block--shape-circular.gallery-block--frame-polaroid .gallery-block__item {
  /* Keep polaroid asymmetric padding (more at bottom) */
  border-radius: 50% 50% 45% 45% / 45% 45% 40% 40%;
}
.gallery-block--shape-circular.gallery-block--frame-polaroid .gallery-block__item img {
  border-radius: 50%;
  aspect-ratio: 1/1;
}

/* ─────────────────────────────────────────────────────────────────────────────
   SHAPE + SHADOW FRAME COMBINATIONS
   Move shadow to container so it's not clipped by overflow:hidden
   ───────────────────────────────────────────────────────────────────────────── */

/* Rounded + Photo: border and shadow on container, image clips to shape */
.gallery-block--shape-rounded.gallery-block--frame-photo .gallery-block__item {
  border-radius: 1rem;
  border: 0.35em solid rgba(255, 255, 255, 0.9);
  box-shadow:
    0 10px 20px rgba(0, 0, 0, 0.19),
    0 6px 6px rgba(0, 0, 0, 0.23);
  overflow: hidden;
}
.gallery-block--shape-rounded.gallery-block--frame-photo .gallery-block__item img {
  border: none;
  border-radius: 0;
  box-shadow: none;
}

/* Circular + Photo: border and shadow on container, image clips to shape */
.gallery-block--shape-circular.gallery-block--frame-photo .gallery-block__item {
  border-radius: 50%;
  border: 0.35em solid rgba(255, 255, 255, 0.9);
  box-shadow:
    0 10px 20px rgba(0, 0, 0, 0.19),
    0 6px 6px rgba(0, 0, 0, 0.23);
  overflow: hidden;
}
.gallery-block--shape-circular.gallery-block--frame-photo .gallery-block__item img {
  border: none;
  border-radius: 0;
  box-shadow: none;
}

/* Rounded + Photo Tilt: border and shadow on container */
.gallery-block--shape-rounded.gallery-block--frame-photo-tilt .gallery-block__item {
  border-radius: 1rem;
  border: 0.35em solid rgba(255, 255, 255, 0.9);
  box-shadow:
    0 10px 20px rgba(0, 0, 0, 0.19),
    0 6px 6px rgba(0, 0, 0, 0.23);
  overflow: hidden;
}
.gallery-block--shape-rounded.gallery-block--frame-photo-tilt .gallery-block__item img {
  border: none;
  border-radius: 0;
  box-shadow: none;
}

/* Circular + Photo Tilt: border and shadow on container */
.gallery-block--shape-circular.gallery-block--frame-photo-tilt .gallery-block__item {
  border-radius: 50%;
  border: 0.35em solid rgba(255, 255, 255, 0.9);
  box-shadow:
    0 10px 20px rgba(0, 0, 0, 0.19),
    0 6px 6px rgba(0, 0, 0, 0.23);
  overflow: hidden;
}
.gallery-block--shape-circular.gallery-block--frame-photo-tilt .gallery-block__item img {
  border: none;
  border-radius: 0;
  box-shadow: none;
}

/* Rounded + Simple Floating: shadow on container */
.gallery-block--shape-rounded.gallery-block--frame-simple-floating .gallery-block__item {
  border-radius: 1rem;
  box-shadow:
    0 10px 20px rgba(0, 0, 0, 0.19),
    0 6px 6px rgba(0, 0, 0, 0.23);
  overflow: hidden;
}
.gallery-block--shape-rounded.gallery-block--frame-simple-floating .gallery-block__item img {
  border-radius: 0;
  box-shadow: none;
}

/* Circular + Simple Floating: shadow on container */
.gallery-block--shape-circular.gallery-block--frame-simple-floating .gallery-block__item {
  border-radius: 50%;
  box-shadow:
    0 10px 20px rgba(0, 0, 0, 0.19),
    0 6px 6px rgba(0, 0, 0, 0.23);
  overflow: hidden;
}
.gallery-block--shape-circular.gallery-block--frame-simple-floating .gallery-block__item img {
  border-radius: 0;
  box-shadow: none;
}

/* Rounded + Perspective: shadow on container */
.gallery-block--shape-rounded.gallery-block--frame-perspective .gallery-block__item {
  border-radius: 1rem;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
  overflow: hidden;
}
.gallery-block--shape-rounded.gallery-block--frame-perspective .gallery-block__item img {
  border-radius: 0;
  box-shadow: none;
}

/* Circular + Perspective: shadow on container */
.gallery-block--shape-circular.gallery-block--frame-perspective .gallery-block__item {
  border-radius: 50%;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
  overflow: hidden;
}
.gallery-block--shape-circular.gallery-block--frame-perspective .gallery-block__item img {
  border-radius: 0;
  box-shadow: none;
}

/* Selection state for gallery block */
.gallery-block.ProseMirror-selectednode {
  outline: 2px solid var(--tw-accent, #3b82f6);
  outline-offset: 4px;
  border-radius: 0.5rem;
}

/* ═══════════════════════════════════════════════════════════════════════════
   TABLE BLOCK
   Per EDITOR-BLOCKS-CONTRACT.md §TABLE
   ═══════════════════════════════════════════════════════════════════════════ */

.table-block,
table {
  width: 100%;
  border-collapse: collapse;
  margin: 1rem 0;
}

.table-block th,
.table-block td,
th, td {
  padding: 0.75rem 1rem;
  text-align: start;
  border: 1px solid var(--tw-border);
}

.table-block th,
th {
  background: var(--tw-surface);
  color: var(--tw-text-heading);
  font-weight: 600;
}

.table-block td,
td {
  color: var(--tw-text);
}

/* Striped rows */
.table-block--striped tbody tr:nth-child(even),
table tbody tr:nth-child(even) {
  background: var(--tw-surface);
}

/* ───────────────────────────────────────────────────────────────────────────
   TOWeb Table - Cell Padding Variants
   Classes are added to tableWrapper via decorations, so target table inside
   ─────────────────────────────────────────────────────────────────────────── */

/* ── Tableaux sur petit écran (#319) ────────────────────────────────────────
   Par défaut, le tableau DÉFILE horizontalement dans sa largeur : un tableau à
   4 colonnes n'en montrait que 2 sur téléphone, sans moyen de voir la suite.
   C'est le standard (Gutenberg, Squarespace, Notion), jamais faux.
   Avec « une fiche par ligne » (l'option « Responsive » de V14), chaque ligne
   devient un bloc et chaque valeur est précédée du nom de sa colonne
   (`data-label`, posé par le générateur depuis la ligne d'en-tête). */
.toweb-table {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  max-width: 100%;
}
@media (max-width: 640px) {
  .toweb-table--mobile-stack table,
  .toweb-table--mobile-stack tbody,
  .toweb-table--mobile-stack tr,
  .toweb-table--mobile-stack td,
  .toweb-table--mobile-stack th {
    display: block;
    width: 100%;
  }
  /* La ligne d'en-tête a donné ses noms : elle n'a plus à s'afficher. */
  .toweb-table--mobile-stack tr:first-child { display: none; }
  .toweb-table--mobile-stack tr {
    border: 1px solid var(--tw-rule, #e2e8f0);
    border-radius: var(--tw-radius-card, 0.5rem);
    margin-bottom: 0.75rem;
    overflow: hidden;
  }
  .toweb-table--mobile-stack td[data-label] {
    display: flex;
    justify-content: space-between;
    gap: 1rem;
    text-align: end;
  }
  .toweb-table--mobile-stack td[data-label]::before {
    content: attr(data-label);
    font-weight: 600;
    text-align: start;
  }
  /* Une première colonne sans nom (le nom de la ligne) devient le titre de la fiche. */
  .toweb-table--mobile-stack td:first-child:not([data-label]) {
    font-weight: 600;
    background: var(--tw-surface-2, rgba(0, 0, 0, 0.03));
  }
}

/* Compact padding */
.toweb-table--padding-compact th,
.toweb-table--padding-compact td,
.toweb-table--padding-compact table th,
.toweb-table--padding-compact table td {
  padding: 0.375rem 0.5rem !important;
}

/* Normal padding (default) */
.toweb-table--padding-normal th,
.toweb-table--padding-normal td,
.toweb-table--padding-normal table th,
.toweb-table--padding-normal table td {
  padding: 0.75rem 1rem !important;
}

/* Spacious padding */
.toweb-table--padding-spacious th,
.toweb-table--padding-spacious td,
.toweb-table--padding-spacious table th,
.toweb-table--padding-spacious table td {
  padding: 1rem 1.25rem !important;
}

/* ───────────────────────────────────────────────────────────────────────────
   TOWeb Table - Header Styling
   Classes are added to tableWrapper via decorations, so target table inside
   IMPORTANT: Use high specificity to override TipTap's default .ProseMirror table th
   ─────────────────────────────────────────────────────────────────────────── */

/* When hasHeader is TRUE, first row has header styling (emphasized) */
.ProseMirror .toweb-table--has-header table th,
.toweb-table--has-header table th,
.tableWrapper.toweb-table--has-header table th {
  background: var(--surface-secondary, rgba(0,0,0,0.05)) !important;
  color: var(--tw-text-heading, inherit) !important;
  font-weight: 600 !important;
}

/* When hasHeader is FALSE, first row looks like regular cells (no emphasis) */
/* High specificity needed to override .ProseMirror table th defaults */
.ProseMirror .toweb-table:not(.toweb-table--has-header) table th,
.ProseMirror .tableWrapper:not(.toweb-table--has-header) table th,
.tableWrapper[data-has-header="false"] table th {
  background: transparent !important;
  background-color: transparent !important;
  color: inherit !important;
  font-weight: normal !important;
}

/* ───────────────────────────────────────────────────────────────────────────
   TOWeb Table - Selection/Focus State (via ProseMirror decorations)
   Classes are added to tableWrapper via decorations
   ─────────────────────────────────────────────────────────────────────────── */

/* Focus state for table block - cursor is inside a cell */
/* Uses double-outline pattern for visibility on any background */
.toweb-table--focused {
  outline: 2px solid white;
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--editor-focus-color, #3b82f6);
  border-radius: 0.5rem;
}

/* NodeSelection state (direct selection of table node) */
/* Uses double-outline pattern for visibility on any background */
.toweb-table.ProseMirror-selectednode,
table.ProseMirror-selectednode {
  outline: 2px solid white;
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--editor-focus-color, #3b82f6);
  border-radius: 0.5rem;
}

/* NOTE: Tables do NOT support animations because CSS transforms don't work on display:table */

/* ═══════════════════════════════════════════════════════════════════════════
   TYPOGRAPHY - Headings, paragraphs, links
   DNA-first (contrat §6 « Règle palette » — tue le bug texte-invisible-sur-bande) :
   le texte BRUT (tapé en TipTap, sans classe brique) suit les tokens C de la
   palette de section/carte (--tw-text-heading / --tw-text), re-pointés par
   [data-palette] et re-localisés par .tw-surface[card]. Les éléments à classe
   brique (`tw-*`) portent LEUR couleur via leur propre token → exclus ici (sinon
   .content-section p écraserait .tw-lead/.tw-quote p par spécificité).
   ═══════════════════════════════════════════════════════════════════════════ */

.content-section :is(h1, h2, h3, h4, h5, h6):not([class*="tw-"]) {
  color: var(--tw-text-heading, inherit);
  /* Free-text headings (typed via TipTap, no tw- class) follow the DNA heading
     treatment like the .tw-h* bricks do: signature font + derived weight scale.
     Without this they fell back to the browser default (700) instead of the DNA's
     --tw-weight-heading (e.g. K = 900 black). Size stays as-is (set elsewhere). */
  font-family: var(--tw-font-heading, inherit);
  font-weight: var(--tw-weight-heading, 700);
  letter-spacing: var(--tw-tracking-heading, normal);
}
.content-section h2:not([class*="tw-"]) { font-weight: clamp(400, calc(var(--tw-weight-heading, 700) - var(--tw-weight-step, 0) * 1), 900); }
.content-section :is(h3, h4):not([class*="tw-"]) { font-weight: clamp(400, calc(var(--tw-weight-heading, 700) - var(--tw-weight-step, 0) * 2), 900); }
.content-section :is(h5, h6):not([class*="tw-"]) { font-weight: clamp(400, calc(var(--tw-weight-heading, 700) - var(--tw-weight-step, 0) * 3), 900); }

/* Issue 4 (Yann 2026-06-12) : un mot mis en italique (Cmd+I) dans un titre LIBRE =
   l'ACCENT du DNA — gradient violet→cyan (K), italique terracotta (C)… — exactement
   comme `.tw-h1 em` du spécimen, via les mêmes tokens --tw-em-*. Le DNA décide si
   l'accent est un vrai italique (--tw-em-style:italic) ou un gradient droit
   (--tw-em-gradient + --tw-em-fill:transparent). TipTap rend l'italique en <em> ;
   on couvre <i> par sécurité. → CMD+I devient « accentuer ce mot façon DNA ». */
.content-section :is(h1, h2, h3, h4):not([class*="tw-"]) :is(em, i) {
  font-style: var(--tw-em-style, italic);
  color: var(--tw-em-color, inherit);
  font-weight: inherit;
  background-image: var(--tw-em-gradient, none);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: var(--tw-em-fill, var(--tw-em-color, currentColor));
}

.content-section :is(p, li, td):not([class*="tw-"]) {
  color: var(--tw-text, inherit);
}

/* M-THEME-SCR.6: Text elements spacing is now handled by the Stack Pattern
   defined in the CONTENT SPACING section above. The * + * selector applies
   margin-top to all elements except the first, creating uniform spacing
   between headings, paragraphs, lists, and atom blocks alike. */

/* SoT: the ONE section-link colour rule (editor + preview + publish all load
   blocks-shared.css). The publish generator's .toweb-section a copy was removed
   in favour of this; the var(--tw-accent) fallback preserves its behaviour. */
/* Les briques-lien (.tw-icon social/team, .tw-cta-link, .tw-nav-link) gèrent LEUR
   couleur via leurs tokens --tw-* → exclues ici (sinon le bleu lien-section écrase
   --tw-icon-color : icônes sociales toutes bleues). [class*="button"] couvre .tw-button. */
/* ⚠️ LE CHROME EST EXCLU — 178 `!important` et 100 `-webkit-text-fill-color`
   en dépendaient, dans 23 feuilles de DNA sur 46.

   Cette règle est en spécificité (0,6,1) ; une règle de DNA plafonne à (0,2,1).
   Les liens de pied et d'en-tête étant des <a> dans une `.content-section`, elle
   leur imposait `--tw-accent`, et aucune feuille ne pouvait reprendre la main sur
   `color`. Elles ont donc gagné par un AUTRE chemin : `-webkit-text-fill-color`,
   qui peint le texte quelle que soit la spécificité de `color`. Mesuré : retirer
   ce fill de dna-western fait virer ses liens de pied au rouille de l'accent.

   Le `!important` qui l'accompagnait, lui, ne servait à rien — il renforçait un
   `color` déjà battu, pendant que le fill faisait le travail. C'est ainsi qu'un
   contournement se propage : le premier auteur mesure, les suivants recopient.

   Le chrome a ses propres règles pilotées par jetons, plus bas dans ce fichier.
   L'exclure ici, c'est rendre `color` de nouveau opérant pour les DNAs. */
/* `--tw-prose-link` : la bande `bold` le re-pointe sur son texte (blocks-base.css,
   filet §6) — l'accent n'y est jamais re-pointé, un lien y était donc de la
   couleur du fond (« WHAT IS IT ? » de rainbowio, import V14 du 2026-09-24). */
.content-section a:not(.cta-button):not([class*="button"]):not(.tw-icon):not(.tw-cta-link):not(.tw-nav-link):not([class*="site-footer-block__"]):not([class*="site-header-block__"]):not(.content-section__suite):not(.tag):not([class*="__tag"]) {
  color: var(--tw-prose-link, var(--tw-accent));
}

.content-section a:not(.cta-button):not([class*="button"]):not(.tw-icon):not(.tw-cta-link):not(.tw-nav-link):not([class*="site-footer-block__"]):not([class*="site-header-block__"]):not(.content-section__suite):not(.tag):not([class*="__tag"]):hover {
  opacity: 0.8;
}
/* Un lien DANS le texte courant se voit autrement que par sa couleur (WCAG 1.4.1) :
   sur une bande `bold`, il prend la couleur du texte (filet ci-dessus) et ne se
   distinguait plus du tout — « avec un lien et du texte courant » dans les 48 DNA
   (audit du 2026-09-28). Soulignement fin, décalé, à la couleur du lien. */
/* La documentation n'est pas une `.content-section` : ses liens dans le texte
   restaient nus, à 1,7:1 du texte courant (axe `link-in-text-block`, audit WCAG
   de lauyan.com du 2026-09-29). Même règle. */
:is(.content-section, .doc-article__contenu) :is(p, li, dd, td, th, blockquote, figcaption) > a:not([class]) {
  text-decoration-line: underline;
  text-decoration-thickness: max(1px, 0.07em);
  text-underline-offset: 0.2em;
}

/* ═══════════════════════════════════════════════════════════════════════════
   BLOCKQUOTE
   ═══════════════════════════════════════════════════════════════════════════ */

/* Scope to direct-child blockquotes only (free-text TipTap content).
   Atom blocks like testimonial-block use <blockquote> with BEM classes
   inside nested wrappers — this rule must NOT affect those. */
.content-section__container > blockquote,
.toweb-container > blockquote {
  border-inline-start: 4px solid var(--tw-accent);
  padding-inline-start: 1rem;
  margin: 1rem 0;
  font-style: italic;
  color: var(--tw-text);
  opacity: 0.9;
}

/* ═══════════════════════════════════════════════════════════════════════════
   FEATURES BLOCK - Grid of Features with Icons
   Per EDITOR-BLOCKS-CONTRACT.md §5.6 and ATOM-BLOCK-CONTRACT.md §6
   M04quater.14.b: featuresBlock
   ═══════════════════════════════════════════════════════════════════════════ */

/* RÉ-ÉCRIT BRIQUES (DNA-BLOCKS-SPEC §3.4) : le features-block est porté par la
   couche briques (bricks/blocks-base.css : conteneur `.features-block .tw-grid`,
   item `.tw-surface`, `.tw-icon`/`.tw-media`/`.tw-h4`/`.tw-body`). Tout le CSS
   features legacy est retiré ci-dessous. (La règle de gap des AUTRES blocs de
   collection juste en dessous reste — ils ne sont pas encore migrés.) */

/* R.6 strip (Yann 2026-06-05): every collection block takes its grid gap from
   the DNA (--tw-gap) instead of the removed per-block Gap H/V knob.
   Fallback = the former 'medium' default → default/un-skinned projects unchanged.
   These containers had NO base gap (relied solely on the emitted collection--gap
   classes), so this rule is what keeps them from collapsing once we stop emitting
   those classes. */
/* (stats-block retiré : migré sur .tw-grid → gap = --tw-gap.) */
/* (timeline-block retiré : migré sur brique DNA — gap via --tw-gap dans blocks-base.css.) */
/* (faq-block retiré : migré sur brique DNA — layout dans blocks-base.css ; gap via flex/grid.) */
/* (team-block retiré : migré sur brique DNA — .tw-grid sur .team-block__grid, gap via --tw-gap.) */

/* (CSS features legacy retiré — DOM brique dans blocks-base.css ; placeholder +
   badge de variante déplacés dans blocks-editor.css. Sélection ci-dessous = éditeur.) */
/* Selection state for features block */
.features-block.ProseMirror-selectednode {
  outline: 2px solid var(--tw-accent, #3b82f6);
  outline-offset: 4px;
  border-radius: 0.5rem;
}

/* (CSS columns-block legacy retiré — layout dans blocks-base.css : .columns-block flex + gap=--tw-gap, .column--{33|50|66} flex, valign, empilement mobile. PAS de brique — compose les enfants.) */

/* (CSS team-block legacy retiré — DOM brique dans blocks-base.css : .tw-grid sur .team-block__grid, item .tw-surface, .tw-media--avatar/square/rounded, .tw-icon socials ; gap=--tw-gap ; affordances éditeur → blocks-editor.css.) */

/* (CSS testimonial-block legacy retiré — DOM brique dans blocks-base.css ; placeholder/badge → blocks-editor.css. Sélection ci-dessous = éditeur.) */
/* Selection state */
.testimonial-block.ProseMirror-selectednode {
  outline: 2px solid var(--tw-accent, #3b82f6);
  outline-offset: 4px;
  border-radius: 0.5rem;
}

/* (CSS pricing-block legacy retiré — DOM brique dans blocks-base.css ; placeholder → blocks-editor.css. Sélection ci-dessous = éditeur.) */
/* Selection state */
.pricing-block.ProseMirror-selectednode {
  outline: 2px solid var(--tw-accent);
  outline-offset: 4px;
  border-radius: 0.5rem;
}

/* ═══════════════════════════════════════════════════════════════════════════
   STATS BLOCK - M04quater.14.f
   Key figures/metrics with counter animation

   All colors MUST use section pattern CSS variables for proper theme support.
   Gap is controlled via collection--gap-* classes applied by the extension.
   ═══════════════════════════════════════════════════════════════════════════ */

/* (CSS stats legacy retiré — DOM brique dans blocks-base.css ; placeholder déplacé
   dans blocks-editor.css. Sélection ci-dessous = éditeur.) */
/* Selection state */
.stats-block.ProseMirror-selectednode {
  outline: 2px solid var(--tw-accent);
  outline-offset: 4px;
  border-radius: 0.5rem;
}

/* TIMELINE BLOCK — retiré (migré sur brique DNA — blocks-base.css §TIMELINE).
   var(--tw-accent) → --tw-rule-strong (connecteur) + .tw-icon (marker).
   Placeholder + sélection → blocks-editor.css. */

/* ═══════════════════════════════════════════════════════════════════════════
   SPACER BLOCK - Vertical Spacing
   Per M04quater.14.g-bis: Explicit vertical spacing between blocks.

   The spacerBlock is a simple empty block with configurable height.
   It provides explicit spacing control when default margins aren't enough.
   ═══════════════════════════════════════════════════════════════════════════ */

/* spacer-block — espacement vertical explicite (layout pur, pas de brique — DNA-BLOCKS-SPEC §3.1).
   Hauteur = classe --{size} OU inline `height` (custom). Les affordances ÉDITEUR (zone
   pointillée, badge "ESPACEUR", sélection) vivent dans blocks-editor.css — surtout PAS ici :
   blocks-shared.css est copié au publish, elles s'afficheraient sur le site publié. */
.spacer-block {
  width: 100%;
  position: relative;
}

/* Predefined height classes */
.spacer-block--small { height: 1rem; }      /* 16px */
.spacer-block--medium { height: 2rem; }     /* 32px */
.spacer-block--large { height: 4rem; }      /* 64px */
.spacer-block--xlarge { height: 6rem; }     /* 96px */
.spacer-block--2xlarge { height: 8rem; }    /* 128px */
.spacer-block--3xlarge { height: 12rem; }   /* 192px */

/* ═══════════════════════════════════════════════════════════════════════════
   MAP BLOCK - Embedded Maps (Google Maps / OpenStreetMap)
   Per M04quater.14.h: Interactive map display with configurable location.

   In the editor, displays a LIVE map via iframe (OpenStreetMap free, Google Maps with API key).
   ═══════════════════════════════════════════════════════════════════════════ */

.map-block {
  /* radius / overflow / position / shadow come from the .tw-media brick */
  width: 100%;
  background: var(--tw-surface, rgba(128, 128, 128, 0.05));
  border: 1px solid var(--tw-border, rgba(128, 128, 128, 0.2));
  /* le map a une hauteur fixe + bordure → ce qui suit se colle sinon (Yann 2026-06-25).
     On rétablit le rythme de bloc dessous (les autres médias sont en flux avec du texte). */
  margin-bottom: var(--theme-block-gap, 1.5rem);
}

/* Live map iframe */
.map-block__iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

/* Dans l'ÉDITEUR seulement : le cadre ne capte pas les clics (le double-clic
   ouvre les réglages). Cette feuille est aussi celle du site publié : sans la
   portée `.ProseMirror`, la carte en ligne était figée, ni molette ni glisser
   (HELO 75119). */
.ProseMirror .map-block__iframe {
  pointer-events: none;
}

/* Provider badge */
.map-block__provider-badge {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  padding: 0.125rem 0.375rem;
  background: var(--tw-accent, #3b82f6);
  color: var(--tw-on-accent, #ffffff);
  border-radius: 0.25rem;
  text-transform: uppercase;
  z-index: 10;
  pointer-events: none;
}

/* Selection state */
.map-block.ProseMirror-selectednode {
  outline: 2px solid var(--tw-accent, #3b82f6);
  outline-offset: 2px;
  border-radius: 0.5rem;
}

/* Keep placeholder styles for fallback/Google Maps without API key */
.map-block__placeholder {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  width: 100%;
  height: 100%;
  min-height: 200px;
  padding: 2rem;
  text-align: center;
  color: var(--tw-text, rgba(128, 128, 128, 0.8));
}

.map-block__placeholder-icon {
  width: 48px;
  height: 48px;
  opacity: 0.7;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 0 24 24' fill='none' stroke='%233b82f6' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z'/%3E%3Ccircle cx='12' cy='10' r='3'/%3E%3C/svg%3E");
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}

.map-block__placeholder-title {
  margin: 0;
  font-size: 1rem;
  font-weight: 600;
  color: var(--tw-text-heading, currentColor);
}

.map-block__placeholder-address {
  margin: 0;
  font-size: 0.875rem;
  color: var(--tw-text, rgba(128, 128, 128, 0.8));
  max-width: 400px;
  word-break: break-word;
}

.map-block__placeholder-coords {
  margin: 0;
  font-size: 0.75rem;
  font-family: monospace;
  color: var(--tw-text, rgba(128, 128, 128, 0.6));
}

.map-block__placeholder-hint {
  margin: 0;
  margin-top: 0.5rem;
  font-size: 0.75rem;
  color: var(--tw-text, rgba(128, 128, 128, 0.5));
  font-style: italic;
}

/* ═══════════════════════════════════════════════════════════════════════════
   FORM BLOCK - Contact form with configurable fields
   ═══════════════════════════════════════════════════════════════════════════
   Per M04quater.14.i: Contact form with customizable fields.

   In the editor, displays a preview of the form (disabled inputs).
   ═══════════════════════════════════════════════════════════════════════════ */

.form-block {
  width: 100%;
  position: relative;
}

/* Variant: card - with background and shadow */
.form-block--card {
  background: var(--tw-surface, white);
  border: 1px solid var(--tw-border, rgba(128, 128, 128, 0.2));
  /* Les formes et l'ombre du DNA (rayon et ombre de CARTE), plus un 8 px et une
     ombre grise identiques dans les 48 (audit du 2026-09-28). */
  border-radius: var(--tw-radius-card, 0.5rem);
  padding: 1.5rem;
  box-shadow: var(--tw-shadow-card, 0 2px 8px rgba(0, 0, 0, 0.08));
  color: var(--tw-on-surface, inherit);
}

/* Form container */
.form-block__form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

/* ─── Inline layout (compact email-capture / newsletter signup) ──────────────
   Fields row + submit button on ONE horizontal row; the input(s) grow (flex:1),
   the submit sits inline at the end. Wraps to stacked on mobile. Pure layout
   switch — reuses every field/validation/alpine/success-error surface as-is.
   A form whose only field is {email} renders as the email-capture widget. */
.form-block--inline .form-block__form {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.5rem;
}

/* The fields container grows and lays its fields in a row. */
.form-block--inline .form-block__fields {
  flex: 1 1 auto;
  flex-wrap: nowrap;
  align-items: flex-end;
  gap: 0.5rem;
  min-width: 0;
}

/* Each field grows to fill the row; the editor's "/ placeholder:" row + half
   width are neutralised so the inline pattern stays a single compact line. */
.form-block--inline .form-block__field {
  flex: 1 1 0;
  min-width: 0;
}

.form-block--inline .form-block__field--half {
  flex: 1 1 0;
}

/* Submit sits inline at the end of the row (not full-width / left-aligned). */
.form-block--inline .form-block__submit {
  align-self: flex-end;
  flex: 0 0 auto;
  white-space: nowrap;
}

/* In the published form the submit is wrapped in its own div (x-show="!sent");
   keep that wrapper inline so the button rides on the same row as the fields. */
.form-block--inline .form-block__form > div:not(.form-block__fields):not(.form-block__success):not(.form-block__error) {
  flex: 0 0 auto;
}

/* Mobile: fall back to the stacked layout (fields + button stack full-width). */
@media (max-width: 640px) {
  .form-block--inline .form-block__form,
  .form-block--inline .form-block__fields {
    flex-direction: column;
    flex-wrap: wrap;
    align-items: stretch;
  }
  .form-block--inline .form-block__field,
  .form-block--inline .form-block__field--half {
    flex: 0 0 100%;
  }
  .form-block--inline .form-block__submit {
    align-self: flex-start;
  }
}

/* Title and description */
.form-block__title {
  margin: 0 0 0.25rem 0;
  font-size: 1.25rem;
  font-weight: 600;
  color: var(--tw-text-heading, currentColor);
}

.form-block__description {
  margin: 0 0 0.5rem 0;
  font-size: 0.875rem;
  color: var(--tw-text, rgba(128, 128, 128, 0.8));
}

/* Fields container */
.form-block__fields {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}

/* Individual field */
.form-block__field {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
}

.form-block__field--full {
  flex: 0 0 100%;
}

.form-block__field--half {
  flex: 0 0 calc(50% - 0.5rem);
}

/* Responsive: full width on mobile */
@media (max-width: 640px) {
  .form-block__field--half {
    flex: 0 0 100%;
  }
}

/* Field label */
.form-block__label {
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--tw-text, currentColor);
}

/* Checkbox label */
.form-block__checkbox-label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.875rem;
  color: var(--tw-text, currentColor);
  cursor: not-allowed;
}

.form-block__checkbox-label input {
  width: 1rem;
  height: 1rem;
  margin: 0;
  cursor: not-allowed;
}

/* Radio button group */
.form-block__radio-group {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.form-block__radio-label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.875rem;
  color: var(--tw-text, currentColor);
  cursor: not-allowed;
}

.form-block__radio-label input {
  width: 1rem;
  height: 1rem;
  margin: 0;
  cursor: not-allowed;
  accent-color: var(--tw-accent, #3b82f6);
}

/* Input styles */
/* Champs (input/textarea/select) = brique .tw-field — le DNA possède leur look
   (DOM-SPEC §12). Plus de styling .form-block__input ici. */

/* ─── Champ fichier (.tw-file) ─────────────────────────────────────────────
   Le contrôle natif est laid et non stylable ; on le rend INVISIBLE mais présent
   (opacity 0 par-dessus la coquille, pas display:none) pour garder le clavier,
   le lecteur d'écran et le clic n'importe où dans la coquille. La coquille
   reprend .tw-field (bordure, rayon, focus) et le bouton les jetons du bouton du
   thème — ainsi le champ change de look avec le DNA comme les autres champs.
   Mobile-first : le bouton et le nom s'empilent, la ligne s'étale dès 480px. */
.tw-file {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  width: 100%;
  min-height: 2.75rem;
  padding: 0.45rem;
  background: var(--tw-field-bg, var(--tw-surface, #fff));
  color: var(--tw-on-surface, inherit);
  border: var(--tw-border-width, 1px) dashed var(--tw-field-border, var(--tw-border, rgba(0,0,0,.25)));
  border-radius: var(--tw-radius-field, var(--tw-radius-button, 8px));
  transition: border-color .15s ease, box-shadow .15s ease;
  cursor: pointer;
}
@media (min-width: 480px) {
  .tw-file { flex-direction: row; align-items: center; }
}
.tw-file__input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
  font-size: 0; /* Safari : sinon le nom natif déborde à droite */
}
.tw-file__input:disabled { cursor: not-allowed; }
.tw-file:focus-within {
  border-color: var(--tw-accent, #888);
  box-shadow: 0 0 0 3px var(--tw-accent-soft, rgba(0,0,0,.08));
}
.tw-file--filled { border-style: solid; }
.tw-file--invalid { border-color: var(--tw-danger, #dc2626); }
.tw-file__button {
  flex: 0 0 auto;
  padding: 0.45rem 0.9rem;
  border-radius: var(--tw-radius-button, 8px);
  font-size: 0.875rem;
  font-weight: var(--tw-btn-weight, 500);
  color: var(--tw-btn-text, var(--tw-on-accent, #fff));
  background: var(--tw-btn-bg, var(--tw-accent, #3b82f6));
  border: var(--tw-btn-border, none);
  text-align: center;
  pointer-events: none; /* le clic va au <input> qui recouvre tout */
}
.tw-file__name {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 0.875rem;
  color: var(--tw-text-muted, #777);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  pointer-events: none;
}
.tw-file--filled .tw-file__name { color: var(--tw-text, currentColor); }
.tw-file__clear {
  position: relative;
  z-index: 1; /* au-dessus de l'input invisible : ce bouton, lui, se clique */
  flex: 0 0 auto;
  align-self: flex-end;
  width: 1.75rem;
  height: 1.75rem;
  border: none;
  border-radius: 999px;
  background: var(--tw-accent-soft, rgba(0,0,0,.08));
  color: var(--tw-text, currentColor);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}
@media (min-width: 480px) { .tw-file__clear { align-self: center; } }
.tw-file__hint {
  margin: 0;
  font-size: 0.75rem;
  color: var(--tw-text-muted, #777);
}
.tw-file__error {
  margin: 0;
  font-size: 0.8125rem;
  color: var(--tw-danger, #dc2626);
}

/* Bandeaux succès / erreur du formulaire (le x-text de l'erreur n'avait aucun
   style : un message rouge sans rouge, c'est un message qu'on ne voit pas). */
.form-block__requis { margin-inline-start: 0.2em; font-weight: 600; }
.form-block__legende-requis { margin: 0; font-size: 0.8125rem; color: var(--tw-text-muted, currentColor); }
.form-block__legende-requis > span { font-weight: 600; }
/* Les régions annoncées (role="status" / "alert") suivent le <form> : leur
   message garde l'écart qu'il avait dans la colonne du formulaire. */
.form-block__statut > * { margin-top: 1rem; }
.form-block__success:focus { outline: none; }
.form-block__success:focus-visible { outline: 2px solid var(--tw-accent, currentColor); outline-offset: 2px; }
.form-block__success {
  padding: 0.75rem 1rem;
  border-radius: var(--tw-radius-field, var(--tw-radius-button, 8px));
  background: var(--tw-accent-soft, rgba(0,0,0,.06));
  color: var(--tw-text, currentColor);
  font-size: 0.9375rem;
}
.form-block__error {
  padding: 0.75rem 1rem;
  border-radius: var(--tw-radius-field, var(--tw-radius-button, 8px));
  border-inline-start: 3px solid var(--tw-danger, #dc2626);
  background: rgba(220, 38, 38, 0.06);
  color: var(--tw-danger, #dc2626);
  font-size: 0.875rem;
}

/* Submit button - M-THEME-SCR.5: Uses theme CSS variables */
.form-block__submit {
  align-self: flex-start;
  /* M-THEME-SCR.5: Use theme button padding/radius/shadow/colors */
  padding: 0.625rem 1.25rem;
  border-radius: var(--tw-radius-button, 8px);
  box-shadow: var(--tw-shadow-button, none);
  font-size: 0.875rem;
  font-weight: 500;
  /* M-THEME-SCR.5: Uses effective primary button colors (respects outline mode) */
  color: var(--tw-btn-text, var(--tw-on-accent, #ffffff));
  background: var(--tw-btn-bg, var(--tw-accent, #3b82f6));
  border: var(--tw-btn-border, none);
  /* ⚠️ La police du site : un <button> ne l'hérite pas, il prenait celle du système. */
  font-family: inherit;
  cursor: pointer;
}
/* ⚠️ INERTE DANS L'ÉDITEUR SEULEMENT. Ces deux lignes étaient dans la règle de
   base : le bouton « Envoyer » des formulaires PUBLIÉS était pâli, avec un
   curseur « interdit » (vu sur la page Assistance de lauyan.com, 2026-09-25). */
:is(.tiptap-editor, .page-tiptap-editor) .form-block__submit {
  cursor: not-allowed;
  opacity: 0.8;
}
/* Pendant l'envoi (`:disabled="sending"`). */
.form-block__submit:disabled {
  cursor: progress;
  opacity: 0.6;
}

/* Placeholder state (empty form) */
.form-block__placeholder {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: 150px;
  padding: 2rem;
  text-align: center;
  color: var(--tw-text, rgba(128, 128, 128, 0.8));
  border: 2px dashed var(--tw-border, rgba(128, 128, 128, 0.3));
  border-radius: 0.5rem;
  background: var(--tw-surface, rgba(128, 128, 128, 0.02));
}

/* Selection state */
.form-block.ProseMirror-selectednode {
  outline: 2px solid var(--tw-accent, #3b82f6);
  outline-offset: 2px;
  border-radius: 0.5rem;
}


/* ═══════════════════════════════════════════════════════════════════════════
   DIVIDER BLOCK - Horizontal separator with styling options
   ═══════════════════════════════════════════════════════════════════════════
   Per M04quater.14.j: Horizontal divider/separator block.

   Variants: solid, dashed, dotted, double, gradient, fade
   Widths: full (100%), 75%, 50%, 25%
   Alignment: left, center, right
   Thickness: thin (1px), medium (2px), thick (4px)
   Spacing: small, medium, large
   ═══════════════════════════════════════════════════════════════════════════ */

.divider-block {
  width: 100%;
  display: flex;
  position: relative;
  min-height: 1.5rem; /* Ensure clickable area for selection */
  padding: 0.5rem 0;
  box-sizing: border-box;
  align-items: center;
}

.divider-block__line {
  border: none;
  margin: 0;
  background: var(--tw-text, currentColor);
  opacity: 0.3;
}

/* ─────────────────────────────────────────────────────────────────────────────
   Width variants
   ───────────────────────────────────────────────────────────────────────────── */

.divider-block--width-full .divider-block__line {
  width: 100%;
}

.divider-block--width-75 .divider-block__line {
  width: 75%;
}

.divider-block--width-50 .divider-block__line {
  width: 50%;
}

.divider-block--width-25 .divider-block__line {
  width: 25%;
}

/* ─────────────────────────────────────────────────────────────────────────────
   Alignment variants
   ───────────────────────────────────────────────────────────────────────────── */

.divider-block--align-left {
  justify-content: flex-start;
}

.divider-block--align-center {
  justify-content: center;
}

.divider-block--align-right {
  justify-content: flex-end;
}

/* ─────────────────────────────────────────────────────────────────────────────
   Thickness variants
   ───────────────────────────────────────────────────────────────────────────── */

.divider-block--thin .divider-block__line {
  height: 1px;
}

.divider-block--medium .divider-block__line {
  height: 2px;
}

.divider-block--thick .divider-block__line {
  height: 4px;
}

/* ─────────────────────────────────────────────────────────────────────────────
   Spacing variants (vertical padding)
   ───────────────────────────────────────────────────────────────────────────── */

.divider-block--spacing-small {
  padding: 0.5rem 0;
}

.divider-block--spacing-medium {
  padding: 1rem 0;
}

.divider-block--spacing-large {
  padding: 2rem 0;
}

/* ─────────────────────────────────────────────────────────────────────────────
   Style variants
   ───────────────────────────────────────────────────────────────────────────── */

/* Solid (default) - already styled above */

/* Dashed */
.divider-block--dashed .divider-block__line {
  background: none;
  border-top-style: dashed;
  border-top-color: var(--tw-text, currentColor);
  opacity: 0.3;
}

.divider-block--dashed.divider-block--thin .divider-block__line {
  border-top-width: 1px;
  height: 0;
}

.divider-block--dashed.divider-block--medium .divider-block__line {
  border-top-width: 2px;
  height: 0;
}

.divider-block--dashed.divider-block--thick .divider-block__line {
  border-top-width: 4px;
  height: 0;
}

/* Dotted */
.divider-block--dotted .divider-block__line {
  background: none;
  border-top-style: dotted;
  border-top-color: var(--tw-text, currentColor);
  opacity: 0.3;
}

.divider-block--dotted.divider-block--thin .divider-block__line {
  border-top-width: 1px;
  height: 0;
}

.divider-block--dotted.divider-block--medium .divider-block__line {
  border-top-width: 2px;
  height: 0;
}

.divider-block--dotted.divider-block--thick .divider-block__line {
  border-top-width: 4px;
  height: 0;
}

/* Double */
.divider-block--double .divider-block__line {
  background: none;
  border-top-style: double;
  border-top-color: var(--tw-text, currentColor);
  opacity: 0.3;
}

.divider-block--double.divider-block--thin .divider-block__line {
  border-top-width: 3px;
  height: 0;
}

.divider-block--double.divider-block--medium .divider-block__line {
  border-top-width: 4px;
  height: 0;
}

.divider-block--double.divider-block--thick .divider-block__line {
  border-top-width: 6px;
  height: 0;
}

/* Gradient - fades from transparent to color to transparent */
.divider-block--gradient .divider-block__line {
  background: linear-gradient(
    to right,
    transparent,
    var(--tw-text, currentColor) 20%,
    var(--tw-text, currentColor) 80%,
    transparent
  );
  opacity: 0.3;
}

/* Fade - fades from center outward */
.divider-block--fade .divider-block__line {
  background: linear-gradient(
    to right,
    transparent,
    var(--tw-text, currentColor) 50%,
    transparent
  );
  opacity: 0.3;
}

/* ─────────────────────────────────────────────────────────────────────────────
   Selection state
   ───────────────────────────────────────────────────────────────────────────── */

.divider-block.ProseMirror-selectednode {
  outline: 2px solid var(--tw-accent, #3b82f6);
  outline-offset: 2px;
  border-radius: 0.25rem;
}

/* (CSS social-block legacy retiré — DOM brique .tw-icon dans blocks-base.css ; tailles via classes social-block--size-* → --tw-icon-size ; affordances éditeur → blocks-editor.css.) */

/* (CSS logo-cloud-block legacy retiré — DOM brique dans blocks-base.css ; placeholders → blocks-editor.css. Sélection ci-dessous = éditeur.) */
/* Selection state */
.logo-cloud-block.ProseMirror-selectednode {
  outline: 2px solid var(--tw-accent, #3b82f6);
  outline-offset: 2px;
  border-radius: 0.375rem;
}

/* ═══════════════════════════════════════════════════════════════════════════
   CODE BLOCK - Source code with syntax highlighting
   Per EDITOR-BLOCKS-CONTRACT.md
   M04quater.14.m: Code block for displaying source code
   ═══════════════════════════════════════════════════════════════════════════ */

.tw-code {
  display: block;
  width: 100%;
  min-height: 3rem; /* Ensure clickable area for selection */
  margin: 0;
  border-radius: 0.5rem;
  overflow: hidden;
  background: var(--tw-code-bg, #1e1e1e);
  color: var(--tw-code-text, #d4d4d4);
  font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Consolas', monospace;
  font-size: 0.875em; /* 87.5% of parent font size - industry standard for code */
  line-height: 1.5;
}

/* Chrome du bloc code (header/numéros) : dérivé de --tw-code-text pour rester
   lisible quel que soit le fond du thème code (clair OU sombre). */
.tw-code__chrome {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.5rem 0.75rem;
  background: color-mix(in srgb, var(--tw-code-text, #fff) 6%, transparent);
  border-bottom: 1px solid var(--tw-code-chrome-rule, rgba(255, 255, 255, 0.1));
}

.tw-code__lang {
  font-size: 0.75rem;
  /* Atténué VERS le fond du terminal, pas vers la transparence : à 60 % de
     transparence, l'étiquette de langue tombait à 3,5:1 sur la barre de BOTANIQUE
     (axe-core, 2026-09-28). À 82 % d'encre, elle reste ≥ 4,5:1 sur les 48. */
  color: color-mix(in oklab, var(--tw-code-text, #fff) 82%, var(--tw-code-bg, #1e1e1e));
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.tw-code__file {
  font-size: 0.75rem;
  color: color-mix(in srgb, var(--tw-code-text, #fff) 80%, transparent);
  margin-left: auto;
  padding-right: 0.5rem;
}

.tw-code__copy {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  margin-left: auto;
  padding: 0;
  background: transparent;
  border: none;
  border-radius: 0.25rem;
  color: color-mix(in srgb, var(--tw-code-text, #fff) 50%, transparent);
  cursor: pointer;
  transition: color 0.2s ease, background 0.2s ease;
}

.tw-code__copy:hover {
  color: color-mix(in srgb, var(--tw-code-text, #fff) 90%, transparent);
  background: color-mix(in srgb, var(--tw-code-text, #fff) 10%, transparent);
}

/* ─── Window chrome (terminal / console look) — opt-in via chrome:'window' ───
   Adds a bordered window container + a header bar with three "traffic-light"
   dots (fixed decorative colours) and an optional centred title. The container
   border uses --tw-* tokens; the body / scroll / syntax styling stay intact.
   chrome omitted / 'none' → none of these rules apply (byte-identical to today). */
.tw-code--window {
  border: 1px solid var(--tw-code-chrome-rule, rgba(255, 255, 255, 0.1));
  box-shadow: 0 12px 32px -12px rgba(0, 0, 0, 0.45);
}

/* Three traffic-light dots on the left of the header bar. */
.tw-code__dots {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  flex: 0 0 auto;
}

/* bricks/blocks-base.css ships a legacy `.tw-code__dots::before/::after` that
   would render 2 phantom pseudo-dots before our real ones — neutralise them
   (we use real <span class="tw-code__dot"> children). */
.tw-code__dots::before,
.tw-code__dots::after {
  content: none;
}

.tw-code__dot {
  width: 0.75rem;
  height: 0.75rem;
  border-radius: 50%;
  background: color-mix(in srgb, var(--tw-code-text, #fff) 25%, transparent);
}

.tw-code__dot--red { background: #ff5f57; }
.tw-code__dot--amber { background: #febc2e; }
.tw-code__dot--green { background: #28c840; }

/* Window title (filename / path) — centred, takes the remaining space so the
   lang label + copy button stay flush right. */
.tw-code__title {
  flex: 1 1 auto;
  min-width: 0;
  text-align: center;
  font-size: 0.75rem;
  color: color-mix(in srgb, var(--tw-code-text, #fff) 70%, transparent);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* In window mode the lang label sits on the right (next to copy), not the left. */
.tw-code--window .tw-code__lang {
  margin-left: auto;
  flex: 0 0 auto;
}

/* The copy button keeps its auto-left in legacy mode; in window mode the title /
   lang already push it right, so neutralise the margin to avoid a double gap.
   Without either (plain text, no title), it keeps its auto-left. */
.tw-code--window .tw-code__title ~ .tw-code__copy,
.tw-code--window .tw-code__lang ~ .tw-code__copy {
  margin-left: 0;
}

.tw-code__copy:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--tw-code-text, #fff) 70%, transparent);
  outline-offset: 2px;
}

.tw-code__copy[data-copie="ok"] {
  color: #28c840;
}

.tw-code__pre {
  margin: 0;
  padding: 0;
  overflow-x: auto;
}

.tw-code__content {
  display: flex;
  padding: 1rem;
}

.tw-code__line-numbers {
  display: flex;
  flex-direction: column;
  padding-right: 1rem;
  margin-right: 1rem;
  border-right: 1px solid var(--tw-code-chrome-rule, rgba(255, 255, 255, 0.1));
  user-select: none;
  text-align: right;
  color: color-mix(in srgb, var(--tw-code-text, #fff) 30%, transparent);
  min-width: 2rem;
}

.tw-code__line-number {
  line-height: 1.5;
}

.tw-code__body {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0;
  white-space: pre;
}

.tw-code__line {
  line-height: 1.5;
}

/* Without line numbers */
.tw-code:not(.tw-code--with-line-numbers) .tw-code__content {
  padding: 1rem;
}

/* Coloration syntaxique → tokens DNA `--tw-code-*` (le DNA possède la palette
   code) ; fallback = thème VS Code Dark+ pour les projets non-DNA. Les types sans
   token DNA dédié (variable/tag/attribute/selector/property) retombent sur le
   token le plus proche (keyword/function/text). */
.code-token--keyword,
.code-token--tag,
.code-token--selector {
  color: var(--tw-code-keyword, #569cd6);
}

.code-token--string {
  color: var(--tw-code-string, #ce9178);
}

.code-token--comment {
  color: var(--tw-code-comment, #6a9955);
  font-style: italic;
}

.code-token--number {
  color: var(--tw-code-number, #b5cea8);
}

.code-token--function,
.code-token--attribute,
.code-token--property {
  color: var(--tw-code-function, #dcdcaa);
}

.code-token--variable {
  color: var(--tw-code-text, #9cdcfe);
}

/* Selection state (éditeur) */
.tw-code.ProseMirror-selectednode {
  outline: 2px solid var(--tw-accent, #3b82f6);
  outline-offset: 2px;
}



/* ═══════════════════════════════════════════════════════════════════════════
   INSTAGRAM-STYLE IMAGE FILTERS (M-SIDEBAR.17)
   CSS filters inspired by CSSgram - Applied ONLY to img elements
   No ::after overlays to avoid affecting block background
   ═══════════════════════════════════════════════════════════════════════════ */

/* 1977 - Vintage warm pink tone */
.image-block--filter-1977 img,
.gallery-block--filter-1977 .gallery-block__item img {
  filter: contrast(1.1) brightness(1.1) saturate(1.3) sepia(0.1);
}

/* Aden - Soft desaturated warm tone */
.image-block--filter-aden img,
.gallery-block--filter-aden .gallery-block__item img {
  filter: hue-rotate(-20deg) contrast(0.9) saturate(0.85) brightness(1.2);
}

/* Brannan - High contrast sepia */
.image-block--filter-brannan img,
.gallery-block--filter-brannan .gallery-block__item img {
  filter: sepia(0.5) contrast(1.4);
}

/* Brooklyn - Subtle warm */
.image-block--filter-brooklyn img,
.gallery-block--filter-brooklyn .gallery-block__item img {
  filter: contrast(0.9) brightness(1.1) sepia(0.05);
}

/* Clarendon - Vibrant saturated */
.image-block--filter-clarendon img,
.gallery-block--filter-clarendon .gallery-block__item img {
  filter: contrast(1.2) saturate(1.35);
}

/* Earlybird - Vintage warm sepia */
.image-block--filter-earlybird img,
.gallery-block--filter-earlybird .gallery-block__item img {
  filter: contrast(0.9) sepia(0.25) saturate(1.1);
}

/* Gingham - Soft washed out vintage */
.image-block--filter-gingham img,
.gallery-block--filter-gingham .gallery-block__item img {
  filter: brightness(1.05) hue-rotate(-10deg) saturate(0.9);
}

/* Hudson - Cool blue tones */
.image-block--filter-hudson img,
.gallery-block--filter-hudson .gallery-block__item img {
  filter: brightness(1.2) contrast(0.9) saturate(1.1) hue-rotate(5deg);
}

/* Inkwell - Classic black & white */
.image-block--filter-inkwell img,
.gallery-block--filter-inkwell .gallery-block__item img {
  filter: grayscale(1) contrast(1.1) brightness(1.1);
}

/* Lofi - High contrast saturated */
.image-block--filter-lofi img,
.gallery-block--filter-lofi .gallery-block__item img {
  filter: contrast(1.5) saturate(1.1);
}

/* Maven - Green-tinted warm tones */
.image-block--filter-maven img,
.gallery-block--filter-maven .gallery-block__item img {
  filter: sepia(0.25) brightness(0.95) contrast(0.95) saturate(1.5) hue-rotate(-10deg);
}

/* Moon - Soft grayscale with glow */
.image-block--filter-moon img,
.gallery-block--filter-moon .gallery-block__item img {
  filter: grayscale(1) contrast(1.1) brightness(1.1);
}

/* Nashville - Warm pink tones */
.image-block--filter-nashville img,
.gallery-block--filter-nashville .gallery-block__item img {
  filter: sepia(0.25) contrast(1.2) brightness(1.05) saturate(1.2) hue-rotate(-5deg);
}

/* Reyes - Dusty vintage with low contrast */
.image-block--filter-reyes img,
.gallery-block--filter-reyes .gallery-block__item img {
  filter: sepia(0.22) brightness(1.1) contrast(0.85) saturate(0.75);
}

/* Rise - Warm soft glow */
.image-block--filter-rise img,
.gallery-block--filter-rise .gallery-block__item img {
  filter: brightness(1.05) sepia(0.2) contrast(0.9) saturate(0.9);
}

/* Slumber - Desaturated muted tones */
.image-block--filter-slumber img,
.gallery-block--filter-slumber .gallery-block__item img {
  filter: saturate(0.66) brightness(1.05) sepia(0.1);
}

/* Toaster - High contrast warm */
.image-block--filter-toaster img,
.gallery-block--filter-toaster .gallery-block__item img {
  filter: contrast(1.5) brightness(0.9) sepia(0.15) saturate(1.2);
}

/* Valencia - Warm faded */
.image-block--filter-valencia img,
.gallery-block--filter-valencia .gallery-block__item img {
  filter: contrast(1.08) brightness(1.08) sepia(0.15) saturate(1.1);
}

/* Walden - Blue/yellow vintage warmth */
.image-block--filter-walden img,
.gallery-block--filter-walden .gallery-block__item img {
  filter: brightness(1.1) hue-rotate(-10deg) sepia(0.3) saturate(1.6);
}

/* Willow - Soft grayscale with warm highlights */
.image-block--filter-willow img,
.gallery-block--filter-willow .gallery-block__item img {
  filter: grayscale(0.5) contrast(0.95) brightness(0.9) sepia(0.1);
}

/* ═══════════════════════════════════════════════════════════════════════════
   MODERN CSS FILTERS (2024+)
   Duotone effects, matte look, vivid colors
   ═══════════════════════════════════════════════════════════════════════════ */

/* Duo-Cyan - Modern tech/cool duotone */
.image-block--filter-duo-cyan img,
.gallery-block--filter-duo-cyan .gallery-block__item img {
  filter: grayscale(1) sepia(1) hue-rotate(150deg) saturate(2.5) contrast(1.1);
}

/* Duo-Rose - Warm pink duotone */
.image-block--filter-duo-rose img,
.gallery-block--filter-duo-rose .gallery-block__item img {
  filter: grayscale(1) sepia(1) hue-rotate(310deg) saturate(1.8) brightness(1.05);
}

/* Duo-Amber - Golden/amber duotone */
.image-block--filter-duo-amber img,
.gallery-block--filter-duo-amber .gallery-block__item img {
  filter: grayscale(1) sepia(1) hue-rotate(15deg) saturate(2) contrast(1.05);
}

/* Duo-Violet - Purple duotone */
.image-block--filter-duo-violet img,
.gallery-block--filter-duo-violet .gallery-block__item img {
  filter: grayscale(1) sepia(1) hue-rotate(230deg) saturate(2) brightness(0.95);
}

/* Matte - Modern flat/faded look */
.image-block--filter-matte img,
.gallery-block--filter-matte .gallery-block__item img {
  filter: contrast(0.85) brightness(1.1) saturate(0.7);
}

/* Vivid - Hyper-saturated modern */
.image-block--filter-vivid img,
.gallery-block--filter-vivid .gallery-block__item img {
  filter: contrast(1.15) saturate(1.5) brightness(1.05);
}

/* ═══════════════════════════════════════════════════════════════════════════
   SYSTEM SECTIONS (header / footer containers)
   No extra padding — blocks fill the full section width.
   Uses !important to override inline styles from getContainerWidthStyle().
   ═══════════════════════════════════════════════════════════════════════════ */

.content-section--system {
  padding-top: 0 !important;
  padding-bottom: 0 !important;
}

.content-section--system > .content-section__container {
  padding-left: 0 !important;
  padding-right: 0 !important;
  max-width: none !important;
}

/* System section atom blocks: no gap margins, stretch to fill container */
.content-section--system > .content-section__container > .atom-block {
  margin-top: 0 !important;
  margin-bottom: 0 !important;
  align-self: stretch;
}

/* ═══════════════════════════════════════════════════════════════════════════
   SITE HEADER BLOCK (.site-header-block)
   Reactive NodeView — displays real navigation items from the store.
   Inherits the DNA palette via the brick tokens (--tw-*) like all other blocks.
   ═══════════════════════════════════════════════════════════════════════════ */

.site-header-block {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100% !important;
  min-width: 0;
  padding: 0.75rem 1rem;
  background: var(--tw-bg, transparent);
  box-shadow: var(--header-shadow, none);
  cursor: pointer;
  user-select: none;
  min-height: 40px;
  /* #155 : le header REPLIE au lieu de déborder. Il était en nowrap implicite, donc
     sous ~390px le cluster d'actions sortait de l'écran et TOUTE page du site publié
     défilait horizontalement (mesuré : scrollWidth figé à 409px sur accueil, boutique
     et checkout — soit un iPhone 14 et la plupart des Android). Le repli est déjà le
     motif retenu pour la variante logo centré ci-dessous.
     Le row-gap serré est appliqué SOUS 640px seulement (voir la media query plus bas) :
     la variante logo centré se replie, elle, à toutes les largeurs, et resserrer son
     row-gap ici aurait modifié son rendu DESKTOP sur les 46 DNAs — un effet de bord que
     ce correctif n'a pas à provoquer (revue Fable 5). */
  flex-wrap: wrap;
  gap: 1.5rem;
  box-sizing: border-box;
}

/* Layout variants */
.site-header-block--logo-center {
  justify-content: center;
  flex-wrap: wrap;
}
.site-header-block--logo-center .site-header-block__nav {
  justify-content: center;
  flex: none;
}
.site-header-block--logo-right {
  flex-direction: row-reverse;
}
.site-header-block--logo-right .site-header-block__nav {
  justify-content: flex-start;
}

/* Logo — uses section link color (matches .toweb-section a rule in Astro theme.css) */
.site-header-block__logo {
  font-family: var(--theme-font-heading, inherit);
  font-weight: 700;
  font-size: 1.25rem;
  color: var(--tw-accent, var(--tw-text-heading, #1e293b));
  white-space: nowrap;
  letter-spacing: -0.01em;
  flex-shrink: 0;
}
.site-header-block__logo-img {
  height: 2rem;
  width: auto;
  object-fit: contain;
  vertical-align: middle;
}

/* Logo size variants (mirrors Astro SiteHeader).
   IMAGE : 32 / 44 / 64 px (HELO 74977, beta.13 — 24 / 32 / 48 avant : « grand »
   restait petit pour un logo qui contient du texte, cas fréquent des artisans).
   La migration 15.2.0 du fichier de projet garde à chaque site sa taille
   (M → S, L → M). Sur téléphone, les deux plus grands se resserrent pour
   laisser la place au bouton du menu. TEXTE : échelle inchangée. */
.site-header-block__logo--small { font-size: 1rem; }
.site-header-block__logo--small .site-header-block__logo-img { height: 2rem; }
.site-header-block__logo--medium { font-size: 1.25rem; }
.site-header-block__logo--medium .site-header-block__logo-img { height: 2.75rem; }
/* 2,25rem et non plus 1,75 : « grand », pour un logo TEXTE, doit tenir lieu
   d'enseigne. Les logos texte des sites V14 (« Restaurant » du modèle V5)
   faisaient ~45 px ; à 28 px l'import les réduisait à une étiquette (scan
   visuel du 2026-09-25). Sans effet sur un logo image, qui a sa hauteur. */
.site-header-block__logo--large { font-size: 2.25rem; }
.site-header-block__logo--large .site-header-block__logo-img { height: 4rem; }
@media (max-width: 640px) {
  .site-header-block__logo--medium .site-header-block__logo-img { height: 2.25rem; }
  .site-header-block__logo--large .site-header-block__logo-img { height: 2.75rem; }
}

/* Logo du MODE SOMBRE (`navigation.logo.imageDark`) : les deux sont émis, la
   feuille montre celui du mode courant. `data-theme` est posé sur <html> (site
   publié) et sur le canevas de l'éditeur. */
.site-header-block__logo-img--sombre { display: none; }
[data-theme="dark"] .site-header-block__logo-img--clair { display: none; }
[data-theme="dark"] .site-header-block__logo-img--sombre { display: inline-block; }

/* HELO 74915 / 74950 — L'IMAGE ET LE NOM côte à côte (`logo.showTextWithImage`),
   comme WordPress et Squarespace : l'image à gauche, le nom à droite, centrés
   sur la même ligne. L'écart suit la densité du DNA (une fraction de son
   `--tw-gap`, borné) ; la typographie du nom est celle que le DNA donne déjà à
   `.site-header-block__logo-text`. En « grand », le nom descend d'un cran :
   c'est l'image qui reste l'emblème. Le logo sombre suit la règle ci-dessus. */
.site-header-block__logo--with-name {
  display: inline-flex;
  align-items: center;
  gap: clamp(0.5rem, calc(var(--tw-gap, 1.5rem) * 0.4), 1rem);
  min-width: 0;
}
.site-header-block__logo--with-name .site-header-block__logo-img { flex-shrink: 0; }
.site-header-block__logo--with-name .site-header-block__logo-text { min-width: 0; line-height: 1.15; }
.site-header-block__logo--large.site-header-block__logo--with-name { font-size: 1.75rem; }
/* Recette beta.12 — les ORNEMENTS de nom des DNA (le point d'ATELIER, de
   RIVIERA, de BOTANIQUE, le « $ » de TERMINAL…) décorent un nom écrit. Posés
   à côté d'une IMAGE, ils passaient à la ligne sous elle : un point orphelin
   et un en-tête plus haut. Une image de logo les retire, nom affiché ou non.
   (Spécificité au-dessus de `[data-dna="…"] .site-header-block__logo::after`.) */
.site-header-block .site-header-block__logo:has(.site-header-block__logo-img)::before,
.site-header-block .site-header-block__logo:has(.site-header-block__logo-img)::after { content: none; }

/* Bouton d'appel (`navigation.cta`) : une brique .tw-button, donc la forme du
   DNA. Il ne passe pas à la ligne, et sous 640 px il quitte l'en-tête pour le
   tiroir mobile, où il est repris en pleine largeur. */
.site-header-block__cta { white-space: nowrap; flex-shrink: 0; }
/* ⚠️ SUR TÉLÉPHONE, C'EST LE BOUTON QUI CÈDE, JAMAIS LE LOGO (démo « Maison Kafé »,
   2026-09-26) : « Réserver un atelier » + son icône gardait toute sa largeur, et le
   nom du site, seul élément compressible, passait à la ligne LETTRE PAR LETTRE —
   un en-tête de ~350 px de haut. Le masquage sous 640 px ne jouait plus pour un
   bouton À ICÔNE : `.tw-button--icone { display: inline-flex }`, plus loin dans
   cette feuille, le rétablissait (même spécificité, plus tard). Désormais :
   sans icône, il part au tiroir (inchangé) ; avec icône, son libellé s'abrège
   (points de suspension), puis sous 420 px il ne reste que l'icône — le libellé
   reste lu par les lecteurs d'écran.
   Pourquoi une largeur BORNÉE plutôt qu'un `flex-shrink` : l'en-tête est en
   `flex-wrap` (#155), et une ligne se coupe sur la taille HYPOTHÉTIQUE des
   éléments, avant tout rétrécissement — un bouton pleine largeur passait donc
   à la ligne, ou comprimait le logo, sans jamais rétrécir lui-même. 24rem ≈ le
   logo, les icônes, le burger et les marges d'un en-tête de téléphone. */
@media (max-width: 767px) {
  .site-header-block__cta .tw-button__libelle {
    max-width: max(3.5em, 100vw - 24rem);
    min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
}
@media (max-width: 640px) { .site-header-block .site-header-block__cta:not(.tw-button--icone) { display: none; } }
@media (max-width: 420px) {
  .site-header-block .site-header-block__cta.tw-button--icone { gap: 0; padding-inline: 0.6em; }
  .site-header-block__cta .tw-button__libelle {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap;
  }
}
.site-nav-drawer__cta-zone { padding: 1rem 1.25rem 0; }
.site-nav-drawer__cta { display: flex; justify-content: center; width: 100%; }

/* Nav container */
.site-header-block__nav {
  display: flex;
  align-items: center;
  flex: 1;
  /* #155 : sans ce min-width, `flex: 1` vaut `flex: 1 1 0%` avec un min-width AUTO —
     la nav ne pouvait donc jamais descendre sous son min-content (la somme des liens
     en nowrap : 226px mesurés pour 3 liens), et poussait le cluster d'actions hors de
     l'écran. Avec 0, elle se comprime et le menu se replie (règle suivante). */
  min-width: 0;
  justify-content: flex-end;
}

/* Menu list — gap is set inline via linkSpacing */
.site-header-block__menu {
  display: flex;
  align-items: center;
  /* #155 : les liens passent à la ligne plutôt que de forcer une largeur infranchissable.
     Indispensable pour un menu fourni : même replié, le header ne doit jamais imposer
     de défilement horizontal au site. */
  flex-wrap: wrap;
  gap: 1.25em;
  list-style: none;
  margin: 0;
  padding: 0;
}
/* The editor's `.page-tiptap-editor__content .ProseMirror ul` (0,2,1) re-arms
   `list-style-type: disc` + `padding-left: 1.5rem` and leaks into the header menu
   (Yann #113: bullets in the editor, absent in publish). A 3-class selector (0,3,0)
   + emptied ::marker kills it for both the menu and dropdown lists. */
.site-header-block .site-header-block__nav .site-header-block__menu,
.site-header-block .site-header-block__nav .site-header-block__dropdown {
  list-style: none;
  padding-inline-start: 0;
  margin: 0;
}
.site-header-block__menu > li::marker,
.site-header-block__dropdown > li::marker {
  content: '';
}

/* Menu item */
.site-header-block__item {
  position: relative;
}

/* Menu link — uses section link color (matches .toweb-section a rule in Astro theme.css) */
/* Header links are spans in the editor / real <a> in publish (R.6 one engine) —
   never browser-underlined; pointer only for the real links. */
.site-header-block__logo,
.site-header-block__link,
.site-header-block__dropdown-link,
.site-header-block__mega-link {
  text-decoration: none;
}
a.site-header-block__link,
a.site-header-block__dropdown-link,
a.site-header-block__mega-link,
a.site-header-block__logo { cursor: pointer; }

.site-header-block__link {
  font-size: 0.875rem;
  /* Neutral default now that link weight is DNA-owned (no inline style from the
     builder). The DNA skin overrides this per its type system. */
  font-weight: 500;
  /* Token-driven, NOT the accent: the DNA `.tw-nav-link` skin sets --tw-nav-color
     (default = --tw-text). Forcing the accent here coloured every DNA's nav with
     its accent and broke palettes that ration the accent (e.g. D = "max 4 amber").
     The brick `.tw-nav-link:hover` (0,1,1) still wins for the hover-accent via
     --tw-nav-color-hover; no `--hover-color` modifier class is emitted. */
  color: var(--tw-nav-color, var(--tw-text, #334155));
  /* Plus d'opacité : posée sur la couleur ATTÉNUÉE que dix-neuf DNA donnent à leur
     menu, elle le faisait passer sous 4,5:1 (3,6 à 4,4 — axe-core, audit du
     2026-09-28). La retenue du menu est dans sa couleur, que le DNA choisit. */
  cursor: default;
  white-space: nowrap;
  transition: opacity 0.15s, color 0.15s, background 0.15s;
  position: relative;
  padding: 0.2em 0;
}

/* Hover effects — applied via modifier class on the root block */
.site-header-block--hover-underline .site-header-block__link:hover {
  opacity: 1;
}
.site-header-block--hover-underline .site-header-block__link:hover::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 1px;
  background: currentColor;
  opacity: 0.6;
}
.site-header-block--hover-background .site-header-block__link:hover {
  opacity: 1;
  background: rgba(0,0,0,0.05);
  border-radius: 3px;
  padding: 0.2em 0.5em;
  margin: -0.2em -0.5em;
}
.site-header-block--hover-color .site-header-block__link:hover {
  opacity: 1;
  color: var(--tw-accent, hsl(222.2, 84%, 55%));
}

/* Separator between menu items (·, |, /) */
.site-header-block__separator {
  color: var(--tw-text, #334155);
  opacity: 0.3;
  font-size: 0.75rem;
  user-select: none;
  pointer-events: none;
  line-height: 1;
}

/* Chevron for items with children */
.site-header-block__chevron {
  margin-inline-start: 0.25em;
  font-size: 0.625rem;
  opacity: 0.5;
}

/* Empty state */
.site-header-block__empty {
  font-size: 0.75rem;
  font-style: italic;
  color: var(--tw-text, #64748b);
  opacity: 0.5;
}

/* ─── Dropdown (recursive flyout) ─── */
.site-header-block__dropdown {
  display: none;
  position: absolute;
  top: 100%;
  inset-inline-start: 0;
  background: var(--tw-bg, #fff);
  border: 1px solid var(--tw-border, rgba(0,0,0,0.08));
  border-radius: 6px;
  box-shadow: 0 4px 12px rgba(0,0,0,0.1);
  padding: 0.5rem 0;
  min-width: 180px;
  z-index: 10;
  list-style: none;
  margin: 0;
}
/* Nested flyout: opens to the left to stay within editor viewport */
.site-header-block__dropdown--nested {
  top: -0.4em;
  inset-inline-end: 100%;
  inset-inline-start: auto;
  margin-inline-end: 2px;
}
/* Invisible hover bridge — prevents dropdown from closing when moving cursor */
.site-header-block__dropdown::before {
  content: '';
  position: absolute;
  top: -10px;
  left: 0;
  right: 0;
  height: 10px;
}
.site-header-block__dropdown--nested::before {
  top: 0;
  inset-inline-start: auto;
  inset-inline-end: -10px;
  width: 10px;
  height: 100%;
}
.site-header-block__item--has-children:hover > .site-header-block__dropdown,
.site-header-block__item--has-children.site-header-block__item--open > .site-header-block__dropdown {
  display: block;
}
/* `--open` est posé par smart-dropdown.ts (site publié) quand le focus clavier
   entre dans l'entrée : Tab ouvre, Échap ferme. Le survol reste en CSS pur —
   sans script, la souris marche encore. */
/* Le dropdown du DERNIER item top-level (souvent « Plus… » d'overflow, le plus à droite)
   s'aligne à DROITE pour ne pas être tronqué par le bord droit du viewport (Yann #79). */
.site-header-block__menu > .site-header-block__item:last-child > .site-header-block__dropdown {
  inset-inline-start: auto;
  inset-inline-end: 0;
}

/* Dropdown items (all levels) */
.site-header-block__dropdown .site-header-block__item {
  position: relative;
}
.site-header-block__dropdown-link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.4rem 1rem;
  font-size: 0.8125rem;
  color: var(--tw-accent, var(--tw-text, #334155));
  white-space: nowrap;
  cursor: default;
  transition: background 0.1s;
}
.site-header-block__dropdown-link:hover {
  background: var(--tw-surface, rgba(0,0,0,0.04));
}
/* Chevron in dropdown items — right-pointing arrow */
.site-header-block__dropdown-link .site-header-block__chevron {
  margin-inline-start: 0.75em;
  font-size: 0.5rem;
  opacity: 0.4;
}

/* ─── Mega-menu (columns — when children have sub-pages) ─── */
.site-header-block__mega {
  display: none;
  position: absolute;
  top: 100%;
  inset-inline-end: 0;
  background: var(--tw-bg, #fff);
  border: 1px solid var(--tw-border, rgba(0,0,0,0.08));
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.12);
  padding: 1.25rem;
  z-index: 10;
  /* grid-template-columns set inline by JS based on actual column count */
  gap: 1.25rem;
}
/* Invisible hover bridge — prevents menu from closing when moving cursor down */
.site-header-block__mega::before {
  content: '';
  position: absolute;
  top: -12px;
  left: 0;
  right: 0;
  height: 12px;
}
.site-header-block__item--has-mega:hover > .site-header-block__mega,
.site-header-block__item--has-mega.site-header-block__item--open > .site-header-block__mega {
  display: grid;
}
/* Garde-fou sans script : un panneau à 5 colonnes fait ~820 px ; sur une
   tablette en paysage (768–1023, nav visible) il déborderait. Le script pose un
   max-width mesuré ; celui-ci évite le défilement horizontal en attendant. */
.site-header-block__mega {
  max-width: calc(100vw - 2rem);
  box-sizing: border-box;
  /* Un menu V14 à trois niveaux (campingcarsite.fr, HELO 74831 : des dizaines
     de rubriques par région) donnait un panneau plus haut que la page. Il
     défile en lui-même ; le pont de survol passe alors sur l'entrée, que le
     défilement rognerait sur le panneau. */
  max-height: calc(100vh - 8rem);
  max-height: calc(100dvh - 8rem);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.site-header-block__mega::before { content: none; }
.site-header-block__item--has-mega:hover::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  height: 12px;
}

.site-header-block__mega-col {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.2em;
}

/* Featured column: root item OG image (large, left side) */
.site-header-block__mega-featured {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.35em;
}
.site-header-block__mega-featured-img {
  width: 100%;
  max-height: 180px;
  object-fit: cover;
  border-radius: 6px;
  display: block;
}

/* Group column thumbnail (small, above heading) */
.site-header-block__mega-thumb {
  width: 80px;
  height: 42px;
  object-fit: cover;
  border-radius: 4px;
  margin-bottom: 0.35em;
  display: block;
}

.site-header-block__mega-heading {
  font-size: 0.7rem;
  font-weight: 600;
  color: var(--tw-accent, var(--tw-text-heading, #1e293b));
  padding-bottom: 0.35em;
  margin-bottom: 0.15em;
  border-bottom: 1px solid var(--tw-rule, rgba(0,0,0,0.08));   /* DNA-aware (invisible sur DNA sombre en dur) */
  white-space: nowrap;
}

.site-header-block__mega-link {
  font-size: 0.7rem;
  color: var(--tw-accent, var(--tw-text, #334155));
  opacity: 0.75;
  white-space: nowrap;
  padding: 0.15em 0;
  cursor: default;
  transition: opacity 0.1s;
}
.site-header-block__mega-link:hover {
  opacity: 1;
}
/* Variante « colonnes » (enfants plats tranchés — collections d'une boutique) :
   ce sont les liens d'un déroulant, à la taille d'un déroulant (0.8125rem), pas
   les sous-entrées d'un groupe (0.7rem). Ils passent à la ligne au lieu d'être
   coupés quand le panneau est contraint en largeur. */
.site-header-block__mega--columns .site-header-block__mega-link {
  font-size: 0.8125rem;
  opacity: 0.85;
  padding: 0.25em 0;
  white-space: normal;
  overflow-wrap: anywhere;
}
.site-header-block__mega--columns .site-header-block__mega-link:hover {
  opacity: 1;
}
/* Variante « groupes » (#416) : intertitres et liens passent eux aussi à la
   ligne. Sans retour, une colonne contrainte laissait déborder ses libellés
   (« Vie Religieuse », « Eglise et Chapelles ») sur la colonne voisine. */
.site-header-block__mega--groups .site-header-block__mega-heading,
.site-header-block__mega--groups .site-header-block__mega-link {
  white-space: normal;
  overflow-wrap: anywhere;
}

/* Focus visible sur les liens de navigation — même motif que les autres
   focalisables de cette feuille (anneau accent, décalé). Avant : aucun anneau
   propre, le navigateur posait le sien ou rien selon le DNA. */
.site-header-block__link:focus-visible,
.site-header-block__group-label:focus-visible,
.site-header-block__dropdown-link:focus-visible,
.site-header-block__mega-link:focus-visible {
  outline: 2px solid var(--tw-accent, #3b82f6);
  outline-offset: 2px;
  border-radius: 2px;
}

/* ── Utility cluster (#69 — theme · search · account · currency · cart) ──
   Markup from buildHeaderActionsHtml — rendered by BOTH engines (publish +
   editor NodeView, WYSIWYG parity; the editor variant is inert, see
   blocks-editor.css). Icons are inline SVG (currentColor) so the cluster
   follows the header text colour in every theme/DNA. Neutral gray-alpha hover
   works on light AND dark headers. */
.site-header-block__actions {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  flex-shrink: 0;
}
/* Alpine x-cloak (badge, dark icon) must stay hidden until Alpine boots. */
.site-header-block [x-cloak] { display: none !important; }

.site-header-block__action {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  padding: 0;
  background: transparent;
  border: none;
  border-radius: 8px;
  color: var(--tw-nav-color, inherit);
  cursor: pointer;
  transition: background-color 150ms ease, color 150ms ease;
  flex-shrink: 0;
  text-decoration: none;
}
.site-header-block__action:hover {
  background-color: rgba(128, 128, 128, 0.15);
  color: var(--tw-nav-color-hover, var(--tw-nav-color, inherit));
}
.site-header-block__action:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}
.site-header-block__action svg { display: block; }
/* Le lien compte est un <a> : forcer la couleur du cluster (comme les boutons du header),
   sinon il hérite la couleur de lien du thème DNA et ressort en bleu. Polish #69. */
.site-header-block__actions a.site-header-block__account { color: var(--tw-nav-color, inherit) !important; }
.site-header-block__actions a.site-header-block__account:hover { color: var(--tw-nav-color-hover, var(--tw-nav-color, inherit)) !important; }

.site-header-block__cart-badge {
  position: absolute;
  top: 1px;
  inset-inline-end: 1px;
  min-width: 17px;
  height: 17px;
  padding: 0 4px;
  background: var(--tw-accent, #2563eb);
  color: var(--tw-on-accent, #fff);
  font-size: 10.5px;
  font-weight: 600;
  line-height: 17px;
  text-align: center;
  border-radius: 9px;
  box-sizing: border-box;
  font-family: var(--theme-font-body, system-ui, sans-serif);
}

/* Compact currency selector — same Alpine store as the floating switcher. */
.site-header-block__currency-wrap {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  margin: 0 0.15rem;
}
.site-header-block__currency {
  padding: 0.25rem 0.4rem;
  border-radius: var(--tw-radius-field, 8px);
  border: 1px solid var(--tw-border, rgba(128, 128, 128, 0.3));
  background: transparent;
  color: inherit;
  font-size: 0.8rem;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}
/* Native dropdown list needs a solid, readable surface in dark headers. */
.site-header-block__currency option {
  background: var(--tw-surface, #fff);
  color: var(--tw-text, #0f172a);
}

/* Le burger n'existe QUE sous le point de rupture — au-dessus, la nav est là. */
/* ══ SÉLECTEUR DE LANGUE (#38) ════════════════════════════════════════════
 *
 * ⚠️ IL ÉTAIT DANS `BaseLayout.astro` ET N'HÉRITAIT DE RIEN. Yann l'a vu sur
 * DNA-SPRINT le 2026-09-18 : mon CSS y inventait ses couleurs
 * (`--tw-surface`, `--tw-accent-soft`) au lieu d'employer les jetons du thème,
 * et le menu jurait avec le reste de l'en-tête.
 *
 * Il vit donc ICI, à côté des autres actions, où les jetons DNA sont en portée.
 * La poignée EST une `site-header-block__action` : elle hérite de la taille, du
 * rayon, du survol et de la couleur de navigation du bouton thème — donc du
 * DNA, quel qu'il soit. C'est la seule façon de ne pas avoir à suivre 48 DNAs
 * à la main.
 */
.site-header-block__langues { position: relative; flex-shrink: 0; }
.site-header-block__langues > summary {
  width: auto;
  gap: 0.3rem;
  padding: 0 0.45rem;
  list-style: none;
  cursor: pointer;
}
.site-header-block__langues > summary::-webkit-details-marker { display: none; }
.site-header-block__langues-code { font-size: 0.72rem; font-weight: 700; letter-spacing: 0.02em; }
/* ⚠️ CE PANNEAU EST UN `site-header-block__dropdown`, et il ne redéclare donc
   NI fond, NI filet, NI rayon, NI ombre, NI police : 41 des 52 DNAs habillent
   cette classe, et c'est là que le thème arrive. Ce bloc ne pose que ce que le
   sous-menu ne sait pas : l'ALIGNEMENT À DROITE (la poignée est au bout de la
   barre — ouvert à gauche, le panneau sortirait de l'écran) et l'OUVERTURE par
   `[open]`, puisque c'est un `<details>` et non un survol de `<li>`.

   Il redéclarait tout, avec des jetons de son cru. Résultat sur DNA-SPRINT
   (capture de Yann, 2026-09-18) : coins arrondis et ombre douce sous un
   en-tête à angles vifs, et deux noirs différents — `--tw-surface` est le fond
   des CARTES, `--tw-bg` celui de la BANDE. Le panneau n'appartenait à rien. */
.site-header-block__langues-liste {
  inset-inline-end: 0;
  inset-inline-start: auto;
  margin-top: 0.35rem;
  min-width: 9.5rem;
  z-index: 40;
}
.site-header-block__langues[open] > .site-header-block__langues-liste { display: block; }
/* Le lien est un `__dropdown-link tw-nav-link` : padding, taille, couleur et
   survol viennent de là, donc du DNA. Seul l'état courant est à nous. */
.site-header-block__langue[aria-current='true'] { font-weight: 700; }
/* « FR | EN » en clair (2 ou 3 langues) : les codes côte à côte, un filet
   fin entre eux, la langue courante en gras et soulignée. Couleur et police
   viennent de `tw-nav-link`, donc du DNA. */
.site-header-block__langues--en-ligne { display: inline-flex; align-items: center; }
.site-header-block__langue-code {
  padding: 0.25rem 0.45rem;
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-decoration: none;
  opacity: 0.72;
}
.site-header-block__langue-code + .site-header-block__langue-code { border-inline-start: 1px solid currentColor; }
.site-header-block__langue-code:hover,
.site-header-block__langue-code:focus-visible { opacity: 1; }
.site-header-block__langue-code[aria-current='true'] { opacity: 1; font-weight: 700; text-decoration: underline; text-underline-offset: 0.3em; text-decoration-thickness: 2px; }

/* Dans le tiroir, la liste est à PLAT : pas de place pour un menu qui se
   superpose, et le tiroir est déjà un menu.
   ⚠️ CACHER LA POIGNÉE NE SUFFISAIT PAS — ça la RETIRAIT. Un `<details>` fermé
   ne rend pas son contenu, donc la liste était dans le document, invisible et
   sans plus aucun moyen de l'ouvrir : Yann n'avait de sélecteur ni dans la
   barre ni dans le tiroir. Le builder émet maintenant `open` pour le tiroir ;
   cette règle ne fait plus que retirer une poignée devenue inutile. */
.site-nav-drawer__langues > summary { display: none; }
.site-nav-drawer__langues-liste { display: flex; flex-wrap: wrap; gap: 0.3rem; }
.site-nav-drawer__langue {
  padding: 0.35rem 0.6rem;
  border: 1px solid var(--tw-border, rgba(0, 0, 0, 0.18));
  border-radius: var(--tw-radius-field, 6px);
  color: inherit;
  font-size: 0.85rem;
  text-decoration: none;
}
.site-nav-drawer__langue[aria-current='true'] { font-weight: 700; }

.site-header-block__burger { display: none; }

/* #156 — point de rupture du header : 767/768, et non plus 640.
   Le 640 n'était pas une constante de design : c'était le correctif d'overflow
   #155, dont les règles (nav pleine largeur, clamps de déroulants) n'existaient
   que parce que la nav restait VISIBLE. Le burger remplace ce régime, donc ces
   règles partent avec lui. À 640 il serait resté une zone 641–767 où la nav
   visible replie encore sur 2–3 lignes avec un menu de 8–10 entrées : le grief
   exact de #156, sur petites tablettes et téléphones en paysage. 768 est
   l'extrémité conservatrice du standard (Wix/Squarespace ~750, Shopify Dawn 990)
   et rejoint la famille 767/768/1024 que cette feuille possède déjà (colonnes
   .gallery-block). Décision Fable 5, déléguée par Yann.
   Le footer garde 640 : sa vraie raison est sa largeur de colonne, pas
   l'alignement sur le header.

   #352 — CE SEUIL EST DEVENU UN RÉGLAGE (« Seuil mobile » : 640, 768, 1024, et
   « Menu burger : toujours »). 768 reste le défaut, et une racine SANS classe
   `--burger-*` (balisage antérieur) le reçoit aussi : `:not(--burger-640)`.
   La classe est posée par `reglagesMenuMobile` (buildHeaderHtml.ts), qui pose
   la même valeur en `data-breakpoint` sur le tiroir pour alpine-nav — la
   feuille et le script lisent donc UNE valeur. Une media query ne lit pas de
   variable CSS : d'où une règle par seuil, et trois seuils seulement.

   Deux régimes distincts, qu'il ne faut plus confondre :
   1. « LE MENU EST DERRIÈRE LE BURGER » — suit le réglage (règles ci-dessous) ;
   2. « L'EN-TÊTE EST CELUI D'UN TÉLÉPHONE » (icônes compactes, devise et
      langues reprises dans le tiroir, nom de site qui passe à la ligne) —
      suit la LARGEUR (767) ET la présence du burger : sans burger (seuil 640,
      entre 640 et 767) il n'y a pas de tiroir, donc rien à y reprendre. */
.site-header-block--burger-always .site-header-block__nav { display: none; }
.site-header-block--burger-always .site-header-block__burger { display: inline-flex; }
@media (max-width: 1023px) {
  .site-header-block--burger-1024 .site-header-block__nav { display: none; }
  .site-header-block--burger-1024 .site-header-block__burger { display: inline-flex; }
}
@media (max-width: 767px) {
  .site-header-block__actions { gap: 0; }
  .site-header-block__action { width: 34px; height: 34px; }
  /* Le menu passe derrière le burger. Le balisage ne bouge pas : le panneau
     mobile est une SECONDE liste, montée à hauteur de <body> par BaseLayout. */
  .site-header-block:not(.site-header-block--burger-640) .site-header-block__nav { display: none; }
  .site-header-block:not(.site-header-block--burger-640) .site-header-block__burger { display: inline-flex; }
  /* Seule action non-icône du cluster (~65px) : elle déborderait à 320px avec
     4 icônes + burger. Elle est rendue une seconde fois DANS le panneau. */
  .site-header-block:not(.site-header-block--burger-640) .site-header-block__currency-wrap { display: none; }
  /* ⚠️ ET LE SÉLECTEUR DE LANGUE AUSSI. Yann, 2026-09-18 : « je le vois
     omniprésent […] le menu de choix d'une langue n'a aucune raison d'être
     visible en permanence ». Il est rendu une seconde fois DANS le panneau, à
     plat — c'est exactement le traitement de la devise, pour la même raison :
     une action non-icône qui déborderait à 320 px. */
  .site-header-block:not(.site-header-block--burger-640) .site-header-block__langues { display: none; }
  /* Lignes compactes une fois replié (cf. la règle de base : le row-gap n'est resserré
     QUE sur mobile, pour ne pas toucher au rendu desktop du logo centré). */
  .site-header-block:not(.site-header-block--burger-640) { row-gap: 0.5rem; }
  /* Un NOM de site long (« AQUARELLES DE FRANCOIS AMOUDRY », site client V14
     importé) passe à la ligne au lieu d'imposer sa largeur : en `nowrap` et sans
     rétrécir, il repoussait les icônes sous lui et la page défilait de 29 px.
     Base 0 : le repli du header se décide sur elle, les icônes restent à droite
     sur la même ligne. Le logo centré garde son propre repli.
     ⚠️ Mais entre les MOTS seulement, et jamais plus étroit que son mot le plus
     long (`min-width: min-content`) : `overflow-wrap: anywhere` + `min-width: 0`
     laissaient un bouton d'appel large écraser « Maison Kafé » en une lettre par
     ligne (démo, 2026-09-26). C'est le bouton qui rétrécit (règle plus haut). */
  .site-header-block:not(.site-header-block--burger-640) > .site-header-block__logo { white-space: normal; min-width: min-content; overflow-wrap: normal; flex-shrink: 0; }
  .site-header-block:not(.site-header-block--logo-center):not(.site-header-block--burger-640) > .site-header-block__logo { flex: 1 0 0; }
}
/* Seuil 640 : les mêmes règles, 128 px plus bas. */
@media (max-width: 639px) {
  .site-header-block--burger-640 .site-header-block__nav { display: none; }
  .site-header-block--burger-640 .site-header-block__burger { display: inline-flex; }
  .site-header-block--burger-640 .site-header-block__currency-wrap { display: none; }
  .site-header-block--burger-640 .site-header-block__langues { display: none; }
  .site-header-block--burger-640 { row-gap: 0.5rem; }
  .site-header-block--burger-640 > .site-header-block__logo { white-space: normal; min-width: min-content; overflow-wrap: normal; flex-shrink: 0; }
  .site-header-block--burger-640:not(.site-header-block--logo-center) > .site-header-block__logo { flex: 1 0 0; }
}
/* HELO 74915 / 74950 — l'image et le nom sur un téléphone. Le logo n'est plus
   un mot qu'on ne coupe pas : c'est le NOM qui cède. Il rétrécit d'un cran et
   tient sur deux lignes au plus (points de suspension au-delà), l'image ne
   bouge pas ; sous 360 px, il sort de l'écran mais reste le nom du lien pour un
   lecteur d'écran (l'image, elle, est décorative). Placé APRÈS les deux blocs
   ci-dessus, à spécificité égale ou supérieure : il les surcharge. */
@media (max-width: 767px) {
  .site-header-block > .site-header-block__logo.site-header-block__logo--with-name { min-width: 0; flex-shrink: 1; white-space: normal; }
  .site-header-block:not(.site-header-block--logo-center) > .site-header-block__logo.site-header-block__logo--with-name { flex: 1 1 0; }
  .site-header-block__logo--with-name .site-header-block__logo-text {
    font-size: 0.85em;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
  }
}
@media (max-width: 359px) {
  .site-header-block__logo--with-name .site-header-block__logo-text {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}
/* Burger à toutes les largeurs, au-dessus du téléphone : la nav cède sa place,
   le logo prend la sienne et le groupe d'actions reste au bout de la ligne
   (`space-between` de la racine). Logo centré : il garde le centre, le groupe
   d'actions part au bout de la ligne — sans ça les deux se collaient au milieu. */
@media (min-width: 768px) {
  .site-header-block--burger-always.site-header-block--logo-center { justify-content: space-between; }
  .site-header-block--burger-always.site-header-block--logo-center > .site-header-block__logo { margin-inline-start: auto; }
  .site-header-block--burger-always.site-header-block--logo-center > .site-header-block__actions { margin-inline-start: auto; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   #156 — PANNEAU DE NAVIGATION MOBILE (.site-nav-drawer)

   Espace de noms NEUF, hors de `site-header-block__*` : 39 des 46 DNAs habillent
   `__dropdown` pour un survol DESKTOP (ombres portées, positionnement absolu,
   ouverture vers le bas). Réutiliser cette classe dans une pile verticale
   donnerait 39 rendus imprévisibles, invérifiables sans ouvrir les 46. Ici rien
   n'est hérité d'un DNA : l'accord avec le thème passe par les jetons de brique,
   donc le panneau est juste sur header clair comme sombre par défaut.

   Le bouton burger, LUI, reste `site-header-block__action` — il doit hériter du
   traitement du cluster que les DNAs connaissent déjà.

   Monté à hauteur de <body> (slot `nav-drawer`), jamais dans le header : le DNA
   « p » pose `position: relative` sur `.site-header-block > *`, ce qui écraserait
   le `position: fixed` ci-dessous. Mêmes étages z-index que le drawer du panier —
   ils ne se superposent jamais, l'ouverture de l'un fermant l'autre.
   ═══════════════════════════════════════════════════════════════════════════ */

.site-nav-drawer__backdrop {
  position: fixed;
  inset: 0;
  z-index: 9999;
  background: rgba(0, 0, 0, 0.4);
}

.site-nav-drawer {
  position: fixed;
  top: 0;
  inset-inline-start: 0;
  bottom: 0;
  z-index: 10000;
  width: 100%;
  max-width: 340px;
  display: flex;
  flex-direction: column;
  background: var(--tw-surface, #fff);
  color: var(--tw-text, #0f172a);
  box-shadow: 4px 0 24px rgba(0, 0, 0, 0.1);
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* Ouverture depuis la GAUCHE : la nav à gauche, le commerce (panier, favoris) à
   droite — répartition Shopify Dawn, et plus de conflit de côté entre les deux. */
.site-nav-drawer--enter,
.site-nav-drawer--leave {
  transition: transform 0.25s ease;
}
.site-nav-drawer--enter-start,
.site-nav-drawer--leave-end {
  transform: translateX(-100%);
}
.site-nav-drawer--enter-end,
.site-nav-drawer--leave-start {
  transform: translateX(0);
}
/* RTL : le tiroir s'ouvre du côté du DÉBUT de ligne — la droite. */
[dir="rtl"] .site-nav-drawer { box-shadow: -4px 0 24px rgba(0, 0, 0, 0.1); }
[dir="rtl"] .site-nav-drawer--enter-start,
[dir="rtl"] .site-nav-drawer--leave-end {
  transform: translateX(100%);
}

/* #352 — « Ouverture : Droite » : le tiroir part de la FIN de ligne (la gauche
   en RTL). Même panneau, même piège de focus ; seuls le bord, l'ombre et le
   sens du glissement changent. Déclarées APRÈS les règles RTL de base, à
   spécificité égale ou supérieure : c'est l'ordre qui tranche. */
.site-nav-drawer--end {
  inset-inline-start: auto;
  inset-inline-end: 0;
  box-shadow: -4px 0 24px rgba(0, 0, 0, 0.1);
}
[dir="rtl"] .site-nav-drawer--end { box-shadow: 4px 0 24px rgba(0, 0, 0, 0.1); }
.site-nav-drawer--end.site-nav-drawer--enter-start,
.site-nav-drawer--end.site-nav-drawer--leave-end {
  transform: translateX(100%);
}
[dir="rtl"] .site-nav-drawer--end.site-nav-drawer--enter-start,
[dir="rtl"] .site-nav-drawer--end.site-nav-drawer--leave-end {
  transform: translateX(-100%);
}

/* #352 — « Ouverture : Plein écran » : le menu devient la page (standard
   Squarespace / Apple sur mobile). Il couvre tout, sans voile ; il apparaît en
   fondu avec un léger glissement vertical — le seul mouvement qui ne dépend pas
   du sens de lecture. Le contenu tient dans une colonne de lecture centrée, en
   typographie de titre : sur 1440 px, un menu plein écran aligné à gauche en
   corps 15 ressemblerait à une liste oubliée. */
.site-nav-drawer--fullscreen {
  inset: 0;
  width: 100%;
  max-width: none;
  box-shadow: none;
}
.site-nav-drawer--fullscreen.site-nav-drawer--enter,
.site-nav-drawer--fullscreen.site-nav-drawer--leave {
  transition: opacity 0.22s ease, transform 0.22s ease;
}
.site-nav-drawer--fullscreen.site-nav-drawer--enter-start,
.site-nav-drawer--fullscreen.site-nav-drawer--leave-end,
[dir="rtl"] .site-nav-drawer--fullscreen.site-nav-drawer--enter-start,
[dir="rtl"] .site-nav-drawer--fullscreen.site-nav-drawer--leave-end {
  opacity: 0;
  transform: translateY(-0.75rem);
}
.site-nav-drawer--fullscreen.site-nav-drawer--enter-end,
.site-nav-drawer--fullscreen.site-nav-drawer--leave-start {
  opacity: 1;
  transform: none;
}
/* L'intitulé « Menu » s'aligne sur la colonne des liens (34rem centrée, liens
   rembourrés de 1rem) ; la croix reste au bout de la ligne, là où était le
   burger — le doigt qui a ouvert retrouve de quoi fermer au même endroit. */
.site-nav-drawer--fullscreen .site-nav-drawer__head {
  border-bottom-color: transparent;
  padding-inline-start: max(1rem, calc((100% - 34rem) / 2 + 1rem));
}
.site-nav-drawer--fullscreen .site-nav-drawer__nav,
.site-nav-drawer--fullscreen .site-nav-drawer__cta-zone,
.site-nav-drawer--fullscreen .site-nav-drawer__foot {
  width: 100%;
  max-width: 34rem;
  margin-inline: auto;
}
.site-nav-drawer--fullscreen .site-nav-drawer__nav { padding-block: 1rem 2rem; }
.site-nav-drawer--fullscreen .site-nav-drawer__link,
.site-nav-drawer--fullscreen .site-nav-drawer__toggle--full {
  min-height: 56px;
  font-family: var(--theme-font-heading, inherit);
  font-size: clamp(1.25rem, 1.05rem + 1vw, 1.75rem);
  line-height: 1.2;
}
.site-nav-drawer--fullscreen .site-nav-drawer__row .site-nav-drawer__toggle { min-height: 56px; }
.site-nav-drawer--fullscreen .site-nav-drawer__sublink { font-size: 1rem; min-height: 44px; }
.site-nav-drawer--fullscreen .site-nav-drawer__foot { border-top-color: transparent; }

.site-nav-drawer__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.875rem 1rem;
  border-bottom: 1px solid var(--tw-border, rgba(0, 0, 0, 0.12));
}
.site-nav-drawer__title {
  font-family: var(--theme-font-heading, inherit);
  font-weight: 600;
  font-size: 0.95rem;
}
.site-nav-drawer__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: inherit;
  cursor: pointer;
}

.site-nav-drawer__nav { padding: 0.5rem 0; }
.site-nav-drawer__list,
.site-nav-drawer__sub {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Une PAGE avec des enfants garde son libellé cliquable et ne déplie que par le
   chevron : sinon la page parente devient inatteignable au doigt. Un GROUPE, lui,
   n'est pas navigable — toute sa rangée déplie. */
.site-nav-drawer__row {
  display: flex;
  align-items: center;
}
/* `--tw-nav-color` est SÛR ici, et ça se mesure. Deux DNAs le repeignent en
   couleur de FOND (dna-v : `--tw-nav-color: var(--theme-background)`), ce qui
   serait invisible sur une surface claire — mais ces surcharges sont portées par
   `[data-palette="bold"]`, un attribut posé UNIQUEMENT sur des <section>. Le
   panneau vit à hauteur de <body>, hors de toute section : il reçoit donc la
   valeur de base du DNA, définie face à SA propre surface. Ne pas déplacer ce
   panneau dans une section sans refaire ce calcul. */
.site-nav-drawer__link,
.site-nav-drawer__sublink,
.site-nav-drawer__toggle {
  font-family: inherit;
  font-size: 0.95rem;
  color: var(--tw-nav-color, var(--tw-text, #0f172a));
  text-decoration: none;
}
/* ⚠️ `display: flex` n'est PAS décoratif ici : un <a> est `inline` par défaut, et
   sur un élément en ligne le rembourrage vertical ne pousse pas la hauteur de
   ligne. Sans lui, ces rangées mesuraient 26px — sous la cible tactile de 44px,
   et deux fois plus courtes que les rangées à chevron (mesuré 26 contre 48).
   `min-height` porte la garantie ; le rembourrage ne fait plus que respirer. */
.site-nav-drawer__link {
  display: flex;
  align-items: center;
  flex: 1;
  min-width: 0;
  min-height: 44px;
  padding: 0.5rem 1rem;
}
.site-nav-drawer__sublink {
  display: flex;
  align-items: center;
  min-height: 40px;
  padding: 0.4rem 1rem 0.4rem 2rem;
  font-size: 0.9rem;
  opacity: 0.85;
}
.site-nav-drawer__toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-width: 44px;
  min-height: 44px;
  /* Même rembourrage que le lien voisin : les deux moitiés d'une rangée à chevron
     doivent mesurer pareil, sinon la rangée dépasse ses voisines. */
  padding: 0.5rem 1rem;
  border: 0;
  background: transparent;
  cursor: pointer;
}
.site-nav-drawer__toggle--full {
  width: 100%;
  justify-content: space-between;
  text-align: start;
}
.site-nav-drawer__toggle svg {
  transition: transform 0.18s ease;
  flex-shrink: 0;
}
.site-nav-drawer__toggle[aria-expanded='true'] svg {
  transform: rotate(180deg);
}

/* Les groupes d'un méga-menu sont APLATIS en intertitres : pas de colonnes sur
   340px de large. */
.site-nav-drawer__group-title {
  padding: 0.75rem 1rem 0.25rem 1rem;
  font-family: var(--theme-font-heading, inherit);
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  opacity: 0.6;
}

.site-nav-drawer__foot {
  margin-top: auto;
  padding: 0.875rem 1rem;
  border-top: 1px solid var(--tw-border, rgba(0, 0, 0, 0.12));
}
.site-nav-drawer__currency {
  width: 100%;
  padding: 0.4rem 0.5rem;
  border: 1px solid var(--tw-border, rgba(0, 0, 0, 0.18));
  border-radius: 6px;
  background: transparent;
  color: inherit;
  font-family: inherit;
  font-size: 0.85rem;
  cursor: pointer;
}
.site-nav-drawer__currency option {
  background: var(--tw-surface, #fff);
  color: var(--tw-text, #0f172a);
}

@media (prefers-reduced-motion: reduce) {
  .site-nav-drawer--enter,
  .site-nav-drawer--leave,
  .site-nav-drawer--fullscreen.site-nav-drawer--enter,
  .site-nav-drawer--fullscreen.site-nav-drawer--leave,
  .site-nav-drawer__toggle svg {
    transition: none;
  }
}

/* Selection */
.site-header-block.ProseMirror-selectednode {
  outline: 2px solid var(--ring, hsl(222.2 84% 55%));
  outline-offset: 2px;
}

/* ═══════════════════════════════════════════════════════════════════════════
   SITE FOOTER BLOCK (.site-footer-block)
   Reactive NodeView — displays real footer data from the store.
   Colours use the DNA brick tokens (--tw-text/-surface/-border…) so the footer
   respects the chosen palette. Fallback values preserve the default dark look.
   ═══════════════════════════════════════════════════════════════════════════ */

.site-footer-block {
  display: flex;
  flex-direction: column;
  gap: 0.5em;
  width: 100% !important;
  min-width: 0;
  padding: var(--footer-py, 3rem) 1.25em 0.75rem 1.25em;
  background: var(--tw-bg, #1a1a2e);
  color: var(--tw-text, rgba(255, 255, 255, 0.85));
  /* Base top separator (in the footer's own text colour so it works on dark and
     light footers). DNA skins override it — dna-c §7 paints a warm hairline. */
  border-top: 1px solid color-mix(in srgb, currentColor 14%, transparent);
  cursor: pointer;
  user-select: none;
  min-height: 40px;
  box-sizing: border-box;
}

/* Grid of footer blocks */
.site-footer-block__grid {
  display: grid;
  /* #155 : minmax(0, …) et non `1fr` seul (= minmax(auto, 1fr)) — sinon chaque colonne
     refuse de descendre sous son min-content et le footer déborde du viewport (mesuré :
     9px de trop à 320px, donc défilement horizontal de la page). */
  grid-template-columns: var(--footer-cols-tpl, repeat(var(--footer-cols, 3), minmax(0, 1fr)));
  gap: 1.25em;
  /* Les colonnes s'empilent sur mobile. Il n'existait AUCUNE media query pour le
     footer : à 360px les 3 colonnes tombaient à ~93px chacune, avec un texte haché
     mot par mot. Ce n'était pas un parti pris de design, juste un oubli — d'où le
     point de rupture ici. Il RESTE à 640 depuis #156, qui a porté le header à 768 :
     la vraie raison de ce seuil est la largeur de colonne (3 × ~215px à 700px reste
     viable), pas l'alignement sur le header. */
  /* Full-width band, content centered + constrained (standard footer pattern) —
     replaces the old .toweb-container the publish component used, and applies in
     the editor too so both stay identical (R.6 one engine). */
  width: 100%;
  max-width: 1200px;
  margin-inline: auto;
}

@media (max-width: 640px) {
  .site-footer-block__grid { grid-template-columns: minmax(0, 1fr); }
  /* Les séparateurs verticaux sont des enfants de la grille : en colonne unique ils
     occuperaient chacun leur propre rangée (traits de 1px + gap doublé entre les
     colonnes empilées). On les masque (revue Fable 5). */
  .site-footer-block__col-divider { display: none; }
}
.site-footer-block__bottom {
  width: 100%;
  max-width: 1200px;
  margin-inline: auto;
}

/* Vertical divider between columns (divider: 'columns' mode) */
.site-footer-block__col-divider {
  width: 1px;
  background: var(--tw-rule, rgba(0, 0, 0, 0.12));
  align-self: stretch;
}

/* Column */
.site-footer-block__column {
  display: flex;
  flex-direction: column;
  gap: 0.25em;
}

/* Column heading */
.site-footer-block__heading {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--tw-text-heading, rgba(255, 255, 255, 0.9));
  margin: 0 0 0.9em 0;
}

/* ── Style du pied (`footer.style`, src/shared/style-du-pied.ts) ──────────────
   Posé À L'IDENTIQUE par l'éditeur et par le site publié (éditeur ≡ site), et
   PAR-DESSUS le DNA : sélecteurs doublés, (0,3,0) et plus, contre les
   `[data-dna] .site-footer-block…` (0,2,0) chargés après cette feuille.
   - titres : `normal` laisse le DNA ; `uppercase` met en capitales (l'espacement
     reste celui du DNA s'il en a un) ; `underline` souligne d'un filet ;
   - séparateur : `line` laisse le filet du DNA (ou celui de base) ; `subtle`
     l'adoucit ; `none` le retire ; `columns` le garde et trace un trait entre
     les colonnes. */
/* L'espacement des capitales, au plus bas (`:where`) : celui du DNA l'emporte. */
:where(.site-footer-block[data-heading-style="uppercase"]) .site-footer-block__heading { letter-spacing: 0.05em; }
.site-footer-block.site-footer-block[data-heading-style="uppercase"] .site-footer-block__heading {
  text-transform: uppercase;
}
.site-footer-block.site-footer-block[data-heading-style="underline"] .site-footer-block__heading {
  border-bottom: 1px solid color-mix(in srgb, currentColor 22%, transparent);
  padding-bottom: 0.3em;
}
.site-footer-block.site-footer-block[data-divider="none"] { border-top: 0; }
.site-footer-block.site-footer-block[data-divider="subtle"] {
  border-top: 1px solid color-mix(in srgb, currentColor 8%, transparent);
}

/* Links list — clean vertical nav, NEVER bulleted (footers don't use disc
   markers). Cover both the editor (.site-footer-block__links) and the published
   (.toweb-footer__links) namespace, and reset the <li> too so a leaking prose
   marker can't reintroduce bullets (Yann 2026-06-03: footer bullets "pas joli"). */
.site-footer-block__links,
.toweb-footer__links {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 0.4em;
}
/* `ul.` prefix → specificity (0,1,2) beats the editor's leaking `.ProseMirror li`
   (0,1,1) vertical margin that was puffing the footer nav apart (Yann #101). */
ul.site-footer-block__links > li,
ul.toweb-footer__links > li {
  list-style: none;
  margin: 0;
  padding: 0;
  line-height: 1.5;
}
ul.site-footer-block__links > li::marker,
ul.toweb-footer__links > li::marker {
  content: '';
}

/* Link item — never underlined (the editor renders a <span>, publish renders a
   real <a> which the browser would underline; force none so both match). */
.site-footer-block__link {
  font-size: 0.95rem;
  line-height: 1.5;
  /* Token-driven, not the accent (same rationale as the header link above):
     fall through to --tw-text so footer links don't inherit the DNA accent. */
  color: var(--tw-nav-color, var(--tw-text, rgba(255, 255, 255, 0.6)));
  text-decoration: none;
  cursor: default;
}
/* Published footer links are real <a> → clickable pointer (editor span stays default). */
a.site-footer-block__link { cursor: pointer; }
a.site-footer-block__link:hover { text-decoration: none; }
/* Kill the editor's leaking `.page-tiptap-editor__content .ProseMirror ul`
   `padding-left: 1.5rem` (0,2,1) on the footer nav — a 3-class selector (0,3,0)
   beats it without !important. Publish is already flush. */
.site-footer-block .site-footer-block__column .site-footer-block__links {
  list-style: none;
  padding: 0;
  margin: 0;
}

/* Navigable items in atom blocks — pointer cursor only when CMD/Ctrl held */
.toweb-ctrl-held .site-header-block__logo[data-nav-home],
.toweb-ctrl-held .site-header-block__link[data-page-id],
.toweb-ctrl-held .site-header-block__link[data-collection],
.toweb-ctrl-held .site-header-block__dropdown-link[data-page-id],
.toweb-ctrl-held .site-header-block__dropdown-link[data-collection],
.toweb-ctrl-held .site-header-block__mega-link[data-page-id],
.toweb-ctrl-held .site-header-block__mega-link[data-collection],
.toweb-ctrl-held .site-footer-block__link[data-page-id],
.toweb-ctrl-held .site-footer-block__link[data-nav-url],
.toweb-ctrl-held .cta-button[data-href^="#page-"],
.toweb-ctrl-held .cta-button[data-href^="/"] {
  cursor: pointer;
}

/* Text block */
.site-footer-block__text,
.toweb-footer__text {
  font-size: 0.95rem;
  color: var(--tw-text, rgba(255, 255, 255, 0.6));
  line-height: 1.6;
  margin: 0;
  /* Honour line breaks typed in the footer Text column textarea (Yann 2026-06-03). */
  white-space: pre-line;
}

/* Logo/image block */
.site-footer-block__logo {
  display: flex;
  flex-direction: column;
  gap: 0.35em;
}
/* (0,2,0) : le logo du pied porte aussi les classes de l'en-tête (bascule
   clair/sombre), dont la hauteur ne vaut que pour l'en-tête. */
.site-footer-block__logo .site-footer-block__logo-img {
  height: 2.75rem;
  width: auto;
  max-width: 100%;
  object-fit: contain;
  align-self: flex-start;
}
.site-footer-block__logo-name {
  font-weight: 700;
  font-size: 1.4rem;
  color: var(--tw-text-heading, rgba(255, 255, 255, 0.9));
  letter-spacing: -0.01em;
}

/* Social icons */
.site-footer-block__social {
  display: flex;
  gap: 0.35em;
  flex-wrap: wrap;
  margin-top: 0.5em;
}

.site-footer-block__social-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  color: var(--tw-text, rgba(255, 255, 255, 0.55));
  transition: opacity 0.15s;
}

.site-footer-block__social-badge:hover {
  opacity: 0.8;
}

/* SVG inside badge — fill the container */
.site-footer-block__social-badge > svg {
  width: 100%;
  height: 100%;
  display: block;
}

/* Newsletter placeholder */
.site-footer-block__newsletter {
  display: flex;
  gap: 0.35em;
  align-items: center;
}

.site-footer-block__input {
  flex: 1;
  padding: 0.25em 0.5em;
  /* DNA-driven : le footer n'est PAS forcément sombre (DNA-C = sable clair) → tokens
     contextuels, pas du blanc-alpha (qui devenait invisible sur fond clair). */
  border: 1px solid var(--tw-field-border, var(--tw-rule, rgba(0, 0, 0, 0.15)));
  border-radius: 4px;
  background: var(--tw-field-bg, var(--tw-surface, transparent));
  color: var(--tw-text, inherit);
  font-size: 0.625rem;
}

.site-footer-block__submit {
  padding: 0.25em 0.5em;
  border-radius: 4px;
  background: var(--tw-accent, #3b82f6);
  color: var(--tw-on-accent, #fff);
  font-size: 0.6875rem;
  font-weight: 600;
}

/* Bottom bar — copyright centered */
.site-footer-block__bottom {
  border-top: 1px solid color-mix(in srgb, var(--tw-text, #fff) 15%, transparent);
  padding-top: 0.75em;
  text-align: center;
}

.site-footer-block__copyright {
  font-size: 0.82rem;
  /* ⚠️ 50 % D'OPACITÉ SUR DU TEXTE COURANT, C'EST 3,78:1 (C15). Mesuré sur le
     pied de page ATELIER sombre : texte crème à demi transparent sur brun, très
     en dessous des 4,5:1 exigés. L'atténuation reste — c'est une ligne
     secondaire — mais à 78 %, qui tient 4,5:1 même sur un pied de page gris
     moyen (le pire cas : 4,9:1), et davantage partout ailleurs.

     > Une opacité est une couleur qu'on n'a pas choisie. Elle se calcule contre
     > le fond, ou elle se mesure trop tard. */
  color: color-mix(in srgb, var(--tw-text, #fff) 78%, transparent);
}

/* Empty state */
.site-footer-block__empty {
  font-size: 0.6875rem;
  font-style: italic;
  color: var(--tw-text, rgba(255, 255, 255, 0.45));
  padding: 0.5em 0;
}

/* Selection */
.site-footer-block.ProseMirror-selectednode {
  outline: 2px solid var(--ring, hsl(222.2 84% 55%));
  outline-offset: 2px;
}

/* ─────────────────────────────────────────────────────────────────────────────
   POST HEADER ATOM BLOCK (M09-POSTHEAD)
   Displays post metadata (title, author, date, tags) inside TipTap editor.
   Uses section palette colors + theme button radius for pills.
   Layout: Featured image → Title → Excerpt → Meta row (author pills, tag pills, date, status)
   ───────────────────────────────────────────────────────────────────────────── */

.atom-block.post-header-block {
  padding: 1.5rem 0;
  min-height: 80px;
  cursor: pointer;
  user-select: none;
  border-bottom: 1px solid color-mix(in srgb, var(--tw-text, #64748b) 15%, transparent);
  margin-bottom: 1rem;
  padding-bottom: 1.5rem;
}

.post-header-block__content {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

/* Featured image */
.post-header-block__featured-image {
  width: 100%;
  border-radius: var(--tw-radius-media, 8px);
  overflow: hidden;
}

.post-header-block__featured-image img {
  width: 100%;
  /* Même plafond que le site publié (`.post-featured-image`, blog-generator). */
  max-height: 500px;
  object-fit: cover;
  display: block;
}

.post-header-block__image-placeholder {
  width: 100%;
  height: 140px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--tw-text, #64748b) 8%, transparent);
  color: var(--tw-text, #64748b);
  font-size: 0.8rem;
  border-radius: var(--tw-radius-button, 8px);
}

/* Title = brique .tw-h2 (le DNA possède taille/graisse/police/couleur/tracking) —
   l'élément reste <h1> (sémantique). Ici : rien de typographique. */
.post-header-block__title {
  margin: 0;
}

/* Subtitle = brique .tw-lead (taille/couleur muted DNA) — parité avec le publish
   (post-subtitle). Ici : marge seulement. */
.post-header-block__subtitle {
  margin: 0;
}

/* Excerpt — for blog listing, smaller and muted */
.post-header-block__excerpt {
  font-size: 1em;
  font-style: italic;
  line-height: 1.6;
  color: var(--tw-text, #64748b);
  margin: 0;
  opacity: 0.7;
}

/* Meta row — author pills, tag pills, date, status */
.post-header-block__meta {
  display: flex;
  gap: 0.5em;
  flex-wrap: wrap;
  align-items: center;
}

/* Dot separator between meta items */
.post-header-block__meta-sep {
  display: inline-block;
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: var(--tw-text, #94a3b8);
  opacity: 0.5;
  flex-shrink: 0;
}

/* Author pills — use section secondary button colors */
.post-header-block__author-badge {
  display: inline-flex;
  align-items: center;
  padding: 0.2em 0.65em;
  border-radius: var(--tw-radius-badge, 9999px);
  font-size: 0.8em;
  font-weight: 500;
  background: var(--tw-surface, color-mix(in srgb, var(--tw-text, #64748b) 10%, transparent));
  color: var(--tw-btn-outline-text, var(--tw-text-heading, #0f172a));
}

/* Tag pills — use section primary button colors */
.post-header-block__tag-badge {
  display: inline-flex;
  align-items: center;
  padding: 0.2em 0.65em;
  border-radius: var(--tw-radius-badge, 9999px);
  font-size: 0.8em;
  font-weight: 500;
  background: var(--tw-accent, #3b82f6);
  color: var(--tw-on-accent, #ffffff);
  opacity: 0.9;
}

/* Date */
.post-header-block__date {
  font-size: 0.85em;
  color: var(--tw-text-muted, #64748b);
}

/* Status badges */
.post-header-block__status {
  display: inline-flex;
  align-items: center;
  padding: 0.15em 0.55em;
  border-radius: var(--tw-radius-badge, 9999px);
  font-size: 0.7em;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.post-header-block__status--draft {
  background: color-mix(in srgb, var(--tw-surface, #fef3c7) 80%, #f59e0b);
  color: var(--tw-text, #92400e);
}

.post-header-block__status--published {
  background: color-mix(in srgb, var(--tw-surface, #d1fae5) 80%, #10b981);
  color: var(--tw-text, #065f46);
}

/* Empty state */
.post-header-block__empty {
  font-size: 0.875rem;
  font-style: italic;
  color: var(--tw-text, #94a3b8);
  padding: 1em 0;
  text-align: center;
}

/* Text alignment variants */
.post-header-block__content--center {
  text-align: center;
}

.post-header-block__content--center .post-header-block__meta {
  justify-content: center;
}

.post-header-block__content--right {
  text-align: right;
}

.post-header-block__content--right .post-header-block__meta {
  justify-content: flex-end;
}

/* Selection */
.post-header-block.ProseMirror-selectednode {
  outline: 2px solid var(--ring, hsl(222.2 84% 55%));
  outline-offset: 2px;
}

/* ═══════════════════════════════════════════════════════════════════════════
   BLOCK ANIMATIONS — Single source of truth (ARCH-BLOCK-ANIM-001)
   Per DATA-MODEL.md DM-ANIM-001: 17 animation types

   Shared between TipTap editor and Astro preview/publish.
   - Editor: animations neutralisées ; la preview one-shot du toggle « Animé »
     est pilotée en STYLES INLINE !important par animPreview.ts (#113).
   - Preview/Publish: blocks hidden (opacity:0) until IntersectionObserver
     adds .is-visible when the block scrolls into view (fallback no-JS en fin
     de section : contenu visible sans animation).
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── @keyframes ── */

/* fade - Simple opacity fade with smooth easing */
@keyframes anim-fade {
  0% { opacity: 0; }
  100% { opacity: 1; }
}

/* fade-up - Enter from below viewport */
@keyframes anim-fade-up {
  0% { opacity: 0; transform: translateY(40px); }
  60% { opacity: 1; }
  100% { opacity: 1; transform: translateY(0); }
}

/* fade-down - Enter from above */
@keyframes anim-fade-down {
  0% { opacity: 0; transform: translateY(-40px); }
  60% { opacity: 1; }
  100% { opacity: 1; transform: translateY(0); }
}

/* fade-left - Enter from right side */
@keyframes anim-fade-left {
  0% { opacity: 0; transform: translateX(48px); }
  60% { opacity: 1; }
  100% { opacity: 1; transform: translateX(0); }
}

/* fade-right - Enter from left side */
@keyframes anim-fade-right {
  0% { opacity: 0; transform: translateX(-48px); }
  60% { opacity: 1; }
  100% { opacity: 1; transform: translateX(0); }
}

/* slide-up - Pure slide from below with spring effect */
@keyframes anim-slide-up {
  0% { opacity: 0; transform: translateY(64px); }
  40% { opacity: 1; }
  70% { transform: translateY(-8px); }
  85% { transform: translateY(3px); }
  100% { transform: translateY(0); }
}

/* slide-down - Pure slide from above with spring effect */
@keyframes anim-slide-down {
  0% { opacity: 0; transform: translateY(-64px); }
  40% { opacity: 1; }
  70% { transform: translateY(8px); }
  85% { transform: translateY(-3px); }
  100% { transform: translateY(0); }
}

/* slide-left - Slide in from right with spring */
@keyframes anim-slide-left {
  0% { opacity: 0; transform: translateX(64px); }
  40% { opacity: 1; }
  70% { transform: translateX(-12px); }
  85% { transform: translateX(4px); }
  100% { transform: translateX(0); }
}

/* slide-right - Slide in from left with spring */
@keyframes anim-slide-right {
  0% { opacity: 0; transform: translateX(-64px); }
  40% { opacity: 1; }
  70% { transform: translateX(12px); }
  85% { transform: translateX(-4px); }
  100% { transform: translateX(0); }
}

/* zoom-in - Scale up with smooth spring bounce */
@keyframes anim-zoom-in {
  0% { opacity: 0; transform: scale(0.3); }
  50% { opacity: 1; }
  70% { transform: scale(1.05); }
  85% { transform: scale(0.98); }
  100% { opacity: 1; transform: scale(1); }
}

/* zoom-out - Scale down with smooth spring bounce */
@keyframes anim-zoom-out {
  0% { opacity: 0; transform: scale(1.8); }
  50% { opacity: 1; }
  70% { transform: scale(0.95); }
  85% { transform: scale(1.02); }
  100% { opacity: 1; transform: scale(1); }
}

/* blur-in - Fade with strong blur effect */
@keyframes anim-blur-in {
  0% { opacity: 0; filter: blur(20px); }
  100% { opacity: 1; filter: blur(0); }
}

/* pop - Elastic bounce scale effect (professional spring) */
@keyframes anim-pop {
  0% { opacity: 0; transform: scale(0.3); }
  40% { opacity: 1; transform: scale(1.15); }
  60% { transform: scale(0.9); }
  75% { transform: scale(1.05); }
  87% { transform: scale(0.97); }
  100% { transform: scale(1); }
}

/* flip-up - 3D flip on X axis with smooth deceleration */
@keyframes anim-flip-up {
  0% { opacity: 0; transform: perspective(800px) rotateX(-90deg) translateY(30px); }
  40% { opacity: 1; }
  70% { transform: perspective(800px) rotateX(10deg); }
  100% { opacity: 1; transform: perspective(800px) rotateX(0) translateY(0); }
}

/* flip-left - 3D flip on Y axis with smooth deceleration */
@keyframes anim-flip-left {
  0% { opacity: 0; transform: perspective(800px) rotateY(90deg) translateX(-30px); }
  40% { opacity: 1; }
  70% { transform: perspective(800px) rotateY(-10deg); }
  100% { opacity: 1; transform: perspective(800px) rotateY(0) translateX(0); }
}

/* rotate-in - Rotation with spring bounce + slide */
@keyframes anim-rotate-in {
  0% { opacity: 0; transform: rotate(-12deg) translateY(40px) scale(0.9); }
  60% { opacity: 1; transform: rotate(3deg) translateY(-5px) scale(1.02); }
  80% { transform: rotate(-1deg) translateY(2px) scale(0.99); }
  100% { opacity: 1; transform: rotate(0) translateY(0) scale(1); }
}

/* reveal - Clip path reveal with smooth wipe effect */
@keyframes anim-reveal {
  0% { clip-path: inset(100% 0 0 0); }
  100% { clip-path: inset(0 0 0 0); }
}

/* ── Base animation properties ── */

[class*="block-anim--"] {
  animation-duration: 1s;
  animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
  animation-fill-mode: both;
}

/* Specific timing overrides for different animation types */
.block-anim--slide-up,
.block-anim--slide-down,
.block-anim--slide-left,
.block-anim--slide-right {
  animation-duration: 0.9s;
  animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);
}

.block-anim--pop {
  animation-duration: 0.8s;
  animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
}

.block-anim--flip-up,
.block-anim--flip-left {
  animation-duration: 1.1s;
  animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
}

/* ── Animation class → keyframe mapping ── */
.block-anim--fade       { animation-name: anim-fade; }
.block-anim--fade-up    { animation-name: anim-fade-up; }
.block-anim--fade-down  { animation-name: anim-fade-down; }
.block-anim--fade-left  { animation-name: anim-fade-left; }
.block-anim--fade-right { animation-name: anim-fade-right; }
.block-anim--slide-up   { animation-name: anim-slide-up; }
.block-anim--slide-down { animation-name: anim-slide-down; }
.block-anim--slide-left { animation-name: anim-slide-left; }
.block-anim--slide-right { animation-name: anim-slide-right; }
.block-anim--zoom-in    { animation-name: anim-zoom-in; }
.block-anim--zoom-out   { animation-name: anim-zoom-out; }
.block-anim--blur-in    { animation-name: anim-blur-in; }
.block-anim--pop        { animation-name: anim-pop; }
.block-anim--flip-up    { animation-name: anim-flip-up; }
.block-anim--flip-left  { animation-name: anim-flip-left; }
.block-anim--rotate-in  { animation-name: anim-rotate-in; }
.block-anim--reveal     { animation-name: anim-reveal; }

/* ── Editor: animations neutralisées (elles ne jouent que sur le site publié).
   La preview one-shot du toggle « Animé » est pilotée par des STYLES INLINE
   !important (animPreview.ts) — pas de pin d'opacité ici : animation:none
   suffit (opacité naturelle = 1) et un pin !important écraserait le fondu
   des keyframes pendant la preview (le !important statique bat les valeurs
   d'animation, cf revue #113). */
.ProseMirror [class*="block-anim--"] {
  animation: none !important;
}


/* ── Preview/Publish: hide blocks until scroll-triggered ──
   IntersectionObserver adds .is-visible → animation plays.
   Blocks without .is-visible stay at opacity:0 (from fill-mode:both
   applying the 'from' keyframe which has opacity:0 for most animations). */
[class*="block-anim--"]:not(.is-visible) {
  animation-play-state: paused;
}

/* Respect prefers-reduced-motion */
@media (prefers-reduced-motion: reduce) {
  [class*="block-anim--"] {
    opacity: 1 !important;
    animation: none !important;
    transform: none !important;
    filter: none !important;
  }
}

/* Kill-switch global « Animations d'apparition » (Theme → settings.animations.enabled=false)
   — BaseLayout pose data-animations="off" sur <html>. Même neutralisation que
   reduced-motion : les blocs gardent leurs réglages par-bloc mais s'affichent
   immédiatement (sans ça, fill-mode:both + play-state:paused les laisserait invisibles). */
[data-animations="off"] [class*="block-anim--"],
[data-animations="off"] .block-anim-stagger * {
  opacity: 1 !important;
  animation: none !important;
  transform: none !important;
  filter: none !important;
}

/* ═══════════════════════════════════════════════════════════════════════════
   STAGGER — Cascade animation on grid children
   When .block-anim-stagger is on a container with block-anim--{type},
   the animation moves from the container to each child with incremental delay.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Map animation type → CSS variable for children */
.block-anim-stagger.block-anim--fade       { --stagger-anim: anim-fade; }
.block-anim-stagger.block-anim--fade-up    { --stagger-anim: anim-fade-up; }
.block-anim-stagger.block-anim--fade-down  { --stagger-anim: anim-fade-down; }
.block-anim-stagger.block-anim--fade-left  { --stagger-anim: anim-fade-left; }
.block-anim-stagger.block-anim--fade-right { --stagger-anim: anim-fade-right; }
.block-anim-stagger.block-anim--slide-up   { --stagger-anim: anim-slide-up; }
.block-anim-stagger.block-anim--slide-down { --stagger-anim: anim-slide-down; }
.block-anim-stagger.block-anim--slide-left { --stagger-anim: anim-slide-left; }
.block-anim-stagger.block-anim--slide-right { --stagger-anim: anim-slide-right; }
.block-anim-stagger.block-anim--zoom-in    { --stagger-anim: anim-zoom-in; }
.block-anim-stagger.block-anim--zoom-out   { --stagger-anim: anim-zoom-out; }
.block-anim-stagger.block-anim--blur-in    { --stagger-anim: anim-blur-in; }
.block-anim-stagger.block-anim--pop        { --stagger-anim: anim-pop; }
.block-anim-stagger.block-anim--flip-up    { --stagger-anim: anim-flip-up; }
.block-anim-stagger.block-anim--flip-left  { --stagger-anim: anim-flip-left; }
.block-anim-stagger.block-anim--rotate-in  { --stagger-anim: anim-rotate-in; }
.block-anim-stagger.block-anim--reveal     { --stagger-anim: anim-reveal; }

/* Suppress container animation — children take over */
[class*="block-anim--"].block-anim-stagger {
  animation: none !important;
  opacity: 1 !important;
}

/* Apply animation to grid children */
.block-anim-stagger > .tw-surface,
.block-anim-stagger > .cards-block__card,
.block-anim-stagger .logo-cloud-block__grid > .logo-cloud-block__item,
.block-anim-stagger > .stats-block__item,
.block-anim-stagger > .pricing-block__plan,
.block-anim-stagger > .testimonial-block__item,
.block-anim-stagger > .gallery-block__item,
.block-anim-stagger > .faq-block__item,
.block-anim-stagger > .timeline-block__item,
.block-anim-stagger > .logo-cloud-block__item,
.block-anim-stagger .team-block__grid > .team-block__member {
  animation-name: var(--stagger-anim, none);
  animation-duration: 0.8s;
  animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
  animation-fill-mode: both;
  animation-play-state: paused;
}

/* When container is visible → children play */
.block-anim-stagger.is-visible > .tw-surface,
.block-anim-stagger.is-visible > .cards-block__card,
.block-anim-stagger.is-visible .logo-cloud-block__grid > .logo-cloud-block__item,
.block-anim-stagger.is-visible > .stats-block__item,
.block-anim-stagger.is-visible > .pricing-block__plan,
.block-anim-stagger.is-visible > .testimonial-block__item,
.block-anim-stagger.is-visible > .gallery-block__item,
.block-anim-stagger.is-visible > .faq-block__item,
.block-anim-stagger.is-visible > .timeline-block__item,
.block-anim-stagger.is-visible > .logo-cloud-block__item,
.block-anim-stagger.is-visible .team-block__grid > .team-block__member {
  animation-play-state: running;
}

/* Stagger delays — 150ms between each child */
.block-anim-stagger > :nth-child(1),
.block-anim-stagger .team-block__grid > :nth-child(1),
.block-anim-stagger .logo-cloud-block__grid > :nth-child(1) { animation-delay: 0ms; }
.block-anim-stagger > :nth-child(2),
.block-anim-stagger .team-block__grid > :nth-child(2),
.block-anim-stagger .logo-cloud-block__grid > :nth-child(2) { animation-delay: 150ms; }
.block-anim-stagger > :nth-child(3),
.block-anim-stagger .team-block__grid > :nth-child(3),
.block-anim-stagger .logo-cloud-block__grid > :nth-child(3) { animation-delay: 300ms; }
.block-anim-stagger > :nth-child(4),
.block-anim-stagger .team-block__grid > :nth-child(4),
.block-anim-stagger .logo-cloud-block__grid > :nth-child(4) { animation-delay: 450ms; }
.block-anim-stagger > :nth-child(5),
.block-anim-stagger .team-block__grid > :nth-child(5),
.block-anim-stagger .logo-cloud-block__grid > :nth-child(5) { animation-delay: 600ms; }
.block-anim-stagger > :nth-child(6),
.block-anim-stagger .team-block__grid > :nth-child(6),
.block-anim-stagger .logo-cloud-block__grid > :nth-child(6) { animation-delay: 750ms; }
.block-anim-stagger > :nth-child(7),
.block-anim-stagger .team-block__grid > :nth-child(7),
.block-anim-stagger .logo-cloud-block__grid > :nth-child(7) { animation-delay: 900ms; }
.block-anim-stagger > :nth-child(8),
.block-anim-stagger .team-block__grid > :nth-child(8),
.block-anim-stagger .logo-cloud-block__grid > :nth-child(8) { animation-delay: 1050ms; }
.block-anim-stagger > :nth-child(n+9),
.block-anim-stagger .team-block__grid > :nth-child(n+9),
.block-anim-stagger .logo-cloud-block__grid > :nth-child(n+9) { animation-delay: 1200ms; }

/* Editor: stagger neutralisé (tous descendants — grilles imbriquées team/logo
   comprises) ; pas de pin d'opacité, cf le suppress principal ci-dessus. */
.ProseMirror .block-anim-stagger * {
  animation: none !important;
}

/* Stagger + reduced motion (tous descendants — grilles imbriquées comprises) */
@media (prefers-reduced-motion: reduce) {
  .block-anim-stagger * {
    opacity: 1 !important;
    animation: none !important;
    transform: none !important;
  }
}

/* ── Fallback sans JavaScript (offline-first #84) + impression ─────────────
   Les keyframes démarrent à opacity:0 (fill-mode both + paused) : si
   scroll-animations.js ne tourne pas (JS bloqué, erreur, impression), le
   contenu resterait invisible. BaseLayout pose data-anim-js sur <html> en
   inline dès le chargement ; sans lui, on neutralise tout (contenu visible,
   pas d'animation). */
html:not([data-anim-js]) [class*="block-anim--"],
html:not([data-anim-js]) .block-anim-stagger * {
  animation: none !important;
  opacity: 1 !important;
  transform: none !important;
  filter: none !important;
}
@media print {
  [class*="block-anim--"],
  .block-anim-stagger * {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
  }
}


/* ═══════════════════════════════════════════════════════════════════════════
   HOVER MICRO-INTERACTIONS — REMOVED (Yann 2026-06-05)
   These cards (features / pricing / team / testimonial / stats) are NOT
   clickable, so a lift + shadow on hover is a false affordance ("on dirait
   qu'un clic mène quelque part"). Hover effects are kept only on genuinely
   interactive elements (showcase cards = links, social links, buttons/CTAs).
   ═══════════════════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════════════
   TEXT REVEAL — Scroll-triggered heading word/line animations
   Applied via data-text-reveal attribute on content sections.
   ═══════════════════════════════════════════════════════════════════════════ */

.text-reveal {
  overflow: hidden;
}

/* Word reveal */
.text-reveal--word-reveal .text-reveal__word {
  display: inline-block;
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1),
              transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
  transition-delay: var(--reveal-delay, 0ms);
}

.text-reveal--word-reveal.text-reveal--visible .text-reveal__word {
  opacity: 1;
  transform: translateY(0);
}

/* Line reveal */
.text-reveal--line-reveal .text-reveal__line {
  display: block;
  opacity: 0;
  transform: translateY(100%);
  transition: opacity 0.6s cubic-bezier(0.16, 1, 0.3, 1),
              transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
  transition-delay: var(--reveal-delay, 0ms);
}

.text-reveal--line-reveal.text-reveal--visible .text-reveal__line {
  opacity: 1;
  transform: translateY(0);
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
  .text-reveal__word,
  .text-reveal__line {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   IMAGE PARALLAX — Subtle scroll-linked vertical shift
   Applied via data-parallax attribute on image blocks.
   ═══════════════════════════════════════════════════════════════════════════ */

[data-parallax] {
  will-change: transform;
  transition: transform 0.1s linear;
}

/* ═══════════════════════════════════════════════════════════════════════════
   LOTTIE BLOCK — Embedded Lottie animations
   ═══════════════════════════════════════════════════════════════════════════ */

.lottie-block {
  display: flex;
  justify-content: center;
  align-items: center;
}

.lottie-block--small { max-width: 200px; margin: 0 auto; }
.lottie-block--medium { max-width: 400px; margin: 0 auto; }
.lottie-block--large { max-width: 600px; margin: 0 auto; }
.lottie-block--full { width: 100%; }

.lottie-block--align-left { justify-content: flex-start; }
.lottie-block--align-center { justify-content: center; }
.lottie-block--align-right { justify-content: flex-end; }

.lottie-block dotlottie-wc,
.lottie-block dotlottie-player,
.lottie-block lottie-player {
  width: 100%;
  height: auto;
  min-height: 100px;
}

/* Editor: player inside NodeView */
.ProseMirror .lottie-block dotlottie-wc,
.ProseMirror .lottie-block dotlottie-player {
  pointer-events: none;
}

/* Editor placeholder (shown when no src) */
.lottie-block__placeholder {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  width: 100%;
  padding: 2rem;
  border: 2px dashed var(--border, hsl(214.3 31.8% 91.4%));
  border-radius: 0.5rem;
  color: var(--muted-foreground, hsl(215.4 16.3% 46.9%));
  font-size: 0.875rem;
  min-height: 120px;
  cursor: pointer;
  transition: border-color 0.2s, background-color 0.2s;
}

.lottie-block__placeholder:hover {
  border-color: var(--primary, hsl(222.2 47.4% 11.2%));
  background-color: var(--muted, hsl(210 40% 96.1%));
}

/* Selection */
.lottie-block.ProseMirror-selectednode {
  outline: 2px solid var(--ring, hsl(222.2 84% 55%));
  outline-offset: 2px;
}

/* ═══════════════════════════════════════════════════════════════════════════
   TW-CAROUSEL — One-slide-at-a-time carousel
   Used by: testimonial carousel, product gallery, lightbox overlay.
   NOT used by gallery carousel (which is a multi-item scroll strip).
   Interactive controls (arrows, dots) powered by Alpine.js `carousel()`.
   ═══════════════════════════════════════════════════════════════════════════ */

.tw-carousel {
  position: relative;
  overflow: hidden;
}

.tw-carousel__track {
  display: flex;
  width: 100%;
  /* TRANSFORM-based (cf carousel() Alpine) : la position vient d'un
     translateX(-current*100%) en :style — déterministe, pas de dépendance à la
     scrollabilité du track (l'ancien scroll-snap devenait un no-op inline). Le
     parent .tw-carousel { overflow:hidden } clippe les slides hors-champ.
     (Le lightbox, lui, RESTAURE le scroll plus bas — il navigue en scrollTo.) */
  transition: transform 0.45s cubic-bezier(0.22, 0.61, 0.36, 1);
  will-change: transform;
}

.tw-carousel__track::-webkit-scrollbar {
  display: none;
}

/* Each slide = 100% width (one visible at a time) */
.tw-carousel__track > * {
  flex: 0 0 100%;
  width: 100%;
  /* min-width: auto would let content widen the slide above 100% — force 100%. */
  min-width: 100%;
  scroll-snap-align: start;
  box-sizing: border-box;
}

/* Images inside slides */
.tw-carousel__track > * img {
  width: 100%;
  height: auto;
  max-height: 70vh;
  object-fit: contain;
}

/* Arrows */
.tw-carousel__prev,
.tw-carousel__next {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  border: none;
  background: rgba(255, 255, 255, 0.85);
  color: #1e293b;
  font-size: 1.25rem;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
  transition: opacity 0.15s, background 0.15s;
  opacity: 0.7;
}

.tw-carousel__prev:hover,
.tw-carousel__next:hover {
  opacity: 1;
  background: #fff;
}

.tw-carousel__prev { left: 0.75rem; }
.tw-carousel__next { right: 0.75rem; }

/* Hide arrows at boundary */
.tw-carousel__prev[disabled],
.tw-carousel__next[disabled] {
  opacity: 0;
  pointer-events: none;
}

/* Dots */
.tw-carousel__dots {
  display: flex;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.75rem 0;
}

.tw-carousel__dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--tw-text, #94a3b8);
  opacity: 0.35;
  cursor: pointer;
  transition: opacity 0.15s, transform 0.15s;
}

.tw-carousel__dot--active {
  opacity: 1;
  transform: scale(1.25);
}

/* Guard: grid layout in editor */
.ProseMirror .tw-carousel__track {
  flex-wrap: wrap;
  overflow: visible;
  scroll-snap-type: none;
}

.ProseMirror .tw-carousel__prev,
.ProseMirror .tw-carousel__next,
.ProseMirror .tw-carousel__dots {
  display: none;
}

/* ─── Lightbox carousel overlay ─── */

/* Clavier (WCAG 2.1.1 / 2.4.7, audit de lauyan.com du 2026-09-29) : une image
   qu'on agrandit est un bouton (lightbox.ts) ; son contour se dessine DEDANS
   (`outline-offset` négatif), la vignette coupant ce qui dépasse. Les contrôles
   de la visionneuse, sur fond sombre, ont un contour blanc. */
img[data-tw-zoom-clavier]:focus-visible { outline: 3px solid var(--tw-accent, #2563eb); outline-offset: -3px; }
.tw-lightbox-overlay button:focus-visible { outline: 2px solid #fff; outline-offset: 2px; box-shadow: 0 0 0 4px rgba(0, 0, 0, 0.6); }

.tw-lightbox-overlay {
  position: fixed;
  inset: 0;
  z-index: 99999;
  background: rgba(0, 0, 0, 0.9);
  display: flex;
  flex-direction: column;
  opacity: 0;
  transition: opacity 0.25s ease;
}

.tw-lightbox-overlay.is-open {
  opacity: 1;
}

.tw-lightbox-overlay .tw-carousel {
  flex: 1;
  min-height: 0;
}

.tw-lightbox-overlay .tw-carousel__track {
  height: 100%;
  /* Le lightbox navigue en scrollTo() (lightbox.ts) + swipe natif : on RESTAURE
     le scroll ici (la base est passée en transform pour le carrousel inline). */
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  transition: none;
  will-change: auto;
}

.tw-lightbox-overlay .tw-carousel__track > * {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem;
}

.tw-lightbox-overlay .tw-carousel__track > * img {
  max-height: calc(100vh - 6rem);
  max-width: 100%;
  object-fit: contain;
  border-radius: 4px;
}

.tw-lightbox-overlay.tw-lightbox--seule :is(.tw-carousel__prev, .tw-carousel__next, .tw-carousel__dots, .tw-lightbox-counter) { display: none; }

.tw-lightbox-overlay .tw-carousel__prev,
.tw-lightbox-overlay .tw-carousel__next {
  width: 3rem;
  height: 3rem;
  font-size: 1.5rem;
}

.tw-lightbox-overlay .tw-carousel__dots {
  padding: 0.5rem 0 1rem;
}

.tw-lightbox-overlay .tw-carousel__dot {
  background: #fff;
}

.tw-lightbox-overlay .tw-lightbox-close {
  position: absolute;
  top: 1rem;
  right: 1rem;
  z-index: 3;
  width: 2.5rem;
  height: 2.5rem;
  border: none;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.15);
  color: #fff;
  font-size: 1.5rem;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s;
}

.tw-lightbox-overlay .tw-lightbox-close:hover {
  background: rgba(255, 255, 255, 0.3);
}

.tw-lightbox-overlay .tw-lightbox-counter {
  position: absolute;
  bottom: 0.75rem;
  left: 50%;
  transform: translateX(-50%);
  color: rgba(255, 255, 255, 0.7);
  font-size: 13px;
  pointer-events: none;
}

/* ═══════════════════════════════════════════════════════════════════════════
   MASONRY — Phase 4A
   CSS columns-based masonry for gallery-block--masonry.
   Shared between editor (Pages) and preview (Astro) — single source of truth.
   ═══════════════════════════════════════════════════════════════════════════ */

.gallery-block--masonry {
  display: block !important;
  /* vertical gap via custom property (applied as margin-bottom on items) */
  --masonry-v-gap: var(--collection-gap-v-medium, 1.5rem);
}

.gallery-block--masonry.collection--gap-v-none { --masonry-v-gap: 0px; }
.gallery-block--masonry.collection--gap-v-small { --masonry-v-gap: var(--collection-gap-v-small, 0.75rem); }
.gallery-block--masonry.collection--gap-v-medium { --masonry-v-gap: var(--collection-gap-v-medium, 1.5rem); }
.gallery-block--masonry.collection--gap-v-large { --masonry-v-gap: var(--collection-gap-v-large, 2.5rem); }

.gallery-block--masonry .gallery-block__item {
  break-inside: avoid;
  margin-bottom: var(--masonry-v-gap);
}

/* Responsive column counts for masonry (mirrors data-cols-* attribute system) */
.gallery-block--masonry[data-cols-mobile="1"] { columns: 1; }
.gallery-block--masonry[data-cols-mobile="2"] { columns: 2; }

@media (min-width: 768px) {
  .gallery-block--masonry[data-cols-tablet="2"] { columns: 2; }
  .gallery-block--masonry[data-cols-tablet="3"] { columns: 3; }
  .gallery-block--masonry[data-cols-tablet="4"] { columns: 4; }
}

@media (min-width: 1024px) {
  .gallery-block--masonry[data-cols-desktop="2"] { columns: 2; }
  .gallery-block--masonry[data-cols-desktop="3"] { columns: 3; }
  .gallery-block--masonry[data-cols-desktop="4"] { columns: 4; }
  .gallery-block--masonry[data-cols-desktop="5"] { columns: 5; }
  .gallery-block--masonry[data-cols-desktop="6"] { columns: 6; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   FAQ ACCORDION SMOOTH ANIMATION — moved to blocks-base.css (DNA-BRICKS migration)
   ═══════════════════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════════════
   PRODUCT SHOWCASE BLOCK — Dynamic product grid
   ═══════════════════════════════════════════════════════════════════════════ */

.product-showcase-block {
  display: grid;
  grid-template-columns: 1fr;
  width: 100%;
  gap: var(--tw-gap, 1.5rem); /* R.6 strip: DNA-owned */
}
.product-showcase-block[data-cols-desktop="2"] { grid-template-columns: repeat(2, 1fr); }
.product-showcase-block[data-cols-desktop="3"] { grid-template-columns: repeat(3, 1fr); }
.product-showcase-block[data-cols-desktop="4"] { grid-template-columns: repeat(4, 1fr); }

/* ⚠️ CASCADE DES POINTS DE RUPTURE. Le 1fr de repli sous chaque point de rupture
   porte un attribut, (0,2,0), et vient APRÈS les règles du palier supérieur : un
   `.product-showcase-block{1fr}` nu (0,1,0) perdait contre `[data-cols-tablet="2"]`
   (0,2,0), qui reste vraie sous 767 px — à 390 px, deux colonnes étroites au lieu
   d'une (vitrines, 2026-09-28). Le générateur pose toujours les trois attributs. */
@media (max-width: 1023px) {
  .product-showcase-block[data-cols-desktop] { grid-template-columns: 1fr; }
  .product-showcase-block[data-cols-tablet="2"] { grid-template-columns: repeat(2, 1fr); }
  .product-showcase-block[data-cols-tablet="3"] { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 767px) {
  .product-showcase-block[data-cols-tablet] { grid-template-columns: 1fr; }
  .product-showcase-block[data-cols-mobile="2"] { grid-template-columns: repeat(2, 1fr); }
}

/* ─────────────────────────────────────────────────────────────────────────
   PRODUCT INDEX CARD — single SoT for "a product shown in a grid".
   Used by BOTH the shop index/collection pages (ProductCard.astro) AND the
   product-showcase block (editor + publish): same DOM, same classes → same
   look, incl. the DNA-C bakery-counter skin in dna-c.css. Moved here from
   ProductCard.astro's scoped <style> so the showcase block shares it (Yann
   2026-06-06: "une seule SoT pour la fiche index produit").
   ───────────────────────────────────────────────────────────────────────── */
/* Carte = ÎLOT surface : re-localise son texte sur son propre on-surface (comme
   .tw-surface[data-surface="card"]), sinon une carte claire posée sur une bande
   `bold` hérite des tokens crème de la bande → titre/prix/texte invisibles
   (bug titres de cartes). EXCLUT .blog-card--flat (fond transparent = « bare
   surface » qui DOIT hériter la bande, cf blocks-base §bare). */
.product-card, .blog-card:not(.blog-card--flat) {
  --tw-text: var(--tw-on-surface);
  --tw-text-heading: var(--tw-on-surface);
  --tw-text-muted: color-mix(in oklab, var(--tw-on-surface) 86%, var(--tw-surface, transparent));
}

.product-card {
  /* DNA-bricks (voie 4) : la carte EST une surface brique — tokens --tw-* only,
     palette/dark-aware. La couleur ne vient plus du theming legacy. */
  background: var(--tw-surface, #fff);
  border-radius: var(--tw-radius-card, 8px);
  overflow: hidden;
  box-shadow: var(--tw-shadow-card, 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06));
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}
/* product cards ARE links → the hover lift is a legit affordance (kept). */
.product-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--tw-shadow-card-hover, 0 10px 25px rgba(0, 0, 0, 0.1));
}
.product-card__link {
  display: block;
  text-decoration: none;
  color: inherit;
}
.product-card__image-wrapper {
  position: relative;
  width: 100%;
  padding-bottom: 100%;
  overflow: hidden;
  background: var(--tw-surface, #f1f5f9);
}
/* Double classe (0,2,0) : `.page-content img{height:auto}` (0,1,1, astro-preview.css,
   chargé après) l'emportait sur `.product-card__image` dans la vitrine d'une page —
   une photo en paysage laissait un vide sous elle dans la carte carrée. */
.product-card__image-wrapper > .product-card__image {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* HELO 247390 : Boutique › Général › « image entière » — la photo n'est plus rognée
   en carré ; elle se pose entière sur le fond de la vignette. */
.product-grid[data-image-fit="contain"] .product-card__image-wrapper > .product-card__image { object-fit: contain; }
.product-card__placeholder {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0.4;
}
/* Hidden by default — a DNA skin opts in (positions it over the photo). */
.product-card__price-sticker {
  display: none;
}
/* ⚠️ EN HAUT À GAUCHE : le cœur des favoris occupe le coin droit
   (`.product-card__wishlist`, BaseLayout). Les deux y étaient, et le cœur
   recouvrait le badge — « SA… » sur chaque carte en promotion (scan visuel
   du 2026-09-23). Placement standard du commerce en ligne : la promo à gauche,
   les favoris à droite. */
.product-card__badge {
  position: absolute;
  top: 0.75rem;
  inset-inline-start: 0.75rem;
  padding: 0.25rem 0.625rem;
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  border-radius: 4px;
  background: var(--tw-badge-bg, #ef4444);
  color: var(--tw-badge-text, #fff);
}
.product-card__body {
  padding: 1rem 1.25rem 1.25rem;
}
.product-card__title {
  /* Typo = brique .tw-h4 (le DNA possède la titraille) ; ici LAYOUT only.
     Taille compacte de carte conservée via un override local (la carte produit
     en grille reste compacte vs le h4 de section). */
  font-size: 1rem;
  margin: 0 0 0.375rem;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.product-card__pricing {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
}
.product-card__price {
  /* Couleur/graisse/police = brique .tw-price ; ici taille de carte seulement. */
  font-size: 1.125rem;
}
/* prix barré + badge "Sale" signalent la promo ; la couleur reste DNA (price-color). */
.product-card__price--sale {
  color: var(--tw-price-color, var(--tw-text-heading, #0f172a));
}
.product-card__compare-price {
  font-size: 0.875rem;
  color: var(--tw-text-muted, #334155);
  text-decoration: line-through;
}
.product-card__tax {
  display: block;
  font-size: 0.75rem;
  color: var(--tw-text-muted, #334155);
  margin-top: 0.125rem;
}
.product-showcase-block--empty {
  min-height: 80px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.product-showcase-block__placeholder {
  color: var(--tw-text-muted, inherit);
  font-size: 0.85rem;
  font-style: italic;
}

/* Gap variants */
.product-showcase-block.collection--gap-h-none { column-gap: 0; }
.product-showcase-block.collection--gap-h-small { column-gap: 0.5rem; }
.product-showcase-block.collection--gap-h-medium { column-gap: 1.5rem; }
.product-showcase-block.collection--gap-h-large { column-gap: 2.5rem; }
.product-showcase-block.collection--gap-v-none { row-gap: 0; }
.product-showcase-block.collection--gap-v-small { row-gap: 0.5rem; }
.product-showcase-block.collection--gap-v-medium { row-gap: 1.5rem; }
.product-showcase-block.collection--gap-v-large { row-gap: 2.5rem; }

/* ═══════════════════════════════════════════════════════════════════════════
   POST SHOWCASE BLOCK — Dynamic blog post grid
   ═══════════════════════════════════════════════════════════════════════════ */

.post-showcase-block {
  display: grid;
  grid-template-columns: 1fr;
  width: 100%;
  gap: var(--tw-gap, 1.5rem); /* R.6 strip: DNA-owned */
}
.post-showcase-block[data-cols-desktop="2"] { grid-template-columns: repeat(2, 1fr); }
.post-showcase-block[data-cols-desktop="3"] { grid-template-columns: repeat(3, 1fr); }
.post-showcase-block[data-cols-desktop="4"] { grid-template-columns: repeat(4, 1fr); }

/* Même cascade que la vitrine de produits (voir plus haut). */
@media (max-width: 1023px) {
  .post-showcase-block[data-cols-desktop] { grid-template-columns: 1fr; }
  .post-showcase-block[data-cols-tablet="2"] { grid-template-columns: repeat(2, 1fr); }
  .post-showcase-block[data-cols-tablet="3"] { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 767px) {
  .post-showcase-block[data-cols-tablet] { grid-template-columns: 1fr; }
  .post-showcase-block[data-cols-mobile="2"] { grid-template-columns: repeat(2, 1fr); }
}

/* List layout variant — container goes single-column ; la carte elle-même
   devient une .blog-card--list (image à gauche, ci-dessous). */
.post-showcase-block--list {
  grid-template-columns: 1fr !important;
}

/* ═══════════════════════════════════════════════════════════════════════════
   .blog-card — CARTE ARTICLE PARTAGÉE (SoT unique)
   Émise à l'identique par : la page blog générée (BlogCard.astro) ET le bloc
   postShowcase (éditeur renderHTML + publish tiptap-to-html). Déplacée ici
   depuis le <style is:global> de BlogCard.astro pour que le bloc la partage —
   calque exact de .product-card (Yann 2026-06-10 : « une seule SoT pour la
   carte article »). 100% tokens --tw-* (palette + dark-aware). DNA skins dans
   dna-skin.css.
   ═══════════════════════════════════════════════════════════════════════════ */
.blog-card {
  position: relative;
  background: var(--tw-surface, #fff);
  border-radius: var(--tw-radius-card, 8px);
  overflow: hidden;
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.blog-card--shadow {
  box-shadow: var(--tw-shadow-card, 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06));
}
.blog-card--bordered {
  border: var(--tw-border-width, 1px) solid var(--tw-border, #e2e8f0);
}
.blog-card--flat {
  background: transparent;
}
.blog-card--hover-lift:hover {
  transform: translateY(-4px);
  box-shadow: var(--tw-shadow-card-hover, 0 10px 25px rgba(0, 0, 0, 0.1));
}
.blog-card--hover-shadow:hover {
  box-shadow: var(--tw-shadow-card-hover, 0 10px 25px rgba(0, 0, 0, 0.15));
}
.blog-card__inner {
  display: flex;
  flex-direction: column;
}
.blog-card__image-wrapper {
  position: relative;
  width: 100%;
  overflow: hidden;
  background: var(--tw-surface, #f1f5f9);
}
.blog-card__image {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.blog-card__body {
  padding: 1.25rem;
}
.blog-card--flat .blog-card__body {
  padding: 1rem 0;
}
.blog-card__title {
  /* Typo = brique .tw-h4 ; ici taille de carte + marge seulement. */
  font-size: 1.125rem;
  margin: 0 0 0.5rem;
}
/* Stretched link : le <a> du titre couvre toute la carte (publish). */
.blog-card__title-link {
  text-decoration: none;
  color: inherit;
}
.blog-card__title-link::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
}
.blog-card__excerpt {
  font-size: 0.875rem;
  color: var(--tw-text-muted, #334155);
  line-height: 1.6;
  margin: 0 0 0.75rem;
}
.blog-card__meta {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  font-size: 0.8125rem;
  color: var(--tw-text-muted, #334155);
  margin-bottom: 0.5rem;
}
.blog-card__authors {
  font-weight: 500;
}
.blog-card__tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
  margin-top: 0.5rem;
}
.blog-card__tag {
  display: inline-block;
  padding: 0.125rem 0.5rem;
  font-size: 0.75rem;
  border-radius: 9999px;
  background: var(--tw-accent-soft, #f1f5f9);
  color: var(--tw-text, #334155);
  text-decoration: none;
  transition: background-color 0.15s ease;
  position: relative;
  z-index: 1;
}
.blog-card__tag:hover {
  background: var(--tw-accent, #3b82f6);
  color: var(--tw-on-accent, #fff);
}
/* ─── List mode : layout horizontal (image gauche, contenu droite) ─── */
.blog-card--list .blog-card__inner {
  flex-direction: row;
}
.blog-card--list .blog-card__image-wrapper {
  width: 200px;
  min-height: 120px;
  flex-shrink: 0;
  padding-bottom: 0;
}
.blog-card--list .blog-card__image {
  position: static;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.blog-card--list .blog-card__body {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
@media (max-width: 480px) {
  .blog-card--list .blog-card__inner {
    flex-direction: column;
  }
  .blog-card--list .blog-card__image-wrapper {
    width: 100%;
    min-height: 0;
    padding-bottom: 56.25%;
  }
  .blog-card--list .blog-card__image {
    position: absolute;
  }
}

.post-showcase-block--empty {
  min-height: 80px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.post-showcase-block__placeholder {
  color: var(--tw-text-muted, inherit);
  opacity: 0.5;
  font-size: 0.85rem;
  font-style: italic;
}

/* Gap variants */
.post-showcase-block.collection--gap-h-none { column-gap: 0; }
.post-showcase-block.collection--gap-h-small { column-gap: 0.5rem; }
.post-showcase-block.collection--gap-h-medium { column-gap: 1.5rem; }
.post-showcase-block.collection--gap-h-large { column-gap: 2.5rem; }
.post-showcase-block.collection--gap-v-none { row-gap: 0; }
.post-showcase-block.collection--gap-v-small { row-gap: 0.5rem; }
.post-showcase-block.collection--gap-v-medium { row-gap: 1.5rem; }
.post-showcase-block.collection--gap-v-large { row-gap: 2.5rem; }


/* ═══════════════════════════════════════════════════════════════════════════
   BOOKING WIDGET — 3-step wizard with 2-column layout
   Uses --theme-* variables from theme.css (site theme).
   ═══════════════════════════════════════════════════════════════════════════ */


/* ═══════════════════════════════════════════════════════════════════════════
 * BOOKING WIDGET (M-BOOKING)
 *
 * Layout inspired by Cal.com / Calendly:
 * - Desktop: calendar left (340-380px), panel right (slots/form)
 * - Mobile: stacked, calendar full-width on top
 * - Calendar cells are 44px+ for touch targets
 *
 * Colors: ALL use the DNA brick tokens (--tw-text/-accent/-surface/-border…),
 * re-pointed per section by the DNA palette ([data-palette]).
 * Layout: uses --theme-radius-*, --theme-font-* (Typography survivor, global).
 * ═══════════════════════════════════════════════════════════════════════════ */

/* ── Container ────────────────────────────────────────────────────────── */
.booking-block {
  margin: 2rem 0;
  font-family: var(--theme-font-body, inherit);
  font-size: 0.925rem;
  color: var(--tw-text, #334155);
}
.booking-block--card {
  background: var(--tw-surface, #f8fafc);
  border: 1px solid var(--tw-border, rgba(0,0,0,0.08));
  border-radius: var(--tw-radius-card, 12px);
  padding: 2rem;
  box-shadow: var(--tw-shadow-card, 0 1px 3px rgba(0,0,0,0.06));
}
.booking-block--compact { max-width: 520px; }
.booking-block__title {
  font-family: var(--theme-font-heading, inherit);
  font-size: 1.5rem;
  font-weight: 700;
  margin-bottom: 0.75rem;
  color: var(--tw-text-heading, #1e293b);
  text-align: center;
}
.booking-block__description {
  color: color-mix(in srgb, var(--tw-text, #334155) 70%, transparent);
  font-size: 0.9rem;
  margin-bottom: 1.5rem;
  text-align: center;
}

/* ── Loading ──────────────────────────────────────────────────────────── */
.booking-block__loading {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 0.75rem; padding: 3rem 1rem;
  color: color-mix(in srgb, var(--tw-text, #334155) 74%, transparent);
  font-size: 0.9rem;
}
.booking-block__spinner {
  width: 32px; height: 32px;
  border: 3px solid var(--tw-border, #e2e8f0);
  border-top-color: var(--tw-accent, #3b82f6);
  border-radius: 50%; animation: bk-spin 0.7s linear infinite;
}
.booking-block__spinner--sm { width: 18px; height: 18px; border-width: 2px; }
.booking-block__spinner--inline { display: inline-block; vertical-align: middle; }
@keyframes bk-spin { to { transform: rotate(360deg); } }
@keyframes bk-check-in {
  from { opacity: 0; transform: scale(0.8); }
  to { opacity: 1; transform: scale(1); }
}

/* ── Step indicator (subtle, compact) ────────────────────────────────── */
.booking-block__steps {
  display: flex; align-items: center; justify-content: center;
  gap: 0; margin-bottom: 1.5rem; padding: 0 1rem;
}
.booking-block__step-item { display: flex; align-items: center; gap: 0.5rem; }
.booking-block__step-circle {
  width: 28px; height: 28px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 0.7rem; font-weight: 600;
  color: color-mix(in srgb, var(--tw-text, #334155) 76%, transparent);
  border: 1.5px solid var(--tw-border, rgba(0,0,0,0.12));
  background: transparent; flex-shrink: 0;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.booking-block__step-circle.is-active {
  background: var(--tw-accent, #3b82f6);
  color: var(--tw-on-accent, #fff);
  border-color: var(--tw-accent, #3b82f6);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--tw-accent, #3b82f6) 15%, transparent);
}
.booking-block__step-circle.is-completed {
  background: var(--tw-accent, #3b82f6);
  color: var(--tw-on-accent, #fff);
  border-color: var(--tw-accent, #3b82f6);
}
.booking-block__step-label {
  font-size: 0.75rem; font-weight: 500; white-space: nowrap;
  color: color-mix(in srgb, var(--tw-text, #334155) 76%, transparent);
  transition: color 0.3s;
}
.booking-block__step-label.is-active {
  color: var(--tw-text-heading, #1e293b); font-weight: 600;
}
.booking-block__step-line {
  width: 32px; height: 1.5px;
  background: var(--tw-border, rgba(0,0,0,0.1));
  margin: 0 0.4rem; flex-shrink: 0;
  transition: background 0.4s; border-radius: 1px;
}
.booking-block__step-line.is-completed {
  background: var(--tw-accent, #3b82f6);
}

/* ── Layout ───────────────────────────────────────────────────────────── */
/* Default: single column, calendar centered.
   When a date is selected (.has-panel): calendar slides left, slots appear right.
   Transition is smooth via margin animation on the calendar. */
.booking-block__columns {
  display: flex; flex-direction: column; gap: 1.25rem;
}
.booking-block__col-calendar {
  min-width: 0; width: 100%; max-width: 420px;
  margin: 0 auto;
  transition: margin 0.3s ease, max-width 0.3s ease;
}
.booking-block__col-panel { min-width: 0; width: 100%; }
.booking-block__col-summary { width: 100%; }
.booking-block__col-form { min-width: 0; width: 100%; }

/* Desktop: when panel is visible, switch to side-by-side */
@media (min-width: 680px) {
  .booking-block__columns.has-panel {
    flex-direction: row; align-items: flex-start; gap: 1.5rem;
  }
  .booking-block__columns.has-panel .booking-block__col-calendar {
    flex: 0 0 340px; max-width: 340px; margin: 0;
  }
  .booking-block__columns.has-panel .booking-block__col-panel {
    flex: 1 1 auto; width: auto;
  }
}

@media (min-width: 720px) {
  /* Step 2 (form): summary left + form right */
  .booking-block__col-summary {
    flex: 0 0 280px; width: auto;
    position: sticky; top: 2rem; align-self: flex-start;
  }
  .booking-block__col-form { flex: 1 1 auto; width: auto; }
}

/* ── Calendar ─────────────────────────────────────────────────────────── */
.booking-block__calendar { margin-bottom: 0.5rem; }
.booking-block__nav {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 0.75rem;
}
.booking-block__nav-btn {
  background: none;
  border: 1px solid transparent;
  border-radius: 50%;
  width: 36px; height: 36px; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  color: var(--tw-text, #334155);
  transition: background 0.15s, color 0.15s;
}
.booking-block__nav-btn:hover {
  background: color-mix(in srgb, var(--tw-text, #334155) 10%, transparent);
  color: var(--tw-text-heading, #1e293b);
}
.booking-block__nav-btn:disabled { opacity: 0.3; cursor: default; }
.booking-block__nav-btn:disabled:hover {
  background: none; color: var(--tw-text, #334155);
}
.booking-block__nav-btn svg { flex-shrink: 0; }
.booking-block__month {
  font-weight: 700; font-size: 1rem;
  color: var(--tw-text-heading, #1e293b);
  text-transform: capitalize;
}
.booking-block__weekdays {
  display: grid; grid-template-columns: repeat(7, 1fr);
  text-align: center; font-size: 0.7rem;
  color: color-mix(in srgb, var(--tw-text, #334155) 55%, transparent);
  font-weight: 600; margin-bottom: 0.35rem;
  text-transform: uppercase; letter-spacing: 0.05em;
}
.booking-block__days-wrapper { position: relative; }
.booking-block__calendar-loading {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 0.5rem;
  /* Voile LÉGER : la grille reste lisible dessous (on change de mois, on ne
     recharge pas la page). Il n'apparaît qu'après un court délai — une réponse
     rapide (le cas courant, un mois = une requête) ne fait donc rien clignoter. */
  background: color-mix(in srgb, var(--tw-bg, #fff) 60%, transparent);
  z-index: 2; border-radius: var(--tw-radius-field, 8px);
  font-size: 0.8rem;
  color: var(--tw-text, #334155);
  animation: tw-booking-voile 0.2s ease 0.15s both;
}
@keyframes tw-booking-voile { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .booking-block__calendar-loading { animation: none; }
}
.booking-block__days {
  display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px;
}
.booking-block__day {
  border: none; background: none;
  aspect-ratio: 1; min-height: 40px;
  border-radius: 50%;
  font-size: 0.875rem; cursor: default;
  /* jour non-réservable (passé/indispo) : atténué mais LISIBLE. Le token muted est
     conscient du mode (≠ --tw-text à 35% qui devient invisible sur fond sombre, gate).
     PLEINE opacité du muted (l'alpha 78% tombait < AA sur certaines cartes sombres de DNA,
     ex. SUMI dark cr~3.8) — le muted seul reste distinct du jour réservable (--tw-text plein). */
  color: var(--tw-text-muted, #64748b);
  display: flex; align-items: center; justify-content: center;
  position: relative;
  transition: background 0.15s, color 0.15s, transform 0.15s;
}
/* ── L'état de chaque jour se VOIT (convention Calendly / Booking / Airbnb) ──
   Le calendrier montrait tous les jours pareils : le visiteur cliquait à
   l'aveugle. Désormais :
   - réservable : pleine couleur, gras, pastille teintée de l'accent du DNA ;
   - complet : atténué et BARRÉ (il pourrait se libérer, mais pas maintenant) ;
   - fermé, hors fenêtre, passé : atténué seulement ;
   - mois pas encore reçu : neutre, sans promesse dans un sens ni dans l'autre.
   Les jours non réservables restent focalisables (aria-disabled) : le curseur
   dit « non », le lecteur d'écran dit pourquoi (aria-label). */
.booking-block__day {
  --lx-jour-libre: color-mix(in srgb, var(--tw-accent, #3b82f6) 16%, transparent);
  --lx-jour-libre-survol: color-mix(in srgb, var(--tw-accent, #3b82f6) 28%, transparent);
}
.booking-block__day.is-available {
  cursor: pointer; color: var(--tw-text, #334155); font-weight: 600;
  background: var(--lx-jour-libre);
}
.booking-block__day.is-available:hover {
  background: var(--lx-jour-libre-survol);
  color: var(--tw-text-heading, var(--tw-text, #1e293b));
}
.booking-block__day[aria-disabled="true"] { cursor: not-allowed; }
.booking-block__day.is-full {
  text-decoration: line-through;
  text-decoration-thickness: 1.5px;
  text-decoration-color: color-mix(in srgb, var(--tw-text-muted, #64748b) 80%, transparent);
}
.booking-block__day.is-loading { color: var(--tw-text-muted, #64748b); cursor: progress; }
.booking-block__day:focus-visible {
  outline: 2px solid var(--tw-accent, #3b82f6);
  outline-offset: 1px;
}
/* Jour « départ seulement » : sa nuit est déjà réservée (on n'y dort pas) mais on
   peut y partir — le moteur accepte les séjours dos-à-dos. Demi-disque diagonal
   (convention Booking/Airbnb) : la moitié « nuit » atténuée, la moitié « départ »
   libre. Sans ce repère le jour paraîtrait réservable comme un autre, alors qu'on
   ne peut QUE le choisir comme date de sortie (audit 2026-07-14). Le hover
   ci-dessus reste prioritaire (déclaré avant, même spécificité → dernier gagne),
   d'où le :hover exclu ici. */
.booking-block__day.is-checkout-only:not(.is-selected):not(.is-checkout):not(:hover) {
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--tw-text-muted, #64748b) 20%, transparent) 0 49.5%,
    transparent 50.5%);
}
.booking-block__day.is-selected,
.booking-block__day.is-checkout {
  background: var(--tw-accent, #3b82f6);
  color: var(--tw-on-accent, #fff);
  font-weight: 700;
}
.booking-block__day.is-in-range {
  background: color-mix(in srgb, var(--tw-accent, #3b82f6) 10%, transparent);
  border-radius: 0;
}
.booking-block__day.is-in-range.is-selected { border-radius: 50% 0 0 50%; }
.booking-block__day.is-in-range.is-checkout { border-radius: 0 50% 50% 0; }
/* Today: small dot below the number (Cal.com / Calendly pattern) */
.booking-block__day.is-today::after {
  content: '';
  position: absolute; bottom: 3px; left: 50%;
  transform: translateX(-50%);
  width: 4px; height: 4px; border-radius: 50%;
  background: var(--tw-accent, #3b82f6);
}
.booking-block__day.is-today.is-selected::after { background: var(--tw-on-accent, #fff); }
/* dates du mois adjacent : atténuées mais LISIBLES. PAS d'opacité par-dessus la couleur
   de base à 35% (35%×opacité = ~14% = invisible sur fond sombre, gate). On pose une
   couleur muted DÉDIÉE (token conscient du mode → lisible en clair ET sombre). */
.booking-block__day.is-other-month { color: color-mix(in srgb, var(--tw-text-muted, #64748b) 76%, transparent); opacity: 1; }

/* ── Period hint ──────────────────────────────────────────────────────── */
.booking-block__period-hint {
  display: flex; align-items: center; justify-content: center;
  gap: 0.5rem; font-size: 0.85rem;
  color: color-mix(in srgb, var(--tw-text, #334155) 74%, transparent);
  padding: 0.75rem 0;
}
.booking-block__nights-badge {
  display: inline-block;
  background: color-mix(in srgb, var(--tw-accent, #3b82f6) 10%, transparent);
  color: var(--tw-accent, #3b82f6);
  font-size: 0.75rem; font-weight: 600;
  padding: 0.15rem 0.5rem; border-radius: 999px;
}

/* ── Hint (small text below calendar, before date selection) ──────────── */
.booking-block__hint {
  display: flex; align-items: center; justify-content: center;
  gap: 0.35rem; padding: 0.5rem 0;
  color: color-mix(in srgb, var(--tw-text, #334155) 40%, transparent);
  font-size: 0.8rem; text-align: center;
}
.booking-block__hint svg { opacity: 0.3; width: 16px; height: 16px; }
/* HELO 74915 — l'aperçu (sans serveur) montre des disponibilités d'exemple et
   le dit : une ligne discrète au-dessus du calendrier, jamais sur le site publié. */
.booking-block__preview-note {
  margin: 0 0 0.6rem; text-align: center;
  font-size: 0.75rem; line-height: 1.4;
  color: color-mix(in srgb, var(--tw-text, #334155) 55%, transparent);
}

/* #158 — refus structurel : le groupe dépasse ce que la ressource peut accueillir.
   Ce n'est ni une erreur ni un succès, c'est une information — d'où le ton neutre
   (la surface du thème, pas un rouge d'alerte) : le visiteur n'a rien fait de mal,
   il doit seulement appeler. Ce bloc REMPLACE le calendrier, il ne le surmonte pas. */
.booking-block__party-block {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 1.25rem 1rem;
  border: 1px solid var(--tw-border, rgba(0, 0, 0, 0.12));
  border-radius: var(--tw-radius-card, 10px);
  background: var(--tw-surface-raised, var(--tw-surface, #fff));
  color: var(--tw-text, #334155);
  font-size: 0.9rem;
  line-height: 1.45;
}
.booking-block__party-block svg { flex-shrink: 0; opacity: 0.55; margin-top: 0.1rem; }

/* ── Duration tabs ────────────────────────────────────────────────────── */
.booking-block__duration-tabs { margin-bottom: 1rem; }
.booking-block__duration-label {
  font-size: 0.75rem; font-weight: 600;
  color: color-mix(in srgb, var(--tw-text, #334155) 55%, transparent);
  text-transform: uppercase; letter-spacing: 0.05em;
  margin-bottom: 0.5rem; display: block;
}
.booking-block__duration-group { display: flex; gap: 0.375rem; flex-wrap: wrap; }
.booking-block__duration-btn {
  padding: 0.5rem 1rem;
  min-height: 40px;
  border: 1px solid var(--tw-border, #e2e8f0);
  border-radius: var(--tw-radius-button, var(--tw-radius-field, 6px));
  background: transparent;
  color: var(--tw-text, #334155);
  font-size: 0.8rem; font-weight: 500; cursor: pointer;
  transition: all 0.15s;
}
.booking-block__duration-btn:hover {
  border-color: var(--tw-accent, #3b82f6);
  color: var(--tw-accent, #3b82f6);
}
.booking-block__duration-btn.is-active {
  background: var(--tw-accent, #3b82f6);
  color: var(--tw-on-accent, #fff);
  border-color: var(--tw-accent, #3b82f6);
}

/* ── Time slots ──────────────────────────────────────────────────────── */
.booking-block__slots { margin-bottom: 1rem; }
.booking-block__date-label {
  font-weight: 600; margin-bottom: 0.75rem; font-size: 0.9rem;
  color: var(--tw-text-heading, #1e293b);
}
.booking-block__slot-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
  gap: 0.4rem;
}
.booking-block__slot {
  display: flex; flex-direction: column; align-items: center;
  justify-content: center;
  gap: 0.1rem;
  min-height: 44px;
  border: 1px solid var(--tw-border, rgba(0,0,0,0.08));
  border-radius: var(--tw-radius-button, var(--tw-radius-field, 8px));
  padding: 0.5rem 0.35rem; cursor: pointer;
  background: transparent;
  color: var(--tw-text, #334155);
  text-align: center;
  transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
}
.booking-block__slot:hover {
  border-color: var(--tw-accent, #3b82f6);
  background: color-mix(in srgb, var(--tw-accent, #3b82f6) 8%, transparent);
}
.booking-block__slot.is-selected {
  background: var(--tw-accent, #3b82f6);
  color: var(--tw-on-accent, #fff);
  border-color: var(--tw-accent, #3b82f6);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--tw-accent, #3b82f6) 25%, transparent);
}
.booking-block__slot-time { font-size: 0.85rem; font-weight: 600; }
.booking-block__slot-badge { font-size: 0.65rem; opacity: 0.65; }
.booking-block__slot.is-selected .booking-block__slot-badge { opacity: 0.8; }
.booking-block__slot-price {
  font-size: 0.7rem; font-weight: 600;
  color: var(--tw-accent, #3b82f6);
}
.booking-block__slot.is-selected .booking-block__slot-price {
  color: var(--tw-on-accent, #fff);
}

/* ── No slots ─────────────────────────────────────────────────────────── */
.booking-block__no-slots {
  text-align: center; padding: 1.5rem 1rem;
  color: color-mix(in srgb, var(--tw-text, #334155) 50%, transparent);
  font-size: 0.85rem;
}

/* ── Sub-resources (rooms/cabins/tables) ──────────────────────────────── */
.booking-block__resources { margin-bottom: 1rem; }
.booking-block__resources-scroll {
  display: flex; gap: 0.5rem; overflow-x: auto;
  padding-bottom: 0.25rem;
  scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch;
}
.booking-block__resource-card {
  flex: 0 0 auto; min-width: 130px; padding: 0.65rem;
  border: 1px solid var(--tw-border, rgba(0,0,0,0.08));
  border-radius: var(--tw-radius-field, 8px);
  background: transparent; cursor: pointer;
  text-align: center; display: flex; flex-direction: column; gap: 0.2rem;
  transition: all 0.15s; scroll-snap-align: start;
}
.booking-block__resource-card:hover {
  border-color: var(--tw-accent, #3b82f6);
  background: color-mix(in srgb, var(--tw-accent, #3b82f6) 5%, transparent);
}
.booking-block__resource-card.is-selected {
  border-color: var(--tw-accent, #3b82f6);
  background: color-mix(in srgb, var(--tw-accent, #3b82f6) 8%, transparent);
  box-shadow: 0 0 0 2px var(--tw-accent, #3b82f6);
}
.booking-block__resource-card.is-disabled {
  opacity: 0.35; cursor: not-allowed;
  pointer-events: none;
}
.booking-block__resource-name {
  font-weight: 600; font-size: 0.85rem;
  color: var(--tw-text-heading, #1e293b);
}
.booking-block__resource-capacity {
  font-size: 0.7rem;
  color: color-mix(in srgb, var(--tw-text, #334155) 55%, transparent);
}
.booking-block__resource-price {
  font-size: 0.8rem; font-weight: 600;
  color: var(--tw-accent, #3b82f6);
}

/* ── Notice (period unavailability — themed, visible) ─────────────────── */
.booking-block__notice {
  display: flex; align-items: flex-start; gap: 0.5rem;
  padding: 1rem;
  background: color-mix(in srgb, var(--tw-accent, #3b82f6) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--tw-accent, #3b82f6) 20%, transparent);
  border-radius: var(--tw-radius-field, 8px);
  color: var(--tw-text, #334155);
  font-size: 0.85rem; line-height: 1.5;
}
.booking-block__notice svg {
  flex-shrink: 0; margin-top: 0.1rem;
  color: var(--tw-accent, #3b82f6);
}
/* Réservations en démonstration : le message qui remplace l'envoi, posé au-dessus du bouton. */
.booking-block__notice--demo {
  margin-bottom: 0.75rem;
}

/* ── Party size (numbered pill buttons) ───────────────────────────────── */
.booking-block__party {
  padding: 0.75rem 0; margin-bottom: 0.5rem;
}
/* Variant: above calendar (periods mode) — centered, with max-width matching calendar */
.booking-block__party--top {
  max-width: 420px; margin: 0 auto 0.75rem;
}
.booking-block__party-label {
  font-weight: 600; font-size: 0.75rem;
  color: color-mix(in srgb, var(--tw-text, #334155) 55%, transparent);
  text-transform: uppercase; letter-spacing: 0.05em;
  margin-bottom: 0.5rem; display: block;
}
.booking-block__party-pills {
  display: flex; gap: 0.35rem; flex-wrap: wrap;
}
.booking-block__party-pill {
  width: 36px; height: 36px;
  border: 1px solid var(--tw-border, #e2e8f0);
  border-radius: 50%;
  background: transparent;
  color: var(--tw-text, #334155);
  font-size: 0.85rem; font-weight: 500;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; transition: all 0.15s;
}
.booking-block__party-pill:hover {
  border-color: var(--tw-accent, #3b82f6);
  color: var(--tw-accent, #3b82f6);
}
.booking-block__party-pill.is-active {
  background: var(--tw-accent, #3b82f6);
  color: var(--tw-on-accent, #fff);
  border-color: var(--tw-accent, #3b82f6);
}

/* ── Price total ──────────────────────────────────────────────────────── */
.booking-block__price-total {
  display: flex; align-items: center; justify-content: space-between;
  padding: 0.65rem 0.85rem;
  background: color-mix(in srgb, var(--tw-text, #334155) 5%, transparent);
  border-radius: var(--tw-radius-field, 8px);
  margin-bottom: 1rem; font-size: 0.9rem;
  color: var(--tw-text, #334155);
}
.booking-block__price-total strong {
  font-size: 1.1rem; color: var(--tw-accent, #3b82f6);
}

/* ── Live stay summary ("5 nights × €280 = €1,400") ──────────────────────
   Shown in the selection panel as soon as the date range / slot is valid.
   Token-only so every DNA skins it (same family as price-total). */
.booking-block__stay-summary {
  display: flex; align-items: baseline; justify-content: space-between;
  flex-wrap: wrap; gap: 0.35rem 0.65rem;
  padding: 0.65rem 0.85rem;
  background: color-mix(in srgb, var(--tw-text, #334155) 5%, transparent);
  border-radius: var(--tw-radius-field, 8px);
  margin-bottom: 1rem; font-size: 0.9rem;
  color: var(--tw-text, #334155);
}
.booking-block__stay-units {
  display: inline-flex; align-items: baseline; flex-wrap: wrap; gap: 0.4rem;
}
.booking-block__stay-rate {
  color: color-mix(in srgb, var(--tw-text, #334155) 74%, transparent);
}
.booking-block__stay-total {
  font-size: 1.1rem; color: var(--tw-accent, #3b82f6);
  white-space: nowrap;
}

/* ── Buttons (DNA button brick tokens --tw-btn-* / --tw-accent) */
.booking-block__actions { padding: 0.5rem 0; }
.booking-block__btn-primary {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem;
  padding: 0.65rem 1.5rem;
  border-width: var(--tw-border-width, 0px);
  border-style: solid; border-color: currentColor;
  border-radius: var(--tw-radius-button, 8px);
  box-shadow: var(--tw-shadow-button, none);
  background: var(--tw-accent, #3b82f6);
  color: var(--tw-on-accent, #fff);
  font-family: var(--theme-font-body, inherit);
  font-size: 0.9rem;
  font-weight: var(--tw-btn-weight, 600);
  cursor: pointer;
  transition: transform 0.15s, opacity 0.15s, background-color 0.15s, box-shadow 0.15s;
}
.booking-block__btn-primary:hover { opacity: 0.9; }
.booking-block__btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
.booking-block__btn-primary--full { width: 100%; }
.booking-block__btn-secondary {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.4rem;
  padding: 0.55rem 1.25rem;
  border: 1px solid var(--tw-border, rgba(0,0,0,0.12));
  border-radius: var(--tw-radius-button, 8px);
  background: transparent;
  color: var(--tw-text, #334155);
  font-family: var(--theme-font-body, inherit);
  font-size: 0.85rem; font-weight: 500; cursor: pointer;
  transition: all 0.15s;
}
.booking-block__btn-secondary:hover {
  border-color: var(--tw-accent, #3b82f6);
  color: var(--tw-accent, #3b82f6);
}
.booking-block__btn-back {
  display: inline-flex; align-items: center; gap: 0.3rem;
  background: none; border: none;
  color: var(--tw-accent, #3b82f6);
  cursor: pointer; font-size: 0.85rem; font-weight: 500;
  padding: 0.5rem 0; margin-top: 0.75rem;
  transition: opacity 0.15s;
}
.booking-block__btn-back:hover { opacity: 0.7; }

/* ── Step 2 form layout: summary left + form right on desktop ─────────── */
.booking-block__form-layout {
  display: flex; flex-direction: column; gap: 1.25rem;
}
@media (min-width: 680px) {
  .booking-block__form-layout {
    flex-direction: row; align-items: flex-start; gap: 1.5rem;
  }
  .booking-block__form-layout .booking-block__col-summary {
    flex: 0 0 280px; width: auto;
    position: sticky; top: 2rem;
  }
  .booking-block__form-layout .booking-block__col-form {
    flex: 1 1 auto; width: auto;
  }
}

/* ── Summary card (step 2) ────────────────────────────────────────────── */
.booking-block__summary-card {
  background: var(--tw-surface, #f8fafc);
  border: 1px solid var(--tw-border, rgba(0,0,0,0.06));
  border-radius: var(--tw-radius-card, 12px);
  padding: 1.25rem;
}
.booking-block__summary-title {
  font-size: 0.75rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.05em;
  color: color-mix(in srgb, var(--tw-text, #334155) 55%, transparent);
  margin-bottom: 0.75rem;
}
.booking-block__summary-lines { display: flex; flex-direction: column; gap: 0; }
.booking-block__summary-line {
  display: flex; justify-content: space-between; align-items: baseline;
  padding: 0.3rem 0; font-size: 0.85rem;
}
.booking-block__summary-label {
  color: color-mix(in srgb, var(--tw-text, #334155) 74%, transparent);
}
.booking-block__summary-value {
  font-weight: 600; color: var(--tw-text-heading, #1e293b);
}
.booking-block__summary-total {
  display: flex; justify-content: space-between; align-items: center;
  padding-top: 0.65rem; margin-top: 0.5rem;
  border-top: 1px solid var(--tw-border, #e2e8f0);
  font-size: 0.9rem; color: var(--tw-text-heading, #1e293b);
}
.booking-block__summary-total strong {
  font-size: 1.1rem; color: var(--tw-accent, #3b82f6);
}

/* ── Form fields ──────────────────────────────────────────────────────── */
.booking-block__form-group { margin-bottom: 0.875rem; }
.booking-block__label {
  display: flex; align-items: center; gap: 0.375rem;
  font-size: 0.8rem; font-weight: 600;
  color: var(--tw-text, #334155);
  margin-bottom: 0.3rem;
}
.booking-block__badge-required { color: #ef4444; font-weight: 700; }
.booking-block__badge-optional {
  font-size: 0.7rem; font-weight: 400;
  color: color-mix(in srgb, var(--tw-text, #334155) 76%, transparent);
  font-style: italic;
}
.booking-block__input,
.booking-block__textarea {
  width: 100%;
  border: 1px solid var(--tw-border, #e2e8f0);
  border-radius: var(--tw-radius-field, 8px);
  padding: 0.55rem 0.75rem; font-size: 0.875rem;
  font-family: inherit;
  color: var(--tw-text-heading, #1e293b);
  background: color-mix(in srgb, var(--tw-bg, #fff) 80%, transparent);
  transition: border-color 0.15s, box-shadow 0.15s;
}
.booking-block__input:focus,
.booking-block__textarea:focus {
  outline: none;
  border-color: var(--tw-accent, #3b82f6);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--tw-accent, #3b82f6) 12%, transparent);
}
.booking-block__textarea { resize: vertical; min-height: 68px; }

/* ── Duplicate warning ────────────────────────────────────────────────── */
.booking-block__duplicate-warning {
  /* DNA-aware : la SURFACE du DNA teintée vers l'ambre (sémantique « warning »), texte DNA,
     bord ambre comme signal → s'adapte au mode (≠ boîte claire en dur sur DNA sombre). */
  background: color-mix(in srgb, var(--tw-surface, #fffbeb) 82%, #f59e0b); border: 1px solid #f59e0b;
  border-radius: var(--tw-radius-field, 8px);
  padding: 1rem; margin-bottom: 1rem; font-size: 0.85rem; color: var(--tw-text, #92400e);
}
.booking-block__duplicate-warning p { margin-bottom: 0.75rem; }
.booking-block__duplicate-actions { display: flex; gap: 0.5rem; justify-content: flex-end; }

/* ── Confirmation (step 3) ────────────────────────────────────────────── */
.booking-block__confirmation { text-align: center; padding: 3rem 1rem; }
.booking-block__confirm-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 64px; height: 64px; border-radius: 50%;
  background: color-mix(in srgb, var(--tw-accent, #3b82f6) 12%, transparent);
  color: var(--tw-accent, #3b82f6);
  margin-bottom: 1rem;
  animation: bk-check-in 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.booking-block__confirm-title {
  font-family: var(--theme-font-heading, inherit);
  font-size: 1.25rem; font-weight: 700;
  color: var(--tw-text-heading, #1e293b);
  margin-bottom: 0.5rem;
}
.booking-block__confirm-id {
  font-size: 0.85rem;
  color: color-mix(in srgb, var(--tw-text, #334155) 74%, transparent);
  margin-bottom: 0.25rem;
}
.booking-block__confirm-email {
  font-size: 0.8rem;
  color: color-mix(in srgb, var(--tw-text, #334155) 74%, transparent);
  margin-bottom: 1.5rem;
}

/* ── Error ─────────────────────────────────────────────────────────────── */
.booking-block__error {
  display: flex; align-items: center; gap: 0.5rem;
  /* DNA-aware : surface DNA teintée rouge (sémantique « error »), texte DNA, bord rouge signal. */
  background: color-mix(in srgb, var(--tw-surface, #fef2f2) 82%, #ef4444); color: var(--tw-text, #b91c1c);
  padding: 0.75rem 1rem; border-radius: var(--tw-radius-field, 8px);
  font-size: 0.85rem; margin-top: 1rem; border: 1px solid #ef4444;
}
.booking-block__error svg { flex-shrink: 0; }

/* ── Responsive — mobile ──────────────────────────────────────────────── */
@media (max-width: 679px) {
  .booking-block { padding-left: 0.25rem; padding-right: 0.25rem; }
  .booking-block--card { padding: 1.25rem; }
  .booking-block__steps { margin-bottom: 1rem; }
  .booking-block__step-label { display: none; }
  .booking-block__step-line { width: 24px; }
  .booking-block__slot-grid { grid-template-columns: repeat(3, 1fr); }
  .booking-block__resource-card { min-width: 110px; }
  /* WCAG 2.5.5 — touch targets ≥ 44px */
  .booking-block__duration-btn { min-height: 44px; padding: 0.6rem 1rem; }
  .booking-block__slot { min-height: 48px; }
  .booking-block__party-pill { min-height: 44px; min-width: 44px; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   HERO BLOCK — DNA-neutral baseline (R.5)
   ───────────────────────────────────────────────────────────────────────────
   Structure + layout only. All decorative treatments live in the per-DNA skin
   files (apps/toweb/src/shared/dna-skins/dna-*.css), scoped by
   `[data-dna="<id>"]` on the editor canvas root and on the generated site body.
   This block consumes theme tokens (`--theme-*`) so user palette/font
   overrides naturally propagate. Keep this layer SHAPE-NEUTRAL — anything
   distinctive (gradients, signature radii, accent treatments) goes in the
   skin, never here.
   ═══════════════════════════════════════════════════════════════════════════ */

/* RÉ-ÉCRIT BRIQUES (Yann 2026-06-07, DNA-BLOCKS-SPEC §3.3) : le LAYOUT + le look
   du hero vivent désormais dans la couche briques (bricks/blocks-base.css :
   .hero-block / __text / __media / __ctas / --layout-{text-only|text-image-right|
   text-image-left|text-image-background} / --align-{left|center|right} + les briques
   .tw-eyebrow/.tw-h1/.tw-lead/.tw-button/.tw-cta-link/.tw-media + placeholder &
   eyebrow-pleine-largeur). Ne restent ICI que les affordances ÉDITEUR : le slot
   média vide et le contour de sélection. */
.hero-block__media--empty {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 4 / 5;
  background: var(--tw-surface, rgba(0, 0, 0, 0.04));
  border: 1px dashed var(--tw-border, rgba(0, 0, 0, 0.15));
  border-radius: var(--tw-radius-card, var(--tw-radius-card, 8px));
}
.hero-block__add-image {
  appearance: none;
  background: transparent;
  border: 0;
  cursor: pointer;
  font-family: var(--theme-font-body, inherit);
  color: var(--tw-text-muted, currentColor);
  opacity: 0.7;
  padding: 0.5rem 1rem;
}
.hero-block__add-image:hover { opacity: 1; }

/* ── Selected state (editor only) ────────────────────────────────────────── */
.hero-block.ProseMirror-selectednode {
  outline: 2px solid var(--tw-accent, #3b82f6);
  outline-offset: 4px;
  border-radius: var(--tw-radius-card, 4px);
}

/* ── Boutons : icône et système visé (2026-09-25) ────────────────────────────
   L'icône suit la couleur du texte, en trait fin (la mode Lucide actuelle :
   1,75 au lieu de 2). Le bouton du système du visiteur passe en tête dès le
   premier rendu (`<html data-os>`, posé avant la peinture) ; sa variante est
   échangée au chargement (`os-visiteur.ts`). */
.tw-button--icone {
  display: inline-flex;
  align-items: center;
  gap: 0.55em;
}
.tw-button__icone { display: inline-flex; flex-shrink: 0; }
.tw-button__icone svg { width: 1.15em; height: 1.15em; } /* le trait vient du réglage du site, cuit dans le SVG à la génération */
html[data-os="windows"] .cta-block > .tw-button[data-os="windows"],
html[data-os="macos"] .cta-block > .tw-button[data-os="macos"],
html[data-os="linux"] .cta-block > .tw-button[data-os="linux"] { order: -1; }

/* ============================================================================
   DOCUMENTATION (#335) — accueil, chapitre, article (`astro/doc-generator.ts`).
   Mobile d'abord : une colonne, la barre latérale repliée derrière « Sommaire »
   (quand le script la déclare repliable ; sans script elle reste dépliée).
   Dès 900 px : barre à gauche ; dès 1200 px : « Sur cette page » à droite.
   Couleurs, rayons et typographie viennent du DNA (--tw-*) ; les variables
   internes sont en --lx-doc-*.
   ============================================================================ */
.toweb-doc {
  --lx-doc-trait: color-mix(in oklab, var(--tw-text, #111) 12%, transparent);
  --lx-doc-doux: var(--tw-text-muted, color-mix(in oklab, var(--tw-text, #111) 72%, transparent));   /* l'atténué du DNA (4,5:1), plus 62 % du texte — 3,9:1 (axe-core, audit du 2026-09-28) */
  --lx-doc-haut: calc(var(--mv-entete-h, 72px) + 1.25rem);
  padding-block: clamp(1.5rem, 4vw, 3rem) clamp(3rem, 6vw, 5rem);
}
.toweb-doc :is(h2, h3)[id] { scroll-margin-top: var(--lx-doc-haut); }
/* `main` est une boîte flex : sans largeur, le cadre se réduisait à son contenu. */
.toweb-doc > .toweb-container { width: 100%; box-sizing: border-box; }
/* En-tête superposé : il n'y a pas de section de tête ici pour prendre la marge. */
.content-section--system[data-entete="overlay"] ~ main.toweb-doc,
.content-section--system[data-entete="overlay"] + * main.toweb-doc { padding-top: calc(var(--mv-entete-h, 72px) + clamp(1.5rem, 4vw, 3rem)); }
/* Les titres du contenu d'une doc : AUCUNE règle ici. Taille, interligne et
   marges viennent de l'échelle de LECTURE du style (`buildReadingRules`,
   shared/theme/themeElementRules.ts), la même dans l'éditeur et sur le site
   (Yann, 2026-10-08). */

/* Accueil */
.doc-accueil__tete { max-width: 46rem; margin: 0 auto clamp(1.75rem, 4vw, 3rem); text-align: center; display: grid; grid-template-columns: minmax(0, 1fr); gap: 1rem; justify-items: center; }
/* ↑ `minmax(0, 1fr)` : sans colonne déclarée, la piste prenait la largeur
   intrinsèque du champ de filtre, et le titre, le chapô et le champ débordaient
   de 15 px à 320 px de large (WCAG 1.4.10, audit de lauyan.com du 2026-09-29). */
.doc-filtre--accueil { width: min(100%, 30rem); }
.doc-filtre__champ {
  width: 100%; box-sizing: border-box; padding: .7rem 1rem; font: inherit; font-size: .95rem;
  color: var(--tw-text, #111); background: var(--tw-surface, #fff);
  border: 1px solid var(--lx-doc-trait); border-radius: var(--tw-radius-button, 10px);
}
.doc-filtre__champ:focus-visible { outline: 2px solid var(--tw-accent, #3b82f6); outline-offset: 1px; }
.doc-cartes { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 19rem), 1fr)); gap: clamp(1rem, 2vw, 1.5rem); }
.doc-carte { padding: 1.4rem 1.5rem; border-radius: var(--tw-radius-card, 14px); border: 1px solid var(--lx-doc-trait); background: var(--tw-surface, #fff); display: flex; flex-direction: column; gap: .6rem; }
.doc-carte__titre { margin: 0; font-size: var(--tw-size-h4, 1.25rem); line-height: 1.25; }
.doc-carte__titre a { color: inherit; text-decoration: none; }
.doc-carte__titre a:hover { color: var(--tw-prose-link, var(--tw-accent, #3b82f6)); }
.doc-carte__resume { margin: 0; color: var(--lx-doc-doux); font-size: .95rem; }
.doc-carte__liste { list-style: none; margin: .25rem 0 0; padding: .6rem 0 0; border-top: 1px solid var(--lx-doc-trait); display: grid; gap: .35rem; }
.doc-carte__liste a, .doc-carte__plus { color: var(--tw-text, #111); text-decoration: none; font-size: .95rem; }
.doc-carte__liste a:hover, .doc-carte__plus:hover { color: var(--tw-prose-link, var(--tw-accent, #3b82f6)); }
.doc-carte__plus { color: var(--tw-prose-link, var(--tw-accent, #3b82f6)); font-weight: 600; font-size: .9rem; }
.doc-vide { text-align: center; color: var(--lx-doc-doux); }

/* Cadre article : barre · article · sommaire */
.doc-cadre { display: grid; grid-template-columns: minmax(0, 1fr); gap: clamp(1.25rem, 3vw, 2.5rem); align-items: start; }
.doc-article { min-width: 0; max-width: 46rem; }
/* Titre d'un article de doc : suit « Taille des titres » (formule de tailleDeTitreCss). */
.doc-article__titre { margin: .35rem 0 .75rem; font-size: calc(min(clamp(2rem, 1.4rem + 2.2vw, 2.9rem), var(--tw-size-base, 1rem)) + max(clamp(2rem, 1.4rem + 2.2vw, 2.9rem) - var(--tw-size-base, 1rem), 0px) * var(--tw-heading-scale, 1)); line-height: 1.1; text-wrap: balance; }
.doc-article__chapo { margin: 0 0 1.75rem; color: var(--lx-doc-doux); }
.doc-article__contenu > :first-child { margin-top: 0; }
.doc-article__contenu :is(h2) { margin-top: 2.4rem; }
.doc-article__contenu :is(h3) { margin-top: 1.8rem; }
/* Les listes restent DANS la colonne de l'article (les puces et numéros débordaient à gauche). */
.doc-article__contenu :is(ul, ol):not([class]) { padding-inline-start: 1.5em; margin-block: .6rem 1rem; }
.doc-article__contenu li + li { margin-top: .3rem; }
.doc-maj { margin-top: 2.5rem; color: var(--lx-doc-doux); }

/* Fil d'Ariane */
.doc-ariane ol { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: .35rem; font-size: .85rem; color: var(--lx-doc-doux); }
.doc-ariane li + li::before { content: "›"; margin-inline-end: .35rem; opacity: .6; }
[dir="rtl"] .doc-ariane li + li::before { content: "‹"; }
.doc-ariane a { color: inherit; text-decoration: none; }
.doc-ariane a:hover { color: var(--tw-prose-link, var(--tw-accent, #3b82f6)); }

/* Barre latérale */
.doc-barre { font-size: .93rem; }
.doc-barre__corps { display: grid; gap: .75rem; }
.doc-barre__racine { font-weight: 700; color: var(--tw-text, #111); text-decoration: none; }
.doc-barre__arbre, .doc-barre__arbre ul { list-style: none; margin: 0; padding: 0; }
.doc-barre__arbre { display: grid; gap: .15rem; }
.doc-barre__groupe summary { cursor: pointer; font-weight: 600; padding: .4rem .5rem; border-radius: 8px; list-style: none; display: flex; justify-content: space-between; align-items: center; }
.doc-barre__groupe summary::-webkit-details-marker { display: none; }
.doc-barre__chapitre { flex: 1; color: inherit; text-decoration: none; }
.doc-barre__chapitre:hover { color: var(--tw-prose-link, var(--tw-accent, #3b82f6)); }
.doc-barre__chapitre[aria-current="page"] { color: var(--tw-prose-link, var(--tw-accent, #3b82f6)); }
.doc-barre__groupe summary::after { content: ""; width: .45rem; height: .45rem; border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; transform: rotate(-45deg); transition: transform .2s ease; opacity: .55; }
.doc-barre__groupe details[open] > summary::after { transform: rotate(45deg); }
[dir="rtl"] .doc-barre__groupe summary::after { transform: rotate(135deg); }
[dir="rtl"] .doc-barre__groupe details[open] > summary::after { transform: rotate(45deg); }
.doc-barre__groupe ul { padding-inline-start: .6rem; margin: .1rem 0 .4rem; border-inline-start: 1px solid var(--lx-doc-trait); margin-inline-start: .5rem; }
.doc-barre__lien { display: block; padding: .32rem .6rem; border-radius: 8px; color: var(--lx-doc-doux); text-decoration: none; line-height: 1.35; }
.doc-barre__lien:hover { color: var(--tw-text, #111); background: color-mix(in oklab, var(--tw-text, #111) 5%, transparent); }
.doc-barre__lien[aria-current="page"] { color: var(--tw-prose-link, var(--tw-accent, #3b82f6)); font-weight: 600; background: color-mix(in oklab, var(--tw-accent, #3b82f6) 10%, transparent); }
.doc-barre__mobile { display: none; }
/* Repliable (script présent, petit écran) : « Sommaire » ouvre la barre. */
@media (max-width: 899px) {
  .doc-barre--repliable { border: 1px solid var(--lx-doc-trait); border-radius: var(--tw-radius-card, 12px); padding: .25rem .75rem; }
  .doc-barre--repliable .doc-barre__mobile { display: block; }
  .doc-barre--repliable .doc-barre__mobile > summary { cursor: pointer; font-weight: 600; padding: .5rem 0; }
  .doc-barre--repliable:not(.is-ouverte) .doc-barre__corps { display: none; }
  .doc-barre--repliable.is-ouverte .doc-barre__corps { padding-bottom: .75rem; }
}
@media (min-width: 900px) {
  .doc-cadre { grid-template-columns: 15.5rem minmax(0, 1fr); }
  .doc-barre { position: sticky; top: var(--lx-doc-haut); max-height: calc(100vh - var(--lx-doc-haut) - 1rem); overflow: auto; padding-inline-end: .5rem; scrollbar-width: thin; }
}

/* Sur cette page — et le bloc « Sommaire » (tocBlock, beta.13), qui reprend
   sa liste (`listeDuSommaire`) et son allure : un seul style de sommaire. */
.doc-sommaire { display: none; font-size: .85rem; }
.doc-sommaire__titre, .toc-block__titre { margin: 0 0 .6rem; font-weight: 700; font-size: .75rem; letter-spacing: .08em; text-transform: uppercase; color: var(--lx-doc-doux); }
.doc-sommaire ul, .toc-block__liste { list-style: none; margin: 0; padding: 0; padding-inline-start: .75rem; border-inline-start: 1px solid var(--lx-doc-trait); display: grid; gap: .4rem; }
.doc-sommaire a, .toc-block__liste a { color: var(--lx-doc-doux); text-decoration: none; display: block; line-height: 1.35; }
.doc-sommaire a:hover, .doc-sommaire a.is-actif, .toc-block__liste a:hover { color: var(--tw-prose-link, var(--tw-accent, #3b82f6)); }
.doc-sommaire a.is-actif { font-weight: 600; }
.doc-sommaire__n3, .toc-block__n3 { padding-inline-start: .75rem; }

/* Le bloc « Sommaire » dans le contenu : une carte discrète aux jetons du DNA
   (texte, surface, rayon), claire ou sombre avec lui. Mobile d'abord : replié
   derrière son titre (`<details>` sans `open`) ; dès 768 px, déplié et le titre
   n'est plus un bouton (`::details-content`, et scripts/sommaire.ts pour les
   navigateurs qui ne le connaissent pas). */
.toc-block {
  --lx-doc-trait: color-mix(in oklab, var(--tw-text, #111) 12%, transparent);
  --lx-doc-doux: var(--tw-text-muted, color-mix(in oklab, var(--tw-text, #111) 72%, transparent));
  box-sizing: border-box; width: 100%; max-width: 42rem; margin-block: 1.25rem 1.75rem;
  padding: .85rem 1.1rem; font-size: .95rem; text-align: start;
  border: 1px solid var(--lx-doc-trait); border-radius: var(--tw-radius-card, 12px);
  background: color-mix(in oklab, var(--tw-text, #111) 3%, var(--tw-surface, transparent));
}
.toc-block__repli > summary { list-style: none; cursor: pointer; display: flex; align-items: center; justify-content: space-between; gap: .75rem; margin: 0; min-height: 1.75rem; }
.toc-block__repli > summary::-webkit-details-marker { display: none; }
.toc-block__repli > summary::after { content: ""; flex: none; width: .45rem; height: .45rem; border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; transform: rotate(45deg) translate(-2px, -2px); transition: transform .2s ease; opacity: .7; }
.toc-block__repli[open] > summary::after { transform: rotate(-135deg) translate(-2px, -2px); }
.toc-block__repli > summary:focus-visible { outline: 2px solid var(--tw-accent, #3b82f6); outline-offset: 3px; border-radius: 4px; }
.toc-block__repli[open] > .toc-block__liste, .toc-block > .toc-block__liste { margin-top: .6rem; }
/* Dans une section, la règle des liens du contenu (couleur `--tw-prose-link`,
   très spécifique) l'emporterait : on lui donne la couleur atténuée du
   sommaire par sa propre variable, posée sur le lien lui-même. Les liens ont
   une classe : le soulignement des liens DANS le texte ne les vise pas. */
/* ⚠️ `!important`, ET C'EST VOULU : le sommaire se pose dans un texte dont
   les liens sont stylés par la section, l'article (`.post-content a`, blog)
   et chacun des 48 DNA, avec des sélecteurs très spécifiques. Mesuré à
   l'œil le 09/10 : liens soulignés et couleur d'accent dans le blog. Une
   liste de navigation n'est pas un lien DANS le texte (WCAG 1.4.1 ne la vise
   pas) : sa couleur et son absence de soulignement ne se négocient pas. */
.toc-block .toc-block__liste a.toc-block__lien { padding-block: .1rem; --tw-prose-link: var(--lx-doc-doux); color: var(--lx-doc-doux) !important; text-decoration: none !important; }
.toc-block .toc-block__liste a.toc-block__lien:hover, .toc-block .toc-block__liste a.toc-block__lien:focus-visible { --tw-prose-link: var(--tw-accent, #3b82f6); color: var(--tw-prose-link, var(--tw-accent, #3b82f6)) !important; opacity: 1; }
.toc-block__liste li { margin: 0; }
@media (min-width: 768px) {
  .toc-block__repli > summary { cursor: default; pointer-events: none; }
  .toc-block__repli > summary::after { display: none; }
  .toc-block__repli::details-content { content-visibility: visible; display: block; block-size: auto; }
  .toc-block__repli > .toc-block__liste { margin-top: .6rem; }
}
@media (prefers-reduced-motion: reduce) { .toc-block__repli > summary::after { transition: none; } }
@media (min-width: 1200px) {
  .doc-cadre--sommaire { grid-template-columns: 15.5rem minmax(0, 1fr) 13rem; }
  .doc-sommaire { display: block; position: sticky; top: var(--lx-doc-haut); max-height: calc(100vh - var(--lx-doc-haut) - 1rem); overflow: auto; }
  /* #497 — un bloc « Sommaire » posé DANS un article de documentation doublait,
     sur grand écran, le « Sur cette page » de la colonne de droite : deux
     sommaires des mêmes titres côte à côte. Dès que la colonne est là, elle
     suffit ; le bloc reste pour le téléphone et la tablette, où elle n'est pas
     affichée. `.doc-cadre--sommaire` n'est posé que si la colonne est rendue
     (réglage `showToc` et au moins deux titres) : sans elle, le bloc reste. */
  .doc-cadre--sommaire .doc-article__contenu .toc-block { display: none; }
}

/* Liste d'un chapitre */
.doc-chapitre__liste { list-style: none; counter-reset: doc; margin: 2rem 0 0; padding: 0; display: grid; gap: .75rem; }
.doc-chapitre__liste li { counter-increment: doc; }
.doc-chapitre__liste a { display: grid; grid-template-columns: 2.25rem 1fr; gap: .15rem .75rem; padding: 1rem 1.1rem; border: 1px solid var(--lx-doc-trait); border-radius: var(--tw-radius-card, 12px); color: inherit; text-decoration: none; transition: border-color .2s ease, transform .2s ease; }
.doc-chapitre__liste a::before { content: counter(doc, decimal-leading-zero); grid-row: span 2; font-variant-numeric: tabular-nums; color: var(--tw-prose-link, var(--tw-accent, #3b82f6)); font-weight: 700; }
.doc-chapitre__liste a:hover { border-color: var(--tw-accent, #3b82f6); transform: translateY(-1px); }
.doc-chapitre__titre { font-weight: 600; }
.doc-chapitre__resume { color: var(--lx-doc-doux); font-size: .92rem; }

/* Précédent / suivant */
.doc-precsuiv { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-top: 2.5rem; padding-top: 1.5rem; border-top: 1px solid var(--lx-doc-trait); }
.doc-precsuiv__lien { display: grid; gap: .2rem; padding: .9rem 1.1rem; border: 1px solid var(--lx-doc-trait); border-radius: var(--tw-radius-card, 12px); color: inherit; text-decoration: none; transition: border-color .2s ease; }
.doc-precsuiv__lien:hover { border-color: var(--tw-accent, #3b82f6); }
.doc-precsuiv__lien--suiv { text-align: end; grid-column: 2; }
.doc-precsuiv__sens { font-size: .8rem; color: var(--lx-doc-doux); }
.doc-precsuiv__titre { font-weight: 600; color: var(--tw-prose-link, var(--tw-accent, #3b82f6)); }
@media (max-width: 599px) { .doc-precsuiv { grid-template-columns: 1fr; } .doc-precsuiv__lien--suiv { grid-column: auto; } }
/* Le filtre masque par `hidden` : il doit l'emporter sur les `display` ci-dessus. */
.toweb-doc [hidden] { display: none !important; }

/* ═══════════════════════════════════════════════════════════════════════════
   ÉCRITURES DE DROITE À GAUCHE (arabe, hébreu, persan, ourdou…) — 2026-09-26
   ═══════════════════════════════════════════════════════════════════════════
   `<html dir="rtl">` est posé par BaseLayout pour une langue RTL (principale ou
   secondaire), et l'éditeur pose `dir` sur son canevas. La plupart des règles
   de ce fichier sont désormais LOGIQUES (inset-inline-*, margin-inline-*,
   text-align: start) et se retournent d'elles-mêmes. Restent ici les cas qui
   ne se retournent PAS, ou qui ne DOIVENT pas se retourner.
   Aucune règle ci-dessous n'agit sans `[dir="rtl"]` ou `:lang(<écriture
   arabe>)` : un site de gauche à droite est rendu exactement comme avant. */

/* Les carrousels calculent en PIXELS vers la gauche : `translateX(-n × 100 %)`
   (carousel Alpine), `scrollTo({ left })` (visionneuse), `scrollLeft`
   (bande de cartes, défilement automatique). En RTL, les éléments d'une
   rangée flex partent de la DROITE et `scrollLeft` devient négatif : le
   carrousel montrait du vide et les flèches se désactivaient. La piste reste
   donc de gauche à droite ; son CONTENU, lui, reste de droite à gauche. */
[dir="rtl"] .tw-carousel__track,
[dir="rtl"] .cards-block--scroll,
[dir="rtl"] [data-autoplay] {
  direction: ltr;
}
[dir="rtl"] .tw-carousel__track > *,
[dir="rtl"] .cards-block--scroll > *,
[dir="rtl"] [data-autoplay] > * {
  direction: rtl;
}

/* Courriel, téléphone, adresse web : saisis de gauche à droite partout, et
   alignés sur le côté du formulaire (tunnel de commande, formulaires). */
[dir="rtl"] input:is([type="email"], [type="tel"], [type="url"]) {
  direction: ltr;
  text-align: right;
}

/* La lettrine flotte au DÉBUT de la ligne. */
[dir="rtl"] .content-section--text-dropcap > :is(.content-section__container,.toweb-container) p:first-of-type::first-letter {
  float: right;
  margin: .05em 0 0 .12em;
}

/* Du CODE reste de gauche à droite, quelle que soit la langue de la page. */
[dir="rtl"] :is(pre, .tw-code) {
  direction: ltr;
  text-align: left;
  unicode-bidi: isolate;
}

/* ⚠️ L'ARABE EST UNE ÉCRITURE LIÉE : un interlettrage casse la liaison des
   lettres (le mot s'écrit en formes isolées, illisible). Plusieurs DNA
   espacent leurs titres, boutons et surtitres (`letter-spacing: .08em` et
   plus) ; pour les langues en écriture arabe, on le neutralise partout.
   L'hébreu n'est pas lié : il garde le réglage du DNA. */
/* `:lang()` suit l'attribut `lang` HÉRITÉ : il couvre la page publiée
   (`<html lang>`) et le canevas de l'éditeur (`lang` sur son conteneur), et
   jamais le chrome de TOWeb. */
:is(:lang(ar), :lang(fa), :lang(ur), :lang(ps), :lang(ckb), :lang(sd), :lang(ug), :lang(ks)) {
  letter-spacing: normal !important;
}

/* #343 — la flèche « Voir la suite » d'une section plein écran (un héros, une
   vidéo de fond). Elle vise une ancre posée juste après la section ; la marge
   d'ancre laisse passer un en-tête collant. */
.content-section--full-height { position: relative; }
.content-section__suite {
  position: absolute;
  left: 50%;
  /* Depuis le HAUT : la section fait 100vh mais commence SOUS l'en-tête, donc son
     bas sort de l'écran. On prend le plus haut des deux : le bas de la section,
     ou 150 px au-dessus du bas de l'écran pour un héros posé sous l'en-tête. */
  top: min(calc(100% - 80px), calc(100svh - 150px));
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  translate: -50% 0;
  border-radius: 999px;
  z-index: 3;
  color: currentColor;
  opacity: .85;
  transition: opacity .2s ease, translate .2s ease;
}
.content-section__suite:hover,
.content-section__suite:focus-visible { opacity: 1; translate: -50% 4px; }
.content-section__suite:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
@media (prefers-reduced-motion: no-preference) {
  /* Deux rebonds (4,8 s), puis la flèche se tient tranquille : un décor qui
     bougerait sans fin devrait pouvoir s'arrêter (WCAG 2.2.2). */
  .content-section__suite svg { animation: tw-suite-rebond 2.4s ease-in-out 2; }
}
@keyframes tw-suite-rebond { 0%, 70%, 100% { transform: none; } 80% { transform: translateY(5px); } 90% { transform: translateY(1px); } }
.tw-ancre-suite { display: block; height: 0; scroll-margin-top: var(--tw-header-height, 72px); }
@media (prefers-reduced-motion: no-preference) {
  html:has(.tw-ancre-suite) { scroll-behavior: smooth; }
}

/* Un lien vers un intertitre (#323, `ancres-de-page.ts`) : sans marge de
   défilement, l'en-tête collant recouvrait l'intertitre visé. */
:is(h2, h3, h4)[id] { scroll-margin-top: calc(var(--tw-header-height, 72px) + 16px); }

/* ── Touches du clavier (#360) ────────────────────────────────────────────────
   La notation V14 `[[CTRL+V]]` devient `<kbd>CTRL</kbd>+<kbd>V</kbd>` à la
   génération (`shared/touches-clavier.ts`). Une touche se lit comme une touche :
   un cadre au rayon des champs du DNA, une ombre basse d'un trait, le texte du
   paragraphe en légèrement plus petit. Tout dérive des jetons de la section
   (texte, surface, bordure), donc suit le DNA et les sections sombres. */
kbd {
  display: inline-block;
  min-width: 1.6em;
  padding: 0.05em 0.4em;
  margin-inline: 0.1em;
  font-family: inherit;
  font-size: 0.85em;
  font-weight: 600;
  line-height: 1.35;
  text-align: center;
  white-space: nowrap;
  vertical-align: 0.05em;
  color: var(--tw-text, currentColor);
  background: color-mix(in oklab, var(--tw-text, currentColor) 6%, var(--tw-surface, transparent));
  border: 1px solid color-mix(in oklab, var(--tw-text, currentColor) 22%, transparent);
  border-bottom-width: 2px;
  border-radius: min(var(--tw-radius-field, 0.35em), 0.45em);
}

/* ─── RTL : les libellés courts qui mêlent chiffres, devise et mots (#341) ───
   Sur un site arabe, « From ‏450.00 UK£ » s'affichait « £UK 450.00 From » et
   « 14-day returns » devenait « day returns-14 » : l'algorithme bidi ordonne
   ces morceaux selon le sens de la PAGE. `plaintext` donne à chaque libellé le
   sens de son propre premier caractère fort — un prix en arabe reste de droite
   à gauche, un texte de repli anglais se lit de gauche à droite. Sans effet sur
   un site de gauche à droite. */
[dir="rtl"] :is(.tw-price, .product-card__price, .product-card__price-sticker, .product-card__compare-price,
  .toweb-product__price, .toweb-product__compare, .toweb-product__tax, .toweb-trust__item span) {
  unicode-bidi: plaintext;
}

/* BANNIÈRE D'ANNONCE (`settings.announcement`, HELO 74977) : une ligne au-dessus
   de l'en-tête, aux couleurs d'accent du DNA (le couple accent / texte sur
   accent, que chaque DNA définit et rend lisible). Elle défile avec la page ;
   l'en-tête collant reste en haut. La croix est à la fin de la ligne (logique :
   à gauche en arabe et en hébreu), le texte reste centré dans l'espace laissé. */
.tw-annonce {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 2.5rem;
  padding: 0.5rem 1.25rem;
  background: var(--tw-accent, #111);
  color: var(--tw-on-accent, #fff);
  font-family: var(--tw-font-body, inherit);
  font-size: 0.875rem;
  line-height: 1.4;
  text-align: center;
}
.tw-annonce[hidden] { display: none; }
.tw-annonce--refermable { padding-inline: 3rem; }
.tw-annonce__texte { margin: 0; max-width: 72ch; text-wrap: balance; }
.tw-annonce__lien {
  color: inherit;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
}
.tw-annonce__lien:hover { text-decoration-thickness: 2px; }
.tw-annonce__fleche { display: inline-block; margin-inline-start: 0.4em; transition: transform 0.2s ease; }
.tw-annonce__lien:hover .tw-annonce__fleche { transform: translateX(0.2em); }
[dir="rtl"] .tw-annonce__fleche { transform: scaleX(-1); }
[dir="rtl"] .tw-annonce__lien:hover .tw-annonce__fleche { transform: scaleX(-1) translateX(0.2em); }
.tw-annonce__fermer {
  position: absolute;
  inset-inline-end: 0.5rem;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: inherit;
  opacity: 0.8;
  cursor: pointer;
}
.tw-annonce__fermer:hover { opacity: 1; background: color-mix(in oklab, currentColor 14%, transparent); }
.tw-annonce :is(.tw-annonce__lien, .tw-annonce__fermer):focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}
@media (max-width: 640px) {
  .tw-annonce { font-size: 0.8125rem; padding-block: 0.45rem; }
  .tw-annonce--refermable { padding-inline: 2.5rem 2.75rem; }
}
@media (prefers-reduced-motion: reduce) {
  .tw-annonce__fleche { transition: none; }
}
@media print {
  .tw-annonce { display: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   ASTRO PREVIEW-ONLY STYLES
   These rules target .toweb-section, .toweb-container, and .page-content
   which exist ONLY in Astro-generated pages (Preview/Publish), NOT in
   the TipTap editor. Keeping them separate from blocks-shared.css
   prevents silent divergence between editor and preview rendering.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════════════
   ASTRO SECTION COMPATIBILITY
   .toweb-section is emitted by AstroGenerator. Since all sections also have
   .content-section (in blocks-shared.css), the variable defaults are already set.
   Padding & background are handled by theme.css (.toweb-section rules there).
   We only need container spacing and typographic rules here.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Astro container spacing ──
   In the editor, .content-section uses flex+gap for child spacing.
   In Astro, .toweb-container is the inner wrapper — mirror the same gap. */
.toweb-section > .toweb-container {
  display: flex;
  flex-direction: column;
  gap: var(--theme-block-gap, 1.5rem);
}

/* Editorial text-columns: CSS multicol (column-count, set in blocks.css) is IGNORED
   on a flex container, so a text-cols section's container must be a BLOCK — exactly
   like the editor override (styles.css `.page-tiptap-editor__content
   .content-section--text-cols-N > .content-section__container`). Without this the
   published page lost the columns (monocolumn) AND spaced blocks via flex-gap instead
   of the block margins, diverging from the editor. Same specificity as the flex rule
   above, declared AFTER it → wins. Block spacing then comes from the per-element +
   text-cols block margins (blocks.css), identical to the editor. */
.content-section--text-cols-2 > .toweb-container,
.content-section--text-cols-3 > .toweb-container {
  display: block;
}

/* Heading spacing inside .toweb-container (mirrors editor's .content-section__container rules) */
.toweb-container > h1,
.toweb-container > h2,
.toweb-container > h3,
.toweb-container > h4,
.toweb-container > h5,
.toweb-container > h6 {
  margin-top: 1.5rem;
  margin-bottom: 0.5rem;
  color: var(--tw-text-heading, #1e293b);
}
.toweb-container > h1:first-child,
.toweb-container > h2:first-child,
.toweb-container > h3:first-child,
.toweb-container > h4:first-child,
.toweb-container > h5:first-child,
.toweb-container > h6:first-child {
  margin-top: 0;
}

/* ═══════════════════════════════════════════════════════════════════════════
   COLUMNS RESPONSIVE STACKING
   In the editor, columns are always side-by-side (desktop editing).
   In Astro preview/publish, columns stack vertically on mobile.
   ═══════════════════════════════════════════════════════════════════════════ */

@media (max-width: 767px) {
  .columns-block {
    flex-direction: column;
  }
  .columns-block .column {
    flex: 1 1 100% !important;
  }
  /* Reverse on mobile: image-first layouts can show text first on small screens */
  .columns-block--reverse-mobile {
    flex-direction: column-reverse;
  }
}

/* Smooth anchor scroll for single-page landing sites */
html {
  scroll-behavior: smooth;
}

/* ═══════════════════════════════════════════════════════════════════════════
   PAGE CONTENT BASE STYLES
   Minimal typography/spacing for TipTap-generated HTML in Astro pages.
   ═══════════════════════════════════════════════════════════════════════════ */

.page-content {
  display: flex;
  flex-direction: column;
  /* No gap here: sections have their own padding (--theme-section-py).
     Block-level spacing is handled by .toweb-container children margins. */
}

/* Typography rules for free-text TipTap content (paragraphs, lists, quotes).
   IMPORTANT: Use .toweb-container > to scope to DIRECT children only.
   This prevents these generic rules from overriding atom block styles
   (pricing-block, testimonial-block, stats-block, etc.) which have their
   own specific styles in blocks-shared.css (the single source of truth). */

.toweb-container > p {
  margin: 0 0 1rem;
}

.toweb-container > ul,
.toweb-container > ol {
  margin: 0 0 1rem;
  padding-left: 1.5rem;
}

.toweb-container > ul li,
.toweb-container > ol li {
  margin: 0.25rem 0;
}

.page-content img {
  max-width: 100%;
  height: auto;
}

/* `.page-content img{height:auto}` above (0,1,1) ties with the base
   `.tw-media > img{height:100%}` in bricks-base.css but loads later, so it would
   override it and collapse every aspected-media box (cards/gallery/map/team) to the
   image's natural height. Re-assert the brick contract for ALL `.tw-media` children
   at higher specificity — (0,2,1) > (0,1,1) — so the media fills its aspect box.
   Publish-only (the editor has no `.page-content img` reset). */
.page-content .tw-media > img,
.page-content .tw-media > video,
.page-content .tw-media > iframe {
  height: 100%;
}

.page-content figure:not([class*="-block"]) {
  margin: 1rem 0;
}

/* Une légende NUE seulement (`:not([class])`). Cette règle atteignait aussi le
   <figcaption> des témoignages (auteur + fonction) : opacité 0,7 par-dessus le
   texte atténué — la fonction tombait sous 3:1 dans 42 DNA sur 48 — et elle
   écrasait sa marge et son alignement, que l'éditeur ne voyait pas (axe-core,
   audit du 2026-09-28). La couleur atténuée du DNA, plus une opacité. */
.page-content figcaption:not([class]) {
  text-align: center;
  font-size: 0.875rem;
  color: var(--tw-text-muted, inherit);
  margin-top: 0.5rem;
}

/* Link styling for free-text only. Atom block links (CTA, pricing, social)
   have their own styles via BEM classes in blocks-shared.css. */
/* `--tw-prose-link` : re-pointé sur le texte par la bande `bold` (blocks-base.css). */
.page-content a:not(.cta-button):not([class*="button"]):not([class*="block__"]):not([class*="block--"]):not(.tw-cta-link):not(.tw-nav-link):not(.tw-icon):not(.tag) {
  color: var(--tw-prose-link, var(--tw-accent));
}
/* Sans soulignement, SAUF un lien posé dans le texte courant : il doit se voir
   autrement que par sa couleur (WCAG 1.4.1 ; souligné par blocks-shared.css).
   Cette règle (0,8,1) l'effaçait au site publié — et à lui seul (audit du 2026-09-28). */
.page-content a:not(.cta-button):not([class*="button"]):not([class*="block__"]):not([class*="block--"]):not(.tw-cta-link):not(.tw-nav-link):not(.tw-icon):not(.tag):not(:is(p, li, dd, td, th, blockquote, figcaption) > a) {
  text-decoration: none;
}
.page-content a:not(.cta-button):not([class*="button"]):not([class*="block__"]):not([class*="block--"]):not(.tw-cta-link):not(.tw-nav-link):not(.tw-icon):not(.tag):hover {
  opacity: 0.8;
}

/* ═══════════════════════════════════════════════════════════════════════════
   BOXED MODE — Per-child corner clipping
   theme.css sets --boxed-radius + border-radius + box-shadow on body,
   but NOT overflow:clip (which would truncate nav dropdowns).
   Instead, each direct child of body individually handles its corners:
   - Header (first-child): top-radius only, NO clip → dropdowns escape
   - Main content (.page-content): overflow-x clip
   - Intermediate sections: overflow clip
   - Footer (last section): bottom-radius + clip
   smart-dropdown.ts repositions dropdowns to stay within body bounds.
   In centered mode --boxed-radius is undefined → fallback 0 = no effect.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Header section + header element: both get rounded top corners.
   Each element's OWN background is rounded by border-radius (no overflow:clip needed).
   Dropdowns open BELOW the header (position:absolute top:100%), so they are NOT
   in the corner area and are not affected by the radius. */
body > :first-child,
body > :first-child .toweb-header {
  border-radius: var(--boxed-radius, 0) var(--boxed-radius, 0) 0 0;
}

/* Fixed header escapes the body box (position: fixed = full viewport width),
   so top border-radius is meaningless — remove it to avoid visual conflict. */
body > :first-child:has(.toweb-header--fixed),
body > :first-child:has(.toweb-header--fixed) .toweb-header {
  border-radius: 0;
}

/* Main content area: clip-x for wide blocks */
body > .page-content {
  overflow-x: clip;
}

/* Intermediate sections between main and footer: clip */
body > .page-content ~ section {
  overflow: clip;
}

/* Footer (last section): rounded bottom corners + clip */
body > .page-content ~ section:last-of-type {
  border-radius: 0 0 var(--boxed-radius, 0) var(--boxed-radius, 0);
  overflow: clip;
}

/* TOWeb V15 – Theme CSS (auto-generated) */
/* Cormorant Garamond — self-hosted (offline-first #84) */
/* cyrillic */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 300 700;
  font-display: swap;
  src: url(../fonts/cormorant-garamond-cyrillic-300-700-italic.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* latin-ext */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 300 700;
  font-display: swap;
  src: url(../fonts/cormorant-garamond-latin-ext-300-700-italic.woff2) format('woff2');
  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;
}
/* latin */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 300 700;
  font-display: swap;
  src: url(../fonts/cormorant-garamond-latin-300-700-italic.woff2) format('woff2');
  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+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url(../fonts/cormorant-garamond-cyrillic-300-700-normal.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* latin-ext */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url(../fonts/cormorant-garamond-latin-ext-300-700-normal.woff2) format('woff2');
  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;
}
/* latin */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url(../fonts/cormorant-garamond-latin-300-700-normal.woff2) format('woff2');
  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+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}


/* DM Sans — self-hosted (offline-first #84) */
/* latin-ext */
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 100 1000;
  font-display: swap;
  src: url(../fonts/dm-sans-latin-ext-100-1000-normal.woff2) format('woff2');
  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;
}
/* latin */
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 100 1000;
  font-display: swap;
  src: url(../fonts/dm-sans-latin-100-1000-normal.woff2) format('woff2');
  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+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}


/* JetBrains Mono — self-hosted (offline-first #84) */
/* cyrillic */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 100 800;
  font-display: swap;
  src: url(../fonts/jetbrains-mono-cyrillic-100-800-normal.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* latin-ext */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 100 800;
  font-display: swap;
  src: url(../fonts/jetbrains-mono-latin-ext-100-800-normal.woff2) format('woff2');
  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;
}
/* latin */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 100 800;
  font-display: swap;
  src: url(../fonts/jetbrains-mono-latin-100-800-normal.woff2) format('woff2');
  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+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}


/* ═══════════════════════════════════════════════════════════════════════════
   THEME GLOBAL VARIABLES — DNA SoT (publish scope)
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
  /* ─────────────────────────────────────────────────────────────────────────
     TYPOGRAPHIE
     ───────────────────────────────────────────────────────────────────────── */

  /* Familles de polices : le DNA POSSÈDE heading + body (bricks/dna-{x}.css,
     overridables via la Typo tab → --user-font-*). On ne les émet PLUS ici : en
     éditeur, themeVars est scopé sur .page-tiptap-editor = le MÊME élément que
     [data-dna], et il est injecté en dernier → il clobberait --theme-font-heading/
     -body du DNA (= la dérive « police éditeur ≠ preview »). Le mono reste legacy
     (le DNA ne le définit pas). Voir dna-{x}.css §polices. */
  --theme-font-mono: 'JetBrains Mono', monospace;

  /* Échelle typographique */
  --theme-size-h1: 3rem;
  --theme-size-h2: 2.25rem;
  --theme-size-h3: 1.875rem;
  --theme-size-h4: 1.5rem;
  --theme-size-h5: 1.25rem;
  --theme-size-h6: 1.125rem;
  --theme-size-body: 1rem;
  --theme-size-small: 0.875rem;
  --theme-size-xs: 0.75rem;

  /* Line heights */
  --theme-lh-headings: 1.2;
  --theme-lh-body: 1.6;
  --theme-lh-tight: 1.25;
  --theme-lh-relaxed: 1.75;

  /* Font weights */
  --theme-weight-heading: 700;
  --theme-weight-body: 400;
  --theme-weight-strong: 600;

  /* Letter spacing */
  --theme-tracking-headings: -0.025em;
  --theme-tracking-body: 0;
  --theme-tracking-wide: 0.05em;

  /* ─────────────────────────────────────────────────────────────────────────
     FORMES (Border Radius)
     ───────────────────────────────────────────────────────────────────────── */

  --theme-radius-none: 0;
  --theme-radius-sm: 0.25rem;
  --theme-radius: 0.5rem;
  --theme-radius-md: 0.75rem;
  --theme-radius-lg: 1rem;
  --theme-radius-xl: 1.5rem;
  --theme-radius-full: 9999px;

  /* Alias sémantiques radius — SUPPRIMÉS (2026-06-12, port DNA-A) : le DNA
     POSSÈDE --theme-radius-button/-card (16 vars user, bricks/dna-{x}.css) et
     l'éditeur les émettait ici sur LE MÊME élément .page-tiptap-editor que
     [data-dna] → tie de spécificité, l'inject <style> gagnait et clobbait le
     radius du DNA (C 14px → 0.5rem ; A pill 999px → 0.5rem). Même famille que
     le clobber des fonts (cf note Phase A ci-dessus). Aucun consommateur
     var(--theme-radius-button/-input/-card/-badge) hors dna-{x}.css (grep 0). */

  /* ─────────────────────────────────────────────────────────────────────────
     CHAMPS DE FORMULAIRE
     ───────────────────────────────────────────────────────────────────────── */

  --theme-input-height-sm: 2rem;
  --theme-input-height: 2.5rem;
  --theme-input-height-lg: 3rem;
  --theme-input-px: 0.75rem;
  --theme-input-py: 0.5rem;
  --theme-input-border-width: 1px;
  --theme-input-focus-ring-width: 2px;
  --theme-input-focus-ring-offset: 2px;
  --theme-input-transition: all 0.15s ease;

  /* BOUTONS (look) — supprimé Phase A : le DNA possède --tw-btn-* (bg/text/
     border/radius/weight/hover) via bricks/dna-{x}.css. */

  /* ─────────────────────────────────────────────────────────────────────────
     ESPACEMENTS
     ───────────────────────────────────────────────────────────────────────── */

  --theme-section-py-sm: 2rem;
  --theme-section-py: 4rem;
  --theme-section-py-lg: 6rem;
  --theme-section-px: 1.5rem;
  --theme-block-gap: 1.5rem;
  --theme-container-max: 1200px;
  --theme-container-px: 1.5rem;

  /* ─────────────────────────────────────────────────────────────────────────
     OMBRES
     ───────────────────────────────────────────────────────────────────────── */

  --theme-shadow-none: none;
  --theme-shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
  --theme-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
  --theme-shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
  --theme-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
  --theme-shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);

  /* COULEURS DE BASE (--theme-primary/-background/-foreground…) — supprimées
     Phase A : le DNA les dérive dans bricks/dna-{x}.css (oklch depuis la teinte).
     Plus aucune émission legacy ici → dna-{x}.css est la SEULE source. */


  /* ─────────────────────────────────────────────────────────────────────────
     LAYOUT
     ───────────────────────────────────────────────────────────────────────── */

  --theme-content-width: 1280px;
  --theme-site-mode: centered;
  --theme-global-bg: #ffffff;
  --theme-header-footer-width: full;

  /* Apply base font (--theme-font-body owned by the DNA; data-dna sits on
     <html> in publish so :root IS the [data-dna] element — fallback kept for
     the no-DNA case) */
  font-family: var(--theme-font-body, system-ui, sans-serif);
  font-size: var(--theme-size-body);
  line-height: var(--theme-lh-body);
  color: var(--tw-text);
}

/* Headings — shared logic, publish (global) scope */
h1:not([class*="tw-"]),
h2:not([class*="tw-"]),
h3:not([class*="tw-"]),
h4:not([class*="tw-"]),
h5:not([class*="tw-"]),
h6:not([class*="tw-"]) {
  font-family: var(--tw-font-heading, var(--theme-font-heading));
  line-height: calc(var(--tw-leading-heading, var(--theme-lh-headings, 1.2)) * var(--tw-leading-scale, 1));
  letter-spacing: var(--tw-tracking-heading, var(--theme-tracking-headings));
  margin: 0.5rem 0;
}

h1:not([class*="tw-"]):first-child,
h2:not([class*="tw-"]):first-child,
h3:not([class*="tw-"]):first-child,
h4:not([class*="tw-"]):first-child,
h5:not([class*="tw-"]):first-child,
h6:not([class*="tw-"]):first-child {
  margin-top: 0;
}

h1:not([class*="tw-"]) { font-size: var(--tw-size-h1, var(--theme-size-h1)); }
h2:not([class*="tw-"]) { font-size: var(--tw-size-h2, var(--theme-size-h2)); }
h3:not([class*="tw-"]) { font-size: var(--tw-size-h3, var(--theme-size-h3)); }
h4:not([class*="tw-"]) { font-size: var(--tw-size-h4, var(--theme-size-h4)); }
h5:not([class*="tw-"]) { font-size: var(--tw-size-h5, var(--theme-size-h5)); }
h6:not([class*="tw-"]) { font-size: var(--tw-size-h6, var(--theme-size-h6)); }

/* Interligne du corps + échelle de lecture (articles, doc, fiche produit) — mêmes builders que l'éditeur */
.content-section:not(.content-section--system) {
  line-height: calc(var(--tw-leading-body, var(--theme-lh-body, 1.6)) * var(--tw-leading-scale, 1));
}

.post-content, .doc-article__contenu, .toweb-product__description {
  font-size: var(--tw-size-base, 1.0625rem);
  line-height: calc(calc(var(--tw-leading-body, var(--theme-lh-body, 1.6)) * var(--tw-leading-scale, 1)) * 1.06);
}
.post-content :is(p, li, blockquote):not([class*="tw-"]), .doc-article__contenu :is(p, li, blockquote):not([class*="tw-"]), .toweb-product__description :is(p, li, blockquote):not([class*="tw-"]) {
  line-height: calc(calc(var(--tw-leading-body, var(--theme-lh-body, 1.6)) * var(--tw-leading-scale, 1)) * 1.06);
}
.post-content h1:not([class*="tw-"]), .doc-article__contenu h1:not([class*="tw-"]), .toweb-product__description h1:not([class*="tw-"]) { font-size: min(var(--tw-size-h1, var(--theme-size-h1)), clamp(calc(var(--tw-size-base, 1.0625rem) * 1.6), calc(var(--tw-size-h1, var(--theme-size-h1)) * 0.5), calc(var(--tw-size-base, 1.0625rem) * 2.2))); }
.post-content h2:not([class*="tw-"]), .doc-article__contenu h2:not([class*="tw-"]), .toweb-product__description h2:not([class*="tw-"]) { font-size: min(var(--tw-size-h2, var(--theme-size-h2)), clamp(calc(var(--tw-size-base, 1.0625rem) * 1.4), calc(var(--tw-size-h2, var(--theme-size-h2)) * 0.6), calc(var(--tw-size-base, 1.0625rem) * 1.75))); }
.post-content h3:not([class*="tw-"]), .doc-article__contenu h3:not([class*="tw-"]), .toweb-product__description h3:not([class*="tw-"]) { font-size: min(var(--tw-size-h3, var(--theme-size-h3)), clamp(calc(var(--tw-size-base, 1.0625rem) * 1.1), calc(var(--tw-size-h3, var(--theme-size-h3)) * 0.8), calc(var(--tw-size-base, 1.0625rem) * 1.35))); }
.post-content h4:not([class*="tw-"]), .doc-article__contenu h4:not([class*="tw-"]), .toweb-product__description h4:not([class*="tw-"]) { font-size: min(var(--tw-size-h4, var(--theme-size-h4)), clamp(calc(var(--tw-size-base, 1.0625rem) * 1.05), calc(var(--tw-size-h4, var(--theme-size-h4)) * 0.85), calc(var(--tw-size-base, 1.0625rem) * 1.15))); }
.post-content :is(h1:not([class*="tw-"]), h2:not([class*="tw-"]), h3:not([class*="tw-"]), h4:not([class*="tw-"])), .doc-article__contenu :is(h1:not([class*="tw-"]), h2:not([class*="tw-"]), h3:not([class*="tw-"]), h4:not([class*="tw-"])), .toweb-product__description :is(h1:not([class*="tw-"]), h2:not([class*="tw-"]), h3:not([class*="tw-"]), h4:not([class*="tw-"])) {
  line-height: max(1.2, calc(var(--tw-leading-heading, var(--theme-lh-headings, 1.2)) * var(--tw-leading-scale, 1)));
  margin: 1.6em 0 0.5em;
}
.post-content :is(h1:not([class*="tw-"]), h2:not([class*="tw-"]), h3:not([class*="tw-"]), h4:not([class*="tw-"])):first-child, .doc-article__contenu :is(h1:not([class*="tw-"]), h2:not([class*="tw-"]), h3:not([class*="tw-"]), h4:not([class*="tw-"])):first-child, .toweb-product__description :is(h1:not([class*="tw-"]), h2:not([class*="tw-"]), h3:not([class*="tw-"]), h4:not([class*="tw-"])):first-child {
  margin-top: 0;
}
.toweb-product__title.toweb-product__title {
  font-size: min(var(--tw-size-h1, var(--theme-size-h1)), clamp(calc(var(--tw-size-base, 1.0625rem) * 1.6), calc(var(--tw-size-h1, var(--theme-size-h1)) * 0.5), calc(var(--tw-size-base, 1.0625rem) * 2.2)));
  line-height: max(1.15, calc(var(--tw-leading-heading, var(--theme-lh-headings, 1.2)) * var(--tw-leading-scale, 1)));
}

/* Code — shared logic, publish (global) scope */
code, pre {
  font-family: var(--theme-font-mono);
  font-size: 0.875em;
}

/* Links — default color (low specificity via :where so section/component rules win) */
a:where(:not(.cta-button):not([class*="button"]):not([class*="block__"]):not([class*="block--"]):not([class*="tag"]):not([class*="card__"]):not([class*="pagination"])) {
  color: var(--tw-prose-link, var(--tw-accent));
}
a:where(:not(.cta-button):not([class*="button"]):not([class*="block__"]):not([class*="block--"]):not([class*="tag"]):not([class*="card__"]):not([class*="pagination"])):hover {
  color: var(--tw-prose-link, var(--tw-accent));
}

/* Button behaviour (transitions + hover effect + secondary mode) — shared logic, publish scope */
.btn, .cta-button, button[class*="btn"] {
  transition: all 0.2s ease;
}

.btn:hover, .cta-button:hover, button[class*="btn"]:hover {
  filter: brightness(0.9);
}

.cta-button--secondary:hover {
  background: var(--tw-surface, var(--tw-accent-2));
  color: var(--tw-btn-outline-text, var(--tw-on-accent));
}


/* ─────────────────────────────────────────────────────────────────────────
   SECTION BASE STYLES
   ───────────────────────────────────────────────────────────────────────── */

.toweb-section {
  padding: calc(var(--tw-section-py, 4rem) * var(--tw-section-density, 1)) 0;
  /* Honour the brick palette token. A palette section paints its band via
     --tw-bg (re-pointed by dna-{x}.css on [data-palette]); a no-palette section
     leaves --tw-bg unset, so it falls back to transparent and the page / custom
     global background shows. Publish sections carry BOTH .toweb-section AND
     .content-section, and this rule loads (theme.css) AFTER bricks-base's
     .content-section background rule at equal specificity — so it MUST resolve to
     the same var(--tw-bg), otherwise it clobbers every palette band to transparent
     in publish while the editor (canvas = .content-section only) still paints them
     (editor-vs-publish parity break — invisible in dark, glaring on the bold band).
     An explicit per-section background stays an inline style (wins by specificity). */
  background: var(--tw-bg, transparent);
  color: var(--tw-text, inherit);
}

.toweb-section .toweb-container {
  max-width: var(--theme-container-max, 1200px);
  margin: 0 auto;
  padding: 0 var(--theme-container-px, 1rem);
}

.toweb-section--narrow .toweb-container { max-width: 768px; }
.toweb-section--wide .toweb-container { max-width: 1400px; }
.toweb-section--full .toweb-container { max-width: none; }

/* R.6 SoT (phase 2): section heading-colour + link-colour rules live ONCE in
   blocks-shared.css (.content-section h1-h6 / .content-section a) — loaded by the
   editor AND publish. The publish element carries both classes (.toweb-section
   .content-section) so the shared rules apply; the .toweb-section copies that used
   to live here were redundant and drifted (the section-link saga). Removed. */

/* Image figures — alignment */
.post-figure { margin: 1.5rem 0; }
.post-figure img { max-width: 100%; height: auto; border-radius: var(--tw-radius-card, 0.5rem); }
.post-figure--center { text-align: center; }
.post-figure--center img { margin: 0 auto; }
.post-figure--left { text-align: left; }
.post-figure--right { text-align: right; }
.post-figure figcaption { font-size: var(--theme-size-small); color: var(--tw-text-muted); margin-top: 0.5rem; }

/* ─────────────────────────────────────────────────────────────────────────
   LAYOUT — Site mode: centered, Content width: 1280px
   ───────────────────────────────────────────────────────────────────────── */
.toweb-section .toweb-container { --tw-content-max: 1280px; --tw-content-pad: var(--theme-container-px, 1rem); }
.toweb-section .toweb-container { max-width: 1280px; }
body { background: #ffffff; }

/* ─────────────────────────────────────────────────────────────────────────
   DNA TONALITY — user-tuned hue / intensity / secondary-strategy / bg-neutral.
   Specificity (0,2,0) beats the DNA's [data-dna="x"] (0,1,0) defaults that load
   later (bricks-dna.css) → the brick OKLCH derivation re-runs with these knobs.
   ───────────────────────────────────────────────────────────────────────── */
[data-dna][data-dna] {
  --hue-shift: 94;
  --chroma-mult: 1;
  --accent2-shift: 0;
  --bg-neutral: 0.85;
}



/* DNA SHAPE — user shape/rhythm overrides (Theme → Formes) */
[data-dna][data-dna] {
  --tw-heading-scale: 0.75;
}

/* COULEURS DU SITE (settings.dnaColors) — reprises de TOWeb 14 à l'import (#145) */
[data-dna][data-dna]:not([data-theme="dark"]) {
  --theme-background: #ffffff;
  --tw-canvas-neutral: #ffffff;
  --tw-canvas-surface-neutral: #ffffff;
  --bg-neutral: 1;
  --theme-global-bg: #ffffff;
  --tw-media-placeholder: color-mix(in srgb, #3b3530 5%, #ffffff);
  --theme-foreground: #3b3530;
  --theme-muted-foreground: color-mix(in srgb, #3b3530 75%, #ffffff);
  --tw-text-heading: #1d1916;
  --theme-primary: #000000;
  --tw-accent: #000000;
  --tw-prose-link: #000000;
  --tw-eyebrow-color: #000000;
}
[data-dna][data-dna]:not([data-theme="dark"]) .site-header-block {
  --tw-bg: #ffffff;
  --tw-text: #000000;
  --tw-text-heading: #000000;
  --tw-on-surface: #000000;
  --tw-text-muted: color-mix(in srgb, #000000 78%, #ffffff);
  --tw-surface: color-mix(in srgb, #ffffff 94%, #000000);
  --tw-surface-raised: color-mix(in srgb, #ffffff 90%, #000000);
  --tw-surface-translucent: #ffffff;
  --tw-border: color-mix(in srgb, #000000 18%, transparent);
  --tw-rule: color-mix(in srgb, #000000 18%, transparent);
  --tw-accent: #2e8cea;
  --tw-prose-link: #2e8cea;
  --tw-em-color: #2e8cea;
  --tw-em-fill: #2e8cea;
  --tw-eyebrow-color: #2e8cea;
  --tw-icon-color: #2e8cea;
  --tw-cta-color: #000000;
  --tw-nav-color: #000000;
  --tw-nav-color-hover: #2e8cea;
  --tw-price-color: #000000;
  --tw-btn-outline-text: #000000;
  --tw-btn-outline-border: color-mix(in srgb, #000000 45%, transparent);
  background: #ffffff;
  color: #000000;
}
[data-dna][data-dna]:not([data-theme="dark"]) .site-footer-block {
  --tw-bg: #ffffff;
  --tw-text: #3b3530;
  --tw-text-heading: #3b3530;
  --tw-on-surface: #3b3530;
  --tw-text-muted: color-mix(in srgb, #3b3530 78%, #ffffff);
  --tw-surface: color-mix(in srgb, #ffffff 94%, #3b3530);
  --tw-surface-raised: color-mix(in srgb, #ffffff 90%, #3b3530);
  --tw-surface-translucent: #ffffff;
  --tw-border: color-mix(in srgb, #3b3530 18%, transparent);
  --tw-rule: color-mix(in srgb, #3b3530 18%, transparent);
  --tw-accent: #00852c;
  --tw-prose-link: #00852c;
  --tw-em-color: #00852c;
  --tw-em-fill: #00852c;
  --tw-eyebrow-color: #00852c;
  --tw-icon-color: #00852c;
  --tw-cta-color: #3b3530;
  --tw-nav-color: #3b3530;
  --tw-nav-color-hover: #00852c;
  --tw-price-color: #3b3530;
  --tw-btn-outline-text: #3b3530;
  --tw-btn-outline-border: color-mix(in srgb, #3b3530 45%, transparent);
  background: #ffffff;
  color: #3b3530;
}
[data-dna][data-dna]:not([data-theme="dark"]) .site-header-block .tw-nav-link,
[data-dna][data-dna]:not([data-theme="dark"]) .site-header-block .site-header-block__link,
[data-dna][data-dna]:not([data-theme="dark"]) .site-header-block__logo,
[data-dna][data-dna]:not([data-theme="dark"]) .site-header-block__logo-text {
  --tw-nav-color: #000000;
  --tw-nav-color-hover: #2e8cea;
  color: #000000;
  -webkit-text-fill-color: #000000;
}
[data-dna][data-dna]:not([data-theme="dark"]) .site-header-block .tw-nav-link:hover,
[data-dna][data-dna]:not([data-theme="dark"]) .site-header-block .site-header-block__link:hover {
  color: #2e8cea;
  -webkit-text-fill-color: #2e8cea;
}
[data-dna][data-dna]:not([data-theme="dark"]) .content-section--system[data-entete]:not([data-entete-haut]:has(~ main > .content-section--text-light:first-child)) > .site-header-block {
  background-color: #ffffff !important;
}
[data-dna][data-dna]:not([data-theme="dark"]) .site-footer-block :is(a, .site-footer-block__heading, .site-footer-block__logo-name, .site-footer-block__text, .site-footer-block__copyright) {
  color: #3b3530;
  -webkit-text-fill-color: #3b3530;
}
[data-dna][data-dna]:not([data-theme="dark"]) .site-footer-block a:hover {
  color: #00852c;
  -webkit-text-fill-color: #00852c;
}

/* La peau signature vit dans bricks/dna-{id}.css (couche R.6 absorbée) */

/* =============================================================================
   dna-c.css — DNA "Warm Artisan" (Style C)
   -----------------------------------------------------------------------------
   La couche DÉRIVATION + SKIN. Le SEUL endroit qui lit --theme-*.
   Contient : (1) les 14 vars user, light+dark ; (2) les internes --dna-c-* ;
   (3) la dérivation des ~85 --tw-* ; (4) le SKIN des briques (au-delà des vars —
   LE point variable du DNA) ; (5) les 4 palettes de section ; (6) le backdrop.
   Réfs valeurs : _design-references/style-C-warm-artisan/brief.md + styles.css.
   ============================================================================= */

[data-dna="atelier"]{
  /* ---- (0) LE SEUL CHOIX USER COULEUR : une TEINTE (+ intensité, stratégie) -
     Le user ne fixe PAS 5 couleurs indépendantes (→ résultats illisibles, cf
     l'enfer des 5 color-pickers). Il déplace la TEINTE de la marque ; le DNA
     recolore TOUT en conservant le L (clarté) et le C (saturation) de la
     palette validée.

     ⚠️ CE QUI EST GARANTI, ET CE QUI NE L'EST PAS (B16, #223). La palette
     LIVRÉE est contrôlée : `pnpm controles` refuse tout couple porteur de
     texte sous 4,5:1, sur les 48 DNAs × clair/sombre. Mais les curseurs
     tournent H à L CONSTANT, et la luminance de WCAG n'est pas perceptuelle :
     une rotation de teinte PEUT faire passer un contraste sous le seuil.
     Mesuré, pas hypothétique — 127 réglages à intensité 1×. L'écran de
     tonalité l'affiche en direct ; il ne l'empêche pas.
       --hue-shift   : rotation de teinte en degrés (number unitless, -180→180)
       --chroma-mult : intensité globale (0 = gris, 1 = palette validée, >1 = vif)
       --accent2-shift : écart du secondaire vs primaire — la "stratégie" de palette
                         0 = analogue (défaut C) · 180 = complémentaire · ±120 = triade */
  --hue-shift:     0;
  --chroma-mult:   1;
  --accent2-shift: 0;

  /* ---- (1) LES 14 VARS USER — les 5 couleurs sont DÉRIVÉES de la teinte ----
     Origines = la palette warm-artisan validée. oklch(from <hex> L C H) reprend
     L et C de l'origine et ne fait tourner que H : à hue-shift:0 on retrouve
     EXACTEMENT la palette validée ; à toute autre teinte, le rapport de clarté
     (donc la lisibilité) est préservé. */
  --theme-primary:        oklch(0.6492 calc(0.1094 * var(--chroma-mult)) calc(50.0706 + var(--hue-shift)));
  --theme-secondary:      oklch(0.6185 calc(0.073 * var(--chroma-mult)) calc(122.1301 + var(--hue-shift) + var(--accent2-shift)));
  --theme-background:     oklch(0.9488 calc(0.0193 * var(--chroma-mult)) calc(80.1179 + var(--hue-shift)));
  --theme-foreground:     oklch(0.2997 calc(0.0335 * var(--chroma-mult)) calc(62.1939 + var(--hue-shift)));
  --theme-muted-foreground:oklch(0.506 calc(0.043 * var(--chroma-mult)) calc(70.524 + var(--hue-shift)));
  /* Polices : défaut signature DNA, OVERRIDABLE par le user (étape Theme → Typo).
     --user-font-* est posé inline sur [data-dna] (canvas/spécimen) et dans le
     theme.css publié quand le user choisit une police ; absent = police du DNA. */
  --theme-font-display:   var(--user-font-display, 'Cormorant Garamond', Georgia, serif);
  --theme-font-body:      var(--user-font-body, 'DM Sans', system-ui, sans-serif);
  /* Le DNA POSSÈDE la police des titres = la display. Déclaré sur [data-dna] (donc
     résolu là où --theme-font-display existe) → tous les consommateurs de
     --theme-font-heading (titres bricks .tw-h1, plain <h1>, footer, foundation)
     héritent la police DNA, en éditeur ET en publish. themeVars n'émet plus
     --theme-font-heading/-body (sinon il clobberait ceci sur le même élément
     .page-tiptap-editor côté éditeur → la dérive police éditeur≠preview). */
  --theme-font-heading:   var(--theme-font-display);
  --theme-font-size-base: 1.0625rem;
  --theme-font-weight-heading: 700;   /* USER : graisse de base des titres (C = uniforme, step 0) */
  --theme-radius-button:  14px;
  --theme-radius-card:    24px;
  --theme-radius-image:   24px;
  --theme-shadow-intensity: soft;     /* mappé en box-shadow ci-dessous */
  --theme-spacing-section-y: clamp(4.5rem, 8vw, 7.5rem);
  --theme-button-style:   filled;

  /* ---- (2) INTERNES SIGNATURE --dna-c-* — même teinte/intensité ----------- */
  --dna-c-paper:        oklch(0.9775 calc(0.0171 * var(--chroma-mult)) calc(84.5867 + var(--hue-shift)));   /* surface plus claire */
  --dna-c-sand:         oklch(0.8813 calc(0.0437 * var(--chroma-mult)) calc(83.2018 + var(--hue-shift)));   /* bande accent sable-doré */
  --dna-c-peach:        oklch(0.9611 calc(0.0254 * var(--chroma-mult)) calc(73.1879 + var(--hue-shift)));   /* pêche douce (sticker, highlight) */
  --dna-c-olive:        oklch(0.6185 calc(0.073 * var(--chroma-mult)) calc(122.1301 + var(--hue-shift) + var(--accent2-shift)));
  --dna-c-peach-deep:   oklch(0.7562 calc(0.0862 * var(--chroma-mult)) calc(59.5123 + var(--hue-shift)));   /* pêche soutenue (hover) */
  --dna-c-border-soft:  rgba(58, 42, 28, 0.10);
  --dna-c-border-medium:rgba(58, 42, 28, 0.18);
  --dna-c-shadow-warm:  rgba(196, 122, 79, 0.12);
  --dna-c-shadow-warm-strong: rgba(196, 122, 79, 0.22);
  /* Ombre des IMAGES : tirée de l'encre du site (brun chaud par défaut), pas d'un
     terracotta en dur. Avec une palette reprise (HELO 74887 : fond blanc, texte
     noir), le terracotta restait et dessinait un halo brun clair autour des œuvres. */
  --dna-c-shadow-media: color-mix(in srgb, var(--theme-foreground) 30%, transparent);

  /* ---- (3) DÉRIVATION : ~85 vars --tw-* (ce que les briques consomment) ---- */
  /* CANVAS — "Fond neutre" (knob user --bg-neutral, 0..1). 0 = ton du DNA (sable) ;
     1 = neutre (blanc en clair / quasi-noir en dark). Ne neutralise QUE la famille
     de fonds (page + surfaces + bande subtle) ; accents, texte, bandes accent/bold
     restent INCHANGÉS → "garder toutes les couleurs SAUF le fond". */
  --bg-neutral:           0;
  --tw-canvas-neutral:    #ffffff;     /* ancre clair = BLANC PUR (dark l'override en noir pur) */
  --tw-canvas-surface-neutral: var(--tw-canvas-neutral); /* surfaces visent la MÊME ancre → à 100% tout est blanc/noir pur ; les cartes se détachent par bordure + ombre */
  /* A. Fonds / surfaces / voiles (contextuel) */
  --tw-page-bg:           color-mix(in srgb, var(--theme-background), var(--tw-canvas-neutral) calc(var(--bg-neutral) * 100%));
  --tw-bg:                var(--tw-page-bg);
  --tw-surface:           color-mix(in srgb, var(--dna-c-paper), var(--tw-canvas-surface-neutral) calc(var(--bg-neutral) * 100%));
  /* ⚠️ LA HAUSSE EST UNE PROPORTION, PAS UNE CONSTANTE (C15). 30 % de blanc
     dans une surface CLAIRE la fait à peine bouger ; dans une surface SOMBRE
     elle la fait passer à rgb(112,104,96) — et le texte crème des cartes y
     tombe à 4,33:1. Mesuré sur le site ATELIER sombre, aux deux largeurs.

     > Une même quantité de blanc ne dit pas la même chose des deux côtés du
     > mode. Ce qu'on veut garder constant, c'est l'ÉCART perçu, pas la dose. */
  --dna-c-lift:           30%;      /* clair : la hausse est franche, le fond est déjà pâle */
  --tw-surface-raised:    color-mix(in srgb, var(--tw-surface) calc(100% - var(--dna-c-lift)), #fff);
  --tw-surface-translucent: color-mix(in srgb, var(--tw-surface) 80%, transparent);
  --tw-on-surface:        var(--theme-foreground);
  --tw-overlay:           color-mix(in srgb, var(--theme-foreground), transparent 84%);
  --tw-media-placeholder: linear-gradient(135deg, var(--dna-c-sand), color-mix(in srgb, var(--dna-c-peach-deep) 55%, var(--theme-foreground)));  /* "photo absente" — dégradé qui suit la teinte (PAS la stratégie secondaire) */
  /* B. Texte */
  --tw-text:              var(--theme-foreground);
  --tw-text-heading:      var(--theme-foreground);
  --tw-text-muted:        var(--theme-muted-foreground);
  --tw-on-accent:         var(--theme-background);
  --tw-eyebrow-color:     var(--tw-eyebrow-color-auto);
  /* C. Accents */
  --tw-accent:            var(--theme-primary);
  --tw-accent-2:          var(--theme-secondary);
  --tw-accent-3:          var(--dna-c-olive);
  --tw-accent-soft:       color-mix(in srgb, var(--theme-primary) 14%, var(--tw-bg));   /* suit le canvas (neutre inclus) */
  /* D. Filets & bordures */
  --tw-border:            var(--dna-c-border-medium);
  --tw-border-width:      1px;
  --tw-rule:              var(--dna-c-border-soft);
  --tw-rule-strong:       color-mix(in srgb, var(--theme-foreground) 55%, transparent);
  --tw-rule-glyph-color:  var(--theme-primary);
  /* E. Typographie */
  --tw-font-heading:      var(--theme-font-display);
  --tw-font-body:         var(--theme-font-body);
  --tw-font-accent:       var(--theme-font-display);
  --tw-font-mono:         ui-monospace, 'SF Mono', Menlo, monospace;
  --tw-size-base:         var(--theme-font-size-base);
  --tw-size-h1:           clamp(3rem, 6.5vw, 5.75rem);
  --tw-size-h2:           clamp(2rem, 3.5vw, 3rem);
  --tw-size-h3:           1.625rem;
  --tw-size-h4:           1.3rem;
  --tw-size-h5:           1.1rem;
  --tw-size-h6:           1rem;
  --tw-size-lead:         1.25rem;
  --tw-size-meta:         0.875rem;
  --tw-size-eyebrow:      0.75rem;
  --tw-weight-heading:    var(--theme-font-weight-heading);   /* base = choix user */
  --tw-weight-step:       0;       /* C = graisse uniforme (Cormorant) ; pas d'échelle */
  --tw-weight-body:       400;
  --tw-tracking-heading:  -0.015em;
  --tw-tracking-eyebrow:  0.16em;
  --tw-case-display:      none;
  --tw-leading-body:      1.65;
  --tw-leading-heading:   1.15;
  --tw-leading-heading-tight: 1.05;
  --tw-leading-lead:      1.5;
  /* ⚠️ L'ACCENT COMME FOND ET L'ACCENT COMME TEXTE NE SONT PAS LE MÊME TON
     (C15). Le terracotta brut sur le papier crème rend 2,89:1 — mesuré sur un
     `<em>` « faite maison », visible seulement en 390 px, où le paragraphe
     change de composition. Foncé vers l'encre il garde sa teinte et passe à
     4,9:1, et il suit toujours les curseurs de l'utilisateur.

     > Une couleur d'accent est choisie pour APPARAÎTRE. Rien ne garantit
     > qu'elle se LISE. */
  --dna-c-accent-encre:   color-mix(in srgb, var(--theme-primary) 65%, var(--theme-foreground));
  --tw-em-color:          var(--dna-c-accent-encre);
  --tw-em-style:          italic;
  --tw-em-weight:         600;
  --tw-dropcap-color:     var(--theme-primary);
  --tw-dropcap-font:      var(--theme-font-display);
  --tw-dropcap-size:      3.4em;
  --tw-text-shadow:       none;
  /* F. Formes / radius */
  --tw-radius-button:     var(--theme-radius-button);
  --tw-radius-card:       var(--theme-radius-card);
  --tw-radius-media:      var(--theme-radius-image);
  --tw-radius-badge:      10px;    /* PAS de pill (interdit en C) */
  --tw-radius-sticker:    14px;
  --tw-radius-field:      12px;
  /* G. Profondeur (shadow = valeur composable, §11ter) */
  --tw-shadow-card:        0 8px 24px var(--dna-c-shadow-warm);
  --tw-shadow-card-hover:  0 12px 32px var(--dna-c-shadow-warm-strong);
  --tw-shadow-button:      0 4px 16px var(--dna-c-shadow-warm);
  --tw-shadow-button-hover:0 6px 20px var(--dna-c-shadow-warm-strong);
  /* Sous l'image seulement (étalement négatif, comme Marbre) : la photo se pose sur
     la page. `0 12px 40px` débordait de ~28 px sur les quatre côtés : un halo. */
  --tw-shadow-media:       0 18px 36px -22px var(--dna-c-shadow-media);
  --tw-glow:              none;
  --tw-blur:              0;
  --tw-hover-lift:        -2px;
  /* H. Bouton (rempli) */
  --tw-btn-bg:            var(--theme-primary);
  --tw-btn-text:          #000;
  --tw-btn-border:        transparent;
  --tw-btn-bg-hover:      var(--theme-primary);
  --tw-btn-text-hover:    #000;
  --tw-btn-weight:        600;
  --tw-btn-hover-lift:    -1px;
  --tw-btn-outline-text:  var(--theme-primary);
  --tw-btn-outline-border:var(--theme-primary);
  /* I. Lien-CTA (soulignement ondulé = signature C) */
  --tw-cta-color:             var(--theme-foreground);
  --tw-cta-underline-color:   var(--theme-primary);
  --tw-cta-underline-style:   wavy;
  --tw-cta-underline-thickness:1.5px;
  --tw-cta-underline-offset:  7px;
  --tw-cta-underline-hover:   var(--theme-secondary);
  /* J. Badge / tag (olive) */
  --tw-badge-bg:          color-mix(in srgb, var(--theme-secondary) 16%, var(--theme-background));
  --tw-badge-text:        color-mix(in oklab, var(--dna-c-olive) 72%, #000);   /* olive assombri : l'olive sur son lavis faisait 2,7:1 (audit du 2026-09-28) */
  --tw-badge-border:      transparent;
  /* K. Prix / sticker (pastille pêche tournée = signature C) */
  --tw-price-color:       var(--dna-c-accent-encre);
  --tw-price-font:        var(--theme-font-body);
  --tw-sticker-bg:        var(--dna-c-peach);
  --tw-sticker-border:    var(--theme-primary);
  --tw-sticker-text:      var(--theme-foreground);
  --tw-sticker-rotate:    -3deg;
  /* L. Rythme */
  --tw-section-py:        var(--theme-spacing-section-y);
  --tw-gap:               clamp(1.5rem, 3vw, 2.5rem);
  --tw-card-padding:      1.75rem;
  --tw-container:         1200px;
  --tw-container-pad:     clamp(1.5rem, 4vw, 3rem);
  /* M. Code (toujours sombre — teinte chaude pour rester cohérent) */
  --tw-code-bg:           #2a2018;
  --tw-code-text:         #e8dcc8;
  --tw-code-prompt:       #dca87a;
  --tw-code-keyword:      #cf9b6a;
  --tw-code-string:       #9eae7e;
  --tw-code-comment:      #8a7860;
  --tw-code-number:       #d9a378;
  --tw-code-function:     #e0c089;
  --tw-code-chrome-rule:  rgba(232, 220, 200, 0.12);  /* séparateur chrome, teinté chaud */
  /* Rating */
  --tw-icon-color:        var(--theme-primary);   /* C — par défaut = accent ; re-pointable par palette */
  --tw-rating-on:         var(--theme-primary);
  --tw-rating-off:        var(--dna-c-border-medium);
  /* Logo N&B (C) : gris foncé sur fond clair ; re-pointé en silhouette claire sur bande sombre/flood */
  --tw-logo-mono-filter:  grayscale(1);
  /* Backdrop (atmosphère — bloom géré par section, défaut none) */
  --tw-backdrop:          none;
  --tw-section-backdrop:  none;

  /* default palette = le set contextuel ci-dessus (sable de base) */
  color-scheme: light;
  /* display (héros) — remplace la règle :has() que 32 feuilles écrivaient */
  --tw-size-display: clamp(3rem, 6.5vw, 5.5rem);
  --tw-leading-display: 1.02;
  --tw-tracking-display: -0.01em;
}

@supports (color: oklch(from red l c h)){

  [data-dna="atelier"]{
  --tw-btn-text:          oklab(from var(--tw-btn-bg) var(--tw-sur-fond) 0 0 / 1);
  --tw-btn-text-hover:    oklab(from var(--tw-btn-bg-hover) var(--tw-sur-fond) 0 0 / 1);
  }
}

/* ---- (1bis) DARK : on n'override QUE les 14 + internes → les --tw-* recalculent */
[data-dna="atelier"][data-theme="dark"]{
  --tw-badge-text: var(--dna-c-olive);   /* en sombre, l'olive clair se lit ; l'olive assombri du mode clair, non */
  /* On n'override QUE les origines (hex sombres) ; hue-shift/chroma/accent2-shift
     restent hérités → la teinte choisie s'applique aussi en sombre. */
  --theme-primary:        oklch(0.7292 calc(0.1041 * var(--chroma-mult)) calc(50.7493 + var(--hue-shift)));
  --theme-secondary:      oklch(0.7259 calc(0.0687 * var(--chroma-mult)) calc(123.0762 + var(--hue-shift) + var(--accent2-shift)));
  --theme-background:     oklch(0.2499 calc(0.0224 * var(--chroma-mult)) calc(57.5148 + var(--hue-shift)));
  --theme-foreground:     oklch(0.9209 calc(0.0302 * var(--chroma-mult)) calc(78.7776 + var(--hue-shift)));
  --theme-muted-foreground:oklch(0.7454 calc(0.0519 * var(--chroma-mult)) calc(77.8289 + var(--hue-shift)));
  --dna-c-paper:          oklch(0.2832 calc(0.0262 * var(--chroma-mult)) calc(63.9637 + var(--hue-shift)));
  --dna-c-sand:           oklch(0.3171 calc(0.0299 * var(--chroma-mult)) calc(64.2914 + var(--hue-shift)));
  --dna-c-peach:          oklch(0.3044 calc(0.0342 * var(--chroma-mult)) calc(51.1018 + var(--hue-shift)));
  --dna-c-olive:          oklch(0.7259 calc(0.0687 * var(--chroma-mult)) calc(123.0762 + var(--hue-shift) + var(--accent2-shift)));
  --dna-c-peach-deep:     oklch(0.8092 calc(0.0823 * var(--chroma-mult)) calc(61.3972 + var(--hue-shift)));
  --dna-c-border-soft:    rgba(240, 227, 207, 0.10);
  --dna-c-border-medium:  rgba(240, 227, 207, 0.20);
  --dna-c-shadow-warm:    rgba(0, 0, 0, 0.30);
  --dna-c-shadow-warm-strong: rgba(0, 0, 0, 0.45);
  --dna-c-shadow-media:   rgba(0, 0, 0, 0.55);   /* en sombre, l'encre est CLAIRE : une ombre claire serait une lueur */
  --dna-c-lift:           15%;         /* cf. `--tw-surface-raised` : 30 % de blanc en sombre tuait le contraste des cartes (C15) */
  --tw-canvas-neutral:    #000000;     /* "Fond neutre" en dark : à 100% = NOIR PUR (le user dose le slider pour adoucir) */
  --tw-logo-mono-filter:  grayscale(1) brightness(0) invert(1);   /* dark : logos en silhouette claire (sinon gris-sur-sombre) */
  color-scheme: dark;
}

/* ============================================================================
   (5) LES 4 PALETTES DE SECTION — chacune réécrit le set contextuel COMPLET
   ============================================================================ */
/* subtle — un cran plus clair (papier) ; suit aussi le canvas neutre */
[data-dna="atelier"] [data-palette="subtle"]{
  --tw-bg:        color-mix(in srgb, var(--dna-c-paper), var(--tw-canvas-surface-neutral) calc(var(--bg-neutral) * 100%));
  --tw-surface:   var(--tw-surface-raised);
  --tw-on-surface:var(--theme-foreground);
  --tw-text:      var(--theme-foreground);
  --tw-text-heading: var(--theme-foreground);
  --tw-text-muted:var(--theme-muted-foreground);
  --tw-border:    var(--dna-c-border-medium);
  --tw-rule:      var(--dna-c-border-soft);
}
/* accent — bande sable-doré teintée (la CTA douce) */
[data-dna="atelier"] [data-palette="accent"]{
  --tw-bg:        var(--dna-c-sand);
  --tw-surface:   var(--theme-background);
  --tw-on-surface:var(--theme-foreground);
  --tw-text:      var(--theme-foreground);
  --tw-text-heading: var(--theme-foreground);
  --tw-text-muted: var(--tw-text);   /* flood MI-TON : même à 88 %, l'atténué tombait à 4:1 — il garde le texte (axe-core, audit du 2026-09-28) */
  --tw-border:    color-mix(in srgb, var(--theme-foreground) 16%, transparent);
  --tw-rule:      color-mix(in srgb, var(--theme-foreground) 14%, transparent);
  --tw-accent-soft: color-mix(in srgb, var(--theme-primary) 18%, var(--dna-c-sand));
}
/* bold — flood terracotta, texte crème (la dramatique) */
[data-dna="atelier"] [data-palette="bold"]{
  /* ⚠️ LE FLOOD ÉTAIT TROP CLAIR POUR SON PROPRE TEXTE (C15). Le terracotta
     brut rend 2,89:1 sous le crème — mesuré sur le site natif, en clair : un
     titre, un paragraphe et un bouton de la bande « On vous garde une table ? ».
     Ces trois-là étaient DANS le relevé de l'audit et n'ont jamais été lus :
     quatorze lignes d'étoiles répétées saturaient l'extrait, qui s'arrête à
     quatorze.

     > Un contrôle qui répète le même constat cent fois n'informe pas plus : il
     > enterre les quatre-vingt-dix-neuf autres.

     On garde la teinte — c'est la signature du DNA — et on la fonce vers
     l'encre, ce qui suit les curseurs de l'utilisateur au lieu d'une valeur
     figée. Crème sur la bande : 4,6:1. */
  --tw-bg:        color-mix(in srgb, var(--theme-primary) 64%, var(--theme-foreground));
  --tw-text:      var(--tw-on-accent);
  --tw-text-heading: var(--tw-on-accent);
  --tw-text-muted: var(--tw-text);   /* flood MI-TON : même à 88 %, l'atténué tombait à 4:1 — il garde le texte (axe-core, audit du 2026-09-28) */
  --tw-surface:   var(--dna-c-peach);
  --tw-surface-raised: color-mix(in srgb, var(--dna-c-peach) calc(100% - var(--dna-c-lift)), #fff);
  --tw-on-surface:var(--theme-foreground);
  --tw-overlay:   color-mix(in srgb, var(--theme-foreground), transparent 80%);
  --tw-border:    color-mix(in srgb, #fff 28%, transparent);
  --tw-rule:      color-mix(in srgb, #fff 30%, transparent);
  --tw-rule-strong: color-mix(in srgb, #fff 65%, transparent);
  /* Crème plutôt que peach : le peach reste sous 4,5:1 sur la bande foncée, et
     le crème est déjà la couleur du texte courant ici. */
  --tw-eyebrow-color: var(--tw-on-accent);
  --tw-em-color:  var(--tw-on-accent);             /* sinon terracotta sur terracotta = invisible */
  --tw-accent-soft: color-mix(in srgb, #fff 18%, transparent);
  /* badge sur flood = MONOCHROME crème ASSUMÉ (≠ K qui garde son secondaire).
     L'olive secondaire est un mi-ton : illisible sur le flood terracotta (~1.1)
     ET faible sur un chip crème (~2.7) — aucune variante ne le rend net. On
     abandonne donc le secondaire ici, contrairement à K dont le cyan (clair)
     ressort à 11.9 sur un chip sombre. C'est un choix PAR DNA, dicté par la
     lisibilité du secondaire sur SON flood. */
  --tw-badge-bg:  color-mix(in srgb, #fff 16%, transparent);
  --tw-badge-text:var(--tw-on-accent);
  --tw-badge-border: color-mix(in srgb, #fff 35%, transparent);
  --tw-price-color: var(--tw-on-accent);
  --tw-cta-color: var(--tw-on-accent);
  --tw-cta-underline-color: var(--tw-on-accent);
  --tw-cta-underline-hover: var(--dna-c-peach);
  /* bouton crème sur terracotta
     ⚠️ Le texte du bouton était le PRIMARY brut sur du crème : 2,89:1 (C15).
     Même geste que la bande — on fonce vers l'encre, la teinte reste. */
  --tw-btn-bg:    var(--theme-background);
  --tw-btn-text:  color-mix(in srgb, var(--theme-primary) 65%, var(--theme-foreground));
  --tw-btn-bg-hover: var(--theme-background);
  --tw-btn-text-hover: color-mix(in srgb, var(--theme-primary) 65%, var(--theme-foreground));
  --tw-btn-border: transparent;
  --tw-btn-outline-text: var(--tw-on-accent);
  --tw-btn-outline-border: color-mix(in srgb, #fff 60%, transparent);
  /* étoiles : sinon rating-on terracotta-sur-terracotta + rating-off brun = invisibles */
  --tw-rating-on:  var(--tw-on-accent);
  --tw-rating-off: color-mix(in srgb, #fff 30%, transparent);
  /* icône + glyphe divider + lettrine : sinon accent terracotta-sur-terracotta = invisibles */
  --tw-icon-color: var(--tw-on-accent);
  --tw-rule-glyph-color: var(--tw-on-accent);
  --tw-dropcap-color: var(--tw-on-accent);
  /* logos N&B : silhouette claire sur le flood (sinon gris-sur-terracotta se fond) */
  --tw-logo-mono-filter: grayscale(1) brightness(0) invert(1);
}

/* (5bis) ÎLOT — une carte MATÉRIALISÉE (card/raised) posée sur le flood `bold` est
   une surface CLAIRE : son CONTENU doit reprendre le contexte clair de base, sinon il
   hérite des tokens « on-flood » (crème) de la bande et devient invisible sur la carte
   (étoiles/icônes/em/prix/boutons). La brique re-localise déjà le TEXTE (--tw-on-surface) ;
   ici le DNA restaure le reste du set contextuel (il a le droit de lire --theme-*). Scopé
   `bold` uniquement → aucun impact sur default/subtle/accent (cartes déjà sur fond clair).
   Le FOND de carte reste --tw-surface (peach) — on ne restaure que le contenu. */
[data-dna="atelier"] [data-palette="bold"] :is(.tw-surface[data-surface="card"], .tw-surface[data-surface="raised"], .product-card, .blog-card:not(.blog-card--flat)){
  --tw-border:    var(--dna-c-border-medium);
  --tw-rule:      var(--dna-c-border-soft);
  --tw-rule-strong: color-mix(in srgb, var(--theme-foreground) 55%, transparent);
  --tw-eyebrow-color: var(--dna-c-accent-encre);
  --tw-em-color:  var(--dna-c-accent-encre);
  --tw-rating-on: var(--theme-primary);   /* symbole : 3:1 suffit (WCAG 1.4.11) */
  --tw-rating-off: var(--dna-c-border-medium);
  --tw-icon-color: var(--theme-primary);
  --tw-rule-glyph-color: var(--theme-primary);
  --tw-dropcap-color: var(--theme-primary);
  --tw-price-color: var(--dna-c-accent-encre);
  --tw-cta-color: var(--theme-foreground);
  --tw-cta-underline-color: var(--theme-primary);
  --tw-cta-underline-hover: var(--theme-secondary);
  --tw-accent-soft: color-mix(in srgb, var(--theme-primary) 14%, var(--theme-background));
  /* badge : retour à l'olive secondaire de base (le flood l'avait passé en crème mono) */
  --tw-badge-bg:  color-mix(in srgb, var(--theme-secondary) 16%, var(--theme-background));
  --tw-badge-text: var(--dna-c-olive);
  --tw-badge-border: transparent;
  /* bouton : retour au rempli terracotta de base (le flood l'avait inversé en crème) */
  --tw-btn-bg:    var(--theme-primary);
  --tw-btn-text:  #000;   /* texte blanc ou noir selon le fond, comme le bouton de la page : le texte fixe faisait 2-3:1 sur la carte d'une bande forte (axe-core, 2026-09-28) */
  --tw-btn-bg-hover: var(--theme-primary);
  --tw-btn-text-hover: var(--theme-background);
  --tw-btn-outline-text: var(--theme-primary);
  --tw-btn-outline-border: var(--theme-primary);
}
@supports (color: oklch(from red l c h)){

  [data-dna="atelier"] [data-palette="bold"] :is(.tw-surface[data-surface="card"], .tw-surface[data-surface="raised"], .product-card, .blog-card:not(.blog-card--flat)){
  --tw-btn-text:  oklab(from var(--tw-btn-bg) var(--tw-sur-fond) 0 0 / 1);
  }
}

/* ============================================================================
   (4) SKIN DES BRIQUES — au-delà des vars (LE point variable de DNA-C)
   Ces règles ne sont PAS exprimables en simple re-pointage de token.
   ============================================================================ */

/* Sticker prix : la pastille pêche tournée (signature warm-artisan).
   La brique la cache par défaut ; DNA-C la matérialise. */
[data-dna="atelier"] .tw-price-sticker{
  display: inline-flex; flex-direction: column; align-items: center; text-align: center;
  position: absolute; bottom: -1rem; inset-inline-end: -1rem;
  background: var(--tw-sticker-bg); color: var(--tw-sticker-text);
  border: 1.5px solid var(--tw-sticker-border);
  border-radius: var(--tw-radius-sticker);
  padding: .55rem .95rem; line-height: 1; font-family: var(--tw-price-font); font-weight: 700;
  transform: rotate(var(--tw-sticker-rotate, var(--tw-tilt, 0deg)));
  box-shadow: 0 4px 12px var(--dna-c-shadow-warm-strong);
}
[data-dna="atelier"] .product-card--has-sticker .tw-price{ /* le body price reste, le sticker double l'info */ }

/* Diviseur ornemental : flourish dessiné à la main (1-2 par page max) */
[data-dna="atelier"] .tw-rule[data-variant="ornament"]{ padding-block: 2rem; opacity: .7; }
[data-dna="atelier"] .tw-rule[data-variant="ornament"]::after{
  content: ""; width: clamp(120px, 16vw, 200px); height: 22px;
  background: var(--tw-accent);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 22'%3E%3Cpath d='M4 11 Q24 1 44 11 T84 11 T124 11 T164 11 T196 11' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3Ccircle cx='100' cy='11' r='3.2' fill='black'/%3E%3C/svg%3E") center/contain no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 22'%3E%3Cpath d='M4 11 Q24 1 44 11 T84 11 T124 11 T164 11 T196 11' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3Ccircle cx='100' cy='11' r='3.2' fill='black'/%3E%3C/svg%3E") center/contain no-repeat;
}

/* Hero bloom : un seul radial pêche derrière le hero (atmosphère = skin), via le
   token --tw-section-backdrop que la couche .tw-backdrop--section (blocks-base, rendue
   DNA-agnostiquement dans chaque section) peint. Cible :has(.hero-block) car TOWeb
   n'émet PAS de classe `.content-section--hero` (le hero est un bloc dans la section).
   position/z-index sont gérés par blocks-base (`:has(> .tw-backdrop--section)`). */
[data-dna="atelier"] .content-section:has(.hero-block){
  --tw-section-backdrop: radial-gradient(circle at 72% 22%, var(--dna-c-peach) 0%, transparent 60%);
}

/* Quote : italique Cormorant + guillemets décoratifs de la brique (::before/::after).
   PAS de barre latérale (retirée — redondante avec les guillemets + désalignait la
   citation par rapport aux étoiles/auteur dans le testimonial. Retour Yann 2026-06-08). */
[data-dna="atelier"] .tw-quote p{ font-family: var(--tw-font-heading); font-style: italic; }

/* Eyebrow : UN seul ornement, le filet chaud SOUS le texte (voir la signature de
   composition, plus bas). Le tiret AVANT a été retiré (2026-09-28) : cumulé au
   filet, le sur-titre portait deux ornements, et un tiret d'un seul côté
   déséquilibre un sur-titre centré. */

/* ═══════════════════════════════════════════════════════════════════════════
   SIGNATURE HEADER + FOOTER — bar d'atelier CHALEUREUSE (cf §13.5bis)
   Reconnaissable rien qu'au menu/footer : crème chaud + filet terracotta +
   logo Cormorant à point d'artisan + nav en pastille chaude. Tokens re-pointés
   par mode (--tw-* / --dna-c-*) → lisible light ET dark, jamais de couleur en dur.
   ═══════════════════════════════════════════════════════════════════════════ */

/* HEADER — bandeau crème/sable doux, ombre chaude (autorisée en C), surmonté
   d'un filet terracotta dégradé qui chauffe la frontière basse (≠ hairline froide). */
[data-dna="atelier"] .site-header-block{
  position: relative;
  background:
    linear-gradient(180deg,
      var(--tw-surface) 0%,
      color-mix(in srgb, var(--dna-c-sand) 35%, var(--tw-surface)) 100%);
  border-bottom: 1px solid var(--tw-rule);
  box-shadow: 0 2px 14px var(--dna-c-shadow-warm);
}
/* le filet terracotta : trait chaud sous la barre, posé sur un ::before (ne
   contient aucun dropdown → backdrop-filter-safe ; ici simple dégradé, pas de frost) */
[data-dna="atelier"] .site-header-block::before{
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -1px;
  height: 2px;
  background: linear-gradient(90deg,
    transparent 0%,
    var(--tw-accent) 22%,
    var(--dna-c-peach-deep) 50%,
    var(--tw-accent) 78%,
    transparent 100%);
  opacity: .55;
  pointer-events: none;
}
/* Logo = mot Cormorant en terracotta, avec un petit point d'atelier accolé */
[data-dna="atelier"] .site-header-block__logo{
  font-family: var(--tw-font-heading);
  font-weight: 600;
  letter-spacing: .005em;
  color: var(--tw-accent);
}
[data-dna="atelier"] .site-header-block__logo::after{
  content: "";
  display: inline-block;
  width: .34em; height: .34em;
  margin-inline-start: .3em;
  border-radius: 50%;
  background: var(--dna-c-peach-deep);
  vertical-align: .12em;
}
/* Nav-link en pastille chaude au survol (coins ~radius C, lavis sable) */
[data-dna="atelier"] .site-header-block__link{
  border-radius: 8px;
  padding: 0.3em 0.65em;
  transition: background .15s ease, color .15s ease;
}
[data-dna="atelier"] .site-header-block__link:hover{
  background: color-mix(in srgb, var(--dna-c-sand) 55%, transparent);
  color: var(--tw-accent);
}

/* FOOTER — sable chaud (conservé), filet terracotta en tête (≠ border générique),
   titres serif chaleureux en petites-capitales tracées. */
[data-dna="atelier"] .site-footer-block{
  background: var(--dna-c-sand);
  border-top: 2px solid color-mix(in srgb, var(--tw-accent) 55%, transparent);
}
[data-dna="atelier"] .site-footer-block__heading{
  font-family: var(--tw-font-heading);
  text-transform: uppercase;
  letter-spacing: .14em;
  font-size: 0.82rem;
  color: color-mix(in srgb, var(--theme-primary) 45%, var(--theme-foreground));   /* terracotta ENCRÉ : le terracotta pur sur le sable du pied faisait 2,4:1 (axe, démo 2026-09-28) */
}
/* nom de marque (logo footer) en Cormorant chaud */
[data-dna="atelier"] .site-footer-block__logo-name{
  font-family: var(--tw-font-heading);
  color: var(--dna-c-accent-encre);   /* terracotta ENCRÉ : le terracotta pur sur le sable du pied faisait 2,4:1 (axe, démo 2026-09-28) */
}

/* Nav-link : l'ondulation est réservée au SURVOL (sinon = correcteur ortho). */
[data-dna="atelier"] .tw-nav-link{ --tw-nav-color: var(--tw-text); }
[data-dna="atelier"] .tw-nav-link:hover{
  color: var(--tw-text);
  text-decoration-line: underline;
  text-decoration-style: wavy;
  text-decoration-color: var(--tw-accent);
  text-decoration-thickness: 1.5px;
  text-underline-offset: 6px;
}

/* ═══════════════════════════════════════════════════════════════════════════
   SIGNATURE DE COMPOSITION — l'artisan CHALEUREUX (grand serif + ornement chaud)
   ═══════════════════════════════════════════════════════════════════════════ */
/* Eyebrow souligné d'un court ornement chaud (filet de 2,5 rem).
   ⚠️ LE FILET SUIT L'ALIGNEMENT DU TEXTE. En `display:block`, il restait collé au
   bord de début sous un titre centré (vitrines, 2026-09-28), et aucun sélecteur ne
   sait qu'un parent est centré. Il est donc EN LIGNE, sur sa propre ligne (`\A`
   sous `white-space: pre`) : `text-align`, hérité, le place comme le texte. Sa
   longueur vient de trois cadratins (3 × 0,8333 rem), son trait d'un fond de 2 px.
   Couleur du sur-titre, pas `--tw-accent` : sur la bande `bold`, un filet
   terracotta sur fond terracotta disparaissait. */
[data-dna="atelier"] .tw-eyebrow{ position: relative; }
[data-dna="atelier"] .tw-eyebrow::after{
  content: "\A\2003\2003\2003"; white-space: pre;
  font-size: 0.8333rem; letter-spacing: 0; line-height: 0;
  background: linear-gradient(var(--tw-eyebrow-color, var(--tw-accent)), var(--tw-eyebrow-color, var(--tw-accent))) left bottom / 100% 2px no-repeat;
}
/* icônes de features chaleureuses, plus présentes */
[data-dna="atelier"] .features-block > .tw-surface > .tw-icon{ --tw-icon-size: 1.5em; }
