/* ibou design system v2.0.0 — généré par scripts/sync-design-system.mjs (feuille unique concaténée). Ne pas éditer. */
@layer reset, ibou.base, ibou.utilities, ibou.components;

/* ——— build/css/tokens.css ——— */
/**
 * ibou design system v2.0.0 — fichier généré par scripts/build.mjs, ne pas éditer à la main.
 * Source de vérité : tokens/**.json
 */

:root {
  color-scheme: light;
  --ibou-color-white: #ffffff; /** Blanc pur. Fond de la mascotte, surfaces de premier plan (cards, champs). */
  --ibou-color-black: #000000; /** Noir absolu. Jamais en UI ; réservé à l’impression du contour mascotte si le noir riche est requis. */
  --ibou-color-neutral-50: #eaeaea;
  --ibou-color-neutral-100: #d4d4d4;
  --ibou-color-neutral-200: #b4b4b4;
  --ibou-color-neutral-300: #949494;
  --ibou-color-neutral-400: #5e5e5e;
  --ibou-color-neutral-500: #282828; /** Anthracite de référence. Texte, traits, bordures signature, mode sombre. Pantone 426 C. */
  --ibou-color-neutral-600: #232323;
  --ibou-color-neutral-700: #1e1e1e;
  --ibou-color-neutral-800: #181818;
  --ibou-color-neutral-900: #121212;
  --ibou-color-neutral-950: #0d0d0d;
  --ibou-color-pink-50: #fef6ff;
  --ibou-color-pink-100: #fceeff;
  --ibou-color-pink-200: #fae1ff;
  --ibou-color-pink-300: #f8d4ff;
  --ibou-color-pink-400: #f4bfff;
  --ibou-color-pink-500: #f0aaff; /** Rose ibou de référence. RVB 240/170/255, CMJN 6/33/0/0. */
  --ibou-color-pink-600: #d396e0;
  --ibou-color-pink-700: #b480bf;
  --ibou-color-pink-800: #906699;
  --ibou-color-pink-900: #6c4c73;
  --ibou-color-pink-950: #4d3652;
  --ibou-color-green-50: #e6fae8;
  --ibou-color-green-100: #ccf5d0;
  --ibou-color-green-200: #a6eead;
  --ibou-color-green-300: #80e68a;
  --ibou-color-green-400: #40da4f;
  --ibou-color-green-500: #00cd14; /** Vert ibou de référence. RVB 0/205/20, CMJN 100/0/90/20. */
  --ibou-color-green-600: #00b412;
  --ibou-color-green-700: #009a0f;
  --ibou-color-green-800: #007b0c;
  --ibou-color-green-900: #005c09;
  --ibou-color-green-950: #004206;
  --ibou-color-beige-50: #fdfbf8; /** Blanc cassé de page. Fond par défaut de tous les supports. */
  --ibou-color-beige-100: #fbf7f0;
  --ibou-color-beige-200: #f8f1e5;
  --ibou-color-beige-300: #f5eada;
  --ibou-color-beige-400: #f0e0c7;
  --ibou-color-beige-500: #ebd6b4; /** Beige ibou de référence. RVB 235/214/180, CMJN 0/9/23/8. */
  --ibou-color-beige-600: #cfbc9e;
  --ibou-color-beige-700: #b0a087;
  --ibou-color-beige-800: #8d806c;
  --ibou-color-beige-900: #6a6051;
  --ibou-color-beige-950: #4b443a;
  --ibou-color-action-tertiary-bg: rgba(0, 0, 0, 0);
  --ibou-color-overlay-scrim: rgba(40, 40, 40, 0.6); /** Anthracite 60 % */
  --ibou-space-0: 0px;
  --ibou-space-1: 4px;
  --ibou-space-2: 8px;
  --ibou-space-3: 12px;
  --ibou-space-4: 16px;
  --ibou-space-5: 20px;
  --ibou-space-6: 24px;
  --ibou-space-8: 32px;
  --ibou-space-10: 40px;
  --ibou-space-12: 48px;
  --ibou-space-14: 56px;
  --ibou-space-16: 64px;
  --ibou-space-20: 80px;
  --ibou-space-24: 96px;
  --ibou-space-32: 128px;
  --ibou-space-40: 160px;
  --ibou-space-px: 1px;
  --ibou-space-0-5: 2px;
  --ibou-size-icon-xs: 16px;
  --ibou-size-icon-sm: 20px;
  --ibou-size-icon-md: 24px; /** Taille de référence de la grille d’icônes */
  --ibou-size-icon-lg: 32px;
  --ibou-size-icon-xl: 48px;
  --ibou-size-control-sm: 36px;
  --ibou-size-control-md: 44px;
  --ibou-size-control-lg: 52px;
  --ibou-size-control-xl: 60px; /** Barre de recherche principale */
  --ibou-size-avatar-sm: 24px;
  --ibou-size-avatar-md: 32px;
  --ibou-size-avatar-lg: 48px;
  --ibou-size-avatar-xl: 64px;
  --ibou-size-target-min: 44px; /** Cible tactile minimale (WCAG 2.5.8 renforcé) */
  --ibou-radius-none: 0px; /** Interdit sauf éléments pleine largeur qui touchent le bord de l’écran */
  --ibou-radius-xs: 4px; /** Micro-éléments < 24px (cases à cocher, curseurs) */
  --ibou-radius-sm: 8px; /** Chips avatar, boîtes d’icône, vignettes */
  --ibou-radius-md: 12px; /** Photos, cards de stats, menus */
  --ibou-radius-lg: 16px; /** Cards standard */
  --ibou-radius-xl: 24px; /** Grandes cards, sections colorées, modales, cadres de mockup */
  --ibou-radius-2xl: 32px; /** Blocs pleine largeur, héros */
  --ibou-radius-full: 9999px; /** Pilule : boutons, tags, champs, barre de recherche */
  --ibou-border-width-hairline: 1px; /** Trait fin : séparateurs, tags, encadrés de texte dense */
  --ibou-border-width-thin: 2px; /** Trait signature de l’interface : champs, boutons, cards, filet de navigation, soulignement de CTA */
  --ibou-border-width-medium: 3px; /** Trait intermédiaire : encadrés de photos, délimitation de blocs, barre de recherche */
  --ibou-border-width-thick: 4px; /** Trait épais : communication grand public, messages simples */
  --ibou-border-width-display: 8px; /** Affiches, très grands formats (≈ contour de la mascotte) */
  --ibou-breakpoint-sm: 640px;
  --ibou-breakpoint-md: 768px;
  --ibou-breakpoint-lg: 1024px;
  --ibou-breakpoint-xl: 1280px;
  --ibou-breakpoint-2xl: 1536px;
  --ibou-container-reading: 44rem; /** ≈ 704px : largeur de lecture (65–75 caractères Poppins 18px) */
  --ibou-container-content: 60rem; /** 960px : réponses, listes de sources */
  --ibou-container-page: 75rem; /** 1200px : largeur maximale du site */
  --ibou-container-wide: 90rem; /** 1440px : outils denses (console éditeurs) */
  --ibou-shadow-none: 0px 0px 0px 0px #00000000;
  --ibou-shadow-sm: 0px 1px 2px 0px #2828280F; /** Cards de stats (brandbook p.43) */
  --ibou-shadow-md: 0px 8px 24px -4px #2828281F; /** Menus, popovers */
  --ibou-shadow-lg: 0px 16px 48px -8px #28282829; /** Modales, feuilles */
  --ibou-opacity-disabled: 0.4;
  --ibou-opacity-muted: 0.6;
  --ibou-opacity-watermark: 0.85; /** Cards semi-transparentes laissant voir un titre géant en filigrane (brandbook p.7, 22–23) */
  --ibou-opacity-scrim: 0.6; /** Voile anthracite derrière une modale */
  --ibou-motion-duration-instant: 0ms;
  --ibou-motion-duration-fast: 120ms; /** Hover, focus, changements de couleur */
  --ibou-motion-duration-base: 200ms; /** Apparition de menus, tags, tooltips */
  --ibou-motion-duration-slow: 320ms; /** Panneaux, modales, transitions de page */
  --ibou-motion-duration-deliberate: 600ms; /** Mascotte uniquement : clignement, surgissement. La révélation échelonnée des sources n’existe plus (D-50) — c’était une révélation de contenu, le plus proche parent de ce que D-36 rejette. */
  --ibou-motion-duration-pulse: 1200ms; /** Cycle du squelette de chargement. Trois noms distincts pour les trois boucles du système, et non un `loop` unique, pour que personne ne les aligne un jour par commodité (D-50). */
  --ibou-motion-duration-sweep: 1200ms; /** Aller de la barre fine de progression (D-40). Écrite en dur à 1,6 s dans `alert.css`, valeur qui ne correspondait ni au chapitre 10 ni au chapitre 08. */
  --ibou-motion-duration-spin: 800ms; /** Tour de l’anneau de chargement d’un bouton, écrit en dur dans `base.css`. Le spinner est réservé aux boutons (D-40). */
  --ibou-motion-delay-tooltip: 400ms; /** Temps avant l’apparition d’une infobulle au survol. Tranche la divergence 300 / 400 ms entre les chapitres 07 et 10 (D-50). */
  --ibou-motion-delay-skeleton: 200ms; /** Temps avant l’apparition d’un squelette de chargement (D-40) : un chargement plus court n’affiche rien, l’écran ne clignote donc pas entre un squelette et le contenu. */
  --ibou-motion-delay-slow: 3000ms; /** Chargement long (D-40) : à 3 s, alerte info « Ça prend plus de temps que prévu. » avec les yeux stressed, une fois. */
  --ibou-motion-delay-cancel: 10000ms; /** Chargement très long (D-40) : à 10 s, barre fine indéterminée et action « Annuler ». */
  --ibou-motion-delay-timeout: 20000ms; /** Fin de l’attente (D-40) : à 20 s, passage à l’erreur ; ce qui est déjà affiché reste affiché. */
  --ibou-motion-easing-standard: cubic-bezier(0.2, 0, 0, 1); /** Entrée et déplacement */
  --ibou-motion-easing-exit: cubic-bezier(0.4, 0, 1, 1); /** Disparition */
  --ibou-motion-easing-emphasized: cubic-bezier(0.3, 0, 0.1, 1); /** Grands mouvements de mise en page */
  --ibou-motion-easing-playful: cubic-bezier(0.34, 1.4, 0.64, 1); /** Mascotte uniquement : léger dépassement */
  --ibou-motion-easing-breathe: cubic-bezier(0.45, 0, 0.55, 1); /** Courbe symétrique, réservée aux boucles alternées. `standard` est une courbe d’entrée : sur une boucle `alternate` elle sursaute puis attend une demi-seconde avant de repartir, deux fois par cycle (D-50). */
  --ibou-z-index-base: 0;
  --ibou-z-index-raised: 10;
  --ibou-z-index-dropdown: 100;
  --ibou-z-index-sticky: 200;
  --ibou-z-index-overlay: 300;
  --ibou-z-index-modal: 400;
  --ibou-z-index-toast: 500;
  --ibou-z-index-tooltip: 600;
  --ibou-font-family-display: 'New Spirit', 'New Spirit Fallback', 'Iowan Old Style', 'Palatino Linotype', 'Book Antiqua', Georgia, serif; /** Serif éditoriale. Titres, chiffres clés, noms de service, registre affiche. Licence Newlyn (commande 260081). */
  --ibou-font-family-text: Poppins, 'Poppins Fallback', 'Helvetica Neue', Arial, system-ui, sans-serif; /** Sans géométrique. Corps, interface, navigation, étiquettes. Licence OFL. */
  --ibou-font-family-mono: 'JetBrains Mono', 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace; /** Code et identifiants techniques uniquement. JetBrains Mono (OFL) est la référence à installer côté Figma et à embarquer côté Flutter si un écran affiche du code ; les suivantes sont des replis système. */
  --ibou-font-weight-light: 300;
  --ibou-font-weight-regular: 400;
  --ibou-font-weight-medium: 500;
  --ibou-font-weight-semibold: 600;
  --ibou-font-weight-bold: 700;
  --ibou-font-weight-heavy: 900; /** New Spirit Heavy uniquement. Jamais en UI ; réservé aux très grands titrages d’affiche. */
  --ibou-font-size-2xs: 0.6875rem; /** 11px */
  --ibou-font-size-xs: 0.75rem; /** 12px */
  --ibou-font-size-sm: 0.875rem; /** 14px */
  --ibou-font-size-md: 1rem; /** 16px */
  --ibou-font-size-lg: 1.125rem; /** 18px */
  --ibou-font-size-xl: 1.25rem; /** 20px */
  --ibou-font-size-2xl: 1.5rem; /** 24px */
  --ibou-font-size-3xl: 1.75rem; /** 28px */
  --ibou-font-size-4xl: 2.25rem; /** 36px */
  --ibou-font-size-5xl: 3rem; /** 48px */
  --ibou-font-size-6xl: 3.75rem; /** 60px */
  --ibou-font-size-7xl: 4.5rem; /** 72px */
  --ibou-font-size-8xl: 6rem; /** 96px */
  --ibou-font-size-9xl: 8rem; /** 128px */
  --ibou-font-line-height-none: 1;
  --ibou-font-line-height-tight: 1.05; /** Display géant */
  --ibou-font-line-height-snug: 1.15; /** Titres */
  --ibou-font-line-height-normal: 1.3; /** Sous-titres, exergues */
  --ibou-font-line-height-relaxed: 1.5; /** Corps de texte (mesuré sur le brandbook p.31) */
  --ibou-font-line-height-loose: 1.65; /** Lecture longue */
  --ibou-font-letter-spacing-tighter: -0.02em; /** Interlettrage de marque (-2 %) : sous-titres, corps, exergues Poppins */
  --ibou-font-letter-spacing-tight: -0.01em; /** Titres New Spirit et petits corps (< 16px) où -2 % nuirait à la lisibilité */
  --ibou-font-letter-spacing-normal: 0em; /** Boutons, menus, étiquettes en capitales (règle du brandbook : 0 %) */
  --ibou-font-letter-spacing-wide: 0.04em; /** Petites capitales de métadonnées (surlignes, catégories) uniquement */
  --ibou-grid-columns-mobile: 4; /** Nombre de colonnes de la grille sous breakpoint.md (mobile) */
  --ibou-grid-columns-tablet: 8; /** Nombre de colonnes de la grille entre breakpoint.md et breakpoint.lg (tablette) */
  --ibou-grid-columns-desktop: 12; /** Nombre de colonnes de la grille à partir de breakpoint.lg (desktop) */
  --ibou-focus-ring-offset: 2px; /** Décalage de l’anneau hors du contrôle : le focus ne change ni la forme ni la taille de l’élément */
  --ibou-toast-max-width: 24rem; /** Largeur maximale d’un toast (24 rem) : un message court, jamais un paragraphe */
  --ibou-tooltip-max-width: 18rem; /** Largeur maximale d’une infobulle (18 rem) : une phrase, pas une aide complète */
  --ibou-modal-max-width: 36rem; /** Largeur maximale de la modale (36 rem), inférieure à container.reading */
  --ibou-menu-min-width: 12rem; /** Largeur minimale d’un menu (12 rem) pour que les libellés en capitales tiennent sur une ligne */
  --ibou-nav-logo-height: 44px; /** 44 px : dessin de 39,8 × 104,7 px (H = 0,905 × hauteur de l’image à cause de la marge du viewBox), au-dessus du minimum de 96 px (D-25) */
  --ibou-footer-wordmark-crop: 0.45; /** Fraction de la hauteur du lettrage géant masquée par le bord inférieur de la page */
  --ibou-logo-clear-space: 1; /** Zone de protection = 1 × hauteur du « o » tout autour */
  --ibou-logo-min-width-horizontal: 96px; /** Logo complet, écran */
  --ibou-logo-min-width-wordmark: 48px; /** Lettrage seul, écran */
  --ibou-logo-min-width-symbol: 24px; /** Mascotte seule, écran (favicon 32px recommandé) */
  --ibou-logo-min-width-print-horizontal: 25mm;
  --ibou-logo-min-width-print-wordmark: 12mm;
  --ibou-logo-min-width-print-symbol: 6mm;
  --ibou-logo-ratio-x-height: 0.488; /** Unité « o » : hauteur du o du lettrage */
  --ibou-logo-ratio-wordmark-gap: 0.174; /** Espace mascotte → lettrage */
  --ibou-logo-ratio-baseline-offset: 0.104; /** La ligne de base du lettrage est 0,104 H au-dessus du bas de la mascotte. Repli typographique d’un service sans verrou dessiné : le verrou de service est le dessin du brandbook (D-63) */
  --ibou-logo-ratio-service-font-size: 0.97; /** @deprecated utiliser le verrou dessiné assets/logo/ibou-lockup-<service>.svg, D-63 — Corps du nom de service composé (New Spirit Regular Italic) : hauteur de capitale = ascendante du b, hauteur de x = « o » (brandbook p.34). Repli d’un service sans verrou dessiné (D-63) */
  --ibou-logo-ratio-service-gap: 0.18; /** @deprecated utiliser le verrou dessiné assets/logo/ibou-lockup-<service>.svg, D-63 — Espace lettrage → nom de service composé, identique à l’espace mascotte → lettrage. Repli d’un service sans verrou dessiné (D-63) */
  --ibou-button-underline-offset: 0.35em; /** CTA texte : soulignement décalé, épaisseur = border.width.thin */
  --ibou-color-bg-canvas: var(--ibou-color-beige-50); /** Fond de page par défaut, tous supports */
  --ibou-color-bg-subtle: var(--ibou-color-beige-100); /** Zones secondaires, fond de site */
  --ibou-color-bg-muted: var(--ibou-color-beige-200); /** Barre de navigation, panneaux latéraux, gabarits de slides */
  --ibou-color-bg-emphasis: var(--ibou-color-beige-400); /** Sections calmes mises en avant (manifeste) */
  --ibou-color-bg-inverse: var(--ibou-color-neutral-500); /** Sections sombres, pieds de page, planches */
  --ibou-color-surface-default: var(--ibou-color-white);
  --ibou-color-surface-raised: var(--ibou-color-white); /** Menus, popovers, modales */
  --ibou-color-surface-sunken: var(--ibou-color-beige-100); /** Zones en creux : code, citations, résumés */
  --ibou-color-surface-muted: var(--ibou-color-neutral-50); /** Comparatif « sans ibou », éléments désactivés */
  --ibou-color-surface-inverse: var(--ibou-color-neutral-500); /** Tooltips, toasts */
  --ibou-color-surface-brand: var(--ibou-color-pink-500); /** Surface rose pleine : boîtes d’icône, bouton menu, bloc de marque */
  --ibou-color-surface-brand-subtle: var(--ibou-color-pink-100); /** Cards et tags roses */
  --ibou-color-surface-brand-muted: var(--ibou-color-pink-200); /** Pilules de question, surfaces roses secondaires */
  --ibou-color-surface-discover: var(--ibou-color-green-500); /** Surface verte pleine : Découverte */
  --ibou-color-surface-discover-subtle: var(--ibou-color-green-100); /** Tags de source d’ibou Découverte (D-71), cards vertes */
  --ibou-color-surface-discover-strong: var(--ibou-color-green-900); /** Bloc vert profond (section Découverte, texte clair) */
  --ibou-color-surface-warm: var(--ibou-color-beige-500); /** Surface beige pleine : Actualités, Maps */
  --ibou-color-surface-warm-subtle: var(--ibou-color-beige-200);
  --ibou-color-surface-highlight: var(--ibou-color-beige-300); /** Mise en évidence passagère (source visée par un appel de citation) : neutre, jamais rose, pour ne pas se confondre avec un avertissement */
  --ibou-color-text-primary: var(--ibou-color-neutral-500); /** Texte courant. Jamais #000. */
  --ibou-color-text-secondary: var(--ibou-color-neutral-400); /** Extraits, métadonnées, légendes (5.9:1 sur canvas) */
  --ibou-color-text-placeholder: var(--ibou-color-neutral-400); /** Texte indicatif d’un champ vide, lisible (6,5:1 sur blanc) mais jamais porteur d’une consigne indispensable */
  --ibou-color-text-disabled: var(--ibou-color-neutral-300); /** Exempt de contraste (WCAG 1.4.3) */
  --ibou-color-text-inverse: var(--ibou-color-beige-50); /** Texte sur fond inverse / sombre */
  --ibou-color-text-inverse-secondary: var(--ibou-color-neutral-200); /** Texte secondaire (métadonnées, légendes) sur un fond inverse ou sombre */
  --ibou-color-text-on-brand: var(--ibou-color-neutral-500); /** Texte sur rose 500 (toujours anthracite, jamais blanc) */
  --ibou-color-text-on-discover: var(--ibou-color-neutral-500); /** Texte sur vert 500 (toujours anthracite, jamais blanc : 2.1:1) */
  --ibou-color-text-on-discover-strong: var(--ibou-color-beige-50); /** Texte sur vert 900 */
  --ibou-color-text-on-warm: var(--ibou-color-neutral-500); /** Texte sur beige 500 (Actualités, Maps) : toujours anthracite */
  --ibou-color-text-brand: var(--ibou-color-pink-900); /** Texte rose lisible sur fonds clairs (6.9:1) */
  --ibou-color-text-discover: var(--ibou-color-green-900); /** Texte vert lisible sur fonds clairs (7.9:1) */
  --ibou-color-text-discover-accent: var(--ibou-color-green-300); /** Titre italique sur vert 900 (brandbook p.40) */
  --ibou-color-text-warm: var(--ibou-color-beige-900); /** Texte beige foncé sur fond clair : mentions, étiquette « Publicité », métadonnées chaudes */
  --ibou-color-text-link: var(--ibou-color-neutral-500); /** Liens = texte primaire souligné (signature CTA). La couleur ne porte jamais seule le lien. */
  --ibou-color-text-link-hover: var(--ibou-color-pink-900); /** Lien survolé : le rose 900 confirme le survol, le soulignement reste */
  --ibou-color-text-link-visited: var(--ibou-color-beige-900); /** Lien déjà visité, beige 900 (5,97:1 sur canvas) */
  --ibou-color-text-link-external: var(--ibou-color-green-900); /** Lien sortant vers une source et nom de média dans ibou Découverte, seul service à dominante verte : c’est le service qui le pose (--ibou-service-source-text, D-71) ; ailleurs, text.link */
  --ibou-color-text-secondary-on-brand: var(--ibou-color-pink-950); /** Texte secondaire sur rose 500 (6:1) */
  --ibou-color-text-secondary-on-discover: var(--ibou-color-green-950); /** Texte secondaire sur vert 500 (5,7:1) */
  --ibou-color-text-secondary-on-warm: var(--ibou-color-beige-950); /** Texte secondaire sur beige 500 (6,8:1) */
  --ibou-color-text-link-on-brand: var(--ibou-color-neutral-500); /** Lien sur rose 500 : anthracite souligné */
  --ibou-color-text-link-on-brand-hover: var(--ibou-color-pink-950); /** Lien survolé sur rose 500 : rose 950, seul rose lisible sur sa propre gamme (6,7:1) */
  --ibou-color-text-link-on-brand-visited: var(--ibou-color-pink-950); /** Lien visité sur rose 500 : rose 950, jamais beige 900 (4,35:1 insuffisant) */
  --ibou-color-text-link-on-discover: var(--ibou-color-neutral-500); /** Lien sur vert 500 : anthracite souligné, jamais blanc (D-09) */
  --ibou-color-text-link-on-discover-hover: var(--ibou-color-green-950); /** Lien survolé sur vert 500 : vert 950, seul vert lisible sur sa propre gamme (5,5:1) ; le pied de page Découverte prenait le rose 950 du pied rose */
  --ibou-color-text-link-on-discover-visited: var(--ibou-color-green-950); /** Lien visité sur vert 500 : vert 950 */
  --ibou-color-text-link-on-warm: var(--ibou-color-neutral-500); /** Lien sur beige 500 : anthracite souligné */
  --ibou-color-text-link-on-warm-visited: var(--ibou-color-beige-950); /** Lien visité sur beige 500 : beige 950 */
  --ibou-color-border-strong: var(--ibou-color-neutral-500); /** Trait signature : champs, boutons, cards actives, filet de nav (13.9:1) */
  --ibou-color-border-default: var(--ibou-color-beige-600); /** Trait chaud décoratif : cards passives, séparateurs (1.8:1, non porteur de sens) */
  --ibou-color-border-subtle: var(--ibou-color-beige-300); /** Séparateurs de listes */
  --ibou-color-border-muted: var(--ibou-color-neutral-100); /** Sur surfaces blanches neutres (stats) */
  --ibou-color-border-inverse: var(--ibou-color-beige-50); /** Trait sur fond sombre */
  --ibou-color-border-focus: var(--ibou-color-neutral-500); /** Anneau de focus (2px + décalage 2px) */
  --ibou-color-border-focus-halo: var(--ibou-color-beige-50); /** Second anneau (décalage) : garantit un contraste ≥ 3:1 sur toute surface, y compris rose 500 */
  --ibou-color-border-focus-inverse: var(--ibou-color-beige-50); /** Anneau de focus d’un élément posé sur bg.inverse ou surface.inverse ; le halo est alors border.focus (base.css, data-surface="inverse") */
  --ibou-color-border-brand: var(--ibou-color-pink-800); /** Trait rose porteur de sens (tag d’état, filet de section Recherche) ; 4,47:1 sur canvas */
  --ibou-color-border-discover: var(--ibou-color-green-700); /** Trait vert porteur de sens (filet de section Découverte ; jamais une source hors d’ibou Découverte, D-71) ; 3,61:1 sur canvas */
  --ibou-color-border-hover: var(--ibou-color-neutral-500); /** Bordure au survol d’une card passive */
  --ibou-color-border-error: var(--ibou-color-pink-900); /** Bordure d’un champ en erreur (avec icône et message) */
  --ibou-color-border-disabled: var(--ibou-color-neutral-100); /** Trait d’un contrôle désactivé, volontairement faible (l’attribut disabled et opacity.disabled portent l’état) */
  --ibou-color-border-focus-on-brand: var(--ibou-color-neutral-500); /** Anneau de focus d’un élément posé sur une surface claire de marque ou d’état fort (rose, vert, beige 500, cards d’alerte), identique dans les deux thèmes */
  --ibou-color-border-focus-halo-on-brand: var(--ibou-color-beige-50); /** Halo de focus (beige 50) d’un élément posé sur une surface claire de marque ou d’état fort, identique dans les deux thèmes */
  --ibou-color-border-focus-on-dark: var(--ibou-color-beige-50); /** Anneau de focus d’un élément posé sur une surface sombre dans les deux thèmes (vert 900, surface.discover-strong) : le couple inverse s’y retourne en sombre et n’y contraste plus. Posé par card.css, identique dans les deux thèmes */
  --ibou-color-border-focus-halo-on-dark: var(--ibou-color-neutral-500); /** Halo de focus (anthracite) d’un élément posé sur une surface sombre dans les deux thèmes, identique dans les deux thèmes */
  --ibou-color-icon-primary: var(--ibou-color-neutral-500); /** Icône par défaut, même couleur que le texte courant */
  --ibou-color-icon-secondary: var(--ibou-color-neutral-400); /** Icône de métadonnée ou d’aide, même couleur que le texte secondaire (6,28:1 sur canvas) */
  --ibou-color-icon-disabled: var(--ibou-color-neutral-300); /** Icône d’un contrôle désactivé, exempte de contraste comme text.disabled */
  --ibou-color-icon-inverse: var(--ibou-color-beige-50); /** Icône sur fond inverse ou sombre */
  --ibou-color-icon-brand: var(--ibou-color-pink-800); /** Icône rose sur fond clair (rose 800, 4,47:1 sur canvas) : signal d’attention ou accent Recherche */
  --ibou-color-icon-discover: var(--ibou-color-green-700); /** Icône verte sur fond clair (vert 700, 3,61:1 sur canvas) : validé, Découverte ; jamais une source hors d’ibou Découverte (D-71) */
  --ibou-color-icon-on-brand: var(--ibou-color-neutral-500); /** Icône sur rose 500 (boîte d’icône, bouton primaire) : anthracite */
  --ibou-color-icon-on-discover: var(--ibou-color-neutral-500); /** Icône sur vert 500 (bouton Découverte) : anthracite, jamais blanche (D-09) */
  --ibou-color-icon-on-warm: var(--ibou-color-neutral-500); /** Icône sur beige 500 : anthracite */
  --ibou-color-icon-on-inverse: var(--ibou-color-beige-50); /** Icône posée sur surface.inverse (toast, infobulle) : suit text.inverse dans les deux thèmes */
  --ibou-color-action-primary-bg: var(--ibou-color-pink-500);
  --ibou-color-action-primary-bg-hover: var(--ibou-color-pink-600);
  --ibou-color-action-primary-bg-active: var(--ibou-color-pink-700);
  --ibou-color-action-primary-text: var(--ibou-color-neutral-500);
  --ibou-color-action-primary-border: var(--ibou-color-neutral-500);
  --ibou-color-action-secondary-bg: var(--ibou-color-white);
  --ibou-color-action-secondary-bg-hover: var(--ibou-color-beige-100);
  --ibou-color-action-secondary-bg-active: var(--ibou-color-beige-200);
  --ibou-color-action-secondary-text: var(--ibou-color-neutral-500);
  --ibou-color-action-secondary-border: var(--ibou-color-neutral-500);
  --ibou-color-action-tertiary-bg-hover: var(--ibou-color-beige-200);
  --ibou-color-action-tertiary-bg-active: var(--ibou-color-beige-300);
  --ibou-color-action-tertiary-text: var(--ibou-color-neutral-500);
  --ibou-color-action-tertiary-underline: var(--ibou-color-neutral-500);
  --ibou-color-action-discover-bg: var(--ibou-color-green-500);
  --ibou-color-action-discover-bg-hover: var(--ibou-color-green-400); /** Plus clair au survol : le vert 600/700 ne garde pas 4.5:1 avec l’anthracite */
  --ibou-color-action-discover-bg-active: var(--ibou-color-green-600);
  --ibou-color-action-discover-text: var(--ibou-color-neutral-500);
  --ibou-color-action-discover-border: var(--ibou-color-neutral-500);
  --ibou-color-action-inverse-bg: var(--ibou-color-beige-50);
  --ibou-color-action-inverse-bg-hover: var(--ibou-color-beige-200);
  --ibou-color-action-inverse-bg-active: var(--ibou-color-beige-400);
  --ibou-color-action-inverse-text: var(--ibou-color-neutral-500);
  --ibou-color-action-inverse-border: var(--ibou-color-beige-50);
  --ibou-color-action-disabled-bg: var(--ibou-color-neutral-50);
  --ibou-color-action-disabled-text: var(--ibou-color-neutral-400); /** 5,4:1 sur le fond désactivé : lisible même exempté (WCAG 1.4.3) */
  --ibou-color-action-disabled-border: var(--ibou-color-neutral-100);
  --ibou-color-action-selected-bg: var(--ibou-color-pink-200); /** Onglet, option ou pilule sélectionnée */
  --ibou-color-action-selected-text: var(--ibou-color-neutral-500);
  --ibou-color-action-selected-border: var(--ibou-color-neutral-500);
  --ibou-color-action-on-strong-bg: var(--ibou-color-white);
  --ibou-color-action-on-strong-bg-hover: var(--ibou-color-beige-100);
  --ibou-color-action-on-strong-bg-active: var(--ibou-color-beige-200);
  --ibou-color-action-on-strong-text: var(--ibou-color-neutral-500);
  --ibou-color-action-on-strong-border: var(--ibou-color-neutral-500);
  --ibou-color-status-success-bg: var(--ibou-color-green-100);
  --ibou-color-status-success-bg-strong: var(--ibou-color-green-400); /** Card « Multisourcé » */
  --ibou-color-status-success-border: var(--ibou-color-green-700);
  --ibou-color-status-success-text: var(--ibou-color-green-900);
  --ibou-color-status-success-icon: var(--ibou-color-green-700);
  --ibou-color-status-success-text-on-strong: var(--ibou-color-neutral-500); /** Texte sur la surface forte (identique dans les deux thèmes : la surface reste claire) */
  --ibou-color-status-success-icon-on-strong: var(--ibou-color-neutral-500); /** Icône posée sur la surface forte du même état (card illustrée, bandeau plein) : même couleur que le texte, jamais la couleur d’accent qui s’y confondrait */
  --ibou-color-status-warning-bg: var(--ibou-color-pink-100);
  --ibou-color-status-warning-bg-strong: var(--ibou-color-pink-400); /** Card « Alerte infox » */
  --ibou-color-status-warning-border: var(--ibou-color-pink-800); /** Un cran plus foncé pour 3:1 sur rose 100 */
  --ibou-color-status-warning-text: var(--ibou-color-pink-950);
  --ibou-color-status-warning-icon: var(--ibou-color-pink-800);
  --ibou-color-status-warning-text-on-strong: var(--ibou-color-neutral-500); /** Texte sur la surface forte (identique dans les deux thèmes : la surface reste claire) */
  --ibou-color-status-warning-icon-on-strong: var(--ibou-color-neutral-500); /** Icône posée sur la surface forte du même état (card illustrée, bandeau plein) : même couleur que le texte, jamais la couleur d’accent qui s’y confondrait */
  --ibou-color-status-error-bg-strong: var(--ibou-color-pink-900);
  --ibou-color-status-error-border: var(--ibou-color-neutral-500);
  --ibou-color-status-error-text: var(--ibou-color-pink-900);
  --ibou-color-status-error-icon: var(--ibou-color-pink-900);
  --ibou-color-status-error-text-on-strong: var(--ibou-color-beige-50);
  --ibou-color-status-error-icon-on-strong: var(--ibou-color-beige-50); /** Icône posée sur la surface forte du même état (card illustrée, bandeau plein) : même couleur que le texte, jamais la couleur d’accent qui s’y confondrait */
  --ibou-color-status-error-border-on-inverse: var(--ibou-color-pink-500); /** Trait d’échec posé sur la surface inverse (toast) : rose 500 sur l’anthracite du thème clair, rose 900 sur le beige 50 du thème sombre. status.error.bg-strong s’y retournait avec le thème et tombait à 2,05:1 en clair, 1,28:1 en sombre, alors que ce trait est l’un des trois signaux cumulés de D-07 */
  --ibou-color-status-info-bg: var(--ibou-color-beige-200);
  --ibou-color-status-info-bg-strong: var(--ibou-color-beige-500);
  --ibou-color-status-info-border: var(--ibou-color-beige-800);
  --ibou-color-status-info-text: var(--ibou-color-beige-950);
  --ibou-color-status-info-icon: var(--ibou-color-beige-800);
  --ibou-color-status-info-text-on-strong: var(--ibou-color-neutral-500); /** Texte sur la surface forte (identique dans les deux thèmes : la surface reste claire) */
  --ibou-color-status-info-icon-on-strong: var(--ibou-color-neutral-500); /** Icône posée sur la surface forte du même état (card illustrée, bandeau plein) : même couleur que le texte, jamais la couleur d’accent qui s’y confondrait */
  --ibou-color-status-neutral-bg: var(--ibou-color-neutral-50);
  --ibou-color-status-neutral-border: var(--ibou-color-neutral-400);
  --ibou-color-status-neutral-text: var(--ibou-color-neutral-400);
  --ibou-color-status-neutral-icon: var(--ibou-color-neutral-400);
  --ibou-color-loading-rest: var(--ibou-color-beige-500); /** Repos, et seule couleur montrée sous mouvement réduit : c’est l’extrémité la plus visible (1,37:1 sur la page, 1,42:1 dans une card). */
  --ibou-color-loading-pulse: var(--ibou-color-beige-300); /** L’autre extrémité de la respiration, vers la surface. Un squelette s’efface légèrement, il ne s’allume pas. */
  --ibou-color-ad-bg: var(--ibou-color-beige-100);
  --ibou-color-ad-label: var(--ibou-color-beige-900);
  --ibou-color-chart-1: var(--ibou-color-neutral-500);
  --ibou-color-chart-2: var(--ibou-color-pink-800);
  --ibou-color-chart-3: var(--ibou-color-green-950);
  --ibou-color-chart-4: var(--ibou-color-beige-800);
  --ibou-color-chart-5: var(--ibou-color-pink-950);
  --ibou-color-chart-6: var(--ibou-color-green-700);
  --ibou-color-chart-grid: var(--ibou-color-beige-300);
  --ibou-color-chart-axis: var(--ibou-color-neutral-400);
  --ibou-spacing-inset-xs: var(--ibou-space-1);
  --ibou-spacing-inset-sm: var(--ibou-space-2);
  --ibou-spacing-inset-md: var(--ibou-space-4);
  --ibou-spacing-inset-lg: var(--ibou-space-6);
  --ibou-spacing-inset-xl: var(--ibou-space-8);
  --ibou-spacing-inset-2xl: var(--ibou-space-12);
  --ibou-spacing-stack-xs: var(--ibou-space-1);
  --ibou-spacing-stack-sm: var(--ibou-space-2);
  --ibou-spacing-stack-md: var(--ibou-space-4);
  --ibou-spacing-stack-lg: var(--ibou-space-6);
  --ibou-spacing-stack-xl: var(--ibou-space-10);
  --ibou-spacing-inline-xs: var(--ibou-space-1);
  --ibou-spacing-inline-sm: var(--ibou-space-2);
  --ibou-spacing-inline-md: var(--ibou-space-3);
  --ibou-spacing-inline-lg: var(--ibou-space-4);
  --ibou-spacing-inline-xl: var(--ibou-space-6);
  --ibou-spacing-gutter-mobile: var(--ibou-space-4);
  --ibou-spacing-gutter-tablet: var(--ibou-space-6);
  --ibou-spacing-gutter-desktop: var(--ibou-space-6);
  --ibou-spacing-margin-mobile: var(--ibou-space-4);
  --ibou-spacing-margin-tablet: var(--ibou-space-8);
  --ibou-spacing-margin-desktop: var(--ibou-space-12);
  --ibou-spacing-section-sm: var(--ibou-space-12);
  --ibou-spacing-section-md: var(--ibou-space-16);
  --ibou-spacing-section-lg: var(--ibou-space-24);
  --ibou-spacing-section-xl: var(--ibou-space-32);
  --ibou-focus-ring-width: var(--ibou-border-width-thin); /** Épaisseur de l’anneau de focus et de son halo, égale au trait signature (D-13) */
  --ibou-card-radius: var(--ibou-radius-lg); /** Rayon de la card standard (radius.lg) ; arrondi toujours visible (D-31) */
  --ibou-card-radius-large: var(--ibou-radius-xl); /** Rayon des grandes cards et sections colorées (radius.xl) */
  --ibou-card-border-width: var(--ibou-border-width-thin); /** Trait de la card : signature 2 px, qu’elle soit passive (beige 600) ou active (anthracite) */
  --ibou-source-card-radius: var(--ibou-radius-lg);
  --ibou-source-card-border-width: var(--ibou-border-width-thin);
  --ibou-source-card-favicon-size: var(--ibou-size-icon-sm);
  --ibou-source-card-favicon-radius: var(--ibou-radius-xs);
  --ibou-tag-radius: var(--ibou-radius-full); /** Les tags sont des pilules (radius.full) */
  --ibou-tag-border-width: var(--ibou-border-width-hairline); /** Trait fin des tags (1 px) : un tag n’est pas un contrôle */
  --ibou-tag-height-sm: var(--ibou-space-6); /** Hauteur du petit tag (24 px) ; s’il est cliquable, étendre la cible à 44 px avec .ibou-hit */
  --ibou-tag-height-md: var(--ibou-space-8); /** Hauteur du tag standard (32 px) ; même règle de cible tactile que tag.height.sm */
  --ibou-tag-padding-x-sm: var(--ibou-space-2); /** Padding horizontal du petit tag */
  --ibou-tag-padding-x-md: var(--ibou-space-3); /** Padding horizontal du tag standard */
  --ibou-chip-radius: var(--ibou-radius-sm);
  --ibou-chip-avatar-size: var(--ibou-size-avatar-md);
  --ibou-chip-border-width: var(--ibou-border-width-hairline);
  --ibou-chip-padding-x: var(--ibou-space-2);
  --ibou-alert-radius: var(--ibou-radius-lg);
  --ibou-alert-border-width: var(--ibou-border-width-thin);
  --ibou-alert-border-width-error: var(--ibou-border-width-medium); /** Trait de l’alerte d’erreur (3 px) : avec la surface blanche et l’icône ×, l’un des trois signaux qui la distinguent de l’avertissement (D-07). Partagé par alert.css et IbouAlertBox. */
  --ibou-alert-icon-size: var(--ibou-size-icon-md);
  --ibou-alert-mascot-size: var(--ibou-space-20); /** Card illustrée : mascotte surgissant du bas */
  --ibou-toast-radius: var(--ibou-radius-lg); /** Rayon du toast (radius.lg), même famille que les cards */
  --ibou-tooltip-radius: var(--ibou-radius-sm); /** Rayon de l’infobulle (radius.sm) : petit élément, petit rayon */
  --ibou-tooltip-padding: var(--ibou-space-2); /** Padding interne de l’infobulle */
  --ibou-modal-radius: var(--ibou-radius-xl); /** Rayon de la modale (radius.xl) */
  --ibou-modal-border-width: var(--ibou-border-width-thin); /** Trait signature de la modale (2 px anthracite) : la seule élévation vient de shadow.lg */
  --ibou-modal-scrim: var(--ibou-color-overlay-scrim); /** Voile derrière la modale : overlay.scrim (anthracite 60 % en clair, plus dense en sombre) */
  --ibou-menu-radius: var(--ibou-radius-md); /** Rayon du menu flottant (radius.md) */
  --ibou-menu-padding: var(--ibou-space-2); /** Marge intérieure du menu autour de ses éléments */
  --ibou-menu-item-height: var(--ibou-size-control-md); /** Hauteur d’un élément de menu : 44 px, cible tactile minimale (D-44) */
  --ibou-menu-item-radius: var(--ibou-radius-sm); /** Rayon d’un élément de menu survolé ou sélectionné (radius.sm) */
  --ibou-nav-height: var(--ibou-space-16);
  --ibou-nav-height-mobile: var(--ibou-space-14);
  --ibou-nav-border-width: var(--ibou-border-width-thin);
  --ibou-nav-logo-height-mobile: var(--ibou-space-8); /** 32 px : lettrage seul de la barre mobile hors service (min 48 px de large) ou symbole seul, jamais le logo complet réduit ; dans la barre d’un service, le verrou entier (D-70, nav.css) */
  --ibou-ad-slot-radius: var(--ibou-radius-lg); /** Rayon de l’emplacement publicitaire (radius.lg), comme une card */
  --ibou-ad-slot-border-width: var(--ibou-border-width-hairline); /** Trait fin (1 px) de l’emplacement publicitaire : il ne ressemble pas à une source card (D-35) */
  --ibou-divider-width: var(--ibou-border-width-hairline); /** Séparateur courant, trait fin (1 px) */
  --ibou-divider-width-strong: var(--ibou-border-width-thin); /** Séparateur fort (2 px), par exemple sous un en-tête de section */
  --ibou-skeleton-radius: var(--ibou-radius-sm); /** Rayon des blocs de squelette (radius.sm) */
  --ibou-stat-card-radius: var(--ibou-radius-md);
  --ibou-stat-card-border-width: var(--ibou-border-width-hairline);
  --ibou-stat-card-icon-box-size: var(--ibou-space-10);
  --ibou-stat-card-icon-box-radius: var(--ibou-radius-sm);
  --ibou-table-row-height: var(--ibou-size-control-sm);
  --ibou-table-cell-padding-x: var(--ibou-space-3);
  --ibou-footer-border-width: var(--ibou-border-width-thin);
  --ibou-button-radius: var(--ibou-radius-full); /** Les boutons sont des pilules (radius.full, D-31) */
  --ibou-button-border-width: var(--ibou-border-width-thin); /** Trait signature du bouton (2 px), compris dans sa hauteur (box-sizing: border-box) */
  --ibou-button-height-sm: var(--ibou-size-control-sm); /** Hauteur du petit bouton (36 px) ; sa cible tactile est étendue à 44 px par .ibou-hit */
  --ibou-button-height-md: var(--ibou-size-control-md); /** Hauteur du bouton par défaut : 44 px, cible tactile minimale (D-44) */
  --ibou-button-height-lg: var(--ibou-size-control-lg); /** Hauteur du grand bouton (52 px), héros et pages marketing */
  --ibou-button-padding-x-sm: var(--ibou-space-4); /** Padding horizontal du petit bouton */
  --ibou-button-padding-x-md: var(--ibou-space-6); /** Padding horizontal du bouton par défaut */
  --ibou-button-padding-x-lg: var(--ibou-space-8); /** Padding horizontal du grand bouton */
  --ibou-icon-button-size: var(--ibou-size-control-md); /** Côté du bouton d’icône (44 px) : pilule carrée, cible tactile minimale */
  --ibou-icon-button-radius: var(--ibou-radius-full); /** Rond (bouton menu rose, brandbook p.40 / BU2 p.27) */
  --ibou-input-radius: var(--ibou-radius-full); /** Les champs d’une ligne sont des pilules (radius.full) ; les zones multilignes utilisent input.textareaRadius */
  --ibou-input-border-width: var(--ibou-border-width-thin); /** Trait signature du champ (2 px) */
  --ibou-input-height-sm: var(--ibou-size-control-sm); /** Hauteur du petit champ (36 px), réservé aux barres compactes */
  --ibou-input-height-md: var(--ibou-size-control-md); /** Hauteur du champ par défaut : 44 px */
  --ibou-input-height-lg: var(--ibou-size-control-lg); /** Hauteur du grand champ (52 px) */
  --ibou-input-padding-x: var(--ibou-space-5); /** Padding horizontal du champ (20 px) */
  --ibou-input-textarea-radius: var(--ibou-radius-xl); /** Les zones multilignes ne sont pas des pilules */
  --ibou-search-bar-height: var(--ibou-size-control-xl);
  --ibou-search-bar-height-compact: var(--ibou-size-control-md); /** En barre de navigation, pages de résultats */
  --ibou-search-bar-radius: var(--ibou-radius-full);
  --ibou-search-bar-border-width: var(--ibou-border-width-medium);
  --ibou-search-bar-padding-x: var(--ibou-space-6);
  --ibou-search-bar-icon-size: var(--ibou-size-icon-lg);
  --ibou-search-bar-max-width: var(--ibou-container-reading);
  --ibou-search-bar-submit-size: var(--ibou-size-control-md); /** Bouton d’envoi rond (flèche →), concentrique avec l’extrémité de la pilule : retrait = (hauteur intérieure − bouton) / 2 sur les trois côtés */
  --ibou-search-bar-submit-size-compact: var(--ibou-space-8); /** 32 px en version compacte (retrait 4 px) */
  --ibou-checkbox-size: var(--ibou-space-5); /** Côté de la case à cocher (20 px) ; la ligne qui la contient fait 44 px (cible tactile) */
  --ibou-checkbox-radius: var(--ibou-radius-xs); /** Rayon de la case à cocher (radius.xs) : micro-élément de moins de 24 px */
  --ibou-checkbox-border-width: var(--ibou-border-width-thin); /** Trait signature de la case (2 px) */
  --ibou-switch-width: var(--ibou-space-12); /** Largeur de l’interrupteur (48 px) */
  --ibou-switch-height: var(--ibou-space-6); /** Hauteur de l’interrupteur (24 px) ; sa ligne fait 44 px */
  --ibou-tab-underline-width: var(--ibou-border-width-thin); /** Soulignement de l’onglet actif : trait signature (2 px) */
  --ibou-tab-padding-x: var(--ibou-space-3); /** Padding horizontal d’un onglet */
  --ibou-tab-height: var(--ibou-size-control-md); /** Hauteur d’un onglet : 44 px, cible tactile minimale */
  --ibou-tab-gap: var(--ibou-space-6); /** Espace entre deux onglets */
  --ibou-radio-size: var(--ibou-space-5);
  --ibou-radio-radius: var(--ibou-radius-full);
  --ibou-radio-border-width: var(--ibou-border-width-thin);
  --ibou-radio-dot-size: var(--ibou-space-2); /** Point central quand sélectionné */
  --ibou-question-pill-height: var(--ibou-size-control-md);
  --ibou-question-pill-radius: var(--ibou-radius-full);
  --ibou-question-pill-border-width: var(--ibou-border-width-thin);
  --ibou-question-pill-padding-x: var(--ibou-space-5);
  --ibou-question-pill-icon-size: var(--ibou-size-icon-sm);
  --ibou-color-status-error-bg: var(--ibou-color-surface-default);
  --ibou-color-ad-border: var(--ibou-color-border-default); /** Suit le trait discret (D-12) au lieu de pointer sur le beige en dur : sans cela un changement d’avis sur D-12 laisserait l’encart derrière. Même valeur qu’avant, beige 600. */
  --ibou-color-chart-stroke: var(--ibou-color-bg-canvas); /** Contour obligatoire des aplats : couleur du fond, il sépare deux séries adjacentes sans porter d’information */
  --ibou-card-padding-sm: var(--ibou-spacing-inset-md); /** Padding d’une card compacte (liste dense, mobile) */
  --ibou-card-padding-md: var(--ibou-spacing-inset-lg); /** Padding de la card standard */
  --ibou-card-padding-lg: var(--ibou-spacing-inset-xl); /** Padding d’une grande card ou d’une section colorée */
  --ibou-card-gap: var(--ibou-spacing-stack-md); /** Espace vertical entre les blocs internes d’une card (titre, texte, actions) */
  --ibou-card-bg: var(--ibou-color-surface-default); /** Fond de card : surface blanche posée sur le beige de page, la profondeur vient du contraste, pas d’une ombre (D-33) */
  --ibou-card-border: var(--ibou-color-border-default); /** Trait d’une card passive : beige 600 décoratif, non porteur de sens (D-12) */
  --ibou-card-border-active: var(--ibou-color-border-strong); /** Card interactive ou porteuse de sens */
  --ibou-source-card-padding: var(--ibou-spacing-inset-md);
  --ibou-source-card-gap: var(--ibou-spacing-stack-sm);
  --ibou-source-card-bg: var(--ibou-color-surface-default);
  --ibou-source-card-border: var(--ibou-color-border-strong);
  --ibou-source-card-highlight-bg: var(--ibou-color-surface-highlight); /** Fond passager quand un appel de citation vise la source */
  --ibou-tag-gap: var(--ibou-spacing-inline-xs); /** Espace entre l’icône (ou le favicon) et le libellé d’un tag */
  --ibou-tag-border: var(--ibou-color-border-strong); /** Trait de tous les tags : anthracite fin, identique dans les deux thèmes via border.strong */
  --ibou-tag-bg-neutral: var(--ibou-color-surface-default); /** Tag sans dominante : fond de surface par défaut */
  --ibou-tag-bg-brand: var(--ibou-color-surface-brand-muted); /** Tag rose (sujet, catégorie Recherche) : rose 200, jamais un état (D-07) */
  --ibou-tag-bg-discover: var(--ibou-color-surface-discover-subtle); /** Tag vert (Découverte) : vert 100 */
  --ibou-tag-bg-warm: var(--ibou-color-surface-warm-subtle); /** Beige clair : le texte primaire reste lisible dans les deux thèmes */
  --ibou-tag-bg-source: var(--ibou-color-surface-discover-subtle); /** Tag d’éditeur / source avec ↗ dans ibou Découverte : c’est le service qui le pose (--ibou-service-source-tag-bg, D-71) ; ailleurs, tag.bg.neutral */
  --ibou-alert-padding: var(--ibou-spacing-inset-md);
  --ibou-alert-gap: var(--ibou-spacing-inline-md);
  --ibou-toast-padding: var(--ibou-spacing-inset-md); /** Padding interne du toast */
  --ibou-toast-bg: var(--ibou-color-surface-inverse); /** Fond du toast : surface inverse (anthracite en clair, beige 50 en sombre) */
  --ibou-toast-text: var(--ibou-color-text-inverse); /** Texte du toast : text.inverse, 14,27:1 dans les deux thèmes */
  --ibou-tooltip-bg: var(--ibou-color-surface-inverse); /** Fond de l’infobulle : surface inverse, comme le toast */
  --ibou-tooltip-text: var(--ibou-color-text-inverse); /** Texte de l’infobulle : text.inverse */
  --ibou-modal-padding: var(--ibou-spacing-inset-xl); /** Padding interne de la modale */
  --ibou-nav-bg: var(--ibou-color-bg-muted);
  --ibou-nav-border: var(--ibou-color-border-strong);
  --ibou-ad-slot-padding: var(--ibou-spacing-inset-md); /** Padding interne de l’emplacement publicitaire */
  --ibou-skeleton-bg: var(--ibou-color-loading-rest); /** Aplat du squelette au repos, sans dégradé ni shimmer (D-14). C’est aussi la seule couleur montrée sous mouvement réduit. */
  --ibou-skeleton-bg-pulse: var(--ibou-color-loading-pulse); /** L’autre extrémité de la respiration. La pulsation anime `background-color` entre les deux, jamais l’opacité (D-50). */
  --ibou-stat-card-border: var(--ibou-color-border-muted);
  --ibou-stat-card-padding: var(--ibou-spacing-inset-lg);
  --ibou-stat-card-icon-box-bg: var(--ibou-color-surface-brand);
  --ibou-table-header-bg: var(--ibou-color-surface-sunken);
  --ibou-table-border: var(--ibou-color-border-subtle);
  --ibou-table-border-strong: var(--ibou-color-border-strong); /** Filet sous l’en-tête */
  --ibou-footer-bg: var(--ibou-color-surface-brand);
  --ibou-footer-text: var(--ibou-color-text-on-brand);
  --ibou-footer-border: var(--ibou-color-border-strong);
  --ibou-footer-padding-y: var(--ibou-spacing-section-md);
  --ibou-launch-screen-bg: var(--ibou-color-bg-canvas); /** Fond de l’écran de lancement, dans les deux thèmes : il suit le réglage système comme l’application. Une couleur unie et opaque : Android remplace par son écran par défaut tout fond qui n’en serait pas une. */
  --ibou-button-gap: var(--ibou-spacing-inline-sm); /** Espace entre l’icône et le libellé d’un bouton */
  --ibou-input-gap: var(--ibou-spacing-inline-md); /** Espace entre l’icône et le texte saisi */
  --ibou-input-bg: var(--ibou-color-surface-default); /** Fond du champ : surface blanche posée sur le beige, thémée en sombre */
  --ibou-input-border: var(--ibou-color-border-strong); /** Trait du champ au repos : border.strong */
  --ibou-input-border-error: var(--ibou-color-border-error); /** Trait du champ en erreur : border.error (rose 900 en clair), toujours accompagné d’une icône et d’un message relié au champ */
  --ibou-checkbox-checked-bg: var(--ibou-color-action-primary-bg); /** Fond de la case cochée : rose 500 (action.primary.bg) avec coche anthracite */
  --ibou-switch-on-bg: var(--ibou-color-status-success-bg-strong); /** Piste de l’interrupteur activé : vert 400 (status.success.bg-strong), avec une icône ✓ dans le curseur, la couleur ne portant jamais seule l’état */
  --ibou-switch-off-bg: var(--ibou-color-surface-muted); /** Piste de l’interrupteur désactivé : surface atténuée, avec une icône × dans le curseur */
  --ibou-radio-checked-bg: var(--ibou-color-action-primary-bg);
  --ibou-question-pill-gap: var(--ibou-spacing-inline-md);
  --ibou-question-pill-bg: var(--ibou-color-surface-brand-muted);
  --ibou-question-pill-border: var(--ibou-color-border-strong);
}

