/* ds.css — design system de l'écosystème (EPIC-023, plan DS1 §4). Trois blocs : tokens · base (éléments natifs) · composants.
 * Sans Bootstrap, aucun nom de classe Bootstrap 5.3.3. Adopter : class="ds" sur la racine d'un écran. Nuancer : un token
 * surchargé sur SA racine (.ds.mon-plugin { --gold: … }), jamais sur :root (contrat des plugins, plan DS1 §4.6).
 * Thème d'un site (audit DS v2, R4) : TROIS sources, --woad (couleur de base), --gold (accent), --title (police de titre),
 * posées sur <html> ; toutes les autres nuances en dérivent. */
@import './fonts.css';

/* ── 1. Tokens ─────────────────────────────────────────────────────────── */
:root {
  /* thème : les trois sources (défaut = tuatha, bleu et or, titres en onciale) */
  --woad: #16294A;
  --gold: #D4B26A;
  --gold-ink: var(--gold); /* or en texte : foncé en clair (--gold est posé en ligne par le site) */
  --fill: oklch(from var(--woad) 0.284 c h); /* --woad en fond (bouton, lien courant), à la luminance du guède : un thème plus clair (émeraude) reste sous le texte AA (AUDITDS1 M1) ; clair en clair */
  --on-gold: var(--night); /* texte sur fond or (bouton primaire) : toujours foncé */
  --title: 'Uncial Antiqua', Georgia, serif;
  /* nuances dérivées de --woad : L et C mesurés sur la palette du deck (tuatha/web/index.html), teinte de --woad
     avec son décalage mesuré — une autre base garde les mêmes écarts de nuance (audit DS v2, C8) */
  --night: oklch(from var(--woad) 0.224 0.040 calc(h - 5.4));
  --woad-line: oklch(from var(--woad) 0.393 0.079 calc(h - 2.4));
  --text: oklch(from var(--woad) 0.943 0.013 calc(h - 9.1));
  --text-soft: oklch(from var(--woad) 0.758 0.042 calc(h - 10));
  --glow: oklch(from var(--woad) 0.787 0.104 calc(h - 30));
  /* Samhain (US-COMM-018) : une nuit qui ne bascule pas en mode clair (calculée sur --woad, jamais sur --night) ; le voile
     est plus sombre au centre, la lueur cerne d'or un texte de la couleur de l'abysse */
  --abysse: oklch(from var(--woad) 0.09 calc(c * 0.6) h);
  --voile: radial-gradient(ellipse at center, var(--abysse) 30%, var(--woad) 80%, oklch(from var(--woad) 0.34 c h));
  --lueur-or: 0 0 0.04em var(--gold), 0 0 0.25em var(--gold), 0 0 0.7em color-mix(in oklch, var(--gold) 60%, transparent);
  /* verre (audit DS v2, R1) : un voile de nuit léger (sombre, translucide), un flou, un bord lumineux,
     une ombre intérieure légère (DSV10 : celle de b2ad363, que DSV1 avait retirée à tort) */
  --glass: color-mix(in srgb, var(--night) 45%, transparent);
  --blur: blur(0.75rem) saturate(1.2);
  --edge: thin solid color-mix(in srgb, var(--text) 12%, transparent);
  --inset: inset 0 0 1.5rem color-mix(in srgb, #000 35%, transparent);
  --voile-pole: #000; --voile-niveau: 25%; /* profondeur [data-niveau] : voile du pôle opposé au texte */
  /* sémantique : AA mesuré sur --night (≥ 6,5:1) ; fixes, quel que soit le thème */
  --ok: #7CCB9A;
  --warn: #E6A85C;
  --danger: #F08A80;
  /* texte */
  --body: system-ui, -apple-system, 'Segoe UI', sans-serif;
  --mono: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;
  --fs-1: 0.875rem;
  --fs-2: 1rem;
  --fs-3: 1.375rem;
  --fs-4: 2.25rem;
  /* espace, forme, trait */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 1rem;
  --space-4: 1.5rem;
  --radius: 0.375rem;
  --stroke: 1.75;
  /* mouvement et halo (hero et cliquables seulement, plan §4.4) */
  --ease: cubic-bezier(0.2, 0.7, 0.2, 1);
  --dur: 180ms;
  --halo: 0 0 0 1px color-mix(in srgb, var(--glow) 55%, transparent),
    0 0 18px color-mix(in srgb, var(--glow) 35%, transparent);
  --backdrop: radial-gradient(120% 90% at 85% 10%, var(--woad) 0%, var(--night) 60%) fixed var(--night);
}

/* Mode clair (DSV10) : l'attribut `data-mode="light"` (`mode.js`) inverse la luminosité des mêmes teintes, calculées
   depuis la palette du site ; sans attribut, le mode sombre de `:root` (défaut). Attribut seul, pas `:root[…]` : il vaut
   sur <html> (dagda, lug) comme sur l'en-tête de tuatha, dont le deck reste sombre. L : AA mesuré (smoke DSV10). */
[data-mode='light'] {
  --voile-pole: #fff;
  --night: oklch(from var(--woad) 0.965 0.012 calc(h - 5.4));
  --woad-line: oklch(from var(--woad) 0.82 0.05 calc(h - 2.4));
  --text: oklch(from var(--woad) 0.25 0.03 calc(h - 9.1));
  --text-soft: oklch(from var(--woad) 0.44 0.04 calc(h - 10));
  --glow: oklch(from var(--woad) 0.47 0.12 calc(h - 30));
  --gold-ink: oklch(from var(--gold) 0.47 c h);
  --fill: oklch(from var(--woad) 0.9 0.035 h);
  --on-gold: var(--text);
  --inset: inset 0 0 3rem color-mix(in srgb, #000 20%, transparent);
  --ok: #1A6E3E; /* AA sur --fill clair (AUDITDS1 M1 : #1E7A45 y valait 3,97) */
  --warn: #8A5300;
  --danger: #B3261E;
  --backdrop: radial-gradient(120% 90% at 85% 10%, var(--woad-line) 0%, var(--night) 60%) fixed var(--night);
}

/* L'écart des primitives ne s'hérite pas : un --gap posé sur un parent ne fuit pas dans ses enfants. */
@property --gap {
  syntax: '*';
  inherits: false;
}

/* ── 2. Base : éléments natifs, sans classe ────────────────────────────── */

/* 🔬 Le fond plein écran est porté par le `<body>`, PAS par une hauteur sur `.ds`.
   Mesuré le 2026-09-20 (lot DS6) : `min-block-size: 100dvh` sur `.ds` valait 900 px alors que
   la racine est montée 57 px SOUS la barre de navigation ⇒ document 973 pour un viewport 900,
   soit 73 px de scroll mort sur TOUS les écrans migrés. Inoffensif tant que rien ne défile au
   montage ; les deux écrans de chat le font (`scrollIntoView`) et y perdaient leur `<h1>`.
   `:has(.ds)` borne la règle aux écrans migrés : un écran non migré garde son fond (contrôle
   négatif : `/dagda/blog` ne matche pas). Elle disparaîtra quand `.ds` passera sur `<body>`
   (`R2`), et son motif est alors sans objet. */
/* `margin: 0` : la valeur du reboot de Bootstrap (`R22`), dont le `<body>` dépendait (piège `DS3` : 16 px de large perdus)
   — posée ici avant le retrait du CDN (IMPL-DS P7). */
/* Le fond est le dégradé du deck (tuatha/web/index.html) et reste fixe : c'est ce que le verre laisse voir (audit DS v2, C4). */
body:has(.ds) {
  margin: 0;
  background: var(--backdrop);
  scrollbar-color: var(--woad-line) var(--night);
}

/* Pas de fond sur .ds : un aplat y masquerait le dégradé du <body>, que le verre doit laisser voir. */
.ds {
  color-scheme: dark;
  color: var(--text);
  font: var(--fs-2) / 1.6 var(--body);
  -webkit-font-smoothing: antialiased;
}

[data-mode='light'], [data-mode='light'] .ds { color-scheme: light; }

.ds *,
.ds *::before,
.ds *::after { box-sizing: border-box; }
.ds :is(h1, h2, h3, h4, p, ul, ol, dl, figure, blockquote, pre, table, fieldset) { margin: 0; }
.ds :is(ul, ol) { padding-inline-start: var(--space-4); }
/* Une liste mise en page par une primitive n'a ni puce ni retrait : sans cette règle, chaque <li> nu d'une
   `ul.stack` gardait son marqueur (24 listes, puces vues à la capture sur Taxonomies, Cercles et le chat). */
.ds :is(ul, ol):is(.stack, .cluster, .grid) { list-style: none; padding-inline-start: 0; }

.ds :is(h1, h2, h3, h4) {
  font-family: var(--title);
  font-weight: 400;
  line-height: 1.15;
  letter-spacing: 0.01em;
  text-wrap: balance;
  /* `anywhere`, pas `break-word` : seul il réduit la largeur minimale d'un titre enfant de flex (« lug.persona.dev »
     débordait de 19 px à 320, AUDITDS10 ; même remède que le sous-titre de PageHeader). */
  overflow-wrap: anywhere;
}

.ds :is(h1, h2) { color: var(--gold-ink); }
.ds h1 { font-size: var(--fs-4); }
.ds h2 { font-size: var(--fs-3); }
.ds h3 {
  font-size: calc(var(--fs-2) * 1.125);
  /* La police de titre est une police d'affiche : en h3 (cartes, listes d'articles), elle coupait un titre sur 2 à 4 lignes
     (critique du CV de silenus, 2026-10-19). Le h3 prend la police du corps ; h1 et h2 gardent celle du thème. */
  font-family: var(--body);
  font-weight: 600;
  letter-spacing: 0;
}

.ds h4 {
  font-size: var(--fs-2);
  font-family: var(--body);
  color: var(--text-soft);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

/* Une puce se lit comme un paragraphe : sans cette borne, 119 caractères par ligne mesurés sur le CV de silenus (2026-10-19).
   Une diapositive est un `<li>` mais pas une puce : bornée, elle tenait 713 px sur 1 888 (US-COMM-018). */
.ds :is(p, li:not([aria-roledescription='slide'])) {
  max-inline-size: 70ch;
  text-wrap: pretty;
}

.ds :is(small, figcaption) {
  font-size: var(--fs-1);
  color: var(--text-soft);
}

/* Media : la largeur est bornee, la hauteur suit le ratio, et le rayon est celui du systeme —
   c est ce que `img-fluid rounded` donnait aux ecrans Bootstrap, et il n y a aucune raison de le
   redire dans chaque composant. Le rayon sur un `svg.icon` n a aucun effet visible (pas de fond,
   pas de bordure) : une seule regle vaut mieux que deux selecteurs. */
.ds :is(img, svg, video) {
  max-inline-size: 100%;
  height: auto;
  border-radius: var(--radius);
}

/* `color` et `opacity` : les valeurs du reboot de Bootstrap (`R22`) — l'A/B CDN bloqué d'Ogma rendait « différent »
   par ce seul filet (IMPL-DS P6) ; 25 % est l'aspect que les écrans migrés montraient déjà, CDN présent. */
.ds hr {
  border: 0;
  border-top: thin solid var(--woad-line);
  margin: var(--space-3) 0;
  color: inherit;
  opacity: 0.25;
}

.ds blockquote {
  padding-inline-start: var(--space-3);
  border-inline-start: 0.1875rem solid var(--gold);
  color: var(--text-soft);
  font-style: italic;
}

.ds :is(code, kbd, pre) {
  color: var(--text);
  font-family: var(--mono);
  font-size: var(--fs-1);
}

/* `overflow-wrap` : la valeur du reboot de Bootstrap sur `code` (`R22`) — sans elle un chemin débordait de 67 px à 320 (IMPL-DS P7). */
.ds :is(code, kbd) {
  padding: 0.1em 0.35em;
  border-radius: var(--radius);
  background: var(--night);
  overflow-wrap: break-word;
}

.ds kbd {
  border: thin solid var(--woad-line);
  border-bottom-width: 0.125rem;
}

.ds pre {
  overflow-x: auto;
  padding: var(--space-3);
  border-radius: var(--radius);
  background: var(--night);
  border: var(--edge);
}

.ds ::selection { background: color-mix(in srgb, var(--gold) 35%, transparent); }

/* Liens de texte et onglets : le halo est une lueur du texte, jamais une boîte. */
.ds a {
  color: var(--glow);
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, var(--glow) 40%, transparent);
  text-underline-offset: 0.2em;
  transition: color var(--dur) var(--ease), text-decoration-color var(--dur) var(--ease),
    text-shadow var(--dur) var(--ease);
}

.ds :is(a[href], [role='tab']):is(:hover, :focus-visible) {
  color: var(--text);
  text-decoration-color: currentColor;
  text-shadow: 0 0 0.75rem color-mix(in srgb, var(--glow) 60%, transparent);
}

.ds [aria-current='page'] { color: var(--gold-ink); }
.ds [id] { scroll-margin-block-start: calc(3.5rem + var(--space-3)); } /* une ancre s'arrête sous l'en-tête collant */

/* Anneau de focus : toujours présent, le halo ne le remplace jamais. */
.ds :focus-visible {
  outline: 0.125rem solid var(--glow);
  outline-offset: 0.125rem;
}

/* Boutons : <button> natif ; submit = action principale (un lien d'action principale : role="button" + data-principal) ;
   data-tone="danger" = destructive. */
.ds :is(button, [role='button']) {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-block-size: 2.75rem;
  padding: var(--space-2) var(--space-3);
  border: thin solid var(--woad-line);
  border-radius: var(--radius);
  background-color: var(--fill);
  color: var(--text);
  font: 600 var(--fs-1) / 1 var(--body);
  /* Un libelle de bouton ne se coupe pas : arme, « Confirmer ? » tombait sur deux lignes dans une
     barre compacte et deformait la ligne entiere (mesure DS9, ConfirmButton). */
  white-space: nowrap;
  letter-spacing: 0.02em;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease),
    box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease);
}

