/* lyodge-kit — thème Braise. Feuille assemblée : ne pas éditer, voir design/. */

/* ── design/fondations/echelles.css ── */
/* Les échelles communes à TOUS les thèmes — ce qui ne change pas d'une marque à l'autre :
 * espacements, tailles de texte, interlignes, graisses, cible tactile, durées, couches.
 * Un thème ne touche jamais à ce fichier ; il ne décide que des couleurs, des polices, des
 * rayons, des ombres et du registre des titres (`themes/<nom>/jetons.css`).
 *
 * Un jeton nomme un RÔLE ou un CRAN, jamais une valeur. */
:root {
  /* Espacements — échelle unique, pas de valeur en dur dans un composant.
   * Les deux crans hauts servent aux pages publiques ; un back-office vit dans les crans bas. */
  --espace-1: 4px;
  --espace-2: 8px;
  --espace-3: 16px;
  --espace-4: 24px;
  --espace-5: 40px;
  --espace-6: 64px;
  --espace-7: 96px;

  /* Le plancher d'une cible tactile, et la hauteur d'un contrôle dense (bureau seulement). */
  --cible-tactile: 44px;
  --cible-dense: 32px;

  /* Texte */
  --taille-mention: 0.8125rem;
  --taille-petite: 0.875rem;
  --taille-base: 1rem;
  --taille-intertitre: 1.125rem;
  --taille-titre: 1.5rem;
  --taille-hero: 2.25rem;

  --interligne-titre: 1.15;
  --interligne-texte: 1.55;

  --graisse-normale: 400;
  --graisse-moyenne: 500;
  --graisse-forte: 600;

  --interlettrage-mention: 0.09em;

  /* Un code d'accès (`.saisie-code`, `.code-affiche`) : lisible du fond d'une salle. */
  --taille-code: 2.5rem;
  --interlettrage-code: 0.3em;

  /* Un chiffre d'affichage (`.chiffre-hero`) : lu d'un coup d'œil, le téléphone à bout de bras
   * entre deux séries. Au-dessus du code, qui se lit assis. */
  --taille-affichage: 3.5rem;

  /* Focus — sans jeton dédié, le focus clavier est celui du navigateur : à la fois un trou
   * d'accessibilité et le signal le plus net qu'une interface n'est pas finie. La couleur
   * vient du thème. */
  --focus-epaisseur: 2px;
  --focus-ecart: 2px;

  --rayon-net: 0;

  /* Mouvement — court, et coupé par `prefers-reduced-motion` (fondations/base.css). */
  --duree-courte: 120ms;
  --duree: 200ms;

  /* Largeurs de page (fondations/mise-en-page.css). */
  --cadre-etroit: 28rem;
  --cadre: 45rem;
  --cadre-large: 75rem;

  /* Couches — l'ordre d'empilement, écrit une fois. */
  --couche-barre: 30;
  --couche-dialogue: 50;
  --couche-notification: 70;
}

/* ── design/themes/braise/jetons.css ── */
/* Braise — le thème rouge et noir du parc (repstack, nota).
 *
 * Sombre seulement : c'est l'identité de repstack (« this IS dark mode »), dont la palette est la
 * source de ce thème — noir profond, gris neutres, un rouge vif pour la marque, des couleurs
 * d'état franches. Trois écarts avec repstack, tous pour passer le contraste AA :
 *   - le rouge de marque en fond descend de #E63946 à #DC2F3E : le blanc dessus passait à 4.2:1 ;
 *   - le rouge écrit sur fond sombre (encre, focus) monte à #F0525F : #E63946 tombait à 4.3:1
 *     sur une carte ;
 *   - le gris « dim » (#6B6B6B, 3.7:1) n'est plus une couleur de texte, seulement de bordure.
 *
 * Polices : celles de repstack, désormais auto-hébergées (`polices/`) au lieu d'être servies par
 * deux CDN — Satoshi pour les titres, DM Sans pour le texte, Geist Mono pour les chiffres. DM Sans
 * et Geist Mono sont sous OFL. Satoshi est sous ITF Free Font License : auto-hébergement permis
 * pour nos propres sites et applications, fichier officiel non modifié (ni sous-ensemble, ni
 * conversion), et JAMAIS redistribué à un tiers — d'où `"private": true` dans package.json,
 * vérifié par le test des thèmes. Détail dans `polices/LISEZMOI.md`.
 *
 * Braise 2 (kit 3.6.0, revue design de nota du 2026-10-01) : les neutres se réchauffent — un
 * noir de braise plutôt qu'un gris d'écran, à contraste égal —, les rayons s'arrondissent
 * (8 / 10 / 14), et le rouge n'est plus que l'ACTION : ce qui est en cours (aujourd'hui, la page
 * active, le sur-titre d'un en-tête) prend `--encre-courant`, une braise orangée. Avant, le rouge
 * disait à la fois « clique », « aujourd'hui », « supprime » et « en retard ».
 *
 * Mêmes noms de jetons qu'Outremer, et la même règle : chaque couleur est listée dans `paires.ts`,
 * sinon le test échoue. */
@font-face {
  font-family: 'Satoshi';
  src: url('polices/satoshi-variable.woff2') format('woff2');
  font-weight: 300 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'DM Sans';
  src: url('polices/dm-sans-variable.woff2') format('woff2');
  font-weight: 100 1000;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Geist Mono';
  src: url('polices/geist-mono-variable.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

:root {
  color-scheme: dark;

  /* Surfaces — du plus profond au plus proche */
  --fond-page: #0d0b0a;
  --texte-page: #f4efe9;

  --fond-carte: #171412;
  --texte-carte: #f4efe9;

  /* Les crans de l'échelle des neutres font au moins 1.14:1 entre voisins (revue du
   * 2026-09-24) : à #1c1c1c / #262626, la pastille par défaut et le segment actif ne se
   * voyaient pas. */
  --fond-discret: #221e1b;
  --texte-discret: #a59d95;

  --fond-eleve: #2c2723;
  --texte-eleve: #f4efe9;

  --fond-champ: #171412;
  --texte-champ: #f4efe9;

  /* L'accent (choisi, en cours, pressé) est une surface NEUTRE, un cran au-dessus de l'élevée :
   * dans un thème dont la marque est rouge, un accent rouge sombre (#2a1014) était à 1.01:1 de
   * la surface d'erreur — un bouton pressé avait l'air d'une alerte. La marque reste dans les
   * bordures et soulignements que les composants dessinent en `--fond-marque`. */
  --fond-accent: #36302b;
  --texte-accent: #f4efe9;

  --fond-marque: #dc2f3e;
  --texte-marque: #ffffff;
  --texte-marque-discret: #ffffff;
  --fond-marque-survol: #c4263a;

  /* L'inverse d'un thème sombre est clair : une barre claire, un bouton sur fond clair. */
  --fond-inverse: #f4efe9;
  --texte-inverse: #0d0b0a;
  --texte-inverse-discret: #4d463f;
  --fond-inverse-survol: #e4ddd4;

  /* États — une teinte sombre et un texte clair de la même famille */
  --fond-succes: #0c2416;
  --texte-succes: #4ade80;
  --fond-attention: #2a1d05;
  --texte-attention: #fbbf24;
  --fond-erreur: #2a0d13;
  --texte-erreur: #fda4af;
  --fond-info: #0b1c30;
  --texte-info: #93c5fd;

  /* Encres */
  --encre-marque: #f0525f;
  --encre-discrete: #a59d95;
  --encre-succes: #22c55e;
  --encre-attention: #f59e0b;
  --encre-erreur: #fb7185;
  --encre-info: #60a5fa;
  /* Ce qui est EN COURS : aujourd'hui, la page active, le sur-titre d'un en-tête. Une braise, pas
     la marque — le rouge reste aux actions, et une colonne du jour n'a plus l'air d'une alerte. */
  --encre-courant: #ff8a4c;

  /* Traits */
  --bordure: #2a2521;
  --bordure-forte: #3a332e;
  --bordure-champ: #6e655d;
  --bordure-inverse: #cbc3b9;
  --filet: #f4efe9;

  --focus-couleur: #f0525f;
  --focus-couleur-inverse: #0d0b0a;

  --voile: rgb(0 0 0 / 0.66);

  /* Séries — les couleurs d'activité de repstack, dans son ordre. La première est le rouge de
   * marque posé (et non un quatrième rouge : #e63946 était celui de repstack avant l'AA). */
  --serie-1: var(--encre-marque);
  --serie-2: #60a5fa;
  --serie-3: #22c55e;
  --serie-4: #f59e0b;
  --serie-5: #a78bfa;
  --serie-6: #f472b6;

  /* La chaleur (carte jour × heure) : une seule teinte en paliers d'opacité. Le bleu de la série 2,
   * pas une teinte chaude : le rouge est l'action, la braise ce qui est en cours, l'ambre une
   * alerte — une case chaude ne doit dire aucun des trois. */
  --chaleur: var(--serie-2);

  /* Forme — un cran plus ronds que ceux de repstack (6 / 8 / 12) depuis Braise 2 : une carte à
     14 px se lit comme un objet qu'on prend, plus comme une case de tableur. */
  --rayon-petit: 8px;
  --rayon: 10px;
  --rayon-grand: 14px;
  --rayon-plein: 999px;

  --ombre-legere: 0 1px 2px rgb(0 0 0 / 0.5);
  --ombre-portee: 0 16px 40px rgb(0 0 0 / 0.6);

  /* Typographie — un registre sportif : titres gras et serrés, chiffres en chasse fixe */
  --police-titre: 'Satoshi', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --police: 'DM Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --police-mono: 'Geist Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  --police-chiffres: var(--police-mono);

  --graisse-titre: 700;
  --interlettrage-titre: -0.02em;
  --casse-titre: none;
  --casse-mention: uppercase;
}

/* ── design/fondations/base.css ── */
/* Les règles de base, communes à tous les thèmes. Elles ne lisent que des jetons : aucune
 * couleur, aucune police n'est écrite ici (vérifié par design/test/themes.test.ts). */
*,
*::before,
*::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--fond-page);
  color: var(--texte-page);
  font-family: var(--police);
  font-size: var(--taille-base);
  line-height: var(--interligne-texte);
  -webkit-text-size-adjust: 100%;
  /* Les contrôles natifs sans classe (curseur `range`, `progress`) prennent la marque, pas le
     bleu du navigateur. Hérité par tout ce qui suit. */
  accent-color: var(--fond-marque);
}

/* Les titres suivent l'échelle : sans taille ici, chaque app héritait de celles du navigateur
   (32 / 24 / 18.7 px) alors que l'échelle dit 36 / 24 / 18. h4 à h6 gardent la police de titre
   à la taille du texte : un titre de rang bas se distingue par sa graisse, pas par sa taille. */
h1,
h2,
h3,
h4,
h5,
h6 {
  font-family: var(--police-titre);
  font-weight: var(--graisse-titre);
  letter-spacing: var(--interlettrage-titre);
  line-height: var(--interligne-titre);
  text-transform: var(--casse-titre);
}

h1 {
  font-size: var(--taille-hero);
}

h2 {
  font-size: var(--taille-titre);
}

h3 {
  font-size: var(--taille-intertitre);
}

h4,
h5,
h6 {
  font-size: var(--taille-base);
}

a {
  color: var(--encre-marque);
  text-underline-offset: 0.15em;
}

code,
kbd,
pre,
samp {
  font-family: var(--police-mono);
}

/* Une étiquette de rubrique, hors composant. Ses deux alias de composants, `.tuile-libelle`
   et `.tableau th`, en reprennent le corps dans tuile.css : base.css part aussi dans la feuille
   `-jetons`, qui ne doit porter aucun sélecteur de composant. */
.mention {
  font-size: var(--taille-mention);
  font-weight: var(--graisse-moyenne);
  letter-spacing: var(--interlettrage-mention);
  text-transform: var(--casse-mention);
  color: var(--encre-discrete);
}

/* Du texte secondaire, hors formulaire : la date d'une ligne, le détail sous un titre, une
   note dans un dépli. `.champ-aide` est le même registre, réservé à l'aide d'un champ. */
.discret {
  font-size: var(--taille-petite);
  color: var(--encre-discrete);
}

/* Lu par un lecteur d'écran, absent à l'écran. */
.visuellement-cache {
  position: absolute !important;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* `display: grid` sur un composant écrase le `display: none` par défaut de [hidden] : sans
   cette règle, le formulaire de connexion reste affiché une fois connecté. Trouvé à l'écran,
   pas en test. Ici plutôt que dans chaque front : c'est la même règle pour la même raison,
   et deux copies finissent toujours par diverger. */
[hidden] {
  display: none !important;
}

:focus-visible {
  outline: var(--focus-epaisseur) solid var(--focus-couleur);
  outline-offset: var(--focus-ecart);
}

/* Une surface inverse (barre sombre, panneau de régie, écran projeté) est un CONTEXTE : le
   conteneur rebranche les jetons de surface sur leurs pendants inverses, et tout composant
   qu'il contient suit — carte, réponse, classement, étiquette, champ — sans variante à
   écrire. Chaque paire rebranchée est une paire que le thème vérifie déjà (inverse / son
   texte, inverse-survol / son texte, inverse / texte discret). `--fond-marque` ne bouge pas :
   le bouton principal reste celui de la marque, sur toute surface. */
.sur-inverse {
  --focus-couleur: var(--focus-couleur-inverse);

  --fond-page: var(--fond-inverse);
  --texte-page: var(--texte-inverse);
  --fond-carte: var(--fond-inverse);
  --texte-carte: var(--texte-inverse);
  --fond-champ: var(--fond-inverse);
  --texte-champ: var(--texte-inverse);
  --fond-discret: var(--fond-inverse-survol);
  --texte-discret: var(--texte-inverse);
  --fond-eleve: var(--fond-inverse-survol);
  --texte-eleve: var(--texte-inverse);
  --fond-accent: var(--fond-inverse-survol);
  --texte-accent: var(--texte-inverse);
  --encre-discrete: var(--texte-inverse-discret);
  --encre-marque: var(--texte-inverse);
  --bordure: var(--bordure-inverse);
  --bordure-forte: var(--texte-inverse-discret);
  --bordure-champ: var(--texte-inverse-discret);
  --filet: var(--texte-inverse);

  color: var(--texte-page);
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ── design/fondations/mise-en-page.css ── */
/* Quatre primitives de mise en page, et rien de plus : une app qui en veut une cinquième
 * l'écrit chez elle avec les mêmes jetons.
 *
 *   .cadre    la largeur de lecture, centrée          (.cadre-etroit, .cadre-large)
 *   .pile     des blocs l'un sous l'autre, espacés     (.pile-serree, .pile-aeree)
 *   .rangee   des éléments côte à côte, qui passent à la ligne
 *   .grille   des colonnes qui se remplissent seules   (--grille-min pour la largeur mini)
 */
.cadre {
  width: 100%;
  max-width: var(--cadre);
  margin-inline: auto;
  padding-inline: var(--espace-3);
}

.cadre-etroit {
  max-width: var(--cadre-etroit);
}

.cadre-large {
  max-width: var(--cadre-large);
}

.pile {
  display: flex;
  flex-direction: column;
  gap: var(--pile-ecart, var(--espace-3));
}

.pile-serree {
  --pile-ecart: var(--espace-2);
}

.pile-aeree {
  --pile-ecart: var(--espace-5);
}

.rangee {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--rangee-ecart, var(--espace-2));
}

.rangee-ecartee {
  justify-content: space-between;
}

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

/* ── design/composants/bouton.css ── */
/* Bouton — une action.
 *
 *   <button class="bouton">Publier</button>                         primaire (par défaut)
 *   .bouton-secondaire  .bouton-fantome  .bouton-danger (surface d'erreur pleine)  .bouton-succes (surface de succès pleine)
 *   .bouton-discret (un fantôme en encre discrète : l'action secondaire d'une ligne)
 *   .bouton-inverse (sur une barre inverse)  .bouton-flottant (rond, ancré en bas à droite)
 *   .bouton-petit (dense, bureau seulement)  .bouton-icone (carré)  .bouton-large (pleine largeur)
 *
 * Une variante par classe, un état par ATTRIBUT — `disabled`, `aria-disabled`, `aria-pressed`,
 * `aria-busy` — pour que ce qu'on voit soit aussi ce qu'annonce un lecteur d'écran. Un bouton
 * `.bouton-icone` porte un `aria-label` : sans lui, il n'a pas de nom. */
.bouton {
  --_fond: var(--fond-marque);
  --_texte: var(--texte-marque);
  --_bordure: var(--fond-marque);
  --_survol: var(--fond-marque-survol);
  --_bordure-survol: var(--fond-marque-survol);

  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--espace-2);
  min-height: var(--cible-tactile);
  padding: 0 var(--espace-3);
  border: 1px solid var(--_bordure);
  border-radius: var(--rayon);
  background: var(--_fond);
  color: var(--_texte);
  font: inherit;
  font-weight: var(--graisse-moyenne);
  line-height: 1.2;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition:
    background-color var(--duree-courte),
    border-color var(--duree-courte),
    color var(--duree-courte);
}

.bouton:hover:not(:disabled, [aria-disabled='true'], [aria-busy='true']) {
  background: var(--_survol);
  border-color: var(--_bordure-survol);
}

.bouton-secondaire {
  --_fond: transparent;
  --_texte: var(--encre-marque);
  --_bordure: var(--bordure-champ);
  --_survol: var(--fond-accent);
  --_bordure-survol: var(--encre-marque);
}

.bouton-fantome,
.bouton-discret {
  --_fond: transparent;
  --_texte: var(--encre-marque);
  --_bordure: transparent;
  --_survol: var(--fond-accent);
  --_bordure-survol: transparent;
}

/* Discret : l'action secondaire d'une ligne ou d'une barre — modifier, supprimer, fermer,
   précédent. Un fantôme, en encre discrète : dans un écran qui en aligne dix, dix encres de
   marque crieraient plus fort que le contenu. Au survol, il prend le texte de sa surface. */
.bouton-discret {
  --_texte: var(--encre-discrete);
}

.bouton-discret:hover:not(:disabled, [aria-disabled='true'], [aria-busy='true']) {
  color: var(--texte-accent);
}

/* Le danger se distingue par sa FORME — une surface d'erreur pleine, bordée — et pas seulement
   par sa couleur : dans un thème dont la marque est rouge (Braise), un bouton danger « texte
   rouge » ne se distinguait plus d'un bouton fantôme que par un filet. */
.bouton-danger {
  --_fond: var(--fond-erreur);
  --_texte: var(--texte-erreur);
  --_bordure: var(--encre-erreur);
  --_survol: var(--fond-erreur);
  --_bordure-survol: var(--texte-erreur);

  font-weight: var(--graisse-forte);
}

/* Sa surface ne change pas au survol (le fond d'erreur n'a pas de variante) : un cerne
   intérieur double la bordure, et le survol se voit. */
.bouton-danger:hover:not(:disabled, [aria-disabled='true'], [aria-busy='true']) {
  box-shadow: inset 0 0 0 1px var(--texte-erreur);
}

/* Le succès clôt un effort — « C'est fait » en séance. Même forme que le danger : une surface
   pleine et bordée, parce qu'un vert écrit sur du noir n'est plus qu'un fantôme de plus. */
