/* ============================================================================
   ALESIA · COUCHE COMPOSANTS
   ----------------------------------------------------------------------------
   Consolidation de ui-kit.html + va_control_plane.html.
   Dépend de tokens.css — à charger APRÈS lui, jamais seul.

       <link rel="stylesheet" href="design/tokens.css">
       <link rel="stylesheet" href="design/alesia.css">
       <script src="design/alesia.js" defer></script>

   Les blocs marqués [FIX] corrigent un défaut présent dans les maquettes
   d'origine ; le détail et la reproduction sont dans design/FIXES.md.
   ========================================================================== */

/* ==========================================================================
   0 · RESET & BASE
   ========================================================================== */
*{box-sizing:border-box}
html,body{margin:0;padding:0}
html{scrollbar-gutter:stable;scroll-behavior:smooth}

body{
  background:var(--bg);
  color:var(--tx);
  font-family:var(--sans);
  font-size:var(--fs-body);
  line-height:var(--lh);
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  /* Signature du produit : deux halos froids décentrés + trame de points. */
  background-image:var(--page-halo-1),var(--page-halo-2),var(--page-dots);
  background-size:auto,auto,var(--page-dots-size) var(--page-dots-size);
  background-attachment:fixed;
}

a{color:inherit;text-decoration:none}
::selection{background:var(--accent-dim);color:var(--sel-tx)}
:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:var(--r-xs)}

::-webkit-scrollbar{width:10px;height:10px}
::-webkit-scrollbar-thumb{background:var(--scroll-thumb);border-radius:var(--r);border:2px solid var(--bg)}
::-webkit-scrollbar-thumb:hover{background:var(--scroll-thumb-h)}
::-webkit-scrollbar-track{background:transparent}

/* Toute donnée numérique comparable passe par .mono → chiffres tabulaires. */
.mono{font-family:var(--mono);font-feature-settings:"tnum" 1}
.muted{color:var(--tx3)}
.up{color:var(--ok)}
.down{color:var(--err)}

/* ==========================================================================
   1 · CHÂSSIS — sidebar 244 / topbar 56 / contenu
   ========================================================================== */
/* La barre est EN FLUX : `flex` et non `grid`, pour que sa largeur pousse le
   contenu au lieu de le recouvrir, et que la transition de largeur soit sûre. */
.app{display:flex;min-height:100vh}
main{flex:1;min-width:0}
.wrap{padding:var(--pad-page);max-width:var(--w-max);margin:0 auto}

/* Séquence de démarrage — trois temps décalés, jamais plus. */
@keyframes boot{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
@keyframes fade{from{opacity:0;transform:translateY(5px)}to{opacity:1;transform:none}}
.side{animation:boot var(--t-enter) var(--ease) both}
.top {animation:boot var(--t-enter) var(--ease) .08s both}
.wrap{animation:boot .55s var(--ease) .16s both}

/* ============================================================================
   ATELIER DE GÉNÉRATION — barre ancrée, galerie, pastilles
   Ajouté le 1er août 2026. Reprend la disposition de la maquette d'origine et
   la rejoue sur les tokens d'ici : rien n'est en valeur brute, tout suit les
   surfaces, les rayons et les rythmes du système.

   Le parti pris tient en une phrase : la barre de saisie est FIXE en bas, la
   production défile derrière elle. On écrit toujours au même endroit, et ce
   qu'on vient de produire monte dans le champ de vision — c'est la boucle d'un
   atelier, pas celle d'un formulaire qu'on remplit puis qu'on soumet.
   ========================================================================= */

/* `.push` n'existait que dans trois conteneurs. On lui ajoute ceux d'ici, plutôt
   que de poser une marge à la main dans chaque vue — c'est exactement le genre
   de valeur brute que la charte proscrit. */
.studio-head .push,.tools .push,.chips .push,.dd-item .push{margin-left:auto}

/* La barre d'outils de la galerie : filtres et compteur, au-dessus du flot.
   C'est une SURFACE du système, pas une rangée posée sur le fond : même rayon,
   même bandeau teinté et même halo que les cartes de lot qui la suivent. Une
   ligne de contrôles gris en tête d'écran n'annonce rien ; teintée, elle donne
   à la page sa couleur d'entrée.

   L'écart entre contrôles y est plus large qu'ailleurs (`--sp-5` et non
   `--sp-3`) : ce sont six groupes de sens différent — deux segmentés, trois
   filtres, un compteur — et non les six pastilles d'un même réglage. Serrés,
   ils se lisent comme une seule rangée indistincte. `flex-wrap` reste : au-delà
   d'un certain rétrécissement, mieux vaut une seconde ligne qu'un débordement. */
.studio-head{display:flex;align-items:center;gap:var(--sp-5);flex-wrap:wrap;
  padding:var(--sp-4);margin-bottom:var(--sp-4);
  background:var(--panel);border:var(--bw) solid var(--cy-line);
  border-radius:var(--r)}

/* Les filtres portent la teinte de la barre sur leur icône seule. `.t-c`
   complet dessinerait une tuile — ici on n'en veut que la couleur. */
.studio-head .dd > .chip > .ico{color:var(--tile-c)}

/* Le rembourrage bas n'est pas décoratif : sans lui, la barre ancrée masque les
   dernières images, et rien n'indique qu'il en reste. */
.studio-gal{padding-bottom:var(--h-dock)}

/* Un lot est une `.card` du système, pas un conteneur propre au module : même
   en-tête en dégradé, même tuile d'icône, même relief que partout ailleurs.
   Il ne lui manque que le rythme entre deux cartes successives. */
.studio-gal > .card + .card{margin-top:var(--sp-4)}
.studio-gal > .kpi-row{margin-bottom:var(--sp-5)}

/* Grille de variantes. Les vignettes d'un même lot partagent leur format, donc
   une grille ne laisse ici aucun trou — contrairement au mur, où les formats se
   mélangent. */
.gal{display:grid;gap:var(--sp-3);grid-template-columns:repeat(auto-fill,minmax(150px,1fr))}

.shot{position:relative;border-radius:var(--r-sm);overflow:hidden;
  border:var(--bw) solid var(--hair);background:var(--panel2);
  aspect-ratio:var(--ar,1);cursor:zoom-in}
.shot img{width:100%;height:100%;object-fit:cover;display:block;
  transition:transform var(--t-panel) var(--ease)}
.shot:hover img{transform:scale(1.04)}
.shot > .tools{position:absolute;top:var(--sp-2);right:var(--sp-2);
  display:flex;gap:var(--sp-1);opacity:0;transition:opacity var(--t-ui) var(--ease)}
.shot:hover > .tools,.shot:focus-within > .tools{opacity:1}
.shot > .fav{position:absolute;top:var(--sp-2);left:var(--sp-2);
  color:var(--warn);font-size:var(--fs-sm);pointer-events:none}

/* Ratios déclarés en classes plutôt qu'en style sur l'élément : une vue ne porte
   aucune valeur brute, c'est la première loi de la charte. */
.ar-9-16{--ar:9/16} .ar-4-5{--ar:4/5} .ar-1-1{--ar:1}
.ar-16-9{--ar:16/9} .ar-3-2{--ar:3/2} .ar-2-3{--ar:2/3}

/* ---- Le mur : images jointives ----
   Une GRILLE de cellules carrées, et non `columns`.

   [FIX] `columns:210px` ne remplissait pas la largeur. Le multi-colonnes
   ÉQUILIBRE par défaut : il cherche la hauteur totale la plus courte, pas la
   largeur la mieux occupée. Avec six images insécables (`break-inside:avoid`)
   dans un cadre qui en accepte cinq de front, il concluait que trois colonnes
   de deux étaient plus courtes — et laissait 40 % du mur vide. Rien ne le
   signalait : le CSS était valide et faisait exactement ce qu'on lui demandait.

   Une grille `auto-fill` n'équilibre rien : elle pose autant de colonnes que la
   largeur en contient, et remplit dans l'ordre. Les cellules sont carrées et
   recadrent — c'est le prix pour qu'aucune rangée ne laisse de trou quand les
   formats se mélangent. Le cadrage complet reste à un clic, dans la
   visionneuse. `gap:0` : c'est tout l'objet de la vue, voir la production comme
   une matière et non comme un tableau. */
.mur{display:grid;gap:0;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));
  margin-top:var(--sp-4)}
/* `width:100%` explicite, et non l'étirement par défaut d'un élément de grille :
   `aspect-ratio` posé sur une cellule dont les DEUX axes sont automatiques fait
   retomber le navigateur sur la taille intrinsèque de l'image. La cellule se
   dessinait alors plus étroite que sa piste et s'y centrait — la grille
   occupait bien toute la largeur, les images non. */
.mur > .shot{width:100%;margin:0}
/* `nu` : ni bordure, ni arrondi, ni fond — les images se touchent réellement. */
.shot.nu{border:0;border-radius:0;background:transparent;aspect-ratio:1;cursor:zoom-in}
.shot.nu img{width:100%;height:100%;object-fit:cover}

/* ---- File d'attente ----
   Ce qui est en cours de production, au-dessus de ce qui est produit. Une série
   de dix générations prend plusieurs minutes : sans cette liste, l'écran reste
   muet et on ne sait pas si quelque chose avance, ni ce qui a échoué. */
.file{display:flex;flex-direction:column;gap:var(--sp-2);margin-bottom:var(--sp-4)}
.file-item{display:flex;align-items:center;gap:var(--sp-3);
  padding:var(--sp-3) var(--sp-4);border:var(--bw) solid var(--hair);
  border-radius:var(--r-sm);background:var(--panel);font-size:var(--fs-sm)}