.ds :is(button[type='submit'], [role='button'][data-principal]) {
  border-color: var(--gold);
  background-color: var(--gold);
  color: var(--on-gold);
}

.ds :is(button, [role='button']):active:not(:disabled) { transform: translateY(0.0625rem); }

.ds :is(button, input, select, textarea):disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* Champs : filet bleu, soulignement en --text-soft (3:1 exigé pour un contrôle, WCAG 1.4.11). */
.ds :is(input:not([type='checkbox'], [type='radio'], [type='range']), select, textarea) {
  inline-size: 100%;
  min-block-size: 2.75rem;
  padding: var(--space-2) var(--space-3);
  border: thin solid var(--woad-line);
  border-bottom-color: var(--text-soft);
  border-radius: var(--radius);
  background: var(--night);
  color: var(--text);
  font: inherit;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}

.ds :is(input, select, textarea):not(:disabled, [aria-invalid='true']):hover { border-color: var(--text-soft); }
.ds :is(input, select, textarea):focus-visible { border-color: var(--glow); }
.ds :is(input, select, textarea)[aria-invalid='true'] { border-color: var(--danger); }

/* Obligation de saisie — marqueur DERIVE, source unique : l'attribut `required` du controle
   (`ADR-053 R14`, pose au premier ecran migre qui en porte). Un asterisque ecrit a la main dans
   chaque libelle serait une SECONDE source, libre de diverger de la garde JS qui grise le bouton.
   Un patron, DEUX points d'application : le libelle prend l'asterisque quand il y en a un, le
   controle prend le rail dans TOUS les cas — les saisies compactes vivent sans `<label>` et
   n'auraient sinon aucun marqueur. Le rail EPAISSIT le filet qui existe deja (1px) : c'est la
   largeur qui porte le signal, la couleur reste neutre : le rouge est reserve a l erreur (`aria-invalid`, AUDITDS1 M3). */