/* ——— build/css/tokens.dark.css ——— */
/**
 * ibou design system v2.0.0 — fichier généré par scripts/build.mjs, ne pas éditer à la main.
 * Source de vérité : tokens/**.json
 */

:root[data-theme="dark"] {
    color-scheme: dark;
    --ibou-color-action-secondary-bg: rgba(0, 0, 0, 0);
    --ibou-color-action-tertiary-bg: rgba(0, 0, 0, 0);
    --ibou-color-overlay-scrim: rgba(13, 13, 13, 0.7);
    --ibou-color-bg-canvas: var(--ibou-color-neutral-700);
    --ibou-color-bg-subtle: var(--ibou-color-neutral-600);
    --ibou-color-bg-muted: var(--ibou-color-neutral-500);
    --ibou-color-bg-emphasis: var(--ibou-color-neutral-900);
    --ibou-color-bg-inverse: var(--ibou-color-beige-50);
    --ibou-color-surface-default: var(--ibou-color-neutral-500);
    --ibou-color-surface-raised: var(--ibou-color-neutral-500);
    --ibou-color-surface-sunken: var(--ibou-color-neutral-800);
    --ibou-color-surface-muted: var(--ibou-color-neutral-600);
    --ibou-color-surface-inverse: var(--ibou-color-beige-50);
    --ibou-color-surface-brand: var(--ibou-color-pink-500);
    --ibou-color-surface-brand-subtle: var(--ibou-color-pink-950);
    --ibou-color-surface-brand-muted: var(--ibou-color-pink-900);
    --ibou-color-surface-discover: var(--ibou-color-green-500);
    --ibou-color-surface-discover-subtle: var(--ibou-color-green-950);
    --ibou-color-surface-discover-strong: var(--ibou-color-green-900);
    --ibou-color-surface-warm: var(--ibou-color-beige-500);
    --ibou-color-surface-warm-subtle: var(--ibou-color-beige-950);
    --ibou-color-surface-highlight: var(--ibou-color-beige-950);
    --ibou-color-text-primary: var(--ibou-color-beige-50);
    --ibou-color-text-secondary: var(--ibou-color-neutral-200);
    --ibou-color-text-placeholder: var(--ibou-color-neutral-200);
    --ibou-color-text-disabled: var(--ibou-color-neutral-300);
    --ibou-color-text-inverse: var(--ibou-color-neutral-500);
    --ibou-color-text-inverse-secondary: var(--ibou-color-neutral-400);
    --ibou-color-text-on-brand: var(--ibou-color-neutral-500);
    --ibou-color-text-on-discover: var(--ibou-color-neutral-500);
    --ibou-color-text-on-discover-strong: var(--ibou-color-beige-50);
    --ibou-color-text-on-warm: var(--ibou-color-neutral-500);
    --ibou-color-text-brand: var(--ibou-color-pink-300);
    --ibou-color-text-discover: var(--ibou-color-green-300);
    --ibou-color-text-discover-accent: var(--ibou-color-green-300);
    --ibou-color-text-warm: var(--ibou-color-beige-300);
    --ibou-color-text-link: var(--ibou-color-beige-50);
    --ibou-color-text-link-hover: var(--ibou-color-pink-300);
    --ibou-color-text-link-visited: var(--ibou-color-beige-400);
    --ibou-color-text-link-external: var(--ibou-color-green-300);
    --ibou-color-text-secondary-on-brand: var(--ibou-color-pink-950);
    --ibou-color-text-secondary-on-discover: var(--ibou-color-green-950);
    --ibou-color-text-secondary-on-warm: var(--ibou-color-beige-950);
    --ibou-color-text-link-on-brand: var(--ibou-color-neutral-500);
    --ibou-color-text-link-on-brand-hover: var(--ibou-color-pink-950);
    --ibou-color-text-link-on-brand-visited: var(--ibou-color-pink-950);
    --ibou-color-text-link-on-discover: var(--ibou-color-neutral-500);
    --ibou-color-text-link-on-discover-hover: var(--ibou-color-green-950);
    --ibou-color-text-link-on-discover-visited: var(--ibou-color-green-950);
    --ibou-color-text-link-on-warm: var(--ibou-color-neutral-500);
    --ibou-color-text-link-on-warm-visited: var(--ibou-color-beige-950);
    --ibou-color-border-strong: var(--ibou-color-beige-50);
    --ibou-color-border-default: var(--ibou-color-neutral-400);
    --ibou-color-border-subtle: var(--ibou-color-neutral-600);
    --ibou-color-border-muted: var(--ibou-color-neutral-400);
    --ibou-color-border-inverse: var(--ibou-color-neutral-500);
    --ibou-color-border-focus: var(--ibou-color-pink-500);
    --ibou-color-border-focus-halo: var(--ibou-color-neutral-700); /** Second anneau (décalage) : garantit un contraste ≥ 3:1 sur toute surface, y compris rose 500 */
    --ibou-color-border-focus-inverse: var(--ibou-color-neutral-500);
    --ibou-color-border-brand: var(--ibou-color-pink-400);
    --ibou-color-border-discover: var(--ibou-color-green-400);
    --ibou-color-border-hover: var(--ibou-color-beige-50);
    --ibou-color-border-error: var(--ibou-color-pink-300);
    --ibou-color-border-disabled: var(--ibou-color-neutral-400);
    --ibou-color-border-focus-on-brand: var(--ibou-color-neutral-500);
    --ibou-color-border-focus-halo-on-brand: var(--ibou-color-beige-50);
    --ibou-color-border-focus-on-dark: var(--ibou-color-beige-50);
    --ibou-color-border-focus-halo-on-dark: var(--ibou-color-neutral-500);
    --ibou-color-icon-primary: var(--ibou-color-beige-50);
    --ibou-color-icon-secondary: var(--ibou-color-neutral-200);
    --ibou-color-icon-disabled: var(--ibou-color-neutral-300);
    --ibou-color-icon-inverse: var(--ibou-color-neutral-500);
    --ibou-color-icon-brand: var(--ibou-color-pink-400);
    --ibou-color-icon-discover: var(--ibou-color-green-400);
    --ibou-color-icon-on-brand: var(--ibou-color-neutral-500);
    --ibou-color-icon-on-discover: var(--ibou-color-neutral-500);
    --ibou-color-icon-on-warm: var(--ibou-color-neutral-500);
    --ibou-color-icon-on-inverse: var(--ibou-color-neutral-500);
    --ibou-color-action-primary-bg: var(--ibou-color-pink-500);
    --ibou-color-action-primary-bg-hover: var(--ibou-color-pink-400);
    --ibou-color-action-primary-bg-active: var(--ibou-color-pink-300);
    --ibou-color-action-primary-text: var(--ibou-color-neutral-500);
    --ibou-color-action-primary-border: var(--ibou-color-pink-500);
    --ibou-color-action-secondary-bg-hover: var(--ibou-color-neutral-400);
    --ibou-color-action-secondary-bg-active: var(--ibou-color-neutral-400);
    --ibou-color-action-secondary-text: var(--ibou-color-beige-50);
    --ibou-color-action-secondary-border: var(--ibou-color-beige-50);
    --ibou-color-action-tertiary-bg-hover: var(--ibou-color-neutral-600);
    --ibou-color-action-tertiary-bg-active: var(--ibou-color-neutral-400);
    --ibou-color-action-tertiary-text: var(--ibou-color-beige-50);
    --ibou-color-action-tertiary-underline: var(--ibou-color-beige-50);
    --ibou-color-action-discover-bg: var(--ibou-color-green-500);
    --ibou-color-action-discover-bg-hover: var(--ibou-color-green-400);
    --ibou-color-action-discover-bg-active: var(--ibou-color-green-300);
    --ibou-color-action-discover-text: var(--ibou-color-neutral-500);
    --ibou-color-action-discover-border: var(--ibou-color-neutral-500); /** Anthracite dans les deux thèmes, comme le texte (D-56 : « vert 500, texte et trait anthracite ») : la pilule verte ne change pas avec le thème. Vert 500 jusqu’au 2026-10-01 : la pilule perdait son trait en sombre, sur la page comme sur la card vert 900. */
    --ibou-color-action-inverse-bg: var(--ibou-color-neutral-500);
    --ibou-color-action-inverse-bg-hover: var(--ibou-color-neutral-600);
    --ibou-color-action-inverse-bg-active: var(--ibou-color-neutral-700);
    --ibou-color-action-inverse-text: var(--ibou-color-beige-50);
    --ibou-color-action-inverse-border: var(--ibou-color-neutral-500);
    --ibou-color-action-disabled-bg: var(--ibou-color-neutral-600);
    --ibou-color-action-disabled-text: var(--ibou-color-neutral-300);
    --ibou-color-action-disabled-border: var(--ibou-color-neutral-400);
    --ibou-color-action-selected-bg: var(--ibou-color-pink-950);
    --ibou-color-action-selected-text: var(--ibou-color-beige-50);
    --ibou-color-action-selected-border: var(--ibou-color-beige-50);
    --ibou-color-action-on-strong-bg: var(--ibou-color-white);
    --ibou-color-action-on-strong-bg-hover: var(--ibou-color-beige-100);
    --ibou-color-action-on-strong-bg-active: var(--ibou-color-beige-200);
    --ibou-color-action-on-strong-text: var(--ibou-color-neutral-500);
    --ibou-color-action-on-strong-border: var(--ibou-color-neutral-500);
    --ibou-color-status-success-bg: var(--ibou-color-green-950);
    --ibou-color-status-success-bg-strong: var(--ibou-color-green-400);
    --ibou-color-status-success-border: var(--ibou-color-green-400);
    --ibou-color-status-success-text: var(--ibou-color-green-300);
    --ibou-color-status-success-icon: var(--ibou-color-green-400);
    --ibou-color-status-success-text-on-strong: var(--ibou-color-neutral-500); /** Texte sur la surface forte (identique dans les deux thèmes : la surface reste claire) */
    --ibou-color-status-success-icon-on-strong: var(--ibou-color-neutral-500); /** Icône posée sur la surface forte du même état (card illustrée, bandeau plein) : même couleur que le texte, jamais la couleur d’accent qui s’y confondrait */
    --ibou-color-status-warning-bg: var(--ibou-color-pink-950);
    --ibou-color-status-warning-bg-strong: var(--ibou-color-pink-400);
    --ibou-color-status-warning-border: var(--ibou-color-pink-400);
    --ibou-color-status-warning-text: var(--ibou-color-pink-300);
    --ibou-color-status-warning-icon: var(--ibou-color-pink-400);
    --ibou-color-status-warning-text-on-strong: var(--ibou-color-neutral-500);
    --ibou-color-status-warning-icon-on-strong: var(--ibou-color-neutral-500); /** Icône posée sur la surface forte du même état (card illustrée, bandeau plein) : même couleur que le texte, jamais la couleur d’accent qui s’y confondrait */
    --ibou-color-status-error-bg: var(--ibou-color-neutral-600);
    --ibou-color-status-error-bg-strong: var(--ibou-color-pink-300);
    --ibou-color-status-error-border: var(--ibou-color-beige-50);
    --ibou-color-status-error-text: var(--ibou-color-pink-300);
    --ibou-color-status-error-icon: var(--ibou-color-pink-300);
    --ibou-color-status-error-text-on-strong: var(--ibou-color-neutral-500);
    --ibou-color-status-error-icon-on-strong: var(--ibou-color-neutral-500); /** Icône posée sur la surface forte du même état (card illustrée, bandeau plein) : même couleur que le texte, jamais la couleur d’accent qui s’y confondrait */
    --ibou-color-status-error-border-on-inverse: var(--ibou-color-pink-900);
    --ibou-color-status-info-bg: var(--ibou-color-beige-950);
    --ibou-color-status-info-bg-strong: var(--ibou-color-beige-500);
    --ibou-color-status-info-border: var(--ibou-color-beige-500);
    --ibou-color-status-info-text: var(--ibou-color-beige-300);
    --ibou-color-status-info-icon: var(--ibou-color-beige-400);
    --ibou-color-status-info-text-on-strong: var(--ibou-color-neutral-500); /** Texte sur la surface forte (identique dans les deux thèmes : la surface reste claire) */
    --ibou-color-status-info-icon-on-strong: var(--ibou-color-neutral-500); /** Icône posée sur la surface forte du même état (card illustrée, bandeau plein) : même couleur que le texte, jamais la couleur d’accent qui s’y confondrait */
    --ibou-color-status-neutral-bg: var(--ibou-color-neutral-600);
    --ibou-color-status-neutral-border: var(--ibou-color-neutral-300);
    --ibou-color-status-neutral-text: var(--ibou-color-neutral-200);
    --ibou-color-status-neutral-icon: var(--ibou-color-neutral-200);
    --ibou-color-loading-rest: var(--ibou-color-beige-900); /** Repos (2,70:1 sur la page, 2,39:1 dans une card). */
    --ibou-color-loading-pulse: var(--ibou-color-beige-950); /** L’autre extrémité, vers la surface (1,74:1 et 1,54:1). Écart de 1,56:1 avec le repos. */
    --ibou-color-ad-bg: var(--ibou-color-neutral-600);
    --ibou-color-ad-border: var(--ibou-color-beige-800); /** Reste un beige en propre, contrairement au thème clair : en sombre, `border.default` est un gris neutre, et c’est justement la chaleur qui sépare l’encart du reste de la page. */
    --ibou-color-ad-label: var(--ibou-color-beige-400);
    --ibou-color-chart-1: var(--ibou-color-neutral-50);
    --ibou-color-chart-2: var(--ibou-color-pink-800);
    --ibou-color-chart-3: var(--ibou-color-green-50);
    --ibou-color-chart-4: var(--ibou-color-beige-800);
    --ibou-color-chart-5: var(--ibou-color-pink-50);
    --ibou-color-chart-6: var(--ibou-color-green-800);
    --ibou-color-chart-grid: var(--ibou-color-neutral-600);
    --ibou-color-chart-axis: var(--ibou-color-neutral-200);
    --ibou-color-chart-stroke: var(--ibou-color-bg-canvas); /** Contour obligatoire des aplats : couleur du fond, il sépare deux séries adjacentes sans porter d’information */
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --ibou-color-action-secondary-bg: rgba(0, 0, 0, 0);
    --ibou-color-action-tertiary-bg: rgba(0, 0, 0, 0);
    --ibou-color-overlay-scrim: rgba(13, 13, 13, 0.7);
    --ibou-color-bg-canvas: var(--ibou-color-neutral-700);
    --ibou-color-bg-subtle: var(--ibou-color-neutral-600);
    --ibou-color-bg-muted: var(--ibou-color-neutral-500);
    --ibou-color-bg-emphasis: var(--ibou-color-neutral-900);
    --ibou-color-bg-inverse: var(--ibou-color-beige-50);
    --ibou-color-surface-default: var(--ibou-color-neutral-500);
    --ibou-color-surface-raised: var(--ibou-color-neutral-500);
    --ibou-color-surface-sunken: var(--ibou-color-neutral-800);
    --ibou-color-surface-muted: var(--ibou-color-neutral-600);
    --ibou-color-surface-inverse: var(--ibou-color-beige-50);
    --ibou-color-surface-brand: var(--ibou-color-pink-500);
    --ibou-color-surface-brand-subtle: var(--ibou-color-pink-950);
    --ibou-color-surface-brand-muted: var(--ibou-color-pink-900);
    --ibou-color-surface-discover: var(--ibou-color-green-500);
    --ibou-color-surface-discover-subtle: var(--ibou-color-green-950);
    --ibou-color-surface-discover-strong: var(--ibou-color-green-900);
    --ibou-color-surface-warm: var(--ibou-color-beige-500);
    --ibou-color-surface-warm-subtle: var(--ibou-color-beige-950);
    --ibou-color-surface-highlight: var(--ibou-color-beige-950);
    --ibou-color-text-primary: var(--ibou-color-beige-50);
    --ibou-color-text-secondary: var(--ibou-color-neutral-200);
    --ibou-color-text-placeholder: var(--ibou-color-neutral-200);
    --ibou-color-text-disabled: var(--ibou-color-neutral-300);
    --ibou-color-text-inverse: var(--ibou-color-neutral-500);
    --ibou-color-text-inverse-secondary: var(--ibou-color-neutral-400);
    --ibou-color-text-on-brand: var(--ibou-color-neutral-500);
    --ibou-color-text-on-discover: var(--ibou-color-neutral-500);
    --ibou-color-text-on-discover-strong: var(--ibou-color-beige-50);
    --ibou-color-text-on-warm: var(--ibou-color-neutral-500);
    --ibou-color-text-brand: var(--ibou-color-pink-300);
    --ibou-color-text-discover: var(--ibou-color-green-300);
    --ibou-color-text-discover-accent: var(--ibou-color-green-300);
    --ibou-color-text-warm: var(--ibou-color-beige-300);
    --ibou-color-text-link: var(--ibou-color-beige-50);
    --ibou-color-text-link-hover: var(--ibou-color-pink-300);
    --ibou-color-text-link-visited: var(--ibou-color-beige-400);
    --ibou-color-text-link-external: var(--ibou-color-green-300);
    --ibou-color-text-secondary-on-brand: var(--ibou-color-pink-950);
    --ibou-color-text-secondary-on-discover: var(--ibou-color-green-950);
    --ibou-color-text-secondary-on-warm: var(--ibou-color-beige-950);
    --ibou-color-text-link-on-brand: var(--ibou-color-neutral-500);
    --ibou-color-text-link-on-brand-hover: var(--ibou-color-pink-950);
    --ibou-color-text-link-on-brand-visited: var(--ibou-color-pink-950);
    --ibou-color-text-link-on-discover: var(--ibou-color-neutral-500);
    --ibou-color-text-link-on-discover-hover: var(--ibou-color-green-950);
    --ibou-color-text-link-on-discover-visited: var(--ibou-color-green-950);
    --ibou-color-text-link-on-warm: var(--ibou-color-neutral-500);
    --ibou-color-text-link-on-warm-visited: var(--ibou-color-beige-950);
    --ibou-color-border-strong: var(--ibou-color-beige-50);
    --ibou-color-border-default: var(--ibou-color-neutral-400);
    --ibou-color-border-subtle: var(--ibou-color-neutral-600);
    --ibou-color-border-muted: var(--ibou-color-neutral-400);
    --ibou-color-border-inverse: var(--ibou-color-neutral-500);
    --ibou-color-border-focus: var(--ibou-color-pink-500);
    --ibou-color-border-focus-halo: var(--ibou-color-neutral-700); /** Second anneau (décalage) : garantit un contraste ≥ 3:1 sur toute surface, y compris rose 500 */
    --ibou-color-border-focus-inverse: var(--ibou-color-neutral-500);
    --ibou-color-border-brand: var(--ibou-color-pink-400);
    --ibou-color-border-discover: var(--ibou-color-green-400);
    --ibou-color-border-hover: var(--ibou-color-beige-50);
    --ibou-color-border-error: var(--ibou-color-pink-300);
    --ibou-color-border-disabled: var(--ibou-color-neutral-400);
    --ibou-color-border-focus-on-brand: var(--ibou-color-neutral-500);
    --ibou-color-border-focus-halo-on-brand: var(--ibou-color-beige-50);
    --ibou-color-border-focus-on-dark: var(--ibou-color-beige-50);
    --ibou-color-border-focus-halo-on-dark: var(--ibou-color-neutral-500);
    --ibou-color-icon-primary: var(--ibou-color-beige-50);
    --ibou-color-icon-secondary: var(--ibou-color-neutral-200);
    --ibou-color-icon-disabled: var(--ibou-color-neutral-300);
    --ibou-color-icon-inverse: var(--ibou-color-neutral-500);
    --ibou-color-icon-brand: var(--ibou-color-pink-400);
    --ibou-color-icon-discover: var(--ibou-color-green-400);
    --ibou-color-icon-on-brand: var(--ibou-color-neutral-500);
    --ibou-color-icon-on-discover: var(--ibou-color-neutral-500);
    --ibou-color-icon-on-warm: var(--ibou-color-neutral-500);
    --ibou-color-icon-on-inverse: var(--ibou-color-neutral-500);
    --ibou-color-action-primary-bg: var(--ibou-color-pink-500);
    --ibou-color-action-primary-bg-hover: var(--ibou-color-pink-400);
    --ibou-color-action-primary-bg-active: var(--ibou-color-pink-300);
    --ibou-color-action-primary-text: var(--ibou-color-neutral-500);
    --ibou-color-action-primary-border: var(--ibou-color-pink-500);
    --ibou-color-action-secondary-bg-hover: var(--ibou-color-neutral-400);
    --ibou-color-action-secondary-bg-active: var(--ibou-color-neutral-400);
    --ibou-color-action-secondary-text: var(--ibou-color-beige-50);
    --ibou-color-action-secondary-border: var(--ibou-color-beige-50);
    --ibou-color-action-tertiary-bg-hover: var(--ibou-color-neutral-600);
    --ibou-color-action-tertiary-bg-active: var(--ibou-color-neutral-400);
    --ibou-color-action-tertiary-text: var(--ibou-color-beige-50);
    --ibou-color-action-tertiary-underline: var(--ibou-color-beige-50);
    --ibou-color-action-discover-bg: var(--ibou-color-green-500);
    --ibou-color-action-discover-bg-hover: var(--ibou-color-green-400);
    --ibou-color-action-discover-bg-active: var(--ibou-color-green-300);
    --ibou-color-action-discover-text: var(--ibou-color-neutral-500);
    --ibou-color-action-discover-border: var(--ibou-color-neutral-500); /** Anthracite dans les deux thèmes, comme le texte (D-56 : « vert 500, texte et trait anthracite ») : la pilule verte ne change pas avec le thème. Vert 500 jusqu’au 2026-10-01 : la pilule perdait son trait en sombre, sur la page comme sur la card vert 900. */
    --ibou-color-action-inverse-bg: var(--ibou-color-neutral-500);
    --ibou-color-action-inverse-bg-hover: var(--ibou-color-neutral-600);
    --ibou-color-action-inverse-bg-active: var(--ibou-color-neutral-700);
    --ibou-color-action-inverse-text: var(--ibou-color-beige-50);
    --ibou-color-action-inverse-border: var(--ibou-color-neutral-500);
    --ibou-color-action-disabled-bg: var(--ibou-color-neutral-600);
    --ibou-color-action-disabled-text: var(--ibou-color-neutral-300);
    --ibou-color-action-disabled-border: var(--ibou-color-neutral-400);
    --ibou-color-action-selected-bg: var(--ibou-color-pink-950);
    --ibou-color-action-selected-text: var(--ibou-color-beige-50);
    --ibou-color-action-selected-border: var(--ibou-color-beige-50);
    --ibou-color-action-on-strong-bg: var(--ibou-color-white);
    --ibou-color-action-on-strong-bg-hover: var(--ibou-color-beige-100);
    --ibou-color-action-on-strong-bg-active: var(--ibou-color-beige-200);
    --ibou-color-action-on-strong-text: var(--ibou-color-neutral-500);
    --ibou-color-action-on-strong-border: var(--ibou-color-neutral-500);
    --ibou-color-status-success-bg: var(--ibou-color-green-950);
    --ibou-color-status-success-bg-strong: var(--ibou-color-green-400);
    --ibou-color-status-success-border: var(--ibou-color-green-400);
    --ibou-color-status-success-text: var(--ibou-color-green-300);
    --ibou-color-status-success-icon: var(--ibou-color-green-400);
    --ibou-color-status-success-text-on-strong: var(--ibou-color-neutral-500); /** Texte sur la surface forte (identique dans les deux thèmes : la surface reste claire) */
    --ibou-color-status-success-icon-on-strong: var(--ibou-color-neutral-500); /** Icône posée sur la surface forte du même état (card illustrée, bandeau plein) : même couleur que le texte, jamais la couleur d’accent qui s’y confondrait */
    --ibou-color-status-warning-bg: var(--ibou-color-pink-950);
    --ibou-color-status-warning-bg-strong: var(--ibou-color-pink-400);
    --ibou-color-status-warning-border: var(--ibou-color-pink-400);
    --ibou-color-status-warning-text: var(--ibou-color-pink-300);
    --ibou-color-status-warning-icon: var(--ibou-color-pink-400);
    --ibou-color-status-warning-text-on-strong: var(--ibou-color-neutral-500);
    --ibou-color-status-warning-icon-on-strong: var(--ibou-color-neutral-500); /** Icône posée sur la surface forte du même état (card illustrée, bandeau plein) : même couleur que le texte, jamais la couleur d’accent qui s’y confondrait */
    --ibou-color-status-error-bg: var(--ibou-color-neutral-600);
    --ibou-color-status-error-bg-strong: var(--ibou-color-pink-300);
    --ibou-color-status-error-border: var(--ibou-color-beige-50);
    --ibou-color-status-error-text: var(--ibou-color-pink-300);
    --ibou-color-status-error-icon: var(--ibou-color-pink-300);
    --ibou-color-status-error-text-on-strong: var(--ibou-color-neutral-500);
    --ibou-color-status-error-icon-on-strong: var(--ibou-color-neutral-500); /** Icône posée sur la surface forte du même état (card illustrée, bandeau plein) : même couleur que le texte, jamais la couleur d’accent qui s’y confondrait */
    --ibou-color-status-error-border-on-inverse: var(--ibou-color-pink-900);
    --ibou-color-status-info-bg: var(--ibou-color-beige-950);
    --ibou-color-status-info-bg-strong: var(--ibou-color-beige-500);
    --ibou-color-status-info-border: var(--ibou-color-beige-500);
    --ibou-color-status-info-text: var(--ibou-color-beige-300);
    --ibou-color-status-info-icon: var(--ibou-color-beige-400);
    --ibou-color-status-info-text-on-strong: var(--ibou-color-neutral-500); /** Texte sur la surface forte (identique dans les deux thèmes : la surface reste claire) */
    --ibou-color-status-info-icon-on-strong: var(--ibou-color-neutral-500); /** Icône posée sur la surface forte du même état (card illustrée, bandeau plein) : même couleur que le texte, jamais la couleur d’accent qui s’y confondrait */
    --ibou-color-status-neutral-bg: var(--ibou-color-neutral-600);
    --ibou-color-status-neutral-border: var(--ibou-color-neutral-300);
    --ibou-color-status-neutral-text: var(--ibou-color-neutral-200);
    --ibou-color-status-neutral-icon: var(--ibou-color-neutral-200);
    --ibou-color-loading-rest: var(--ibou-color-beige-900); /** Repos (2,70:1 sur la page, 2,39:1 dans une card). */
    --ibou-color-loading-pulse: var(--ibou-color-beige-950); /** L’autre extrémité, vers la surface (1,74:1 et 1,54:1). Écart de 1,56:1 avec le repos. */
    --ibou-color-ad-bg: var(--ibou-color-neutral-600);
    --ibou-color-ad-border: var(--ibou-color-beige-800); /** Reste un beige en propre, contrairement au thème clair : en sombre, `border.default` est un gris neutre, et c’est justement la chaleur qui sépare l’encart du reste de la page. */
    --ibou-color-ad-label: var(--ibou-color-beige-400);
    --ibou-color-chart-1: var(--ibou-color-neutral-50);
    --ibou-color-chart-2: var(--ibou-color-pink-800);
    --ibou-color-chart-3: var(--ibou-color-green-50);
    --ibou-color-chart-4: var(--ibou-color-beige-800);
    --ibou-color-chart-5: var(--ibou-color-pink-50);
    --ibou-color-chart-6: var(--ibou-color-green-800);
    --ibou-color-chart-grid: var(--ibou-color-neutral-600);
    --ibou-color-chart-axis: var(--ibou-color-neutral-200);
    --ibou-color-chart-stroke: var(--ibou-color-bg-canvas); /** Contour obligatoire des aplats : couleur du fond, il sépare deux séries adjacentes sans porter d’information */
  }
}