.bouton-succes {
  --_fond: var(--fond-succes);
  --_texte: var(--texte-succes);
  --_bordure: var(--encre-succes);
  --_survol: var(--fond-succes);
  --_bordure-survol: var(--texte-succes);

  font-weight: var(--graisse-forte);
}

.bouton-succes:hover:not(:disabled, [aria-disabled='true'], [aria-busy='true']) {
  box-shadow: inset 0 0 0 1px var(--texte-succes);
}

.bouton-inverse {
  --_fond: transparent;
  --_texte: var(--texte-inverse);
  --_bordure: var(--bordure-inverse);
  --_survol: var(--fond-inverse-survol);
  --_bordure-survol: var(--texte-inverse-discret);
  --focus-couleur: var(--focus-couleur-inverse);
}

/* Un bouton à bascule : filtre actif, vue choisie. */
.bouton[aria-pressed='true'] {
  --_fond: var(--fond-accent);
  --_texte: var(--texte-accent);
  --_bordure: var(--encre-marque);
}

.bouton-petit {
  min-height: var(--cible-dense);
  padding: 0 var(--espace-2);
  font-size: var(--taille-petite);
}

.bouton-icone {
  width: var(--cible-tactile);
  padding: 0;
  /* Un glyphe (×, ‹, ›) à la taille du texte est minuscule dans un carré de 44 px. */
  font-size: 1.25rem;
}

.bouton-icone.bouton-petit {
  width: var(--cible-dense);
}

.bouton-large {
  width: 100%;
}

/* Flottant : l'action toujours sous le pouce (le coach de repstack). Rond, ancré en bas à
   droite, au-dessus de l'encoche ; `--bouton-flottant-bas` le remonte au-dessus d'une barre du
   bas. Il porte `.pastille` (étiquette) quand quelque chose attend. Un seul par écran. */
.bouton-flottant {
  position: fixed;
  right: var(--espace-3);
  bottom: calc(var(--bouton-flottant-bas, 0px) + var(--espace-3) + env(safe-area-inset-bottom, 0px));
  z-index: var(--couche-barre);
  width: 3.5rem;
  min-height: 3.5rem;
  padding: 0;
  border-radius: var(--rayon-plein);
  box-shadow: var(--ombre-portee);
}

.bouton-flottant.bouton-petit {
  width: var(--cible-tactile);
  min-height: var(--cible-tactile);
}

.bouton:disabled,
.bouton[aria-disabled='true'] {
  opacity: 0.45;
  cursor: not-allowed;
}

/* En cours : le libellé garde sa place (le bouton ne change pas de largeur), une roue le
   remplace à l'écran, et `aria-busy` le dit au lecteur d'écran. */
.bouton[aria-busy='true'] {
  color: transparent;
  cursor: progress;
}

.bouton[aria-busy='true']::after {
  content: '';
  position: absolute;
  inset: 0;
  margin: auto;
  width: 1.1em;
  height: 1.1em;
  border: 2px solid var(--_texte);
  border-right-color: transparent;
  border-radius: var(--rayon-plein);
  animation: bouton-tourne 700ms linear infinite;
}

@keyframes bouton-tourne {
  to {
    transform: rotate(1turn);
  }
}

/* ── design/composants/champ.css ── */
/* Champ — un libellé, une saisie, une aide, une erreur.
 *
 *   <div class="champ">
 *     <label class="champ-libelle" for="nom">Nom</label>
 *     <input class="saisie" id="nom" aria-describedby="nom-aide nom-erreur">
 *     <p class="champ-aide" id="nom-aide">Tel qu'il apparaîtra aux participants.</p>
 *     <p class="champ-erreur" id="nom-erreur" role="alert"></p>
 *   </div>
 *
 * `.saisie` va sur <input>, <select> et <textarea>. L'erreur se déclare par `aria-invalid="true"`
 * sur la saisie ; `.champ-erreur` vide reste caché, il apparaît dès qu'on y écrit.
 * Variantes : .saisie-nombre (chiffres alignés à droite), .saisie-code (un code à saisir en
 * grand), .champ-groupe pour une unité collée à la saisie (« € », « kg »), et
 * .saisie-surlignee pour une saisie dont certains mots se colorent pendant la frappe. */
.champ {
  display: grid;
  /* `minmax(0, …)` : un champ dans une colonne étroite rétrécit avec elle, au lieu de prendre la
     largeur de ce qu'il contient et de déborder sur son voisin. */
  grid-template-columns: minmax(0, 1fr);
  gap: var(--espace-1);
}

/* En casse de phrase, pas en mention : un formulaire de six champs devenait une colonne de
   capitales interlettrées, et un libellé se lit plus vite en minuscules. Les capitales restent
   aux en-têtes de tableau et aux légendes de tuile. */
.champ-libelle {
  font-size: var(--taille-petite);
  font-weight: var(--graisse-moyenne);
  color: var(--texte-page);
}

.saisie {
  display: block;
  width: 100%;
  min-height: var(--cible-tactile);
  padding: 0 var(--espace-3);
  border: 1px solid var(--bordure-champ);
  border-radius: var(--rayon);
  background-color: var(--fond-champ);
  color: var(--texte-champ);
  font: inherit;
  /* 16px au moins : en dessous, Safari iOS zoome sur la page au focus. */
  font-size: max(1rem, 16px);
  transition: border-color var(--duree-courte);
}

.saisie:hover:not(:disabled) {
  border-color: var(--encre-discrete);
}

.saisie:focus-visible {
  border-color: var(--focus-couleur);
}

.saisie[aria-invalid='true'] {
  border-color: var(--encre-erreur);
}

.saisie:disabled {
  background-color: var(--fond-discret);
  color: var(--encre-discrete);
  cursor: not-allowed;
}

.saisie::placeholder {
  color: var(--encre-discrete);
  opacity: 1;
}

textarea.saisie {
  min-height: 6rem;
  padding: var(--espace-2) var(--espace-3);
  line-height: var(--interligne-texte);
  resize: vertical;
}

/* Un chevron dessiné en `currentColor` : il suit la couleur du texte, quel que soit le thème. */
select.saisie {
  appearance: none;
  padding-right: calc(var(--espace-3) * 2.5);
  background-image:
    linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position:
    calc(100% - 1.25rem) 50%,
    calc(100% - 0.9375rem) 50%;
  background-size: 0.3125rem 0.3125rem;
  background-repeat: no-repeat;
}

.saisie-nombre {
  text-align: right;
  font-family: var(--police-mono);
  font-variant-numeric: tabular-nums;
}

.saisie-code {
  min-height: 3.5rem;
  text-align: center;
  font-family: var(--police-mono);
  font-size: var(--taille-titre);
  letter-spacing: var(--interlettrage-code);
}

/* Saisie surlignée : des mots reconnus pendant la frappe (un #projet, « demain ») se colorent.
   Une saisie ne sait pas colorer un mot : un calque, SOUS elle, redessine le texte, et la saisie
   devient transparente (`data-surligne`) — le curseur et la sélection restent les siens.
     <div class="saisie-surlignee">
       <div class="saisie-calque" aria-hidden="true">Pain <span class="surlignage" style="--teinte: var(--serie-5)">demain</span></div>
       <input class="saisie" data-surligne>
     </div>
   Le calque reprend la police, la taille et le retrait de la saisie, au pixel : c'est ce qui
   garde le curseur sur la lettre. La teinte d'un mot vient de l'usage (`--teinte`, la couleur
   du projet) ; son fond est un pseudo-élément en opacité, qui ne change pas la métrique.
   Deux choses restent à l'app : n'écrire `data-surligne` que si le calque redit TOUT le texte,
   et recopier le défilement de la saisie sur le calque (`calque.scrollLeft = saisie.scrollLeft`,
   à chaque frappe — Safari iOS n'émet pas `scroll` sur une saisie) dès que le texte dépasse. */
.saisie-surlignee {
  position: relative;
  border-radius: var(--rayon);
  background: var(--fond-champ);
}

.saisie-surlignee > .saisie {
  position: relative;
  background-color: transparent;
}

.saisie-surlignee > .saisie[data-surligne] {
  color: transparent;
  caret-color: var(--texte-champ);
}

.saisie-calque {
  position: absolute;
  inset: 0;
  /* Un bloc centré par son interligne, pas un flex : un flex jette l'espace seul entre deux
     mots surlignés (un texte anonyme fait seulement de blancs n'y est pas rendu), et le calque
     se décalait d'un caractère par rapport à la saisie. */
  overflow: hidden;
  line-height: var(--cible-tactile);
  padding: 0 calc(var(--espace-3) + 1px);
  color: var(--texte-champ);
  font-size: max(1rem, 16px);
  white-space: pre;
  pointer-events: none;
}

.surlignage {
  position: relative;
  color: var(--teinte, var(--encre-marque));
}

.surlignage::before {
  content: '';
  position: absolute;
  inset: -2px -3px;
  border-radius: var(--rayon-petit);
  background: currentColor;
  opacity: 0.16;
}

.champ-groupe {
  display: flex;
}

.champ-groupe > .saisie {
  flex: 1;
  min-width: 0;
}

.champ-groupe > .saisie:not(:last-child) {
  border-top-right-radius: var(--rayon-net);
  border-bottom-right-radius: var(--rayon-net);
}

.champ-groupe > .saisie:not(:first-child) {
  border-top-left-radius: var(--rayon-net);
  border-bottom-left-radius: var(--rayon-net);
}

.champ-affixe {
  display: flex;
  align-items: center;
  padding: 0 var(--espace-3);
  border: 1px solid var(--bordure-champ);
  background: var(--fond-discret);
  color: var(--texte-discret);
  white-space: nowrap;
}

.champ-affixe:first-child {
  border-right: 0;
  border-radius: var(--rayon) var(--rayon-net) var(--rayon-net) var(--rayon);
}

.champ-affixe:last-child {
  border-left: 0;
  border-radius: var(--rayon-net) var(--rayon) var(--rayon) var(--rayon-net);
}

.champ-aide {
  margin: 0;
  font-size: var(--taille-petite);
  color: var(--encre-discrete);
}

.champ-erreur {
  margin: 0;
  padding-left: var(--espace-2);
  border-left: 3px solid var(--encre-erreur);
  font-size: var(--taille-petite);
  color: var(--encre-erreur);
}

.champ-erreur:empty {
  display: none;
}

/* ── design/composants/choix.css ── */
/* Choix — case à cocher, bouton radio, interrupteur, carte de choix.
 *
 *   <label class="option"><input type="checkbox"> Me prévenir</label>
 *   <label class="option"><input type="radio" name="vue"> Liste</label>
 *   <label class="option"><input type="checkbox" class="interrupteur" role="switch"> Mode projection</label>
 *   <label class="choix-carte"><input type="radio" name="r"><span class="choix-repere">A</span> Réponse</label>
 *
 *   .choix-carte-compacte (icône au-dessus, libellé court)   .choix-teinte (une pastille de couleur)
 *   .coche (la case ronde d'une tâche, d'une course)   .coche-teintee (à la couleur de son projet)
 *
 * Les contrôles restent NATIFS : le clavier, le groupe radio et l'annonce au lecteur d'écran
 * viennent gratuitement. Seul l'aspect change. */
.option {
  display: inline-flex;
  align-items: center;
  gap: var(--espace-2);
  min-height: var(--cible-tactile);
  cursor: pointer;
}

.option input[type='checkbox']:not(.interrupteur),
.option input[type='radio'] {
  width: 1.125rem;
  height: 1.125rem;
  margin: 0;
  cursor: inherit;
}

.option:has(input:disabled) {
  opacity: 0.45;
  cursor: not-allowed;
}

/* Coche : la case ronde d'une ligne de liste — une tâche à terminer, une course à prendre.
     <input type="checkbox" class="coche" aria-label="Pain">          un état : pris / à prendre
     <button class="coche" type="button" aria-label="Terminer"></button>   une action : la tâche quitte la liste
   Au doigt, sa cible déborde jusqu'à 44 px ; à la souris elle reste à sa taille, pour ne pas
   mordre sur la ligne voisine d'une liste dense. */
.coche {
  appearance: none;
  position: relative;
  flex: none;
  width: 1.25rem;
  height: 1.25rem;
  margin: 0;
  padding: 0;
  border: 1.5px solid var(--bordure-champ);
  border-radius: var(--rayon-plein);
  background: none;
  cursor: pointer;
  transition:
    border-color var(--duree-courte),
    background-color var(--duree-courte);
}

@media (pointer: coarse) {
  .coche::after {
    content: '';
    position: absolute;
    inset: calc((1.25rem - var(--cible-tactile)) / 2);
  }
}

.coche:hover:not(:disabled) {
  border-color: var(--encre-marque);
}

.coche:checked {
  border-color: var(--fond-marque);
  background: var(--fond-marque);
}

.coche:checked::before {
  content: '';
  position: absolute;
  top: 45%;
  left: 50%;
  width: 0.3rem;
  height: 0.6rem;
  border: solid var(--texte-marque);
  border-width: 0 2px 2px 0;
  transform: translate(-50%, -50%) rotate(45deg);
}

.coche:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* Teintée : le cercle prend la couleur de ce qui la porte (`--teinte`, celle du projet), et se
   remplit de cette couleur une fois cochée. Le trait est alors foncé, `--fond-page` : une teinte
   de données est claire dans un thème sombre, et un trait blanc dessus ne se lirait pas. */
.coche-teintee {
  border-color: var(--teinte, var(--bordure-champ));
}

.coche-teintee:checked {
  border-color: var(--teinte, var(--fond-marque));
  background: var(--teinte, var(--fond-marque));
}

.coche-teintee:checked::before {
  border-color: var(--fond-page);
}

/* Interrupteur : une case à cocher qui dit « activé / désactivé ». `role="switch"` sur l'input
   pour que le lecteur d'écran l'annonce comme tel. */
.interrupteur {
  appearance: none;
  position: relative;
  flex: none;
  width: 2.75rem;
  height: 1.5rem;
  margin: 0;
  border-radius: var(--rayon-plein);
  background: var(--bordure-champ);
  cursor: inherit;
  transition: background-color var(--duree);
}

.interrupteur::before {
  content: '';
  position: absolute;
  top: 0.1875rem;
  left: 0.1875rem;
  width: 1.125rem;
  height: 1.125rem;
  border-radius: var(--rayon-plein);
  /* Le curseur est clair dans tous les thèmes : il se lit sur la piste grise comme sur la marque. */
  background: var(--texte-marque);
  transition: transform var(--duree);
}

.interrupteur:checked {
  background: var(--fond-marque);
}

.interrupteur:checked::before {
  transform: translateX(1.25rem);
}

/* Carte de choix : une réponse de quiz, une formule, une option qui mérite plus qu'une ligne.
   L'input est caché à l'écran mais garde le focus et le clavier. */
.choix-carte {
  /* Ancre l'input caché dans SA carte : sans ça, il se place contre <body> et le focus clavier
     fait sauter la page vers le haut. */
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--espace-3);
  min-height: calc(var(--cible-tactile) + var(--espace-2));
  padding: var(--espace-2) var(--espace-3);
  border: 1px solid var(--bordure-forte);
  border-radius: var(--rayon-grand);
  background: var(--fond-carte);
  color: var(--texte-carte);
  cursor: pointer;
  transition:
    border-color var(--duree-courte),
    background-color var(--duree-courte);
}

.choix-carte > input {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
  pointer-events: none;
}

.choix-carte:hover:not(:has(input:disabled)) {
  border-color: var(--encre-marque);
}

.choix-carte:has(input:checked) {
  border-color: var(--fond-marque);
  background: var(--fond-accent);
  color: var(--texte-accent);
  box-shadow: inset 0 0 0 1px var(--fond-marque);
}

.choix-carte:has(input:focus-visible) {
  outline: var(--focus-epaisseur) solid var(--focus-couleur);
  outline-offset: var(--focus-ecart);
}

.choix-carte:has(input:disabled) {
  opacity: 0.45;
  cursor: not-allowed;
}

.choix-repere {
  display: grid;
  place-items: center;
  flex: none;
  width: 2rem;
  height: 2rem;
  border: 1px solid var(--bordure-forte);
  border-radius: var(--rayon);
  font-weight: var(--graisse-forte);
}

.choix-carte:has(input:checked) .choix-repere {
  border-color: var(--fond-marque);
  background: var(--fond-marque);
  color: var(--texte-marque);
}

/* Compacte : icône au-dessus, libellé court en dessous — quatre humeurs sur une ligne de
   téléphone. `.choix-icone` est un glyphe ou un pictogramme, qui ne change pas à la sélection :
   c'est la carte qui le dit.
     <label class="choix-carte choix-carte-compacte"><input type="radio" name="d"><span class="choix-icone">💪</span> Facile</label> */
.choix-carte-compacte {
  flex: 1 1 0;
  flex-direction: column;
  justify-content: center;
  gap: var(--espace-1);
  padding: var(--espace-2);
  text-align: center;
  font-size: var(--taille-mention);
  font-weight: var(--graisse-moyenne);
}

.choix-icone {
  flex: none;
  font-size: 1.5rem;
  line-height: 1;
}

/* Teinte : une pastille de couleur à choisir, pour la couleur d'un type de séance. La teinte
   vient de l'usage (`--teinte`), jamais d'une classe par couleur ; l'input caché garde le
   clavier, et le nom de la couleur reste au lecteur d'écran (`.visuellement-cache`).
     <label class="choix-teinte" style="--teinte: var(--serie-2)"><input type="radio" name="c"><span class="visuellement-cache">Bleu</span></label> */
.choix-teinte {
  position: relative;
  display: inline-grid;
  place-items: center;
  width: var(--cible-tactile);
  height: var(--cible-tactile);
  cursor: pointer;
}

.choix-teinte::before {
  content: '';
  width: 2rem;
  height: 2rem;
  border-radius: var(--rayon-plein);
  background: var(--teinte, var(--fond-marque));
  box-shadow: 0 0 0 2px var(--fond-page);
  transition:
    transform var(--duree-courte),
    box-shadow var(--duree-courte);
}

.choix-teinte > input {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
  cursor: inherit;
}

.choix-teinte:has(input:checked)::before {
  transform: scale(1.1);
  box-shadow:
    0 0 0 2px var(--fond-page),
    0 0 0 4px var(--texte-page);
}

.choix-teinte:has(input:focus-visible)::before {
  outline: var(--focus-epaisseur) solid var(--focus-couleur);
  outline-offset: var(--focus-ecart);
}

/* ── design/composants/onglets.css ── */
/* Onglets et segments — choisir une vue parmi quelques-unes.
 *
 *   <div class="onglets" role="tablist">
 *     <button class="onglet" role="tab" aria-selected="true" aria-controls="p1">Semaine</button>
 *     <button class="onglet" role="tab" aria-selected="false" aria-controls="p2">Mois</button>
 *   </div>
 *
 *   <div class="segments" role="group" aria-label="Magasin">
 *     <button class="segment" aria-pressed="true">La Fourche</button>
 *     <button class="segment" aria-pressed="false">Carrefour</button>
 *   </div>
 *
 * Les onglets changent le CONTENU affiché (role="tab") ; les segments changent un RÉGLAGE
 * (aria-pressed). L'état actif se lit dans l'attribut, jamais dans une classe.
 * Des onglets qui débordent (un téléphone, cinq statuts) défilent sans barre : le bord qui cache
 * encore un onglet s'estompe, et redevient net en bout de course.
 *   .segments-etendus : sur toute la largeur, en parts égales (un téléphone) */