.ds label:has(+ input[required], + select[required], + textarea[required])::after {
  content: ' *';
  color: var(--danger);
}

.ds :is(input, select, textarea)[required] { border-inline-start: 0.1875rem solid var(--text-soft); }

.ds ::placeholder { color: var(--text-soft); opacity: 0.7; }

.ds textarea { min-block-size: 6rem; resize: vertical; }

.ds :is(input[type='checkbox'], input[type='radio'], input[type='range'], progress, meter) {
  accent-color: var(--gold);
  margin: 0;
  color: inherit;
  font: inherit;
}

/* `inline-block` : la valeur du reboot de Bootstrap, dont 7 libellés hors `.field` dépendaient encore (A/B CDN bloqué, P0). */
.ds label { display: inline-block; font-size: var(--fs-1); font-weight: 600; }

/* Terme d'une liste de définitions : `700`, la valeur du reboot de Bootstrap (`R22`). Les fiches de lug (détail d'un
   agent, d'un cycle) en dépendaient : l'A/B CDN bloqué rendait « différent » par ce seul gras (IMPL-DS P6). */
.ds dt { font-weight: 700; }

.ds fieldset {
  padding: var(--space-3);
  border: thin solid var(--woad-line);
  border-radius: var(--radius);
}

.ds legend {
  padding-inline: var(--space-2);
  font-size: var(--fs-3);
  color: var(--gold-ink);
  font-family: var(--title);
}

/* Tableaux : défilent sur mobile, occupent la largeur ensuite. */
.ds table {
  font-variant-numeric: tabular-nums;
  display: block;
  overflow-x: auto;
  max-inline-size: 100%;
  border-collapse: collapse;
  font-size: var(--fs-1);
}

/* Onglets d'admin (M6) : une ligne qui défile, au lieu de quatre lignes à 390. */
.ds ul[data-admin-tabs] { flex-wrap: nowrap; overflow-x: auto; white-space: nowrap; }

/* Indice de défilement (M2, M6), en CSS seul : les caches suivent le contenu (`local`) et couvrent l'ombre du bord
   atteint ; l'ombre (`scroll`) reste au bord tant qu'il reste à voir. Cache en --night : le fond du verre. */
.ds table, .ds ul[data-admin-tabs] {
  --indice: color-mix(in srgb, var(--text-soft) 35%, transparent);
  background:
    linear-gradient(to right, var(--night) 30%, transparent) left / 2rem 100% no-repeat local,
    linear-gradient(to left, var(--night) 30%, transparent) right / 2rem 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%, var(--indice), transparent) left / 0.75rem 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%, var(--indice), transparent) right / 0.75rem 100% no-repeat scroll;
}

/* Parité avec le reboot de Bootstrap (bordures nulles mais PLEINES) : sans elle, l'A/B CDN bloqué rendait « différent »
   sur chaque tableau, pour une bordure de 0 px — un bruit qui aurait caché une vraie régression au retrait du CDN (P7). */
.ds :is(thead, tbody, tfoot, tr, td, th) { border: 0 solid; border-color: inherit; }

.ds :is(th, td) {
  padding: var(--space-2) var(--space-3);
  border-bottom: thin solid var(--woad-line);
  text-align: start;
  vertical-align: top;
}
/* Contrôles en cellule (US-DAG-DS-008) : une liste garde sa largeur naturelle (à 100 % d'une cellule en largeur auto, elle
   se réduisait à sa flèche) — `:not([multiple])` donne la spécificité qui passe la règle des champs ; une case SEULE dans sa
   cellule a une cible (avec un libellé, le libellé l'élargit déjà, et la case agrandie le renvoyait à la ligne). */
.ds td select:not([multiple]) { inline-size: auto; }
.ds td > :is(input[type='checkbox'], input[type='radio']) { inline-size: var(--space-4); block-size: var(--space-4); }

.ds th {
  color: var(--gold-ink);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
}

.ds tbody tr { transition: background-color var(--dur) var(--ease); }
.ds tbody tr:hover { background: color-mix(in srgb, var(--fill) 55%, transparent); }
.ds tbody tr[tabindex] { cursor: pointer; }
/* Bouton de tri (AUDITDS4 M12) : à plat dans son en-tête, il en garde la typographie. */
.ds th > button {
  min-block-size: 0;
  padding: 0;
  border-color: transparent;
  background-color: transparent;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
}
/* Densité compacte (É0 D8) : une ligne de table tient sur une ligne de texte. */
.ds table[data-densite='compact'] :is(th, td) { padding: var(--space-1) var(--space-2); white-space: nowrap; }
.ds table[data-densite='compact'] td .cluster { flex-wrap: nowrap; }
/* Compacte : la table tient dans sa largeur ; une cellule trop longue se coupe (le titre complet reste dans `title`). */
.ds table[data-densite='compact'] { table-layout: fixed; }
/* Cible tactile : 2,75rem partout (M8), 2,5rem dans une table compacte — à 2,75rem ses lignes grandissent (capture AUDITDS2 É8) ;
   une liste suit le bouton (US-DAG-DS-008). */