.file-item > .txt{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.file-item > .meta{color:var(--tx3);font-family:var(--mono);
  font-size:var(--fs-meta);white-space:nowrap}

/* ---- Visionneuse ----
   En mosaïque, le clic est le SEUL moyen de lire un prompt en entier : le survol
   le tronque. Le prompt est donc sélectionnable — on vient souvent y recopier
   celui qui a marché. */
.viewer{position:fixed;inset:0;z-index:var(--z-toast);background:var(--backdrop);
  backdrop-filter:blur(var(--backdrop-blur));-webkit-backdrop-filter:blur(var(--backdrop-blur));
  display:flex;align-items:center;justify-content:center;padding:var(--sp-7);cursor:zoom-out}
.viewer[hidden]{display:none}
.viewer img{max-width:100%;max-height:100%;border-radius:var(--r);cursor:default}
.viewer > .vprompt{position:fixed;bottom:78px;left:50%;transform:translateX(-50%);
  max-width:min(880px,calc(100vw - 64px));max-height:22vh;overflow-y:auto;
  cursor:text;user-select:text;background:var(--panel);
  border:var(--bw) solid var(--line);border-radius:var(--r-sm);
  padding:var(--sp-3) var(--sp-4);font-size:var(--fs-sm);line-height:1.55;color:var(--tx)}
.viewer > .vbar{position:fixed;bottom:var(--sp-5);left:50%;transform:translateX(-50%);
  display:flex;align-items:center;gap:var(--sp-3);cursor:default;
  background:var(--panel);border:var(--bw) solid var(--line);
  border-radius:var(--r-sm);padding:var(--sp-2) var(--sp-3)}

/* Le prompt d'une image du mur, au survol. Rien ne doit couper le mur tant qu'on
   ne demande rien : c'est tout l'intérêt de cette vue. */
.shot > .mos-prompt{position:absolute;inset:auto 0 0 0;padding:var(--sp-3);
  font-size:var(--fs-note);line-height:1.4;color:var(--tx);
  background:var(--panel);border-top:var(--bw) solid var(--hair);
  opacity:0;transition:opacity var(--t-ui) var(--ease);
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.shot:hover > .mos-prompt{opacity:1}

/* ---- La barre ancrée ----
   `.ancrage` est un emplacement VIDE, posé dans `main` à côté de `.wrap` et non
   dedans. Ce n'est pas une préférence de rangement : `.wrap` porte une animation
   d'entrée en `both`, donc un `transform` — fût-il l'identité — qui subsiste une
   fois l'animation finie. Un ancêtre transformé devient le bloc conteneur de
   tout `position:fixed` qu'il abrite : la barre se serait ancrée au bas de la
   VUE au lieu du bas de l'écran, et elle aurait défilé avec le contenu.

   Le symptôme est déroutant — une barre « fixe » qui bouge — et la cause est
   invisible dans la règle fautive. D'où cet emplacement dédié. */
   Cet emplacement accueille aussi les MODALES d'une vue, pour la même raison
   poussée d'un cran : `.wrap` transformée n'est pas seulement un bloc conteneur,
   c'est un contexte d'empilement. Le voile, lui, est ajouté au `body`. Une
   modale restée dans `.wrap` passait donc SOUS le voile quel que soit son
   z-index — on voyait l'écran flouté par-dessus la modale. */
.ancrage[hidden]{display:none}

.dock{position:fixed;bottom:var(--sp-4);z-index:var(--z-topbar);
  left:calc(var(--w-rail) + var(--bw) + var(--pad-page-x));right:var(--pad-page-x);
  transition:left var(--t-panel) var(--ease-panel)}
/* La barre suit le dépliement de la navigation. `position:fixed` l'exclut du
   flux, donc rien ne le ferait tout seul : elle passerait sous la barre de
   gauche déployée. Le sélecteur de frère atteint `main` depuis `.side`. */
.side.open ~ main .dock{left:calc(var(--w-side) + var(--bw) + var(--pad-page-x))}
.dock-box{background:var(--panel);border:var(--bw) solid var(--line);
  border-radius:var(--r);padding:var(--sp-4);box-shadow:var(--shadow-2)}
.dock-box:focus-within{border-color:var(--accent)}
.dock-row{display:flex;align-items:flex-end;gap:var(--sp-4)}
.dock-row > .saisie{flex:1;min-width:0}
/* Le champ n'a ni cadre ni fond : c'est la barre entière qui fait le contenant.
   Un input dessiné à l'intérieur d'un panneau dessiné, ce sont deux cadres pour
   une seule idée. */
.dock-row textarea{width:100%;border:0;background:transparent;resize:none;
  padding:var(--sp-2);font-family:var(--sans);font-size:var(--fs-body);
  line-height:1.55;color:var(--tx);min-height:52px;max-height:200px}
.dock-row textarea:focus{outline:none;box-shadow:none}
/* Le compte sur le bouton : le nombre de variantes se lit avant de cliquer, et
   quatre images se facturent.

   Le fond est un VOILE de la lumière du bouton, pas une surface du thème : le
   bouton primaire porte le même bleu dans les deux thèmes, donc `--sunken` y
   posait un pavé noir en sombre et blanc en clair — un corps étranger, et
   d'une couleur différente selon l'heure. La couleur du texte est héritée. */
.dock-box .cnt{font-family:var(--mono);font-size:var(--fs-meta);
  font-weight:var(--fw-semibold);background:var(--btn-p-cnt);color:inherit;
  border-radius:var(--r-xs);padding:1px var(--sp-2);margin-left:var(--sp-1)}

/* Zone de dépôt : la barre ENTIÈRE est la cible, pas seulement le champ. Viser
   une zone d'une ligne avec un fichier au curseur est pénible ; la barre est une
   cible large et évidente. L'overlay ne capte pas les événements — sinon il
   volerait le `drop` à la barre qu'il recouvre. */
.dock-box.depot{border-color:var(--accent)}
.dock-box > .depot-voile{position:absolute;inset:0;display:grid;place-items:center;
  border-radius:var(--r);background:var(--backdrop);pointer-events:none;
  font-size:var(--fs-sm);color:var(--tx);z-index:var(--z-float)}
.dock-box{position:relative}

/* TOUT composant qui déclare son `display` doit déclarer son état caché.

   La règle du navigateur pour `[hidden]` est `display:none` à spécificité de
   type ; une règle de classe la bat systématiquement. Un élément marqué caché
   reste alors visible, sans erreur, sans avertissement — on cherche ailleurs.

   Le cas s'est présenté trois fois de suite ici : `.toggle`, `.fld`, `.refs`.
   La liste est donc traitée d'un bloc, et tout composant ajouté plus tard qui
   pose un `display` doit s'y inscrire.

   `.btn` Y MANQUAIT, et c'était un trou du système entier : tout bouton marqué
   caché restait affiché. Vu sur le dépôt de fichier, dont la croix de retrait
   n'apparaît que lorsqu'un fichier est choisi — elle était là en permanence, et
   cliquer dessus ne faisait rien puisqu'il n'y avait rien à retirer. */
.fld[hidden],.refs[hidden],.file[hidden],.opts[hidden],.chips[hidden],
.btn[hidden],.fichier[hidden]{display:none}

/* ---- Rythme vertical d'un corps de modale ----
   Un compositeur enchaîne trois sections, six champs et une rangée de réglages.
   Sans rythme déclaré, chaque élément se colle au précédent et l'ensemble se lit
   comme un bloc unique : on ne voit plus où commence une section.

   Le titre de section respire davantage AU-DESSUS qu'en dessous — il appartient
   à ce qui suit, pas à ce qui précède. C'est ce qui fait la coupure. */
.modal-body.stack > * + *{margin-top:var(--sp-5)}
.modal-body.stack > .eyebrow{margin-top:var(--sp-7)}
.modal-body.stack > .eyebrow:first-child{margin-top:0}
.modal-body.stack > .eyebrow + *{margin-top:var(--sp-3)}
/* Une note commente le champ juste au-dessus : elle lui reste attachée. */
.modal-body.stack > .note{margin-top:var(--sp-2)}

/* ---- Choix de mode : trois boutons DÉTACHÉS ----
   `.opts` et non `.seg`. Le segment accolé convient à un choix binaire —
   Tout/Favoris — où les deux termes s'opposent. Ici, chaque bouton change TOUT
   le contenu en dessous : ce sont trois modes, pas deux faces d'une bascule, et
   l'aplat plein d'un segment ne le disait pas assez fort. */
.opts{display:flex;gap:var(--sp-2);flex-wrap:wrap}
.opts button{height:var(--h-ctrl);padding:0 var(--sp-4);border-radius:var(--r-sm);
  border:var(--bw) solid var(--hair);background:var(--sunken);color:var(--tx2);
  font-family:var(--sans);font-size:var(--fs-sm);font-weight:var(--fw-medium);cursor:pointer;
  transition:border-color var(--t-ui) var(--ease),color var(--t-ui) var(--ease)}
.opts button:hover{color:var(--tx);border-color:var(--accent)}
.opts button.on{color:var(--tx);border-color:var(--accent);background:var(--elev);
  box-shadow:inset 0 0 14px -6px var(--accent-glow)}

/* ---- Vignettes de référence ----
   Petites, carrées, recadrées : ce qu'on vérifie ici est « quelle image ai-je
   jointe », pas son cadrage. Le bouton de retrait est en surimpression et
   toujours visible — une référence jointe par erreur doit se retirer sans avoir
   à deviner où survoler. */
.refs{display:flex;gap:var(--sp-2);flex-wrap:wrap;align-items:center}
.ref-thumb{position:relative;width:56px;height:56px;border-radius:var(--r-sm);
  overflow:hidden;border:var(--bw) solid var(--hair);background:var(--panel2)}
.ref-thumb img{width:100%;height:100%;object-fit:cover;display:block}
.ref-thumb > .rm{position:absolute;top:2px;right:2px;width:18px;height:18px;
  border:0;border-radius:var(--r-xs);background:var(--elev);color:var(--tx2);
  cursor:pointer;font-size:var(--fs-meta);line-height:1;display:grid;place-items:center}
.ref-thumb > .rm:hover{color:var(--tx)}

/* Choisir dans la galerie : les vignettes sont cliquables et la sélection se
   marque par un liseré intérieur — un liseré extérieur décalerait la grille. */
.gal.choix .shot{cursor:pointer}
.gal.choix .shot.pris{outline:2px solid var(--accent);outline-offset:-2px}

/* ---- Les pastilles de réglage ---- */
.chips{display:flex;align-items:center;gap:var(--sp-2);flex-wrap:wrap;margin-top:var(--sp-3)}
.chip{position:relative;display:inline-flex;align-items:center;gap:var(--sp-2);
  cursor:pointer;height:var(--h-ctrl);padding:0 var(--sp-3);
  background:var(--sunken);border:var(--bw) solid var(--hair);border-radius:var(--r-sm);
  font-family:var(--sans);font-size:var(--fs-sm);color:var(--tx2);white-space:nowrap;
  transition:border-color var(--t-ui) var(--ease),color var(--t-ui) var(--ease)}
.chip:hover{border-color:var(--accent);color:var(--tx)}
.chip > .val{color:var(--tx);font-weight:var(--fw-semibold)}
.chip > .ico{display:inline-flex;color:var(--tx3)}
.chip > .ico svg{width:var(--ico-sz);height:var(--ico-sz);
  stroke:currentColor;fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.chip.on{border-color:var(--accent);color:var(--accent)}
/* `.lumineux` — la pastille qui ouvre un OUTIL, pas un réglage.
   Halo interne plutôt qu'aplat plein : la barre porte déjà un bouton primaire,
   et un second aplat créerait deux points d'attention en concurrence. */
.chip.lumineux{color:var(--accent);border-color:var(--accent);
  background:linear-gradient(180deg,var(--accent-dim),transparent);
  box-shadow:inset 0 0 14px -2px var(--accent-glow),var(--inset-hi)}
.chip.lumineux > .ico{color:var(--accent)}
.chip.lumineux:hover{box-shadow:inset 0 0 22px -2px var(--accent-glow),var(--inset-hi)}
/* Le compteur de variantes : deux boutons et un nombre, dans la même pastille.
   Une largeur fixe sur le nombre empêche la rangée de tressauter de 1 à 10. */
.chip > .pas{display:inline-flex;align-items:center;gap:var(--sp-1)}
.chip > .pas button{border:0;background:transparent;color:var(--tx3);cursor:pointer;
  font:inherit;font-size:var(--fs-body);line-height:1;padding:0 var(--sp-2)}
.chip > .pas button:hover{color:var(--tx)}
.chip > .pas .n{font-family:var(--mono);font-weight:var(--fw-bold);color:var(--tx);
  min-width:30px;text-align:center}

/* Menu de pastille : il s'ouvre VERS LE HAUT. Dans une barre collée en bas, un
   menu qui descend sort de l'écran. */
/* Ouverture vers le haut — barre ancrée en bas d'écran. L'origine du déroulé
   passe en BAS : le panneau doit se dérouler depuis son déclencheur, qui est
   alors sous lui. Gardée en haut, l'animation partirait du bord opposé. */
.dd.haut > .dd-menu{top:auto;bottom:calc(100% + 6px);
  transform-origin:bottom center;transform:translateY(4px) scaleY(.92)}
.dd.haut.open > .dd-menu{transform:none}
.dd > .chip .caret{width:0;height:0;flex:0 0 auto;margin-left:2px;
  border-left:4px solid transparent;border-right:4px solid transparent;
  border-top:4px solid currentColor;opacity:.6;
  transition:transform var(--t-ui) var(--ease)}
.dd.open > .chip .caret{transform:rotate(180deg)}

@media (prefers-reduced-motion:reduce){
  /* Le contrat : rien ne bouge, et rien n'est vide. Les outils d'une vignette
     deviennent donc visibles d'emblée au lieu d'apparaître sans transition. */
  .shot > .tools{opacity:1;transition:none}
  .shot:hover img{transform:none}
}

/* ---------- Sidebar rétractable ----------
   Rail (64px, icônes seules) ↔ panneau (244px, icônes + libellés).
   Elle se déploie au survol après un délai d'intention, ou reste ouverte si
   elle est épinglée. Voir alesia.js → mountSidebar().

   INVARIANT : l'icône ne bouge jamais. Elle vit dans un slot de --slot-ico
   décalé de --rail-pad, donc son centre tombe au même x dans les deux états
   (8 + 48/2 = 32 = 64/2). Seul le libellé s'anime, en opacité.
   Sans ça, chaque ouverture ferait glisser toute la colonne d'icônes. */
.side{
  /* La bordure s'AJOUTE au rail, elle ne s'y prélève pas — corrigé le 1er août
     2026. Avec `box-sizing:border-box`, les 64 px incluaient le trait : la zone
     utile tombait à 63, l'item à 47, et le créneau d'icône de 48 dépassait d'un
     pixel. Le halo de l'item actif se centrait alors sur 31,5 au lieu de 32,
     l'axe que la charte donne pourtant comme invariant. */
  width:calc(var(--w-rail) + var(--bw));flex:0 0 auto;overflow:hidden;
  border-right:var(--bw) solid var(--hair);
  background:var(--side-grad);
  display:flex;flex-direction:column;
  position:sticky;top:0;height:100vh;
  transition:width var(--t-panel) var(--ease-panel);
}
.side.open{width:calc(var(--w-side) + var(--bw))}

/* Tout ce qui n'est pas l'icône disparaît en opacité, sans reflow. */
.side .lbl{white-space:nowrap;opacity:0;
  transition:opacity var(--t-panel) var(--ease-panel)}
.side.open .lbl{opacity:1}

/* Slot d'icône — la pièce maîtresse : largeur fixe, centre invariant. */
.slot{display:grid;place-items:center;width:var(--slot-ico);flex:0 0 auto;
  margin-left:var(--rail-pad)}

.brand{display:flex;align-items:center;height:var(--h-top);flex:0 0 auto;
  border-bottom:var(--bw) solid var(--hair)}
.brand .lbl{display:flex;flex-direction:column;justify-content:center;min-width:0}
.brand b{font-weight:var(--fw-semibold);font-size:var(--fs-title);letter-spacing:var(--ls-title)}
.brand span{display:block;font-size:var(--fs-caption);color:var(--tx3);letter-spacing:.3px}

.logo{width:26px;height:26px;border-radius:var(--r-sm);flex:0 0 auto;
  background:var(--logo-grad);
  border:var(--bw) solid var(--logo-line);display:grid;place-items:center;position:relative;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.08)}
.logo::before{content:"";width:9px;height:9px;border-radius:var(--r-dot);background:var(--accent);
  box-shadow:0 0 12px 1px var(--accent-glow);animation:logoGlow 4s ease-in-out infinite}
@keyframes logoGlow{
  0%,100%{box-shadow:0 0 12px 1px rgba(61,214,195,.5)}
  50%    {box-shadow:0 0 16px 2px rgba(61,214,195,.75)}
}

/* Bascule 2 positions — segmenté pleine largeur de la sidebar */
.toggle{display:flex;gap:var(--sp-1);margin:12px;padding:3px;
  background:var(--sunken);border:var(--bw) solid var(--hair);border-radius:var(--r-sm);position:relative}
/* Une bascule posée dans le bandeau du haut n'appartient qu'à une vue : elle
   se masque sur les autres. `display:flex` ci-dessus l'emporterait sur le
   `[hidden]` du navigateur — il faut donc le dire ici, comme pour `.subnav`. */
.toggle[hidden]{display:none}
/* Dans le bandeau, la bascule s'aligne sur la hauteur des autres contrôles et
   perd sa marge : c'est la rangée du bandeau qui espace, pas elle. */
.top-right .toggle{margin:0;align-self:center}
/* `white-space:nowrap` n'est pas une précaution : c'est ce qui donne à chaque
   bouton une largeur minimale égale à son libellé entier. Sans lui, un
   segmenté serré coupe « Vue A » en deux lignes, la rangée gagne 18 px et
   tout ce qui l'entoure se décale. Une bascule à deux positions reste sur
   une ligne, ou elle déborde — jamais elle ne se replie. */
.toggle button{flex:1;display:inline-flex;align-items:center;justify-content:center;
  border:0;background:transparent;color:var(--tx2);font-family:var(--sans);
  font-size:var(--fs-sm);font-weight:var(--fw-medium);padding:6px 8px;border-radius:var(--r-xs);cursor:pointer;
  white-space:nowrap;
  transition:color var(--t-ui) var(--ease),background var(--t-ui) var(--ease)}
.toggle button:hover{color:var(--tx)}
.toggle button.on{background:var(--elev);color:var(--tx);
  box-shadow:var(--inset-hi),var(--shadow-1),inset 0 0 14px -8px var(--accent-glow)}

/* Navigation */
/* `overflow:clip` et non `auto` — modifié le 1er août 2026.

   Le livrable laissait la barre défiler. Six pixels de dépassement suffisaient
   à faire apparaître l'ascenseur, qui prélève alors 10 px de largeur : les
   rangées passaient de 48 à 38 px et le halo de l'item actif se retrouvait
   décentré, 16 px de marge à gauche du tracé contre 5 à droite. Le défaut se
   lisait comme un mauvais réglage de padding, alors que c'était l'ascenseur.

   `clip` ne crée pas de conteneur de défilement : ni barre, ni scroll, ni
   largeur prélevée. Le rembourrage vertical passe de 6 à 2 px pour absorber le
   dépassement. Contrepartie assumée : au-delà d'une quinzaine d'entrées sur un
   petit écran, les dernières seraient rognées. */
nav{padding:2px 0;overflow:clip;flex:1;position:relative}

/* Le libellé de groupe s'aligne sur la colonne des ICÔNES — modifié le 1er août
   2026. Le livrable le posait sur la verticale des libellés (56 px).

   Il démarre sur `--axe-ico`, là où commence le TRACÉ de l'icône, et non sur
   `--rail-pad`, qui est le bord de son slot : le tracé est centré dans les
   48 px du slot, donc les deux verticales sont distantes de 16 px. Aligné sur
   le bord du slot, le titre paraît collé au bord de la barre.
   Il disparaît avec le rail : au format replié il n'aurait aucun sens. */
.nav-lbl{font-size:10px;text-transform:uppercase;letter-spacing:var(--ls-nav);color:var(--tx3);
  padding:var(--sp-4) var(--sp-3) 6px var(--axe-ico);
  font-weight:var(--fw-semibold);white-space:nowrap;margin-left:0;
  opacity:0;transition:opacity var(--t-panel) var(--ease-panel)}
.side.open .nav-lbl{opacity:1}

.nav-item{display:flex;align-items:center;overflow:hidden;
  margin:1px var(--rail-pad);padding:var(--sp-2) 0;
  border-radius:var(--r-sm);color:var(--tx2);font-size:var(--fs-base);cursor:pointer;
  position:relative;
  transition:background var(--t-quick) var(--ease),color var(--t-quick) var(--ease)}
.nav-item:hover{background:var(--hover-nav);color:var(--tx)}
.nav-item:hover .ico{color:var(--tx2)}
.nav-item:active{background:var(--hover-nav-on)}
.nav-item.active{background:var(--hover-nav-on);color:var(--tx);
  box-shadow:inset 0 0 16px -8px var(--accent-glow)}

/* L'icône reprend le slot invariant, décalé de la marge de l'item. */
.nav-item .ico{display:grid;place-items:center;flex:0 0 auto;
  width:var(--slot-ico);
  color:var(--tx3);transition:color var(--t-quick) var(--ease)}
.nav-item .ico svg{width:var(--ico-sz);height:var(--ico-sz);stroke:currentColor;fill:none;stroke-width:1.7;
  stroke-linecap:round;stroke-linejoin:round}
.nav-item.active .ico{color:var(--accent)}

.nav-item .lbl{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis}
.nav-item .tag{margin-left:auto;margin-right:var(--sp-3);font-size:10px;font-family:var(--mono);
  color:var(--tx3);border:var(--bw) solid var(--hair);border-radius:var(--r-xs);padding:0 5px;
  background:var(--sunken);white-space:nowrap;
  opacity:0;transition:opacity var(--t-panel) var(--ease-panel)}
.side.open .nav-item .tag{opacity:1}

/* Bouton d'épinglage — même gabarit qu'un item de nav. */
.pin{display:flex;align-items:center;overflow:hidden;width:calc(100% - 2 * var(--rail-pad));
  margin:0 var(--rail-pad) 6px;padding:var(--sp-2) 0;
  border:0;border-radius:var(--r-sm);background:none;color:var(--tx3);
  font-family:var(--sans);font-size:var(--fs-sm);cursor:pointer;text-align:left;
  transition:background var(--t-quick) var(--ease),color var(--t-quick) var(--ease)}
.pin:hover{background:var(--hover-nav);color:var(--tx)}
.pin.on{color:var(--accent)}
.pin .ico{display:grid;place-items:center;flex:0 0 auto;
  width:var(--slot-ico)}
.pin .ico svg{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.7;
  stroke-linecap:round;stroke-linejoin:round}

/* [FIX] `top:0` explicite. Sans lui l'indicateur part de sa position statique
   (après les groupes de nav) et le translateY se cumule → rail décalé. */
.nav-ind{position:absolute;top:0;left:0;width:var(--rail);height:15px;border-radius:var(--rail);
  background:var(--accent);box-shadow:0 0 8px var(--accent-glow);opacity:0;pointer-events:none;
  transition:transform var(--t-slide) var(--ease),opacity var(--t-ui) var(--ease)}
.nav-ind.on{opacity:1}

.side-foot{margin-top:auto;flex:0 0 auto;border-top:var(--bw) solid var(--hair);
  padding:var(--sp-2) 0 var(--sp-2)}
.side-status{display:flex;align-items:center;overflow:hidden;
  padding:var(--sp-2) 0;font-size:var(--fs-meta);color:var(--tx3)}
.side-status .ico{display:grid;place-items:center;flex:0 0 auto;
  width:var(--slot-ico);margin-left:var(--rail-pad)}

/* Pastille vivante — le seul élément en boucle infinie avec le logo. */
.pulse{width:7px;height:7px;border-radius:50%;background:var(--ok);
  box-shadow:0 0 0 0 var(--pulse-halo);animation:pulse 2.4s infinite}
@keyframes pulse{
  0%  {box-shadow:0 0 0 0 var(--pulse-halo)}
  70% {box-shadow:0 0 0 6px transparent}
  100%{box-shadow:0 0 0 0 transparent}
}

/* ---------- Topbar ----------
   Le padding horizontal est celui de `.wrap` : le titre de page tombe sur la
   même verticale que le contenu de la vue. Ne pas les désolidariser. */
.top{height:var(--h-top);border-bottom:var(--bw) solid var(--hair);
  display:flex;align-items:center;justify-content:space-between;padding:0 var(--pad-page-x);
  position:sticky;top:0;z-index:var(--z-topbar);
  background:var(--glass);
  backdrop-filter:blur(12px) saturate(1.2);-webkit-backdrop-filter:blur(12px) saturate(1.2)}
/* Identité de la vue : icône + titre, toujours dans cet ordre, toujours dans
   le bandeau du haut. L'icône est la MÊME que celle de l'item de nav qui mène
   à la vue — c'est ce qui relie la barre à la page. */
.top-left{display:flex;align-items:center;gap:var(--sp-3);min-width:0}
.top-ico{display:grid;place-items:center;width:26px;height:26px;flex:0 0 auto;
  border-radius:var(--r-sm);border:var(--bw) solid var(--hair);
  background:var(--panel2);color:var(--accent);box-shadow:var(--inset-hi)}
.top-ico svg{width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:1.7;
  stroke-linecap:round;stroke-linejoin:round}
.top h1{font-size:var(--fs-title);font-weight:var(--fw-bold);letter-spacing:var(--ls-caps);
  text-transform:uppercase;margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.top .crumb{font-size:var(--fs-note);color:var(--tx3);font-family:var(--mono);white-space:nowrap}
.top-right{display:flex;align-items:center;gap:var(--sp-4);font-size:var(--fs-code);color:var(--tx2)}
@media(max-width:900px){.top .crumb{display:none}}

/* ---------- Ouverture de vue ----------
   Structure imposée de toute section : le titre vit dans le bandeau du haut,
   et la vue s'ouvre — quand c'est pertinent — sur son toggle multipage, calé
   en haut à GAUCHE. `.view-head` porte ce toggle et ses actions.

   Le toggle n'est PAS systématique : une vue à page unique n'en a pas, et on
   n'en fabrique pas un pour faire symétrie. Voir la charte §6. */
.view-head{display:flex;align-items:center;gap:var(--sp-3);flex-wrap:wrap;
  min-height:var(--h-ctrl);margin:0 0 var(--sp-5)}
.view-head .push{margin-left:auto}
.chip-env{display:flex;align-items:center;gap:var(--sp-2);border:var(--bw) solid var(--hair);
  border-radius:var(--r-sm);padding:4px 9px;font-size:var(--fs-meta);font-family:var(--mono);
  color:var(--tx2);background:var(--glass-chip)}

/* ---------- Zone hors charte ----------
   L'échappatoire, et elle est délibérée.

   Quand on greffe le châssis sur une interface qui existe déjà, on ne peut pas
   tout reprendre le premier jour. Le reset de la section 0 pose sur `body` la
   police, la couleur et le fond du produit : une page étrangère déposée telle
   quelle dans une vue serait à moitié restylée — ni son ancien aspect, ni le
   nouveau. Le pire des deux.

   `.hors-charte` coupe cet héritage. Ce qui est dedans redevient du HTML nu,
   libre de porter sa propre feuille de style.

   C'est une DETTE, pas une porte de sortie. Elle se déclare, elle se date, et
   elle se referme page par page. Une zone hors charte qui dure est une zone
   dont personne n'a fixé l'échéance. */
.hors-charte{
  /* Les propriétés héritées, remises à zéro. Une par une : `all:revert` ne
     suffit pas, il rendrait la main à l'agent utilisateur dont la valeur par
     défaut est justement « hérite du parent ».

     `Canvas` / `CanvasText` sont les couleurs SYSTÈME du navigateur — le fond
     et le texte qu'aurait une page sans aucune feuille de style. C'est plus
     juste qu'un blanc écrit à la main : on ne remplace pas notre décision par
     une autre, on rend la main. */
  color:CanvasText; background:Canvas;
  font-family:initial; font-size:initial; font-weight:initial;
  line-height:initial; letter-spacing:initial; text-align:initial;
  -webkit-font-smoothing:auto;
  /* Le contenu étranger garde ses dimensions : il ne subit pas la grille. */
  display:block; isolation:isolate;
  border-radius:var(--r); overflow:hidden;
}
.hors-charte a{color:revert;text-decoration:revert}
.hors-charte ::selection{background:revert;color:revert}

/* Isolation totale — le cas « on ne touche à rien ».
   Un cadre embarqué ne partage ni CSS, ni scripts, ni sélecteurs : l'interface
   d'origine s'exécute exactement comme avant. C'est le seul moyen de le
   GARANTIR. En échange : sa hauteur ne s'ajuste pas seule, le thème ne la
   traverse pas, et le routeur ne la connaît pas. */
.frame{display:block;width:100%;border:0;border-radius:var(--r);
  background:Canvas;min-height:var(--frame-h,70vh)}
/* `.frame.plein` — une APPLICATION embarquée, pas un extrait. Elle a sa propre
   navigation, son propre défilement, ses propres marges : tout rembourrage
   qu'on ajoute autour vient s'additionner aux siens et la fait passer pour une
   vignette au milieu d'une page. Elle se pose donc de bord à bord, sous le
   bandeau et contre la barre de gauche — comme une vraie page.

   Les marges négatives annulent, dans l'ordre, le rembourrage haut du
   `.tab-panel` (--sp-5), puis les trois côtés du `.wrap` (--sp-6, --pad-page-x,
   --sp-9).

   La largeur se calcule, elle ne se déduit pas : une iframe est un élément
   REMPLACÉ, donc `width:auto` lui rend sa largeur intrinsèque de 300 px au lieu
   de suivre les marges négatives. On lui rend donc explicitement les deux
   rembourrages latéraux qu'on vient de retirer.
   Le rayon tombe à zéro — un coin arrondi contre un bord se voit.
   Ajouté le 1er août 2026. */
.frame.plein{height:var(--frame-h-plein);min-height:var(--frame-h-plein);
  width:calc(100% + 2 * var(--pad-page-x));border-radius:0;
  background:var(--frame-bg);
  margin:calc(-1 * (var(--sp-5) + var(--sp-6))) calc(-1 * var(--pad-page-x)) calc(-1 * var(--sp-9))}

/* Bandeau d'échéance — une dette qui ne se voit pas ne se rembourse pas.
   Réservé au cas où l'original n'est PAS conservé (cf. §4.6) : dès qu'il y a
   une bascule d'origine, la comparaison remplace avantageusement la date. */
.dette{display:flex;align-items:center;gap:var(--sp-2);
  font-size:var(--fs-meta);font-family:var(--mono);color:var(--warn);
  border:var(--bw) solid var(--warn-line);background:var(--warn-bg);
  border-radius:var(--r-sm);padding:var(--sp-2) var(--sp-3);margin-bottom:var(--sp-3)}

/* Marque d'origine — ce qu'on regarde n'est PAS la refonte.
   Sans elle, quelqu'un relit l'ancien écran et signale des défauts de design
   déjà corrigés à côté. Elle ne colore pas seule : elle porte un mot. */
.origine{display:inline-flex;align-items:center;gap:var(--sp-2);
  height:var(--h-badge);padding:0 var(--sp-3);border-radius:var(--r-pill);
  font-size:var(--fs-meta);font-family:var(--mono);white-space:nowrap;
  color:var(--tx3);background:var(--panel2);border:var(--bw) solid var(--hair)}
/* Le cadre d'origine se signale aussi par son pourtour, discrètement. */
.tab-panel.origine-vue .frame{outline:var(--bw) dashed var(--line);outline-offset:2px}

/* ---------- Sous-navigation ----------
   Le second niveau de navigation. La barre de gauche mène à une SECTION ;
   cette bande mène à une de ses PAGES. « IG API » est une section ; « Comptes »,
   « Planning », « Santé » en sont les pages.

   Trois choses la distinguent d'onglets (`.tabs`), et ce sont ces trois-là qui
   justifient un composant de plus :
     · elle est adressable — `#ig-api/planning` se partage et survit au rechargement,
     · elle figure au fil d'ariane, donc on sait toujours où l'on est,
     · elle vit dans le châssis, sous le bandeau, jamais dans le contenu.
   Des onglets découpent une carte ou un panneau. Celle-ci découpe l'application.

   Elle colle sous la topbar : en descendant dans un long tableau, on garde le
   moyen de changer de page sans remonter. */
.subnav{position:sticky;top:var(--h-top);z-index:var(--z-topbar);
  height:var(--h-sub);padding:0 var(--pad-page-x);
  border-bottom:var(--bw) solid var(--hair);
  background:var(--glass);
  backdrop-filter:blur(12px) saturate(1.2);-webkit-backdrop-filter:blur(12px) saturate(1.2);
  overflow-x:auto;overflow-y:hidden;scrollbar-width:none}
.subnav::-webkit-scrollbar{display:none}
.subnav[hidden]{display:none}

/* La piste porte l'indicateur : il lui faut un parent positionné qui ne défile
   pas indépendamment des items. `max-content` empêche les libellés de se
   tasser — une bande trop étroite défile, elle ne comprime pas. */
.subnav-track{position:relative;display:flex;align-items:center;gap:var(--sp-1);
  height:100%;min-width:max-content}

.subnav-item{display:inline-flex;align-items:center;justify-content:center;gap:var(--sp-2);
  height:100%;padding:0 var(--sp-4);border:0;background:transparent;cursor:pointer;
  font-family:var(--sans);font-size:var(--fs-base);font-weight:var(--fw-medium);
  color:var(--tx2);white-space:nowrap;position:relative;
  transition:color var(--t-quick) var(--ease)}
.subnav-item:hover{color:var(--tx)}
.subnav-item[aria-current="page"]{color:var(--tx);font-weight:var(--fw-semibold)}
.subnav-item svg{width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:1.7;
  stroke-linecap:round;stroke-linejoin:round}
/* Le compteur d'une page reste lisible sans devenir l'objet du regard. */
.subnav-item .tag{font-size:var(--fs-micro)}

/* Un seul trait qui glisse, plutôt qu'un soulignement qui apparaît et
   disparaît : le déplacement dit d'où l'on vient. */
.subnav-ind{position:absolute;bottom:0;left:0;height:2px;width:0;
  border-radius:var(--r-dot);background:var(--accent);opacity:0;
  transition:transform var(--t-ui) var(--ease),width var(--t-ui) var(--ease),
             opacity var(--t-quick) var(--ease)}
.subnav-ind.on{opacity:1}

/* Sous 760px la topbar n'est plus collée en haut (la barre passe en bandeau) :
   la sous-navigation se cale donc sur 0, sinon elle flotterait dans le vide. */
@media(max-width:760px){.subnav{top:0}}

/* ---------- Vues ---------- */
.section{display:none}
.section.show{display:block;animation:fade .3s var(--ease)}

/* Une page de section. Même mécanique que .section, un cran plus bas. */
.subview{display:none}
.subview.show{display:block}

/* RYTHME VERTICAL — deux blocs empilés ne se touchent jamais.

   Sans cela, l'espacement dépend de ce que chaque composant a bien voulu
   déclarer en `margin-bottom` : un `.row` n'en a aucune — c'est un outil
   d'alignement horizontal, pas un bloc de mise en page — donc ce qui le suit
   vient se coller dessous. On ne peut pas demander à chaque écran d'y penser.

   `.stack` est POSÉ, pas universel, et c'est délibéré : certaines paires
   doivent rester serrées — un titre et sa description se lisent d'un bloc, les
   écarter les sépare. Le gabarit le porte, donc tout écran qui en est issu
   l'a d'office ; un document qui règle lui-même son rythme ne le prend pas.

   Les marges adjacentes fusionnent en flux normal : un composant qui déclare
   déjà plus que `--flow` garde sa valeur au lieu de s'additionner. C'est donc
   un plancher, jamais une couche de plus. */
.stack > * + *{margin-top:var(--stack)}
/* Ce qui est déjà rythmé par sa gouttière garde la sienne. */
.stack > .grid + .grid{margin-top:var(--gap)}

/* Un bloc de prose se borne lui-même. Le conteneur, lui, prend toute la
   largeur : c'est le texte qui a une mesure de lecture, pas la page. */
.read{max-width:var(--w-read)}
.wrap{transition:opacity .18s var(--ease),transform .18s var(--ease)}
.wrap.swap{opacity:0;transform:translateY(6px)}

/* ==========================================================================
   2 · RÉVÉLATION EN CASCADE
   Chaque enfant direct d'une section entre décalé de --stagger, plafonné à 7.
   ========================================================================== */
.rv{opacity:0;transform:translateY(10px)}
.rv.in{opacity:1;transform:none;
  transition:opacity var(--t-enter) var(--ease),transform var(--t-enter) var(--ease);
  transition-delay:calc(var(--d,0) * var(--stagger))}
/* [FIX] Une fois l'entrée jouée, le delay doit tomber — sinon toute
   transition ultérieure d'opacity/transform sur ce bloc traîne de N×55ms. */
.rv.settled{transition-delay:0s}

/* ==========================================================================
   3 · TYPOGRAPHIE DE CONTENU
   ========================================================================== */
/* Eyebrow — surtitre de section, terminé par un filet qui s'évanouit. */
.eyebrow{font-size:var(--fs-caption);text-transform:uppercase;letter-spacing:var(--ls-eyebrow);
  color:var(--accent);font-weight:var(--fw-bold);margin-bottom:12px;
  display:flex;align-items:center;gap:9px}
/* Cas asymétrique de la famille des séparateurs à dégradé : le filet part du
   titre et s'éteint vers la droite. Réservé aux en-têtes de section. */
.eyebrow::after{content:"";flex:1 1 auto;height:1px;min-width:16px;
  background:linear-gradient(90deg,var(--sep-accent) 0%,
    rgba(61,214,195,.04) 60%,transparent 100%)}

/* Bandeau d'information — le seul bleu du produit.
   STATIQUE et PLEINE LARGEUR : il ne se déplie pas, ne se clique pas, ne se
   tronque pas. Son contenu est visible d'emblée ou il n'a pas lieu d'être.
   Un bandeau replié est un bandeau que personne ne lit.

   POSOLOGIE — c'est une règle, pas une suggestion :
   au plus UN par écran, et seulement quand il porte une information sans
   laquelle l'écran serait mal interprété (clé de jointure, unité de mesure,
   périmètre des données). Jamais en décoration ni en reformulation du titre.
   Un écran sans piège n'a pas de bandeau. */
.lead{display:block;width:100%;
  color:var(--lead-tx);font-size:var(--fs-sm);margin:0 0 var(--sp-6);position:relative;
  background:linear-gradient(180deg,var(--navy-bg),var(--lead-bg2));
  border:var(--bw) solid var(--navy);border-left:var(--rail) solid var(--cyan);
  border-radius:var(--r-sm);padding:var(--sp-3) var(--sp-5) var(--sp-3) 40px;
  box-shadow:var(--inset-hi),var(--shadow-1),inset 10px 0 26px -20px rgba(67,211,222,.55)}
.lead::before{content:"i";position:absolute;left:12px;top:var(--sp-3);width:18px;height:18px;
  border-radius:50%;border:var(--bw) solid var(--lead-line);color:var(--cyan);
  font-size:var(--fs-meta);font-weight:var(--fw-bold);font-family:var(--mono);
  display:grid;place-items:center;line-height:1}
.lead b{color:var(--tx);font-weight:var(--fw-semibold)}
.lead .code{background:var(--lead-code-bg);border-color:var(--navy);color:var(--cy-tx)}

/* --------------------------------------------------------------------------
   SÉPARATEURS À DÉGRADÉ
   Un filet plein s'arrête net et durcit la mise en page. Ceux-ci s'éteignent
   sur leurs extrémités : la ligne existe là où elle sépare, et disparaît là
   où elle toucherait un bord.

   Trois déclinaisons, une par géométrie — ne pas en inventer une quatrième :
     .rule-fade          horizontal, s'éteint aux DEUX bords
     .rule-fade-neutral  idem, teinte neutre pour une séparation ordinaire
     .cols > .col        vertical entre colonnes, s'éteint en haut et en bas
   Le filet de l'`.eyebrow` (qui s'éteint vers la droite) appartient à la même
   famille : c'est le cas asymétrique, réservé aux en-têtes de section.
   -------------------------------------------------------------------------- */
.rule-fade,.rule-fade-neutral{height:1px;border:0;width:100%;margin:var(--sp-4) 0}
.rule-fade{background:linear-gradient(90deg,
  transparent 0%,var(--sep-accent) 25%,var(--sep-accent) 75%,transparent 100%)}
.rule-fade-neutral{background:linear-gradient(90deg,
  transparent 0%,var(--sep-neutral) 25%,var(--sep-neutral) 75%,transparent 100%)}

.cols{display:flex;align-items:stretch}
.cols > .col{position:relative;flex:1;min-width:0;padding:var(--sp-1) var(--sp-5);
  font-size:var(--fs-base);color:var(--tx2)}
.cols > .col:first-child{padding-left:0}
.cols > .col:last-child{padding-right:0}
.cols > .col:not(:last-child)::after{content:"";position:absolute;top:0;right:0;
  width:1px;height:100%;pointer-events:none;
  background:linear-gradient(180deg,
    transparent 0%,var(--sep-accent) 25%,var(--sep-accent) 75%,transparent 100%)}
.cols > .col.neutral:not(:last-child)::after{
  background:linear-gradient(180deg,
    transparent 0%,var(--sep-neutral) 25%,var(--sep-neutral) 75%,transparent 100%)}
@media(max-width:760px){
  .cols{flex-direction:column;gap:var(--sp-4)}
  .cols > .col{padding:0}
  .cols > .col:not(:last-child)::after{display:none}
}

/* Note technique — liseré cyan sourd, toujours en fin de bloc. */
.note{font-size:var(--fs-note);color:var(--tx3);border-left:2px solid var(--cy-line);
  padding:2px 0 2px var(--sp-3);margin:var(--sp-4) 0 0}

.code{font-family:var(--mono);font-size:var(--fs-code);background:var(--sunken);
  border:var(--bw) solid var(--hair);border-radius:var(--r-xs);padding:2px 6px;color:var(--tx2)}

/* Titre de bloc — carré teal + capitales espacées. */
.head-row{display:flex;align-items:center;justify-content:space-between;
  margin-bottom:12px;gap:12px;flex-wrap:wrap}
.head-row h3{margin:0;font-size:var(--fs-sm);font-weight:var(--fw-bold);text-transform:uppercase;
  letter-spacing:var(--ls-caps);display:flex;align-items:center;gap:9px}
.head-row h3::before{content:"";width:8px;height:8px;border-radius:var(--r-dot);background:var(--accent);
  box-shadow:0 0 8px var(--accent-glow);flex:0 0 auto}

/* ==========================================================================
   4 · CARTES
   ========================================================================== */
.card{background:var(--panel);border:var(--bw) solid var(--hair);border-radius:var(--r);
  box-shadow:var(--inset-hi),var(--shadow-1);position:relative;
  transition:border-color var(--t-ui) var(--ease)}
.card:hover{border-color:var(--hover-border)}
.card.pad{padding:var(--pad-card)}
/* 🔴 UNE CARTE PEUT ÊTRE UN BOUTON, et sans ces trois reprises elle est
   illisible. C'est le cas d'un choix qu'on fait dans une grille de vignettes —
   quel iPhone, quelle créatrice.

   `.card` ne déclare AUCUNE couleur de texte : elle compte sur l'héritage
   depuis `body`. Or un `<button>` n'hérite ni de la couleur ni de la police —
   le navigateur lui impose `color:buttontext`, c'est-à-dire du NOIR, quel que
   soit le thème. Sur fond sombre, le texte disparaissait ; en thème clair il
   passait pour normal, donc le défaut ne se voyait qu'à moitié.

   ⚠️ `text-align:left` COMPTE AUTANT. Le défaut d'un bouton est `center`, ce qui
   centrait chaque ligne d'une vignette qui en porte trois. */
/* `cursor` ET `width` VIENNENT AVEC : un bouton ne prend pas la main du
   pointeur tout seul dans tous les navigateurs, et un `<button>` reste
   `inline-block` — donc plus étroit que sa piste de grille, ce qui casse
   l'alignement d'une rangée de vignettes dès que les libellés diffèrent. */
button.card{color:inherit;font:inherit;text-align:left;
  cursor:pointer;width:100%}
button.card:disabled{cursor:default;opacity:.55}

/* Halo de bordure qui suit le curseur.
   Il n'a PAS de couleur propre : il reprend celle de la carte qu'il éclaire,
   via --glow-1 / --glow-2 que chaque teinte de bandeau redéfinit plus bas.
   Un halo d'une couleur et un bandeau d'une autre casseraient la lecture
   périphérique que les quatre teintes servent précisément à donner.

   [FIX] La règle `mask` standard était absente : sans elle, Firefox ignore le
   masque et peint un disque plein par-dessus la carte au lieu du seul liseré. */
.glow{position:relative}
.glow::after{content:"";position:absolute;inset:-1px;border-radius:inherit;padding:1px;
  background:radial-gradient(240px circle at var(--mx,50%) var(--my,50%),
    var(--glow-1),var(--glow-2) 45%,transparent 70%);
  -webkit-mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);
          mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);
  -webkit-mask-composite:xor;
          mask-composite:exclude;
  opacity:0;pointer-events:none;transition:opacity var(--t-state) var(--ease)}
.glow:hover::after{opacity:1}
.tbl-wrap.glow::after{inset:0}

/* Bandeau de tête teinté — sept teintes, une par famille de contenu.

   IL COUVRE TOUTE LA CARTE DEPUIS LE 5 AOÛT 2026, et ce n'est pas un
   agrandissement : la teinte n'y descend plus du haut, elle entre par les
   QUATRE BORDS vers le centre. Le dégradé lui-même est dans les tokens
   `--hd-*`, avec le pourquoi de ses bornes.

   Le `min(130px,100%)` d'avant réglait un défaut disparu avec lui : à 130 px
   fixes, le dégradé débordait les angles arrondis du bas sur toute carte plus
   courte. À `inset:0` avec le rayon complet, il n'y a plus de bord à déborder.

   Un `.hd` sans teinte n'affiche rien — `--hd-*` n'est posé que par `.hd-g`,
   `.hd-c`… C'était déjà vrai, ça le reste. */
.hd{position:relative}
.hd::before{content:"";position:absolute;inset:0;
  border-radius:var(--r);pointer-events:none;z-index:0}
/* `position:relative` SEUL, sans z-index — et c'est le correctif d'un défaut
   qui ne se voyait qu'à l'usage.

   Un élément positionné AVEC un z-index crée un contexte d'empilement. Tout ce
   qu'il contient y est enfermé : un menu déroulant posé dans une rangée de
   carte gardait bien son `z-index:20`, mais celui-ci ne valait plus que DANS
   la rangée. À l'extérieur, la rangée entière pesait 1 — donc la carte suivante
   du même niveau, plus loin dans le DOM, se peignait par-dessus le menu. On
   voyait ses boutons traverser la liste, ce qui se lit comme un menu
   transparent alors que c'est un menu enterré.

   Le z-index n'était pas nécessaire : `.hd::before` est le PREMIER enfant, et
   un frère positionné qui le suit se peint au-dessus de lui sans qu'on ait à
   le demander. On garde donc l'empilement local et on rend au menu la portée
   qu'il croyait avoir. */
.hd > *{position:relative}
.kpi.hd::before{height:100%;border-radius:var(--r);opacity:.85}
.hd-g::before{background:var(--hd-g)}
.hd-c::before{background:var(--hd-c)}
.hd-o::before{background:var(--hd-o)}
.hd-v::before{background:var(--hd-v)}
.hd-b::before{background:var(--hd-b)}
.hd-m::before{background:var(--hd-m)}
.hd-r::before{background:var(--hd-r)}

/* Chaque teinte redéfinit le halo de survol : la bordure s'illumine de la
   couleur de la carte. Poser la teinte suffit — rien à câbler en plus. */
.hd-g{--glow-1:rgba(95,221,157,.55); --glow-2:rgba(53,210,154,.25)}
.hd-c{--glow-1:rgba(67,211,222,.55); --glow-2:rgba(61,214,195,.25)}
.hd-o{--glow-1:rgba(230,167,92,.55); --glow-2:rgba(230,167,92,.22)}
.hd-v{--glow-1:rgba(124,116,255,.6); --glow-2:rgba(124,116,255,.24)}
.hd-b{--glow-1:rgba(var(--blue-rgb),.55); --glow-2:rgba(var(--blue-rgb),.25)}
.hd-m{--glow-1:rgba(var(--mag-rgb),.58);  --glow-2:rgba(var(--mag-rgb),.24)}
.hd-r{--glow-1:rgba(var(--red-rgb),.58);  --glow-2:rgba(var(--red-rgb),.24)}

/* Cartes sans bandeau mais à teinte explicite — même mécanique. */
.tone-g{--glow-1:rgba(95,221,157,.55); --glow-2:rgba(53,210,154,.25)}
.tone-c{--glow-1:rgba(67,211,222,.55); --glow-2:rgba(61,214,195,.25)}
.tone-o{--glow-1:rgba(230,167,92,.55); --glow-2:rgba(230,167,92,.22)}
.tone-v{--glow-1:rgba(124,116,255,.6); --glow-2:rgba(124,116,255,.24)}
.tone-b{--glow-1:rgba(var(--blue-rgb),.55); --glow-2:rgba(var(--blue-rgb),.25)}
.tone-m{--glow-1:rgba(var(--mag-rgb),.58);  --glow-2:rgba(var(--mag-rgb),.24)}
.tone-r{--glow-1:rgba(var(--red-rgb),.58);  --glow-2:rgba(var(--red-rgb),.24)}

/* Tuile d'icône — même teinte que le bandeau de la carte qui la porte. */
.itile{width:32px;height:32px;border-radius:var(--r-tile);flex:0 0 auto;
  display:grid;place-items:center;border:var(--bw) solid;
  box-shadow:var(--inset-hi),0 2px 8px -4px rgba(0,0,0,.5)}
.itile svg{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.7;
  stroke-linecap:round;stroke-linejoin:round}
.t-g{color:var(--tile-g);border-color:rgba(95,221,157,.32);
  background:linear-gradient(145deg,rgba(95,221,157,.18),rgba(95,221,157,.04));
  box-shadow:var(--inset-hi),inset 0 0 12px -2px rgba(95,221,157,.28)}
.t-c{color:var(--tile-c);border-color:rgba(67,211,222,.32);
  background:linear-gradient(145deg,rgba(67,211,222,.18),rgba(67,211,222,.04));
  box-shadow:var(--inset-hi),inset 0 0 12px -2px rgba(67,211,222,.28)}
.t-o{color:var(--tile-o);border-color:rgba(230,167,92,.32);
  background:linear-gradient(145deg,rgba(230,167,92,.18),rgba(230,167,92,.04));
  box-shadow:var(--inset-hi),inset 0 0 12px -2px rgba(230,167,92,.26)}
.t-v{color:var(--tile-v);border-color:rgba(124,116,255,.34);
  background:linear-gradient(145deg,rgba(124,116,255,.20),rgba(124,116,255,.05));
  box-shadow:var(--inset-hi),inset 0 0 12px -2px rgba(124,116,255,.3)}
.t-b{color:var(--tile-b);border-color:rgba(var(--blue-rgb),.32);
  background:linear-gradient(145deg,rgba(var(--blue-rgb),.18),rgba(var(--blue-rgb),.04));
  box-shadow:var(--inset-hi),inset 0 0 12px -2px rgba(var(--blue-rgb),.28)}
.t-m{color:var(--tile-m);border-color:rgba(var(--mag-rgb),.34);
  background:linear-gradient(145deg,rgba(var(--mag-rgb),.20),rgba(var(--mag-rgb),.05));
  box-shadow:var(--inset-hi),inset 0 0 12px -2px rgba(var(--mag-rgb),.3)}
.t-r{color:var(--tile-r);border-color:rgba(var(--red-rgb),.34);
  background:linear-gradient(145deg,rgba(var(--red-rgb),.20),rgba(var(--red-rgb),.05));
  box-shadow:var(--inset-hi),inset 0 0 12px -2px rgba(var(--red-rgb),.3)}

/* En-tête de carte : tuile · (eyebrow + titre) · actions poussées à droite */
/* Sans z-index, pour la même raison que `.hd > *` : un en-tête de carte porte
   souvent un menu d'actions, et un contexte d'empilement l'enfermerait. */
.card-top{display:flex;align-items:flex-start;gap:12px;margin:0 0 var(--sp-4);
  position:relative}
.card-top .titles{display:flex;flex-direction:column;gap:3px;min-width:0;
  justify-content:center;min-height:32px}
.card-top .titles .eyebrow{margin:0}
.card-top .titles .eyebrow::after{display:none}
.card-top .titles h3{margin:0;font-size:var(--fs-base)}
.card-top .titles h3::before{display:none}
.card-top .actions{margin-left:auto;display:flex;align-items:center;gap:var(--sp-3);
  flex-wrap:wrap;justify-content:flex-end}

/* Carte à deux façades */
.split{display:grid;grid-template-columns:1fr 1fr;gap:0}
.facade{padding:var(--pad-card)}
.facade + .facade{border-left:var(--bw) solid var(--hair)}
.facade h4{margin:0 0 4px;font-size:var(--fs-base)}
.facade p{margin:0;color:var(--tx3);font-size:var(--fs-code)}

/* ==========================================================================
   5 · KPI
   ========================================================================== */
.kpi{background:var(--panel);border:var(--bw) solid var(--hair);border-radius:var(--r);
  padding:15px 16px;padding-right:48px;position:relative;
  box-shadow:var(--inset-hi),var(--shadow-1);
  transition:border-color var(--t-ui) var(--ease),transform var(--t-ui) var(--ease)}
.kpi:hover{border-color:var(--hover-border);transform:translateY(-1px)}
.kpi .k-lbl{font-size:var(--fs-meta);color:var(--tx3);letter-spacing:.02em}
.kpi .k-val{font-family:var(--mono);font-size:var(--fs-metric);font-weight:var(--fw-semibold);
  letter-spacing:var(--ls-tight);margin-top:8px;font-feature-settings:"tnum" 1}
.kpi .k-sub{font-size:var(--fs-meta);margin-top:6px;color:var(--tx2);
  display:flex;align-items:center;gap:6px}
.kpi .itile{position:absolute;top:12px;right:12px;width:27px;height:27px;border-radius:var(--r-sm)}
.kpi .itile svg{width:14px;height:14px}
.kpi.hero{display:flex;flex-direction:column;justify-content:space-between;
  box-shadow:var(--inset-hi),var(--shadow-1),inset 0 -26px 46px -36px var(--accent-glow)}
.kpi.hero .k-val{font-size:var(--fs-metric-lg)}

/* LE KPI QUI EST UN BOUTON — meme parti que `button.card`, et pour les memes
   raisons : un `<button>` n'herite ni la couleur ni la police, ne prend le
   pointeur tout seul dans aucun navigateur, et reste `inline-block` — donc plus
   etroit que sa piste de grille, ce qui casse l'alignement d'une rangee.

   Le repere chiffre porte deja `:hover` : il annonce depuis toujours qu'on peut
   le toucher. Ce bloc ne fait que tenir la promesse. */
button.kpi{color:inherit;font:inherit;text-align:left;
  cursor:pointer;width:100%;display:block}
button.kpi:disabled{cursor:default;opacity:.55}

/* ==========================================================================
   6 · TABLEAUX
   ========================================================================== */
table{width:100%;border-collapse:collapse;font-size:var(--fs-base)}
.tbl-wrap{overflow:auto;border:var(--bw) solid var(--hair);border-radius:var(--r);
  background:var(--panel);box-shadow:var(--inset-hi),var(--shadow-1);position:relative;
  /* [FIX] L'en-tête sticky n'a d'effet que si le conteneur peut défiler
     verticalement. Poser --tbl-h sur .tbl-wrap pour l'activer :
        <div class="tbl-wrap" style="--tbl-h:420px">
     Sans hauteur, thead reste figé en tête de tableau — comportement normal. */
  max-height:var(--tbl-h,none)}
thead th{text-align:left;font-size:var(--fs-caption);text-transform:uppercase;
  letter-spacing:var(--ls-label);color:var(--tx3);font-weight:var(--fw-semibold);
  padding:var(--pad-cell);border-bottom:var(--bw) solid var(--hair);white-space:nowrap;
  background:var(--thead-grad);
  position:sticky;top:0;z-index:var(--z-sticky)}
tbody td{padding:var(--pad-cell);border-bottom:var(--bw) solid var(--hair);
  color:var(--tx);white-space:nowrap}
tbody tr:last-child td{border-bottom:0}
tbody tr{background:var(--panel);
  transition:background var(--t-fast) var(--ease),opacity .18s var(--ease)}
tbody tr:nth-child(even){background:var(--zebra)}
tbody tr:hover{background:var(--hover-cell)}

/* --------------------------------------------------------------------------
   LA LIGNE-CIBLE — `tr[data-open]`, pendant en tableau de `.card[data-open]`.

   Une ligne de tableau qui ouvre quelque chose se clique EN ENTIER. Réserver le
   geste au dernier bouton oblige à viser 80 px au bout d'une ligne large de
   1200 : on parcourt la ligne de gauche à droite, et il faut ensuite revenir la
   pointer là où on ne regardait pas.

   🔴 AUCUN SÉLECTEUR À RETENIR : c'est l'attribut qui porte le style. Une ligne
   qui reçoit `data-open` devient cliquable et le montre, sans qu'on puisse
   oublier de lui ajouter une classe — ce qui aurait donné des lignes qui
   s'ouvrent sans le dire, le pire des deux mondes.

   ⚠️ CONTRAIREMENT À `.card[data-open]`, UN BOUTON DEDANS EST PERMIS. Un
   `<button>` dans un `<tr>` est du HTML valide, et son clic REMONTE jusqu'à la
   ligne : le bouton reste donc l'affordance visible et le point d'entrée au
   clavier, sans câblage propre. La condition est qu'il ne porte NI `data-open`
   NI `hx-get` — sinon les deux se déclenchent et la requête part en double.

   Le survol monte d'un cran par rapport à une ligne ordinaire : `--hover-cell`
   dit « c'est cette ligne que tu lis », `--hover-nav-on` dit « et elle
   s'ouvre ». Elle reste distincte d'une ligne SÉLECTIONNÉE, qui porte en plus
   le trait d'accent à gauche. */
tbody tr[data-open]{cursor:pointer}
tbody tr[data-open]:hover{background:var(--hover-nav-on)}

td .sub{color:var(--tx3);font-size:var(--fs-note)}
.num{font-family:var(--mono);text-align:right;font-feature-settings:"tnum" 1}
tr.hidden-row{display:none}

/* Première colonne figée — à activer par .tbl-wrap.freeze sur les tables larges */
.freeze th:first-child,.freeze td:first-child{position:sticky;left:0;background:inherit;
  z-index:var(--z-base)}
.freeze thead th:first-child{z-index:var(--z-float);background:var(--thead-solid)}

/* Tri au clic */
thead th.sortable{cursor:pointer;user-select:none;transition:color var(--t-fast) var(--ease)}
thead th.sortable:hover{color:var(--tx)}
thead th.sortable::after{content:"⇅";margin-left:6px;opacity:.35;font-size:9px}
thead th.s-asc::after {content:"▲";opacity:.9;color:var(--accent)}
thead th.s-desc::after{content:"▼";opacity:.9;color:var(--accent)}

/* ==========================================================================
   7 · BADGES · PASTILLES · PILLS
   Règle : la couleur seule ne porte jamais l'information. Un badge = pastille
   + mot. Une pastille nue n'est admise qu'avec un title/aria-label.
   ========================================================================== */
/* Hauteur fixe : un badge posé dans une `.row` s'aligne au centre des boutons. */
.badge{display:inline-flex;align-items:center;gap:6px;height:var(--h-badge);
  font-size:var(--fs-meta);white-space:nowrap;
  font-weight:var(--fw-medium);padding:0 8px;border-radius:var(--r-pill);
  border:var(--bw) solid var(--hair);color:var(--tx2);
  transition:background var(--t-state) var(--ease),color var(--t-state) var(--ease),
             border-color var(--t-state) var(--ease)}
.b-ok  {color:var(--ok-tx);  border-color:var(--ok-line);  background:var(--ok-bg);
  box-shadow:inset 0 0 10px -3px rgba(53,210,154,.35)}
.b-warn{color:var(--warn-tx);border-color:var(--warn-line);background:var(--warn-bg);
  box-shadow:inset 0 0 10px -3px rgba(230,167,92,.3)}
.b-err {color:var(--err-tx); border-color:var(--err-line); background:var(--err-bg);
  box-shadow:inset 0 0 10px -3px rgba(236,106,94,.3)}
.b-acc {color:var(--acc-tx); border-color:var(--acc-line); background:var(--acc-bg);
  box-shadow:inset 0 0 10px -3px rgba(124,116,255,.35)}
/* EXTERNE — ce qui ne vient pas du système : API tierce, service partenaire. */
.b-ext {color:var(--ext-tx); border-color:var(--ext-line); background:var(--ext-bg);
  box-shadow:inset 0 0 10px -3px rgba(var(--blue-rgb),.35)}
/* HUMAIN — ce qui exige une personne : tâche assignée, dérogation manuelle. */
.b-hum {color:var(--hum-tx); border-color:var(--hum-line); background:var(--hum-bg);
  box-shadow:inset 0 0 10px -3px rgba(var(--mag-rgb),.35)}
/* RISQUE — un état permanent, pas un événement : politique de plateforme,
   quota proche, menace de blocage. À ne pas confondre avec .b-err, qui dit
   qu'un échec A EU LIEU. Le cramoisi est plus sombre que le corail d'erreur :
   c'est cette différence de clarté qui les sépare, la teinte ne suffit pas. */
.b-risk{color:var(--risk-tx); border-color:var(--risk-line); background:var(--risk-bg);
  box-shadow:inset 0 0 10px -3px rgba(var(--red-rgb),.35)}
.b-off {color:var(--tx3)}

.dot{width:7px;height:7px;border-radius:50%;flex:0 0 auto}
.d-ok{background:var(--ok)}   .d-warn{background:var(--warn)} .d-err{background:var(--err)}
.d-off{background:var(--off)} .d-acc{background:var(--violet)} .d-info{background:var(--info)}
.d-ext{background:var(--blue)} .d-hum{background:var(--mag)} .d-risk{background:var(--red)}

.pill-list{display:flex;flex-wrap:wrap;gap:var(--sp-2)}
.pill{font-size:var(--fs-code);border:var(--bw) solid var(--hair);border-radius:var(--r-pill);
  /* ⚠️ `inline-flex` ET NON `flex`. Une pastille posée hors d'un `.pill-list`
     — donc sous un parent qui ne dispose rien — devenait un bloc et s'étirait
     d'un bord à l'autre : une barre sombre de la largeur de la carte, pour
     trois pixels de contenu. Dans un conteneur flex, la blocification du
     modèle de boîte rend les deux valeurs rigoureusement équivalentes. */
  padding:4px var(--sp-3);display:inline-flex;align-items:center;gap:var(--sp-2);
  background:var(--panel2);
  white-space:nowrap;
  transition:border-color var(--t-quick) var(--ease),opacity var(--t-state) var(--ease),
             transform var(--t-state) var(--ease)}
.pill:hover{border-color:var(--hover-border)}
.pill.leaving{opacity:0;transform:scale(.85)}
.pill .x{color:var(--tx3);cursor:pointer;border-radius:50%;line-height:1;padding:0 2px;
  transition:color var(--t-fast) var(--ease)}
.pill .x:hover{color:var(--err)}

/* ==========================================================================
   8 · CONTRÔLES

   RÈGLE D'ALIGNEMENT — la raison d'être des hauteurs fixes.
   Des contrôles de même famille posés sur une même ligne DOIVENT partager la
   même hauteur et le même axe. On l'obtient par une hauteur fixe (--h-ctrl),
   jamais par du padding : deux boutons aux libellés de longueurs différentes
   doivent occuper exactement la même boîte.
   Toute ligne de contrôles se pose dans `.row`.
   ========================================================================== */
.row{display:flex;align-items:center;gap:var(--sp-3);flex-wrap:wrap;
  min-height:var(--h-ctrl)}
.row.tight{gap:var(--sp-2)}
.row.end{justify-content:flex-end}
.row.spread{justify-content:space-between}
/* Pousse tout ce qui suit à droite, sans marge magique. */
.row .push{margin-left:auto}

/* Le contenu d'un bouton est TOUJOURS centré, horizontalement et verticalement,
   emoji compris. `inline-flex` + `center/center` + hauteur fixe : il n'y a
   aucun padding vertical à ajuster, donc rien à recaler écran par écran.

   Le texte est BLANC dans les deux thèmes, détaché du fond par une ombre
   courte. C'est ce qui le fait ressortir aussi bien sur le voile neutre que
   sur le dégradé teal, sans changer de couleur selon la variante. La face
   neutre s'assombrit en thème clair pour que ce blanc tienne (cf. --btn-face). */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--sp-2);
  height:var(--h-ctrl);padding:0 var(--sp-4);white-space:nowrap;line-height:1;
  font-family:var(--sans),var(--emoji);font-size:var(--fs-sm);font-weight:var(--fw-medium);
  color:var(--btn-tx);text-shadow:var(--btn-tx-shadow);
  border-radius:var(--r-sm);border:var(--bw) solid var(--btn-line);
  background:var(--btn-face);cursor:pointer;
  /* Relief : reflet en haut, ombre portée en bas. */
  box-shadow:var(--relief);
  transition:border-color var(--t-quick) var(--ease),background var(--t-quick) var(--ease),
             color var(--t-quick) var(--ease),box-shadow var(--t-quick) var(--ease),
             transform .1s var(--ease)}