.onglets {
  display: flex;
  gap: var(--espace-1);
  overflow-x: auto;
  /* Le filet est dessiné en ombre intérieure, pas en bordure : `overflow-x: auto` rend aussi
     l'axe vertical défilant, et un onglet qui déborde d'1px sur une bordure serait rogné (et la
     barre défilerait d'un pixel). Le soulignement actif se pose ainsi PAR-DESSUS le filet. */
  box-shadow: inset 0 -1px 0 var(--bordure);
  scrollbar-width: none;
}

/* L'indice de défilement : un masque en dégradé dont chaque bord ne s'ouvre que s'il reste à
   défiler de ce côté, piloté par la position de défilement (animation liée au défilement). Le
   masque, et non une ombre ou un dégradé peint : il ne suppose rien de la surface dessous (son
   `black` n'est pas une couleur, seulement l'opacité du masque). Sans
   débordement, la timeline est inactive, l'animation ne s'applique pas, aucun bord ne s'estompe.
   Sans `animation-timeline` (Firefox), rien : les onglets défilent comme avant. Le dégradé va
   de gauche à droite (apps en LTR). Une app qui pose `animation` sur `.onglets` écrase l'indice. */
@property --_onglets-debut {
  syntax: '<length>';
  inherits: false;
  initial-value: 0px;
}

@property --_onglets-fin {
  syntax: '<length>';
  inherits: false;
  initial-value: 0px;
}

@supports (animation-timeline: scroll()) {
  .onglets {
    mask-image: linear-gradient(
      to right,
      transparent,
      black var(--_onglets-debut),
      black calc(100% - var(--_onglets-fin)),
      transparent
    );
    animation: onglets-bords linear both;
    animation-timeline: scroll(self inline);
  }
}

@keyframes onglets-bords {
  from {
    --_onglets-debut: 0px;
    --_onglets-fin: var(--espace-5);
  }

  2% {
    --_onglets-debut: var(--espace-5);
  }

  98% {
    --_onglets-fin: var(--espace-5);
  }

  to {
    --_onglets-debut: var(--espace-5);
    --_onglets-fin: 0px;
  }
}

.onglet {
  flex: none;
  min-height: var(--cible-tactile);
  padding: 0 var(--espace-3);
  border: 0;
  border-bottom: 2px solid transparent;
  background: none;
  color: var(--encre-discrete);
  font: inherit;
  font-weight: var(--graisse-moyenne);
  cursor: pointer;
  transition:
    color var(--duree-courte),
    border-color var(--duree-courte);
}

.onglet:hover {
  color: var(--texte-page);
}

.onglet[aria-selected='true'] {
  border-bottom-color: var(--encre-courant);
  color: var(--texte-page);
}

.segments {
  display: inline-flex;
  gap: 2px;
  padding: 2px;
  border-radius: var(--rayon-grand);
  background: var(--fond-discret);
}

.segments-etendus {
  display: flex;
}

.segments-etendus > .segment {
  flex: 1;
}

.segment {
  min-height: calc(var(--cible-tactile) - 4px);
  padding: 0 var(--espace-3);
  border: 0;
  border-radius: var(--rayon);
  background: none;
  color: var(--texte-discret);
  font: inherit;
  font-weight: var(--graisse-moyenne);
  cursor: pointer;
}

.segment[aria-pressed='true'] {
  background: var(--fond-eleve);
  color: var(--texte-eleve);
  /* Le cerne, en plus de l'ombre : dans un thème sombre, l'ombre ne se voit pas et la surface
     élevée ne se détache de la discrète que de 1.1:1. */
  box-shadow:
    inset 0 0 0 1px var(--bordure-forte),
    var(--ombre-legere);
}

/* ── design/composants/carte.css ── */
/* Carte — un bloc de contenu qui se détache de la page.
 *
 *   <section class="carte">…</section>
 *   .carte-discrete (fond gris, sans bordure)   .carte-accent (filet de marque à gauche —
 *   ou de la couleur posée dans `--carte-accent`, pour une carte par type de séance)
 *   .carte-cliquable (sur un <a> ou un <button> : toute la carte est la cible)
 *   .carte-serree : le cran de padding en dessous, pour une liste de cartes sur mobile
 *   aria-pressed="true" / aria-selected="true" : la carte CHOISIE (un sélecteur, un choix multiple)
 *   aria-current="true" : la carte EN COURS (aujourd'hui, la séance lancée) — pas un choix */
.carte {
  display: block;
  padding: var(--espace-4);
  border: 1px solid var(--bordure);
  border-radius: var(--rayon-grand);
  background: var(--fond-carte);
  color: var(--texte-carte);
}

.carte > :first-child {
  margin-top: 0;
}

.carte > :last-child {
  margin-bottom: 0;
}

.carte-discrete {
  border-color: transparent;
  background: var(--fond-discret);
  color: var(--texte-discret);
}

.carte-serree {
  padding: var(--espace-3);
}

.carte-accent {
  border-left: 3px solid var(--carte-accent, var(--fond-marque));
}

.carte-cliquable {
  width: 100%;
  font: inherit;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: border-color var(--duree-courte);
}

.carte-cliquable:hover {
  border-color: var(--bordure-forte);
}

/* Choisie : le cerne de marque, comme une `.puce` ou une `.tuile` enfoncée — c'est l'état
   « choisi » de tout le kit. Une carte cochée dans un sélecteur n'est pas « en cours » : la
   cerner en braise mêlait les deux rôles que Braise 2 venait de séparer (revue repstack,
   2026-10-01). */
.carte[aria-pressed='true'],
.carte[aria-selected='true'] {
  border-color: var(--encre-marque);
  box-shadow: inset 0 0 0 1px var(--encre-marque);
}

/* En cours : l'encre courante (Braise 2) — la colonne d'aujourd'hui n'est plus cerclée de rouge
   comme une erreur de saisie. */
.carte[aria-current='true'] {
  border-color: var(--encre-courant);
  box-shadow: inset 0 0 0 1px var(--encre-courant);
}

/* Le titre d'un bloc — d'une carte, d'une barre, d'un état vide : une seule typographie,
   posée ici pour les trois. */
.carte-titre,
.barre-titre,
.vide-titre {
  font-family: var(--police-titre);
  font-size: var(--taille-intertitre);
  font-weight: var(--graisse-titre);
  letter-spacing: var(--interlettrage-titre);
  text-transform: var(--casse-titre);
}

/* `display: block` : dans une `.carte-cliquable` (un <button> ou un <a>), le titre est souvent
   un <span> — sans ça, il se colle au texte qui suit, sur la même ligne. */
.carte-titre {
  display: block;
  margin: 0 0 var(--espace-2);
}

/* ── design/composants/etiquette.css ── */
/* Étiquette — un statut, une catégorie, un compteur. Courte, jamais cliquable (sinon c'est un
 * bouton à bascule : `.bouton[aria-pressed]`).
 *
 *   <span class="etiquette">Brouillon</span>
 *   .etiquette-marque (en direct, en cours : la marque pleine)
 *   .etiquette-succes  .etiquette-attention  .etiquette-erreur  .etiquette-info
 *   .etiquette-inverse : sur une surface inverse (barre, panneau sombre) — un contour, pas
 *   une surface : la surface discrète y donnait un rectangle clair sous du texte clair
 *   .etiquette-point : une pastille devant le texte, de la couleur du texte — ou de celle posée
 *   dans `--etiquette-teinte` (le projet d'une tâche : la couleur appartient aux données de l'app)
 *   .point (seul) : la pastille sans étiquette, devant un nom ou un titre, à la couleur posée
 *   dans `--teinte` ; .point-grand
 * L'étiquette a SA variable, et non `--teinte` : une variable s'hérite, et une étiquette d'état
 * posée dans une ligne teintée (la rangée d'une habitude) aurait pris la couleur de la ligne.
 *   .pastille (seule, sur un bouton) : le point « quelque chose attend »
 *
 * La marque est PLEINE et non sur la surface d'accent : dans Outremer l'accent est la même
 * couleur que l'info, dans Braise presque celle de l'erreur — « en direct » ressemblait à
 * « révision » ou à « échec ». */
.etiquette {
  --_fond: var(--fond-discret);
  --_texte: var(--texte-discret);

  display: inline-flex;
  align-items: center;
  gap: var(--espace-1);
  padding: 0.125rem var(--espace-2);
  border-radius: var(--rayon-plein);
  background: var(--_fond);
  color: var(--_texte);
  font-size: var(--taille-mention);
  font-weight: var(--graisse-moyenne);
  line-height: 1.5;
  white-space: nowrap;
}

.etiquette-marque {
  --_fond: var(--fond-marque);
  --_texte: var(--texte-marque);
}

.etiquette-succes {
  --_fond: var(--fond-succes);
  --_texte: var(--texte-succes);
}

.etiquette-attention {
  --_fond: var(--fond-attention);
  --_texte: var(--texte-attention);
}

.etiquette-erreur {
  --_fond: var(--fond-erreur);
  --_texte: var(--texte-erreur);
}

.etiquette-info {
  --_fond: var(--fond-info);
  --_texte: var(--texte-info);
}

.etiquette-inverse {
  --_fond: transparent;
  --_texte: var(--texte-inverse);

  box-shadow: inset 0 0 0 1px var(--bordure-inverse);
}

/* Pastille : un point posé sur le coin d'un bouton — « quelque chose attend ». Le bouton est déjà
   `position: relative` ; le cerne de la couleur de page la détache du bouton. Elle ne porte pas de
   texte : c'est au bouton de dire, dans son `aria-label`, ce qui attend. */
.pastille {
  position: absolute;
  top: 0;
  right: 0;
  width: 0.75rem;
  height: 0.75rem;
  border: 2px solid var(--fond-page);
  border-radius: var(--rayon-plein);
  background: var(--encre-erreur);
}

.etiquette-point::before {
  content: '';
  width: 0.5em;
  height: 0.5em;
  border-radius: var(--rayon-plein);
  background: var(--etiquette-teinte, currentColor);
}

.point {
  display: inline-block;
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: var(--rayon-plein);
  background: var(--teinte, currentColor);
}

.point-grand {
  width: 0.75rem;
  height: 0.75rem;
}

/* ── design/composants/tuile.css ── */
/* Tuile — un chiffre clé, sa légende, son contexte.
 *
 *   <div class="tuile">
 *     <span class="tuile-libelle">Participants</span>
 *     <span class="tuile-valeur">42</span>
 *     <span class="tuile-detail">dont 3 en retard</span>
 *   </div>
 *
 *   .tuile-forte : LA tuile qui compte, sur le fond de marque. Une par écran.
 *   .tuile-centree : tout centré, avec une icône au-dessus (`.tuile-icone`, à l'encre discrète
 *   ou à la couleur posée dans `--tuile-teinte`) — trois chiffres côte à côte sur un téléphone.
 * Des chiffres tabulaires : une colonne de tuiles s'aligne. `.tuile-libelle` a le corps de
 * `.mention`. */
.tuile {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-content: start;
  gap: var(--espace-1);
  padding: var(--espace-3) var(--espace-4);
  border: 1px solid var(--bordure);
  border-radius: var(--rayon-grand);
  background: var(--fond-carte);
  color: var(--texte-carte);
}

/* Le corps de `.mention` (fondations/base.css), partagé avec l'en-tête de tableau : posé ici
   parce que tuile.css est assemblée avant tableau.css. */
.tuile-libelle,
.tableau th {
  font-size: var(--taille-mention);
  font-weight: var(--graisse-moyenne);
  letter-spacing: var(--interlettrage-mention);
  text-transform: var(--casse-mention);
  color: var(--encre-discrete);
}

.tuile-valeur {
  font-family: var(--police-chiffres);
  font-size: var(--taille-titre);
  font-variant-numeric: tabular-nums;
  line-height: var(--interligne-titre);
}

.tuile-detail {
  font-size: var(--taille-petite);
  color: var(--encre-discrete);
}

.tuile-centree {
  justify-items: center;
  padding: var(--espace-3) var(--espace-2);
  text-align: center;
}

.tuile-icone {
  display: inline-flex;
  color: var(--tuile-teinte, var(--encre-discrete));
}

.tuile-forte {
  border-color: var(--fond-marque);
  background: var(--fond-marque);
  color: var(--texte-marque);
}

.tuile-forte .tuile-libelle,
.tuile-forte .tuile-detail {
  color: var(--texte-marque-discret);
}

/* ── design/composants/tableau.css ── */
/* Tableau — des données en lignes et en colonnes.
 *
 *   <div class="tableau-cadre">                    (défile horizontalement sur petit écran)
 *     <table class="tableau">
 *       <thead><tr><th>Poste</th><th class="nombre">Montant</th></tr></thead>
 *       <tbody><tr><td>Salaire</td><td class="nombre">42 000</td></tr></tbody>
 *     </table>
 *   </div>
 *
 *   .nombre (aligné à droite, chiffres tabulaires)   tr.est-total   tr[aria-current="true"]
 *   .tableau-dense   .tableau-cliquable (survol des lignes, quand une ligne ouvre un détail) */
.tableau-cadre {
  overflow-x: auto;
}

.tableau {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}

.tableau th,
.tableau td {
  padding: var(--espace-2) var(--espace-3);
  border-bottom: 1px solid var(--bordure);
  text-align: left;
  vertical-align: top;
}

/* La typographie de l'en-tête est celle de `.mention`, posée avec `.tuile-libelle`
   (tuile.css). */
.tableau th {
  border-bottom-color: var(--bordure-forte);
  white-space: nowrap;
}

.tableau .nombre {
  text-align: right;
  white-space: nowrap;
}

.tableau tr.est-total td {
  border-top: 1px solid var(--filet);
  border-bottom: 0;
  font-weight: var(--graisse-forte);
}

.tableau tr[aria-current='true'] td {
  background: var(--fond-accent);
  color: var(--texte-accent);
}

.tableau-cliquable tbody tr {
  cursor: pointer;
}

.tableau-cliquable tbody tr:hover td {
  background: var(--fond-discret);
}

.tableau-dense th,
.tableau-dense td {
  padding: var(--espace-1) var(--espace-2);
  font-size: var(--taille-petite);
}

/* ── design/composants/alerte.css ── */
/* Alerte — un message qui reste dans la page : erreur de formulaire, avertissement, confirmation.
 *
 *   <div class="alerte alerte-erreur" role="alert">Le fichier dépasse 20 Mo.</div>
 *   .alerte-succes  .alerte-attention  .alerte-erreur  .alerte-info (par défaut)
 *   .alerte-sobre : sans surface, seulement le filet et la couleur — pour une alerte dans un
 *   formulaire dense, où une boîte colorée crierait.
 *
 * Vide, elle ne s'affiche pas : on peut la laisser dans le HTML et n'y écrire qu'en cas de besoin.
 * role="alert" pour ce qui interrompt, role="status" pour ce qui informe. */
.alerte {
  --_fond: var(--fond-info);
  --_texte: var(--texte-info);
  --_encre: var(--encre-info);

  margin: 0;
  padding: var(--espace-3);
  border-left: 3px solid var(--_encre);
  border-radius: var(--rayon-net) var(--rayon) var(--rayon) var(--rayon-net);
  background: var(--_fond);
  color: var(--_texte);
}

.alerte:empty {
  display: none;
}

.alerte-succes {
  --_fond: var(--fond-succes);
  --_texte: var(--texte-succes);
  --_encre: var(--encre-succes);
}

.alerte-attention {
  --_fond: var(--fond-attention);
  --_texte: var(--texte-attention);
  --_encre: var(--encre-attention);
}

.alerte-erreur {
  --_fond: var(--fond-erreur);
  --_texte: var(--texte-erreur);
  --_encre: var(--encre-erreur);
}

.alerte-sobre {
  padding: 0 0 0 var(--espace-3);
  background: transparent;
  color: var(--_encre);
}

.alerte-titre {
  display: block;
  margin-bottom: var(--espace-1);
  font-weight: var(--graisse-forte);
}

/* Bandeau — la même alerte, mais en travers du haut de l'écran : hors ligne, mise à jour
   disponible, session expirée. Fixé au-dessus de tout, sous l'encoche ; `.bandeau-collant`
   reste dans le flux (sticky) quand l'écran a déjà sa barre. Il informe, il n'interrompt pas :
   role="status", et jamais deux à la fois.

     <div class="bandeau bandeau-attention" role="status"><svg …/><span>Hors ligne</span></div>
     .bandeau-succes  .bandeau-attention  .bandeau-erreur  .bandeau-info (par défaut)
     .bandeau-marque : ce que la marque annonce (une mise à jour), pas un état du monde */
.bandeau {
  --_fond: var(--fond-info);
  --_texte: var(--texte-info);

  position: fixed;
  inset-inline: 0;
  top: 0;
  z-index: var(--couche-notification);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--espace-2);
  min-height: var(--cible-dense);
  padding: calc(var(--espace-1) + env(safe-area-inset-top, 0px)) var(--espace-3) var(--espace-1);
  background: var(--_fond);
  color: var(--_texte);
  font-size: var(--taille-petite);
  font-weight: var(--graisse-moyenne);
}

/* Collant : dans le flux, avant la barre de l'écran. Une couche au-dessus d'elle, sinon la barre
   (sticky elle aussi, même `top`) le recouvre au premier défilement ; et la barre qui le suit
   n'absorbe pas l'encoche une seconde fois. */
.bandeau-collant {
  position: sticky;
  z-index: calc(var(--couche-barre) + 1);
}

.bandeau-collant + .barre {
  min-height: 3.5rem;
  padding-top: 0;
}

.bandeau > svg {
  flex: none;
}

.bandeau-succes {
  --_fond: var(--fond-succes);
  --_texte: var(--texte-succes);
}

.bandeau-attention {
  --_fond: var(--fond-attention);
  --_texte: var(--texte-attention);
}

.bandeau-erreur {
  --_fond: var(--fond-erreur);
  --_texte: var(--texte-erreur);
}

.bandeau-marque {
  --_fond: var(--fond-marque);
  --_texte: var(--texte-marque);
}