.ds table[data-densite='compact'] :is(button, [role='button'], select) { min-block-size: 2.5rem; }
.ds table[data-densite='compact'] :is(th, td) { overflow: hidden; text-overflow: ellipsis; }
/* Pleine largeur du registre Opérer (É0 D8) : lug, ogma, admin. */
.ds .page[data-largeur='pleine'] { max-inline-size: none; }

/* Accordéon : <details> natif. */
.ds details { border-bottom: thin solid var(--woad-line); }

.ds summary {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-radius: var(--radius); /* son halo de survol suit le rayon commun (coins carrés mesurés le 2026-10-10) */
  list-style: none;
  cursor: pointer;
  font-weight: 600;
}

.ds summary::-webkit-details-marker { display: none; }

.ds summary::after {
  content: '+';
  color: var(--gold-ink);
  transition: transform var(--dur) var(--ease);
}

.ds details[open] > summary::after { transform: rotate(45deg); }
.ds details > :not(summary) { padding-block-end: var(--space-3); }

/* Onglets : `role="tab"` porté par un <button> OU par un <a> — le contrat de la maison veut un onglet
   ADRESSABLE (`?onglet=`, `BarreOngletsInternes.tsx`), donc une ancre. 🔬 D'où `text-decoration: none` :
   mesuré à la capture du lot DSCOMP1, une barre d'onglets en ancres sortait SOULIGNÉE — trois onglets
   soulignés à côté d'un onglet désactivé (un <span>) qui ne l'était pas, et un filet or sous l'onglet
   actif DOUBLÉ par le soulignement. Aucune porte ne le voyait : le défaut est arrivé avec la migration
   (le `.nav-link` de Bootstrap ne soulignait pas), et il est en production sur les écrans d'admin. */
/* Barre d'onglets : chaque onglet est un bouton de verre (demande du porteur, 2026-09-24 : « pas assez marqués »),
   la barre ne porte pas de voile (verre jamais empilé, taste/design-taste.md) ; l'actif : bord, texte et teinte d'or. */
.ds [role='tablist'] {
  display: flex;
  gap: var(--space-2);
  padding-block: var(--space-1); /* l'anneau de focus déborde de 0,25rem : la barre défile, elle le couperait */
  overflow-x: auto;
}

.ds [role='tab'] {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding-inline: var(--space-3);
  min-block-size: 2.75rem;
  border: var(--edge);
  border-radius: var(--radius);
  background-color: var(--glass);
  box-shadow: var(--inset);
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
  color: var(--text-soft);
  text-decoration: none;
  /* Un onglet ne se coupe pas, qu'il soit un bouton ou un lien : la barre défile (IMPL-DS P6 lot 20, onglets de nuada à 390). */
  white-space: nowrap;
}

.ds [role='tab'][aria-selected='true'] {
  border-color: var(--gold);
  background-image: linear-gradient(color-mix(in srgb, var(--gold) 24%, transparent) 0 0);
  color: var(--gold-ink);
}

/* Modale, tiroir et menu déroulant : <dialog> et [popover] natifs, en verre (seuls panneaux flottants : aucune classe). */
.ds :is(dialog, [popover]) {
  border: thin solid var(--woad-line);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--night) 72%, transparent);
  color: var(--text);
  box-shadow: var(--inset);
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
}

.ds dialog {
  inline-size: min(40rem, 100% - 2 * var(--space-3));
  padding: var(--space-4);
}

.ds dialog::backdrop {
  background: color-mix(in srgb, var(--night) 60%, transparent);
  backdrop-filter: blur(0.25rem);
}

.ds [popover] { padding: var(--space-2); }

/* Menu déroulant : ancré sous son bouton (ancre implicite de `popovertarget`). Mesuré le 2026-09-21 (DSCOMP2) :
   sans cette règle, il s'ouvrait au CENTRE de la fenêtre. Sans anchor positioning, il y reste — lisible. */
@supports (position-area: bottom) {
  .ds [popover] {
    inset: auto;
    margin: var(--space-1) 0;
    position-area: bottom span-right;
    position-try-fallbacks: flip-block, flip-inline;
  }
}

/* ── 3. Composants : une classe n'existe que si un élément natif ne suffit pas ── */

/* Mise en page : trois primitives, un seul écart (--gap) — pas d'utilitaire d'espacement. */
.ds .stack {
  display: flex;
  flex-direction: column;
  gap: var(--gap, var(--space-3));
}

.ds .cluster {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--gap, var(--space-3));
}

.ds .grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--min, 15rem)), 1fr));
  gap: var(--gap, var(--space-3));
}

/* Matrice 2×2 (US-DAG-DS-006, D4 de BREN7) : quatre quadrants, lus haut-gauche, haut-droite, bas-gauche, bas-droite ;
   une colonne sous 62em, deux au-delà. Budget annoncé : 12 lignes ; employé : 6. */
.ds .matrix { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--gap, var(--space-3)); }

.ds a.surface {
  color: inherit;
  text-decoration: none;
}
/* Carte-bouton (elle ouvre sur place : KanbanCard, épopées de nuada) : la carte, pas le bouton — ni centrage, ni ligne unique, ni graisse. */
.ds button.surface { align-items: stretch; justify-content: flex-start; white-space: normal; text-align: start; font: inherit;
  letter-spacing: normal; color: inherit; }

/* Surface : du verre (audit DS v2, R1) — voile de nuit léger, flou, bord lumineux, ombre intérieure. Partage sa recette avec le hero et les colonnes du cadre. */
.ds :is(.surface, .hero, .frame > nav) {
  border: var(--edge);
  border-radius: var(--radius);
  background: var(--glass);
  box-shadow: var(--inset);
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
}

.ds .surface { padding: var(--space-4); }

/* Imbriquée, une surface n'ajoute ni voile ni flou : son bord la détache (une carte dans une carte se lit mal).
   ⟲ Le motif « trois voiles sous AA » (audit DS v2) valait pour la recette d'alors : remesuré le 2026-10-10 sur
   --glass actuel, un voile de nuit de plus MONTE le contraste (silenus projet/04-qa/audits/2026-10-10-…). */