.btn:hover{border-color:var(--hover-line);background:var(--btn-face-h);
  box-shadow:var(--relief-hover)}
/* Au clic il s'enfonce d'un pixel et l'ombre s'inverse — pas un scale. */
.btn:active{transform:translateY(1px);box-shadow:var(--relief-active)}
.btn:disabled{opacity:.5;pointer-events:none;transform:none}

/* ---------- Attente ----------
   L'action est PARTIE, la réponse n'est pas là. Sans ce retour, un formulaire
   posté paraît ignoré : on reclique, et on poste deux fois.

   Piloté par `aria-busy="true"` — l'attribut standard, annoncé par les lecteurs
   d'écran — et non par la classe d'un client HTTP particulier. Le système ne
   connaît aucun framework ; c'est l'écran qui pose l'attribut, à l'aller comme
   au retour.

   L'anneau est un ENFANT FLEX, pas une couche posée par-dessus : le bouton
   s'élargit de sa largeur au lieu de masquer son libellé. On garde donc à lire
   ce qu'on attend — ce qu'un rond seul ne dit pas. */
.btn[aria-busy="true"]{pointer-events:none}
.btn[aria-busy="true"]::before{content:"";width:13px;height:13px;flex:0 0 auto;
  border-radius:50%;border:2px solid var(--btn-tx);border-top-color:transparent;
  opacity:.9;animation:btnAttente .6s linear infinite}