/* ── design/composants/notification.css ── */
/* Notification — un message qui passe : « enregistré », « hors ligne », avec au plus une action.
 *
 *   <div class="notifications" aria-live="polite">          (une fois par page, vide au départ)
 *     <div class="notification notification-succes" role="status">
 *       <span>Présentation publiée</span>
 *       <button class="bouton bouton-fantome bouton-petit">Annuler</button>
 *     </div>
 *   </div>
 *
 * Le conteneur est ancré en bas, au-dessus de l'encoche — et d'une barre du bas si l'app pose
 * `--notifications-bas` (sa hauteur) sur un ancêtre. Le ton se lit dans le filet de gauche ; le
 * texte le dit aussi, la couleur n'est jamais le seul signal.
 *
 * Sous un `<dialog>` ouvert par `showModal()`, le conteneur doit être DANS le dialogue du dessus
 * (un portail, ou un `append`) : le dialogue modal rend inerte tout ce qui n'est pas en lui, et
 * le z-index n'y peut rien — il vit dans la couche supérieure du navigateur. Une notification
 * restée dans la page passerait sous le voile, incliquable. Le conteneur revient dans la page à
 * la fermeture du dialogue (nota : `useDialogueDuDessus`, la vitrine : `placerNotifications`).
 * `popover` ne sauve pas : un popover hors du dialogue modal reste inerte.
 *
 * Testé à la main dans nota le 2026-10-02 (Chrome, React, portail ; VoiceOver non piloté, l'annonce
 * est déduite du DOM) :
 *   - focus : dans le dialogue, Tab passe par l'action et la fermeture de la notification (elles
 *     sont dans le cycle piégé) ; Échap ferme le dialogue, la notification revient dans la page
 *     avec son action, le focus revient à l'élément qui a ouvert le dialogue ;
 *   - annonce : une région live n'est annoncée que si elle existe AVANT que son contenu change.
 *     Changer de parent recrée le conteneur (React démonte et remonte le portail ; un `append`
 *     déplace le nœud, ce que l'arbre d'accessibilité traite comme une insertion) : posé déjà
 *     rempli, il se tait. Le cas réel : fermer un dialogue et notifier dans le même tick
 *     (« Créé »), le message n'est jamais annoncé. La règle : à chaque changement de parent, le
 *     conteneur arrive VIDE et le message n'y entre qu'après un court délai (nota : 100 ms,
 *     `Toast.tsx`). Une notification présente pendant le déplacement disparaît puis revient.
 *   - limite connue : focus sur l'action d'une notification au moment où un dialogue s'ouvre,
 *     puis Échap → le focus retombe sur <body> (le bouton qu'il devait retrouver a été recréé). */
.notifications {
  position: fixed;
  inset-inline: 0;
  bottom: calc(var(--notifications-bas, 0px) + var(--espace-3) + env(safe-area-inset-bottom, 0px));
  z-index: var(--couche-notification);
  display: grid;
  justify-items: center;
  gap: var(--espace-2);
  padding-inline: var(--espace-3);
  pointer-events: none;
}

.notification {
  --_ton: var(--encre-marque);

  display: flex;
  align-items: center;
  gap: var(--espace-3);
  width: min(28rem, 100%);
  min-height: var(--cible-tactile);
  padding: var(--espace-2) var(--espace-2) var(--espace-2) var(--espace-3);
  border: 1px solid var(--bordure);
  border-left: 3px solid var(--_ton);
  border-radius: var(--rayon-net) var(--rayon) var(--rayon) var(--rayon-net);
  background: var(--fond-eleve);
  color: var(--texte-eleve);
  box-shadow: var(--ombre-portee);
  pointer-events: auto;
}

.notification > :first-child {
  flex: 1;
}

.notification-succes {
  --_ton: var(--encre-succes);
}

.notification-attention {
  --_ton: var(--encre-attention);
}

.notification-erreur {
  --_ton: var(--encre-erreur);
}

/* ── design/composants/dialogue.css ── */
/* Dialogue — ce qui interrompt : une confirmation, un formulaire court, un détail.
 *
 *   <dialog class="dialogue" aria-labelledby="d-titre">
 *     <header class="dialogue-entete">
 *       <h2 id="d-titre">Supprimer la présentation ?</h2>
 *       <button class="bouton bouton-fantome bouton-icone" aria-label="Fermer">×</button>
 *     </header>
 *     <div class="dialogue-corps">…</div>
 *     <footer class="dialogue-pied">…boutons…</footer>
 *   </dialog>
 *
 * Toujours un <dialog> natif, ouvert par `showModal()` : le piège à focus, Échap, le voile et
 * l'inertie de la page derrière viennent du navigateur — trois choses qu'un dialogue fait main
 * rate une fois sur deux.
 *   .dialogue-feuille   ancré en bas, pleine largeur (mobile)
 *   .dialogue-panneau   ancré à droite, pleine hauteur (un détail, un menu latéral)
 *   .dialogue-tiroir    le panneau, ancré à gauche : le menu d'un téléphone
 *   .dialogue-etroit / .dialogue-large   24 / 42rem au lieu de 32 (ou `--dialogue-largeur`)
 *   .dialogue-haut      ancré sous le haut de l'écran : une recherche, une saisie rapide
 *   .dialogue-debordant un menu ou une liste de suggestions peut dépasser du dialogue */
.dialogue {
  /* Écrit, et non laissé au navigateur : une remise à zéro qui pose `margin: 0` sur tout (le
     preflight de Tailwind 4) décentre un <dialog>, qui ne tient au milieu que par sa marge. */
  margin: auto;
  width: min(var(--dialogue-largeur, 32rem), calc(100% - 2 * var(--espace-3)));
  max-height: calc(100dvh - 2 * var(--espace-4));
  padding: 0;
  border: 1px solid var(--bordure);
  border-radius: var(--rayon-grand);
  background: var(--fond-eleve);
  color: var(--texte-eleve);
  box-shadow: var(--ombre-portee);
}

.dialogue::backdrop {
  background: var(--voile);
}

.dialogue[open] {
  display: flex;
  flex-direction: column;
}

.dialogue-entete {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--espace-3);
  padding: var(--espace-3) var(--espace-3) var(--espace-2) var(--espace-4);
}

.dialogue-entete > h2 {
  margin: 0;
  font-size: var(--taille-intertitre);
}

.dialogue-corps {
  overflow-y: auto;
  padding: 0 var(--espace-4) var(--espace-3);
}

.dialogue-pied {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--espace-2);
  padding: var(--espace-3) var(--espace-4);
  border-top: 1px solid var(--bordure);
}

.dialogue-feuille {
  width: 100%;
  max-width: none;
  margin: auto 0 0;
  border-width: 1px 0 0;
  border-radius: var(--rayon-grand) var(--rayon-grand) var(--rayon-net) var(--rayon-net);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}

.dialogue-panneau {
  width: min(22rem, 90vw);
  height: 100dvh;
  max-height: none;
  margin: 0 0 0 auto;
  border-width: 0 0 0 1px;
  border-radius: var(--rayon-net);
}

.dialogue-tiroir {
  margin: 0 auto 0 0;
  padding: env(safe-area-inset-top, 0px) 0 env(safe-area-inset-bottom, 0px) env(safe-area-inset-left, 0px);
  border-width: 0 1px 0 0;
}

.dialogue-etroit {
  --dialogue-largeur: 24rem;
}

.dialogue-large {
  --dialogue-largeur: 42rem;
}

/* En haut, et non centré : le contenu d'une recherche change de hauteur à chaque frappe, et un
   dialogue centré ferait sauter le champ sous le curseur. */
.dialogue-haut {
  margin-top: var(--espace-6);
  max-height: calc(100dvh - var(--espace-6) - var(--espace-4));
}

/* Un <dialog> rogne ce qui dépasse (il défile) : un menu ouvert près du bord bas y serait
   coupé, ou ferait défiler le dialogue. Ici le dialogue laisse dépasser — pour un dialogue
   court, et seulement sur un écran assez haut pour le contenir : sur un téléphone couché ou
   sous le clavier, il redevient un dialogue qui défile, sans quoi ses boutons sortiraient de
   l'écran sans aucun moyen de les atteindre. */
@media (min-height: 40rem) {
  .dialogue-debordant,
  .dialogue-debordant > .dialogue-corps {
    overflow: visible;
  }
}

/* ── design/composants/barre.css ── */
/* Barres — l'en-tête d'une page et la navigation du bas.
 *
 *   <header class="barre">
 *     <strong class="barre-titre">Slides</strong>
 *     <nav class="rangee">…liens ou boutons…</nav>
 *   </header>
 *
 *   <nav class="barre-bas" aria-label="Navigation principale">
 *     <a class="barre-bas-lien" href="/" aria-current="page"><svg …/>Accueil</a>
 *     <a class="barre-bas-lien" href="/recos"><svg …/>Recos <span class="etiquette etiquette-marque">3</span></a>
 *     …
 *   </nav>
 *
 *   Une `.etiquette` dans un lien du bas est un compteur : elle se pose en haut à droite de la
 *   pastille de l'icône, hors du flux — le libellé reste centré sous l'icône.
 *
 *   .barre-inverse (et .barre-bas.barre-inverse) : la barre sombre — la régie de slides, un
 *   bandeau d'outils. Elle bascule d'elle-même l'anneau de focus (`.sur-inverse`).
 *   .barre-en-bas : la même barre d'OUTILS, ancrée en bas (le bandeau de séance de slides).
 *   Ce n'est pas `.barre-bas`, qui est une navigation à onglets égaux : ici on aligne des
 *   commandes et un état, avec un écart, comme dans la barre du haut.
 * Les deux barres respectent l'encoche et la barre d'accueil iOS (safe-area). `.barre-titre` a la
 * typographie de `.carte-titre` (carte.css). */
.barre {
  position: sticky;
  top: 0;
  z-index: var(--couche-barre);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--espace-3);
  /* 3.5rem SOUS l'encoche : la hauteur est en border-box, et sans l'encoche dans le calcul la
     barre d'un téléphone à encoche ne faisait plus que la hauteur de son contenu — ce qui se
     cale sous elle (`top: calc(3.5rem + env(safe-area-inset-top))`) laissait alors un jour. */
  min-height: calc(3.5rem + env(safe-area-inset-top, 0px));
  padding: env(safe-area-inset-top, 0px) var(--espace-3) 0;
  border-bottom: 1px solid var(--bordure);
  background: var(--fond-carte);
  color: var(--texte-carte);
}

.barre-en-bas {
  position: fixed;
  top: auto;
  inset-inline: 0;
  bottom: 0;
  min-height: var(--cible-tactile);
  padding: 0 var(--espace-3) env(safe-area-inset-bottom, 0px);
  border-top: 1px solid var(--bordure);
  border-bottom: 0;
}

.barre-bas {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: var(--couche-barre);
  display: flex;
  justify-content: space-around;
  padding: 0 var(--espace-2) env(safe-area-inset-bottom, 0px);
  border-top: 1px solid var(--bordure);
  background: var(--fond-carte);
  color: var(--texte-carte);
}

.barre-bas-lien {
  position: relative;
  display: flex;
  flex: 1;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-height: 3.5rem;
  color: var(--encre-discrete);
  font-size: var(--taille-mention);
  font-weight: var(--graisse-moyenne);
  text-decoration: none;
}

/* L'icône (un <svg>, premier enfant) se pose dans une pastille. La page en cours la remplit de la
   surface d'accent et passe en `--encre-courant` : une forme ET une couleur, pas la couleur seule. */
.barre-bas-lien > svg {
  box-sizing: content-box;
  padding: 4px var(--espace-3);
  border-radius: var(--rayon-plein);
  transition: background-color var(--duree-courte);
}

.barre-bas-lien > .etiquette {
  position: absolute;
  top: 0.25rem;
  left: calc(50% + 0.5rem);
}

.barre-bas-lien[aria-current='page'] {
  color: var(--texte-carte);
}

.barre-bas-lien[aria-current='page'] > svg {
  background: var(--fond-accent);
  color: var(--encre-courant);
}

.barre-inverse {
  --focus-couleur: var(--focus-couleur-inverse);

  border-color: var(--bordure-inverse);
  background: var(--fond-inverse);
  color: var(--texte-inverse);
}

.barre-inverse .barre-bas-lien {
  color: var(--texte-inverse-discret);
}

.barre-inverse .barre-bas-lien[aria-current='page'] {
  color: var(--texte-inverse);
}

.barre-inverse .barre-bas-lien[aria-current='page'] > svg {
  background: var(--fond-inverse-survol);
  color: var(--texte-inverse);
}

/* ── design/composants/entete.css ── */
/* En-tête — le haut d'une page d'app : où l'on est (le titre), quand (le sur-titre), et ce
 * qu'on y fait (les actions, à droite).
 *
 *   <header class="entete">
 *     <div class="entete-texte">
 *       <p class="entete-surtitre">Jeudi 1 octobre · semaine 40</p>
 *       <h1 class="entete-titre">Aujourd'hui</h1>
 *     </div>
 *     <div class="entete-actions">
 *       <button class="bouton" type="button">Tâche</button>
 *     </div>
 *   </header>
 *
 * Le sur-titre dit le MOMENT (une date, une semaine, un taux) en `--encre-courant` : c'est ce qui
 * situe la page sans phrase. Il est facultatif ; le titre ne l'est pas. Les actions passent sous
 * le titre quand la place manque. Sur un téléphone, l'en-tête vit SOUS la `.barre` : la barre
 * porte la navigation, l'en-tête porte la page.
 *   .entete-compact : le titre d'un cran plus petit (un panneau, une sous-page) */
.entete {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--espace-3);
}

.entete-texte {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--espace-2);
  min-width: 0;
}

.entete-surtitre {
  margin: 0;
  font-family: var(--police-chiffres);
  font-size: var(--taille-mention);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--interlettrage-mention);
  text-transform: var(--casse-mention);
  color: var(--encre-courant);
}

.entete-titre {
  margin: 0;
  font-family: var(--police-titre);
  font-size: var(--taille-hero);
  font-weight: var(--graisse-titre);
  line-height: var(--interligne-titre);
  letter-spacing: var(--interlettrage-titre);
  text-transform: var(--casse-titre);
  overflow-wrap: anywhere;
}

.entete-compact .entete-titre {
  font-size: var(--taille-titre);
}

.entete-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--espace-2);
}

/* ── design/composants/vide.css ── */
/* État vide — ce qu'on montre quand il n'y a encore rien, et ce qu'on peut y faire.
 *
 *   <div class="vide">
 *     <p class="vide-titre">Aucune présentation</p>
 *     <p>Déposez un zip pour commencer.</p>
 *     <button class="bouton">Déposer un deck</button>
 *   </div> */
.vide {
  display: grid;
  justify-items: center;
  gap: var(--espace-2);
  padding: var(--espace-6) var(--espace-4);
  text-align: center;
  color: var(--encre-discrete);
}

.vide > * {
  margin: 0;
}

/* La typographie du titre est celle de `.carte-titre` (carte.css). */
.vide-titre {
  color: var(--texte-page);
}

.vide > .bouton {
  margin-top: var(--espace-2);
}

/* ── design/composants/progression.css ── */
/* Progression — une jauge qui se remplit, une roue qui tourne, un squelette qui attend.
 *
 *   <div class="jauge" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100"
 *        aria-label="Téléversement" style="--valeur: 40"></div>
 *   .jauge-fine (2px, discrète)  .jauge-succes  .jauge-attention  .jauge-erreur
 *   `--jauge-teinte` : la couleur d'une donnée (une habitude), posée à l'usage
 *
 *   <span class="chargement" role="status" aria-label="Chargement"></span>   .chargement-grand
 *   <div class="squelette" style="height: 1rem; width: 60%"></div>
 *   Et plus bas : .anneau (la jauge en rond), .jalons (les étapes d'une série), .niveau (1 à 5).
 *
 * `--valeur` (0 à 100) et `aria-valuenow` disent la même chose, l'un à l'œil, l'autre au lecteur
 * d'écran : les deux se posent ensemble.
 *
 * La `.colonne` (graphique.css) est une jauge debout : sa piste, son remplissage et ses trois
 * tons sont posés ici avec ceux de la jauge — le ton de base avant les tons, pour qu'ils le
 * remplacent. */
.jauge,
.colonne {
  position: relative;
  overflow: hidden;
  /* La piste est un trait, pas une surface : `--fond-discret` se confond avec la page (1.1:1
     dans les deux thèmes) et une jauge à 40 % ressemblait à un trait court. */
  background: var(--bordure);
}

.jauge {
  --_ton: var(--jauge-teinte, var(--fond-marque));

  height: 0.5rem;
  border-radius: var(--rayon-plein);
}

.colonne {
  --_ton: var(--colonne-teinte, var(--fond-marque));
}

.jauge::after,
.colonne::after {
  content: '';
  position: absolute;
  border-radius: inherit;
  background: var(--_ton);
}

.jauge::after {
  inset: 0 auto 0 0;
  width: calc(var(--valeur, 0) * 1%);
  transition: width var(--duree);
}

.jauge-fine {
  height: 2px;
}

.jauge-succes,
.colonne-succes {
  --_ton: var(--encre-succes);
}

.jauge-attention,
.colonne-attention {
  --_ton: var(--encre-attention);
}

.jauge-erreur,
.colonne-erreur {
  --_ton: var(--encre-erreur);
}

.chargement {
  display: inline-block;
  width: 1.25rem;
  height: 1.25rem;
  border: 2px solid var(--bordure-forte);
  border-top-color: var(--fond-marque);
  border-radius: var(--rayon-plein);
  animation: chargement-tourne 700ms linear infinite;
  vertical-align: middle;
}

.chargement-grand {
  width: 2.5rem;
  height: 2.5rem;
  border-width: 3px;
}

@keyframes chargement-tourne {
  to {
    transform: rotate(1turn);
  }
}

/* Une roue figée ne dit plus « ça travaille ». Sous `prefers-reduced-motion`, la règle de base
   coupe les animations ; celle-ci est l'exception : elle ralentit au lieu de s'arrêter. */
@media (prefers-reduced-motion: reduce) {
  .chargement {
    animation-duration: 2s !important;
    animation-iteration-count: infinite !important;
  }
}

.squelette {
  min-height: 1em;
  border-radius: var(--rayon);
  background: var(--bordure);
  animation: squelette-pulse 1.4s ease-in-out infinite;
}

@keyframes squelette-pulse {
  50% {
    opacity: 0.5;
  }
}

/* Anneau — la jauge en rond : un objectif hebdomadaire, un repos qui s'écoule.
 *
 *   <span class="anneau-cadre" style="--valeur: 60">
 *     <svg class="anneau" viewBox="0 0 36 36" role="img" aria-label="3 sur 5">
 *       <circle class="anneau-piste" cx="18" cy="18" r="16" pathLength="100"/>
 *       <circle class="anneau-trace" cx="18" cy="18" r="16" pathLength="100"/>
 *     </svg>
 *     <span class="anneau-valeur">3</span>
 *   </span>
 *
 * `pathLength="100"` sur les cercles : la trace se dessine en pourcents, quel que soit le rayon.
 * `--anneau-taille` (2rem), `--anneau-epaisseur` (3) et `--anneau-teinte` (la marque) se règlent
 * à l'usage — la teinte d'un type d'activité, la taille d'un chrono de repos. */
.anneau-cadre {
  position: relative;
  display: inline-grid;
  place-items: center;
  width: var(--anneau-taille, 2rem);
  height: var(--anneau-taille, 2rem);
}

.anneau {
  grid-area: 1 / 1;
  width: 100%;
  height: 100%;
  transform: rotate(-90deg);
}

.anneau-piste,
.anneau-trace {
  fill: none;
  stroke-width: var(--anneau-epaisseur, 3);
}

.anneau-piste {
  stroke: var(--bordure);
}

.anneau-trace {
  stroke: var(--anneau-teinte, var(--fond-marque));
  stroke-linecap: round;
  stroke-dasharray: 100;
  stroke-dashoffset: calc(100 - var(--valeur, 0));
  transition: stroke-dashoffset var(--duree);
}