.ds .surface .surface {
  background: none;
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

/* Profondeur : chaque [data-niveau] imbriqué ajoute un voile (pôle opposé au texte : le contraste monte, --text-soft
   6,04 → 9,07:1 en sombre, 6,05 → 7,14:1 en clair, mesuré le 2026-10-10) et une ombre intérieure, sans flou. */
.ds [data-niveau] { padding: var(--space-2); border: var(--edge); border-radius: var(--radius); box-shadow: var(--inset);
  background: color-mix(in srgb, var(--voile-pole) var(--voile-niveau), transparent); }

/* Cascade (Cascade.tsx) : dépliant sans filet (le bord du niveau le borne), libellé aligné sur le contenu des cartes. */
.ds details:has(> [data-niveau]) { border: 0; }
.ds details:has(> [data-niveau]) > summary { padding-inline: var(--space-2); color: var(--text-soft); }

/* Carte longue (CarteLongue) : média ou icône décorative à gauche, texte à droite (dessous faute de 9rem), suite dessous. */
.ds .media { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--space-3); padding: var(--space-3); }
.ds .media > :first-child { flex: 0 1 clamp(4rem, 28%, 12rem); min-inline-size: 0; aspect-ratio: 16 / 9; object-fit: cover; }
.ds .media > :nth-child(2) { flex: 1 1 9rem; }
.ds .media > :nth-child(n + 3) { flex: 1 0 100%; }
.ds .media > span:first-child { display: grid; place-items: center; border: var(--edge); border-radius: var(--radius); color: var(--gold-ink); font-size: var(--fs-4); }

/* En-tête et pied de la coquille (audit DS v2, R2) : repérés par leur rôle, pas par une classe — chaque écran a
   aussi ses <header>. Le verre sans rayon, collé en haut : le contenu défile visiblement dessous. */
.ds :is([role='banner'], [role='contentinfo']) {
  padding: var(--space-2) var(--space-3);
  background: var(--glass);
  box-shadow: var(--inset);
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
}

.ds [role='banner'] {
  position: sticky;
  inset-block-start: 0;
  z-index: 10;
  border-block-end: var(--edge);
  justify-content: space-between;
  align-items: center;
}

/* Lien d'évitement (M8) : premier lien de l'en-tête, hors du flux, caché au-dessus de l'écran jusqu'au focus clavier. */
.ds [data-evitement] {
  position: absolute;
  inset-block-start: var(--space-2);
  inset-inline-start: var(--space-3);
  padding: var(--space-2) var(--space-3);
  background: var(--night);
  transform: translateY(-300%);
}
.ds [data-evitement]:focus-visible { transform: none; }

/* En-tête (`EnTete.tsx`) : la marque à gauche, en police de titre et en or — le seul accent ; la navigation à droite,
   en boutons discrets (ni fond ni bord). Un menu ouvert garde l'or sur son bouton. */
.ds [role='banner'] [data-marque] {
  font: 400 var(--fs-3) / 1 var(--title);
  color: var(--gold-ink);
  text-decoration: none;
}

.ds [role='banner'] > nav > ul { align-items: center; gap: var(--space-1); }

.ds [role='banner'] :is(nav button, button[data-marque]) {
  border-color: transparent;
  background-color: transparent;
  color: var(--text-soft);
  font-weight: 500;
  /* Retrait serré : à 390 px, la navigation tient sur UNE ligne sous la marque (mesuré : 3 lignes sinon). */
  padding-inline: var(--space-2);
}

.ds [role='banner'] button[data-marque] { color: var(--gold-ink); font-weight: 400; }

/* Un lien posé dans la barre (« Se connecter ») a l'allure d'un bouton de menu, pas celle d'une entrée de liste. */
.ds [role='banner'] > nav > ul > li > a { padding-inline: var(--space-2); font: 500 var(--fs-1) / 1 var(--body); }

/* Un titre à icône garde l'icône sur sa première ligne : c'est le texte qui passe à la ligne (mesuré : un titre
   long laissait l'icône seule au-dessus). */
.ds :is(h1, h2, h3).cluster { flex-wrap: nowrap; }

.ds [role='banner'] :is(nav button, button[data-marque]):is(:hover, :focus-visible, :has(+ :popover-open)) {
  background-color: color-mix(in srgb, var(--fill) 55%, transparent);
  color: var(--text);
}

.ds [role='banner'] button:has(+ :popover-open) { color: var(--gold-ink); }

/* La coquille — seule racine qui porte le pied — occupe la fenêtre : sur une page courte, le pied reste en bas.
   L'en-tête est DANS cette racine, donc 100dvh ne crée plus le scroll mort mesuré en DS6. */
.ds:has(> [role='contentinfo']) {
  display: flex;
  flex-direction: column;
  min-block-size: 100dvh;
}

.ds [role='contentinfo'] {
  margin-block-start: auto;
  padding-block: var(--space-4);
  border-block-start: var(--edge);
  color: var(--text-soft);
  font-size: var(--fs-1);
}

/* Lien isolé du pied de page : cible tactile d'au moins 24 px (WCAG 2.5.8). */
.ds [role='contentinfo'] a {
  display: inline-block;
  padding-block: var(--space-1);
}

/* Verre indisponible, ou transparence réduite par l'utilisateur : un fond opaque, le bord reste (audit DS v2, R1). */
@supports not ((backdrop-filter: blur(0)) or (-webkit-backdrop-filter: blur(0))) {
  .ds :is(dialog, [popover], .surface, .hero, .frame > nav, [role='tab'], [role='banner'], [role='contentinfo']) {
    background: var(--night);
  }
}

@media (prefers-reduced-transparency: reduce) {
  .ds :is(dialog, [popover], .surface, .hero, .frame > nav, [role='tab'], [role='banner'], [role='contentinfo']) {
    background: var(--night);
  }
}

/* Hero : un seul, et le verre seul (audit DS v2, C5) — ni dégradé, ni halo, ni titre en dégradé : un effet, pas quatre. */
.ds .hero {
  display: grid;
  gap: var(--space-3);
  padding: calc(var(--space-4) * 2) var(--space-4);
  text-align: center;
}

.ds .hero h1 {
  font-size: calc(var(--fs-4) * 1.6);
  letter-spacing: -0.01em;
}

.ds .hero p {
  margin-inline: auto;
  color: var(--text-soft);
  font-size: calc(var(--fs-2) * 1.125);
}

/* Les actions d'un hero, centrées comme son texte. */
.ds .hero .cluster { justify-content: center; }

/* Étiquette (et non « badge », classe de Bootstrap) ; ton porté par data-tone. */
.ds .tag {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 0.125rem var(--space-2);
  border: thin solid var(--woad-line);
  border-radius: var(--radius);
  color: var(--text-soft);
  font-family: var(--body); /* pas d onciale dans un titre (AUDITDS2 É9) */
  font-size: var(--fs-1);
  line-height: 1.4;
}