/* ——— build/css/typography.css ——— */
/**
 * ibou design system v2.0.0 — fichier généré par scripts/build.mjs, ne pas éditer à la main.
 * Source de vérité : tokens/**.json
 */

:root {
  --ibou-typography-display-poster-font-family: 'New Spirit', 'New Spirit Fallback', 'Iowan Old Style', 'Palatino Linotype', 'Book Antiqua', Georgia, serif;
  --ibou-typography-display-poster-font-weight: 300;
  --ibou-typography-display-poster-font-size: clamp(3.5rem, 1.5rem + 8vw, 8rem);
  --ibou-typography-display-poster-line-height: 0.95;
  --ibou-typography-display-poster-letter-spacing: -0.01em;
  --ibou-typography-display-poster: italic var(--ibou-typography-display-poster-font-weight) var(--ibou-typography-display-poster-font-size)/var(--ibou-typography-display-poster-line-height) var(--ibou-typography-display-poster-font-family);
  --ibou-typography-display-xl-font-family: 'New Spirit', 'New Spirit Fallback', 'Iowan Old Style', 'Palatino Linotype', 'Book Antiqua', Georgia, serif;
  --ibou-typography-display-xl-font-weight: 400;
  --ibou-typography-display-xl-font-size: clamp(3rem, 1.75rem + 4vw, 5rem);
  --ibou-typography-display-xl-line-height: 1.05;
  --ibou-typography-display-xl-letter-spacing: -0.01em;
  --ibou-typography-display-xl: var(--ibou-typography-display-xl-font-weight) var(--ibou-typography-display-xl-font-size)/var(--ibou-typography-display-xl-line-height) var(--ibou-typography-display-xl-font-family);
  --ibou-typography-display-lg-font-family: 'New Spirit', 'New Spirit Fallback', 'Iowan Old Style', 'Palatino Linotype', 'Book Antiqua', Georgia, serif;
  --ibou-typography-display-lg-font-weight: 400;
  --ibou-typography-display-lg-font-size: clamp(2.5rem, 1.5rem + 3vw, 3.75rem);
  --ibou-typography-display-lg-line-height: 1.05;
  --ibou-typography-display-lg-letter-spacing: -0.01em;
  --ibou-typography-display-lg: var(--ibou-typography-display-lg-font-weight) var(--ibou-typography-display-lg-font-size)/var(--ibou-typography-display-lg-line-height) var(--ibou-typography-display-lg-font-family);
  --ibou-typography-heading-1-font-family: 'New Spirit', 'New Spirit Fallback', 'Iowan Old Style', 'Palatino Linotype', 'Book Antiqua', Georgia, serif;
  --ibou-typography-heading-1-font-weight: 400;
  --ibou-typography-heading-1-font-size: clamp(2rem, 1.25rem + 2.5vw, 3rem);
  --ibou-typography-heading-1-line-height: 1.15;
  --ibou-typography-heading-1-letter-spacing: -0.01em;
  --ibou-typography-heading-1: var(--ibou-typography-heading-1-font-weight) var(--ibou-typography-heading-1-font-size)/var(--ibou-typography-heading-1-line-height) var(--ibou-typography-heading-1-font-family);
  --ibou-typography-heading-2-font-family: 'New Spirit', 'New Spirit Fallback', 'Iowan Old Style', 'Palatino Linotype', 'Book Antiqua', Georgia, serif;
  --ibou-typography-heading-2-font-weight: 400;
  --ibou-typography-heading-2-font-size: clamp(1.625rem, 1.125rem + 1.5vw, 2.25rem);
  --ibou-typography-heading-2-line-height: 1.15;
  --ibou-typography-heading-2-letter-spacing: -0.01em;
  --ibou-typography-heading-2: var(--ibou-typography-heading-2-font-weight) var(--ibou-typography-heading-2-font-size)/var(--ibou-typography-heading-2-line-height) var(--ibou-typography-heading-2-font-family);
  --ibou-typography-heading-3-font-family: 'New Spirit', 'New Spirit Fallback', 'Iowan Old Style', 'Palatino Linotype', 'Book Antiqua', Georgia, serif;
  --ibou-typography-heading-3-font-weight: 400;
  --ibou-typography-heading-3-font-size: 1.5rem;
  --ibou-typography-heading-3-line-height: 1.2;
  --ibou-typography-heading-3-letter-spacing: -0.01em;
  --ibou-typography-heading-3: var(--ibou-typography-heading-3-font-weight) var(--ibou-typography-heading-3-font-size)/var(--ibou-typography-heading-3-line-height) var(--ibou-typography-heading-3-font-family);
  --ibou-typography-heading-4-font-family: 'New Spirit', 'New Spirit Fallback', 'Iowan Old Style', 'Palatino Linotype', 'Book Antiqua', Georgia, serif;
  --ibou-typography-heading-4-font-weight: 500;
  --ibou-typography-heading-4-font-size: 1.25rem;
  --ibou-typography-heading-4-line-height: 1.3;
  --ibou-typography-heading-4-letter-spacing: -0.01em;
  --ibou-typography-heading-4: var(--ibou-typography-heading-4-font-weight) var(--ibou-typography-heading-4-font-size)/var(--ibou-typography-heading-4-line-height) var(--ibou-typography-heading-4-font-family);
  --ibou-typography-heading-5-font-family: 'New Spirit', 'New Spirit Fallback', 'Iowan Old Style', 'Palatino Linotype', 'Book Antiqua', Georgia, serif;
  --ibou-typography-heading-5-font-weight: 500;
  --ibou-typography-heading-5-font-size: 1.125rem;
  --ibou-typography-heading-5-line-height: 1.3;
  --ibou-typography-heading-5-letter-spacing: 0em;
  --ibou-typography-heading-5: var(--ibou-typography-heading-5-font-weight) var(--ibou-typography-heading-5-font-size)/var(--ibou-typography-heading-5-line-height) var(--ibou-typography-heading-5-font-family);
  --ibou-typography-subtitle-xl-font-family: Poppins, 'Poppins Fallback', 'Helvetica Neue', Arial, system-ui, sans-serif;
  --ibou-typography-subtitle-xl-font-weight: 500;
  --ibou-typography-subtitle-xl-font-size: clamp(1.5rem, 1.1rem + 1.2vw, 2rem);
  --ibou-typography-subtitle-xl-line-height: 1.3;
  --ibou-typography-subtitle-xl-letter-spacing: -0.02em;
  --ibou-typography-subtitle-xl: var(--ibou-typography-subtitle-xl-font-weight) var(--ibou-typography-subtitle-xl-font-size)/var(--ibou-typography-subtitle-xl-line-height) var(--ibou-typography-subtitle-xl-font-family);
  --ibou-typography-subtitle-lg-font-family: Poppins, 'Poppins Fallback', 'Helvetica Neue', Arial, system-ui, sans-serif;
  --ibou-typography-subtitle-lg-font-weight: 500;
  --ibou-typography-subtitle-lg-font-size: 1.25rem;
  --ibou-typography-subtitle-lg-line-height: 1.3;
  --ibou-typography-subtitle-lg-letter-spacing: -0.02em;
  --ibou-typography-subtitle-lg: var(--ibou-typography-subtitle-lg-font-weight) var(--ibou-typography-subtitle-lg-font-size)/var(--ibou-typography-subtitle-lg-line-height) var(--ibou-typography-subtitle-lg-font-family);
  --ibou-typography-subtitle-md-font-family: Poppins, 'Poppins Fallback', 'Helvetica Neue', Arial, system-ui, sans-serif;
  --ibou-typography-subtitle-md-font-weight: 500;
  --ibou-typography-subtitle-md-font-size: 1.125rem;
  --ibou-typography-subtitle-md-line-height: 1.3;
  --ibou-typography-subtitle-md-letter-spacing: -0.02em;
  --ibou-typography-subtitle-md: var(--ibou-typography-subtitle-md-font-weight) var(--ibou-typography-subtitle-md-font-size)/var(--ibou-typography-subtitle-md-line-height) var(--ibou-typography-subtitle-md-font-family);
  --ibou-typography-subtitle-sm-font-family: Poppins, 'Poppins Fallback', 'Helvetica Neue', Arial, system-ui, sans-serif;
  --ibou-typography-subtitle-sm-font-weight: 500;
  --ibou-typography-subtitle-sm-font-size: 1rem;
  --ibou-typography-subtitle-sm-line-height: 1.3;
  --ibou-typography-subtitle-sm-letter-spacing: -0.01em;
  --ibou-typography-subtitle-sm: var(--ibou-typography-subtitle-sm-font-weight) var(--ibou-typography-subtitle-sm-font-size)/var(--ibou-typography-subtitle-sm-line-height) var(--ibou-typography-subtitle-sm-font-family);
  --ibou-typography-lede-font-family: Poppins, 'Poppins Fallback', 'Helvetica Neue', Arial, system-ui, sans-serif;
  --ibou-typography-lede-font-weight: 600;
  --ibou-typography-lede-font-size: 1.25rem;
  --ibou-typography-lede-line-height: 1.3;
  --ibou-typography-lede-letter-spacing: -0.02em;
  --ibou-typography-lede: var(--ibou-typography-lede-font-weight) var(--ibou-typography-lede-font-size)/var(--ibou-typography-lede-line-height) var(--ibou-typography-lede-font-family);
  --ibou-typography-body-lg-font-family: Poppins, 'Poppins Fallback', 'Helvetica Neue', Arial, system-ui, sans-serif;
  --ibou-typography-body-lg-font-weight: 400;
  --ibou-typography-body-lg-font-size: 1.125rem;
  --ibou-typography-body-lg-line-height: 1.65;
  --ibou-typography-body-lg-letter-spacing: -0.02em;
  --ibou-typography-body-lg: var(--ibou-typography-body-lg-font-weight) var(--ibou-typography-body-lg-font-size)/var(--ibou-typography-body-lg-line-height) var(--ibou-typography-body-lg-font-family);
  --ibou-typography-body-md-font-family: Poppins, 'Poppins Fallback', 'Helvetica Neue', Arial, system-ui, sans-serif;
  --ibou-typography-body-md-font-weight: 400;
  --ibou-typography-body-md-font-size: 1rem;
  --ibou-typography-body-md-line-height: 1.5;
  --ibou-typography-body-md-letter-spacing: -0.02em;
  --ibou-typography-body-md: var(--ibou-typography-body-md-font-weight) var(--ibou-typography-body-md-font-size)/var(--ibou-typography-body-md-line-height) var(--ibou-typography-body-md-font-family);
  --ibou-typography-body-sm-font-family: Poppins, 'Poppins Fallback', 'Helvetica Neue', Arial, system-ui, sans-serif;
  --ibou-typography-body-sm-font-weight: 400;
  --ibou-typography-body-sm-font-size: 0.875rem;
  --ibou-typography-body-sm-line-height: 1.5;
  --ibou-typography-body-sm-letter-spacing: -0.01em;
  --ibou-typography-body-sm: var(--ibou-typography-body-sm-font-weight) var(--ibou-typography-body-sm-font-size)/var(--ibou-typography-body-sm-line-height) var(--ibou-typography-body-sm-font-family);
  --ibou-typography-body-strong-font-family: Poppins, 'Poppins Fallback', 'Helvetica Neue', Arial, system-ui, sans-serif;
  --ibou-typography-body-strong-font-weight: 600;
  --ibou-typography-body-strong-font-size: 1rem;
  --ibou-typography-body-strong-line-height: 1.5;
  --ibou-typography-body-strong-letter-spacing: -0.02em;
  --ibou-typography-body-strong: var(--ibou-typography-body-strong-font-weight) var(--ibou-typography-body-strong-font-size)/var(--ibou-typography-body-strong-line-height) var(--ibou-typography-body-strong-font-family);
  --ibou-typography-label-lg-font-family: Poppins, 'Poppins Fallback', 'Helvetica Neue', Arial, system-ui, sans-serif;
  --ibou-typography-label-lg-font-weight: 500;
  --ibou-typography-label-lg-font-size: 1rem;
  --ibou-typography-label-lg-line-height: 1;
  --ibou-typography-label-lg-letter-spacing: 0em;
  --ibou-typography-label-lg: var(--ibou-typography-label-lg-font-weight) var(--ibou-typography-label-lg-font-size)/var(--ibou-typography-label-lg-line-height) var(--ibou-typography-label-lg-font-family);
  --ibou-typography-label-md-font-family: Poppins, 'Poppins Fallback', 'Helvetica Neue', Arial, system-ui, sans-serif;
  --ibou-typography-label-md-font-weight: 500;
  --ibou-typography-label-md-font-size: 0.875rem;
  --ibou-typography-label-md-line-height: 1;
  --ibou-typography-label-md-letter-spacing: 0em;
  --ibou-typography-label-md: var(--ibou-typography-label-md-font-weight) var(--ibou-typography-label-md-font-size)/var(--ibou-typography-label-md-line-height) var(--ibou-typography-label-md-font-family);
  --ibou-typography-label-sm-font-family: Poppins, 'Poppins Fallback', 'Helvetica Neue', Arial, system-ui, sans-serif;
  --ibou-typography-label-sm-font-weight: 500;
  --ibou-typography-label-sm-font-size: 0.75rem;
  --ibou-typography-label-sm-line-height: 1;
  --ibou-typography-label-sm-letter-spacing: 0.04em;
  --ibou-typography-label-sm: var(--ibou-typography-label-sm-font-weight) var(--ibou-typography-label-sm-font-size)/var(--ibou-typography-label-sm-line-height) var(--ibou-typography-label-sm-font-family);
  --ibou-typography-tag-md-font-family: Poppins, 'Poppins Fallback', 'Helvetica Neue', Arial, system-ui, sans-serif;
  --ibou-typography-tag-md-font-weight: 500;
  --ibou-typography-tag-md-font-size: 0.875rem;
  --ibou-typography-tag-md-line-height: 1;
  --ibou-typography-tag-md-letter-spacing: 0em;
  --ibou-typography-tag-md: var(--ibou-typography-tag-md-font-weight) var(--ibou-typography-tag-md-font-size)/var(--ibou-typography-tag-md-line-height) var(--ibou-typography-tag-md-font-family);
  --ibou-typography-tag-sm-font-family: Poppins, 'Poppins Fallback', 'Helvetica Neue', Arial, system-ui, sans-serif;
  --ibou-typography-tag-sm-font-weight: 500;
  --ibou-typography-tag-sm-font-size: 0.75rem;
  --ibou-typography-tag-sm-line-height: 1;
  --ibou-typography-tag-sm-letter-spacing: 0em;
  --ibou-typography-tag-sm: var(--ibou-typography-tag-sm-font-weight) var(--ibou-typography-tag-sm-font-size)/var(--ibou-typography-tag-sm-line-height) var(--ibou-typography-tag-sm-font-family);
  --ibou-typography-caption-font-family: Poppins, 'Poppins Fallback', 'Helvetica Neue', Arial, system-ui, sans-serif;
  --ibou-typography-caption-font-weight: 400;
  --ibou-typography-caption-font-size: 0.75rem;
  --ibou-typography-caption-line-height: 1.4;
  --ibou-typography-caption-letter-spacing: 0em;
  --ibou-typography-caption: var(--ibou-typography-caption-font-weight) var(--ibou-typography-caption-font-size)/var(--ibou-typography-caption-line-height) var(--ibou-typography-caption-font-family);
  --ibou-typography-overline-font-family: Poppins, 'Poppins Fallback', 'Helvetica Neue', Arial, system-ui, sans-serif;
  --ibou-typography-overline-font-weight: 500;
  --ibou-typography-overline-font-size: 0.75rem;
  --ibou-typography-overline-line-height: 1.4;
  --ibou-typography-overline-letter-spacing: 0.04em;
  --ibou-typography-overline: var(--ibou-typography-overline-font-weight) var(--ibou-typography-overline-font-size)/var(--ibou-typography-overline-line-height) var(--ibou-typography-overline-font-family);
  --ibou-typography-service-font-family: 'New Spirit', 'New Spirit Fallback', 'Iowan Old Style', 'Palatino Linotype', 'Book Antiqua', Georgia, serif;
  --ibou-typography-service-font-weight: 400;
  --ibou-typography-service-font-size: 1em;
  --ibou-typography-service-line-height: 1;
  --ibou-typography-service-letter-spacing: 0em;
  --ibou-typography-service: italic var(--ibou-typography-service-font-weight) var(--ibou-typography-service-font-size)/var(--ibou-typography-service-line-height) var(--ibou-typography-service-font-family);
  --ibou-typography-figure-lg-font-family: 'New Spirit', 'New Spirit Fallback', 'Iowan Old Style', 'Palatino Linotype', 'Book Antiqua', Georgia, serif;
  --ibou-typography-figure-lg-font-weight: 400;
  --ibou-typography-figure-lg-font-size: 3rem;
  --ibou-typography-figure-lg-line-height: 1;
  --ibou-typography-figure-lg-letter-spacing: -0.01em;
  --ibou-typography-figure-lg: var(--ibou-typography-figure-lg-font-weight) var(--ibou-typography-figure-lg-font-size)/var(--ibou-typography-figure-lg-line-height) var(--ibou-typography-figure-lg-font-family);
  --ibou-typography-figure-md-font-family: 'New Spirit', 'New Spirit Fallback', 'Iowan Old Style', 'Palatino Linotype', 'Book Antiqua', Georgia, serif;
  --ibou-typography-figure-md-font-weight: 400;
  --ibou-typography-figure-md-font-size: 1.75rem;
  --ibou-typography-figure-md-line-height: 1;
  --ibou-typography-figure-md-letter-spacing: -0.01em;
  --ibou-typography-figure-md: var(--ibou-typography-figure-md-font-weight) var(--ibou-typography-figure-md-font-size)/var(--ibou-typography-figure-md-line-height) var(--ibou-typography-figure-md-font-family);
  --ibou-typography-code-font-family: 'JetBrains Mono', 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
  --ibou-typography-code-font-weight: 400;
  --ibou-typography-code-font-size: 0.9em;
  --ibou-typography-code-line-height: 1.5;
  --ibou-typography-code-letter-spacing: 0em;
  --ibou-typography-code: var(--ibou-typography-code-font-weight) var(--ibou-typography-code-font-size)/var(--ibou-typography-code-line-height) var(--ibou-typography-code-font-family);
}