/* Ghost n'a pas de face, donc pas de blanc : son anneau suit son texte. */
.btn.ghost[aria-busy="true"]::before{border-color:var(--tx2);
  border-top-color:transparent}
@keyframes btnAttente{to{transform:rotate(360deg)}}

/* Les icônes d'un bouton reçoivent le même traitement que son texte : elles
   héritent du blanc et portent la même ombre. Un glyphe non ombré à côté d'un
   texte ombré se lirait comme un corps étranger. */
.btn svg{width:14px;height:14px;flex:0 0 auto;stroke:currentColor;fill:none;stroke-width:1.7;
  stroke-linecap:round;stroke-linejoin:round;filter:var(--btn-ico-shadow)}
/* Emoji : police système dédiée (Apple sur macOS/iOS, repli ailleurs) et
   taille alignée sur celle du texte pour ne pas déformer la hauteur de ligne. */
.btn .emo{font-family:var(--emoji);font-size:1.05em;line-height:1;flex:0 0 auto;
  filter:var(--btn-ico-shadow)}

/* Primaire — pièce en relief, aucun halo. Un seul par écran : deux primaires
   côte à côte se disputent le regard et n'en désignent plus aucun.

   Le relief se lit en quatre couches (cf. tokens.css) : arête spéculaire en
   haut, assise sombre en bas, cerne interne, puis contact + ombre ambiante
   teintée par le bouton. C'est ce qui lui donne une épaisseur physique plutôt
   qu'un simple aplat coloré. */