/* Chiffre de tête d'une carte : la valeur DOIT dominer le libellé qui la nomme. 🔬 Mesuré au rendu
   du cockpit SOC le 2026-09-20 : en `<code>` nu, la valeur d'un KPI sort à --fs-1, soit PLUS PETITE
   que son étiquette — le seul chiffre qu'on vient lire était le moins lisible de l'écran. Aucune
   porte ne le voyait ; la capture, si. Le `<code>` garde le chiffre en chasse fixe (colonnes). */
.ds .metric {
  font-size: var(--fs-3);
  line-height: 1.2;
}

/* Message (alerte) : role="status" ou role="alert", icône + texte ; le ton teinte le filet (pas de barre latérale). */
.ds .note {
  display: flex;
  gap: var(--space-2);
  padding: var(--space-3);
  border: thin solid var(--woad-line);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--fill) 45%, transparent);
}

/* Ton : data-tone pose --tone ; l'étiquette, le message et le bouton destructif le lisent (DRY). */
.ds [data-tone='ok'] { --tone: var(--ok); }
.ds [data-tone='warn'] { --tone: var(--warn); }
.ds [data-tone='danger'] { --tone: var(--danger); }
/* Palette des tiers de LLM (É0 D9) : trois jetons existants, aucun neuf ; `LlmBadge` la porte. */
.ds [data-tier='fast'] { --tone: var(--glow); }
.ds [data-tier='standard'] { --tone: var(--text-soft); }
.ds [data-tier='reasoning'] { --tone: var(--gold-ink); }

.ds :is(.tag, .note, button, [role='button'])[data-tone],
.ds .tag[data-tier] {
  border-color: var(--tone);
  color: var(--tone);
}

.ds :is(button, [role='button'])[data-tone] { background-color: transparent; }

/* Bouton a deux etats :  porte l etat, le fond teinte le rend VISIBLE. Un geste
   irreversible ne s annonce pas par son seul libelle (ConfirmButton s arme au 1er clic). */
.ds :is(button, [role='button'])[aria-pressed='true'] {
  background-color: color-mix(in srgb, var(--tone, var(--gold)) 22%, transparent);
}
.ds .note[data-tone] { color: var(--text); }

/* Échelle d'accord (quiz, US-DAG-QUIZ-001) : 7 radios natifs sur le dégradé des trois tons ; le <p> qui la précède porte ses libellés aux deux bords. */
.ds .scale {
  display: grid; grid-template-columns: repeat(7, 1fr); justify-items: center; padding-block: var(--space-2);
  border-radius: var(--radius); background: linear-gradient(90deg, var(--danger), var(--warn), var(--ok));
}
.ds .scale input { inline-size: 1.25rem; block-size: 1.25rem; accent-color: var(--night); }
.ds p:has(+ .scale) { display: flex; justify-content: space-between; }

/* Champ : libellé, contrôle, aide ; l'aide passe au rouge quand le contrôle est invalide. */
.ds .field {
  display: grid;
  gap: var(--space-1);
}

.ds .field:has([aria-invalid='true']) small { color: var(--danger); }

/* Icône du sprite Lucide : <svg class="icon"><use href="…/icons.svg#nom" /></svg>. */
.ds .icon {
  flex-shrink: 0;
  inline-size: 1.25em;
  block-size: 1.25em;
  vertical-align: -0.25em;
  fill: none;
  stroke: currentColor;
  stroke-width: var(--stroke);
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Page : la coquille d'un écran migré — gouttière, largeur de lecture, centrage. Le gabarit, lui,
   passe par `.frame` : mesuré le 2026-09-20, à 75em `.frame` réserve 14rem et 15rem pour un `aside`
   et un `nav` absents, ce qui pousse un écran simple au tiers de la fenêtre. */
.ds .page {
  max-inline-size: 60rem;
  margin-inline: auto;
  padding: var(--space-3);
}

/* Gabarit trois colonnes (audit DS v2, R3) : l'image à gauche (<figure>), le contenu au centre (<article>), la
   navigation à droite (<nav>). Une colonne sous 62em, l'image au-dessus du contenu ; image et contenu à 62em ;
   les trois à 75em. L'en-tête et le pied de la coquille l'encadrent sur toute la largeur. */
.ds .frame {
  display: grid;
  grid-template-areas: 'side' 'main' 'nav';
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-4);
  padding: var(--space-3);
}

/* Décoration et sommaire collés sous l'en-tête collant (56 px = 3,5rem sur une ligne, mesuré à 1440, là où ils sont côte à côte). */
.ds .frame > :is(figure, nav) { position: sticky; inset-block-start: calc(3.5rem + var(--space-3)); }
.ds .frame > figure { grid-area: side; align-self: start; }
/* Colonne de gauche = décoration (audit DS v3, R3) : `aria-hidden`, masquée sous 62em, où elle n'apporte rien. */
.ds .frame > figure[aria-hidden='true'] { display: none; }
.ds .frame > article { grid-area: main; }
/* Sans sommaire (démo d'une section), la démo prend aussi la colonne de droite. */
.ds .frame:not(:has(> :is(nav, aside))) > article { grid-column: main-start / nav-end; }

.ds .frame > :is(nav, aside) {
  grid-area: nav;
  align-self: start;
  padding: var(--space-3);
}
/* Menu et réglages (catalogue) : un <aside> replié au-dessus du contenu sur une colonne, à droite, déplié et collant à 62em (audit responsive F1-F2). */
.ds .frame:has(> aside) { grid-template-areas: 'nav' 'main'; grid-template-columns: minmax(0, 1fr); }

/* Menus : listes natives dans nav, aside et tiroirs ; la page courante en or. */
/* Une liste dans une liste de menu (articles d'une section, sous-sections) : en retrait, pour se lire comme une branche. */
.ds :is(nav, aside, dialog) ul ul { padding-inline-start: var(--space-3); }

.ds :is(nav, aside, dialog) ul {
  display: grid;
  gap: var(--space-1);
  margin: 0;
  padding: 0;
  list-style: none;
}

.ds :is(nav, aside, dialog) li a {
  display: block;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius);
  color: var(--text-soft);
  text-decoration: none;
}

.ds :is(nav, aside, dialog) li a[aria-current='page'] {
  background: var(--fill);
  /* Le texte courant et non l'or : or sur --fill = 3,09:1 sur un thème vert (silenus, 2026-10-19), sous AA. */
  color: var(--text);
}

/* Fil d'Ariane : un <ol> dans un <nav> (patron APG). Les règles de menu visent tout `li a` d'un nav :
   celle-ci les annule, jusqu'à la page courante, qui garde l'or sans le fond. */
.ds nav ol {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  padding: 0;
  list-style: none;
}

.ds nav ol li + li::before {
  content: '/' / '';
  margin-inline-end: var(--space-1);
  color: var(--text-soft);
}

.ds nav ol li :is(a, a[aria-current='page']) {
  display: inline-block; /* hauteur de ligne pleine : cible tactile d'au moins 24 px (WCAG 2.5.8) */
  padding: 0;
  background: none;
}