@layer ibou.utilities {
  .ibou-text-display-poster {
    font-family: var(--ibou-typography-display-poster-font-family);
    font-weight: var(--ibou-typography-display-poster-font-weight);
    font-size: var(--ibou-typography-display-poster-font-size);
    line-height: var(--ibou-typography-display-poster-line-height);
    letter-spacing: var(--ibou-typography-display-poster-letter-spacing);
    font-style: italic;
    text-transform: uppercase;
  }

  .ibou-text-display-xl {
    font-family: var(--ibou-typography-display-xl-font-family);
    font-weight: var(--ibou-typography-display-xl-font-weight);
    font-size: var(--ibou-typography-display-xl-font-size);
    line-height: var(--ibou-typography-display-xl-line-height);
    letter-spacing: var(--ibou-typography-display-xl-letter-spacing);
  }

  .ibou-text-display-lg {
    font-family: var(--ibou-typography-display-lg-font-family);
    font-weight: var(--ibou-typography-display-lg-font-weight);
    font-size: var(--ibou-typography-display-lg-font-size);
    line-height: var(--ibou-typography-display-lg-line-height);
    letter-spacing: var(--ibou-typography-display-lg-letter-spacing);
  }

  .ibou-text-heading-1 {
    font-family: var(--ibou-typography-heading-1-font-family);
    font-weight: var(--ibou-typography-heading-1-font-weight);
    font-size: var(--ibou-typography-heading-1-font-size);
    line-height: var(--ibou-typography-heading-1-line-height);
    letter-spacing: var(--ibou-typography-heading-1-letter-spacing);
  }

  .ibou-text-heading-2 {
    font-family: var(--ibou-typography-heading-2-font-family);
    font-weight: var(--ibou-typography-heading-2-font-weight);
    font-size: var(--ibou-typography-heading-2-font-size);
    line-height: var(--ibou-typography-heading-2-line-height);
    letter-spacing: var(--ibou-typography-heading-2-letter-spacing);
  }

  .ibou-text-heading-3 {
    font-family: var(--ibou-typography-heading-3-font-family);
    font-weight: var(--ibou-typography-heading-3-font-weight);
    font-size: var(--ibou-typography-heading-3-font-size);
    line-height: var(--ibou-typography-heading-3-line-height);
    letter-spacing: var(--ibou-typography-heading-3-letter-spacing);
  }

  .ibou-text-heading-4 {
    font-family: var(--ibou-typography-heading-4-font-family);
    font-weight: var(--ibou-typography-heading-4-font-weight);
    font-size: var(--ibou-typography-heading-4-font-size);
    line-height: var(--ibou-typography-heading-4-line-height);
    letter-spacing: var(--ibou-typography-heading-4-letter-spacing);
  }

  .ibou-text-heading-5 {
    font-family: var(--ibou-typography-heading-5-font-family);
    font-weight: var(--ibou-typography-heading-5-font-weight);
    font-size: var(--ibou-typography-heading-5-font-size);
    line-height: var(--ibou-typography-heading-5-line-height);
    letter-spacing: var(--ibou-typography-heading-5-letter-spacing);
  }

  .ibou-text-subtitle-xl {
    font-family: var(--ibou-typography-subtitle-xl-font-family);
    font-weight: var(--ibou-typography-subtitle-xl-font-weight);
    font-size: var(--ibou-typography-subtitle-xl-font-size);
    line-height: var(--ibou-typography-subtitle-xl-line-height);
    letter-spacing: var(--ibou-typography-subtitle-xl-letter-spacing);
  }

  .ibou-text-subtitle-lg {
    font-family: var(--ibou-typography-subtitle-lg-font-family);
    font-weight: var(--ibou-typography-subtitle-lg-font-weight);
    font-size: var(--ibou-typography-subtitle-lg-font-size);
    line-height: var(--ibou-typography-subtitle-lg-line-height);
    letter-spacing: var(--ibou-typography-subtitle-lg-letter-spacing);
  }

  .ibou-text-subtitle-md {
    font-family: var(--ibou-typography-subtitle-md-font-family);
    font-weight: var(--ibou-typography-subtitle-md-font-weight);
    font-size: var(--ibou-typography-subtitle-md-font-size);
    line-height: var(--ibou-typography-subtitle-md-line-height);
    letter-spacing: var(--ibou-typography-subtitle-md-letter-spacing);
  }

  .ibou-text-subtitle-sm {
    font-family: var(--ibou-typography-subtitle-sm-font-family);
    font-weight: var(--ibou-typography-subtitle-sm-font-weight);
    font-size: var(--ibou-typography-subtitle-sm-font-size);
    line-height: var(--ibou-typography-subtitle-sm-line-height);
    letter-spacing: var(--ibou-typography-subtitle-sm-letter-spacing);
  }

  .ibou-text-lede {
    font-family: var(--ibou-typography-lede-font-family);
    font-weight: var(--ibou-typography-lede-font-weight);
    font-size: var(--ibou-typography-lede-font-size);
    line-height: var(--ibou-typography-lede-line-height);
    letter-spacing: var(--ibou-typography-lede-letter-spacing);
  }

  .ibou-text-body-lg {
    font-family: var(--ibou-typography-body-lg-font-family);
    font-weight: var(--ibou-typography-body-lg-font-weight);
    font-size: var(--ibou-typography-body-lg-font-size);
    line-height: var(--ibou-typography-body-lg-line-height);
    letter-spacing: var(--ibou-typography-body-lg-letter-spacing);
  }

  .ibou-text-body-md {
    font-family: var(--ibou-typography-body-md-font-family);
    font-weight: var(--ibou-typography-body-md-font-weight);
    font-size: var(--ibou-typography-body-md-font-size);
    line-height: var(--ibou-typography-body-md-line-height);
    letter-spacing: var(--ibou-typography-body-md-letter-spacing);
  }

  .ibou-text-body-sm {
    font-family: var(--ibou-typography-body-sm-font-family);
    font-weight: var(--ibou-typography-body-sm-font-weight);
    font-size: var(--ibou-typography-body-sm-font-size);
    line-height: var(--ibou-typography-body-sm-line-height);
    letter-spacing: var(--ibou-typography-body-sm-letter-spacing);
  }

  .ibou-text-body-strong {
    font-family: var(--ibou-typography-body-strong-font-family);
    font-weight: var(--ibou-typography-body-strong-font-weight);
    font-size: var(--ibou-typography-body-strong-font-size);
    line-height: var(--ibou-typography-body-strong-line-height);
    letter-spacing: var(--ibou-typography-body-strong-letter-spacing);
  }

  .ibou-text-label-lg {
    font-family: var(--ibou-typography-label-lg-font-family);
    font-weight: var(--ibou-typography-label-lg-font-weight);
    font-size: var(--ibou-typography-label-lg-font-size);
    line-height: var(--ibou-typography-label-lg-line-height);
    letter-spacing: var(--ibou-typography-label-lg-letter-spacing);
    text-transform: uppercase;
  }

  .ibou-text-label-md {
    font-family: var(--ibou-typography-label-md-font-family);
    font-weight: var(--ibou-typography-label-md-font-weight);
    font-size: var(--ibou-typography-label-md-font-size);
    line-height: var(--ibou-typography-label-md-line-height);
    letter-spacing: var(--ibou-typography-label-md-letter-spacing);
    text-transform: uppercase;
  }

  .ibou-text-label-sm {
    font-family: var(--ibou-typography-label-sm-font-family);
    font-weight: var(--ibou-typography-label-sm-font-weight);
    font-size: var(--ibou-typography-label-sm-font-size);
    line-height: var(--ibou-typography-label-sm-line-height);
    letter-spacing: var(--ibou-typography-label-sm-letter-spacing);
    text-transform: uppercase;
  }

  .ibou-text-tag-md {
    font-family: var(--ibou-typography-tag-md-font-family);
    font-weight: var(--ibou-typography-tag-md-font-weight);
    font-size: var(--ibou-typography-tag-md-font-size);
    line-height: var(--ibou-typography-tag-md-line-height);
    letter-spacing: var(--ibou-typography-tag-md-letter-spacing);
  }

  .ibou-text-tag-sm {
    font-family: var(--ibou-typography-tag-sm-font-family);
    font-weight: var(--ibou-typography-tag-sm-font-weight);
    font-size: var(--ibou-typography-tag-sm-font-size);
    line-height: var(--ibou-typography-tag-sm-line-height);
    letter-spacing: var(--ibou-typography-tag-sm-letter-spacing);
  }

  .ibou-text-caption {
    font-family: var(--ibou-typography-caption-font-family);
    font-weight: var(--ibou-typography-caption-font-weight);
    font-size: var(--ibou-typography-caption-font-size);
    line-height: var(--ibou-typography-caption-line-height);
    letter-spacing: var(--ibou-typography-caption-letter-spacing);
  }

  .ibou-text-overline {
    font-family: var(--ibou-typography-overline-font-family);
    font-weight: var(--ibou-typography-overline-font-weight);
    font-size: var(--ibou-typography-overline-font-size);
    line-height: var(--ibou-typography-overline-line-height);
    letter-spacing: var(--ibou-typography-overline-letter-spacing);
    text-transform: uppercase;
  }

  .ibou-text-service {
    font-family: var(--ibou-typography-service-font-family);
    font-weight: var(--ibou-typography-service-font-weight);
    font-size: var(--ibou-typography-service-font-size);
    line-height: var(--ibou-typography-service-line-height);
    letter-spacing: var(--ibou-typography-service-letter-spacing);
    font-style: italic;
  }

  .ibou-text-figure-lg {
    font-family: var(--ibou-typography-figure-lg-font-family);
    font-weight: var(--ibou-typography-figure-lg-font-weight);
    font-size: var(--ibou-typography-figure-lg-font-size);
    line-height: var(--ibou-typography-figure-lg-line-height);
    letter-spacing: var(--ibou-typography-figure-lg-letter-spacing);
  }

  .ibou-text-figure-md {
    font-family: var(--ibou-typography-figure-md-font-family);
    font-weight: var(--ibou-typography-figure-md-font-weight);
    font-size: var(--ibou-typography-figure-md-font-size);
    line-height: var(--ibou-typography-figure-md-line-height);
    letter-spacing: var(--ibou-typography-figure-md-letter-spacing);
  }

  .ibou-text-code {
    font-family: var(--ibou-typography-code-font-family);
    font-weight: var(--ibou-typography-code-font-weight);
    font-size: var(--ibou-typography-code-font-size);
    line-height: var(--ibou-typography-code-line-height);
    letter-spacing: var(--ibou-typography-code-letter-spacing);
  }
}

/* ——— build/css/base.css ——— */
/* base.css : reset, body, focus, utilitaires (copie de src/css/base.css) — ibou design system v2.0.0, généré par scripts/build.mjs. */
/* base.css — couche de base ibou. Chargée après les tokens, avant les composants.
   Reset minimal, page, focus (D-13), utilitaires, mouvement réduit, contraste élevé. */

/* Ordre des couches, déclaré ici une fois pour toutes : il ne dépend donc plus de l'ordre
   de concaténation des fichiers. Les utilitaires viennent AVANT les composants, à rebours
   de l'habitude Tailwind : ce ne sont pas des surcharges de dernier recours mais des aides
   partagées (.ibou-spinner, .ibou-hit, .ibou-poster) que les composants ont le droit
   d'ajuster — la barre de recherche masque le spinner au repos, le bouton le redimensionne.
   Inverser les deux couches laisse le spinner tourner en permanence. .ibou-visually-hidden
   porte !important : elle l'emporte de toute façon, quelle que soit la couche. */
@layer ibou.base, ibou.utilities, ibou.components;