.anneau-valeur {
  grid-area: 1 / 1;
  font-family: var(--police-chiffres);
  font-size: var(--taille-mention);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

/* Jalons — les étapes d'une suite courte, faites, en cours, à venir : les séries d'un exercice.
 *
 *   <ol class="jalons" aria-label="Séries">
 *     <li class="jalon" data-fait><span class="jalon-repere">✓</span>S1</li>
 *     <li class="jalon" aria-current="step"><span class="jalon-repere">2</span>S2</li>
 *     <li class="jalon"><span class="jalon-repere">3</span>S3</li>
 *   </ol>
 *
 * Un état par attribut : `data-fait`, `aria-current="step"`. Pas plus de dix — au-delà, c'est une
 * jauge. */
.jalons {
  display: flex;
  justify-content: center;
  gap: var(--espace-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.jalon {
  display: grid;
  justify-items: center;
  gap: var(--espace-1);
  font-family: var(--police-chiffres);
  font-size: var(--taille-mention);
  color: var(--encre-discrete);
}

.jalon-repere {
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  border-radius: var(--rayon-plein);
  background: var(--fond-discret);
  color: var(--texte-discret);
  font-weight: var(--graisse-forte);
}

/* L'étape en cours dit « maintenant » : l'encre courante (Braise 2), en cerne sur la surface
   discrète — un disque plein de marque en faisait un bouton, ce que le rouge ne dit plus que
   pour agir. Forme ET couleur : l'anneau se voit sans la couleur. */
.jalon[aria-current='step'] {
  color: var(--encre-courant);
}

.jalon[aria-current='step'] .jalon-repere {
  color: var(--encre-courant);
  box-shadow: inset 0 0 0 2px var(--encre-courant);
}

.jalon[data-fait] .jalon-repere {
  background: var(--fond-succes);
  color: var(--texte-succes);
  box-shadow: inset 0 0 0 1px var(--encre-succes);
}

/* Niveau — de un à cinq points : la complexité d'un exercice, la difficulté d'une piste.
 *
 *   <span class="niveau" data-niveau="3" role="img" aria-label="Niveau 3 sur 5"><i></i><i></i><i></i><i></i><i></i></span> */
.niveau {
  display: inline-flex;
  gap: 2px;
  vertical-align: middle;
}

.niveau > i {
  width: 0.375rem;
  height: 0.375rem;
  border-radius: var(--rayon-plein);
  background: var(--bordure-forte);
}

.niveau[data-niveau='1'] > i:nth-child(-n + 1),
.niveau[data-niveau='2'] > i:nth-child(-n + 2),
.niveau[data-niveau='3'] > i:nth-child(-n + 3),
.niveau[data-niveau='4'] > i:nth-child(-n + 4),
.niveau[data-niveau='5'] > i:nth-child(-n + 5) {
  background: var(--encre-attention);
}

/* ── design/composants/code.css ── */
/* Code — un extrait, une touche du clavier, un bloc à copier.
 *
 *   <code class="code">npm test</code>
 *   <kbd class="touche">Échap</kbd>
 *   <pre class="bloc-code"><code>…</code></pre> */
.code {
  padding: 0.1em 0.35em;
  border-radius: var(--rayon-petit);
  background: var(--fond-discret);
  color: var(--texte-discret);
  font-size: 0.9em;
}

.touche {
  display: inline-block;
  min-width: 1.6em;
  padding: 0.05em 0.4em;
  border: 1px solid var(--bordure-forte);
  border-bottom-width: 2px;
  border-radius: var(--rayon);
  background: var(--fond-carte);
  color: var(--texte-carte);
  font-size: 0.85em;
  text-align: center;
}

.bloc-code {
  margin: 0;
  padding: var(--espace-3);
  overflow-x: auto;
  border-radius: var(--rayon);
  background: var(--fond-discret);
  color: var(--texte-discret);
  font-size: var(--taille-petite);
  line-height: var(--interligne-texte);
}

/* ── design/composants/deroulant.css ── */
/* Déroulant — un détail qu'on ouvre à la demande : le calcul derrière un chiffre, une option
 * avancée, une ligne de tableau.
 *
 *   <details class="deroulant">
 *     <summary>Voir le calcul</summary>
 *     <div>…</div>
 *   </details>
 *
 * Un <details> natif : le clavier et l'état ouvert/fermé annoncé viennent du navigateur. */
.deroulant {
  border-bottom: 1px solid var(--bordure);
}

.deroulant > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--espace-3);
  min-height: var(--cible-tactile);
  font-weight: var(--graisse-moyenne);
  list-style: none;
  cursor: pointer;
}

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

.deroulant > summary::after {
  content: '';
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  margin-right: var(--espace-1);
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: translateY(-25%) rotate(45deg);
  transition: transform var(--duree);
}

.deroulant[open] > summary::after {
  transform: translateY(25%) rotate(-135deg);
}

.deroulant > :not(summary) {
  padding-bottom: var(--espace-3);
}

/* ── design/composants/reponse.css ── */
/* Réponse — une réponse de quiz, de sondage, de QCM : une lettre, un libellé, et ce qu'il en
 * advient (choisie, juste, la mienne, sa part des votes).
 *
 *   <div class="reponses" role="radiogroup" aria-label="…">          --reponses-colonnes: 2
 *     <button class="reponse" role="radio" aria-checked="false"><span class="reponse-lettre">A</span><span class="reponse-libelle">…</span></button>
 *   </div>
 *
 *   Dépouillement :
 *   <div class="reponse" data-juste data-mienne>
 *     <span class="reponse-lettre">B</span><span class="reponse-libelle">…</span>
 *     <span class="reponse-part" style="--part: 60%"></span><span class="reponse-compte">12</span>
 *   </div>
 *
 * Un état par attribut : `aria-checked` (choisie — un radio, donc les flèches et Entrée
 * viennent du navigateur), `disabled` (verrouillée), `data-juste` (la bonne), `data-mienne`
 * (celle qu'on avait choisie). Pas de couleur par réponse : les lettres font le travail
 * qu'on demanderait à quatre aplats, pour zéro jeton. Sur une surface inverse, poser
 * `.sur-inverse` sur le conteneur suffit. */
.reponses {
  display: grid;
  grid-template-columns: repeat(var(--reponses-colonnes, 1), minmax(0, 1fr));
  /* 8 px : assez pour qu'une erreur de tap ne valide pas le voisin. C'est l'action
     principale, sous pression de temps, au doigt. */
  gap: var(--espace-2);
}

.reponse {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--espace-3);
  width: 100%;
  /* Au-dessus du plancher de 44 px : le doigt tremble. */
  min-height: calc(var(--cible-tactile) + var(--espace-2));
  padding: var(--espace-2) var(--espace-3);
  overflow: hidden;
  border: 1px solid var(--bordure);
  border-radius: var(--rayon);
  background: var(--fond-carte);
  color: var(--texte-carte);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition:
    border-color var(--duree-courte),
    background-color var(--duree-courte);
}

.reponse:hover:not(:disabled) {
  border-color: var(--bordure-forte);
}

.reponse:disabled {
  cursor: default;
}

.reponse-lettre {
  display: grid;
  place-items: center;
  flex: none;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--rayon);
  background: var(--fond-marque);
  color: var(--texte-marque);
  font-family: var(--police-mono);
  font-weight: var(--graisse-forte);
  font-size: var(--taille-petite);
}

.reponse-libelle {
  flex: 1 1 auto;
  min-width: 0;
}

/* Choisie : la seule surface de marque large, et elle ne dure que le temps d'une question. */
.reponse[aria-checked='true'] {
  background: var(--fond-marque);
  color: var(--texte-marque);
  border-color: var(--fond-marque);
}

.reponse[aria-checked='true'] .reponse-lettre {
  background: var(--texte-marque);
  color: var(--fond-marque);
}

/* La part des votes : une barre derrière le libellé, jamais un graphique à part — la
   comparaison se fait sur la ligne qu'on est en train de lire. Une part à zéro reste un
   filet : « personne » plutôt qu'un affichage disparu, qu'on lirait comme un bug. */
.reponse-part {
  position: absolute;
  inset: 0 auto 0 0;
  z-index: 0;
  width: var(--part, 0%);
  min-width: 2px;
  background: var(--fond-accent);
}

.reponse-lettre,
.reponse-libelle,
.reponse-compte {
  position: relative;
  z-index: 1;
}

.reponse-compte {
  flex: none;
  font-family: var(--police-chiffres);
  font-variant-numeric: tabular-nums;
  font-size: var(--taille-petite);
  color: var(--encre-discrete);
}

/* La bonne réponse : la seule couleur du dépouillement, et elle est déjà au système. */
.reponse[data-juste] {
  border-color: var(--encre-succes);
  box-shadow: inset 0 0 0 1px var(--encre-succes);
}

.reponse[data-juste] .reponse-lettre {
  background: var(--encre-succes);
  color: var(--fond-page);
}

.reponse[data-mienne] .reponse-libelle {
  font-weight: var(--graisse-forte);
}

/* ── design/composants/classement.css ── */
/* Classement — des rangs, des noms, des points : un podium de quiz, un tableau de scores.
 *
 *   <ol class="classement">
 *     <li><span class="classement-rang">1</span><span class="classement-nom">Camille</span><span class="classement-points">840</span></li>
 *     <li aria-current="true">…</li>                                   la ligne du lecteur
 *   </ol>
 *
 * Pas une région live : lu à voix haute, un classement de vingt-cinq noms est une torture.
 * Sur une surface inverse, `.sur-inverse` sur le conteneur suffit. */
.classement {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 0;
  list-style: none;
}

.classement > li {
  display: flex;
  align-items: baseline;
  gap: var(--espace-3);
  padding: var(--espace-2) 0;
  border-bottom: 1px solid var(--bordure);
}

.classement > li[aria-current='true'] {
  font-weight: var(--graisse-forte);
}

.classement-rang {
  flex: none;
  width: 2ch;
  font-family: var(--police-chiffres);
  font-variant-numeric: tabular-nums;
  color: var(--encre-discrete);
}

.classement-nom {
  flex: 1 1 auto;
  /* Un nom de soixante signes ne doit pas repousser les points hors de l'écran. */
  overflow-wrap: anywhere;
}

.classement-points {
  flex: none;
  font-family: var(--police-chiffres);
  font-variant-numeric: tabular-nums;
}

/* ── design/composants/code-affiche.css ── */
/* Code affiché — un code d'accès à lire du fond d'une salle : sur un mur, sur un téléphone.
 *
 *   <div class="code-affiche">
 *     <p class="code-affiche-mention">Rejoindre la présentation</p>
 *     <p class="code-affiche-valeur">482913</p>
 *     <p class="code-affiche-aide">slides.lyodge.com</p>
 *   </div>
 *
 * Le pendant de `.saisie-code` (champ) : même chasse, même interlettrage. `clamp` plutôt
 * qu'une taille fixe, parce que le même bloc sert un vidéoprojecteur 16:9 et un téléphone
 * tenu à la main — `--code-affiche-max` borne le haut (9rem par défaut). Sur une surface
 * inverse, `.sur-inverse` sur le conteneur suffit.
 *
 * La taille suit la largeur du BLOC (`cqi`), pas celle de l'écran : six chiffres en chasse
 * fixe, interlettrage compris, font ~5,7 fois la taille du texte — 17cqi les tient dans le
 * bloc. En `vw`, ils débordaient de ~11 px à 375 px dès que le bloc avait une marge (slides,
 * 2026-10-02). Le bloc prend toute la largeur offerte (`inline-size: 100%`) : la mesure par
 * conteneur ne regarde plus son contenu, un élément flexible sans largeur tomberait à zéro. */
.code-affiche {
  container-type: inline-size;
  inline-size: 100%;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--espace-3);
  text-align: center;
}

.code-affiche > p {
  margin: 0;
}

.code-affiche-mention {
  font-size: var(--taille-mention);
  font-weight: var(--graisse-moyenne);
  letter-spacing: var(--interlettrage-mention);
  text-transform: var(--casse-mention);
  color: var(--encre-discrete);
}

.code-affiche-valeur {
  font-family: var(--police-mono);
  font-size: clamp(var(--taille-code), 17cqi, var(--code-affiche-max, 9rem));
  letter-spacing: var(--interlettrage-code);
  /* L'interlettrage suit aussi le dernier chiffre : le même retrait devant recentre le code. */
  text-indent: var(--interlettrage-code);
  line-height: 1;
}

.code-affiche-aide {
  font-size: var(--taille-intertitre);
  color: var(--encre-discrete);
}

/* ── design/composants/compteur.css ── */
/* Compteur — un nombre qu'on règle au doigt : séries, répétitions, kilos, secondes de repos.
 *
 *   <div class="compteur">
 *     <span class="compteur-libelle" id="c-series">Séries</span>
 *     <div class="compteur-commandes" role="group" aria-labelledby="c-series">
 *       <button class="compteur-bouton" type="button" aria-label="Moins">−</button>
 *       <input class="compteur-valeur" inputmode="decimal" value="3" aria-labelledby="c-series">
 *       <button class="compteur-bouton" type="button" aria-label="Plus">+</button>
 *     </div>
 *   </div>
 *
 * La valeur est une saisie : on tape un nombre sans appuyer trente fois. Les deux boutons
 * portent un `aria-label` et un appui long qui répète (logique de l'app). `.compteur-petit` :
 * la version dense, quand quatre compteurs tiennent dans une carte. */
.compteur {
  display: grid;
  gap: var(--espace-1);
  justify-items: start;
}

.compteur-libelle {
  font-size: var(--taille-mention);
  font-weight: var(--graisse-moyenne);
  color: var(--encre-discrete);
}

.compteur-commandes {
  display: inline-flex;
  align-items: center;
  gap: var(--espace-1);
}

.compteur-bouton {
  display: grid;
  place-items: center;
  flex: none;
  width: var(--cible-tactile);
  height: var(--cible-tactile);
  border: 1px solid var(--bordure);
  border-radius: var(--rayon);
  background: var(--fond-discret);
  color: var(--texte-discret);
  font: inherit;
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
  touch-action: manipulation;
  user-select: none;
  -webkit-user-select: none;
  transition:
    background-color var(--duree-courte),
    color var(--duree-courte);
}

.compteur-bouton:hover:not(:disabled) {
  background: var(--fond-eleve);
  color: var(--texte-eleve);
}

.compteur-bouton:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.compteur-valeur {
  display: grid;
  place-items: center;
  width: 4rem;
  height: var(--cible-tactile);
  margin: 0;
  padding: 0;
  border: 1px solid var(--bordure-champ);
  border-radius: var(--rayon);
  background: var(--fond-champ);
  color: var(--texte-champ);
  font-family: var(--police-chiffres);
  /* 16px au moins : en dessous, Safari iOS zoome sur la page au focus. */
  font-size: max(1rem, 16px);
  font-weight: var(--graisse-forte);
  font-variant-numeric: tabular-nums;
  text-align: center;
  appearance: textfield;
}

.compteur-valeur::-webkit-outer-spin-button,
.compteur-valeur::-webkit-inner-spin-button {
  appearance: none;
}

.compteur-valeur:focus-visible {
  border-color: var(--focus-couleur);
}

.compteur-petit .compteur-bouton,
.compteur-petit .compteur-valeur {
  height: var(--cible-dense);
}

.compteur-petit .compteur-bouton {
  width: var(--cible-dense);
}

.compteur-petit .compteur-valeur {
  width: 3.5rem;
}

/* ── design/composants/menu.css ── */
/* Menu — une liste d'options qui s'ouvre sous un bouton : un filtre à cases, un choix parmi dix.
 *
 *   <details class="menu" data-actif>
 *     <summary>Muscles <span class="menu-compte">2</span></summary>
 *     <div class="menu-liste" role="group" aria-label="Muscles">
 *       <label class="menu-option"><input type="checkbox" checked> Pectoraux</label>
 *       <label class="menu-option"><input type="checkbox"> Dos</label>
 *       <button class="menu-vider" type="button">Effacer</button>
 *     </div>
 *   </details>
 *
 * Un <details> natif : ouvrir, fermer et l'état annoncé viennent du navigateur ; la fermeture au
 * clic dehors reste à l'app (un `toggle` sur le document). `data-actif` sur le menu quand une
 * option est cochée, `.menu-compte` pour dire combien. `.menu-droite` aligne la liste à droite,
 * pour le dernier menu d'une rangée qui déborderait de l'écran.
 *
 * Une option est un <label> (une case) ou un <button> (une valeur à prendre) ; celle qui est
 * choisie, ou sous le curseur du clavier, porte `aria-selected="true"` ou `aria-current="true"`.
 *
 *   <div class="menu menu-etendu">                       des suggestions sous une saisie
 *     <input class="saisie" role="combobox" aria-expanded="true" aria-controls="sug">
 *     <div class="menu-liste" id="sug" role="listbox">
 *       <button class="menu-option" type="button" role="option" aria-selected="true">Pain</button>
 *     </div>
 *   </div>
 *
 * `.menu-etendu` : le menu prend la largeur de ce qu'il contient (une saisie), la liste aussi —
 * sans <details>, c'est l'app qui montre et retire la liste. */
.menu {
  position: relative;
  display: inline-block;
}

.menu > summary {
  display: inline-flex;
  align-items: center;
  gap: var(--espace-1);
  min-height: var(--cible-tactile);
  padding: 0 var(--espace-3);
  border: 1px solid var(--bordure-champ);
  border-radius: var(--rayon-plein);
  color: var(--encre-discrete);
  font-size: var(--taille-petite);
  font-weight: var(--graisse-moyenne);
  white-space: nowrap;
  list-style: none;
  cursor: pointer;
  transition:
    border-color var(--duree-courte),
    color var(--duree-courte);
}

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

.menu > summary::after {
  content: '';
  flex: none;
  width: 0.375rem;
  height: 0.375rem;
  margin-left: var(--espace-1);
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: translateY(-25%) rotate(45deg);
  transition: transform var(--duree);
}

.menu[open] > summary::after {
  transform: translateY(25%) rotate(-135deg);
}

.menu > summary:hover {
  border-color: var(--encre-discrete);
  color: var(--texte-page);
}

.menu[data-actif] > summary {
  border-color: var(--encre-marque);
  color: var(--encre-marque);
}