.btn.p{
  background:
    linear-gradient(180deg,var(--btn-p-wash) 0%,rgba(255,255,255,0) 42%),
    var(--grad-btn-p);
  border-color:transparent;font-weight:var(--fw-semibold);
  /* L'assise et le pourtour montent du bord et s'éteignent (flou + spread
     négatif) : aucun trait dur ne fait le tour de la pièce. */
  box-shadow:
    inset 0 1px 0 var(--btn-p-spec),
    inset 0 -9px 12px -9px var(--btn-p-seat),
    inset 0 0 14px -8px var(--btn-p-edge),
    0 1px 1px rgba(0,0,0,.14),
    0 3px 8px -2px var(--btn-p-cast);
  transition:box-shadow var(--t-quick) var(--ease),filter var(--t-quick) var(--ease),
             transform .1s var(--ease)}
/* Au survol la pièce se soulève : l'ombre ambiante s'étale et s'éloigne. */
.btn.p:hover{filter:brightness(1.07);
  box-shadow:
    inset 0 1px 0 var(--btn-p-spec),
    inset 0 -9px 12px -9px var(--btn-p-seat),
    inset 0 0 14px -8px var(--btn-p-edge),
    0 2px 3px rgba(0,0,0,.12),
    0 6px 14px -3px var(--btn-p-cast)}
/* Au clic elle s'enfonce : le spéculaire s'éteint, l'ombre projetée se referme
   et une ombre interne diffuse prend sa place. */
.btn.p:active{transform:translateY(1px);filter:brightness(.94);
  box-shadow:
    inset 0 3px 8px -3px rgba(0,0,0,.45),
    inset 0 0 12px -6px var(--btn-p-edge),
    0 1px 1px rgba(0,0,0,.1)}

.btn.sm{height:var(--h-ctrl-sm);padding:0 12px;font-size:var(--fs-note)}

/* Ghost — pas de face, donc pas de texte blanc : il serait illisible en thème
   clair. C'est la seule variante qui suit la couleur de texte du document. */
.btn.ghost{background:transparent;border-color:transparent;box-shadow:none;
  color:var(--tx2);text-shadow:none}
.btn.ghost svg,.btn.ghost .emo{filter:none}
.btn.ghost:hover{background:var(--hover-nav);border-color:transparent;box-shadow:none;
  color:var(--tx)}
.btn.ghost:active{transform:translateY(1px);box-shadow:none}

/* Bouton carré — pour une icône seule (bascule de thème, action de barre). */
.btn.ico-only{width:var(--h-ctrl);padding:0}
.btn.ico-only.sm{width:var(--h-ctrl-sm)}

/* ---------- Menu déroulant ----------
   Un menu se pose AU-DESSUS du contenu : il doit rester lisible.
   PLANCHER D'OPACITÉ 70 % — en dessous, le texte du menu se confond avec ce
   qu'il recouvre. On travaille à 94 % + flou : le flou donne la profondeur,
   l'opacité garantit la lecture. Ne jamais descendre --dd-bg sous .7.

   Structure :
     <div class="dd">
       <button class="btn" data-dd>Libellé <span class="caret"></span></button>
       <div class="dd-menu" role="menu"> <button class="dd-item">…</button> </div>
     </div>                                                                    */
.dd{position:relative;display:inline-flex}
.dd > .btn .caret{width:0;height:0;flex:0 0 auto;margin-left:2px;
  border-left:4px solid transparent;border-right:4px solid transparent;
  border-top:4px solid currentColor;opacity:.7;
  transition:transform var(--t-ui) var(--ease)}
.dd.open > .btn .caret{transform:rotate(180deg)}

.dd-menu{position:absolute;top:calc(100% + 6px);left:0;z-index:var(--z-dd);
  min-width:var(--dd-w-min);padding:var(--sp-1);
  background:var(--dd-bg);
  backdrop-filter:blur(14px) saturate(1.3);-webkit-backdrop-filter:blur(14px) saturate(1.3);
  border:var(--bw) solid var(--line);border-radius:var(--r);
  box-shadow:var(--dd-shadow),var(--inset-hi);
  /* Le menu SE DÉROULE : il se dépose depuis son déclencheur au lieu
     d'apparaître d'un bloc. L'origine est en haut, donc le panneau s'ouvre vers
     le bas — le mouvement raconte d'où il vient. `.dd.haut` inverse l'origine
     plus bas, sinon un menu qui s'ouvre vers le haut se déroulerait à l'envers. */
  transform-origin:top center;
  opacity:0;transform:translateY(-4px) scaleY(.92);pointer-events:none;
  transition:opacity var(--t-ui) var(--ease),transform var(--t-ui) var(--ease)}
.dd.open > .dd-menu{opacity:1;transform:none;pointer-events:auto}
.dd-menu.right{left:auto;right:0}

.dd-item{display:flex;align-items:center;gap:var(--sp-2);width:100%;
  height:var(--h-dd-item);padding:0 var(--sp-3);
  border:0;background:transparent;border-radius:var(--r-xs);cursor:pointer;
  font-family:var(--sans),var(--emoji);font-size:var(--fs-sm);color:var(--tx2);
  text-align:left;white-space:nowrap;
  transition:background var(--t-fast) var(--ease),color var(--t-fast) var(--ease)}
.dd-item:hover{background:var(--hover-nav);color:var(--tx)}
.dd-item[aria-checked="true"]{color:var(--accent)}
.dd-item[aria-checked="true"]::after{content:"✓";margin-left:auto;font-size:var(--fs-meta)}
.dd-item svg{width:14px;height:14px;flex:0 0 auto;stroke:currentColor;fill:none;stroke-width:1.7;
  stroke-linecap:round;stroke-linejoin:round}
.dd-item .emo{font-family:var(--emoji);font-size:1.05em;line-height:1}
.dd-sep{height:1px;margin:var(--sp-1) 0;border:0;
  background:linear-gradient(90deg,transparent,var(--sep-neutral) 20%,
    var(--sep-neutral) 80%,transparent)}
.dd-lbl{padding:var(--sp-2) var(--sp-3) var(--sp-1);font-size:10px;
  text-transform:uppercase;letter-spacing:var(--ls-nav);color:var(--tx3);
  font-weight:var(--fw-semibold)}

.seg{display:inline-flex;align-items:center;gap:3px;height:var(--h-ctrl);
  background:var(--sunken);border:var(--bw) solid var(--hair);
  border-radius:var(--r-sm);padding:3px}
/* Texte en PLEIN et non en gris — modifié le 2 août 2026. Un segmenté n'est pas
   une liste d'options désactivées : ses deux termes sont également disponibles,
   et seul le fond dit lequel est retenu. En `--tx2`, l'option non choisie se
   lisait comme indisponible. */
.seg button{display:inline-flex;align-items:center;height:100%;
  border:0;background:transparent;color:var(--tx);font-family:var(--mono);
  font-size:var(--fs-code);padding:0 9px;border-radius:var(--r-xs);cursor:pointer;
  white-space:nowrap;
  transition:color var(--t-quick) var(--ease),background var(--t-quick) var(--ease)}
.seg button:hover{color:var(--tx)}
.seg button.on{background:var(--elev);color:var(--tx);
  box-shadow:var(--inset-hi),var(--shadow-1),inset 0 0 12px -7px var(--accent-glow)}

.sw{width:32px;height:18px;border-radius:var(--r-pill);background:var(--elev);
  border:var(--bw) solid var(--line);position:relative;cursor:pointer;flex:0 0 auto;padding:0;
  transition:background var(--t-ui) var(--ease),border-color var(--t-ui) var(--ease)}
.sw::after{content:"";position:absolute;top:2px;left:2px;width:12px;height:12px;border-radius:50%;
  background:var(--tx3);
  transition:transform var(--t-ui) var(--ease),background var(--t-ui) var(--ease)}
.sw.on{background:var(--accent-dim);border-color:rgba(61,214,195,.5);
  box-shadow:inset 0 0 10px -4px var(--accent-glow)}
.sw.on::after{transform:translateX(14px);background:var(--accent)}

/* Champ — creusé : l'inverse exact du relief du bouton. */
.tester{display:flex;align-items:center;gap:var(--sp-3);flex-wrap:wrap}
.tester input,.input{flex:1;min-width:240px;height:var(--h-ctrl);
  background:var(--field-bg);border:var(--bw) solid var(--field-line);
  border-radius:var(--r-sm);padding:0 12px;color:var(--tx);
  font-family:var(--mono);font-size:var(--fs-code);
  box-shadow:var(--sunk);
  transition:border-color var(--t-quick) var(--ease),box-shadow var(--t-quick) var(--ease)}
.tester input:hover,.input:hover{border-color:rgba(61,214,195,.22)}
.tester input:focus,.input:focus{outline:none;border-color:rgba(61,214,195,.4);
  box-shadow:var(--sunk),0 0 0 2px rgba(61,214,195,.18)}
.tester input::placeholder,.input::placeholder{color:var(--tx3)}
textarea.input{height:auto;min-height:96px;padding:10px 12px;resize:vertical;line-height:1.6}
.test-out{margin-top:12px;font-size:var(--fs-sm);display:flex;align-items:center;gap:8px;
  flex-wrap:wrap;min-height:26px}
.test-out .chip{font-family:var(--mono);font-size:var(--fs-note);border:var(--bw) solid var(--cy-line);
  background:var(--cy-bg);color:var(--cy-tx);border-radius:var(--r-sm);padding:3px 9px}

/* ==========================================================================
   9 · DATAVIZ LÉGÈRE
   Le produit ne charge aucune librairie de graphe : barres CSS + SVG inline.
   Toute barre s'anime par transform (scaleX/scaleY), jamais par width/height.
   ========================================================================== */
/* La piste est CREUSÉE, pas une dalle : même langage matériel que le champ de
   saisie. Le remplissage est un aplat à bord droit — un bout arrondi laisserait
   croire que la valeur se prolonge au-delà. */
.bar{height:6px;border-radius:var(--r-pill);background:var(--bar-track);overflow:hidden;
  box-shadow:inset 0 1px 1px rgba(0,0,0,.16)}
.bar > i{display:block;height:100%;background:var(--bar-fill);
  transform-origin:left;transform:scaleX(var(--w,1));
  transition:transform var(--t-chart) var(--ease)}
.bars-wait .bar > i{transform:scaleX(0)}
/* Seuils — on ne pose une couleur que lorsqu'elle veut dire quelque chose. */
.bar.warn > i{background:var(--bar-warn)}
.bar.err  > i{background:var(--bar-err)}
.bar.ok   > i{background:var(--bar-ok)}

.cbar{display:inline-flex;align-items:center;gap:var(--sp-3)}
.cbar .bar{width:72px}
.stockbar{display:inline-flex;align-items:center;gap:var(--sp-3)}
.stockbar .bar{width:72px;position:relative;overflow:visible}
.stockbar .bar > i{border-radius:var(--r-pill) 0 0 var(--r-pill)}
.stockbar .bar::after{content:"";position:absolute;left:25%;top:-3px;bottom:-3px;width:1px;
  background:var(--tx3);opacity:.55}

/* Un filet de la couleur du panneau sépare les segments : sans lui, quatre
   aplats contigus se lisent comme une seule tache. */
.stacked{display:flex;height:12px;border-radius:var(--r-pill);overflow:hidden;
  box-shadow:inset 0 1px 1px rgba(0,0,0,.16)}
.stacked i{display:block;height:100%;transform-origin:left;
  transition:transform .8s var(--ease)}
.stacked i + i{box-shadow:inset 1px 0 0 var(--panel)}
.bars-wait .stacked i{transform:scaleX(0)}

.days{display:flex;align-items:flex-end;gap:5px;height:110px;padding-top:6px}
.days .d{flex:1;display:flex;flex-direction:column;justify-content:flex-end;gap:5px;min-width:0}
/* L'historique est sourd, la dernière colonne seule est accentuée : c'est elle
   qu'on vient lire. Tout peindre de la même couleur oblige à compter. */
.days .d i{display:block;border-radius:var(--r-dot) var(--r-dot) 0 0;background:var(--col-dim);
  transform-origin:bottom;
  transition:transform .7s var(--ease),background var(--t-quick) var(--ease)}
.days .d.on i{background:var(--col-on)}
.days .d:hover i{background:var(--col-on)}
.days .d.on span{color:var(--tx2)}
.days .d span{font-size:9px;color:var(--tx3);font-family:var(--mono);text-align:center;
  white-space:nowrap}
.bars-wait .days .d i{transform:scaleY(0)}

.chart-legend{display:flex;gap:var(--sp-5);flex-wrap:wrap;font-size:var(--fs-meta);
  color:var(--tx3);margin-top:var(--sp-4)}
.chart-legend span{display:inline-flex;align-items:center;gap:var(--sp-2)}
.chart-legend i{width:8px;height:8px;border-radius:50%;flex:0 0 auto}
.chart-legend b{color:var(--tx);font-weight:var(--fw-medium)}

/* Sparkline.
   [FIX] `preserveAspectRatio:none` étire le viewBox 200×34 à la largeur du
   conteneur : sans non-scaling-stroke, le trait s'épaissit horizontalement et
   le point de fin devient une ellipse. */
.spark{margin-top:var(--sp-3);height:34px}
.spark svg{width:100%;height:100%;display:block;overflow:visible}
.spark svg path{vector-effect:non-scaling-stroke}

/* ==========================================================================
   10 · LISTES & FLUX
   ========================================================================== */
.feed-row{display:flex;align-items:center;gap:var(--sp-3);padding:8px 0;
  border-bottom:var(--bw) solid var(--hair);font-size:var(--fs-sm);
  transition:background var(--t-fast) var(--ease)}
.feed-row:hover{background:var(--hover-row)}
.feed-row:last-child{border-bottom:0}
.feed-row .t{margin-left:auto;color:var(--tx3);font-size:var(--fs-meta);font-family:var(--mono)}
@keyframes feedIn{from{opacity:0;transform:translateX(-8px)}to{opacity:1;transform:none}}
.feed-row.new{animation:feedIn .4s var(--ease)}

.rank-row{display:flex;align-items:center;gap:var(--sp-4);padding:9px 0;
  border-bottom:var(--bw) solid var(--hair);transition:background var(--t-fast) var(--ease)}
/* Barre étiquetée : libellé à gauche, valeur à droite, piste au milieu qui
   prend le reste. Les colonnes sont fixes pour que dix lignes s'alignent. */
.metric-row{display:grid;grid-template-columns:96px 1fr 52px;align-items:center;
  gap:var(--sp-4);padding:var(--sp-2) 0;font-size:var(--fs-sm)}