/* Tiroir : un <dialog> modal collé à un bord ; display seulement ouvert (sinon il battrait la règle UA qui le cache). */
.ds dialog.drawer {
  inline-size: min(20rem, 100% - 3rem);
  max-block-size: none;
  block-size: 100%;
  margin: 0 0 0 auto;
  border-block: 0;
  border-radius: 0;
}

.ds dialog.drawer[data-side='start'] { margin: 0 auto 0 0; }

.ds dialog.drawer[open] {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.ds dialog.drawer > button { align-self: flex-end; }

/* ── Effets : un seul groupe de cliquables, plus .hero (plan §4.4) ───────── */
.ds :is(button:not([role='tab']), [role='button'], summary, a.surface):not(:disabled):is(:hover, :focus-visible) {
  box-shadow: var(--halo);
  background-image: linear-gradient(110deg, transparent 40%, color-mix(in srgb, var(--gold) 22%, transparent) 50%,
      transparent 60%);
  background-size: 250% 100%;
  background-repeat: no-repeat;
  animation: ds-shimmer calc(var(--dur) * 5) var(--ease) 1 both;
}

.ds a.surface:is(:hover, :focus-visible) {
  box-shadow: var(--inset), var(--halo);
  color: inherit;
  text-shadow: none;
}

/* Diaporama (lot DIAPO, aria-patterns §16) : une piste native à défilement accroché — le balayage est celui du navigateur —,
   l'aperçu de la suivante sur téléphone, les commandes collées en bas, sous le pouce. 0 classe : les attributs du motif ARIA
   et `data-gabarit`. `colonnes` et `duo` sont des `.grid` de `.surface`, sans règle propre ; `chiffres` pose `--min: 8rem`
   sur sa `.grid`. Le comportement (commandes, compteur, `inert`) vit dans `diaporama.js`. */
.ds [aria-roledescription='carousel'] { container-type: inline-size; }
.ds [aria-roledescription='carousel'] > ol {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: calc(100% - 1.25rem);
  gap: var(--space-3);
  padding: 0;
  list-style: none;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}
.ds [aria-roledescription='slide'] {
  position: relative;
  isolation: isolate;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-content: start;
  gap: var(--space-3);
  padding: var(--space-4);
  overflow: hidden;
  border: var(--edge);
  border-radius: var(--radius);
  background: var(--backdrop);
  background-attachment: scroll;
  scroll-snap-align: start;
  scroll-snap-stop: always;
}
.ds [aria-roledescription='slide'] :is(h1, h2) { font-size: clamp(1.75rem, 1.25rem + 2.5cqi, 3rem); overflow-wrap: break-word; hyphens: auto; }
.ds [aria-roledescription='slide'] :is(h1, h2) + p { color: var(--glow); }
.ds [aria-roledescription='slide'] li::marker { color: var(--gold); }
.ds [aria-roledescription='slide'] dd { margin: 0; }
.ds [aria-roledescription='carousel'] > nav {
  position: sticky;
  inset-block-end: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding-block: var(--space-2);
  background: var(--glass);
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
}
.ds [aria-roledescription='carousel'] > nav button { min-inline-size: 2.75rem; min-block-size: 2.75rem; justify-content: center; }
.ds [aria-roledescription='carousel'] output { color: var(--text-soft); font-variant-numeric: tabular-nums; }
.ds [data-gabarit='couverture'] { align-content: end; min-block-size: min(34rem, 85dvh); }
.ds [data-gabarit='couverture'] h1 { font-size: clamp(2.5rem, 1.5rem + 6cqi, 6rem); }
.ds [data-gabarit='couverture'] > img {
  position: absolute;
  inset: 0;
  z-index: -1;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  object-position: top;
  border-radius: 0;
  mask-image: linear-gradient(#000 35%, transparent 90%);
}
.ds [data-gabarit='brique'] > img { inline-size: 100%; aspect-ratio: 16 / 9; object-fit: cover; }
.ds [data-gabarit='chiffres'] .metric { color: var(--gold-ink); font-size: clamp(1.75rem, 1rem + 4cqi, var(--fs-4)); white-space: nowrap; }
@container (min-width: 40em) {
  .ds [aria-roledescription='carousel'] > ol { grid-auto-columns: 100%; }
  .ds [aria-roledescription='slide'] { min-block-size: min(80dvh, 56.25cqi); padding: calc(var(--space-4) * 2); }
  .ds [data-gabarit='brique'] { grid-template-columns: minmax(0, 2fr) minmax(0, 3fr); align-items: center; }
  .ds [data-gabarit='brique'] > img { block-size: 100%; aspect-ratio: auto; }
}

/* Récit (US-COMM-018, cahier AC3-bis de la fiche) : une présentation en écrans pleine hauteur sur la nuit de Samhain
   (`--voile`). Écran 1, la planche (`[data-planche]`) : l'image seule, entière (jamais recadrée, ses frises visibles), la
   plus grande que l'écran permet sous l'en-tête. Écran 2 : le titre seul, centré, dans le panneau de verre. Les écrans
   de section : l'image assombrie, le panneau de verre au texte or (`[data-texte]`) posé sur son bas. Les écrans
   défilent à l'horizontale, aimantés (le doigt les fait glisser) ; une flèche de chaque côté. Tokens de Samhain seuls :
   le récit reste nuit en mode clair. 0 classe. */
.ds[data-recit] {
  position: fixed;
  inset: 0;
  display: flex;
  overflow: auto hidden;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  background: var(--abysse);
}
.ds[data-recit] > section {
  flex: 0 0 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  block-size: 100svh;
  padding: 4.5rem 1rem 2rem;
  scroll-snap-align: start;
  background: var(--voile);
}
/* Le panneau garde sa hauteur ; l'image, entière, prend la place qui reste et se pose sur lui. */
.ds[data-recit] > section > img { flex: 0 1 auto; min-block-size: 0; inline-size: 100%; object-fit: contain; object-position: bottom; }
.ds[data-recit] > section > img { filter: brightness(0.6); }
/* Un seul panneau de texte sur tous les écrans : un verre un peu sombre, sans flou, aux coins légèrement arrondis. Le
   titre d'une section a le même verre, posé sur le panneau : plus sombre derrière lui. Pas de filet : un halo or diffus,
   discret (au-delà, il vire au néon sur un fond sombre). */
.ds[data-recit] :is([data-texte], [data-texte] h2) {
  box-shadow: 0 0 1.25rem color-mix(in oklch, var(--gold) 22%, transparent);
  border-radius: 0.75rem;
  background: color-mix(in oklch, var(--abysse) 55%, transparent);
}
.ds[data-recit] [data-texte] {
  display: grid;
  justify-items: center;
  gap: var(--space-4);
  flex: none;
  inline-size: min(100%, 52rem);
  padding: var(--space-4) calc(var(--space-4) * 1.5);
  text-align: center;
  color: var(--gold);
}
.ds[data-recit] [data-texte] h2 { justify-self: stretch; padding: var(--space-2) var(--space-4); }
/* Sous une image, le panneau se pose sur son bas ; seul (écran du titre), il est centré par l'écran. */
.ds[data-recit] > section > img + [data-texte] { margin-block-start: -3rem; }
/* La planche prend toute la hauteur que laissent l'en-tête et le padding (6,5rem), au format 16:9 des planches, sans
   dépasser la largeur ; une image d'un autre format reste entière. */
.ds[data-recit] [data-planche] { inline-size: min(100%, calc((100svh - 6.5rem) * 16 / 9)); }
.ds[data-recit] [data-planche] > img {
  display: block;
  inline-size: 100%;
  block-size: auto;
  max-block-size: calc(100svh - 6.5rem);
  object-fit: contain;
}
.ds[data-recit] :is(h1, h2) { font-family: var(--title); font-weight: 400; line-height: 1.1; color: var(--gold); }
.ds[data-recit] h1 { font-size: clamp(1.5rem, 0.75rem + 3.5vw, 4.5rem); }
.ds[data-recit] h2 { font-size: clamp(1.75rem, 1rem + 2.5vw, 3.25rem); }
/* Le texte explicatif en gris clair (fixe, comme la nuit du récit), pour qu'il se détache de l'or des titres et des portes. */
.ds[data-recit] [data-texte] p { font-size: clamp(1.0625rem, 0.85rem + 0.6vw, 1.5rem); line-height: 1.55; color: oklch(from var(--woad) 0.88 0.01 h); }
.ds[data-recit] [data-texte] .cluster { justify-content: center; }
/* Les portes : des pilules cernées d'or, pleines au survol. */
.ds[data-recit] [data-texte] [role='button'] {
  border: thin solid var(--gold);
  border-radius: 999px;
  background-color: transparent;
  color: var(--gold);
  padding-inline: var(--space-4);
}
.ds[data-recit] [data-texte] [role='button']:hover { background-color: var(--gold); color: var(--abysse); }
/* Les flèches : des pastilles or sur la nuit, à mi-hauteur, collées aux bords. */
.ds[data-recit] > nav button {
  position: fixed;
  inset-block-start: 50%;
  z-index: 1;
  translate: 0 -50%;
  inline-size: 3rem;
  block-size: 3rem;
  padding: 0;
  border: thin solid var(--gold);
  border-radius: 50%;
  background-color: color-mix(in oklch, var(--abysse) 55%, transparent);
  color: var(--gold);
  font-size: 1.5rem;
}
.ds[data-recit] > nav button:hover { background-color: var(--gold); color: var(--abysse); }
.ds[data-recit] > nav [data-sens='precedent'] { inset-inline-start: 0.5rem; }
.ds[data-recit] > nav [data-sens='suivant'] { inset-inline-end: 0.5rem; }
/* Le dernier écran (AVIS1 L2) : le like et le fil, qui grandit. Le récit masque le débordement vertical (`overflow: auto
   hidden`) : le panneau défile donc en lui-même, borné à la hauteur de l'écran, au lieu d'être coupé. */
.ds[data-recit] > section[data-fin] > [data-texte] { overflow-y: auto; max-block-size: 100%; inline-size: min(100%, 48rem); }
/* Une seule carte, le panneau (porte de goût A3, 2026-10-31) : ni cadre autour du like ni verre autour du titre, titre un
   cran sous celui des écrans, lien or. Le formulaire et les commentaires gardent leur bord. */
.ds[data-recit] > section[data-fin] > [data-texte] > .surface,
.ds[data-recit] > section[data-fin] > [data-texte] > section > h2 { border: 0; background: none; box-shadow: none; backdrop-filter: none; -webkit-backdrop-filter: none; padding: 0; }
.ds[data-recit] > section[data-fin] > [data-texte] > section > h2 { font-size: clamp(var(--fs-3), 1rem + 1.5vw, var(--fs-4)); }
.ds[data-recit] > section[data-fin] a:not(:hover, :focus-visible) { color: var(--gold); text-decoration-color: color-mix(in oklch, var(--gold) 40%, transparent); }
.ds[data-recit] > footer { display: grid; place-items: center; flex: 0 0 auto; padding-inline: 2rem; scroll-snap-align: end; }
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .ds[data-recit] [data-texte] { animation: ds-apparition linear both; animation-timeline: view(inline); animation-range: entry 0% entry 100%; }
  }
}
@keyframes ds-apparition { from { opacity: 0; translate: 0 2rem; } }