.menu-compte {
  display: inline-grid;
  place-items: center;
  min-width: 1.25rem;
  height: 1.25rem;
  padding: 0 var(--espace-1);
  border-radius: var(--rayon-plein);
  background: var(--fond-marque);
  color: var(--texte-marque);
  font-family: var(--police-chiffres);
  font-size: var(--taille-mention);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.menu-liste {
  position: absolute;
  top: calc(100% + var(--espace-1));
  left: 0;
  z-index: var(--couche-barre);
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  min-width: 12rem;
  max-height: 16rem;
  overflow-y: auto;
  padding: var(--espace-1);
  border: 1px solid var(--bordure);
  border-radius: var(--rayon);
  background: var(--fond-eleve);
  color: var(--texte-eleve);
  box-shadow: var(--ombre-portee);
}

.menu-droite .menu-liste {
  right: 0;
  left: auto;
}

.menu-etendu {
  display: block;
}

.menu-etendu > .menu-liste {
  right: 0;
}

/* La rangée : un <li>, un <a>, un <button> ou un <label> remis à zéro, aligné, à la hauteur
   d'un doigt. L'option de menu, la ligne de liste (liste.css) et le lien de navigation
   (navigation.css) la partagent ; chacun pose ensuite son écart, son retrait, sa couleur. Elle
   vit ici parce que menu.css est la première des trois dans l'ordre d'assemblage
   (design/assembler.ts) : posée plus loin, son `font: inherit` écraserait le `font-size` de
   l'option. */
.ligne,
.navigation-lien,
.menu-option {
  display: flex;
  align-items: center;
  width: 100%;
  min-height: var(--cible-tactile);
  border: 0;
  background: none;
  font: inherit;
  text-align: left;
}

.menu-option {
  gap: var(--espace-2);
  padding: 0 var(--espace-2);
  border-radius: var(--rayon-petit);
  color: inherit;
  font-size: var(--taille-petite);
  white-space: nowrap;
  cursor: pointer;
}

.menu-option:hover,
.menu-option[aria-selected='true'],
.menu-option[aria-current='true'] {
  background: var(--fond-accent);
  color: var(--texte-accent);
}

.menu-option > input {
  margin: 0;
}

.menu-vider {
  min-height: var(--cible-dense);
  margin-top: var(--espace-1);
  border: 0;
  border-top: 1px solid var(--bordure);
  background: none;
  color: var(--encre-discrete);
  font: inherit;
  font-size: var(--taille-petite);
  cursor: pointer;
}

.menu-vider:hover {
  color: var(--texte-page);
}

/* ── design/composants/puce.css ── */
/* Puce — un mot qu'on allume : une catégorie, un muscle, un modèle, une plage de dates.
 *
 *   <div class="puces" role="group" aria-label="Muscles">
 *     <button class="puce" type="button" aria-pressed="true">Pectoraux</button>
 *     <button class="puce" type="button" aria-pressed="false">Dos</button>
 *   </div>
 *
 * Un bouton à bascule (`aria-pressed`), en pilule et en petit texte — ce n'est pas une
 * étiquette (qui ne se clique pas) ni un segment (qui choisit UNE vue). Pour un choix unique,
 * `role="radio"` + `aria-checked` sur les puces et `role="radiogroup"` sur le groupe.
 *   .puces-defilantes : sur une ligne, qui défile — les modèles d'activité sur un téléphone
 *   .puce-point : une pastille de la couleur posée dans `--puce-teinte` (un type, sa couleur)
 *   .puce-attention / .puce-succes : allumée dans une autre couleur que la marque, quand la
 *   rangée dit autre chose qu'un filtre (un niveau, un schéma de mouvement) */
.puces {
  display: flex;
  flex-wrap: wrap;
  gap: var(--espace-2);
}

.puces-defilantes {
  flex-wrap: nowrap;
  overflow-x: auto;
  padding-bottom: 2px;
  scrollbar-width: none;
}

.puces-defilantes::-webkit-scrollbar {
  display: none;
}

.puce {
  --_allumee: var(--encre-marque);

  display: inline-flex;
  align-items: center;
  gap: var(--espace-1);
  flex: none;
  min-height: var(--cible-tactile);
  padding: 0 var(--espace-3);
  border: 1px solid var(--bordure-champ);
  border-radius: var(--rayon-plein);
  background: transparent;
  color: var(--encre-discrete);
  font: inherit;
  font-size: var(--taille-petite);
  font-weight: var(--graisse-moyenne);
  line-height: 1.2;
  white-space: nowrap;
  cursor: pointer;
  transition:
    border-color var(--duree-courte),
    background-color var(--duree-courte),
    color var(--duree-courte);
}

.puce:hover:not(:disabled) {
  border-color: var(--encre-discrete);
  color: var(--texte-page);
}

/* Allumée : la surface d'accent et SON texte (une paire que le thème vérifie) ; la couleur ne
   va que dans le cerne. Écrire l'encre de marque sur l'accent donnait 3,4:1 dans Braise —
   trouvé en revue, pas par le test, qui ne connaît pas cette paire. */
.puce[aria-pressed='true'],
.puce[aria-checked='true'] {
  border-color: var(--_allumee);
  background: var(--fond-accent);
  color: var(--texte-accent);
  box-shadow: inset 0 0 0 1px var(--_allumee);
}

.puce-attention {
  --_allumee: var(--encre-attention);
}

.puce-succes {
  --_allumee: var(--encre-succes);
}

.puce:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.puce-point::before {
  content: '';
  width: 0.5em;
  height: 0.5em;
  border-radius: var(--rayon-plein);
  background: var(--puce-teinte, currentColor);
}

.puce-petite {
  min-height: var(--cible-dense);
  padding: 0 var(--espace-2);
  font-size: var(--taille-mention);
}

/* ── design/composants/bulle.css ── */
/* Bulle — un fil de messages : le coach de repstack, une aide en ligne.
 *
 *   <div class="bulles" role="log" aria-live="polite">
 *     <p class="bulle-date">Aujourd'hui</p>
 *     <div class="bulle">Salut ! Pose-moi une question sur ta séance.</div>
 *     <div class="bulle bulle-moi">Je fais quoi demain ?</div>
 *     <div class="bulle bulle-frappe" role="status" aria-label="Le coach écrit"><i></i><i></i><i></i></div>
 *   </div>
 *
 * L'autre parle sur la surface élevée, à gauche ; moi sur la marque, à droite — deux paires que
 * le thème vérifie. Le texte garde ses retours à la ligne (`pre-wrap`) et casse les mots trop
 * longs : une URL ne déborde jamais de l'écran. */
.bulles {
  display: flex;
  flex-direction: column;
  gap: var(--espace-2);
}

.bulle {
  align-self: flex-start;
  max-width: 80%;
  padding: var(--espace-2) var(--espace-3);
  border-radius: var(--rayon-grand);
  border-bottom-left-radius: var(--rayon-petit);
  background: var(--fond-eleve);
  color: var(--texte-eleve);
  font-size: var(--taille-petite);
  line-height: var(--interligne-texte);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.bulle-moi {
  align-self: flex-end;
  border-bottom-left-radius: var(--rayon-grand);
  border-bottom-right-radius: var(--rayon-petit);
  background: var(--fond-marque);
  color: var(--texte-marque);
}

.bulle-date {
  align-self: center;
  margin: var(--espace-2) 0 0;
  font-family: var(--police-chiffres);
  font-size: var(--taille-mention);
  color: var(--encre-discrete);
}

/* « … écrit » : trois points qui respirent, à la place d'un message qui n'existe pas encore. */
.bulle-frappe {
  display: flex;
  align-items: center;
  gap: var(--espace-1);
  min-height: 2.5rem;
}

.bulle-frappe > i {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: var(--rayon-plein);
  background: currentColor;
  opacity: 0.4;
  animation: bulle-frappe 1s ease-in-out infinite;
}

.bulle-frappe > i:nth-child(2) {
  animation-delay: 0.15s;
}

.bulle-frappe > i:nth-child(3) {
  animation-delay: 0.3s;
}

@keyframes bulle-frappe {
  50% {
    opacity: 1;
    transform: translateY(-3px);
  }
}

/* ── design/composants/chiffre.css ── */
/* Chiffre — LA valeur de l'écran : les répétitions à faire, le chrono, le poids du jour.
 *
 *   <p class="chiffre chiffre-hero">12<span class="chiffre-unite">reps</span></p>
 *   <p class="chiffre chiffre-grand">82,4<span class="chiffre-unite">kg</span></p>
 *   <span class="chiffre">3 × 12</span>
 *
 * Toujours en chasse fixe et tabulaire : un chrono qui passe de 10 à 9 ne bouge pas d'un
 * pixel. `.chiffre-hero` se lit à bout de bras entre deux séries ; `.chiffre-grand` en tête
 * d'une feuille ; nu, dans une ligne de texte. L'unité reste petite et discrète : c'est le
 * nombre qu'on regarde.
 *   .chiffre-legende : ce que le nombre compte, sous lui (une bande de chiffres, un cas client)
 *     <p class="chiffre chiffre-grand">76</p><p class="chiffre-legende">personnes formées</p> */
.chiffre {
  margin: 0;
  font-family: var(--police-chiffres);
  font-weight: var(--graisse-forte);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  letter-spacing: var(--interlettrage-titre);
}

.chiffre-grand {
  font-size: var(--taille-hero);
}

.chiffre-hero {
  font-size: var(--taille-affichage);
}

.chiffre-unite {
  margin-left: var(--espace-1);
  font-family: var(--police);
  font-size: var(--taille-intertitre);
  font-weight: var(--graisse-normale);
  letter-spacing: normal;
  color: var(--encre-discrete);
}

.chiffre-legende {
  max-width: 22rem;
  margin: 0;
  font-size: var(--taille-petite);
  line-height: var(--interligne-texte);
  color: var(--encre-discrete);
}

/* ── design/composants/vignette.css ── */
/* Vignette — l'icône d'une chose, dans un carré teinté : un type de séance, une activité, un
 * exercice avec sa photo.
 *
 *   <span class="vignette" style="--teinte: var(--serie-2)"><svg …/></span>
 *   <span class="vignette vignette-pleine" style="--teinte: var(--serie-3)"><svg …/></span>
 *   <span class="vignette vignette-image"><img src="…" alt=""></span>
 *   .vignette-petite  .vignette-grande
 *
 * La teinte vient de l'usage (`--teinte`), jamais d'une classe par couleur : les couleurs d'un
 * type appartiennent aux données de l'app. Le fond est la teinte elle-même, à 14 % — un
 * pseudo-élément en opacité, parce qu'un thème ne fournit pas une surface par teinte. */
.vignette {
  --_teinte: var(--teinte, var(--encre-marque));

  position: relative;
  display: inline-grid;
  place-items: center;
  flex: none;
  width: 2.5rem;
  height: 2.5rem;
  overflow: hidden;
  border-radius: var(--rayon);
  color: var(--_teinte);
}

.vignette::before {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--_teinte);
  opacity: 0.14;
}

.vignette > * {
  position: relative;
}

.vignette-petite {
  width: 2.25rem;
  height: 2.25rem;
}

.vignette-grande {
  width: 3rem;
  height: 3rem;
  border-radius: var(--rayon-grand);
}

/* Pleine : la teinte en fond, l'icône claire — quand la vignette EST le titre (l'en-tête d'un
   récapitulatif). Le texte de la marque, clair dans tous les thèmes, sert d'encre. */
.vignette-pleine {
  color: var(--texte-marque);
}

.vignette-pleine::before {
  opacity: 1;
}

.vignette-image {
  background: var(--fond-eleve);
}

.vignette-image::before {
  display: none;
}

.vignette-image > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ── design/composants/graphique.css ── */
/* Graphique — le cadre d'un graphique, et la mini-courbe qui tient dans une ligne.
 *
 *   <div class="graphique">…un <svg> (recharts, d3, à la main)…</div>
 *   <svg class="mini-graphique" width="80" height="16" role="img" aria-label="…" style="color: var(--serie-2)">
 *     <rect …/>
 *   </svg>
 *
 * Le cadre pose la surface et le padding ; ce qu'il y a dedans lit les jetons : `--bordure` pour
 * la grille, `--encre-discrete` pour les graduations, `--serie-1` à `--serie-6` pour les
 * séries, `--fond-eleve` / `--texte-eleve` pour l'infobulle. Aucune couleur écrite dans le code
 * du graphique — la même règle que partout.
 * La mini-courbe dessine en `currentColor` : sa couleur se pose sur le <svg>, en encre.
 *
 *   <ol class="colonnes" aria-label="Semaine">
 *     <li class="colonne colonne-succes" style="--valeur: 80" aria-label="Lundi, 1 760 sur 2 200 kcal"></li>
 *     <li class="colonne colonne-erreur" style="--valeur: 100" aria-current="date" aria-label="…"></li>
 *   </ol>
 *
 * Les colonnes : quelques jours côte à côte, une jauge debout chacun — la semaine d'un budget.
 * Chaque jour garde sa piste : un jour vide se lit encore comme un jour. `--valeur` de 0 à 100
 * (et `--colonne-teinte` pour la couleur d'une donnée, comme `--jauge-teinte`),
 * `aria-current="date"` pour aujourd'hui, et ce que vaut la colonne dans son `aria-label`.
 *   .colonnes-objectif : un trait en pointillé à `--objectif` (0 à 100) de la hauteur — la cible
 *   qu'on voit sans lire un chiffre
 *
 * Les tracés : des classes pour le CONTENU du <svg> d'un `.graphique`, pour qu'un graphique écrit
 * à la main (ou par d3) ne pose aucun `stroke`, aucun `fill` dans son code.
 *
 *   <svg viewBox="0 0 320 120" class="graphique-svg" role="img" aria-label="…">
 *     <rect class="graphique-repere" …/>                     la colonne survolée, derrière tout
 *     <line class="graphique-grille" …/>
 *     <g class="graphique-axe"><line …/><text …>12 k</text></g>
 *     <path class="graphique-aire" d="…"/>
 *     <path class="graphique-trace" d="…"/>
 *     <path class="graphique-precedent" d="…"/>             la période précédente, en pointillé
 *     <path class="graphique-trace graphique-partiel" d="…"/>   la période pas encore finie
 *     <rect class="graphique-barre" style="--trace-teinte: var(--serie-2)" …/>
 *     <g class="graphique-seuil"><line …/><text …>200 k</text></g>
 *   </svg>
 *
 * La teinte d'une série se pose à l'usage dans `--trace-teinte` : sur le tracé, son aire, ses
 * barres — un `<g style="--trace-teinte: …">` la donne à tout un groupe. Par défaut `--serie-2`,
 * pas `--serie-1` : en Braise, la série 1 est le rouge de marque, et une courbe n'est pas une action.
 * Le texte des axes est en `--taille-mention`, en unités du viewBox : l'app pose le viewBox à
 * la taille RENDUE (elle mesure le cadre), sinon les libellés grossissent avec la largeur.
 * `.graphique-partiel` s'ajoute à un tracé ou à une barre ; `--seuil-teinte` (par défaut
 * `--encre-attention`) colore un seuil qui n'est pas une alerte. */
.graphique {
  position: relative;
  padding: var(--espace-2) var(--espace-2) var(--espace-1);
  border: 1px solid var(--bordure);
  border-radius: var(--rayon-grand);
  background: var(--fond-carte);
  color: var(--texte-carte);
}

.graphique text {
  font-family: var(--police-chiffres);
  font-variant-numeric: tabular-nums;
}

.colonnes {
  display: flex;
  align-items: stretch;
  gap: var(--espace-1);
  height: var(--colonnes-hauteur, 2.5rem);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* La piste, le remplissage et les tons sont ceux de la jauge (progression.css) ; ici, ce qui la
   met debout. */
.colonne {
  flex: 1;
  border-radius: var(--rayon-petit);
}

.colonne::after {
  inset: auto 0 0;
  height: calc(var(--valeur, 0) * 1%);
  transition: height var(--duree);
}

.colonne[aria-current='date'] {
  box-shadow: 0 0 0 1px var(--encre-courant);
}

.colonnes-objectif {
  position: relative;
}

.colonnes-objectif::after {
  content: '';
  position: absolute;
  inset-inline: 0;
  bottom: calc(var(--objectif, 100) * 1%);
  border-top: 1px dashed var(--encre-discrete);
  pointer-events: none;
}

.mini-graphique {
  display: block;
  color: var(--encre-discrete);
}

.mini-graphique rect,
.mini-graphique path {
  fill: currentColor;
}

.mini-graphique rect[data-vide] {
  opacity: 0.25;
}

/* Tracés */
.graphique-svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

.graphique-grille {
  stroke: var(--bordure);
  stroke-dasharray: 3 3;
  shape-rendering: crispEdges;
}

.graphique-axe line,
.graphique-axe path {
  stroke: var(--bordure-forte);
  shape-rendering: crispEdges;
}

.graphique-axe text {
  fill: var(--encre-discrete);
  font-size: var(--taille-mention);
}

.graphique-trace {
  fill: none;
  stroke: var(--trace-teinte, var(--serie-2));
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.graphique-aire {
  fill: var(--trace-teinte, var(--serie-2));
  fill-opacity: 0.16;
  stroke: none;
}

.graphique-barre {
  fill: var(--trace-teinte, var(--serie-2));
  stroke: none;
}

.graphique-precedent {
  fill: none;
  stroke: var(--encre-discrete);
  stroke-width: 1.5;
  stroke-dasharray: 4 3;
  vector-effect: non-scaling-stroke;
}

.graphique-partiel {
  opacity: 0.45;
}

.graphique-seuil line,
.graphique-seuil path {
  stroke: var(--seuil-teinte, var(--encre-attention));
  stroke-width: 1;
  stroke-dasharray: 6 4;
  vector-effect: non-scaling-stroke;
}

.graphique-seuil text {
  fill: var(--seuil-teinte, var(--encre-attention));
  font-size: var(--taille-mention);
}

.graphique-repere {
  fill: var(--fond-accent);
  stroke: none;
}

/* ── design/composants/liste.css ── */
/* Liste — des lignes qu'on parcourt, qu'on coche, qu'on ouvre : des tâches, des courses, des
 * messages, les résultats d'une recherche.
 *
 *   <ul class="liste">
 *     <li class="ligne ligne-cliquable">
 *       <button class="coche" type="button" aria-label="Terminer"></button>
 *       <span class="ligne-corps">
 *         <span class="ligne-titre">Acheter du pain</span>
 *         <span class="ligne-detail">Aujourd'hui · chaque semaine</span>
 *       </span>
 *       <span class="ligne-repere">2/3</span>
 *       <button class="bouton bouton-discret bouton-icone bouton-petit ligne-action" aria-label="Supprimer">…</button>
 *     </li>
 *   </ul>
 *
 * `.ligne` va sur un <li>, un <a> ou un <button> : quand toute la ligne est la cible,
 * `.ligne-cliquable`. Un état par attribut — `data-fait` (rayée), `aria-selected="true"` ou
 * `aria-current="true"` (la ligne sous le curseur du clavier dans une recherche).
 *   .ligne-bordee : chaque ligne est un objet (un message, un article du catalogue), sur sa carte
 *   .ligne-dense  : dans une colonne étroite ou un tableau de bord, bureau seulement
 *   .ligne-action : une action qui n'apparaît qu'au survol ou au focus — toujours visible dès
 *                   qu'un doigt peut la toucher (téléphone, tablette, portable tactile) */
.liste {
  display: grid;
  /* `minmax(0, …)` : sans lui, la colonne prend la largeur du plus long détail (qui ne passe
     pas à la ligne) et la liste déborde de l'écran au lieu de le tronquer. */
  grid-template-columns: minmax(0, 1fr);
  gap: 2px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.liste-aeree {
  gap: var(--espace-1);
}

/* La rangée remise à zéro (flex, pleine largeur, 44 px, sans bordure ni fond) est posée avec
   `.menu-option`, dans menu.css. */
.ligne {
  gap: var(--espace-3);
  padding: var(--espace-1) var(--espace-3);
  border-radius: var(--rayon);
  color: inherit;
  text-decoration: none;
}

.ligne-cliquable {
  cursor: pointer;
  transition:
    background-color var(--duree-courte),
    border-color var(--duree-courte);
}

.ligne-cliquable:hover {
  background: var(--fond-discret);
}

.ligne[aria-selected='true'],
.ligne[aria-current='true'] {
  background: var(--fond-accent);
  color: var(--texte-accent);
}

.ligne-bordee {
  padding: var(--espace-2) var(--espace-3);
  border: 1px solid var(--bordure);
  background: var(--fond-carte);
  color: var(--texte-carte);
}

.ligne-bordee.ligne-cliquable:hover {
  border-color: var(--bordure-forte);
  background: var(--fond-carte);
}

.ligne-dense {
  gap: var(--espace-2);
  min-height: var(--cible-dense);
  padding: 2px var(--espace-2);
  font-size: var(--taille-petite);
}

.ligne-corps {
  flex: 1;
  min-width: 0;
}

/* Deux lignes au plus, puis des points : un titre long ne pousse pas la liste, et une URL
   collée ne déborde pas de l'écran. */
.ligne-titre {
  display: -webkit-box;
  overflow: hidden;
  overflow-wrap: anywhere;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  line-height: 1.35;
}

.ligne-detail {
  display: block;
  overflow: hidden;
  font-size: var(--taille-mention);
  color: var(--encre-discrete);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ligne[data-fait] .ligne-titre {
  color: var(--encre-discrete);
  text-decoration: line-through;
}

/* Un repère en bout ou en tête de ligne : une heure, un compte, une quantité. */
.ligne-repere {
  flex: none;
  font-family: var(--police-chiffres);
  font-size: var(--taille-mention);
  font-variant-numeric: tabular-nums;
  color: var(--encre-discrete);
}

/* Cachée seulement là où AUCUN doigt ne peut la toucher : sur un portable tactile, le pointeur
   principal survole mais un doigt tombe quand même sur un bouton invisible. */
@media (hover: hover) and (not (any-pointer: coarse)) {
  .ligne-action {
    opacity: 0;
    transition: opacity var(--duree-courte);
  }

  .ligne:hover .ligne-action,
  .ligne:focus-within .ligne-action {
    opacity: 1;
  }
}

/* ── design/composants/navigation.css ── */
/* Navigation — la liste des destinations, sur le côté : le menu latéral d'un bureau, le tiroir
 * d'un téléphone (dans un `.dialogue-tiroir`).
 *
 *   <nav class="navigation" aria-label="Navigation principale">
 *     <a class="navigation-lien" href="/today" aria-current="page"><svg …/>Aujourd'hui</a>
 *     <a class="navigation-lien" href="/messages"><svg …/>Messages <span class="etiquette etiquette-marque">2</span></a>
 *     <button class="navigation-lien" type="button"><svg …/>Rechercher <kbd class="touche">/</kbd></button>
 *   </nav>
 *
 * Un lien ou un bouton, c'est la même ligne. La destination en cours se dit par
 * `aria-current="page"` — la surface d'accent et l'icône en `--encre-courant`, jamais la couleur
 * seule (Braise 2 : le filet de marque à gauche disait « alerte » plus que « vous êtes ici »).
 * Une étiquette ou une touche en fin de ligne se range à droite d'elle-même.
 *   .navigation-dense : sur un bureau, où la cible n'a pas à faire 44 px */
.navigation {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-content: start;
  gap: 2px;
}

/* La rangée remise à zéro (flex, pleine largeur, 44 px, sans bordure ni fond) est posée avec
   `.menu-option`, dans menu.css. */
.navigation-lien {
  gap: var(--espace-2);
  padding: 0 var(--espace-2);
  border-radius: var(--rayon);
  color: var(--encre-discrete);
  font-size: var(--taille-petite);
  font-weight: var(--graisse-moyenne);
  text-decoration: none;
  cursor: pointer;
  transition:
    background-color var(--duree-courte),
    color var(--duree-courte);
}

.navigation-lien > svg {
  flex: none;
}

.navigation-lien > .etiquette,
.navigation-lien > .touche {
  margin-left: auto;
}

.navigation-lien:hover {
  background: var(--fond-discret);
  color: var(--texte-page);
}

.navigation-lien[aria-current='page'] {
  background: var(--fond-accent);
  color: var(--texte-accent);
  font-weight: var(--graisse-forte);
}

.navigation-lien[aria-current='page'] > svg {
  color: var(--encre-courant);
}

.navigation-dense .navigation-lien {
  min-height: var(--cible-dense);
}

/* ── design/composants/suivi.css ── */
/* Suivi — une case par jour, faite ou non : la grille d'une habitude, une semaine d'un coup d'œil.
 *
 *   <button class="case" type="button" aria-pressed="true" aria-label="Lundi 28, fait"
 *           style="--teinte: var(--serie-3)"></button>
 *   <button class="case" type="button" aria-pressed="false" aria-current="date" aria-label="Aujourd'hui, à faire"></button>
 *   <button class="case case-ronde" type="button" aria-pressed="false" aria-label="Marquer fait"></button>
 *
 * Un bouton à bascule (`aria-pressed`) ; aujourd'hui se dit `aria-current="date"`. La teinte vient
 * de l'usage (`--teinte`, la couleur de l'habitude — la marque par défaut), et le survol en montre
 * un quart : c'est un pseudo-élément en opacité, parce qu'un thème ne fournit pas une surface par
 * teinte. Un jour qui n'est pas encore arrivé est `disabled` : une case vide en pointillé, pas une
 * case grisée qu'on prendrait pour un jour manqué. La taille se règle par `--case-taille` (2rem) ; la mise en grille reste à l'écran.
 *   .case-ronde : LA case du jour, à la taille du doigt, avec un point au centre */
.case {
  --_teinte: var(--teinte, var(--fond-marque));

  position: relative;
  flex: none;
  width: var(--case-taille, 2rem);
  height: var(--case-taille, 2rem);
  padding: 0;
  overflow: hidden;
  border: 0;
  border-radius: var(--rayon-petit);
  background: var(--fond-discret);
  cursor: pointer;
}

.case::before {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--_teinte);
  opacity: 0;
  transition: opacity var(--duree-courte);
}

/* Au survol seulement là où il existe : au doigt, `:hover` colle à la dernière case touchée,
   et une case qu'on vient de décocher garderait un quart de teinte. */
@media (hover: hover) {
  .case:hover:not(:disabled, [aria-pressed='true'])::before {
    opacity: 0.25;
  }
}

.case[aria-pressed='true']::before {
  opacity: 1;
}

/* Aujourd'hui : un anneau intérieur à la teinte de l'habitude — la case à remplir se voit. */
.case[aria-current='date'] {
  box-shadow: inset 0 0 0 2px var(--_teinte);
}

.case:disabled {
  border: 1px dashed var(--bordure-forte);
  background: none;
  cursor: not-allowed;
}

.case-ronde {
  width: var(--cible-tactile);
  height: var(--cible-tactile);
  border-radius: var(--rayon-plein);
  background: none;
  box-shadow: inset 0 0 0 1px var(--bordure-champ);
}

.case-ronde::after {
  content: '';
  position: absolute;
  inset: 0;
  width: 0.75rem;
  height: 0.75rem;
  margin: auto;
  border-radius: var(--rayon-plein);
  background: var(--bordure-champ);
}

/* Le point est clair dans tous les thèmes, comme le curseur de l'interrupteur : il se lit sur la
   teinte, quelle qu'elle soit. */
.case-ronde[aria-pressed='true']::after {
  background: var(--texte-marque);
}

/* ── design/composants/legende.css ── */
/* Légende — les séries d'un graphique, et le bouton qui en masque une.
 *
 *   <ul class="legende" aria-label="Séries">
 *     <li><button class="legende-entree" type="button" aria-pressed="true" style="--legende-teinte: var(--serie-2)">Entrée</button></li>
 *     <li><button class="legende-entree" type="button" aria-pressed="false" style="--legende-teinte: var(--serie-3)">Sortie</button></li>
 *     <li><span class="legende-entree legende-precedent">Période précédente</span></li>
 *   </ul>
 *
 * La pastille prend la teinte de sa série (`--legende-teinte`, la même que `--trace-teinte`).
 * Une entrée qui se clique est un bouton à bascule : `aria-pressed="true"` = série affichée,
 * `"false"` = masquée (pastille vide, libellé barré) — ce qu'on voit est ce qu'annonce le
 * lecteur d'écran. Une légende qu'on ne clique pas est faite de <span>.
 *   .legende-precedent : un trait en pointillé au lieu d'une pastille, comme `.graphique-precedent` */
.legende {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--espace-3);
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--taille-petite);
}

.legende-entree {
  display: inline-flex;
  align-items: center;
  gap: var(--espace-2);
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
}

.legende-entree::before {
  content: '';
  flex: none;
  width: 0.625rem;
  height: 0.625rem;
  border: 2px solid var(--legende-teinte, var(--serie-2));
  border-radius: var(--rayon-plein);
  background: var(--legende-teinte, var(--serie-2));
}

button.legende-entree {
  min-height: var(--cible-tactile);
  cursor: pointer;
}

.legende-entree[aria-pressed='false'] {
  color: var(--encre-discrete);
  text-decoration: line-through;
}

.legende-entree[aria-pressed='false']::before {
  background: transparent;
}

.legende-precedent::before {
  width: 1rem;
  height: 0;
  border-width: 2px 0 0;
  border-style: dashed;
  border-color: var(--encre-discrete);
  border-radius: 0;
  background: none;
}

/* ── design/composants/infobulle.css ── */
/* Infobulle — ce que vaut un point d'un graphique, sous le curseur ou le doigt.
 *
 *   <div class="infobulle" role="tooltip" style="left: 120px; top: 24px">
 *     <p class="infobulle-titre">Mardi 30 sept.</p>
 *     <p class="infobulle-ligne" style="--infobulle-teinte: var(--serie-2)">
 *       <span>Entrée</span><span class="infobulle-nombre">182 k</span>
 *     </p>
 *     <p class="infobulle-ligne infobulle-precedent"><span>Semaine d'avant</span><span class="infobulle-nombre">140 k</span></p>
 *   </div>
 *
 * Sur la surface élevée, une ligne par série avec sa pastille (`--infobulle-teinte`), les
 * nombres alignés à droite en chiffres tabulaires. L'app la POSE (`left`, `top`, dans le
 * `.graphique`, qui est son repère) et la garde dans le cadre ; le kit ne calcule rien. Elle
 * ne prend pas le pointeur : elle ne vole pas le survol du graphique qu'elle commente. */
.infobulle {
  position: absolute;
  z-index: 1;
  min-width: 9rem;
  max-width: min(18rem, calc(100vw - 2 * var(--espace-3)));
  padding: var(--espace-2) var(--espace-3);
  border: 1px solid var(--bordure-forte);
  border-radius: var(--rayon);
  background: var(--fond-eleve);
  color: var(--texte-eleve);
  box-shadow: var(--ombre-portee);
  font-size: var(--taille-petite);
  line-height: var(--interligne-titre);
  pointer-events: none;
}

.infobulle-titre {
  margin: 0 0 var(--espace-1);
  font-weight: var(--graisse-forte);
}

.infobulle-ligne {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--espace-2);
  margin: 0;
}

.infobulle-ligne::before {
  content: '';
  width: 0.5rem;
  height: 0.5rem;
  border-radius: var(--rayon-plein);
  background: var(--infobulle-teinte, var(--serie-2));
}

.infobulle-precedent::before {
  height: 0;
  border-top: 2px dashed var(--encre-discrete);
  border-radius: 0;
  background: none;
}

.infobulle-nombre {
  font-family: var(--police-chiffres);
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
}

/* ── design/composants/chaleur.css ── */
/* Carte de chaleur — une valeur par jour et par heure : quand on travaille.
 *
 *   <div class="chaleur" role="img" aria-label="Sessions par jour et par heure, pic le mardi à 10 h">
 *     <span></span>
 *     <span class="chaleur-heure">0</span><span class="chaleur-heure"></span>…(24, l'app n'écrit que 0, 6, 12, 18)
 *     <span class="chaleur-jour">lun.</span>
 *     <span class="chaleur-cellule" style="--intensite: 0.42" data-valeur="12 sessions"></span>
 *     <span class="chaleur-cellule" style="--intensite: 0"></span>          zéro : une case grise
 *     <span class="chaleur-cellule" data-vide></span>                       pas de donnée : vide
 *     …(7 lignes de 1 + 24)
 *   </div>
 *   <p class="chaleur-echelle"><span>moins</span><i></i><i></i><i></i><i></i><i></i><span>plus</span></p>
 *
 * Une seule teinte, `--chaleur` (définie par le thème, contrastée à 3:1 sur la carte et sur la
 * case vide), en quatre paliers d'opacité : `--intensite` de 0 à 1 est arrondie au quart
 * supérieur — une valeur non nulle ne se confond jamais avec zéro. Zéro est une case grise ;
 * « pas de donnée » (`data-vide` : une heure à venir, avant le début de l'historique) est un
 * pointillé. La grille fait 1 + 24 colonnes, les cases restent carrées et tiennent dans 390 px.
 * Ce que vaut une case se lit dans une `.infobulle` ; le résumé, dans l'`aria-label`. */
.chaleur {
  display: grid;
  grid-template-columns: auto repeat(24, minmax(0, 1fr));
  gap: 2px;
  align-items: center;
  font-family: var(--police-chiffres);
  font-size: var(--taille-mention);
  font-variant-numeric: tabular-nums;
  color: var(--encre-discrete);
}

.chaleur-jour {
  padding-right: var(--espace-2);
  white-space: nowrap;
}

.chaleur-heure {
  overflow: visible;
  white-space: nowrap;
}

.chaleur-cellule,
.chaleur-echelle i {
  position: relative;
  aspect-ratio: 1;
  border-radius: 2px;
  background: var(--fond-discret);
}

.chaleur-cellule::after,
.chaleur-echelle i::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: var(--chaleur);
  opacity: var(--intensite, 0);
}