@layer ibou.base {
  *, *::before, *::after { box-sizing: border-box; }

  html {
    -webkit-text-size-adjust: 100%;
    /* Réserve des barres collantes (04 · Mobile d'abord, 11 · Focus jamais masqué, WCAG 2.4.12) : ce qui colle
       en haut, la barre et son trait, 58 px puis 66 à partir de breakpoint.md (D-69 : seule la barre colle ;
       la seconde ligne, sœur du header, se gare sous elle et n'en laisse dépasser que son trait, de la même
       épaisseur que le filet de la barre) ; la barre basse quand la page en fixe une ; rien pour une barre
       `data-static` ; zéro sous 480 px de haut, où les barres se détachent (nav.css). Les deux propriétés
       sont l'API : un élément collant s'aligne sur --ibou-sticky-top (jamais sur la hauteur mesurée de
       .ibou-nav), une application dont la barre diffère les redéfinit. */
    --ibou-sticky-top: calc(var(--ibou-nav-height-mobile) + var(--ibou-nav-border-width));
    --ibou-sticky-bottom: 0px;
    /* Place réservée à la pile de toasts, pour les éléments fixés en bas qui partagent son retrait
       (retour en haut, bouton flottant, bandeau de nouvelle version) : ils ajoutent cette propriété
       à leur `inset-block-end` et la pile ne les recouvre plus. La pile ne cède, elle, qu'à la barre
       basse (07 · Toast, « Qui cède le passage »). Une réserve plus courte qu'un toast réel ne
       protège rien : c'est donc le plafond d'UN toast que la valeur réserve, tel que l'anatomie du
       chapitre 07 le borne — ses deux paddings, puis la plus haute de ses deux rangées possibles,
       le message à deux lignes (son maximum) ou le bouton d'action (`button.height.sm`) —, plus
       l'écart de pile. La hauteur ne vient PAS de `size.icon.md` : c'est la largeur des yeux, hauts
       de 14 px pour 24 de large (D-28, rapport 188,14 / 112,35), et ils ne mènent jamais la rangée.
       Une application qui empile deux ou trois toasts au-dessus d'un contrôle fixé remplace la
       valeur par la hauteur mesurée de `.ibou-toasts`, sur `document.documentElement` — le nom, lui,
       ne s'invente plus. */
    --ibou-toast-stack-height: 0px;
    scroll-padding-top: calc(var(--ibou-sticky-top) + var(--ibou-space-4));
    scroll-padding-bottom: var(--ibou-sticky-bottom);
    /* Jamais `smooth` (D-50) : un saut vers une source, ou un défilement déclenché par le
       déplacement du focus, est un geste explicite de l'application et n'a pas à être adouci.
       Le défaut est ancien et il coûtait plus qu'un effet : la mesure de reflow du harnais
       d'accessibilité défilait puis lisait la position avant la fin de l'animation, et rendait
       zéro sur des pages qui débordent réellement. */
    scroll-behavior: auto;
  }
  /* DÉPRÉCIÉ (D-69) : seconde ligne DANS le header d'une barre qui colle, l'ancien balisage : les deux lignes
     collent, la réserve compte chaque rangée (filet et 44 px) : 103 px puis 111, 148 et 156 avec deux rangées
     (la question et les verticales de la Recherche, sur une seconde ligne ou deux) ; elle n'en comptait qu'une,
     et une ancre tombait sous l'en-tête. La seconde ligne sœur du header ne correspond pas. */
  html:has(.ibou-nav:not([data-static]) .ibou-nav__second) { --ibou-nav-legacy-rows: 1; --ibou-sticky-top: calc(var(--ibou-nav-height-mobile) + var(--ibou-nav-legacy-rows) * (var(--ibou-border-width-hairline) + var(--ibou-tab-height)) + var(--ibou-nav-border-width)); }
  html:has(.ibou-nav:not([data-static]) :is(.ibou-nav__second > .ibou-nav__row + .ibou-nav__row, .ibou-nav__second + .ibou-nav__second)) { --ibou-nav-legacy-rows: 2; }
  /* Une page dont les barres sont toutes statiques (accueil sans fil, démonstrations du styleguide) ne réserve
     rien : elle réservait une rangée pour une barre qui ne collait pas. */
  html:has(.ibou-nav[data-static]):not(:has(.ibou-nav:not([data-static]))) { --ibou-sticky-top: 0px; }
  html:has(.ibou-tabbar[data-fixed]) { --ibou-sticky-bottom: calc(var(--ibou-nav-height-mobile) + env(safe-area-inset-bottom)); }
  html:has(.ibou-toasts:not([data-static]) > *) { --ibou-toast-stack-height: calc(2 * var(--ibou-toast-padding) + max(2 * var(--ibou-typography-body-sm-font-size) * var(--ibou-typography-body-sm-line-height), var(--ibou-button-height-sm)) + var(--ibou-spacing-stack-sm)); }
  @media (min-width: 768px) { /* breakpoint.md : la barre basse disparaît, sauf data-always */
    html { --ibou-sticky-top: calc(var(--ibou-nav-height) + var(--ibou-nav-border-width)); }
    html:has(.ibou-nav:not([data-static]) .ibou-nav__second) { --ibou-sticky-top: calc(var(--ibou-nav-height) + var(--ibou-nav-legacy-rows) * (var(--ibou-border-width-hairline) + var(--ibou-tab-height)) + var(--ibou-nav-border-width)); }
    html:has(.ibou-tabbar[data-fixed]:not([data-always])) { --ibou-sticky-bottom: 0px; }
  }
  @media (max-height: 480px) { /* barres détachées (nav.css) */
    html, html:has(.ibou-nav:not([data-static]) .ibou-nav__second), html:has(.ibou-tabbar[data-fixed]) { --ibou-sticky-top: 0px; --ibou-sticky-bottom: 0px; }
  }

  body {
    margin: 0;
    background: var(--ibou-color-bg-canvas);
    color: var(--ibou-color-text-primary);
    font-family: var(--ibou-typography-body-md-font-family);
    font-size: var(--ibou-typography-body-md-font-size);
    line-height: var(--ibou-typography-body-md-line-height);
    letter-spacing: var(--ibou-typography-body-md-letter-spacing);
    font-weight: var(--ibou-typography-body-md-font-weight);
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
  }

  h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
  h1 { font: var(--ibou-typography-heading-1-font-weight) var(--ibou-typography-heading-1-font-size) / var(--ibou-typography-heading-1-line-height) var(--ibou-typography-heading-1-font-family); letter-spacing: var(--ibou-typography-heading-1-letter-spacing); }
  h2 { font: var(--ibou-typography-heading-2-font-weight) var(--ibou-typography-heading-2-font-size) / var(--ibou-typography-heading-2-line-height) var(--ibou-typography-heading-2-font-family); letter-spacing: var(--ibou-typography-heading-2-letter-spacing); }
  h3 { font: var(--ibou-typography-heading-3-font-weight) var(--ibou-typography-heading-3-font-size) / var(--ibou-typography-heading-3-line-height) var(--ibou-typography-heading-3-font-family); letter-spacing: var(--ibou-typography-heading-3-letter-spacing); }
  h4 { font: var(--ibou-typography-heading-4-font-weight) var(--ibou-typography-heading-4-font-size) / var(--ibou-typography-heading-4-line-height) var(--ibou-typography-heading-4-font-family); letter-spacing: var(--ibou-typography-heading-4-letter-spacing); }
  strong, b { font-weight: var(--ibou-font-weight-semibold); }
  small { font-size: var(--ibou-typography-caption-font-size); }
  code, kbd, pre { font-family: var(--ibou-font-family-mono); font-size: var(--ibou-typography-code-font-size); }
  mark { background: var(--ibou-color-surface-highlight); color: inherit; border-radius: var(--ibou-radius-xs); padding-inline: 0.1em; }
  hr { border: 0; border-top: var(--ibou-divider-width) solid var(--ibou-color-border-subtle); margin: 0; }
  img, svg, video { max-width: 100%; height: auto; display: block; }
  ul, ol { padding-inline-start: var(--ibou-space-5); margin: 0; }

  /* Liens : toujours soulignés, la couleur ne porte jamais seule le lien (02-couleur).
     Chaque état lit d'abord la propriété que pose une surface restée claire dans les deux thèmes
     (`--ibou-action-link*`, card chaude) : le survol et le lien externe gardaient text.link-hover
     et text.link-external, rose 300 et vert 300 sur beige 500 en sombre (1,07:1 et 1,09:1).
     Le lien sortant vers une source lit ensuite le ton des sources du service (D-71) :
     `--ibou-service-source-text`, que seul un service à dominante verte (ibou Découverte) règle,
     sur text.link-external. Sans réglage, il est neutre, text.link : pas de vert hors Découverte. */
  a { color: var(--ibou-action-link, var(--ibou-color-text-link)); text-decoration: underline; text-underline-offset: 0.15em; text-decoration-thickness: 1px; }
  @media (hover: hover) { a:hover { color: var(--ibou-action-link-hover, var(--ibou-color-text-link-hover)); } }
  a:visited { color: var(--ibou-action-link-visited, var(--ibou-color-text-link-visited)); }
  a[rel~="external"], a[data-external] { color: var(--ibou-action-link-external, var(--ibou-service-source-text, var(--ibou-color-text-link))); }

  /* Contrôles : on part d'une base nue, les composants habillent */
  button, input, select, textarea { font: inherit; letter-spacing: inherit; color: inherit; margin: 0; }
  button { appearance: none; -webkit-appearance: none; background: none; border: 0; padding: 0; cursor: pointer; }
  button:disabled { cursor: not-allowed; }
  [hidden] { display: none !important; }

  /* Focus : double anneau (D-13). L'anneau touche l'élément, le halo touche la surface. */
  /* L'anneau de focus est double : un halo côté élément, un anneau côté surface, pour qu'au
     moins un des deux bords ressorte quel que soit le fond. Le couple est porté par deux
     variables héritées plutôt que par des sélecteurs de descendance : n'importe quel conteneur
     le redéfinit pour lui et ses enfants, un conteneur imbriqué reprend la main sans bataille
     de spécificité, et un composant dont la surface est sombre alors que son rôle ne l'est pas
     peut corriger le couple seul (voir la card d'alerte en erreur, alert.css). */
  :root {
    --ibou-focus-ring: var(--ibou-color-border-focus);
    --ibou-focus-halo: var(--ibou-color-border-focus-halo);
  }
  :focus-visible {
    outline: var(--ibou-focus-ring-width) solid var(--ibou-focus-ring);
    outline-offset: var(--ibou-focus-ring-offset);
    box-shadow: 0 0 0 calc(var(--ibou-focus-ring-width) + var(--ibou-focus-ring-offset)) var(--ibou-focus-halo);
  }
  :focus:not(:focus-visible) { outline: none; box-shadow: none; }

  /* Une couche flottante (popover, menu, modale, feuille) peint sa propre surface, `surface.raised`,
     qui suit le thème : elle ne peut pas hériter de ce qu'une surface forte a réglé pour ses enfants.
     Un popover ouvert depuis une card chaude en sombre montrait sinon ses liens anthracite sur
     anthracite (1,00:1) et des boutons blancs à trait anthracite invisible. `initial` rend chaque
     composant à sa couleur de repli ; l'anneau revient au couple par défaut. Placée avant les règles
     `data-surface` : une couche qui déclare elle-même une surface garde la main. `npm run check:css`
     vérifie que chaque propriété posée par une surface est remise ici. */
  :where(.ibou-popover, .ibou-menu, .ibou-modal, .ibou-sheet) {
    --ibou-focus-ring: var(--ibou-color-border-focus);
    --ibou-focus-halo: var(--ibou-color-border-focus-halo);
    --ibou-action-bg: initial; --ibou-action-bg-hover: initial; --ibou-action-bg-active: initial;
    --ibou-action-border: initial; --ibou-action-text: initial;
    --ibou-action-secondary-bg: initial; --ibou-action-secondary-bg-hover: initial; --ibou-action-secondary-bg-active: initial;
    --ibou-action-secondary-border: initial; --ibou-action-secondary-text: initial;
    --ibou-action-tertiary-text: initial; --ibou-action-tertiary-text-hover: initial;
    --ibou-action-tertiary-underline: initial; --ibou-action-tertiary-bg-hover: initial;
    --ibou-action-icon-bg: initial; --ibou-action-icon-bg-hover: initial; --ibou-action-icon-border: initial; --ibou-action-icon-color: initial;
    --ibou-action-link: initial; --ibou-action-link-hover: initial; --ibou-action-link-visited: initial; --ibou-action-link-external: initial;
  }

  /* Surface sombre : anneau clair, halo anthracite. */
  [data-surface="inverse"] {
    --ibou-focus-ring: var(--ibou-color-border-focus-inverse);
    --ibou-focus-halo: var(--ibou-color-border-focus);
    /* Les actions (architecture, lot 2). Une surface règle la couleur par défaut de ce qu'elle
       porte ; les composants lisent ces propriétés avec leur couleur habituelle en repli. Une
       variante explicite (`data-variant`) l'emporte toujours sur la surface.
       Rien ici pour le bouton texte par défaut : sur une surface inversée le balisage emploie
       `data-tone="inverse"`, et un bouton primaire y reste lisible. Le tertiaire, lui, serait
       sombre sur sombre sans ces propriétés : c'est un cas supporté, pas du code mort. */
    --ibou-action-tertiary-text: var(--ibou-color-text-inverse);
    --ibou-action-tertiary-text-hover: var(--ibou-color-action-inverse-text);
    --ibou-action-tertiary-underline: var(--ibou-color-border-inverse);
    --ibou-action-tertiary-bg-hover: var(--ibou-color-action-inverse-bg-hover);
    --ibou-action-icon-bg: transparent;
    --ibou-action-icon-bg-hover: var(--ibou-color-action-inverse-bg-hover);
    --ibou-action-icon-border: var(--ibou-color-border-inverse);
    --ibou-action-icon-color: var(--ibou-color-icon-inverse);
  }

  /* Surface claire de marque ou d'état fort, dans les deux thèmes : anneau anthracite, halo beige 50. */
  [data-surface="brand"], [data-surface="strong"] {
    --ibou-focus-ring: var(--ibou-color-border-focus-on-brand);
    --ibou-focus-halo: var(--ibou-color-border-focus-halo-on-brand);
    /* Tout bouton sans variante y prend les tokens action.on-strong.* : la surface reste claire
       dans les deux thèmes, fond blanc, trait et texte anthracite. */
    --ibou-action-bg: var(--ibou-color-action-on-strong-bg);
    --ibou-action-bg-hover: var(--ibou-color-action-on-strong-bg-hover);
    --ibou-action-bg-active: var(--ibou-color-action-on-strong-bg-active);
    --ibou-action-border: var(--ibou-color-action-on-strong-border);
    --ibou-action-text: var(--ibou-color-action-on-strong-text);
    --ibou-action-tertiary-text: var(--ibou-color-action-on-strong-text);
    --ibou-action-tertiary-text-hover: var(--ibou-color-action-on-strong-text);
    --ibou-action-tertiary-underline: var(--ibou-color-action-on-strong-text);
    --ibou-action-tertiary-bg-hover: var(--ibou-color-action-on-strong-bg-hover);
    /* Le secondaire aussi. Transparent à texte et trait beige 50 en sombre, il tombait à 1,37:1 sur une
       card beige 500 et à 1,5:1 sur rose 400 (02-couleur : « c'est action.on-strong qui s'y pose »).
       Sa forme ne change pas — pilule bordée —, seules ses couleurs suivent la surface ; en clair,
       on-strong et secondaire ont les mêmes valeurs, rien ne bouge. */
    --ibou-action-secondary-bg: var(--ibou-color-action-on-strong-bg);
    --ibou-action-secondary-bg-hover: var(--ibou-color-action-on-strong-bg-hover);
    --ibou-action-secondary-bg-active: var(--ibou-color-action-on-strong-bg-active);
    --ibou-action-secondary-border: var(--ibou-color-action-on-strong-border);
    --ibou-action-secondary-text: var(--ibou-color-action-on-strong-text);
  }

  /* Sélection de texte */
  ::selection { background: var(--ibou-color-surface-brand-muted); color: var(--ibou-color-text-primary); }

  /* Voile de modale : sur <dialog> seulement. Un `::backdrop` sans sujet voilait aussi la couche
     supérieure d'un élément `popover` (anthracite à 60 % sur toute la page), alors qu'un popover ne
     bloque jamais la page (07 · Popover). La modale et la feuille règlent le leur (modal.css, sheet.css). */
  dialog::backdrop { background: var(--ibou-color-overlay-scrim); }
}

@layer ibou.utilities {
  /* Masqué visuellement, lu par les lecteurs d'écran */
  .ibou-visually-hidden { position: absolute !important; 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; }

  /* Le nom « ibou » ne prend jamais de capitales, même dans un bouton en capitales (D-02, D-42) */
  .ibou-brand-name { text-transform: none; }

  /* Cible tactile de 44 px par débordement invisible (11-accessibilité) */
  .ibou-hit { position: relative; }
  .ibou-hit::before { content: ""; position: absolute; inset: 50% auto auto 50%; translate: -50% -50%; min-width: var(--ibou-size-target-min); min-height: var(--ibou-size-target-min); width: 100%; height: 100%; }

  /* Indicateur de chargement : anneau fin, jamais « génération » (D-36) */
  .ibou-spinner { display: inline-block; width: var(--ibou-size-icon-sm); height: var(--ibou-size-icon-sm); border: var(--ibou-border-width-thin) solid currentColor; border-right-color: transparent; border-radius: var(--ibou-radius-full); animation: ibou-spin var(--ibou-motion-duration-spin) linear infinite; vertical-align: middle; }
  @keyframes ibou-spin { to { rotate: 1turn; } }

  /* Registre affiche : la typographie court jusqu'aux bords (03-typographie) */
  .ibou-poster { overflow: hidden; text-wrap: balance; }

  /* Zone défilante accessible au clavier */
  .ibou-scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .ibou-scroll-x:focus-visible { outline-offset: calc(-1 * var(--ibou-focus-ring-width)); }

  /* Icône de trait autonome (06-iconographie, D-34).
     Dans un composant, l'icône n'a pas besoin de cette classe : le composant la dimensionne
     lui-même (`.ibou-btn > svg`, `.ibou-menu__item > svg`…), et c'est le cas normal. Cette
     classe sert à l'icône qui n'est dans aucun composant : dans une prose, dans une grille, dans
     un composant qu'on écrit soi-même. Le chapitre 06 en décrivait trois versions concurrentes
     et n'en livrait aucune ; celle-ci est la seule, et elle est livrée. */
  .ibou-icon { display: inline-block; vertical-align: middle; flex: none; width: var(--ibou-size-icon-md); height: var(--ibou-size-icon-md); color: currentColor; }
  .ibou-icon[data-size="xs"] { width: var(--ibou-size-icon-xs); height: var(--ibou-size-icon-xs); }
  .ibou-icon[data-size="sm"] { width: var(--ibou-size-icon-sm); height: var(--ibou-size-icon-sm); }
  .ibou-icon[data-size="lg"] { width: var(--ibou-size-icon-lg); height: var(--ibou-size-icon-lg); }
  .ibou-icon[data-size="xl"] { width: var(--ibou-size-icon-xl); height: var(--ibou-size-icon-xl); }
  .ibou-icon[data-tone="primary"]   { color: var(--ibou-color-icon-primary); }
  .ibou-icon[data-tone="secondary"] { color: var(--ibou-color-icon-secondary); }
  .ibou-icon[data-tone="disabled"]  { color: var(--ibou-color-icon-disabled); }
  .ibou-icon[data-tone="inverse"]   { color: var(--ibou-color-icon-inverse); }
  .ibou-icon[data-tone="brand"]     { color: var(--ibou-color-icon-brand); }
  .ibou-icon[data-tone="discover"]  { color: var(--ibou-color-icon-discover); }
  /* Les lunettes ne sont pas carrées : viewBox 188,14 × 112,35 (D-28) */
  .ibou-icon[data-glasses] { width: var(--ibou-size-icon-lg); height: auto; aspect-ratio: 188.14 / 112.35; }
}

/* Bascule de thème : aucune transition, jamais (10-motion, D-50).
   Le chapitre l'interdit nommément, et le défaut était mesurable : pendant la fondu des
   couleurs, le titre d'une card de source passait par un contraste de 1,03:1 sur environ 40 ms.
   C'est le seul endroit du web où le CSS ne suffit pas : l'application pose `data-theme-switching`
   sur `<html>` AVANT de changer le thème, et le retire après deux images
   (`requestAnimationFrame` imbriqué). À écrire dans les prérequis d'intégration.
   Ce que cela coûte, et il faut le savoir : le `!important` fige aussi, le temps d'une image,
   toute transition en cours ailleurs — au premier chef le curseur de l'interrupteur qui sert
   justement de commande de thème. */
html[data-theme-switching] *, html[data-theme-switching] *::before, html[data-theme-switching] *::after {
  transition: none !important;
}

/* Mouvement réduit (10-motion, D-50).
   Ce qui bouge s'arrête ; ce qui change de couleur garde sa durée. WCAG 2.3.3 vise le mouvement,
   pas les fondus de teinte, et une interface qui change de couleur d'un coup n'est pas plus
   accessible — elle est seulement plus brutale. La règle ne coupe donc pas les transitions :
   elle réduit la liste des propriétés transitionnables aux propriétés de couleur et d'opacité.
   Tout ce qui déplace (`transform`, `translate`, `scale`, `rotate`, `inset`, `margin`…) sort de
   la liste, donc s'applique d'un coup.
   Cet amendement suppose la règle de bascule de thème ci-dessus : sans elle, c'était la coupe
   globale qui rendait la bascule instantanée sous mouvement réduit. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-property: color, background-color, border-color, outline-color, text-decoration-color, box-shadow, opacity, fill, stroke !important;
    scroll-behavior: auto !important;
  }
  .ibou-spinner { animation: none; border-right-color: currentColor; opacity: var(--ibou-opacity-muted); }
}

/* Contraste élevé (Windows) : les box-shadow disparaissent, l'outline reste */
@media (forced-colors: active) {
  :focus-visible { outline: 2px solid Highlight; box-shadow: none; }
  .ibou-spinner { border-color: CanvasText; border-right-color: transparent; }
  ::selection { background: Highlight; color: HighlightText; }
}

/* ——— src/css/components/button.css ——— */
/* button.css — Bouton (07 · Bouton, D-18, D-30, D-42).
   Reset des <button> et focus : base.css. Variantes par data-variant, tailles par data-size.
   Cible tactile 44 px : min-height md/lg, débordement invisible (::before) pour sm. */
@layer ibou.components {
  /* L'API de couleur du bouton (architecture, lot 2). Six propriétés locales ; chaque variante
     ne fait que les régler, et une seule règle par état les lit. Avant, chaque variante répétait
     son triplet repos, survol, appui — dix-huit règles pour dire six fois la même chose.
     La variante par défaut et la tertiaire lisent en plus les propriétés héritées de la surface
     qui les porte (`--ibou-action-*`, posées par `data-surface` dans base.css), avec la couleur
     habituelle en repli : un contexte règle des tokens, il ne repeint jamais le bouton. */
  .ibou-btn {
    --ibou-btn-bg: var(--ibou-action-bg, var(--ibou-color-action-primary-bg));
    --ibou-btn-bg-hover: var(--ibou-action-bg-hover, var(--ibou-color-action-primary-bg-hover));
    --ibou-btn-bg-active: var(--ibou-action-bg-active, var(--ibou-color-action-primary-bg-active));
    --ibou-btn-border: var(--ibou-action-border, var(--ibou-color-action-primary-border));
    --ibou-btn-text: var(--ibou-action-text, var(--ibou-color-action-primary-text));
    --ibou-btn-text-hover: var(--ibou-btn-text);
    position: relative;
    display: inline-flex; align-items: center; justify-content: center; gap: var(--ibou-button-gap);
    min-height: var(--ibou-button-height-md);
    padding: var(--ibou-space-2) var(--ibou-button-padding-x-md);
    border: var(--ibou-button-border-width) solid var(--ibou-btn-border);
    border-radius: var(--ibou-button-radius);
    background: var(--ibou-btn-bg);
    color: var(--ibou-btn-text);
    font-family: var(--ibou-typography-label-md-font-family);
    font-size: var(--ibou-typography-label-md-font-size);
    font-weight: var(--ibou-typography-label-md-font-weight);
    letter-spacing: var(--ibou-typography-label-md-letter-spacing);
    line-height: var(--ibou-font-line-height-tight);
    text-transform: uppercase; text-decoration: none; text-align: center; white-space: nowrap;
    transition: background-color var(--ibou-motion-duration-fast) var(--ibou-motion-easing-standard);
  }
  @media (hover: hover) { .ibou-btn:hover { background: var(--ibou-btn-bg-hover); color: var(--ibou-btn-text-hover); } }
  .ibou-btn:active { background: var(--ibou-btn-bg-active); }

  /* Le secondaire lit, lui aussi, la surface qui le porte (`--ibou-action-secondary-*`, posées par data-surface="brand|strong") :
     sur une surface forte, il prend les couleurs action.on-strong.* au lieu de tomber à 1,37:1 en sombre. */
  .ibou-btn[data-variant="secondary"] { --ibou-btn-bg: var(--ibou-action-secondary-bg, var(--ibou-color-action-secondary-bg)); --ibou-btn-bg-hover: var(--ibou-action-secondary-bg-hover, var(--ibou-color-action-secondary-bg-hover)); --ibou-btn-bg-active: var(--ibou-action-secondary-bg-active, var(--ibou-color-action-secondary-bg-active)); --ibou-btn-border: var(--ibou-action-secondary-border, var(--ibou-color-action-secondary-border)); --ibou-btn-text: var(--ibou-action-secondary-text, var(--ibou-color-action-secondary-text)); }
  .ibou-btn[data-tone="discover"] { --ibou-btn-bg: var(--ibou-color-action-discover-bg); --ibou-btn-bg-hover: var(--ibou-color-action-discover-bg-hover); --ibou-btn-bg-active: var(--ibou-color-action-discover-bg-active); --ibou-btn-border: var(--ibou-color-action-discover-border); --ibou-btn-text: var(--ibou-color-action-discover-text); }
  .ibou-btn[data-tone="inverse"] { --ibou-btn-bg: var(--ibou-color-action-inverse-bg); --ibou-btn-bg-hover: var(--ibou-color-action-inverse-bg-hover); --ibou-btn-bg-active: var(--ibou-color-action-inverse-bg-active); --ibou-btn-border: var(--ibou-color-action-inverse-border); --ibou-btn-text: var(--ibou-color-action-inverse-text); }
  /* Sur une surface forte (card d'alerte rose 400 / vert 400, aplat rose 500) : fond blanc, trait et texte anthracite,
     identiques dans les deux thèmes parce que la surface reste claire (tokens action.on-strong.*). */
  .ibou-btn[data-tone="on-strong"] { --ibou-btn-bg: var(--ibou-color-action-on-strong-bg); --ibou-btn-bg-hover: var(--ibou-color-action-on-strong-bg-hover); --ibou-btn-bg-active: var(--ibou-color-action-on-strong-bg-active); --ibou-btn-border: var(--ibou-color-action-on-strong-border); --ibou-btn-text: var(--ibou-color-action-on-strong-text); }

  .ibou-btn[data-variant="tertiary"] {
    --ibou-btn-bg: var(--ibou-color-action-tertiary-bg);
    --ibou-btn-bg-hover: var(--ibou-action-tertiary-bg-hover, var(--ibou-color-action-tertiary-bg-hover));
    --ibou-btn-bg-active: var(--ibou-color-action-tertiary-bg-active);
    --ibou-btn-text: var(--ibou-action-tertiary-text, var(--ibou-color-action-tertiary-text));
    --ibou-btn-text-hover: var(--ibou-action-tertiary-text-hover, var(--ibou-btn-text));
    min-height: 0; padding: var(--ibou-space-1) var(--ibou-space-2);
    border: 0; border-radius: var(--ibou-radius-xs);
    text-decoration: underline; text-decoration-thickness: var(--ibou-border-width-thin);
    text-decoration-color: var(--ibou-action-tertiary-underline, var(--ibou-color-action-tertiary-underline));
    text-underline-offset: var(--ibou-button-underline-offset);
  }
  @media (hover: hover) { .ibou-btn[data-variant="tertiary"]:hover { text-decoration-thickness: var(--ibou-border-width-medium); } }

  .ibou-btn[data-size="sm"] { min-height: var(--ibou-button-height-sm); padding-inline: var(--ibou-button-padding-x-sm); font-size: var(--ibou-typography-label-sm-font-size); letter-spacing: var(--ibou-typography-label-sm-letter-spacing); }
  .ibou-btn[data-size="lg"] { min-height: var(--ibou-button-height-lg); padding-inline: var(--ibou-button-padding-x-lg); font-size: var(--ibou-typography-label-lg-font-size); letter-spacing: var(--ibou-typography-label-lg-letter-spacing); }
  /* Cible 44 px pour la taille sm (même mécanisme que .ibou-hit) */
  .ibou-btn[data-size="sm"]::before, .ibou-btn[data-variant="tertiary"]::before { content: ""; position: absolute; inset: 50% auto auto 50%; translate: -50% -50%; min-width: var(--ibou-size-target-min); min-height: var(--ibou-size-target-min); width: 100%; height: 100%; }

  .ibou-btn:disabled, .ibou-btn[aria-disabled="true"] {
    background: var(--ibou-color-action-disabled-bg); border-color: var(--ibou-color-action-disabled-border);
    color: var(--ibou-color-action-disabled-text); text-decoration-color: var(--ibou-color-action-disabled-border);
  }
  .ibou-btn[data-variant="tertiary"]:disabled, .ibou-btn[data-variant="tertiary"][aria-disabled="true"] { background: transparent; }
  .ibou-btn[aria-busy="true"] { pointer-events: none; }
  .ibou-btn > svg { inline-size: var(--ibou-size-icon-sm); block-size: var(--ibou-size-icon-sm); flex: none; }
  .ibou-btn[data-size="lg"] > svg { inline-size: var(--ibou-size-icon-md); block-size: var(--ibou-size-icon-md); }
  .ibou-btn .ibou-spinner { inline-size: var(--ibou-size-icon-sm); block-size: var(--ibou-size-icon-sm); }
  .ibou-btn[data-block] { display: flex; inline-size: 100%; }

  /* Bouton d'icône rond (menu de la nav, actions d'une source). Même API que le bouton texte.
     L'état pressé passe aussi par les propriétés : un « Garder » pressé qu'on presse à nouveau
     montre l'appui de sa variante, comme avant, et non le fond de sélection. */
  .ibou-icon-btn {
    --ibou-icon-btn-bg: var(--ibou-action-icon-bg, var(--ibou-color-surface-brand));
    --ibou-icon-btn-bg-hover: var(--ibou-action-icon-bg-hover, var(--ibou-color-action-primary-bg-hover));
    --ibou-icon-btn-bg-active: var(--ibou-color-action-primary-bg-active);
    --ibou-icon-btn-border: var(--ibou-action-icon-border, var(--ibou-color-border-strong));
    --ibou-icon-btn-icon: var(--ibou-action-icon-color, var(--ibou-color-icon-on-brand));
    display: inline-grid; place-items: center; flex: none;
    inline-size: var(--ibou-icon-button-size); block-size: var(--ibou-icon-button-size);
    border: var(--ibou-button-border-width) solid var(--ibou-icon-btn-border);
    border-radius: var(--ibou-icon-button-radius);
    background: var(--ibou-icon-btn-bg); color: var(--ibou-icon-btn-icon);
    transition: background-color var(--ibou-motion-duration-fast) var(--ibou-motion-easing-standard);
  }
  @media (hover: hover) { .ibou-icon-btn:hover { background: var(--ibou-icon-btn-bg-hover); } }
  .ibou-icon-btn:active { background: var(--ibou-icon-btn-bg-active); }
  .ibou-icon-btn[data-variant="secondary"] { --ibou-icon-btn-bg: var(--ibou-color-action-secondary-bg); --ibou-icon-btn-bg-hover: var(--ibou-color-action-secondary-bg-hover); --ibou-icon-btn-bg-active: var(--ibou-color-action-secondary-bg-active); --ibou-icon-btn-icon: var(--ibou-color-icon-primary); }
  .ibou-icon-btn[data-tone="discover"] { --ibou-icon-btn-bg: var(--ibou-color-action-discover-bg); --ibou-icon-btn-bg-hover: var(--ibou-color-action-discover-bg-hover); --ibou-icon-btn-icon: var(--ibou-color-icon-on-discover); }
  .ibou-icon-btn[data-variant="ghost"] { --ibou-icon-btn-bg: transparent; --ibou-icon-btn-bg-hover: var(--ibou-color-action-tertiary-bg-hover); --ibou-icon-btn-border: transparent; --ibou-icon-btn-icon: var(--ibou-color-icon-primary); }
  .ibou-icon-btn[aria-pressed="true"] { --ibou-icon-btn-bg: var(--ibou-color-action-selected-bg); --ibou-icon-btn-border: var(--ibou-color-action-selected-border); --ibou-icon-btn-icon: var(--ibou-color-icon-primary); }
  /* Lien d'icône vers la page courante d'un groupe : l'état sélectionné, porté par `aria-current="page"`
     puisque ce sont des liens, jamais `aria-pressed`. Il mène là où l'on est déjà : ni le survol ni l'appui
     ne le changent. Venu de D-68 ; aucun produit ne l'emploie plus, ni le choix de vue d'un fil depuis le
     2026-09-25 (deux icônes nues, view-choice.css), ni la loupe d'ibou Actualités sur sa page de recherche
     depuis le 2026-09-26 (D-70 amendé : plus de loupe dans sa barre). Gardé, sans exemple : le retirer
     casserait un produit qui le poserait ; réexaminé à la prochaine version majeure. */
  .ibou-icon-btn[aria-current="page"] {
    --ibou-icon-btn-bg: var(--ibou-color-action-selected-bg); --ibou-icon-btn-bg-hover: var(--ibou-color-action-selected-bg); --ibou-icon-btn-bg-active: var(--ibou-color-action-selected-bg);
    --ibou-icon-btn-border: var(--ibou-color-action-selected-border); --ibou-icon-btn-icon: var(--ibou-color-icon-primary);
  }
  .ibou-icon-btn:disabled { background: var(--ibou-color-action-disabled-bg); border-color: var(--ibou-color-action-disabled-border); color: var(--ibou-color-icon-disabled); }
  .ibou-icon-btn > svg { inline-size: var(--ibou-size-icon-md); block-size: var(--ibou-size-icon-md); }
  .ibou-icon-btn[data-size="sm"] { inline-size: var(--ibou-size-control-sm); block-size: var(--ibou-size-control-sm); }
  .ibou-icon-btn[data-size="sm"] > svg { inline-size: var(--ibou-size-icon-sm); block-size: var(--ibou-size-icon-sm); }
  .ibou-icon-btn[data-size="sm"]::before { content: ""; position: absolute; inset: 50% auto auto 50%; translate: -50% -50%; min-width: var(--ibou-size-target-min); min-height: var(--ibou-size-target-min); }
  .ibou-icon-btn[data-size="sm"] { position: relative; }
  /* La pression est instantanée (D-50) : le fond hérite sinon de la transition de 120 ms et
     l'appui traîne derrière le doigt. Une réserve va avec, elle est au chapitre 10 : cela ne se
     verra que sur le bouton primaire, l'écart de `action.secondary.bg-active` avec son fond au
     repos étant trop faible pour qu'un appui produise quoi que ce soit à l'œil. */
  .ibou-btn:active, .ibou-icon-btn:active { transition-duration: var(--ibou-motion-duration-instant); }
  /* Couleurs forcées (D-68) : le trait transparent d'un lien d'icône fantôme était repeint en
     couleur de lien (LinkText), et le lien qui n'est PAS la page courante se lisait comme la vue
     cerclée. Il reste sans trait (`Canvas`, une couleur système que le contraste élevé respecte).
     La page courante garde deux signes, aplat `SelectedItem` et anneau `CanvasText`, comme
     l'onglet courant prend `CanvasText` (tabs.css). L'anneau de focus reste celui de base.css. */
  @media (forced-colors: active) {
    a.ibou-icon-btn[data-variant="ghost"]:not([aria-current="page"]) { border-color: Canvas; }
    .ibou-icon-btn[aria-current="page"] { forced-color-adjust: none; background: SelectedItem; color: SelectedItemText; border-color: CanvasText; }
  }

}

/* ——— src/css/components/link.css ——— */
/* link.css — Lien et appel de citation (07 · Lien). Le lien par défaut (a) est stylé par base.css ;
   .ibou-link explicite les variantes, .ibou-cite est l'exposant numéroté de la réponse sourcée (08). */