.metric-row > .lbl{color:var(--tx2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* `.val` sert aussi hors grille (barre compacte, barre à seuil) : la règle est
   donc générale, et la grille ne fait qu'ajouter l'alignement à droite. */
.val{font-family:var(--mono);font-size:var(--fs-note);color:var(--tx2);
  font-feature-settings:"tnum" 1;white-space:nowrap}
.metric-row > .val{text-align:right}
.metric-row.strong > .val{color:var(--tx)}
.rank-row:hover{background:var(--hover-row)}
.rank-row:last-child{border-bottom:0}
.rank{width:22px;text-align:center;font-family:var(--mono);color:var(--tx3);font-size:var(--fs-code)}

.kv{display:flex;justify-content:space-between;padding:9px 0;
  border-bottom:var(--bw) solid var(--hair);font-size:var(--fs-sm);gap:var(--sp-3)}
.kv:last-child{border-bottom:0}
.kv span{color:var(--tx3)}

.flow{display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:var(--fs-code)}
.flow .node{border:var(--bw) solid var(--line);border-radius:var(--r-sm);padding:var(--sp-2) 11px;
  background:var(--panel2);white-space:nowrap;box-shadow:var(--inset-hi);
  transition:border-color var(--t-quick) var(--ease)}
.flow .node:hover{border-color:var(--hover-line)}
.flow .arr{color:var(--tx3)}

/* Fondu de contenu au changement de filtre */
.fade-swap{transition:opacity var(--t-ui) var(--ease)}
.fading{opacity:0}

/* ==========================================================================
   11 · TOASTS
   Pile en bas à droite, 3 maximum, 2,8 s de vie.
   [FIX] `pointer-events:auto` à l'état visible — sinon le toast n'est jamais
   ni cliquable ni survolable, et le clic pour fermer est impossible.
   ========================================================================== */
.toasts{position:fixed;bottom:22px;right:22px;z-index:var(--z-toast);
  display:flex;flex-direction:column;gap:8px;align-items:flex-end}
.toast{display:flex;align-items:center;gap:9px;background:var(--elev);
  border:var(--bw) solid var(--line);border-radius:var(--r-sm);padding:var(--sp-3) var(--sp-4);
  font-size:var(--fs-sm);color:var(--tx);box-shadow:var(--shadow-2);cursor:pointer;
  opacity:0;transform:translateY(8px);pointer-events:none;
  transition:opacity var(--t-state) var(--ease),transform var(--t-state) var(--ease)}
.toast.on{opacity:1;transform:none;pointer-events:auto}

/* ==========================================================================
   12 · SCHÉMAS SVG
   Langage figé : zone en pointillés → box teintée → arête → label mono.
   ========================================================================== */
.svg-card{background:var(--panel);border:var(--bw) solid var(--hair);border-radius:var(--r);
  padding:var(--sp-4);box-shadow:var(--inset-hi),var(--shadow-1);position:relative}
svg .box  {fill:var(--panel2);stroke:var(--line)}
svg .box-a{fill:var(--acc-bg); stroke:var(--acc-line)}   /* violet — exception  */
svg .box-b{fill:var(--ok-bg);  stroke:var(--ok-line)}    /* vert — nominal      */
svg .box-c{fill:var(--warn-bg);stroke:var(--warn-line)}  /* orange — vigilance  */
svg .box-d{fill:var(--ext-bg);stroke:var(--ext-line)}   /* bleu — externe      */
svg .box-e{fill:var(--hum-bg);stroke:var(--hum-line)}   /* magenta — humain    */
svg .box-f{fill:var(--risk-bg);stroke:var(--risk-line)}  /* cramoisi — risque   */
svg .lbl  {fill:var(--tx);font-family:var(--sans);font-size:var(--fs-sm);
  font-weight:var(--fw-medium)}
svg .sub  {fill:var(--tx3);font-family:var(--mono);font-size:var(--fs-caption)}
svg .edge  {stroke:var(--line);stroke-width:1.4;fill:none;stroke-linecap:round}
svg .edge-a{stroke:var(--violet);stroke-width:1.6;fill:none;opacity:.9;stroke-linecap:round}
svg .etxt {fill:var(--tx3);font-family:var(--mono);font-size:10px}
svg .zone {fill:none;stroke:var(--hair);stroke-dasharray:4 4}
svg .zlbl {fill:var(--tx3);font-family:var(--sans);font-size:var(--fs-meta);
  font-weight:var(--fw-semibold);letter-spacing:.08em;text-transform:uppercase}

.legend{display:flex;gap:16px;flex-wrap:wrap;font-size:var(--fs-meta);color:var(--tx3);
  padding:var(--sp-3) 6px 2px}
.legend span{display:inline-flex;align-items:center;gap:var(--sp-2)}
.legend i{width:12px;height:12px;border-radius:var(--r-xs);border:var(--bw) solid var(--line);
  background:var(--panel2)}
.legend i.la{background:var(--acc-bg); border-color:var(--acc-line)}
.legend i.lb{background:var(--ok-bg);  border-color:var(--ok-line)}
.legend i.lc{background:var(--warn-bg);border-color:var(--warn-line)}
.legend i.ld{background:var(--ext-bg);border-color:var(--ext-line)}
.legend i.le{background:var(--hum-bg);border-color:var(--hum-line)}
.legend i.lf{background:var(--risk-bg);border-color:var(--risk-line)}

/* ==========================================================================
   15 · COMPOSANTS D'ÉCRAN
   Les quatorze briques qu'un écran de console rencontre le premier jour.
   Elles manquaient : sans elles, chaque écran les aurait improvisées, et la
   promesse de la charte — ne rien retoucher pendant le développement —
   n'aurait pas tenu deux jours.
   ========================================================================== */

/* --------------------------------------------------------------------------
   15.1 · ÉTATS — vide, chargement, erreur
   Une console passe beaucoup de temps SANS données. Ces trois états sont donc
   des composants de plein droit, pas des cas dégradés.
   Structure commune : tuile · titre · explication · action.
   L'explication dit ce qui s'est passé ET ce qu'on peut faire — « Aucun
   résultat » tout seul laisse l'utilisateur sans issue.
   -------------------------------------------------------------------------- */
.state{display:flex;flex-direction:column;align-items:center;justify-content:center;
  text-align:center;gap:var(--sp-3);padding:var(--sp-8) var(--sp-6);min-height:200px}
.state .itile{width:44px;height:44px;border-radius:var(--r);margin-bottom:var(--sp-1)}
.state .itile svg{width:20px;height:20px}
.state h4{margin:0;font-size:var(--fs-base);font-weight:var(--fw-semibold);color:var(--tx)}
.state p{margin:0;font-size:var(--fs-sm);color:var(--tx3);max-width:48ch;line-height:1.6}
.state .row{margin-top:var(--sp-2)}
.state.compact{min-height:0;padding:var(--sp-6) var(--sp-5)}

/* --------------------------------------------------------------------------
   15.2 · SQUELETTE
   On ne montre pas un rond qui tourne : on montre la FORME de ce qui arrive.
   L'écran ne saute donc pas au moment où les données se posent.
   -------------------------------------------------------------------------- */
.sk{display:block;border-radius:var(--r-xs);background:var(--sk-base);
  position:relative;overflow:hidden}
.sk::after{content:"";position:absolute;inset:0;transform:translateX(-100%);
  background:linear-gradient(90deg,transparent,var(--sk-shine),transparent);
  animation:skShine 1.5s var(--ease) infinite}
@keyframes skShine{to{transform:translateX(100%)}}
.sk.line{height:10px;margin:var(--sp-2) 0}
.sk.line.sm{height:8px}
.sk.w30{width:30%} .sk.w45{width:45%} .sk.w60{width:60%} .sk.w80{width:80%}
.sk.block{height:76px;border-radius:var(--r-sm)}
.sk.circle{width:var(--av);height:var(--av);border-radius:50%}
.sk-row{display:flex;align-items:center;gap:var(--sp-4);padding:var(--sp-3) 0;
  border-bottom:var(--bw) solid var(--hair)}
.sk-row:last-child{border-bottom:0}
.sk-row > .sk.line{flex:1;margin:0}

/* --------------------------------------------------------------------------
   15.3 · VOILE, MODALE, TIROIR
   Le voile assombrit ET floute : le flou dit que ce qui est derrière est
   inactif, l'assombrissement rend la modale lisible.
   Une modale est RÉSERVÉE à ce qui interrompt : confirmation destructive,
   formulaire court qui bloque la suite. Pour consulter sans interrompre, c'est
   un tiroir. Pour naviguer, c'est une vue.
   -------------------------------------------------------------------------- */
.backdrop{position:fixed;inset:0;z-index:var(--z-modal);background:var(--backdrop);
  backdrop-filter:blur(var(--backdrop-blur));-webkit-backdrop-filter:blur(var(--backdrop-blur));
  opacity:0;pointer-events:none;transition:opacity var(--t-state) var(--ease)}
.backdrop.on{opacity:1;pointer-events:auto}

.modal{position:fixed;inset:0;z-index:calc(var(--z-modal) + 1);
  display:grid;place-items:center;padding:var(--sp-6);pointer-events:none}
.modal-card{width:100%;max-width:var(--modal-w);max-height:calc(100vh - var(--sp-8));
  display:flex;flex-direction:column;
  background:var(--panel);border:var(--bw) solid var(--line);border-radius:var(--r);
  box-shadow:var(--shadow-2),var(--inset-hi);
  opacity:0;transform:translateY(10px) scale(.985);pointer-events:none;
  transition:opacity var(--t-state) var(--ease),transform var(--t-state) var(--ease)}
.modal.on .modal-card{opacity:1;transform:none;pointer-events:auto}
.modal.sm{--modal-w:380px}
.modal.lg{--modal-w:760px}
/* `xl` — pour une modale qui porte un ÉDITEUR et non un formulaire : un prompt,
   un gabarit, du code. À 760px, quinze lignes de texte se replient en trente et
   on perd la structure de ce qu'on écrit, qui est précisément ce qu'on vient
   régler. Bornée par la fenêtre (`min`), donc jamais plus large que l'écran. */
.modal.xl{--modal-w:min(1120px, 92vw)}

/* 🔴 UNE MODALE QUI GÈRE UNE LISTE PREND LA HAUTEUR DE L'ÉCRAN, pas celle de son
   contenu. Sans elle, une modale à deux entrées fait deux cents pixels de haut :
   on n'y voit rien, et chaque menu déroulant qu'elle abrite s'ouvre dans le vide
   — le corps porte `overflow:auto`, qui DÉCOUPE tout descendant en position
   absolue. Une hauteur ferme donne au menu la place de se déployer.

   Bornée par le haut : au-delà, le pied — donc le bouton « Fermer » — passerait
   sous le pli. */
.modal.haute .modal-card{height:min(86vh, 860px)}

.modal-head{display:flex;align-items:flex-start;gap:var(--sp-3);flex:0 0 auto;
  padding:var(--sp-5) var(--sp-5) var(--sp-4);border-bottom:var(--bw) solid var(--hair)}
.modal-head h3{margin:0;font-size:var(--fs-base);font-weight:var(--fw-semibold)}
.modal-head p{margin:4px 0 0;font-size:var(--fs-sm);color:var(--tx3);line-height:1.6}
.modal-head .x{margin-left:auto;flex:0 0 auto}
.modal-body{flex:1 1 auto;overflow-y:auto;padding:var(--sp-5);min-height:0}

/* [FIX] Un `<form>` glissé entre la carte et son corps rompait la chaîne de
   défilement. La carte est une colonne flex plafonnée en hauteur ; le corps
   défile parce qu'il en est l'enfant souple. Intercalé, le formulaire devenait
   cet enfant — sans plafond ni `overflow` — et le corps reprenait sa hauteur
   naturelle : la modale débordait sous elle-même, barre de défilement comprise.
   Le formulaire doit RELAYER la colonne, pas l'interrompre.

   `min-height:0` va avec : la taille minimale par défaut d'un élément flex est
   son contenu, ce qui suffit à faire déborder un enfant qui défile. */
.modal-card > form{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;
  margin:0}
.modal-foot{flex:0 0 auto;display:flex;align-items:center;gap:var(--sp-3);
  padding:var(--sp-4) var(--sp-5);border-top:var(--bw) solid var(--hair)}
/* L'action de confirmation est à DROITE, l'annulation à sa gauche : l'œil part
   du texte et finit sur l'action, jamais l'inverse. */
.modal-foot .push{margin-left:auto}

/* Confirmation destructive — la seule modale qui porte du rouge. */
.modal.danger .modal-head .itile{color:var(--err-tx);border-color:var(--err-line);
  background:var(--err-bg)}
.btn.danger{background:linear-gradient(180deg,#b8483d,#8e3229);border-color:transparent;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.22),var(--shadow-1)}
.btn.danger:hover{filter:brightness(1.08)}

/* Tiroir — glisse depuis la droite, ne bloque pas la lecture du contexte. */
.drawer{position:fixed;top:0;right:0;bottom:0;z-index:calc(var(--z-modal) + 1);
  width:min(var(--drawer-w),100vw);display:flex;flex-direction:column;
  background:var(--panel);border-left:var(--bw) solid var(--line);
  box-shadow:var(--shadow-2);
  transform:translateX(100%);transition:transform var(--t-state) var(--ease)}
.drawer.on{transform:none}

/* --------------------------------------------------------------------------
   15.4 · INFOBULLE
   Elle explique, elle ne cache pas. Rien d'indispensable ne vit dans une
   infobulle : au doigt, elle n'existe pas.
   -------------------------------------------------------------------------- */
[data-tip]{position:relative}
[data-tip]::after{content:attr(data-tip);position:absolute;z-index:var(--z-tip);
  bottom:calc(100% + 7px);left:50%;transform:translateX(-50%) translateY(3px);
  padding:5px 9px;border-radius:var(--r-xs);white-space:nowrap;
  background:var(--tip-bg);color:var(--tip-tx);font-size:var(--fs-note);font-family:var(--sans);
  box-shadow:var(--shadow-2);pointer-events:none;
  opacity:0;transition:opacity var(--t-quick) var(--ease),transform var(--t-quick) var(--ease)}
[data-tip]:hover::after,[data-tip]:focus-visible::after{opacity:1;transform:translateX(-50%)}
[data-tip].tip-r::after{bottom:auto;left:calc(100% + 7px);top:50%;
  transform:translateY(-50%) translateX(-3px)}
[data-tip].tip-r:hover::after{transform:translateY(-50%)}

/* --------------------------------------------------------------------------
   15.5 · ONGLETS
   Le `.seg` est un FILTRE compact posé dans une ligne d'outils. Les onglets
   découpent une VUE en pages. Ne pas les confondre : deux composants, deux
   rôles. L'indicateur glisse — comme le rail de la nav, même langage.
   -------------------------------------------------------------------------- */
.tabs{display:flex;align-items:stretch;gap:var(--sp-1);position:relative;
  min-height:var(--h-tab);border-bottom:var(--bw) solid var(--hair)}
.tab{display:inline-flex;align-items:center;gap:var(--sp-2);height:var(--h-tab);
  padding:0 var(--sp-4);border:0;background:transparent;cursor:pointer;
  font-family:var(--sans);font-size:var(--fs-base);color:var(--tx2);white-space:nowrap;
  transition:color var(--t-quick) var(--ease)}
.tab:hover{color:var(--tx)}
.tab.on{color:var(--tx);font-weight:var(--fw-medium)}
.tab .tag{font-size:10px;font-family:var(--mono);color:var(--tx3);
  border:var(--bw) solid var(--hair);border-radius:var(--r-xs);padding:0 5px;
  background:var(--sunken)}
.tab-ind{position:absolute;bottom:-1px;left:0;height:2px;width:0;border-radius:var(--r-dot);
  background:var(--accent);box-shadow:0 0 8px var(--accent-glow);opacity:0;
  transition:transform var(--t-slide) var(--ease),width var(--t-slide) var(--ease),
             opacity var(--t-ui) var(--ease)}
.tab-ind.on{opacity:1}
.tab-panel{display:none;padding-top:var(--sp-5)}
.tab-panel.show{display:block;animation:fade .3s var(--ease)}

/* --------------------------------------------------------------------------
   15.6 · PAGINATION
   Toujours accompagnée de la PLAGE affichée : « 1–25 sur 214 ». Des flèches
   sans nombre ne disent pas où l'on en est.
   -------------------------------------------------------------------------- */
.pager{display:flex;align-items:center;gap:var(--sp-3);flex-wrap:wrap;
  min-height:var(--h-ctrl);margin-top:var(--sp-4)}
.pager .range{font-size:var(--fs-note);color:var(--tx3);font-family:var(--mono)}
.pager .pages{display:inline-flex;align-items:center;gap:2px;margin-left:auto}
.pager .pg{display:inline-flex;align-items:center;justify-content:center;
  min-width:var(--h-ctrl-sm);height:var(--h-ctrl-sm);padding:0 8px;
  border:0;background:transparent;border-radius:var(--r-xs);cursor:pointer;
  font-family:var(--mono);font-size:var(--fs-note);color:var(--tx2);
  transition:background var(--t-fast) var(--ease),color var(--t-fast) var(--ease)}
.pager .pg:hover:not(:disabled){background:var(--hover-nav);color:var(--tx)}
.pager .pg.on{background:var(--hover-nav-on);color:var(--tx);
  box-shadow:inset 0 0 12px -6px var(--accent-glow)}
.pager .pg:disabled{opacity:.35;cursor:not-allowed}
.pager .gap{color:var(--tx3);padding:0 2px;font-family:var(--mono);font-size:var(--fs-note)}

/* --------------------------------------------------------------------------
   15.7 · ACCORDÉON
   ATTENTION — la loi 12 interdit de replier ce qui doit être lu. L'accordéon
   est donc réservé au FACULTATIF : charge utile brute, réglages avancés,
   journal. Jamais du contenu primaire, jamais pour « gagner de la place ».
   Bâti sur <details>/<summary> : le clavier et les lecteurs d'écran suivent
   sans une ligne de JS.
   -------------------------------------------------------------------------- */
.acc{border:var(--bw) solid var(--hair);border-radius:var(--r);background:var(--panel);
  box-shadow:var(--inset-hi),var(--shadow-1);overflow:hidden}
.acc > details + details{border-top:var(--bw) solid var(--hair)}
.acc summary{display:flex;align-items:center;gap:var(--sp-3);min-height:var(--h-row);
  padding:var(--sp-3) var(--sp-5);cursor:pointer;list-style:none;
  font-size:var(--fs-base);color:var(--tx2);
  transition:background var(--t-fast) var(--ease),color var(--t-fast) var(--ease)}
.acc summary::-webkit-details-marker{display:none}
.acc summary:hover{background:var(--hover-nav);color:var(--tx)}
.acc summary::after{content:"";margin-left:auto;width:0;height:0;flex:0 0 auto;
  border-left:4px solid transparent;border-right:4px solid transparent;
  border-top:4px solid currentColor;opacity:.6;
  transition:transform var(--t-ui) var(--ease)}
.acc details[open] > summary{color:var(--tx)}
.acc details[open] > summary::after{transform:rotate(180deg)}
.acc .acc-body{padding:0 var(--sp-5) var(--sp-5);font-size:var(--fs-sm);color:var(--tx2)}

/* --------------------------------------------------------------------------
   15.8 · FIL D'ÉTAPES
   Trois états, jamais quatre : fait · en cours · à venir.
   -------------------------------------------------------------------------- */
.steps{display:flex;align-items:flex-start;gap:0;flex-wrap:wrap}
/* [FIX F24] LE REPÈRE EST AU-DESSUS, PAS À CÔTÉ. En rangée, le libellé occupait
   la bande horizontale que le trait de liaison traverse à mi-hauteur du repère
   (`top:11px`) : la ligne passait littéralement à travers le texte. Invisible
   sur un libellé d'un mot, illisible dès qu'il en porte deux. En colonne, le
   trait ne longe plus que le repère, et le texte se pose dessous. */
.step{display:flex;flex-direction:column;align-items:flex-start;gap:var(--sp-2);
  flex:1;min-width:150px;position:relative;padding-right:var(--sp-4)}
.step .mark{display:grid;place-items:center;width:22px;height:22px;flex:0 0 auto;
  border-radius:50%;border:var(--bw) solid var(--line);background:var(--panel2);
  font-family:var(--mono);font-size:10px;color:var(--tx3)}
.step .lbl{font-size:var(--fs-sm);color:var(--tx3);line-height:1.4}
.step .lbl b{display:block;color:var(--tx2);font-weight:var(--fw-medium)}
/* Le trait de liaison part du repère et s'arrête avant le suivant. */
.step:not(:last-child)::after{content:"";position:absolute;left:22px;right:var(--sp-2);
  top:11px;height:1px;background:var(--hair)}
.step.done .mark{border-color:var(--ok-line);background:var(--ok-bg);color:var(--ok-tx)}
.step.done .lbl b{color:var(--tx2)}
.step.done:not(:last-child)::after{background:var(--ok-line)}
.step.now .mark{border-color:var(--accent);background:var(--accent-dim);color:var(--accent);
  box-shadow:0 0 0 3px var(--accent-dim)}
.step.now .lbl b{color:var(--tx)}

/* --------------------------------------------------------------------------
   15.9 · AVATAR
   Initiales par défaut : dans une console, une photo manquante est la norme,
   pas l'exception. La teinte est dérivée du nom, donc stable d'un écran à
   l'autre — c'est ce qui permet de reconnaître quelqu'un sans lire.
   -------------------------------------------------------------------------- */
.av{display:inline-grid;place-items:center;width:var(--av);height:var(--av);flex:0 0 auto;
  border-radius:50%;overflow:hidden;
  font-size:var(--fs-meta);font-weight:var(--fw-semibold);letter-spacing:.02em;
  border:var(--bw) solid var(--hair);background:var(--panel2);color:var(--tx2);
  box-shadow:var(--inset-hi)}
.av img{width:100%;height:100%;object-fit:cover}

/* --------------------------------------------------------------------------
   L'AVATAR CARRÉ

   Un avatar rond accompagne un nom ; un avatar carré tient lieu de PHOTO. Le
   rayon est celui des tuiles d'icône, seule exception de rayon déjà assumée par
   la charte : en inventer un second aurait fait deux familles de coins arrondis
   dans le même écran.
   -------------------------------------------------------------------------- */
.av.carre{border-radius:var(--r-tile)}
.av[data-ic] svg,.av .ico svg{width:44%;height:44%;stroke:currentColor;fill:none;
  stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round}

/* --------------------------------------------------------------------------
   LA CARTE-AFFICHE — `.affiche` dans une `.grid.g-affiches`

   Une photo en tête, le texte dessous : un rectangle DEBOUT. C'est le format
   d'un catalogue de personnes, et il tient pour une raison simple — on reconnaît
   quelqu'un à son visage, pas à son nom, donc l'image doit être le premier tiers
   de la carte et non une pastille à côté d'une ligne de texte.

   🔴 LA PHOTO EST EN 3:4, PAS EN CARRÉ. Un portrait cadré carré coupe soit le
   haut de la tête soit le buste ; le 3:4 est le cadrage naturel d'une personne
   debout, et c'est celui que le recadrage côté navigateur doit produire — les
   deux doivent dire le même rapport, sinon `object-fit:cover` rogne une seconde
   fois ce qui avait déjà été cadré.

   ⚠️ L'IMAGE EST EN POSITION ABSOLUE. Sans ça, c'est elle qui donne sa taille
   intrinsèque au cadre : une photo de 288 px de large imposerait 288 px à une
   carte qui en fait 272, et le rapport de forme ne servirait plus à rien. Le
   défaut ne se voit qu'une fois la première photo posée.

   🔴 LE NOMBRE DE COLONNES N'EST ÉCRIT NULLE PART. `auto-fill` en pose autant
   que la largeur en autorise : quatre sur un écran ordinaire, cinq ou six sur un
   très grand, deux sur un portable. Une liste de paliers en `@media` aurait
   fallu la retoucher à chaque changement de largeur de carte.
   -------------------------------------------------------------------------- */
/* 🔴 LE PLAFOND SE POSE SUR LA PISTE, PAS SUR LA GRILLE, et c'est tout l'objet
   du `max()`. La piste vaut le PLUS GRAND de deux nombres : la largeur minimale
   d'une carte, et la largeur qu'aurait une colonne si l'on en posait exactement
   `--n-affiches-max`. Tant que la fenêtre est ordinaire, c'est le minimum qui
   gagne et `auto-fill` compte les colonnes ; dès qu'elle devient très large,
   c'est la seconde qui gagne et il n'en tient plus que huit — qui remplissent
   toute la largeur.

   ⚠️ UNE `max-width` SUR LA GRILLE AURAIT AUSSI TENU LES HUIT COLONNES, en
   laissant sept cents pixels de vide à droite sur un écran de 2560. Ici les
   cartes grandissent : huit de 290 px plutôt que huit de 205 px collées à
   gauche. */
.g-affiches{grid-template-columns:repeat(auto-fill,
  minmax(max(var(--w-affiche),
             (100% - (var(--n-affiches-max) - 1) * var(--gap))
             / var(--n-affiches-max)), 1fr))}

.affiche{display:flex;flex-direction:column;gap:var(--sp-3);height:100%;min-width:0}
.affiche-photo{position:relative;width:100%;aspect-ratio:3/4;overflow:hidden;
  display:grid;place-items:center;
  border:var(--bw) solid var(--hair);border-radius:var(--r-sm);
  background:var(--panel2);box-shadow:var(--inset-hi);
  font-size:var(--fs-metric);font-weight:var(--fw-semibold);
  letter-spacing:var(--ls-caps);color:var(--tx3)}
.affiche-photo img{position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover}

.affiche-txt{display:flex;flex-direction:column;gap:var(--sp-2);min-width:0}
/* `h4` n'a aucune autre règle dans ce fichier : sans celle-ci il tombe sur le
   défaut du navigateur, ~18 px de marge en haut ET en bas — trois fois
   l'espacement voulu, et seulement sur la première ligne, donc des écarts
   inégaux par-dessus le marché. */
.affiche-txt h4{margin:0;font-size:var(--fs-base);font-weight:var(--fw-semibold);
  line-height:var(--lh);color:var(--tx);overflow-wrap:anywhere}
.affiche-txt .mono{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Les chiffres au corps de note : à 272 px, deux paires libellé-nombre en 14 px
   passent à la ligne sur les valeurs longues, et une carte qui gagne une ligne
   casse l'alignement de toute la rangée. */
.affiche-num{font-size:var(--fs-note);color:var(--tx3)}
.affiche-num .num{color:var(--tx2)}

/* --------------------------------------------------------------------------
   LES SEPT TEINTES DE CLASSEMENT — `.cl-*`

   Un classement libre : rôle d'un téléphone, catégorie d'une créatrice, et ce
   qui viendra. Ces sept teintes n'ont PLUS DE SENS IMPOSÉ depuis le 1er août
   2026 — le vert ne veut pas dire « bon », le rouge ne dit aucun échec. Elles
   servent à DISTINGUER, pas à qualifier.

   🔴 ON REPREND LES TRIOS SÉMANTIQUES DU SYSTÈME, ON NE LES RECOMPOSE PAS. Ils
   sont déjà réglés pour le contraste dans les deux thèmes ; les refabriquer à
   partir de `--tile-*` donnerait du texte clair sur fond clair au premier
   basculement. La charte le dit en une ligne : « toujours utilisées en trio,
   jamais recomposées à la main ».

   `err` est volontairement absente : elle dit un échec survenu et n'a rien à
   faire dans un classement libre.

   ⚠️ DEUX CLASSES, ET C'EST OBLIGATOIRE : `.badge.cl-g`, jamais `.cl-g` seule.
   `.badge` pose son fond et son liseré et vit plus bas dans ce fichier ; une
   règle à une seule classe perdrait la cascade sans que rien ne le signale — la
   pastille resterait grise. Le même piège que `.btn.ghost`, payé le 5 août.
   -------------------------------------------------------------------------- */
/* 🔴 LA TEINTE SEULE S'APPLIQUE À N'IMPORTE QUEL ÉLÉMENT, et c'est délibéré.
   Elle a d'abord été écrite `.pt.cl-g` — deux classes obligatoires — et le
   premier bouton qui portait `cl-g` sans `pt` est sorti NOIR : la règle ne le
   qualifiait pas, et rien ne le signalait. C'est le piège de `.btn.ghost`, payé
   une seconde fois le 13 août 2026.

   Elle emprunte la famille `--tile-*`, calculée pour être lue sur une surface.
   `.badge.cl-*` plus haut la surcharge pour les badges, qui ont besoin d'un trio
   complet — fond, liseré, texte — et non d'une couleur seule. */
.cl-g{color:var(--tile-g)}
.cl-c{color:var(--tile-c)}
.cl-o{color:var(--tile-o)}
.cl-v{color:var(--tile-v)}
.cl-b{color:var(--tile-b)}
.cl-m{color:var(--tile-m)}
.cl-r{color:var(--tile-r)}

/* LA PASTILLE — un rond plein, pour que le choix se REGARDE au lieu de se lire. */
.pt{width:10px;height:10px;border-radius:50%;flex:0 0 auto;background:currentColor}

.badge.cl-g{color:var(--ok-tx);  border-color:var(--ok-line);  background:var(--ok-bg)}
.badge.cl-c{color:var(--cy-tx);  border-color:var(--cy-line);  background:var(--cy-bg)}
.badge.cl-o{color:var(--warn-tx);border-color:var(--warn-line);background:var(--warn-bg)}
.badge.cl-v{color:var(--acc-tx); border-color:var(--acc-line); background:var(--acc-bg)}
.badge.cl-b{color:var(--ext-tx); border-color:var(--ext-line); background:var(--ext-bg)}
.badge.cl-m{color:var(--hum-tx); border-color:var(--hum-line); background:var(--hum-bg)}
.badge.cl-r{color:var(--risk-tx);border-color:var(--risk-line);background:var(--risk-bg)}

/* --------------------------------------------------------------------------
   CHOISIR UNE TEINTE — `.pt-choix`, sept pastilles au lieu d'un menu

   🔴 UN MENU DÉROULANT NE SURVIT PAS À UN CONTENEUR QUI DÉFILE. `.tbl-wrap` et
   `.modal-body` portent `overflow:auto` — qui découpe tout descendant en
   position absolue, que le défilement soit nécessaire ou non. Un `.dd` posé
   dans un tableau ou dans le corps d'une modale voit donc son menu rogné à la
   hauteur de sa ligne : on clique, il « ne se passe rien », et rien ne le
   signale. Payé le 13 août 2026 sur le tableau des catégories.

   Sept pastilles alignées n'ouvrent rien, ne débordent de rien, et se lisent
   d'un regard — un choix parmi sept n'a jamais eu besoin d'être replié.

   ⚠️ CE SONT DES BOUTONS DE SOUMISSION. Chacun porte `name="couleur"` et sa
   propre valeur : cliquer une pastille enregistre la ligne AVEC cette teinte, en
   un geste et sans une ligne de script. C'est du HTML, pas une astuce.
   -------------------------------------------------------------------------- */
.pt-choix{width:18px;height:18px;padding:0;flex:0 0 auto;cursor:pointer;
  border-radius:50%;border:2px solid transparent;background:transparent;
  display:grid;place-items:center;
  transition:border-color var(--t-quick) var(--ease),transform var(--t-quick) var(--ease)}
.pt-choix::before{content:"";width:10px;height:10px;border-radius:50%;
  background:currentColor}
.pt-choix:hover{transform:scale(1.15)}
.pt-choix:focus-visible{outline:var(--rail) solid var(--accent);outline-offset:2px}
/* CELLE QUI EST POSÉE PORTE UN ANNEAU. Sans repère, on ne sait plus laquelle est
   active — et on la reclique pour rien. */
.pt-choix.on{border-color:currentColor}

/* --------------------------------------------------------------------------
   LA ZONE DE DÉPÔT — `.depot`

   Un fichier se dépose ou se choisit ; les deux gestes visent la MÊME cible, et
   c'est ce qui la rend lisible. Un bouton « Parcourir » à côté d'un rectangle
   pointillé, ce sont deux affordances pour une seule action, et la moitié des
   gens n'essaient jamais le glisser-déposer parce que rien ne leur dit qu'il
   existe.

   ⚠️ LE CONTRÔLE NATIF RESTE, MASQUÉ DERRIÈRE. `<input type=file>` ne se dessine
   pas et jurerait ici ; mais lui seul ouvre le sélecteur du système, et lui seul
   est atteignable au clavier. La zone est donc un `<label>` qui le pointe — le
   navigateur fait le reste, sans une ligne de script.

   `.survol` est posée par le script pendant le survol d'un fichier. Le bord
   pointillé DIT qu'on peut déposer, la teinte d'accent dit qu'on est au bon
   endroit — sans elle, on lâche à côté et la page ouvre l'image en plein écran.
   -------------------------------------------------------------------------- */
.depot{display:flex;align-items:center;gap:var(--sp-4);min-width:0;cursor:pointer;
  padding:var(--sp-3);border:var(--bw) dashed var(--line);border-radius:var(--r);
  background:var(--sunken);color:var(--tx2);font-size:var(--fs-sm);
  transition:border-color var(--t-ui) var(--ease),
             background var(--t-ui) var(--ease),color var(--t-ui) var(--ease)}
.depot:hover{border-color:var(--hover-border);color:var(--tx)}
.depot.survol{border-color:var(--accent);background:var(--acc-bg);color:var(--tx);
  box-shadow:inset 0 0 24px -8px var(--accent-glow)}
.depot:focus-within{outline:var(--rail) solid var(--accent);outline-offset:2px}
.depot .lbl{min-width:0}
.depot .lbl b{display:block;color:var(--tx)}

.av.sm{--av:22px;font-size:9.5px}
.av.lg{--av:36px;font-size:var(--fs-sm)}
/* `--av` ANNONÇAIT DÉJÀ CETTE TAILLE — « .sm 22 · .lg 36 · .xl voir ci-dessous »
   — et le « ci-dessous » n'existait pas. Elle sert quand une personne est le
   SUJET d'une ligne et non une mention à côté d'un nom : un choix qu'on vient de
   faire et qu'on doit reconnaître d'un coup d'œil. À 36 px on lit le nom puis on
   vérifie la photo ; à 64 px on reconnaît d'abord le visage, ce qui est l'ordre
   dans lequel on identifie quelqu'un. */
.av.xl{--av:64px;font-size:var(--fs-base)}
.av.t-g{color:var(--tile-g);border-color:rgba(95,221,157,.3);background:rgba(95,221,157,.12)}
.av.t-c{color:var(--tile-c);border-color:rgba(67,211,222,.3);background:rgba(67,211,222,.12)}
.av.t-o{color:var(--tile-o);border-color:rgba(230,167,92,.3);background:rgba(230,167,92,.12)}
.av.t-v{color:var(--tile-v);border-color:rgba(124,116,255,.3);background:rgba(124,116,255,.12)}
.av.t-b{color:var(--tile-b);border-color:rgba(var(--blue-rgb),.3);background:rgba(var(--blue-rgb),.12)}
.av.t-m{color:var(--tile-m);border-color:rgba(var(--mag-rgb),.3);background:rgba(var(--mag-rgb),.12)}
.av.t-r{color:var(--tile-r);border-color:rgba(var(--red-rgb),.3);background:rgba(var(--red-rgb),.12)}
/* Pile — les avatars se chevauchent, le dernier porte le reste du compte. */
.av-stack{display:inline-flex;align-items:center}
.av-stack .av + .av{margin-left:-8px}
.av-stack .av{box-shadow:0 0 0 2px var(--panel),var(--inset-hi)}

/* --------------------------------------------------------------------------
   15.10 · FORMULAIRE
   Un champ, c'est QUATRE parties : libellé, contrôle, aide, erreur. L'aide se
   lit AVANT la saisie, l'erreur APRÈS — elles n'occupent donc pas la même
   place et ne se remplacent pas. Un champ obligatoire est marqué ; on ne
   marque jamais les facultatifs, ils sont toujours plus nombreux.
   -------------------------------------------------------------------------- */
.form{display:flex;flex-direction:column;gap:var(--sp-5)}
.form.g2{display:grid;grid-template-columns:1fr 1fr;gap:var(--sp-5)}
/* TROIS ET QUATRE COLONNES — pour une modale LARGE, pas pour une carte. Quatre
   champs courts — un choix, une adresse, un prénom, une date — tiennent sur une
   ligne dès 900 px de formulaire et y gagnent : sur quatre lignes séparées, les
   trois quarts droits de la modale restent vides et l'œil descend pour rien.
   La gouttière est celle de `.form`, jamais `--gap` : deux blocs de formulaire
   côte à côte avec 18 px d'un côté et 14 de l'autre se voient. */
.form.g3,.form.g4{display:grid;gap:var(--sp-5)}
.form.g3{grid-template-columns:repeat(3,1fr)}
.form.g4{grid-template-columns:repeat(4,1fr)}
.fld{display:flex;flex-direction:column;gap:var(--sp-2);min-width:0}
.fld > label{font-size:var(--fs-note);font-weight:var(--fw-medium);color:var(--tx2);
  display:flex;align-items:center;gap:var(--sp-2)}
.fld > label .req{color:var(--err);font-size:var(--fs-meta)}
.fld > label .opt{color:var(--tx3);font-weight:var(--fw-regular)}
/* 🔴 `.help` VIT AUSSI HORS D'UN CHAMP. La règle était `.fld .help` : partout
   ailleurs — et il y a plus de cent `<p class="help">` dans le produit — elle
   ne s'appliquait pas, donc une note discrète se rendait en texte courant,
   pleine taille et pleine teinte. Elle prenait alors le poids d'un titre juste
   au-dessus de ce qu'elle commente. Rien ne le signalait : la classe existe,
   l'écran s'affiche, il est simplement faux. */
.help{font-size:var(--fs-note);color:var(--tx3);line-height:1.5}
.fld .err{display:none;font-size:var(--fs-note);color:var(--err-tx);line-height:1.5;
  align-items:center;gap:var(--sp-2)}
.fld.invalid .err{display:flex}
.fld.invalid .help{display:none}
.fld.invalid .input,.fld.invalid select,.fld.invalid textarea{
  border-color:var(--err-line);box-shadow:var(--sunk),0 0 0 2px rgba(236,106,94,.16)}
/* SÉLECTEUR DESSINÉ.
   Le popup d un <select> natif est rendu par le SYSTÈME, pas par la page :
   aucune règle CSS ne l atteint. Il arrivait donc en bleu Windows au milieu
   d une console sombre. Le moteur remplace donc tout <select> d un .fld par
   ce composant, bâti sur le même menu que les autres dropdowns — le <select>
   d origine reste dans le DOM, caché, pour porter la valeur au formulaire. */
.select{display:block;width:100%}
.select > .select-btn{display:flex;align-items:center;gap:var(--sp-2);width:100%;
  height:var(--h-ctrl);padding:0 var(--sp-3);border-radius:var(--r-sm);cursor:pointer;
  background:var(--field-bg);border:var(--bw) solid var(--field-line);color:var(--tx);
  font-family:var(--sans);font-size:var(--fs-sm);text-align:left;
  box-shadow:var(--sunk);
  transition:border-color var(--t-quick) var(--ease),box-shadow var(--t-quick) var(--ease)}
.select > .select-btn:hover{border-color:rgba(61,214,195,.22)}
.select.open > .select-btn,.select > .select-btn:focus-visible{outline:none;
  border-color:rgba(61,214,195,.4);box-shadow:var(--sunk),0 0 0 2px rgba(61,214,195,.18)}
.select .select-val{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.select > .select-btn .caret{width:0;height:0;flex:0 0 auto;
  border-left:4px solid transparent;border-right:4px solid transparent;
  border-top:4px solid currentColor;opacity:.55;
  transition:transform var(--t-ui) var(--ease)}
.select.open > .select-btn .caret{transform:rotate(180deg)}
/* Le menu épouse la largeur du champ : un menu plus étroit que son champ
   donnerait l impression de deux contrôles distincts. */
.select > .dd-menu{width:100%;min-width:0;max-height:240px;overflow-y:auto}
.fld.invalid .select > .select-btn{border-color:var(--err-line);
  box-shadow:var(--sunk),0 0 0 2px rgba(236,106,94,.16)}

/* Le `select` natif conserve ce gabarit — utile si le moteur ne tourne pas. */
.fld select{appearance:none;-webkit-appearance:none;height:var(--h-ctrl);width:100%;
  padding:0 30px 0 12px;border-radius:var(--r-sm);cursor:pointer;
  background:var(--field-bg);border:var(--bw) solid var(--field-line);color:var(--tx);
  font-family:var(--sans);font-size:var(--fs-sm);box-shadow:var(--sunk);
  background-image:linear-gradient(45deg,transparent 50%,currentColor 50%),
                   linear-gradient(135deg,currentColor 50%,transparent 50%);
  background-position:calc(100% - 15px) 14px,calc(100% - 11px) 14px;
  background-size:4px 4px,4px 4px;background-repeat:no-repeat;
  transition:border-color var(--t-quick) var(--ease),box-shadow var(--t-quick) var(--ease)}
.fld select:focus{outline:none;border-color:rgba(61,214,195,.4);
  box-shadow:var(--sunk),0 0 0 2px rgba(61,214,195,.18)}
.fld textarea{min-height:96px;padding:10px 12px;resize:vertical;line-height:1.6;
  width:100%;border-radius:var(--r-sm);
  background:var(--field-bg);border:var(--bw) solid var(--field-line);color:var(--tx);
  font-family:var(--mono);font-size:var(--fs-code);box-shadow:var(--sunk);
  transition:border-color var(--t-quick) var(--ease),box-shadow var(--t-quick) var(--ease)}
.fld textarea:focus{outline:none;border-color:rgba(61,214,195,.4);
  box-shadow:var(--sunk),0 0 0 2px rgba(61,214,195,.18)}
.fld .input{min-width:0;flex:0 0 auto}

/* ---- Dépôt de fichier ----
   Le contrôle natif est le seul du formulaire que le navigateur dessine LUI-MÊME
   et qu'aucune propriété ne rattrape : « Choisir un fichier / Aucun fichier
   choisi », dans la police du système, avec un bouton gris qui n'existe nulle
   part ailleurs dans la console. Il se cache donc et se rejoue, exactement comme
   le `<select>` — même doctrine, même moteur (`mountFichiers`).

   LE NOM DU FICHIER EST LA MOITIÉ DU COMPOSANT. Un bouton seul ne dit pas si
   quelque chose a été choisi ; c'est la seule information que le contrôle natif
   donnait, et la perdre en l'habillant serait un mauvais échange. */
.fichier{display:flex;align-items:center;gap:var(--sp-3);min-width:0}
.fichier > .fichier-nom{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;color:var(--tx3);font-size:var(--fs-sm)}
/* Posé : le nom devient une DONNÉE — chasse fixe, teinte de texte secondaire.
   Vide, c'est une consigne, donc de la prose en teinte tertiaire. */
.fichier > .fichier-nom.pose{color:var(--tx2);font-family:var(--mono);
  font-size:var(--fs-code)}

/* Case à cocher — dessinée, pour tenir le même langage que le reste. */
.ck{display:inline-grid;place-items:center;width:16px;height:16px;flex:0 0 auto;
  appearance:none;-webkit-appearance:none;margin:0;cursor:pointer;
  border-radius:var(--r-xs);border:var(--bw) solid var(--line);background:var(--field-bg);
  box-shadow:var(--sunk);
  transition:background var(--t-quick) var(--ease),border-color var(--t-quick) var(--ease)}
.ck::after{content:"";width:9px;height:5px;margin-top:-2px;
  border-left:2px solid var(--on-accent);border-bottom:2px solid var(--on-accent);
  transform:rotate(-45deg) scale(.6);opacity:0;
  transition:opacity var(--t-quick) var(--ease),transform var(--t-quick) var(--ease)}
.ck:checked{background:var(--accent);border-color:var(--accent)}
.ck:checked::after{opacity:1;transform:rotate(-45deg) scale(1)}
.ck:indeterminate{background:var(--accent);border-color:var(--accent)}
.ck:indeterminate::after{opacity:1;width:9px;height:0;margin:0;border-bottom:2px solid var(--on-accent);
  border-left:0;transform:none}
.ck-row{display:flex;align-items:center;gap:var(--sp-3);font-size:var(--fs-sm);
  color:var(--tx2);cursor:pointer}

/* --------------------------------------------------------------------------
   15.11 · SÉLECTION DE LIGNES & ACTIONS GROUPÉES
   La barre d'actions groupées REMPLACE la ligne d'outils au lieu de s'ajouter :
   deux barres empilées feraient sauter le contenu à chaque sélection.
   -------------------------------------------------------------------------- */
tbody tr.sel{background:var(--hover-nav-on)}
tbody tr.sel td:first-child{box-shadow:inset 2px 0 0 var(--accent)}
/* Les deux barres occupent LA MÊME cellule de grille : passer de l une à
   l autre ne déplace donc rien. Une barre qui s ajoute ferait sauter tout le
   contenu à chaque sélection — c est ce qu il faut éviter. */
.toolbar{display:grid;align-items:center;min-height:var(--h-ctrl)}
.toolbar > .tools,.toolbar > .bulkbar{grid-area:1 / 1}
.toolbar > .tools{display:flex;align-items:center;gap:var(--sp-3);flex-wrap:wrap}
.toolbar.bulk > .tools{visibility:hidden;pointer-events:none}
.toolbar.bulk > .bulkbar{display:flex}

/* Hauteur STRICTEMENT égale à celle de la ligne d outils : un padding vertical
   la rendrait plus haute et le contenu sauterait quand même à la sélection. */
.bulkbar{display:none;align-items:center;gap:var(--sp-4);
  height:var(--h-ctrl);padding:0 var(--sp-4);border-radius:var(--r-sm);
  background:var(--accent-dim);border:var(--bw) solid rgba(61,214,195,.35);
  box-shadow:var(--inset-hi);animation:fade .2s var(--ease)}
.bulkbar.on{display:flex}
.bulkbar .count{font-size:var(--fs-sm);color:var(--tx);font-weight:var(--fw-medium)}
.bulkbar .push{margin-left:auto}

/* --------------------------------------------------------------------------
   15.12 · RECHERCHE & PALETTE
   Le champ de recherche porte son icône À GAUCHE et son raccourci à droite :
   le raccourci se découvre en regardant le champ, pas dans une documentation.
   -------------------------------------------------------------------------- */
.search{display:flex;align-items:center;gap:var(--sp-2);height:var(--h-ctrl);
  padding:0 var(--sp-3);min-width:200px;border-radius:var(--r-sm);
  background:var(--field-bg);border:var(--bw) solid var(--field-line);
  box-shadow:var(--sunk);
  transition:border-color var(--t-quick) var(--ease),box-shadow var(--t-quick) var(--ease)}
.search:focus-within{border-color:rgba(61,214,195,.4);
  box-shadow:var(--sunk),0 0 0 2px rgba(61,214,195,.18)}
.search svg{width:14px;height:14px;flex:0 0 auto;stroke:var(--tx3);fill:none;stroke-width:1.7;
  stroke-linecap:round;stroke-linejoin:round}
.search input{flex:1;min-width:0;border:0;background:transparent;outline:none;
  color:var(--tx);font-family:var(--sans);font-size:var(--fs-sm)}
.search input::placeholder{color:var(--tx3)}
.search kbd{flex:0 0 auto;font-family:var(--mono);font-size:10px;color:var(--tx3);
  border:var(--bw) solid var(--hair);border-radius:var(--r-xs);padding:1px 5px;
  background:var(--sunken)}

/* Palette de commandes — même matière que le menu déroulant, en plus large. */
.palette{position:fixed;top:12vh;left:50%;transform:translateX(-50%) translateY(-8px);
  z-index:calc(var(--z-modal) + 2);width:min(560px,calc(100vw - var(--sp-7)));
  background:var(--dd-bg);
  backdrop-filter:blur(14px) saturate(1.3);-webkit-backdrop-filter:blur(14px) saturate(1.3);
  border:var(--bw) solid var(--line);border-radius:var(--r);
  box-shadow:var(--dd-shadow),var(--inset-hi);overflow:hidden;
  opacity:0;pointer-events:none;
  transition:opacity var(--t-ui) var(--ease),transform var(--t-ui) var(--ease)}
.palette.on{opacity:1;transform:translateX(-50%);pointer-events:auto}
.palette .search{border:0;border-bottom:var(--bw) solid var(--hair);border-radius:0;
  box-shadow:none;height:var(--h-palette);background:transparent}
.palette .search:focus-within{box-shadow:none}
.palette-list{max-height:46vh;overflow-y:auto;padding:var(--sp-1)}
.palette .dd-item{height:34px}
.palette .dd-item .hint{margin-left:auto;font-family:var(--mono);font-size:10px;
  color:var(--tx3)}

/* --------------------------------------------------------------------------
   15.13 · PLAGE DE DATES
   Pas de calendrier : dans une console on choisit presque toujours une période
   RELATIVE. Les préréglages couvrent le cas courant, les deux champs le reste.
   -------------------------------------------------------------------------- */
.daterange .dd-menu{min-width:260px;padding:var(--sp-2)}
.daterange .dr-grid{display:grid;grid-template-columns:1fr 1fr;gap:var(--sp-2);
  padding:var(--sp-2) var(--sp-3) var(--sp-1)}
.daterange .dr-grid .fld{gap:4px}
.daterange .dr-grid label{font-size:10px;text-transform:uppercase;
  letter-spacing:var(--ls-nav);color:var(--tx3)}
.daterange input[type=date]{height:var(--h-ctrl-sm);width:100%;padding:0 8px;
  border-radius:var(--r-xs);background:var(--field-bg);
  border:var(--bw) solid var(--field-line);color:var(--tx);
  font-family:var(--mono);font-size:var(--fs-note);box-shadow:var(--sunk)}
.daterange input[type=date]::-webkit-calendar-picker-indicator{filter:invert(.5)}

/* ==========================================================================
   13 · GRILLES
   Une seule gouttière : --gap (14px). Les colonnes se replient en 3 paliers.
   ========================================================================== */
.grid{display:grid;gap:var(--gap)}
.g2{grid-template-columns:1fr 1fr}
.g3{grid-template-columns:repeat(3,1fr)}
.g4{grid-template-columns:repeat(4,1fr)}
.g5{grid-template-columns:repeat(5,1fr)}
.g2.asym  {grid-template-columns:3fr 2fr}
.g2.asym-r{grid-template-columns:2fr 3fr}
.kpi-row{display:grid;grid-template-columns:2.2fr repeat(4,1fr);gap:var(--gap)}

/* --------------------------------------------------------------------------
   TROIS COLONNES, QUATRE AU LARGE — `.g3.g4-au-large`

   Une grille de CARTES, pas de repères : au-delà d'une certaine largeur, une
   quatrième colonne devient lisible ; en dessous, elle serre trop. Le modificateur
   se pose SUR `.g3`, ce qui lui fait hériter de tout l'escalier plus bas — deux
   colonnes sous 900 px, une seule sous 520 — au lieu d'un second escalier à tenir.

   ⚠️ LE SEUIL EST UNE LARGEUR DE FENÊTRE, PAS DE CONTENU. Le rail de navigation
   et les marges de page en retirent environ 130 px : à 1500 px de fenêtre, une
   carte sur quatre fait encore ~345 px, de quoi loger une vignette de 102 px et
   son bloc de texte sans que le chemin Drive ne se tronque.
   -------------------------------------------------------------------------- */
@media(min-width:1500px){
  .g3.g4-au-large{grid-template-columns:repeat(4,1fr)}
}

@media(max-width:1100px){
  .kpi-row{grid-template-columns:1fr 1fr}
  .kpi.hero{grid-column:1/-1}
  /* ⚠️ LE PALIER EST UNE LARGEUR DE FENÊTRE, PAS DE MODALE — et une modale
     large suit la fenêtre (`min(1120px, 92vw)`). Sous 1100, quatre champs
     tombent à ~230 px : une date et un nom y tiennent encore, mais leur
     libellé se replie sur deux lignes. Deux colonnes valent mieux.
     La règle est répétée ici parce que `.form.g4` bat `.g4` en spécificité :
     le palier des grilles ordinaires ne l'atteint pas. */
  .form.g4{grid-template-columns:1fr 1fr}
}
@media(max-width:900px){
  .g2,.g3,.g4,.g5{grid-template-columns:1fr 1fr}
  .g2.asym,.g2.asym-r{grid-template-columns:1fr}
}
@media(max-width:760px){
  /* Sous 760px la barre n'a plus de rail : elle passe en bandeau horizontal
     pleine largeur, toujours déployée. Le survol n'existe pas au doigt. */
  .app{flex-direction:column}
  .side,.side.open{position:static;width:100%;height:auto}
  .side .lbl,.side .nav-lbl,.side .nav-item .tag{opacity:1}
  nav{max-height:none}
  .wrap{padding:var(--sp-5) var(--pad-page-x) var(--sp-8)}
  .split{grid-template-columns:1fr}
  .facade + .facade{border-left:0;border-top:var(--bw) solid var(--hair)}
  .toasts{left:16px;right:16px;bottom:16px;align-items:stretch}
}
@media(max-width:520px){
  .g2,.g3,.g4,.g5,.kpi-row{grid-template-columns:1fr}
}

/* --------------------------------------------------------------------------
   Composants d ecran en etroit
   -------------------------------------------------------------------------- */
@media(max-width:760px){
  /* Un formulaire a deux colonnes sur un ecran etroit donne deux champs de
     200px : on y saisit mal. Il se replie. */
  .form.g2,.form.g3,.form.g4{grid-template-columns:1fr}
  /* Le fil d etapes passe en liste verticale. Sur plusieurs lignes, le trait
     de liaison horizontal pointerait dans le vide en fin de rangee. */
  .steps{flex-direction:column;gap:var(--sp-4)}
  .step{min-width:0;padding-right:0}
  .step:not(:last-child)::after{display:none}
  /* Le tiroir laisse toujours voir un peu de contexte — sinon autant ouvrir
     une vue. */
  .drawer{width:min(var(--drawer-w),calc(100vw - var(--sp-7)))}
  /* Une barre d onglets plus large que l ecran doit defiler, pas se tasser. */
  .tabs{overflow-x:auto;scrollbar-width:none}
  .tabs::-webkit-scrollbar{display:none}
  .modal{padding:var(--sp-4)}
}

/* ==========================================================================
   14 · MOUVEMENT RÉDUIT — non négociable
   [FIX] Les maquettes ne neutralisaient que `.bars-wait .bar > i`.
   Les colonnes verticales et les barres empilées restaient à scale 0 tant que
   le JS n'avait pas retiré la classe : un utilisateur en reduced-motion
   voyait un graphe vide. Les trois cas sont couverts ici.
   ========================================================================== */
@media(prefers-reduced-motion:reduce){
  /* Le selecteur universel ne couvre PAS les pseudo-elements : ecrit `*` seul,
     il laissait tourner `.logo::before` (halo) et `.sk::after` (balayage du
     squelette). Deux animations infinies qui continuaient chez quelqu un ayant
     demande l arret du mouvement. */
  *,*::before,*::after{animation:none !important;transition:none !important}
  html{scroll-behavior:auto}
  .rv{opacity:1;transform:none}
  .bars-wait .bar > i     {transform:scaleX(var(--w,1))}
  .bars-wait .stacked i   {transform:scaleX(1)}
  .bars-wait .days .d i   {transform:scaleY(1)}
  /* Le toast reste piloté par `.on` : sans transition il apparaît net. */
  /* L'anneau d'attente ne tourne plus — il se REFERME. Laissé ouvert et
     immobile, il ne se lit plus comme une attente mais comme un cercle mal
     tracé. Le libellé du bouton, lui, n'a jamais été masqué : l'action en cours
     reste nommée, donc rien n'est vide. */
  .btn[aria-busy="true"]::before{border-top-color:var(--btn-tx);opacity:.5}
  .btn.ghost[aria-busy="true"]::before{border-top-color:var(--tx2)}
}