@keyframes ds-shimmer {
  from { background-position: 100% 0; }
  to { background-position: 0 0; }
}

@media (prefers-reduced-motion: reduce) {
  .ds *,
  .ds *::before,
  .ds *::after {
    animation: none !important;
    transition: none !important;
  }
}

/* ── Ruptures : deux, pas plus (D6) ─────────────────────────────────────── */
@media (min-width: 62em) {
  .ds table {
    display: table;
    inline-size: 100%;
    background: none; /* ne défile plus : pas d indice */
  }
  /* Ici seulement la compacte fixe ses colonnes (en dessous, la table est un bloc qui défile) : une cellule de contrôles
     ne s'y coupe jamais, ses boutons passent à la ligne (une `width` de colonne les garde sur une ligne). AUDITDS5, AUDITDS6. */
  .ds table[data-densite='compact'] td:has(button, [role='button']) { overflow: visible; white-space: normal; }
  .ds table[data-densite='compact'] td:has(button, [role='button']) .cluster { flex-wrap: wrap; }
  /* Les onglets reprennent le retour à la ligne : sans défilement, pas d'indice (son cache se verrait sur une surface). */
  .ds ul[data-admin-tabs] { flex-wrap: wrap; background: none; }

  .ds .frame {
    grid-template-areas: 'side main' 'side nav';
    grid-template-columns: 16rem minmax(0, 1fr);
  }

  .ds .frame > figure[aria-hidden='true'] { display: block; }
  .ds .frame:has(> aside) { grid-template-areas: 'main nav'; grid-template-columns: minmax(0, 1fr) 15rem; }
  .ds .frame > aside { position: sticky; inset-block-start: calc(3.5rem + var(--space-3)); max-block-size: calc(100dvh - 5.5rem); overflow-y: auto; }
  /* Son menu : en ligne replié au-dessus du contenu, en colonne dépliée à droite (l'ouverture suit `matchMedia`). */
  .ds .frame > aside ul { display: grid; }
  .ds .frame > aside summary { display: none; }
  .ds .matrix { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 75em) {
  .ds .frame {
    grid-template-areas: 'side main nav';
    grid-template-columns: 18rem minmax(0, 1fr) 15rem;
  }
}