@layer ibou.components {
  /* `--ibou-action-link*` : posées par une surface restée claire dans les deux thèmes (card chaude, card.css),
     où text.link, beige 50 en sombre, tombe à 1,37:1. Le lien externe lit ensuite le ton des sources du
     service, `--ibou-service-source-text` (D-71) : neutre sans réglage, vert dans ibou Découverte seulement. */
  .ibou-link {
    color: var(--ibou-action-link, var(--ibou-color-text-link));
    text-decoration: underline; text-decoration-thickness: var(--ibou-border-width-hairline);
    text-underline-offset: 0.2em; border-radius: var(--ibou-radius-xs);
    transition: color var(--ibou-motion-duration-fast) var(--ibou-motion-easing-standard);
  }
  @media (hover: hover) { .ibou-link:hover { color: var(--ibou-action-link-hover, var(--ibou-color-text-link-hover)); text-decoration-thickness: var(--ibou-border-width-thin); } }
  .ibou-link:active  { background: var(--ibou-color-action-tertiary-bg-active); }
  .ibou-link:visited { color: var(--ibou-action-link-visited, var(--ibou-color-text-link-visited)); }
  .ibou-link[data-external], .ibou-link[data-external]:visited { color: var(--ibou-action-link-external, var(--ibou-service-source-text, var(--ibou-color-text-link))); }
  @media (hover: hover) { .ibou-link[data-external]:hover { background: var(--ibou-color-action-tertiary-bg-hover); } }
  .ibou-link[data-external]::after { content: " ↗"; white-space: nowrap; }
  .ibou-link[data-tone="inverse"], .ibou-link[data-tone="inverse"]:visited { color: var(--ibou-color-text-inverse); text-decoration-color: var(--ibou-color-border-inverse); }
  /* Sur les aplats de marque : tokens link-on-* (jamais text.link, illisible en mode sombre) */
  [data-surface="brand"] .ibou-link, [data-surface="brand"] .ibou-link:visited { color: var(--ibou-color-text-link-on-brand); }
  @media (hover: hover) { [data-surface="brand"] .ibou-link:hover { color: var(--ibou-color-text-link-on-brand-hover); background: transparent; } }
  /* Lien isolé (hors paragraphe) : cible 44 px */
  .ibou-link[data-standalone] { display: inline-block; padding-block: var(--ibou-space-2); }

  /* Appel de citation : [1] dans la réponse, relié à la source card (min. 12 px, tag.sm) */
  .ibou-cite {
    display: inline-grid; place-content: center; position: relative;
    min-inline-size: var(--ibou-size-icon-xs); block-size: var(--ibou-size-icon-xs); padding-inline: var(--ibou-space-1); margin-inline: var(--ibou-space-0-5);
    border: var(--ibou-border-width-hairline) solid var(--ibou-color-border-strong); border-radius: var(--ibou-radius-xs);
    background: var(--ibou-color-surface-default); color: var(--ibou-color-text-primary);
    font-family: var(--ibou-typography-tag-sm-font-family); font-weight: var(--ibou-typography-tag-sm-font-weight); font-size: var(--ibou-typography-tag-sm-font-size);
    line-height: 1; text-decoration: none; vertical-align: super;
  }
  .ibou-cite:focus-visible { background: var(--ibou-color-surface-highlight); color: var(--ibou-color-text-primary); }
  @media (hover: hover) { .ibou-cite:hover { background: var(--ibou-color-surface-highlight); color: var(--ibou-color-text-primary); } }
  .ibou-cite::before { content: ""; position: absolute; inset: 50% auto auto 50%; translate: -50% -50%; min-width: var(--ibou-size-icon-md); min-height: var(--ibou-size-icon-md); }
  /* La pression est instantanée (D-50) : le fond hérite sinon de la transition de 120 ms et
     l'appui traîne derrière le doigt. Une réserve va avec, elle est au chapitre 10 : cela ne se
     verra que sur le bouton primaire, l'écart de `action.secondary.bg-active` avec son fond au
     repos étant trop faible pour qu'un appui produise quoi que ce soit à l'œil. */
  .ibou-link:active { transition-duration: var(--ibou-motion-duration-instant); }

}

/* ——— src/css/components/tag.css ——— */
/* tag.css — Tag (07 · Tag, D-07, D-18). Pilule à trait fin, casse de phrase, jamais tronquée (white-space: normal).
   Variante d'état : icône + libellé obligatoires, la couleur ne dit rien seule. Cible 44 px par débordement sur a/button. */
@layer ibou.components {
  .ibou-tag {
    position: relative; display: inline-flex; align-items: center; gap: var(--ibou-tag-gap);
    min-height: var(--ibou-tag-height-md); padding: var(--ibou-space-0-5) var(--ibou-tag-padding-x-md);
    border: var(--ibou-tag-border-width) solid var(--ibou-tag-border); border-radius: var(--ibou-tag-radius);
    background: var(--ibou-tag-bg-neutral); color: var(--ibou-color-text-primary); white-space: normal; text-decoration: none; text-align: start;
    font: var(--ibou-typography-tag-md); line-height: var(--ibou-font-line-height-tight);
    letter-spacing: var(--ibou-typography-tag-md-letter-spacing);
  }
  .ibou-tag > svg { inline-size: var(--ibou-size-icon-xs); block-size: var(--ibou-size-icon-xs); flex: none; }
  .ibou-tag[data-size="sm"] { min-height: var(--ibou-tag-height-sm); padding-inline: var(--ibou-tag-padding-x-sm); font-size: var(--ibou-typography-tag-sm-font-size); }
  .ibou-tag[data-tone="brand"]    { background: var(--ibou-tag-bg-brand); }
  .ibou-tag[data-tone="discover"] { background: var(--ibou-tag-bg-discover); color: var(--ibou-color-text-discover); }
  .ibou-tag[data-tone="warm"]     { background: var(--ibou-tag-bg-warm); color: var(--ibou-color-text-primary); }
  /* Tag de source : le ton des sources du service (D-71). Neutre sans réglage (tag neutre + « ↗ ») ; ibou Découverte
     règle `--ibou-service-source-tag-bg` sur tag.bg.source (vert 100) et `--ibou-service-source-text` sur
     text.link-external (vert 900). */
  .ibou-tag[data-tone="source"]   { background: var(--ibou-service-source-tag-bg, var(--ibou-tag-bg-neutral)); color: var(--ibou-service-source-text, var(--ibou-color-text-primary)); }
  .ibou-tag[data-tone="source"]::after { content: "↗"; }
  .ibou-tag[data-status="success"] { background: var(--ibou-color-status-success-bg); border-color: var(--ibou-color-status-success-border); color: var(--ibou-color-status-success-text); }
  .ibou-tag[data-status="warning"] { background: var(--ibou-color-status-warning-bg); border-color: var(--ibou-color-status-warning-border); color: var(--ibou-color-status-warning-text); }
  .ibou-tag[data-status="error"]   { background: var(--ibou-color-status-error-bg); border-color: var(--ibou-color-status-error-border); color: var(--ibou-color-status-error-text); }
  .ibou-tag[data-status="info"]    { background: var(--ibou-color-status-info-bg); border-color: var(--ibou-color-status-info-border); color: var(--ibou-color-status-info-text); }
  .ibou-tag[data-status="neutral"] { background: var(--ibou-color-status-neutral-bg); border-color: var(--ibou-color-status-neutral-border); color: var(--ibou-color-status-neutral-text); }
  .ibou-tag[data-status="success"] > svg { color: var(--ibou-color-status-success-icon); }
  .ibou-tag[data-status="warning"] > svg { color: var(--ibou-color-status-warning-icon); }
  .ibou-tag[data-status="error"] > svg   { color: var(--ibou-color-status-error-icon); }
  .ibou-tag[data-status="info"] > svg    { color: var(--ibou-color-status-info-icon); }
  a.ibou-tag, button.ibou-tag { cursor: pointer; }
  a.ibou-tag::before, button.ibou-tag::before { content: ""; position: absolute; inset: 50% auto auto 50%; translate: -50% -50%; min-width: var(--ibou-size-target-min); min-height: var(--ibou-size-target-min); width: 100%; height: 100%; }
  @media (hover: hover) { a.ibou-tag:hover, button.ibou-tag:hover { background: var(--ibou-color-action-secondary-bg-hover); color: var(--ibou-color-text-primary); } }
  @media (hover: hover) { a.ibou-tag[data-tone="discover"]:hover, button.ibou-tag[data-tone="discover"]:hover { background: var(--ibou-color-surface-discover-subtle); color: var(--ibou-color-text-discover); filter: brightness(0.94); } }
  /* Survol du tag de source : la surface de survol de son ton, assombrie comme celle des tags colorés ;
     action.secondary.bg-hover sans réglage, vert 100 dans ibou Découverte (le survol d'avant D-71). */
  @media (hover: hover) { a.ibou-tag[data-tone="source"]:hover { background: var(--ibou-service-source-tag-bg, var(--ibou-color-action-secondary-bg-hover)); color: var(--ibou-service-source-text, var(--ibou-color-text-primary)); filter: brightness(0.94); } }
  @media (hover: hover) { a.ibou-tag[data-tone="brand"]:hover, button.ibou-tag[data-tone="brand"]:hover { background: var(--ibou-color-surface-brand-muted); filter: brightness(0.94); } }
  button.ibou-tag[aria-pressed="true"] { background: var(--ibou-color-action-selected-bg); border-width: var(--ibou-border-width-thin); border-color: var(--ibou-color-action-selected-border); color: var(--ibou-color-action-selected-text); padding-block: 0; }
  .ibou-tag:disabled, .ibou-tag[aria-disabled="true"] { background: var(--ibou-color-action-disabled-bg); border-color: var(--ibou-color-action-disabled-border); color: var(--ibou-color-action-disabled-text); pointer-events: none; }
  /* Retrait d'un filtre : cible 44 px par débordement invisible */
  .ibou-tag__remove { position: relative; display: grid; place-items: center; inline-size: var(--ibou-size-icon-sm); block-size: var(--ibou-size-icon-sm); margin-inline-end: calc(-1 * var(--ibou-space-1)); color: inherit; border-radius: var(--ibou-radius-full); }
  .ibou-tag__remove::before { content: ""; position: absolute; inset: 50% auto auto 50%; translate: -50% -50%; min-width: var(--ibou-size-target-min); min-height: var(--ibou-size-target-min); }
  @media (hover: hover) { .ibou-tag__remove:hover { background: var(--ibou-color-action-tertiary-bg-hover); } }
  .ibou-tag__remove > svg { inline-size: var(--ibou-size-icon-xs); block-size: var(--ibou-size-icon-xs); }
  .ibou-tags { display: flex; flex-wrap: wrap; gap: var(--ibou-spacing-inline-sm); margin: 0; padding: 0; list-style: none; }
}

/* ——— src/css/components/card.css ——— */
/* card.css — Card (07 · Card, D-10, D-31, D-33, D-65). Blanc sur beige, trait, sans ombre. Trait beige = passive, anthracite = interactive.
   Card interactive : un seul lien (le titre) étendu par ::after ; les actions restent des cibles distinctes. */
@layer ibou.components {
  .ibou-card {
    position: relative; display: flex; flex-direction: column; gap: var(--ibou-card-gap);
    padding: var(--ibou-card-padding-md); border: var(--ibou-card-border-width) solid var(--ibou-card-border);
    border-radius: var(--ibou-card-radius); background: var(--ibou-card-bg); color: var(--ibou-color-text-primary);
    transition: background-color var(--ibou-motion-duration-fast) var(--ibou-motion-easing-standard);
  }
  .ibou-card[data-size="sm"] { padding: var(--ibou-card-padding-sm); }
  .ibou-card[data-size="lg"] { padding: var(--ibou-card-padding-lg); }
  .ibou-card[data-interactive] { border-color: var(--ibou-card-border-active); }
  /* Survol sur `bg.subtle`, appui sur `action.tertiary.bg-hover` : mêmes valeurs qu'avant en clair (beige 100,
     beige 200) ; en sombre #232323, où `action.secondary.bg-*` (#5E5E5E) mettait `text.secondary` à 3,13:1 et
     `text.link-external` à 4,20:1. En sombre, survol et appui se distinguent à peine du repos (1,07:1) : le
     titre souligné signale le survol. Une card à tonalité ou sélectionnée garde son fond : le survol (0,3,0)
     l'écrasait (0,2,0), jusqu'à 2,27:1 pour le texte d'une card chaude en sombre. */
  @media (hover: hover) { .ibou-card[data-interactive]:not([data-tone], [data-selected], [aria-selected="true"]):hover { background: var(--ibou-color-bg-subtle); } }
  @media (hover: hover) { .ibou-card[data-interactive]:hover .ibou-card__title > :is(a, button) { text-decoration: underline; text-decoration-thickness: var(--ibou-border-width-thin); } }
  .ibou-card[data-interactive]:not([data-tone], [data-selected], [aria-selected="true"]):active { background: var(--ibou-color-action-tertiary-bg-hover); }
  .ibou-card[data-interactive]:has(.ibou-card__title > :is(a, button):focus-visible) {
    outline: var(--ibou-focus-ring-width) solid var(--ibou-color-border-focus); outline-offset: var(--ibou-focus-ring-offset);
    box-shadow: 0 0 0 calc(var(--ibou-focus-ring-width) + var(--ibou-focus-ring-offset)) var(--ibou-color-border-focus-halo);
  }
  /* Titre de contenu d'un fil (article, sujet, résultat) : Poppins Medium, subtitle.md (D-65). La card colorée,
     bloc éditorial où ibou parle en son nom (IbouPromo, « Rien de précis à chercher ? »), garde New Spirit
     (heading.4). Un style composé posé sur le titre l'emporte sur les deux : `.ibou-text-heading-3`, `-heading-4`,
     `-subtitle-md`, `-subtitle-lg` (grande card) ou `-subtitle-xl` (la une, D-65 complété), que la couche des
     utilitaires, sous celle des composants, ne pouvait pas imposer seule. */
  .ibou-card__title { margin: 0; font: var(--ibou-typography-subtitle-md); letter-spacing: var(--ibou-typography-subtitle-md-letter-spacing); }
  .ibou-card[data-tone] .ibou-card__title, .ibou-card .ibou-card__title.ibou-text-heading-4 { font: var(--ibou-typography-heading-4); letter-spacing: var(--ibou-typography-heading-4-letter-spacing); }
  .ibou-card .ibou-card__title.ibou-text-heading-3 { font: var(--ibou-typography-heading-3); letter-spacing: var(--ibou-typography-heading-3-letter-spacing); }
  .ibou-card .ibou-card__title.ibou-text-subtitle-md { font: var(--ibou-typography-subtitle-md); letter-spacing: var(--ibou-typography-subtitle-md-letter-spacing); }
  .ibou-card .ibou-card__title.ibou-text-subtitle-lg { font: var(--ibou-typography-subtitle-lg); letter-spacing: var(--ibou-typography-subtitle-lg-letter-spacing); }
  .ibou-card .ibou-card__title.ibou-text-subtitle-xl { font: var(--ibou-typography-subtitle-xl); letter-spacing: var(--ibou-typography-subtitle-xl-letter-spacing); }
  /* Le titre porte l'unique cible de la card : un lien quand elle mène à une page, un bouton quand elle
     ouvre une couche (modale, feuille). Le bouton garde l'alignement du titre, que le navigateur centrerait. */
  .ibou-card__title > :is(a, button) { color: inherit; text-decoration: none; text-align: inherit; }
  .ibou-card__title > :is(a, button)::after { content: ""; position: absolute; inset: 0; border-radius: inherit; }
  .ibou-card__body { margin: 0; font-size: var(--ibou-typography-body-sm-font-size); line-height: var(--ibou-typography-body-sm-line-height); letter-spacing: var(--ibou-typography-body-sm-letter-spacing); }
  .ibou-card__meta { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--ibou-spacing-inline-sm); color: var(--ibou-color-text-secondary); font-size: var(--ibou-typography-caption-font-size); }
  .ibou-card__actions { position: relative; z-index: var(--ibou-z-index-raised); display: flex; flex-wrap: wrap; align-items: center; gap: var(--ibou-spacing-inline-sm); }
  .ibou-card[data-tone="brand"]    { background: var(--ibou-color-surface-brand-subtle); border-color: var(--ibou-color-border-strong); border-radius: var(--ibou-card-radius-large); }
  .ibou-card[data-tone="discover"] { background: var(--ibou-color-surface-discover-subtle); border-color: var(--ibou-color-border-strong); border-radius: var(--ibou-card-radius-large); }
  /* La card chaude reste beige 500 dans les deux thèmes : c'est une surface forte. Elle porte data-surface="strong"
     (anneau de focus, boutons en action.on-strong, base.css) et règle ses liens sur text.link-on-warm : au repos, au
     survol et vers une source externe, qui garde sa flèche ↗ et son soulignement — comme sur rose 500 (link.css). */
  .ibou-card[data-tone="warm"]     { background: var(--ibou-color-surface-warm); color: var(--ibou-color-text-on-warm); border-color: var(--ibou-color-border-strong); border-radius: var(--ibou-card-radius-large);
    --ibou-action-link: var(--ibou-color-text-link-on-warm); --ibou-action-link-hover: var(--ibou-color-text-link-on-warm); --ibou-action-link-visited: var(--ibou-color-text-link-on-warm-visited); --ibou-action-link-external: var(--ibou-color-text-link-on-warm); }
  .ibou-card[data-tone="warm"] .ibou-card__meta, .ibou-card[data-tone="warm"] .ibou-card__body { color: var(--ibou-color-text-secondary-on-warm); }
  /* Vert 900 est sombre dans les deux thèmes. Le couple par défaut y est invisible en clair (anneau anthracite à 1,78:1
     contre la card, halo beige 50 à 2,09:1 contre la pilule verte « Ouvrir Découverte » d'ibou.io, D-56), et le couple
     inverse, qui se retourne en sombre, l'est en sombre (anneau anthracite, halo rose 500 à 1,22:1 contre le vert 500).
     La card pose donc le couple des surfaces sombres dans les deux thèmes, border.focus-on-dark et -halo-on-dark, que
     `npm run check` vérifie contre le bouton inverse et contre la pilule verte. */
  .ibou-card[data-tone="discover"][data-variant="strong"] { background: var(--ibou-color-surface-discover-strong); color: var(--ibou-color-text-on-discover-strong); border: 0; border-radius: var(--ibou-card-radius-large);
    --ibou-focus-ring: var(--ibou-color-border-focus-on-dark); --ibou-focus-halo: var(--ibou-color-border-focus-halo-on-dark); }
  .ibou-card[data-tone="discover"][data-variant="strong"] .ibou-card__title { color: var(--ibou-color-text-discover-accent); font-style: italic; }
  /* La pilule verte « Ouvrir Découverte » sur vert 900 (D-56, D-78). Son trait anthracite vient du jeton
     action.discover.border, le même dans les deux thèmes depuis le 2026-10-01 : vert 900 ne change pas avec le
     thème, la pilule qu'il porte non plus (elle perdait son trait en sombre, au vert 500 de son fond). Elle ne
     sort pas de la card : un libellé trop long pour la ligne (texte agrandi, téléphone) passe à la ligne, alors
     que le bouton du système ne se coupe pas (button.css, `white-space: nowrap`). Même règle pour le bouton
     inverse, l'autre bouton admis sur vert 900 (07 · Bouton).
     - Interligne `font.lineHeight.normal` (1,3, celui d'IbouPillButton) : à `tight` (1,05), les deux lignes se
       touchaient, l'accent de « DÉCOUVERTE » frôlait « OUVRIR ».
     - Largeur minimale au mot le plus long (`min-content`) : à texte 200 %, sur la colonne que laisse la mascotte
       à 320 px, « DÉCOUVERTE » est plus large que la colonne ; la pilule déborde alors de la colonne, à côté de la
       mascotte, plutôt que son libellé de la pilule.
     - Le libellé balisé selon D-42, `Ouvrir <span class="ibou-brand-name">ibou</span> Découverte`, fait trois
       éléments flex d'un mot chacun, entre lesquels un `inline-flex` sans retour ne coupe jamais : la pilule était
       bornée et son libellé en sortait, dès la taille de texte normale. D'où `flex-wrap`, mais seulement quand le
       libellé n'a, pour éléments, que des noms de marque : à côté d'une icône, il renverrait le libellé entier sous
       elle. Avec une icône, le libellé tient dans un seul nœud, texte ou `<span>` qui enveloppe le tout, et ce
       nœud se coupe à l'intérieur. */
  .ibou-card[data-tone="discover"][data-variant="strong"] .ibou-btn:is([data-tone="discover"], [data-tone="inverse"]) { max-inline-size: 100%; min-inline-size: min-content; white-space: normal; line-height: var(--ibou-font-line-height-normal); }
  .ibou-card[data-tone="discover"][data-variant="strong"] .ibou-btn:is([data-tone="discover"], [data-tone="inverse"]):not(:has(> :not(.ibou-brand-name))) { flex-wrap: wrap; row-gap: 0; }
  .ibou-card[data-read] { background: var(--ibou-color-surface-muted); color: var(--ibou-color-text-secondary); }
  .ibou-card[data-selected], .ibou-card[aria-selected="true"] { background: var(--ibou-color-action-selected-bg); border-color: var(--ibou-color-action-selected-border); }
  .ibou-card__media { margin: calc(-1 * var(--ibou-card-padding-md)) calc(-1 * var(--ibou-card-padding-md)) 0; border-radius: calc(var(--ibou-card-radius) - var(--ibou-card-border-width)) calc(var(--ibou-card-radius) - var(--ibou-card-border-width)) 0 0; overflow: hidden; aspect-ratio: 16 / 9; background: var(--ibou-color-surface-sunken); display: grid; place-items: center; }
  .ibou-card[data-size="sm"] .ibou-card__media { margin: calc(-1 * var(--ibou-card-padding-sm)) calc(-1 * var(--ibou-card-padding-sm)) 0; }
  .ibou-card__media > img { inline-size: 100%; block-size: 100%; object-fit: cover; }
  .ibou-card__media > svg { inline-size: 40%; block-size: auto; }
  /* Mascotte coupée par le bord bas, cards éditoriales seulement */
  .ibou-card__mascot { position: absolute; inset-inline-end: var(--ibou-space-4); bottom: 0; inline-size: var(--ibou-space-20); block-size: calc(var(--ibou-space-20) * 0.8); overflow: hidden; pointer-events: none; }
  .ibou-card__mascot > svg { display: block; inline-size: 100%; block-size: auto; aspect-ratio: 224.5 / 262.42; translate: 0 35%; }
  .ibou-card:has(> .ibou-card__mascot) { padding-inline-end: calc(var(--ibou-card-padding-md) + var(--ibou-space-20)); }
  /* La pression est instantanée (D-50) : le fond hérite sinon de la transition de 120 ms et
     l'appui traîne derrière le doigt. Une réserve va avec, elle est au chapitre 10 : cela ne se
     verra que sur le bouton primaire, l'écart de `action.secondary.bg-active` avec son fond au
     repos étant trop faible pour qu'un appui produise quoi que ce soit à l'œil. */
  .ibou-card[data-interactive]:active { transition-duration: var(--ibou-motion-duration-instant); }

}

/* ——— src/css/components/nav.css ——— */
/* nav.css — Barre de navigation (07 · Barre de navigation, D-25, D-32, D-33, D-69, D-70) et barre basse mobile .ibou-tabbar.
   Mobile d'abord : 56 px ; à partir de breakpoint.md : 64 px. Dans un service, le verrou dessiné (.ibou-nav__lockup,
   D-63) à toutes les largeurs : 37,1 px de mascotte au plus sous breakpoint.md, réduit en entier quand la place manque
   (D-70), puis 39,8 px. Hors service, lettrage seul (32 px, D-25), puis logo complet (44 px).
   Seule la barre colle (D-69) : la seconde ligne, sœur du <header>, se gare sous elle et n'en laisse dépasser que
   son trait. */