/* Quatre paliers : 0, puis 0.25 à 1 au quart supérieur. Sans round(), le dégradé est continu —
   et la règle doit être sous @supports : avec un var(), une déclaration inconnue reste valide à
   l'analyse et tomberait à 1 au calcul, toutes les cases en pleine teinte. */
@supports (opacity: round(up, 0.5, 0.25)) {
  .chaleur-cellule::after,
  .chaleur-echelle i::after {
    opacity: round(up, var(--intensite, 0), 0.25);
  }
}

.chaleur-cellule[data-vide] {
  background: transparent;
  outline: 1px dashed var(--bordure-forte);
  outline-offset: -1px;
}

.chaleur-cellule[data-vide]::after {
  content: none;
}

.chaleur-cellule:hover {
  outline: 1px solid var(--texte-carte);
  outline-offset: 0;
}

.chaleur-echelle {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 2px;
  margin: var(--espace-2) 0 0;
  font-size: var(--taille-mention);
  color: var(--encre-discrete);
}

.chaleur-echelle i {
  width: 0.75rem;
}

.chaleur-echelle i:nth-of-type(2) { --intensite: 0.25; }
.chaleur-echelle i:nth-of-type(3) { --intensite: 0.5; }
.chaleur-echelle i:nth-of-type(4) { --intensite: 0.75; }
.chaleur-echelle i:nth-of-type(5) { --intensite: 1; }

.chaleur-echelle span:first-child {
  margin-right: var(--espace-1);
}

.chaleur-echelle span:last-child {
  margin-left: var(--espace-1);
}

/* ── design/composants/part.css ── */
/* Part — la part d'un total, en jauge fine à côté de son nombre : dans une cellule de tableau,
 * au bout d'une ligne.
 *
 *   <td><span class="part" style="--valeur: 42"><span class="part-nombre">42 %</span></span></td>
 *   <span class="part" style="--valeur: 8; --part-teinte: var(--serie-2)"><span class="part-nombre">1,2 M</span></span>
 *
 * `--valeur` de 0 à 100 : la longueur de la barre, pas forcément le nombre écrit (la part d'un
 * top peut se mesurer au premier du top). Le nombre a une largeur fixe (`--part-nombre`, 5ch par
 * défaut), aligné à droite en chiffres tabulaires : dans une colonne, toutes les barres partent
 * du même bord et finissent au même nombre. Teinte par défaut : l'encre discrète — la barre
 * accompagne le nombre, elle ne crie pas ; `--part-teinte` pour la couleur d'une donnée. */
.part {
  display: grid;
  grid-template-columns: minmax(2rem, 1fr) var(--part-nombre, 5ch);
  align-items: center;
  gap: var(--espace-2);
  width: 100%;
  min-width: 6rem;
}

.part::before {
  content: '';
  height: 0.375rem;
  border-radius: var(--rayon-plein);
  background-color: var(--bordure);
  background-image: linear-gradient(var(--part-teinte, var(--encre-discrete)), var(--part-teinte, var(--encre-discrete)));
  background-repeat: no-repeat;
  background-size: calc(var(--valeur, 0) * 1%) 100%;
}

.part-nombre {
  font-family: var(--police-chiffres);
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
}

/* ── design/composants/variation.css ── */
/* Variation — ce qui a bougé depuis la période d'avant : « ▲ 12 % », « ▼ 22 % », « = ».
 *
 *   <span class="tuile-detail"><span class="variation" data-sens="bon"><span aria-hidden="true">▼</span> 22 %<span class="visuellement-cache"> de moins que la semaine d'avant</span></span></span>
 *   <span class="variation" data-sens="mauvais"><span aria-hidden="true">▲</span> 12 %</span>
 *   <span class="variation" data-sens="neutre">=</span>
 *
 * La flèche dit le SENS du chiffre, la couleur dit si c'est une bonne nouvelle — et ce n'est
 * pas la même chose : une baisse de tokens est bonne. D'où `data-sens="bon|mauvais|neutre"`,
 * posé par l'app qui sait ce que mesure le chiffre ; jamais « baisse = rouge ». Le bon et le
 * mauvais sont les encres de succès et d'erreur, pas la marque : le rouge de marque est une
 * action. Chiffres tabulaires ; sur une `.tuile-forte`, la variation prend le texte de la tuile. */
.variation {
  display: inline-flex;
  align-items: baseline;
  gap: 0.25em;
  font-family: var(--police-chiffres);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: var(--encre-discrete);
}

.variation[data-sens='bon'] {
  color: var(--encre-succes);
}

.variation[data-sens='mauvais'] {
  color: var(--encre-erreur);
}

.tuile-forte .variation {
  color: inherit;
}

/* ── design/composants/filtres.css ── */
/* Barre de filtres — la plage et les dimensions d'une vue, collée sous la barre du haut.
 *
 *   <section class="filtres" aria-label="Filtres">
 *     <div class="filtres-rangee">
 *       <div class="puces" role="radiogroup" aria-label="Plage">…</div>
 *       <span class="filtres-separateur" aria-hidden="true"></span>
 *       <details class="menu" data-actif>…</details>
 *       <details class="menu">…</details>
 *     </div>
 *     <div class="filtres-pied">
 *       <p class="filtres-fenetre">1 sept. → 30 sept. · 214 sessions</p>
 *       <button class="bouton bouton-discret bouton-petit" type="button">Tout effacer</button>
 *     </div>
 *   </section>
 *
 * Collante (`--filtres-haut` : la hauteur de la barre au-dessus, 0 par défaut), sur le fond de
 * la page, sous la couche des barres. La rangée tient sur UNE ligne et défile horizontalement
 * sur un téléphone — jusqu'à ce qu'un menu s'ouvre : un défilement couperait sa liste, alors la
 * rangée passe à la ligne le temps qu'il est ouvert (`:has()` ; sans lui, la rangée passe
 * toujours à la ligne plutôt que de couper un menu). Le pied porte la fenêtre résolue (les
 * dates réelles derrière « 30 jours ») et « Tout effacer ». */