@layer ibou.components {
  .ibou-nav { position: sticky; top: 0; z-index: var(--ibou-z-index-sticky); background: var(--ibou-nav-bg); border-block-end: var(--ibou-nav-border-width) solid var(--ibou-nav-border); color: var(--ibou-color-text-primary); }
  .ibou-nav[data-static] { position: static; }
  .ibou-nav__row { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: var(--ibou-spacing-inline-lg);
    min-height: var(--ibou-nav-height-mobile); max-width: var(--ibou-container-page); margin-inline: auto; padding-inline: var(--ibou-spacing-margin-mobile); }
  .ibou-nav__logo { display: inline-flex; align-items: center; gap: var(--ibou-spacing-inline-sm); border-radius: var(--ibou-radius-sm); color: var(--ibou-color-text-primary); text-decoration: none; }
  @media (hover: hover) { .ibou-nav__logo:hover { color: var(--ibou-color-text-primary); } }
  .ibou-nav__logo-full { display: none; block-size: var(--ibou-nav-logo-height); inline-size: auto; }
  .ibou-nav__logo-mark { display: block; block-size: var(--ibou-nav-logo-height-mobile); inline-size: auto; }
  /* H : hauteur du DESSIN de la mascotte du logo complet — l'image porte 2 % de marge, d'où
     H = 0,905 × hauteur affichée. */
  .ibou-nav__logo { --ibou-logo-h: calc(var(--ibou-nav-logo-height) * 0.905); --ibou-lockup-h: 37.1px; }
  /* Verrou de service (D-63, D-70) : le dessin du brandbook, deux images, une par thème — `.ibou-nav__lockup`,
     et `.ibou-nav__lockup[data-tone="inverse"]` pour la version blanche (lettrage et nom blancs, mascotte
     inchangée, D-26). Une seule est affichée : celle du thème appliqué (`data-theme` forcé, sinon
     `prefers-color-scheme`, les sélecteurs des tokens sombres) ; en couleurs forcées, celle du système, dont la
     barre prend alors le fond. Avec `loading="lazy"` sur les deux, seule l'affichée se charge. Le verrou
     remplace le logo complet et le lettrage : la barre d'un service ne montre jamais le lettrage seul (D-70).
     Hauteur de l'image = H, hauteur de la mascotte (fichier sans marge), multipliée par
     `--ibou-lockup-height-ratio` pour un verrou dont une lettre descend sous la mascotte (Images 1,064,
     Platform 1,055 : height / owlHeight de assets/lockups.json). --ibou-lockup-h : 37,1 px sous breakpoint.md,
     `IbouServiceLockup.defaultHeight` des applications (même barre sur le web mobile et dans l'application),
     puis --ibou-logo-h (39,8 px), la mascotte du logo complet. */
  .ibou-nav__lockup { display: block; flex: none; min-inline-size: 0; inline-size: auto;
    block-size: calc(var(--ibou-lockup-h) * var(--ibou-lockup-height-ratio, 1)); }
  .ibou-nav__lockup[data-tone="inverse"] { display: none; }
  :root[data-theme="dark"] .ibou-nav__lockup:not([data-tone="inverse"]) { display: none; }
  :root[data-theme="dark"] .ibou-nav__lockup[data-tone="inverse"] { display: block; }
  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .ibou-nav__lockup:not([data-tone="inverse"]) { display: none; }
    :root:not([data-theme="light"]) .ibou-nav__lockup[data-tone="inverse"] { display: block; }
  }
  @media (forced-colors: active) and (prefers-color-scheme: light) {
    :root[data-theme] .ibou-nav__lockup:not([data-tone="inverse"]) { display: block; }
    :root[data-theme] .ibou-nav__lockup[data-tone="inverse"] { display: none; }
  }
  @media (forced-colors: active) and (prefers-color-scheme: dark) {
    :root[data-theme] .ibou-nav__lockup:not([data-tone="inverse"]) { display: none; }
    :root[data-theme] .ibou-nav__lockup[data-tone="inverse"] { display: block; }
  }
  /* Barre Inverse (`data-tone="inverse"`, fond bg.inverse : sombre en thème clair, clair en thème sombre) : le
     verrou de son fond, pas celui du thème, soit la version blanche en thème clair et la foncée en thème
     sombre ; celui du thème y effaçait le nom du service (lettrage anthracite sur anthracite, blanc sur
     blanc). En couleurs forcées, la barre prend le fond du système, que les règles ci-dessus suivent déjà. */
  @media (forced-colors: none) {
    .ibou-nav[data-tone="inverse"] .ibou-nav__lockup:not([data-tone="inverse"]) { display: none; }
    .ibou-nav[data-tone="inverse"] .ibou-nav__lockup[data-tone="inverse"] { display: block; }
    :root[data-theme="dark"] .ibou-nav[data-tone="inverse"] .ibou-nav__lockup:not([data-tone="inverse"]) { display: block; }
    :root[data-theme="dark"] .ibou-nav[data-tone="inverse"] .ibou-nav__lockup[data-tone="inverse"] { display: none; }
  }
  @media (forced-colors: none) and (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .ibou-nav[data-tone="inverse"] .ibou-nav__lockup:not([data-tone="inverse"]) { display: block; }
    :root:not([data-theme="light"]) .ibou-nav[data-tone="inverse"] .ibou-nav__lockup[data-tone="inverse"] { display: none; }
  }
  .ibou-nav__logo:has(> .ibou-nav__lockup) > :is(.ibou-nav__logo-full, .ibou-nav__logo-mark) { display: none; }
  /* Sous breakpoint.md, la barre d'un service tient le verrou entier (D-70). Le centre quitte la barre (services
     dans la barre basse, question en seconde ligne) ; il reste le verrou et les actions (la loupe s'il y en a une, le menu).
     La colonne du verrou prend sa largeur naturelle, puis ce qui reste quand elle manque : l'image garde sa
     boîte de H de haut et le dessin s'y réduit en entier, jamais rogné ni remplacé (`object-fit: contain`).
     Entre le verrou et son voisin, la zone de protection (D-24), une unité « o » à la taille la plus grande
     (0,488 × 37,1 = 18,1 px), donc au moins o à toute taille réduite ; elle remplace les deux écarts de la
     grille, et les actions gardent leurs 44 px et leur écart de 8 (04). Avec le menu seul (ibou Actualités, D-70 amendé),
     le verrou garde 37,0 px de mascotte à 320 px et sa taille pleine dès 321 px. */
  .ibou-nav__row:has(> .ibou-nav__logo > .ibou-nav__lockup) { grid-template-columns: minmax(0, max-content) minmax(0, 1fr) auto; column-gap: 0; }
  .ibou-nav__row:has(> .ibou-nav__logo > .ibou-nav__lockup) > .ibou-nav__center { display: none; }
  .ibou-nav__logo:has(> .ibou-nav__lockup) { min-inline-size: 0; margin-inline-end: calc(var(--ibou-lockup-h) * var(--ibou-logo-ratio-x-height)); }
  .ibou-nav__logo > .ibou-nav__lockup { max-inline-size: 100%; object-fit: contain; object-position: 0 50%; }
  /* DÉPRÉCIÉ (D-63, 2026-09-24) : logo + nom de service composé en texte (`.ibou-text-service`,
     `typography.service`, `logo.ratio.service*`). Le nom de service est un dessin : le verrou
     `assets/logo/ibou-lockup-<service>.svg`, image que le site pose dans `.ibou-nav__logo` à la
     place du logo complet, une par thème (01-marque « Verrou de service ») ; un service sans dessin
     reçoit un verrou généré (scripts/generate-lockup.py), jamais un nom en texte vif. Ces règles
     restent le temps que les produits qui s'en servent encore (annotations : « ibou Annotations »)
     passent au logo seul ou à un verrou.
     Construction mesurée sur la p.34 : ligne de base COMMUNE au lettrage et au nom, corps du nom
     0,97 H, écart lettrage → nom 0,18 H ; le décalage remonte le texte de 0,104 H (ligne de base
     du lettrage au-dessus du bas du dessin) plus la marge de l'image. Elle vaut pour le LOGO
     COMPLET : à côté du lettrage seul (mobile, D-25), on n'affiche pas le nom de service. */
  .ibou-nav__logo:has(.ibou-text-service) {
    align-items: baseline;
    gap: calc(var(--ibou-logo-h) * var(--ibou-logo-ratio-service-gap));
  }
  .ibou-nav__logo .ibou-text-service {
    font-size: calc(var(--ibou-logo-h) * var(--ibou-logo-ratio-service-font-size));
    line-height: 1;
    position: relative;
    top: calc(-1 * var(--ibou-logo-h) * var(--ibou-logo-ratio-baseline-offset) - var(--ibou-nav-logo-height) * 0.02);
  }
  .ibou-nav__center { display: flex; justify-content: center; min-width: 0; }
  .ibou-nav__center > .ibou-search { max-width: var(--ibou-search-bar-max-width); }
  .ibou-nav__tabs { justify-content: flex-start; }
  /* Actions de droite (la loupe s'il y en a une et le menu ; au large, ce que le service y ajoute) : un groupe, toujours dans
     la dernière colonne, même quand le centre est masqué. Un bouton seul, sans groupe, y va aussi. */
  .ibou-nav__actions { grid-column: -2; position: relative; display: flex; align-items: center; justify-content: flex-end; gap: var(--ibou-spacing-inline-sm); }
  .ibou-nav > .ibou-nav__row > :last-child:not(:first-child) { grid-column: -2; }
  .ibou-nav__second { border-block-start: var(--ibou-border-width-hairline) solid var(--ibou-color-border-subtle); }
  .ibou-nav__second > .ibou-nav__row { min-height: var(--ibou-tab-height); grid-template-columns: minmax(0, 1fr); }
  .ibou-nav__second > .ibou-nav__row + .ibou-nav__row { border-block-start: var(--ibou-border-width-hairline) solid var(--ibou-color-border-subtle); }
  .ibou-nav[data-tone="inverse"] { background: var(--ibou-color-bg-inverse); border-color: var(--ibou-color-border-inverse); color: var(--ibou-color-text-inverse); }
  /* Seule la barre colle (D-69). La seconde ligne (rubriques, sujets, verticales, question sur mobile) est la
     SŒUR du <header>, juste après lui : un élément collant ne colle que dans son parent, et dans le header elle
     ne pouvait que coller avec lui. Header et seconde ligne sont enfants du conteneur de toute la page (body,
     racine de l'application), sans enveloppe commune ni ancêtre en `overflow` autre que visible ou clip.
     Au repos, l'en-tête est celui d'avant : filet fin entre les lignes, un seul trait de 2 px, sous la seconde
     ligne (la barre ne porte plus le sien). En défilant, la seconde ligne glisse SOUS la barre et s'y gare :
     elle colle à top = barre − ses rangées (filet + 44 px chacune), si bien que seul son trait dépasse, pile
     sous la barre, et devient celui de la barre. Aucune animation, aucun script : rien ne dépend du sens du
     défilement. Garée, elle revient sous la barre, entière, dès que le focus VISIBLE y entre (`:focus-visible` :
     au clavier, ou dans un champ) ou qu'un de ses panneaux est ouvert (`aria-expanded="true"`, D-64) : WCAG
     2.4.11. Pas sur `:focus-within` : le bouton qu'on touche ou qu'on clique garde le focus sans anneau (Chrome,
     Edge, Android), et la ligne restait collée, entière, pendant toute la lecture. Une ou deux rangées (lu de la
     page) ; au-delà, `--ibou-nav-second-rows` en `style`. Réserve des éléments collants : la barre et le trait,
     58 px puis 66 (base.css). */
  .ibou-nav:has(+ .ibou-nav__second) { border-block-end: 0; }
  .ibou-nav + .ibou-nav__second {
    --ibou-nav-bar-height: var(--ibou-nav-height-mobile);
    position: sticky; z-index: calc(var(--ibou-z-index-sticky) - 1);
    top: calc(var(--ibou-nav-bar-height) - var(--ibou-nav-second-rows, 1) * (var(--ibou-border-width-hairline) + var(--ibou-tab-height)));
    background: var(--ibou-nav-bg); color: var(--ibou-color-text-primary);
    border-block-end: var(--ibou-nav-border-width) solid var(--ibou-nav-border);
  }
  /* La garer suppose des rangées de 44 px tout juste : une rangée que son contenu agrandit (texte à 200 %,
     question compacte de 52 px) laissait dépasser de la barre le bas de la ligne garée, au-dessus du trait. La
     rangée garde donc sa hauteur, filet compris pour la seconde (`box-sizing: border-box`) ; un contenu plus
     haut déborde de part et d'autre, sans rien masquer. */
  .ibou-nav + .ibou-nav__second > .ibou-nav__row { block-size: var(--ibou-tab-height); }
  .ibou-nav + .ibou-nav__second > .ibou-nav__row + .ibou-nav__row { block-size: calc(var(--ibou-border-width-hairline) + var(--ibou-tab-height)); }
  .ibou-nav + .ibou-nav__second:has(> .ibou-nav__row + .ibou-nav__row) { --ibou-nav-second-rows: 2; }
  .ibou-nav + .ibou-nav__second:has(:focus-visible, [aria-expanded="true"]) { top: var(--ibou-nav-bar-height); }
  /* Rangée par le focus visible, elle passe au-dessus de la barre : l'anneau (D-13) déborde de 4 px au-dessus
     du contrôle, sur la barre, qui le cachait. Son haut est alors le bas de la barre : seul ce qui déborde de
     la ligne (l'anneau) s'y peint. */
  .ibou-nav + .ibou-nav__second:has(:focus-visible) { z-index: calc(var(--ibou-z-index-sticky) + 1); }
  /* Mais une couche de la barre (menu, sélecteur, popover, infobulle) descend sous elle, là où se range
     la seconde ligne : ouverte (`aria-expanded="true"`) ou montrée au survol, elle passait sous la ligne
     rangée par le focus, qui en cachait le haut (ibou Actualités, 25/09 : focus clavier dans les rubriques
     et popover météo de la barre survolé, 48 px cachés, titre compris). La barre repasse alors au-dessus
     de la ligne ; le haut de l'anneau de son contrôle (4 px) repasse sous la barre le temps que la couche
     est là. */
  .ibou-nav:has(+ .ibou-nav__second):is(:hover, :has([aria-expanded="true"])) { z-index: calc(var(--ibou-z-index-sticky) + 2); }
  .ibou-nav[data-static] + .ibou-nav__second { position: static; }
  .ibou-nav[data-tone="inverse"] + .ibou-nav__second { background: var(--ibou-color-bg-inverse); border-block-end-color: var(--ibou-color-border-inverse); color: var(--ibou-color-text-inverse); }
  /* DÉPRÉCIÉ (D-69) : la seconde ligne DANS le <header>. Rendu d'avant, inchangé le temps de la migration : les
     deux lignes collent ensemble (103 px, puis 111 ; 148 et 156 avec deux rangées), au-dessus du plafond de
     100 px (04, 11), et base.css réserve toutes les rangées. Aucune règle CSS ne peut la garer dans le header,
     qui colle d'un bloc. Retrait à la prochaine version majeure. */

  /* Le focus qui entre dans la barre collante, ou dans sa seconde ligne rangée sous elle, ne fait pas défiler
     la page (D-69). La réserve haute de base.css (`scroll-padding-top`) est la place de la barre : ce qu'elle
     porte y tombe, et le navigateur faisait défiler la page pour « montrer » un contrôle déjà à l'écran
     (banc du DS à 375 px, page à 900 px : clic sur le menu, Tab, Échap, et la page passait à 513, 503 puis
     116 px). Une marge de défilement négative de la même valeur descend la zone de défilement sous
     la réserve : rien ne bouge. Menus et popovers accrochés à la barre : le bas de leur zone reste en place
     (décalé, il sortait de la zone utile au bas d'un panneau qui descend jusqu'à la barre basse), et un
     panneau qui défile lui-même rend la réserve en `scroll-padding`. Barre détachée (data-static, sous 480 px
     de haut) : rien à annuler, un contrôle sorti de l'écran revient à space.4 du bord, comme tout contenu.
     Repris d'ibou Actualités (global.css), où il est mesuré. */
  :is(.ibou-nav, .ibou-nav + .ibou-nav__second) { --ibou-nav-reserve: calc(var(--ibou-sticky-top) + var(--ibou-space-4)); }
  :is(.ibou-nav[data-static], .ibou-nav[data-static] + .ibou-nav__second) { --ibou-nav-reserve: 0px; }
  :is(.ibou-nav, .ibou-nav + .ibou-nav__second) * { scroll-margin-block: calc(-1 * var(--ibou-nav-reserve)) var(--ibou-nav-reserve); }
  :is(.ibou-nav, .ibou-nav + .ibou-nav__second) :is(.ibou-menu, .ibou-popover) { scroll-padding-block-start: var(--ibou-nav-reserve); }
  :is(.ibou-nav, .ibou-nav + .ibou-nav__second) :is(.ibou-menu, .ibou-popover) * { scroll-margin-block-end: 0; }
  /* Lien d'évitement : premier élément focalisable, visible au focus (style bouton secondaire) */
  .ibou-skip { position: absolute; inset-inline-start: var(--ibou-space-4); top: var(--ibou-space-2); translate: 0 -200%; z-index: var(--ibou-z-index-tooltip); }
  .ibou-skip:focus-visible { translate: 0 0; }
  /* Visible, le lien d'évitement se posait par-dessus la barre et ne laissait que 2 px de zone
     cliquable au logo (WCAG 2.5.8). La barre descend le temps qu'il ait le focus : il ne
     recouvre plus rien, et tout revient en place dès qu'on le quitte. */
  .ibou-nav:has(.ibou-skip:focus-visible), .ibou-skip:focus-visible + .ibou-nav { padding-block-start: calc(var(--ibou-size-target-min) + var(--ibou-space-4)); }

  /* Barre basse mobile : les services, 4 ou 5 entrées, icône + libellé label.sm ; disparaît à partir de breakpoint.md */
  .ibou-tabbar { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); min-height: var(--ibou-nav-height-mobile); padding-block-end: env(safe-area-inset-bottom);
    background: var(--ibou-nav-bg); border-block-start: var(--ibou-nav-border-width) solid var(--ibou-nav-border); }
  .ibou-tabbar[data-fixed] { position: fixed; inset-inline: 0; bottom: 0; z-index: var(--ibou-z-index-sticky); }
  .ibou-tabbar > a { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--ibou-space-0-5); min-height: var(--ibou-size-target-min); padding: var(--ibou-space-1);
    color: var(--ibou-color-text-secondary); text-decoration: none; border-radius: var(--ibou-radius-xs);
    font: var(--ibou-typography-label-sm); letter-spacing: var(--ibou-typography-label-sm-letter-spacing); text-transform: uppercase; }
  .ibou-tabbar > a > svg { inline-size: var(--ibou-size-icon-md); block-size: var(--ibou-size-icon-md); }
  @media (hover: hover) { .ibou-tabbar > a:hover { color: var(--ibou-color-text-primary); background: var(--ibou-color-action-tertiary-bg-hover); } }
  .ibou-tabbar > a[aria-current="page"] { color: var(--ibou-color-text-primary); }
  .ibou-tabbar > a[aria-current="page"] > svg { color: var(--ibou-color-icon-brand); }
  .ibou-tabbar > a[aria-current="page"]::after { content: ""; inline-size: var(--ibou-space-6); block-size: var(--ibou-tab-underline-width); border-radius: var(--ibou-radius-full); background: var(--ibou-color-border-strong); }
  /* Couleurs forcées : le trait de l'entrée courante est un fond de pseudo-élément, que le contraste élevé peignait
     de la couleur de la barre ; il prend CanvasText, comme le soulignement de l'onglet courant (tabs.css, D-64). */
  @media (forced-colors: active) {
    .ibou-tabbar > a[aria-current="page"]::after { background: CanvasText; }
  }

  @media (min-width: 768px) { /* breakpoint.md */
    .ibou-nav__row { min-height: var(--ibou-nav-height); padding-inline: var(--ibou-spacing-margin-tablet); gap: var(--ibou-spacing-inline-xl); }
    .ibou-nav__logo-full { display: block; }
    .ibou-nav__logo-mark { display: none; }
    .ibou-nav__logo { --ibou-lockup-h: var(--ibou-logo-h); }
    .ibou-nav__row:has(> .ibou-nav__logo > .ibou-nav__lockup) { grid-template-columns: auto minmax(0, 1fr) auto; column-gap: var(--ibou-spacing-inline-xl); }
    .ibou-nav__row:has(> .ibou-nav__logo > .ibou-nav__lockup) > .ibou-nav__center { display: flex; }
    .ibou-nav__logo:has(> .ibou-nav__lockup) { margin-inline-end: 0; }
    .ibou-nav__logo > .ibou-nav__lockup { max-inline-size: none; }
    .ibou-nav__tabs { justify-content: center; }
    .ibou-nav + .ibou-nav__second { --ibou-nav-bar-height: var(--ibou-nav-height); }
    .ibou-tabbar:not([data-always]) { display: none; }
  }
  @media (min-width: 1024px) { /* breakpoint.lg */
    .ibou-nav__row { padding-inline: var(--ibou-spacing-margin-desktop); }
  }
  /* Sous 480 px de haut (téléphone en paysage, zoom 200 %), les barres se détachent (04, 11) : collées,
     elles mangeaient l'écran. La réserve de défilement tombe avec elles (base.css) ; le padding-bottom que
     l'application pose pour la barre basse se retire dans la même requête. La règle porte sur la hauteur,
     pas sur la largeur : elle ne contredit pas le mobile d'abord (04). */
  @media (max-height: 480px) {
    .ibou-nav, .ibou-nav + .ibou-nav__second, .ibou-tabbar[data-fixed] { position: static; }
    :is(.ibou-nav, .ibou-nav + .ibou-nav__second) { --ibou-nav-reserve: 0px; }
  }
}

/* ——— src/css/components/tabs.css ——— */
/* tabs.css — Onglets (07 · Onglets, D-18, D-32, D-64). Navigation entre services (liens, aria-current) ou vues (role=tab).
   Mobile d'abord : label.sm sous breakpoint.sm, label.md au-delà. Reset des <button> : base.css.
   Une rangée de navigation qui ne tient pas ne défile pas (D-64, variante « À débordement ») : elle montre les onglets
   qui tiennent en entier, et un bouton-onglet en bout de ligne (aria-expanded ; en tête jusqu'au 2026-09-25, D-64 amendé)
   ouvre la liste complète. Le défilement de .ibou-tabs ne sert plus qu'aux rangées que D-64 ne couvre pas encore
   (sujets du fil Découverte). */
@layer ibou.components {
  /* Un conteneur défilant rogne tout ce qui dépasse, anneau de focus compris : la marge intérieure garde la
     place de l'anneau et du halo (focus.ring.width + focus.ring.offset), la marge négative la rend à la mise
     en page, qui ne bouge pas (soulignement toujours collé au filet). */
  .ibou-tabs { display: flex; gap: var(--ibou-tab-gap); overflow-x: auto; scrollbar-width: none; margin: calc(-1 * (var(--ibou-focus-ring-width) + var(--ibou-focus-ring-offset))); padding: calc(var(--ibou-focus-ring-width) + var(--ibou-focus-ring-offset)); list-style: none; }
  .ibou-tabs::-webkit-scrollbar { display: none; }
  .ibou-tab {
    position: relative; display: inline-flex; align-items: center; gap: var(--ibou-spacing-inline-sm); flex: none;
    min-height: var(--ibou-tab-height); padding-inline: var(--ibou-tab-padding-x); white-space: nowrap;
    color: var(--ibou-color-text-secondary); text-decoration: none; border-radius: var(--ibou-radius-xs);
    font: var(--ibou-typography-label-sm);
    letter-spacing: var(--ibou-typography-label-sm-letter-spacing); text-transform: uppercase;
    transition: color var(--ibou-motion-duration-fast) var(--ibou-motion-easing-standard);
  }
  .ibou-tab::after { content: ""; position: absolute; inset-inline: var(--ibou-tab-padding-x); bottom: 0; height: var(--ibou-tab-underline-width); background: transparent; }
  @media (hover: hover) { .ibou-tab:hover { color: var(--ibou-color-text-primary); background: var(--ibou-color-action-tertiary-bg-hover); } }
  @media (hover: hover) { .ibou-tab:hover::after { background: var(--ibou-color-border-default); } }
  /* Trois façons d'être l'onglet courant, un seul rendu : un lien de navigation (aria-current), un onglet
     de tablist (aria-selected), ou un bouton à bascule qui filtre la vue sans changer de panneau
     (aria-pressed, sujets du fil Découverte, 08 · Fil Découverte). Ce dernier cas était repeint localement. */
  .ibou-tab:is([aria-current="page"], [aria-selected="true"], [aria-pressed="true"]) { color: var(--ibou-color-text-primary); }
  .ibou-tab:is([aria-current="page"], [aria-selected="true"], [aria-pressed="true"])::after { background: var(--ibou-color-border-strong); }
  .ibou-tab[aria-disabled="true"], .ibou-tab:disabled { color: var(--ibou-color-text-disabled); pointer-events: none; }
  .ibou-tab > .ibou-tag { text-transform: none; }
  /* Icône d'un onglet (chevron du bouton de débordement) : size.icon.sm, comme dans tout contrôle md (07 · Icônes). */
  .ibou-tab > svg { flex: none; inline-size: var(--ibou-size-icon-sm); block-size: var(--ibou-size-icon-sm); }
  /* Bouton de débordement ouvert (D-64). Le fond de survol, action.tertiary.bg-hover, vaut beige 200 : celui de la
     barre de navigation, sur laquelle l'état ouvert ne se voyait pas. Il prend donc le fond des états à bascule
     (action.selected.bg, rose 200, comme un filtre pressé et l'élément courant du menu qu'il ouvre), sans
     soulignement, pour ne pas imiter l'onglet courant. Le chevron reste fixe (D-50 : aucun mouvement ajouté). */
  .ibou-tab[aria-expanded="true"] { color: var(--ibou-color-action-selected-text); background: var(--ibou-color-action-selected-bg); }
  .ibou-tab[aria-expanded="true"]::after { background: transparent; }
  .ibou-tabpanel { padding-block: var(--ibou-spacing-stack-md); }
  @media (min-width: 640px) { /* breakpoint.sm */
    .ibou-tab { font-size: var(--ibou-typography-label-md-font-size); letter-spacing: var(--ibou-typography-label-md-letter-spacing); }
  }
  /* Contraste élevé : le soulignement de l'onglet courant est un fond de pseudo-élément, que les couleurs forcées
     peignaient de la couleur de la barre (rgb(255, 255, 255) mesuré sous Chromium, forcedColors: active). Le
     courant et les autres avaient alors même couleur (LinkText) et même graisse : indiscernables. */
  @media (forced-colors: active) {
    .ibou-tab:is([aria-current="page"], [aria-selected="true"], [aria-pressed="true"])::after { background: CanvasText; }
  }
}

/* ——— src/css/components/footer.css ——— */
/* footer.css — Pied de page (07 · Pied de page, D-01, D-02, D-26). Surface rose 500 (footer.bg), texte et lettrage anthracite,
   lettrage géant tronqué par le bas et mascotte posée dessus. Liens : tokens link-on-brand. data-surface="brand" pour le focus (base.css). */
@layer ibou.components {
  .ibou-footer { position: relative; overflow: hidden; border-block-start: var(--ibou-footer-border-width) solid var(--ibou-footer-border); background: var(--ibou-footer-bg); color: var(--ibou-footer-text); padding-block-start: var(--ibou-footer-padding-y); }
  .ibou-footer__inner { max-width: var(--ibou-container-page); margin-inline: auto; padding-inline: var(--ibou-spacing-margin-mobile); display: grid; gap: var(--ibou-spacing-section-sm); }
  .ibou-footer__cols { display: grid; grid-template-columns: 1fr 1fr; gap: var(--ibou-spacing-gutter-mobile); }
  .ibou-footer__cols h2, .ibou-footer__cols h3 { margin: 0 0 var(--ibou-spacing-stack-sm); color: inherit; }
  .ibou-footer__cols ul { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--ibou-spacing-stack-xs); font-size: var(--ibou-typography-body-sm-font-size); line-height: var(--ibou-typography-body-sm-line-height); }
  .ibou-footer__cols a { color: var(--ibou-color-text-link-on-brand); text-decoration: underline; text-decoration-thickness: var(--ibou-border-width-hairline); text-underline-offset: 0.2em; display: inline-block; padding-block: var(--ibou-space-1); }
  @media (hover: hover) { .ibou-footer__cols a:hover { color: var(--ibou-color-text-link-on-brand-hover); text-decoration-thickness: var(--ibou-border-width-thin); } }
  .ibou-footer__cols a:visited { color: var(--ibou-color-text-link-on-brand-visited); }
  .ibou-footer__claim { margin: 0; font: var(--ibou-typography-heading-4); letter-spacing: var(--ibou-typography-heading-4-letter-spacing); }
  .ibou-footer__legal { margin: 0; font-size: var(--ibou-typography-caption-font-size); line-height: var(--ibou-typography-caption-line-height); }
  /* display: flow-root : la marge négative du lettrage (qui le rogne) doit rester DANS ce conteneur. Sans
     contexte de formatage, elle le traversait, le conteneur gardait la hauteur du lettrage entier (577 px
     au lieu de 318 à 1280 px), et `bottom: 38 %` posait la mascotte dans le « u », coupée par le bas. */
  /* `container-type: inline-size` : la mascotte se mesure en `cqw`, c'est-à-dire en part de la
     largeur du lettrage, et non plus en pixels fixes. En pixels fixes (64 / 96), elle était 1,76 fois
     trop petite à 1440 px et 1,5 fois trop grande à 375 px : la composition changeait avec l'écran.
     La part, 9,9 %, est relevée sur deux maquettes indépendantes en y superposant le dessin :
     brandbook p.29 (9,82 %) et maquette du pied d'ibou.io (9,98 %). `cqw` mesure la largeur du
     conteneur, qui est celle de l'image du lettrage — viewBox comprise, approches latérales incluses.
     Une part mesurée sur l'encre seule, ou sur le lettrage privé du « i », en donne 10,2 ou 11,8 :
     c'est de là que venait le 11,6 % précédent, qui faisait la mascotte 18 % trop grande. Contrôle
     sans échelle : sa boîte vaut 37 % de la largeur du « o » sur les deux maquettes, 44 % à 11,6. */
  .ibou-footer__mark { container-type: inline-size; --ibou-footer-mascot-h: 9.9cqw; position: relative; display: flow-root; margin-block-start: var(--ibou-spacing-section-sm); line-height: 0; }
  .ibou-footer__wordmark { display: block; inline-size: 100%; block-size: auto; aspect-ratio: 587.64 / 268; margin-block-end: calc(-1 * var(--ibou-footer-wordmark-crop) * 100% * 0.456); }
  /* Assise sur le « o », comme au brandbook p.29. Mesuré sur ibou-wordmark.svg (viewBox 587.64 × 268,
     coupe `footer.wordmarkCrop`) : le « o » est centré à 57,56 % de la largeur du mot et son sommet
     tombe à 37,5 % du bloc visible. La largeur de la mascotte se déduit de sa hauteur par le ratio du
     dessin (224.5 / 262.42 ≈ 0,8555). Le bas de sa boîte s'enfonce de 23,1 % de sa hauteur sous le
     sommet de la lettre : chiffre relevé sur la maquette du brandbook p.29 en y superposant le dessin
     (recouvrement 0,85, centre calé à 0,2 px de celui de la lettre). Posée tangente — le bas de
     l'encre sur l'arc, 13,33 % —, elle avait l'air de voler : l'œil lit l'appui à la part du corps
     que la lettre mange, pas au contact des deux traits. */
  .ibou-footer__mascot { position: absolute; inset-inline-start: calc(57.56% - var(--ibou-footer-mascot-h) * 0.8555 / 2); inset-inline-end: auto; bottom: calc(37.5% - var(--ibou-footer-mascot-h) * 0.231); block-size: var(--ibou-footer-mascot-h); inline-size: auto; aspect-ratio: 224.5 / 262.42; }
  .ibou-footer__logo { block-size: var(--ibou-nav-logo-height-mobile); inline-size: auto; }
  .ibou-footer[data-variant="short"] { padding-block: var(--ibou-spacing-inset-lg); background: var(--ibou-color-bg-muted); color: var(--ibou-color-text-primary); }
  .ibou-footer[data-variant="short"] .ibou-footer__mark { display: none; }
  .ibou-footer[data-variant="short"] .ibou-footer__inner { gap: var(--ibou-spacing-stack-md); }
  .ibou-footer[data-variant="short"] .ibou-footer__cols a { color: var(--ibou-color-text-link); }
  @media (hover: hover) { .ibou-footer[data-variant="short"] .ibou-footer__cols a:hover { color: var(--ibou-color-text-link-hover); } }
  .ibou-footer[data-variant="short"] .ibou-footer__cols a:visited { color: var(--ibou-color-text-link-visited); }
  .ibou-footer[data-tone="inverse"] { background: var(--ibou-color-bg-inverse); color: var(--ibou-color-text-inverse); border-color: var(--ibou-color-border-inverse); }
  .ibou-footer[data-tone="inverse"] .ibou-footer__cols a, .ibou-footer[data-tone="inverse"] .ibou-footer__cols a:visited { color: var(--ibou-color-text-inverse); text-decoration-color: var(--ibou-color-border-inverse); }
  @media (hover: hover) { .ibou-footer[data-tone="inverse"] .ibou-footer__cols a:hover { color: var(--ibou-color-text-inverse); text-decoration-color: var(--ibou-color-border-inverse); } }
  .ibou-footer[data-tone="discover"] { background: var(--ibou-color-surface-discover); color: var(--ibou-color-text-on-discover); }
  /* Sans règle de survol propre, la règle générale du pied s'appliquait : les liens du pied vert viraient au rose 950 du pied rose. */
  .ibou-footer[data-tone="discover"] .ibou-footer__cols a { color: var(--ibou-color-text-link-on-discover); }
  @media (hover: hover) { .ibou-footer[data-tone="discover"] .ibou-footer__cols a:hover { color: var(--ibou-color-text-link-on-discover-hover); } }
  .ibou-footer[data-tone="discover"] .ibou-footer__cols a:visited { color: var(--ibou-color-text-link-on-discover-visited); }
  @media (min-width: 768px) { /* breakpoint.md */
    .ibou-footer__inner { padding-inline: var(--ibou-spacing-margin-tablet); }
    .ibou-footer__cols { grid-template-columns: repeat(4, 1fr); gap: var(--ibou-spacing-gutter-desktop); }
  }
  @media (min-width: 1024px) { /* breakpoint.lg */
    .ibou-footer__inner { padding-inline: var(--ibou-spacing-margin-desktop); }
  }
}

/* ——— src/css/components/layout.css ——— */
/* layout.css — Gabarits de page (08 · Page de résultats, Réponse sourcée, Fil Découverte). Mobile d'abord : une colonne ;
   à partir de breakpoint.lg, lecture 8/12 + colonne droite 4/12. L'ordre du DOM ne change pas : la grille déplace, pas le HTML. */
@layer ibou.components {
  .ibou-page { max-width: var(--ibou-container-page); margin-inline: auto; padding: var(--ibou-spacing-stack-lg) var(--ibou-spacing-margin-mobile) var(--ibou-spacing-section-sm); display: grid; gap: var(--ibou-spacing-stack-lg); }
  .ibou-results { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--ibou-spacing-section-sm) var(--ibou-spacing-gutter-desktop); align-items: start; }
  /* grid-template-columns explicite : une colonne implicite se dimensionne sur le contenu le
     plus large, et un enfant qui contient un défileur horizontal (.ibou-side-by-side, dont les
     pistes ont un plancher de 16 rem) lui impose sa largeur intrinsèque. À 320 px, la colonne
     de droite passait ainsi à 800 px et ses filtres devenaient inatteignables. minmax(0, 1fr)
     interdit à la piste de dépasser son conteneur, quel que soit le contenu qu'on y place. */
  .ibou-results__main { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--ibou-spacing-stack-lg); min-width: 0; }
  .ibou-results__side { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--ibou-spacing-stack-lg); min-width: 0; align-content: start; }
  .ibou-answer { display: grid; gap: var(--ibou-spacing-stack-md); max-width: var(--ibou-container-reading); }
  .ibou-answer__head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ibou-spacing-inline-sm); }
  .ibou-answer__title { margin: 0; font: var(--ibou-typography-heading-3); letter-spacing: var(--ibou-typography-heading-3-letter-spacing); }
  .ibou-answer__body { display: grid; gap: var(--ibou-spacing-stack-md); font-size: var(--ibou-typography-body-lg-font-size); line-height: var(--ibou-typography-body-lg-line-height); letter-spacing: var(--ibou-typography-body-lg-letter-spacing); }
  .ibou-answer__body > p { margin: 0; }
  .ibou-answer__meta { margin: 0; color: var(--ibou-color-text-secondary); font-size: var(--ibou-typography-caption-font-size); line-height: var(--ibou-typography-caption-line-height); display: flex; flex-wrap: wrap; gap: var(--ibou-spacing-inline-sm); }
  .ibou-section-title { margin: 0; display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--ibou-spacing-inline-md); }
  /* Choix de vue au bout de la ligne du titre (07 · Choix de vue, D-68) : la paire flotte au bout de la
     DERNIÈRE ligne du titre, centrée sur elle, et le titre, en ligne, garde exactement les lignes qu'il
     aurait sans elle. En flex, il se repliait pour lui faire place : une ligne de titre de plus (57,6 px
     à texte 200 %). Quand cette dernière ligne ne lui laisse pas ses 80 px (l'écart et les deux icônes),
     la paire passe dessous, au bout : sa hauteur et son centrage, jamais une ligne de titre. Le
     conteneur n'a pas de hauteur de ligne à lui (`line-height: 0`) : seule celle du titre, heading.3,
     compte, et la ligne garde sa hauteur au pixel près. */
  .ibou-section-title:has(> .ibou-view-choice) { display: flow-root; line-height: 0; }
  .ibou-section-title:has(> .ibou-view-choice) > :first-child { display: inline; }
  .ibou-section-title > .ibou-view-choice {
    float: inline-end; margin-inline-start: var(--ibou-spacing-inline-md);
    margin-block-start: calc((var(--ibou-typography-heading-3-font-size) * var(--ibou-typography-heading-3-line-height) - var(--ibou-size-icon-md)) / 2);
  }
  .ibou-filters { display: grid; gap: var(--ibou-spacing-stack-md); }
  .ibou-feed { display: grid; gap: var(--ibou-spacing-stack-md); margin: 0; padding: 0; list-style: none; }
  .ibou-feed > li { display: contents; }
  .ibou-side-by-side { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(16rem, 1fr); gap: var(--ibou-spacing-gutter-mobile); overflow-x: auto; scroll-snap-type: x mandatory; padding-block-end: var(--ibou-space-2); }
  .ibou-side-by-side > * { scroll-snap-align: start; }
  @media (min-width: 768px) { /* breakpoint.md */
    .ibou-page { padding-inline: var(--ibou-spacing-margin-tablet); }
    .ibou-feed[data-columns] { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--ibou-spacing-gutter-tablet); }
    .ibou-side-by-side { grid-auto-flow: row; grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); overflow: visible; }
  }
  @media (min-width: 1024px) { /* breakpoint.lg */
    .ibou-page { padding-inline: var(--ibou-spacing-margin-desktop); }
    .ibou-results { grid-template-columns: minmax(0, 8fr) minmax(0, 4fr); }
    .ibou-feed[data-columns] { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--ibou-spacing-gutter-desktop); }
    .ibou-side-by-side { grid-template-columns: 1fr; }
  }
}

/* ——— src/css/components/mascot.css ——— */
/* mascot.css — Mascotte, yeux et état vide (07 · Mascotte, D-26, D-29 revue). La mascotte ne change jamais de couleur.
   Elle n'exprime que les états d'ibou (accueil, vide, attente, chargement long, panne, gardé, nouveauté, fin de fil),
   jamais un jugement éditorial. Une seule mascotte par écran. */
@layer ibou.components {
  .ibou-mascot { display: block; inline-size: var(--ibou-space-24); block-size: auto; aspect-ratio: 224.5 / 262.42; }
  .ibou-mascot[data-pose="01"] { aspect-ratio: 233.72 / 296.53; }
  .ibou-mascot[data-pose="03"] { aspect-ratio: 326.48 / 297.68; }
  .ibou-mascot[data-pose="04"] { aspect-ratio: 326.49 / 264.89; }
  /* Mascotte en pied dans une expression (#ibou-mascot-expression-0N-*) : viewBox commun aux sept */
  .ibou-mascot[data-expression] { aspect-ratio: 242.61 / 307.8; }
  .ibou-mascot[data-size="sm"] { inline-size: var(--ibou-size-avatar-xl); }
  .ibou-mascot[data-size="lg"] { inline-size: var(--ibou-space-32); }
  .ibou-eyes { display: inline-block; inline-size: var(--ibou-size-icon-md); block-size: auto; aspect-ratio: 188.14 / 112.35; vertical-align: middle; }
  .ibou-eyes[data-size="sm"] { inline-size: var(--ibou-size-icon-sm); }
  .ibou-eyes[data-size="lg"] { inline-size: var(--ibou-size-icon-xl); }
  /* Le liseré du visage (01-marque · Yeux seuls). Règle générale : le contour de la monture doit rester
     à 3:1 au moins sur toute surface où les yeux sont autorisés, et ils le sont partout — toast, infobulle,
     alerte, card, barre de recherche, boîte de chiffre clé, état vide. Le dessin ne change jamais de couleur
     (D-26) : la monture est anthracite et les verres blancs, ce qui suffit sur une surface claire et ne suffit
     nulle part ailleurs. Seuls, les yeux n'ont pas le blanc du visage qui porte cette monture sur la mascotte
     entière : sur l'anthracite il ne restait que deux verres flottants, et le thème sombre en avait fait la règle
     partout — la page, les cards, le beige 950 de l'alerte de chargement long, la barre de recherche où les
     lunettes de D-28 n'étaient plus reconnaissables. Le liseré d'un pixel du blanc du corps (color.white) rend
     ce visage autour du trait. Il est donc posé sans condition : c'est la monture ou lui qui ressort, jamais
     aucun des deux. Sur une surface pâle il ne se voit pas et la monture s'y suffit ; sur les aplats de
     marque (rose 500, vert 500) il se voit, et c'est le blanc du visage que la mascotte entière porte déjà ;
     `npm run check` vérifie le couple sur chaque surface, dans les deux thèmes. */
  :is(.ibou-eyes, .ibou-search__glasses, svg[data-eyes], svg[data-glasses], svg[data-kept]) {
    filter: drop-shadow(1px 0 0 var(--ibou-color-white)) drop-shadow(-1px 0 0 var(--ibou-color-white)) drop-shadow(0 1px 0 var(--ibou-color-white)) drop-shadow(0 -1px 0 var(--ibou-color-white));
  }
  /* État vide (08 · États vides) : mascotte dans l’expression de l’état (sleepy pour un vide, #ibou-mascot-expression-06-sleepy), un titre, une raison, une sortie ; fond beige 100 sans trait */
  .ibou-empty { display: grid; justify-items: center; text-align: center; gap: var(--ibou-spacing-stack-sm); max-width: var(--ibou-container-reading); margin-inline: auto; padding: var(--ibou-spacing-section-md) var(--ibou-spacing-inset-lg); border-radius: var(--ibou-radius-xl); background: var(--ibou-color-surface-sunken); color: var(--ibou-color-text-primary); }
  .ibou-empty > .ibou-mascot { inline-size: var(--ibou-space-16); margin-block-end: var(--ibou-space-2); }
  /* Gabarit de page d'erreur (08 · Erreurs) : `data-size="lg"`. Une 404, une 500 ou l'écran de panne
     au démarrage d'une application ne sont pas un vide dans une page, ce sont la page ; le chapitre 08
     leur prescrit une mascotte de 96 px sur téléphone et 128 px au-delà, contre 64 et 96 pour un état
     vide. Aucune classe ne portait cette taille : les six produits rendaient la panne aux dimensions de
     l'état vide, faute de pouvoir écrire autre chose. */
  .ibou-empty[data-size="lg"] > .ibou-mascot { inline-size: var(--ibou-space-24); }
  .ibou-empty__title { margin: 0; font: var(--ibou-typography-heading-3); letter-spacing: var(--ibou-typography-heading-3-letter-spacing); }
  .ibou-empty__text { margin: 0; color: var(--ibou-color-text-secondary); }
  .ibou-empty__actions { display: grid; gap: var(--ibou-spacing-inline-md); inline-size: 100%; margin-block-start: var(--ibou-space-2); }
  .ibou-empty__actions > .ibou-btn { inline-size: 100%; }
  @media (min-width: 640px) { /* breakpoint.sm */
    .ibou-empty > .ibou-mascot { inline-size: var(--ibou-space-24); }
    .ibou-empty[data-size="lg"] > .ibou-mascot { inline-size: var(--ibou-space-32); }
    .ibou-empty__actions { display: flex; flex-wrap: wrap; justify-content: center; inline-size: auto; }
    .ibou-empty__actions > .ibou-btn { inline-size: auto; }
  }
}

/* ——— src/css/components/divider.css ——— */
/* divider.css — Séparateur (07 · Séparateur). 1 px subtle / default, 2 px strong ; l'espacement sépare mieux que le trait. */
@layer ibou.components {
  .ibou-divider { border: 0; border-block-start: var(--ibou-divider-width) solid var(--ibou-color-border-subtle); margin-block: var(--ibou-spacing-stack-md); }
  .ibou-divider[data-variant="default"] { border-color: var(--ibou-color-border-default); }
  .ibou-divider[data-variant="strong"] { border-block-start-width: var(--ibou-divider-width-strong); border-color: var(--ibou-color-border-strong); margin-block: var(--ibou-spacing-section-sm); }
  .ibou-divider[data-tone="inverse"] { border-color: var(--ibou-color-border-inverse); }
  .ibou-divider[aria-orientation="vertical"] { border: 0; border-inline-start: var(--ibou-divider-width) solid var(--ibou-color-border-default); block-size: var(--ibou-size-icon-md); margin: 0 var(--ibou-spacing-inline-md); align-self: center; }
  .ibou-divider[data-label] { display: flex; align-items: center; gap: var(--ibou-spacing-inline-md); border: 0; color: var(--ibou-color-text-secondary); }
  .ibou-divider[data-label]::before, .ibou-divider[data-label]::after { content: ""; flex: 1; border-block-start: var(--ibou-divider-width) solid var(--ibou-color-border-default); }
}

/* ——— src/css/components/input.css ——— */
/* input.css — Champ de texte et zone de texte (07 · Champ de texte, Zone de texte, D-07, D-31).
   L'anneau de focus est porté par la pilule (:has(input:focus-visible)) avec les mêmes déclarations que base.css ;
   l'outline natif du champ intérieur n'est neutralisé qu'en dehors du contraste élevé. */
@layer ibou.components {
  .ibou-field { display: grid; gap: var(--ibou-spacing-stack-sm); }
  .ibou-field__label { font: var(--ibou-typography-subtitle-sm); letter-spacing: var(--ibou-typography-subtitle-sm-letter-spacing); color: var(--ibou-color-text-primary); }
  .ibou-input {
    display: flex; align-items: center; gap: var(--ibou-input-gap);
    min-height: var(--ibou-input-height-md); padding-inline: var(--ibou-input-padding-x);
    border: var(--ibou-input-border-width) solid var(--ibou-input-border);
    border-radius: var(--ibou-input-radius); background: var(--ibou-input-bg);
    color: var(--ibou-color-text-primary); max-width: 100%;
  }
  .ibou-input > input { flex: 1; min-width: 0; min-height: calc(var(--ibou-input-height-md) - 2 * var(--ibou-input-border-width)); border: 0; background: none; color: inherit; padding: 0;
    font: var(--ibou-typography-body-md);
    letter-spacing: var(--ibou-typography-body-md-letter-spacing); }
  .ibou-input > input::placeholder { color: var(--ibou-color-text-placeholder); opacity: 1; }
  .ibou-input > input::-webkit-search-cancel-button { display: none; }
  .ibou-input > svg { inline-size: var(--ibou-size-icon-sm); block-size: var(--ibou-size-icon-sm); color: var(--ibou-color-icon-secondary); flex: none; }
  .ibou-input:has(> input:focus-visible) {
    outline: var(--ibou-focus-ring-width) solid var(--ibou-color-border-focus);
    outline-offset: var(--ibou-focus-ring-offset);
    box-shadow: 0 0 0 calc(var(--ibou-focus-ring-width) + var(--ibou-focus-ring-offset)) var(--ibou-color-border-focus-halo);
  }
  .ibou-input[data-size="sm"] { min-height: var(--ibou-input-height-sm); }
  .ibou-input[data-size="sm"] > input { min-height: calc(var(--ibou-input-height-sm) - 2 * var(--ibou-input-border-width)); font-size: var(--ibou-typography-body-sm-font-size); }
  .ibou-input[data-size="lg"] { min-height: var(--ibou-input-height-lg); }
  .ibou-input[data-size="lg"] > input { min-height: calc(var(--ibou-input-height-lg) - 2 * var(--ibou-input-border-width)); font-size: var(--ibou-typography-body-lg-font-size); }
  /* Erreur : trait rose 900 + icône × + message (D-07) ; le fond reste blanc (status.error.bg) */
  .ibou-input:has([aria-invalid="true"]) { border-color: var(--ibou-input-border-error); background: var(--ibou-color-status-error-bg); }
  .ibou-input:has([aria-invalid="true"]) > svg[data-role="status"] { color: var(--ibou-color-status-error-icon); }
  .ibou-input:has(> input:disabled) { background: var(--ibou-color-action-disabled-bg); border-color: var(--ibou-color-action-disabled-border); color: var(--ibou-color-text-disabled); }
  .ibou-input:has(> input:disabled) > input { cursor: not-allowed; }
  .ibou-input:has(> input:read-only:not(:disabled)) { background: var(--ibou-color-surface-sunken); border-color: var(--ibou-color-border-default); }
  .ibou-input__action { margin-inline-end: calc(-1 * (var(--ibou-input-padding-x) - var(--ibou-space-1))); }
  .ibou-field__help  { color: var(--ibou-color-text-secondary); font-size: var(--ibou-typography-caption-font-size); line-height: var(--ibou-typography-caption-line-height); }
  /* Le message d'erreur est le texte qu'on doit lire quand ça va mal : body.sm (14 px), pas caption */
  .ibou-field__error { display: flex; align-items: flex-start; gap: var(--ibou-spacing-inline-xs); color: var(--ibou-color-status-error-text); font-size: var(--ibou-typography-body-sm-font-size); line-height: var(--ibou-typography-body-sm-line-height); }
  .ibou-field__error:empty { display: none; }
  .ibou-field__error > svg { inline-size: var(--ibou-size-icon-xs); block-size: var(--ibou-size-icon-xs); flex: none; margin-block-start: 0.2em; color: var(--ibou-color-status-error-icon); }

  .ibou-textarea {
    display: block; width: 100%; max-width: var(--ibou-container-reading);
    min-height: calc(3 * var(--ibou-font-line-height-relaxed) * 1em + 2 * var(--ibou-spacing-inset-md));
    padding: var(--ibou-spacing-inset-md) var(--ibou-input-padding-x);
    border: var(--ibou-input-border-width) solid var(--ibou-input-border);
    border-radius: var(--ibou-input-textarea-radius); background: var(--ibou-input-bg);
    color: var(--ibou-color-text-primary); resize: vertical; field-sizing: content;
    font: var(--ibou-typography-body-md);
    letter-spacing: var(--ibou-typography-body-md-letter-spacing);
  }
  .ibou-textarea::placeholder { color: var(--ibou-color-text-placeholder); opacity: 1; }
  .ibou-textarea[aria-invalid="true"] { border-color: var(--ibou-input-border-error); background: var(--ibou-color-status-error-bg); }
  .ibou-textarea:disabled { background: var(--ibou-color-action-disabled-bg); border-color: var(--ibou-color-action-disabled-border); color: var(--ibou-color-text-disabled); cursor: not-allowed; }
  .ibou-textarea:read-only:not(:disabled) { background: var(--ibou-color-surface-sunken); border-color: var(--ibou-color-border-default); }
  .ibou-textarea__count { text-align: end; color: var(--ibou-color-text-secondary); font-size: var(--ibou-typography-caption-font-size); }
  .ibou-textarea__count[data-over] { color: var(--ibou-color-status-error-text); }
}
@media (forced-colors: none) {
  .ibou-input > input:focus-visible { outline: none; box-shadow: none; }
}

/* ——— src/css/components/stat-card.css ——— */
/* stat-card.css — Chiffre clé (07 · Chiffre clé, D-06, D-22). Card blanche, trait fin gris, shadow.sm (exception documentée à D-33),
   boîte d'icône rose 500, chiffre en New Spirit. Attend une valeur, une unité et une date. */
@layer ibou.components {
  .ibou-stat {
    display: grid; gap: var(--ibou-spacing-stack-sm); align-content: start; margin: 0; padding: var(--ibou-stat-card-padding);
    border: var(--ibou-stat-card-border-width) solid var(--ibou-stat-card-border); border-radius: var(--ibou-stat-card-radius);
    background: var(--ibou-color-surface-default); box-shadow: var(--ibou-shadow-sm); color: var(--ibou-color-text-primary);
  }
  .ibou-stat__icon { display: grid; place-items: center; inline-size: var(--ibou-stat-card-icon-box-size); block-size: var(--ibou-stat-card-icon-box-size); border-radius: var(--ibou-stat-card-icon-box-radius); background: var(--ibou-stat-card-icon-box-bg); color: var(--ibou-color-icon-on-brand); }
  .ibou-stat__icon > svg { inline-size: var(--ibou-size-icon-sm); block-size: var(--ibou-size-icon-sm); }
  .ibou-stat__icon > svg[data-eyes] { inline-size: var(--ibou-size-icon-md); block-size: auto; aspect-ratio: 188.14 / 112.35; }
  .ibou-stat__value { margin: 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--ibou-spacing-inline-sm); font: var(--ibou-typography-figure-lg); letter-spacing: var(--ibou-typography-figure-lg-letter-spacing); font-variant-numeric: tabular-nums; }
  .ibou-stat__label { margin: 0; color: var(--ibou-color-text-secondary); font-size: var(--ibou-typography-body-sm-font-size); line-height: var(--ibou-typography-body-sm-line-height); }
  .ibou-stat__date { margin: 0; color: var(--ibou-color-text-secondary); font-size: var(--ibou-typography-caption-font-size); }
  .ibou-stat[data-density="compact"] .ibou-stat__value { font-size: var(--ibou-typography-figure-md-font-size); line-height: var(--ibou-typography-figure-md-line-height); }
  .ibou-stat[data-tone="inverse"] { background: var(--ibou-color-surface-inverse); color: var(--ibou-color-text-inverse); border-color: var(--ibou-color-border-inverse); box-shadow: none; }
  .ibou-stat[data-tone="inverse"] .ibou-stat__label, .ibou-stat[data-tone="inverse"] .ibou-stat__date { color: var(--ibou-color-text-inverse-secondary); }
  .ibou-stat[data-interactive] { border: var(--ibou-border-width-thin) solid var(--ibou-color-border-strong); box-shadow: none; }
  @media (hover: hover) { .ibou-stat[data-interactive]:hover { background: var(--ibou-color-action-secondary-bg-hover); } }
  .ibou-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); gap: var(--ibou-spacing-gutter-mobile); align-items: stretch; }
  @media (min-width: 768px) { /* breakpoint.md */
    .ibou-stats { gap: var(--ibou-spacing-gutter-desktop); }
  }
}

/* ——— src/css/components/choice.css ——— */
/* choice.css — Case à cocher, bouton radio, interrupteur (07 · Case, Radio, Interrupteur).
   Input natif masqué mais présent ; case, cercle et piste dessinés en CSS ; coche et point en currentColor (contraste élevé). */
@layer ibou.components {
  /* Case à cocher */
  .ibou-check { position: relative; display: grid; grid-template-columns: var(--ibou-checkbox-size) 1fr; column-gap: var(--ibou-spacing-inline-md); align-items: start; min-height: var(--ibou-size-target-min); padding-block: calc((var(--ibou-size-target-min) - var(--ibou-checkbox-size)) / 2); color: var(--ibou-color-text-primary); }
  .ibou-check > input { position: absolute; opacity: 0; inline-size: 1px; block-size: 1px; margin: 0; }
  .ibou-check__box {
    inline-size: var(--ibou-checkbox-size); block-size: var(--ibou-checkbox-size); display: grid; place-items: center;
    border: var(--ibou-checkbox-border-width) solid var(--ibou-color-border-strong); border-radius: var(--ibou-checkbox-radius);
    background: var(--ibou-color-surface-default); color: var(--ibou-color-icon-primary);
    transition: background-color var(--ibou-motion-duration-fast) var(--ibou-motion-easing-standard);
  }
  .ibou-check__box > svg { inline-size: var(--ibou-size-icon-xs); block-size: var(--ibou-size-icon-xs); display: none; }
  @media (hover: hover) { .ibou-check:hover .ibou-check__box { background: var(--ibou-color-action-secondary-bg-hover); } }
  .ibou-check:has(> input:checked) .ibou-check__box, .ibou-check:has(> input:indeterminate) .ibou-check__box { background: var(--ibou-checkbox-checked-bg); color: var(--ibou-color-icon-on-brand); }
  .ibou-check:has(> input:checked) .ibou-check__box > svg[data-check], .ibou-check:has(> input:indeterminate) .ibou-check__box > svg[data-mixed] { display: block; }
  .ibou-check:has(> input:focus-visible) .ibou-check__box {
    outline: var(--ibou-focus-ring-width) solid var(--ibou-color-border-focus); outline-offset: var(--ibou-focus-ring-offset);
    box-shadow: 0 0 0 calc(var(--ibou-focus-ring-width) + var(--ibou-focus-ring-offset)) var(--ibou-color-border-focus-halo);
  }
  .ibou-check:has(> input:disabled) { color: var(--ibou-color-text-disabled); }
  .ibou-check:has(> input:disabled) .ibou-check__box { background: var(--ibou-color-action-disabled-bg); border-color: var(--ibou-color-action-disabled-border); color: var(--ibou-color-icon-disabled); }
  .ibou-check:has([aria-invalid="true"]) .ibou-check__box { border-color: var(--ibou-input-border-error); background: var(--ibou-color-status-error-bg); }
  .ibou-check__desc { grid-column: 2; color: var(--ibou-color-text-secondary); font-size: var(--ibou-typography-caption-font-size); line-height: var(--ibou-typography-caption-line-height); }
  .ibou-check-group { display: grid; border: 0; padding: 0; margin: 0; min-inline-size: 0; }
  .ibou-check-group > legend, .ibou-radio-group > legend { padding: 0; margin-block-end: var(--ibou-spacing-stack-sm); font: var(--ibou-typography-subtitle-sm); }
  .ibou-check-group[data-list] > .ibou-check + .ibou-check { border-block-start: var(--ibou-divider-width) solid var(--ibou-color-border-subtle); }

  /* Bouton radio */
  .ibou-radio { position: relative; display: grid; grid-template-columns: var(--ibou-radio-size) 1fr; column-gap: var(--ibou-spacing-inline-md); align-items: center; min-height: var(--ibou-size-target-min); color: var(--ibou-color-text-primary); }
  .ibou-radio > input { position: absolute; opacity: 0; inline-size: 1px; block-size: 1px; margin: 0; }
  .ibou-radio__circle { inline-size: var(--ibou-radio-size); block-size: var(--ibou-radio-size); display: grid; place-items: center;
    border: var(--ibou-radio-border-width) solid var(--ibou-color-border-strong); border-radius: var(--ibou-radio-radius); background: var(--ibou-color-surface-default); color: var(--ibou-color-icon-primary);
    transition: background-color var(--ibou-motion-duration-fast) var(--ibou-motion-easing-standard); }
  .ibou-radio__circle::after { content: ""; inline-size: var(--ibou-radio-dot-size); block-size: var(--ibou-radio-dot-size); border-radius: var(--ibou-radius-full); background: currentColor; opacity: 0; }
  @media (hover: hover) { .ibou-radio:hover .ibou-radio__circle { background: var(--ibou-color-action-secondary-bg-hover); } }
  .ibou-radio:has(> input:checked) .ibou-radio__circle { background: var(--ibou-radio-checked-bg); color: var(--ibou-color-icon-on-brand); }
  .ibou-radio:has(> input:checked) .ibou-radio__circle::after { opacity: 1; }
  .ibou-radio:has(> input:focus-visible) .ibou-radio__circle {
    outline: var(--ibou-focus-ring-width) solid var(--ibou-color-border-focus); outline-offset: var(--ibou-focus-ring-offset);
    box-shadow: 0 0 0 calc(var(--ibou-focus-ring-width) + var(--ibou-focus-ring-offset)) var(--ibou-color-border-focus-halo);
  }
  .ibou-radio:has(> input:disabled) { color: var(--ibou-color-text-disabled); }
  .ibou-radio:has(> input:disabled) .ibou-radio__circle { background: var(--ibou-color-action-disabled-bg); border-color: var(--ibou-color-action-disabled-border); color: var(--ibou-color-icon-disabled); }
  .ibou-radio__desc { grid-column: 2; color: var(--ibou-color-text-secondary); font-size: var(--ibou-typography-caption-font-size); }
  .ibou-radio-group { display: grid; gap: var(--ibou-spacing-stack-xs); border: 0; padding: 0; margin: 0; min-inline-size: 0; }
  /* Flex et non `grid-auto-flow: column` : la grille fabriquait autant de colonnes qu'il y
     avait d'options, quelle que soit la place, et trois options faisaient déborder la page à
     320 px (WCAG 1.4.10). En ligne quand ça tient, en colonnes quand ça ne tient plus. */
  .ibou-radio-group[data-orientation="horizontal"] { display: flex; flex-wrap: wrap; align-items: center; column-gap: var(--ibou-spacing-inline-xl); row-gap: var(--ibou-spacing-stack-xs); }
  .ibou-radio-group[data-orientation="horizontal"] > legend { flex-basis: 100%; }
  .ibou-radio-group:has([aria-invalid="true"]) .ibou-radio__circle { border-color: var(--ibou-input-border-error); background: var(--ibou-color-status-error-bg); }
  /* Variante cards : le label englobe une card, la sélection se lit au trait plus au point */
  .ibou-radio[data-card] { grid-template-columns: var(--ibou-radio-size) 1fr; padding: var(--ibou-card-padding-sm); border: var(--ibou-card-border-width) solid var(--ibou-card-border-active); border-radius: var(--ibou-card-radius); background: var(--ibou-card-bg); }
  .ibou-radio[data-card]:has(> input:checked) { background: var(--ibou-color-action-selected-bg); border-color: var(--ibou-color-action-selected-border); }

  /* Interrupteur : <button role="switch" aria-checked> ; coche dans le curseur quand activé (la couleur n'est jamais seule) */
  .ibou-switch { display: inline-flex; align-items: center; justify-content: space-between; gap: var(--ibou-spacing-inline-md); min-height: var(--ibou-size-target-min); color: var(--ibou-color-text-primary); }
  .ibou-switch[data-label="right"] { flex-direction: row-reverse; justify-content: flex-end; }
  .ibou-switch__track {
    position: relative; inline-size: var(--ibou-switch-width); block-size: var(--ibou-switch-height); flex: none;
    border: var(--ibou-border-width-thin) solid var(--ibou-color-border-strong); border-radius: var(--ibou-radius-full);
    background: var(--ibou-switch-off-bg); color: var(--ibou-color-icon-primary);
    transition: background-color var(--ibou-motion-duration-fast) var(--ibou-motion-easing-standard);
  }
  .ibou-switch__track::before { content: ""; position: absolute; inset: 50% auto auto 50%; translate: -50% -50%; min-width: var(--ibou-size-target-min); min-height: var(--ibou-size-target-min); }
  .ibou-switch__track::after {
    content: ""; position: absolute; top: 50%; inset-inline-start: 2px; translate: 0 -50%; box-sizing: border-box;
    inline-size: calc(var(--ibou-switch-height) - 8px); block-size: calc(var(--ibou-switch-height) - 8px);
    border: var(--ibou-border-width-thin) solid var(--ibou-color-border-strong); border-radius: var(--ibou-radius-full);
    background: var(--ibou-color-surface-default); transition: inset-inline-start var(--ibou-motion-duration-fast) var(--ibou-motion-easing-standard);
  }
  .ibou-switch__track[aria-checked="true"] { background: var(--ibou-switch-on-bg); }
  .ibou-switch__track[aria-checked="true"]::after { inset-inline-start: calc(var(--ibou-switch-width) - var(--ibou-switch-height) + 2px); }
  .ibou-switch__track > svg { position: absolute; top: 50%; inset-inline-start: calc(var(--ibou-switch-width) - var(--ibou-switch-height) + 2px + (var(--ibou-switch-height) - 8px) / 2); translate: -50% -50%; inline-size: 10px; block-size: 10px; display: none; z-index: 1; pointer-events: none; }
  .ibou-switch__track[aria-checked="true"] > svg { display: block; }
  @media (hover: hover) { .ibou-switch__track:hover::after { background: var(--ibou-color-action-secondary-bg-hover); } }
  .ibou-switch__track:disabled { background: var(--ibou-color-action-disabled-bg); border-color: var(--ibou-color-action-disabled-border); }
  .ibou-switch__track:disabled::after { background: var(--ibou-color-action-disabled-bg); border-color: var(--ibou-color-action-disabled-border); }
  .ibou-switch:has(> .ibou-switch__track:disabled) { color: var(--ibou-color-text-disabled); }
  .ibou-switch__state { color: var(--ibou-color-text-secondary); font-size: var(--ibou-typography-caption-font-size); }
}

/* ——— polices (D-38 : New Spirit sous licence Newlyn, servie par ibou.io seul) ——— */
@font-face { font-family: "New Spirit"; font-weight: 400; font-style: normal; font-display: swap; src: url("fonts/new-spirit/NewSpirit-Regular.woff2") format("woff2"); }
@font-face { font-family: "New Spirit"; font-weight: 400; font-style: italic; font-display: swap; src: url("fonts/new-spirit/NewSpirit-RegularItalic.woff2") format("woff2"); }
@font-face { font-family: "New Spirit"; font-weight: 500; font-style: normal; font-display: swap; src: url("fonts/new-spirit/NewSpirit-Medium.woff2") format("woff2"); }
@font-face { font-family: "Poppins"; font-weight: 400; font-style: normal; font-display: swap; src: url("fonts/poppins/Poppins-Regular.woff2") format("woff2"); }
@font-face { font-family: "Poppins"; font-weight: 400; font-style: italic; font-display: swap; src: url("fonts/poppins/Poppins-Italic.woff2") format("woff2"); }
@font-face { font-family: "Poppins"; font-weight: 500; font-style: normal; font-display: swap; src: url("fonts/poppins/Poppins-Medium.woff2") format("woff2"); }
@font-face { font-family: "Poppins"; font-weight: 600; font-style: normal; font-display: swap; src: url("fonts/poppins/Poppins-SemiBold.woff2") format("woff2"); }
/* Replis calibrés (D-23), copiés de assets/fonts/fonts.css */
@font-face { font-family: "New Spirit Fallback"; src: local("Iowan Old Style"), local("Palatino Linotype"), local("Georgia"); size-adjust: 104%; ascent-override: 92%; descent-override: 26%; }
@font-face { font-family: "Poppins Fallback"; src: local("Helvetica Neue"), local("Arial"); size-adjust: 108%; ascent-override: 98%; descent-override: 33%; }