.filtres {
  position: sticky;
  top: var(--filtres-haut, 0);
  z-index: calc(var(--couche-barre) - 1);
  display: flex;
  flex-direction: column;
  gap: var(--espace-1);
  padding: var(--espace-2) var(--espace-3);
  border-bottom: 1px solid var(--bordure);
  background: var(--fond-page);
  color: var(--texte-page);
}

.filtres-rangee {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--espace-2);
  /* Le défilement ne doit pas couper l'anneau de focus des contrôles du bord. */
  padding: calc(var(--focus-epaisseur) + var(--focus-ecart));
  margin: calc(-1 * (var(--focus-epaisseur) + var(--focus-ecart)));
}

@supports selector(:has(*)) {
  .filtres-rangee {
    flex-wrap: nowrap;
    overflow-x: auto;
    /* Fine mais visible : c'est le seul signe qu'il reste des filtres à droite. */
    scrollbar-width: thin;
  }

  .filtres-rangee:has(details[open]) {
    flex-wrap: wrap;
    overflow-x: visible;
  }
}

.filtres-rangee > * {
  flex: none;
}

.filtres-rangee .puces {
  flex-wrap: nowrap;
}

.filtres-separateur {
  align-self: stretch;
  width: 1px;
  margin: var(--espace-1) 0;
  background: var(--bordure-forte);
}

.filtres-pied {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0 var(--espace-2);
  min-height: var(--cible-dense);
}

.filtres-fenetre {
  margin: 0;
  font-size: var(--taille-petite);
  font-variant-numeric: tabular-nums;
  color: var(--encre-discrete);
}

/* ── design/composants/estime.css ── */
/* Valeur estimée — un chiffre qu'on a calculé au lieu de le lire : préfixé de « ~ ».
 *
 *   <span class="tuile-valeur" data-estime>1,2 M</span>
 *   <td class="nombre" data-estime>48 300</td>
 *   <p class="chiffre chiffre-grand" data-estime>3,4<span class="chiffre-unite">€</span></p>
 *
 * Un attribut, pas une classe : c'est un état de la donnée, qui se pose sur `.chiffre`,
 * `.tuile-valeur` ou `.nombre` sans changer leur forme. Le « ~ » est discret et annoncé
 * « environ » (texte alternatif du `content`). Dans une colonne `.nombre`, alignée à droite, il
 * s'ajoute à gauche et les chiffres restent alignés ; dans une tuile, il déborde dans la marge
 * pour que le chiffre commence au même bord que celui de la tuile voisine. */
:is(.chiffre, .tuile-valeur, .nombre)[data-estime]::before {
  content: '~';
  content: '~' / 'environ ';
  margin-right: 0.1em;
  font-weight: var(--graisse-normale);
  color: var(--encre-discrete);
}

.tuile-valeur[data-estime]::before {
  margin-left: -1.1ch;
}

.tuile-forte .tuile-valeur[data-estime]::before {
  color: var(--texte-marque-discret);
}

/* ── design/composants/copiable.css ── */
/* Bloc copiable — ce qu'on colle ailleurs : une règle de CLAUDE.md, un réglage JSON, un script.
 *
 *   <div class="copiable" data-copie="ok">
 *     <div class="copiable-entete">
 *       <span class="copiable-cible">~/.claude/settings.json</span>
 *       <button class="bouton bouton-discret bouton-petit copiable-bouton" type="button">Copié</button>
 *     </div>
 *     <pre class="bloc-code" tabindex="0"><code>…</code></pre>
 *   </div>
 *
 * Un `.bloc-code` coiffé d'un en-tête : la cible à gauche (où ça va, tronquée si elle est
 * longue), le bouton Copier à droite — une cible de 44 px même en `.bouton-petit`, il se tape
 * au doigt. Le bloc a une hauteur bornée (`--copiable-max`, 16rem par défaut) et défile dans
 * les deux sens : une ligne longue ne s'enroule pas, elle se colle telle quelle. Le <pre> porte
 * `tabindex="0"` : sans lui, Safari et Firefox ne le font pas défiler au clavier.
 *
 * `data-copie="ok"` / `"echec"` sur le conteneur : le bouton passe au vert ou au rouge
 * d'erreur. Le texte (« Copié », « Échec ») et son annonce (`role="status"`) restent à l'app,
 * qui retire l'attribut après 2 s. */
.copiable {
  border: 1px solid var(--bordure);
  border-radius: var(--rayon);
  background: var(--fond-discret);
  color: var(--texte-discret);
}

.copiable-entete {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--espace-2);
  padding-left: var(--espace-3);
  border-bottom: 1px solid var(--bordure);
}

.copiable-cible {
  min-width: 0;
  overflow: hidden;
  font-family: var(--police-mono);
  font-size: var(--taille-mention);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.copiable-bouton {
  flex: none;
  min-width: var(--cible-tactile);
  min-height: var(--cible-tactile);
}

.copiable[data-copie='ok'] .copiable-bouton {
  color: var(--encre-succes);
}

.copiable[data-copie='echec'] .copiable-bouton {
  color: var(--encre-erreur);
}

.copiable .bloc-code {
  max-height: var(--copiable-max, 16rem);
  overflow: auto;
  border-radius: 0 0 var(--rayon) var(--rayon);
  white-space: pre;
}

/* ── design/composants/heros.css ── */
/* Héros — la première section d'un site : une promesse en très grand, une phrase qui la
 * précise, l'action et l'adresse. Texte seul, aligné à gauche : pas de visuel à droite, pas
 * de centrage (la page d'accueil générique, c'est le héros centré sur une illustration).
 *
 *   <section class="heros">
 *     <div class="cadre cadre-large heros-cadre">
 *       <p class="heros-surtitre">Transformation IA · 50 à 200 personnes</p>
 *       <h1 class="heros-titre">L'IA dans vos équipes, pour de vrai.</h1>
 *       <p class="heros-texte">Nous accompagnons…</p>
 *       <div class="heros-actions">
 *         <a class="bouton" href="mailto:…">Réserver un diagnostic de 30 min</a>
 *         <span class="heros-adresse">contact@exemple.fr</span>
 *       </div>
 *     </div>
 *   </section>
 *
 * Le titre est fluide (`clamp`) : de `--taille-hero` au téléphone à plus du double au bureau,
 * sans palier. Au bureau (48rem et plus), la section occupe 80 % de la hauteur d'écran, pas
 * plus : le haut de la section suivante doit se voir. Le sur-titre est facultatif.
 *
 * L'adresse en clair (`.heros-adresse`, et `.bande-adresse` dans une bande d'appel) est là
 * parce qu'un `mailto:` sans client mail configuré ne fait rien : elle se sélectionne d'un
 * clic (`user-select: all`) pour être copiée. Le sélecteur est groupé ici, première des deux
 * feuilles dans l'ordre d'assemblage. */
.heros {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-content: center;
  padding-block: var(--espace-6);
  background: var(--fond-page);
  color: var(--texte-page);
}

.heros-cadre {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  justify-items: start;
  gap: var(--espace-4);
}

.heros-surtitre {
  margin: 0;
  font-family: var(--police-chiffres);
  font-size: var(--taille-mention);
  letter-spacing: var(--interlettrage-mention);
  text-transform: var(--casse-mention);
  color: var(--encre-discrete);
}

.heros-titre {
  max-width: 16ch;
  margin: 0;
  font-family: var(--police-titre);
  font-size: clamp(var(--taille-hero), 1rem + 5vw, 5rem);
  font-weight: var(--graisse-titre);
  line-height: 1.05;
  letter-spacing: var(--interlettrage-titre);
  text-transform: var(--casse-titre);
  text-wrap: balance;
}

.heros-texte {
  max-width: 38rem;
  margin: 0;
  font-size: var(--taille-intertitre);
  line-height: var(--interligne-texte);
  color: var(--encre-discrete);
}

.heros-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--espace-3) var(--espace-4);
  margin-top: var(--espace-2);
}

.heros-adresse,
.bande-adresse {
  font-family: var(--police-mono);
  font-size: var(--taille-petite);
  color: var(--texte-page);
  user-select: all;
  overflow-wrap: anywhere;
}

@media (min-width: 48rem) {
  .heros {
    min-height: 80svh;
    padding-block: var(--espace-7);
  }
}

/* ── design/composants/bande.css ── */
/* Bande — une section pleine largeur qui coupe la page : des chiffres qui prouvent, ou un
 * dernier appel à l'action. Elle porte son fond (`--fond-page`) : dans `.sur-inverse`, elle
 * devient la bande claire d'une page sombre (ou l'inverse) sans variante.
 *
 *   <section class="bande bande-chiffres">
 *     <div class="cadre cadre-large">
 *       <ul class="bande-liste">
 *         <li><p class="chiffre chiffre-grand">1 000</p><p class="chiffre-legende">personnes formées</p></li>
 *         …
 *       </ul>
 *     </div>
 *   </section>
 *
 *   <section class="bande bande-cta sur-inverse">
 *     <div class="cadre cadre-large bande-cadre">
 *       <h2 class="bande-titre">Parlons de vos équipes.</h2>
 *       <div class="bande-actions">
 *         <a class="bouton" href="mailto:…">Réserver un diagnostic</a>
 *         <span class="bande-adresse">contact@exemple.fr</span>
 *       </div>
 *     </div>
 *   </section>
 *
 *   .bande-chiffres : 3 ou 4 `.chiffre` côte à côte, filets haut et bas ; empilés sur un téléphone
 *   .bande-cta      : un titre et l'action, côte à côte au bureau (48rem et plus)
 * `.bande-adresse` : l'adresse en clair à côté du bouton (règle dans heros.css). */
.bande {
  padding-block: var(--espace-6);
  background: var(--fond-page);
  color: var(--texte-page);
}

.bande-chiffres {
  padding-block: var(--espace-5);
  border-block: 1px solid var(--bordure-forte);
}

.bande-liste {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr));
  gap: var(--espace-5) var(--espace-4);
  margin: 0;
  padding: 0;
  list-style: none;
}

.bande-liste > li {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-content: start;
  gap: var(--espace-2);
}

.bande-cadre {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--espace-4);
}

.bande-titre {
  max-width: 20ch;
  margin: 0;
  font-family: var(--police-titre);
  font-size: clamp(var(--taille-titre), 1rem + 2.5vw, 3rem);
  font-weight: var(--graisse-titre);
  line-height: var(--interligne-titre);
  letter-spacing: var(--interlettrage-titre);
  text-transform: var(--casse-titre);
  text-wrap: balance;
}

.bande-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--espace-3) var(--espace-4);
}

@media (min-width: 48rem) {
  .bande {
    padding-block: var(--espace-7);
  }

  .bande-chiffres {
    padding-block: var(--espace-6);
  }
}

/* ── design/composants/offre.css ── */
/* Offre — ce qu'on vend, en RANGÉES numérotées pleine largeur, séparées par un filet : pas une
 * grille de cartes (la page générique). Le numéro donne l'ordre de lecture, la colonne de
 * droite dit ce qu'on a en main à la fin.
 *
 *   <ol class="offres">
 *     <li class="offre">
 *       <span class="offre-numero">01</span>
 *       <div class="offre-corps">
 *         <h3 class="offre-titre">Formations</h3>
 *         <p class="offre-texte">Des parcours par niveau, sur vos cas d'usage.</p>
 *       </div>
 *       <div class="offre-livrables">
 *         <p class="offre-livrables-titre">Vous repartez avec</p>
 *         <ul><li>des équipes autonomes</li><li>…</li></ul>
 *       </div>
 *     </li>
 *   </ol>
 *
 * Trois colonnes au bureau (48rem et plus) — numéro, corps, livrables —, une seule en dessous.
 * Le numéro est en chasse fixe et discret : il range, il ne s'achète pas (la marque est
 * l'action). Dans `.sur-inverse`, le filet et les encres suivent. */
.offres {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  margin: 0;
  padding: 0;
  list-style: none;
}

.offre {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--espace-3);
  padding-block: var(--espace-5);
}

.offre + .offre {
  border-top: 1px solid var(--bordure-forte);
}

.offre-numero {
  font-family: var(--police-chiffres);
  font-size: var(--taille-titre);
  font-variant-numeric: tabular-nums;
  line-height: var(--interligne-titre);
  color: var(--encre-discrete);
}

.offre-corps {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-content: start;
  gap: var(--espace-2);
}

.offre-titre {
  margin: 0;
  font-family: var(--police-titre);
  font-size: var(--taille-titre);
  font-weight: var(--graisse-titre);
  line-height: var(--interligne-titre);
  letter-spacing: var(--interlettrage-titre);
  text-transform: var(--casse-titre);
}

.offre-texte {
  max-width: 36rem;
  margin: 0;
  line-height: var(--interligne-texte);
}

.offre-livrables {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-content: start;
  gap: var(--espace-2);
}

.offre-livrables-titre {
  margin: 0;
  font-size: var(--taille-mention);
  letter-spacing: var(--interlettrage-mention);
  text-transform: var(--casse-mention);
  color: var(--encre-discrete);
}

.offre-livrables ul {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--espace-1);
  margin: 0;
  padding: 0;
  list-style: none;
}

.offre-livrables li {
  position: relative;
  padding-left: var(--espace-4);
  line-height: var(--interligne-texte);
}

.offre-livrables li::before {
  content: "—";
  position: absolute;
  left: 0;
  color: var(--encre-discrete);
}

@media (min-width: 48rem) {
  .offre {
    grid-template-columns: 5rem minmax(0, 3fr) minmax(0, 2fr);
    gap: var(--espace-5);
    padding-block: var(--espace-6);
  }
}

/* ── design/composants/cas-client.css ── */
/* Cas client — une mission racontée en RANGÉE : le récit à gauche, ses chiffres empilés à
 * droite, la citation dessous sur toute la largeur. Deux cas se séparent par un filet, jamais
 * par une carte.
 *
 *   <article class="cas-client">
 *     <div class="cas-client-recit">
 *       <span class="etiquette">Expertise comptable · 35 personnes</span>
 *       <h3 class="cas-client-titre">Notion comme ERP</h3>
 *       <p>La situation…</p>
 *       <p>Ce que nous avons fait…</p>
 *     </div>
 *     <ul class="cas-client-chiffres">
 *       <li><p class="chiffre chiffre-grand">+100 %</p><p class="chiffre-legende">de productivité sur l'administratif</p></li>
 *       …
 *     </ul>
 *     <blockquote class="cas-client-citation"><p>« On a enfin un seul endroit… »</p></blockquote>
 *   </article>
 *
 * Au bureau (48rem et plus) : deux colonnes, récit 2/3, chiffres 1/3. En dessous : les chiffres
 * passent sous le récit. La citation n'est pas attribuée par le composant : une `<footer>` dans
 * le `blockquote` la signe si l'app le veut. */
.cas-client {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--espace-4);
  padding-block: var(--espace-5);
}

.cas-client + .cas-client {
  border-top: 1px solid var(--bordure-forte);
}

.cas-client-recit {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  justify-items: start;
  gap: var(--espace-3);
}

.cas-client-recit > p {
  max-width: 40rem;
  margin: 0;
  line-height: var(--interligne-texte);
}

.cas-client-titre {
  margin: 0;
  font-family: var(--police-titre);
  font-size: var(--taille-titre);
  font-weight: var(--graisse-titre);
  line-height: var(--interligne-titre);
  letter-spacing: var(--interlettrage-titre);
  text-transform: var(--casse-titre);
}

.cas-client-chiffres {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-content: start;
  gap: var(--espace-4);
  margin: 0;
  padding: 0;
  list-style: none;
}

.cas-client-chiffres > li {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--espace-1);
}

.cas-client-citation {
  margin: 0;
  padding-left: var(--espace-4);
  border-left: 2px solid var(--bordure-forte);
  font-family: var(--police-titre);
  font-size: var(--taille-intertitre);
  line-height: var(--interligne-texte);
}

.cas-client-citation p {
  margin: 0;
}

.cas-client-citation footer {
  margin-top: var(--espace-2);
  font-family: var(--police);
  font-size: var(--taille-petite);
  color: var(--encre-discrete);
}

@media (min-width: 48rem) {
  .cas-client {
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
    gap: var(--espace-5) var(--espace-6);
    padding-block: var(--espace-6);
  }

  .cas-client-citation {
    grid-column: 1 / -1;
  }
}

/* ── design/composants/capture.css ── */
/* Capture — une capture d'écran d'un produit, dans un cadre de fenêtre discret (bord, ombre,
 * barre à trois points) et sa légende. Ce n'est pas une illustration : la barre dit « ceci est
 * l'écran réel ».
 *
 *   <figure class="capture capture-grande">
 *     <div class="capture-fenetre">
 *       <picture>
 *         <source srcset="images/vue.webp" type="image/webp">
 *         <img src="images/vue.png" alt="La vue d'ensemble : …" width="1440" height="900" loading="lazy">
 *       </picture>
 *     </div>
 *     <figcaption class="capture-legende">Vue d'ensemble : la semaine en un écran.</figcaption>
 *   </figure>
 *
 * `width` et `height` sur l'`<img>` : le navigateur réserve la place avant le chargement, la
 * page ne saute pas. Une image absente laisse le cadre et le texte `alt`, pas un trou.
 *   .capture-grande : la capture principale d'une section — barre plus haute, ombre portée,
 *   légende au corps du texte. Les petites se rangent dans une `.grille`. */
.capture {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--espace-2);
  margin: 0;
}

.capture-fenetre {
  --_barre: 1.5rem;
  --_point: 3px;

  position: relative;
  overflow: hidden;
  padding-top: var(--_barre);
  border: 1px solid var(--bordure-forte);
  border-radius: var(--rayon);
  background: var(--fond-eleve);
  box-shadow: var(--ombre-legere);
}

/* La barre de fenêtre : trois points (un point et ses deux ombres), un filet dessous. */
.capture-fenetre::before {
  content: "";
  position: absolute;
  top: calc(var(--_barre) / 2 - var(--_point));
  left: calc(var(--_barre) / 2);
  width: calc(var(--_point) * 2);
  height: calc(var(--_point) * 2);
  border-radius: var(--rayon-plein);
  background: var(--bordure-champ);
  box-shadow: calc(var(--_point) * 4) 0 0 var(--bordure-champ), calc(var(--_point) * 8) 0 0 var(--bordure-champ);
}

.capture-fenetre img {
  display: block;
  width: 100%;
  height: auto;
  border-top: 1px solid var(--bordure);
}

.capture-legende {
  font-size: var(--taille-petite);
  line-height: var(--interligne-texte);
  color: var(--encre-discrete);
}

.capture-grande .capture-fenetre {
  --_barre: 2rem;
  --_point: 4px;

  border-radius: var(--rayon-grand);
  box-shadow: var(--ombre-portee);
}

.capture-grande .capture-legende {
  font-size: var(--taille-base);
}
