/* ============================================================
   LDY.CSS — Système d'identité visuelle des Loups de Yela
   Direction retenue : F · Passerelle
   ------------------------------------------------------------
   Feuille unique et autonome. Aucune dépendance, aucun build.

   Installation :
     <link rel="stylesheet" href="ldy.css">
     + les deux familles Google Fonts (voir § Démarrage)
     + le sprite ldy-icons.svg pour les icônes

   Tout est piloté par des custom properties sur :root. Pour
   ajuster la marque, ne toucher qu'aux tokens — jamais aux
   règles de composant.
   ============================================================ */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

/* ---------- Tokens structurels (identiques pour les 5 directions) ----------
   Seules les valeurs chromatiques et typographiques varient d'un thème à
   l'autre. Rythme d'espacement, z-index et tailles d'icône sont communs. */
:root {
  --sp-1: 4px;   --sp-2: 8px;   --sp-3: 12px;  --sp-4: 16px;
  --sp-5: 24px;  --sp-6: 32px;  --sp-7: 48px;  --sp-8: 64px;

  --ic-sm: 14px; --ic-md: 18px; --ic-lg: 22px;

  --z-base: 0; --z-sticky: 50; --z-drop: 200;
  --z-overlay: 400; --z-toast: 600; --z-tip: 700;

  --t-fast: 140ms; --t-base: 200ms; --t-slow: 300ms;
  --ease-out: cubic-bezier(.22,.61,.36,1);

  /* Gouttière de page — marge de sécurité entre le contenu et le bord
     de l'écran. Jamais de composant collé au bord : c'est le défaut le
     plus visible d'une interface non relue. Valeur responsive, ajustée
     plus bas selon la largeur. */
  --gutter: 16px;

  /* Largeur maximale du contenu. Token et non valeur enfouie dans
     .wrap : un projet peut vouloir une colonne plus large sans
     toucher aux règles de composant. */
  --wrap-max: 1180px;

  --tap: 44px;      /* cible tactile minimale (Apple HIG) */
  --tap-dense: 32px; /* densité écran pointeur fin */
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

/* ---------- Ascenseurs ----------
   Stylés partout, pas seulement sur les conteneurs à défilement
   déclarés : la page elle-même, les tableaux à en-tête collant, les
   listes d'autocomplétion et les zones de texte en héritent.
   La poignée prend la couleur de bordure de contrôle (3:1) et vire
   à l'accent au survol ; le rayon suit celui du thème, donc les
   directions à angles vifs gardent une poignée rectangulaire. */

* {
  scrollbar-width: thin;                              /* Firefox */
  scrollbar-color: var(--line-ctl) transparent;
}

*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: var(--surface); }
*::-webkit-scrollbar-thumb {
  background: var(--line-ctl);
  border: 2px solid var(--surface);
  border-radius: var(--radius-sm);
}
*::-webkit-scrollbar-thumb:hover { background: var(--accent); }
*::-webkit-scrollbar-corner { background: var(--surface); }

/* La page : piste sur le fond général, pas sur une surface de panneau. */
html::-webkit-scrollbar-track,
body::-webkit-scrollbar-track { background: var(--bg); }
html::-webkit-scrollbar-thumb,
body::-webkit-scrollbar-thumb { border-color: var(--bg); }

/* Réserve la gouttière pour éviter que le contenu saute quand un
   ascenseur apparaît au chargement des données. */
.tbl-sticky, .combo-list, .textarea { scrollbar-gutter: stable; }

/* Le carrousel garde une barre plus fine — elle est décorative,
   le défilement s'y fait au geste ou à la molette. */
.carousel::-webkit-scrollbar { height: 8px; }

/* Icônes SVG — jamais d'emoji comme icône structurelle.
   Un seul jeu (Lucide), une seule graisse de tracé, trois tailles. */
.ic { width: var(--ic-md); height: var(--ic-md); flex: none; }
.ic-sm { width: var(--ic-sm); height: var(--ic-sm); }
.ic-lg { width: var(--ic-lg); height: var(--ic-lg); }

/* Unité monétaire inline (croquette) — alignée sur la ligne de base.
   Par défaut elle porte l'accent : dans du texte courant et sur les
   prix, la teinte de marque la fait lire comme une unité.
   Dans un conteneur qui a déjà sa propre encre (bouton, badge,
   alerte, message d'erreur), elle s'aligne dessus — sinon un os
   rouge sur un bouton rouge disparaît. */
.cur { display: inline-block; vertical-align: -.15em; color: var(--cur-ink, var(--accent)); }
.btn, .badge, .count, .alert, .err, .bulk,
.seg button.is-on, .pager a.is-on { --cur-ink: currentColor; }

/* Libellé réservé aux lecteurs d'écran */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

img { max-width: 100%; display: block; }

::selection { background: var(--accent); color: var(--accent-ink); }

:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

/* ---------- Typographie ---------- */

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: var(--display-weight);
  letter-spacing: var(--display-track);
  text-transform: var(--display-case);
  margin: 0;
  color: var(--text);
}

/* Les classes .t-h* appliquent l'apparence d'un titre sans en être
   un. À utiliser quand la taille voulue ne correspond pas au niveau
   réel dans le plan du document : le niveau suit la structure, la
   classe suit le rendu. Ne jamais sauter un niveau pour obtenir une
   taille — c'est ce que ces classes évitent. */
h1, .t-h1 { font-size: 2.35rem; line-height: 1.1; }
h2, .t-h2 { font-size: 1.5rem;  line-height: 1.15; }
h3, .t-h3 { font-size: 1.1rem;  line-height: 1.2; }
h4, .t-h4 { font-size: .95rem;  line-height: 1.25; }

.t-h1, .t-h2, .t-h3, .t-h4 {
  font-family: var(--font-display);
  font-weight: var(--display-weight);
  letter-spacing: var(--display-track);
  text-transform: var(--display-case);
  color: var(--text);
  margin: 0;
  display: block;
}

p { margin: 0 0 .8em; }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

.lead { font-size: 1.05rem; color: var(--text-2); max-width: 68ch; }
.muted { color: var(--text-3); }
.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.serif { font-family: var(--font-serif); font-style: italic; }

.num {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  letter-spacing: -.01em;
}

.overline {
  font-family: var(--font-display);
  font-size: .68rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--text-3);
  display: flex;
  align-items: center;
  gap: .6rem;
}
.overline::before {
  content: "";
  width: 18px; height: 2px;
  background: var(--accent);
  flex: none;
}

/* ---------- Layout ---------- */

/* max() avec env() : sur un écran à encoche ou à barre gestuelle, la
   zone sûre peut dépasser la gouttière — on prend la plus grande des
   deux, jamais la somme. Sans ça le contenu passe sous l'encoche en
   paysage. */
.wrap {
  max-width: var(--wrap-max);
  margin-inline: auto;
  padding-left:  max(var(--gutter), env(safe-area-inset-left));
  padding-right: max(var(--gutter), env(safe-area-inset-right));
}

/* Tout élément fixé en bas doit respecter la barre gestuelle. */
.safe-bottom { padding-bottom: max(var(--sp-4), env(safe-area-inset-bottom)); }

.sec { padding: var(--sp-7) 0 var(--sp-2); border-top: 1px solid var(--line); margin-top: var(--sp-7); }
.sec:first-of-type { border-top: 0; margin-top: 0; }
.sec > .overline { margin-bottom: var(--sp-2); }
.sec > h2 { margin-bottom: var(--sp-1); }
.sec > .lead { margin-bottom: var(--sp-5); }

.block { margin: 0 0 var(--sp-6); }
.block > .label {
  font-family: var(--font-mono);
  font-size: .7rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text-3);
  padding-bottom: var(--sp-2);
  margin-bottom: var(--sp-4);
  border-bottom: 1px dashed var(--line);
}

.row { display: flex; flex-wrap: wrap; gap: var(--sp-3); align-items: center; }
.col { display: flex; flex-direction: column; gap: var(--sp-3); }
/* min(Xpx, 100%) et non Xpx seul : sous la largeur plancher, minmax()
   impose la valeur fixe et fait déborder la page horizontalement.
   Le min() laisse la colonne se réduire jusqu'à la largeur disponible. */
.grid  { display: grid; gap: var(--sp-4); grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr)); }
.grid2 { display: grid; gap: var(--sp-4); grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr)); }
.grid4 { display: grid; gap: var(--sp-3); grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr)); }

/* ---------- Surfaces ---------- */

.panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--sp-4);
  position: relative;
  box-shadow: var(--shadow);
}
.panel-flat { box-shadow: none; background: var(--surface-2); }
.panel-hd {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-3); margin: calc(var(--sp-4) * -1) calc(var(--sp-4) * -1) var(--sp-4); padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--line);
  background: var(--surface-hd);
  border-radius: var(--radius) var(--radius) 0 0;
  flex-wrap: wrap;
}
.panel-hd :is(h1,h2,h3,h4,h5,h6) { font-size: .92rem; }

.card {
  display: block;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--sp-4);
  position: relative;
  transition: border-color var(--t-fast), transform var(--t-fast), box-shadow var(--t-fast);
}
.card:hover { border-color: var(--line-hi); transform: translateY(-1px); }
.card.is-active { border-color: var(--accent); }

/* ---------- Boutons ---------- */

.btn {
  --btn-bg: var(--surface-2);
  --btn-fg: var(--text);
  --btn-bd: var(--line-ctl);
  min-height: var(--tap-dense);
  display: inline-flex; align-items: center; justify-content: center; gap: .5em;
  font-family: var(--font-ui);
  font-size: .82rem;
  font-weight: var(--btn-weight);
  letter-spacing: var(--btn-track);
  text-transform: var(--btn-case);
  padding: .62em 1.1em;
  border: 1px solid var(--btn-bd);
  border-radius: var(--radius-sm);
  background: var(--btn-bg);
  color: var(--btn-fg);
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast), transform 80ms;
  text-decoration: none;
}
.btn:hover { filter: brightness(1.18); text-decoration: none; }
.btn:active { transform: translateY(1px); }
.btn[disabled], .btn.is-disabled { opacity: .38; pointer-events: none; }

.btn-primary { --btn-bg: var(--accent); --btn-fg: var(--accent-ink); --btn-bd: var(--accent); }
.btn-ghost   { --btn-bg: transparent; --btn-bd: var(--line-ctl); --btn-fg: var(--text-2); }
.btn-quiet   { --btn-bg: transparent; --btn-bd: transparent; --btn-fg: var(--text-3); }
.btn-danger  { --btn-bg: transparent; --btn-bd: var(--danger); --btn-fg: var(--danger); }
.btn-ok      { --btn-bg: transparent; --btn-bd: var(--ok); --btn-fg: var(--ok); }
.btn-sm { font-size: .72rem; padding: .45em .8em; }
.btn-lg { font-size: .92rem; padding: .8em 1.5em; }
.btn-ico { padding: .55em; aspect-ratio: 1; }
.btn-block { width: 100%; }

.btn-loading { position: relative; color: transparent; pointer-events: none; }
.btn-loading::after {
  content: ""; position: absolute; width: 13px; height: 13px;
  border: 2px solid currentColor; border-top-color: transparent;
  border-radius: 50%; animation: spin .7s linear infinite;
  color: var(--accent-ink);
}
@keyframes spin { to { transform: rotate(360deg); } }

.btn-group { display: inline-flex; }
/* -1px : chevauchement volontaire des bordures pour n'en afficher
   qu'une entre deux boutons. Ce n'est pas de l'espacement. */
.btn-group .btn { border-radius: 0; margin-left: -1px; }
.btn-group .btn:first-child { border-radius: var(--radius-sm) 0 0 var(--radius-sm); margin-left: 0; }
.btn-group .btn:last-child  { border-radius: 0 var(--radius-sm) var(--radius-sm) 0; }

/* ---------- Segmented ---------- */

.seg {
  display: inline-flex; gap: var(--sp-1); padding: var(--sp-1);
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}
.seg button {
  font-family: var(--font-ui); font-size: .76rem; font-weight: 600;
  letter-spacing: var(--btn-track); text-transform: var(--btn-case);
  padding: .42em .95em; border: 0; background: transparent;
  color: var(--text-3); cursor: pointer;
  border-radius: calc(var(--radius-sm) - 2px);
}
.seg button.is-on { background: var(--accent); color: var(--accent-ink); }

/* ---------- Formulaires ---------- */

.field { display: flex; flex-direction: column; gap: var(--sp-1); }
.field > label, .field > .lbl {
  font-family: var(--font-ui);
  font-size: .7rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--text-3); font-weight: 600;
}
.help { font-size: .76rem; color: var(--text-3); }

/* ---------- États de validation ----------
   Trois niveaux, jamais portés par la seule couleur : chaque message
   est précédé d'une icône et reste lisible sans distinction de teinte.
   Le message se place SOUS le champ concerné, jamais uniquement en
   haut du formulaire. */
.err, .okmsg, .warnmsg {
  display: flex; align-items: flex-start; gap: .4em;
  font-size: .76rem;
}
.err     { color: var(--danger); }
.okmsg   { color: var(--ok); }
.warnmsg { color: var(--warn); }
.err .ic, .okmsg .ic, .warnmsg .ic { margin-top: .15em; }

.field.is-err  .input, .field.is-err  .select, .field.is-err  .textarea { border-color: var(--danger); }
.field.is-ok   .input, .field.is-ok   .select, .field.is-ok   .textarea { border-color: var(--ok); }
.field.is-warn .input, .field.is-warn .select, .field.is-warn .textarea { border-color: var(--warn); }

/* Astérisque d'obligation — doublé d'un libellé textuel en légende de
   formulaire, l'astérisque seul n'est pas compris par tout le monde. */
.req { color: var(--danger); margin-left: .2em; }

/* Lecture seule ≠ désactivé. Un champ en lecture seule reste lisible et
   sélectionnable : il ne doit pas être grisé comme un champ inactif. */
.input[readonly], .textarea[readonly] {
  background: var(--surface-2); border-style: dashed; color: var(--text-2);
}
.input[disabled], .select[disabled], .textarea[disabled] {
  opacity: .45; cursor: not-allowed;
}
.check input[disabled], .toggle input[disabled] { opacity: .45; cursor: not-allowed; }

/* Regroupement sémantique — le lecteur d'écran annonce la légende
   avant chaque champ du groupe. */
fieldset.group {
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: var(--sp-4); margin: 0;
}
fieldset.group > legend {
  padding: 0 var(--sp-2); font-family: var(--font-ui);
  font-size: .7rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--accent);
}

/* Résumé d'erreurs — obligatoire dès qu'un envoi en renvoie plusieurs.
   Chaque entrée est un lien ancré vers le champ fautif. */
.errsum {
  border: 1px solid var(--danger); border-left-width: 3px;
  background: var(--danger-soft); border-radius: var(--radius);
  padding: var(--sp-3) var(--sp-4);
}
.errsum h3 { font-size: .9rem; color: var(--danger); margin-bottom: var(--sp-2); }
.errsum ol { margin: 0; padding-left: 1.2em; font-size: .84rem; }
.errsum li { padding: .15em 0; }
.errsum a { color: var(--danger); text-decoration: underline; }

.input, .select, .textarea {
  font-family: var(--font-body); font-size: 1rem;
  width: 100%;
  min-height: var(--tap);
  padding: .6em .85em;
  background: var(--input-bg);
  color: var(--text);
  border: 1px solid var(--line-ctl);
  border-radius: var(--radius-sm);
  transition: border-color var(--t-fast), background var(--t-fast);
}
.input::placeholder, .textarea::placeholder { color: var(--text-4); }
.input:focus, .select:focus, .textarea:focus {
  /* Surtout PAS outline:none — le changement de bordure ne suffit pas
     comme indicateur de focus et un utilisateur au clavier perdait
     l'anneau sur chaque champ. L'anneau de :focus-visible reste. */
  border-color: var(--accent); background: var(--input-bg-focus);
}
.textarea { min-height: 96px; resize: vertical; font-family: var(--font-mono); font-size: .82rem; }
.select {
  appearance: none; padding-right: 2.2em;
  background-image: var(--caret); background-repeat: no-repeat;
  background-position: right .8em center;
  /* color-scheme sur l'élément lui-même : dès qu'un background est
     imposé à un <select>, le navigateur rend la liste déroulante avec
     son thème par défaut (clair) et ignore celui de :root. Suit le
     thème actif (sombre par défaut, clair via body.light-theme) plutôt
     que d'être fixé en dur — sinon la liste déroulante resterait
     rendue en clair même en thème sombre. */
  color-scheme: dark;
}
body.light-theme .select { color-scheme: light; }
/* La liste déroulante est peinte par le système : seuls background et
   color des <option> la traversent. Sans ces deux lignes, le panneau
   ressort en blanc avec une sélection bleue, hors identité. */
.select option, .select optgroup {
  background: var(--surface-hi);
  color: var(--text);
}
.select option:checked { background: var(--accent-solid); color: var(--accent-ink); }
.field.is-err .input { border-color: var(--danger); }

.input-ico { position: relative; }
.input-ico .input { padding-left: 2.4em; }
.input-ico > span { position: absolute; left: .8em; top: 50%; transform: translateY(-50%); color: var(--text-3); display: flex; pointer-events: none; }

.search .input { font-size: .95rem; padding: .85em 1em .85em 2.6em; }

/* checkbox / radio */
.check { display: inline-flex; align-items: center; gap: .55em; cursor: pointer; font-size: .87rem; color: var(--text-2); }
.check input { appearance: none; margin: 0; width: 20px; height: 20px; flex: none;
  border: 1px solid var(--line-ctl); background: var(--input-bg); border-radius: var(--radius-xs); cursor: pointer; position: relative; }
.check input[type=radio] { border-radius: 50%; }
.check input:checked { background: var(--accent); border-color: var(--accent); }
.check input:checked::after {
  content: ""; position: absolute; inset: 0; margin: auto;
  width: 5px; height: 9px; border: solid var(--accent-ink);
  border-width: 0 2px 2px 0; transform: rotate(42deg) translate(-1px,-1px);
}
.check input[type=radio]:checked::after { width: 7px; height: 7px; border: 0; border-radius: 50%; background: var(--accent-ink); transform: none; }

/* toggle */
.toggle { display: inline-flex; align-items: center; gap: .6em; cursor: pointer; font-size: .87rem; color: var(--text-2); }
.toggle input { appearance: none; margin: 0; width: 38px; height: 21px; flex: none;
  background: var(--surface-2); border: 1px solid var(--line-ctl); border-radius: 99px; position: relative; cursor: pointer; transition: background var(--t-base), border-color var(--t-base); }
.toggle input::after {
  content: ""; position: absolute; top: 2px; left: 2px; width: 15px; height: 15px;
  border-radius: 50%; background: var(--text-3); transition: transform var(--t-base), background var(--t-base);
}
.toggle input:checked { background: var(--accent); border-color: var(--accent); }
.toggle input:checked::after { transform: translateX(17px); background: var(--accent-ink); }

/* slider */
.range { appearance: none; width: 100%; height: 6px; background: var(--surface-2); border-radius: 99px; border: 1px solid var(--line-ctl); }
.range::-webkit-slider-thumb { appearance: none; width: 15px; height: 15px; border-radius: 50%; background: var(--accent); border: 2px solid var(--bg); cursor: pointer; }
.range::-moz-range-thumb { width: 13px; height: 13px; border-radius: 50%; background: var(--accent); border: 2px solid var(--bg); cursor: pointer; }

/* dropzone */
.drop {
  border: 1px dashed var(--line-ctl); border-radius: var(--radius);
  padding: var(--sp-5); text-align: center; color: var(--text-3); font-size: .85rem;
  background: var(--surface-2);
}
.drop strong { color: var(--accent); font-weight: 600; }

/* ---------- Navigation ---------- */

.topbar {
  display: flex; align-items: center; gap: var(--sp-4);
  padding: var(--sp-3) var(--sp-4);
  background: var(--surface-hd);
  border: 1px solid var(--line);
  border-radius: var(--radius) var(--radius) 0 0;
  flex-wrap: wrap;
}
.brand { display: flex; align-items: center; gap: var(--sp-3); }
.brand .mark {
  width: 40px; height: 40px; flex: none;
  display: grid; place-items: center;
  border: 1px solid var(--accent);
  border-radius: var(--mark-radius);
  background: var(--mark-bg);
  color: var(--accent);
  font-family: var(--font-display); font-weight: 900; font-size: .8rem;
  letter-spacing: .02em; text-indent: .02em; /* compense la chasse finale */
  line-height: 1;
}
.brand .name { font-family: var(--font-display); font-weight: var(--display-weight); letter-spacing: var(--display-track); text-transform: var(--display-case); font-size: 1.02rem; line-height: 1.1; }
.brand .sub { font-size: .68rem; letter-spacing: .16em; text-transform: uppercase; color: var(--text-3); }

.nav {
  display: flex; gap: var(--sp-1); flex-wrap: wrap;
  padding: 0 var(--sp-2);
  background: var(--surface-2);
  border: 1px solid var(--line); border-top: 0;
}
.nav a {
  font-family: var(--font-ui); font-size: .78rem; font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase;
  padding: .85em 1em; color: var(--text-3);
  border-bottom: 2px solid transparent;
  text-decoration: none; display: flex; align-items: center; gap: .45em;
}
.nav a:hover { color: var(--text); text-decoration: none; }
.nav a.is-on { color: var(--accent); border-bottom-color: var(--accent); }

.crumb { display: flex; align-items: center; gap: .5em; font-size: .78rem; color: var(--text-3); }
.crumb a { color: var(--text-3); }
.crumb span::after { content: "/"; margin-left: .5em; opacity: .45; }
.crumb b { color: var(--text); font-weight: 600; }

.tabs { display: flex; gap: var(--sp-5); border-bottom: 1px solid var(--line); }
.tabs button {
  background: none; border: 0; cursor: pointer;
  font-family: var(--font-ui); font-size: .84rem; font-weight: 600;
  color: var(--text-3); padding: 0 0 .7em; margin-bottom: -1px;
  border-bottom: 2px solid transparent;
}
.tabs button.is-on { color: var(--text); border-bottom-color: var(--accent); }

.side { display: flex; flex-direction: column; gap: var(--sp-1); }
.side a {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2);
  padding: .65em .8em; border-radius: var(--radius-sm);
  color: var(--text-2); font-size: .86rem; text-decoration: none;
  border-left: 2px solid transparent;
}
.side a:hover { background: var(--surface-2); text-decoration: none; }
.side a.is-on { background: var(--accent-soft); color: var(--accent); border-left-color: var(--accent); }
.side a i { font-style: normal; font-family: var(--font-mono); font-size: .72rem; color: var(--text-4); }

.pager { display: flex; gap: var(--sp-1); align-items: center; }
.pager a, .pager span {
  min-width: 32px; height: 32px; display: grid; place-items: center;
  font-family: var(--font-mono); font-size: .78rem;
  border: 1px solid var(--line-ctl); border-radius: var(--radius-sm);
  color: var(--text-2); text-decoration: none;
}
.pager a.is-on { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.pager span { border-color: transparent; color: var(--text-3); }

/* ---------- Badges / tags / statuts ---------- */

.badge {
  display: inline-flex; align-items: center; gap: .35em;
  white-space: nowrap;
  font-family: var(--font-ui); font-size: .66rem; font-weight: 700;
  letter-spacing: .09em; text-transform: uppercase;
  padding: .3em .6em; border-radius: var(--radius-xs);
  border: 1px solid var(--line-hi); color: var(--text-2);
  background: var(--surface-2);
}
.badge-accent { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.badge-ok     { border-color: var(--ok);     color: var(--ok);     background: var(--ok-soft); }
.badge-warn   { border-color: var(--warn);   color: var(--warn);   background: var(--warn-soft); }
.badge-danger { border-color: var(--danger); color: var(--danger); background: var(--danger-soft); }
.badge-info   { border-color: var(--info);   color: var(--info);   background: var(--info-soft); }
.badge-solid  { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }

.count {
  min-width: 18px; height: 18px; padding: 0 var(--sp-1);
  display: inline-grid; place-items: center;
  background: var(--danger); color: var(--danger-ink);
  font-family: var(--font-mono); font-size: .66rem; font-weight: 700;
  border-radius: 99px;
}

.dot { width: 8px; height: 8px; border-radius: 50%; background: var(--text-4); flex: none; }
.dot-ok { background: var(--ok); box-shadow: 0 0 0 3px var(--ok-soft); }
.dot-warn { background: var(--warn); box-shadow: 0 0 0 3px var(--warn-soft); }
.dot-off { background: var(--text-4); }
.dot-live { background: var(--danger); box-shadow: 0 0 0 3px var(--danger-soft); animation: pulse 1.8s infinite; }
@keyframes pulse { 50% { opacity: .35; } }

/* échelle de RARETÉ — reprise du système g1→g8 de La Cache.
   Distincte du GRADE A–D, qui est une caractéristique technique. */
.grade {
  white-space: nowrap;
  font-family: var(--font-mono); font-size: .66rem; font-weight: 700;
  letter-spacing: .06em; padding: .25em .5em; border-radius: var(--radius-xs);
  border: 1px solid currentColor;
}
.g1 { color: var(--g1); background: var(--g1-bg); }
.g2 { color: var(--g2); background: var(--g2-bg); }
.g3 { color: var(--g3); background: var(--g3-bg); }
.g4 { color: var(--g4); background: var(--g4-bg); }
.g5 { color: var(--g5); background: var(--g5-bg); }

.avatar {
  width: 30px; height: 30px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--avatar-radius);
  font-family: var(--font-display); font-size: .74rem; font-weight: 700;
  /* line-height: 1 — sinon l'interligne hérité (1,55) crée un demi-blanc
     asymétrique et le glyphe se cale trop haut dans le carré.
     letter-spacing: 0 — une chasse résiduelle ajoute un espace APRÈS la
     dernière lettre, qui décale visuellement le contenu vers la gauche. */
  line-height: 1; letter-spacing: 0;
  background: var(--surface-2); border: 1px solid var(--line-hi); color: var(--text-2);
  overflow: hidden;
}
.avatar-accent { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
.avatar-lg { width: 44px; height: 44px; font-size: .95rem; }
/* Le jeton de dépassement (« +9 ») porte deux caractères : on lui laisse
   la largeur nécessaire plutôt que de rogner le glyphe. */
.avatar-more { width: auto; min-width: 30px; padding: 0 var(--sp-1); }
/* Groupe d'avatars — PAS de recouvrement.
   Le chevauchement est un idiome d'avatars ronds, où il se lit comme
   de la profondeur. Sur des cadres carrés il ne produit que des
   cellules de largeurs inégales : les éléments recouverts n'ont plus
   que 22 px visibles contre 30 au dernier, et aucun réglage de padding
   ne rattrape ça proprement puisque le décalage dépend de l'ordre de
   peinture. Un intervalle régulier donne des cellules identiques, donc
   des glyphes centrés de la même façon partout. */
.stack { display: flex; gap: var(--sp-1); }
.stack .avatar { border-color: var(--line-hi); }

/* ---------- Données ---------- */

.stat {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: var(--sp-3) var(--sp-4);
  display: flex; flex-direction: column; gap: var(--sp-1); position: relative;
}
.stat .k { font-family: var(--font-ui); font-size: .66rem; letter-spacing: .14em; text-transform: uppercase; color: var(--text-3); }
.stat .v { font-family: var(--font-num); font-size: 1.85rem; font-weight: 700; line-height: 1.05; font-variant-numeric: tabular-nums; }
.stat .d { font-size: .74rem; color: var(--text-3); }
.stat-accent .v { color: var(--accent); }
.stat-ok .v { color: var(--ok); }

.kv { display: grid; grid-template-columns: auto 1fr; gap: .45em 1.2em; font-size: .85rem; }
.kv dt { color: var(--text-3); }
.kv dd { margin: 0; text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

table.tbl { width: 100%; border-collapse: collapse; font-size: .84rem; }
table.tbl th {
  text-align: left; font-family: var(--font-ui); font-size: .66rem;
  letter-spacing: .12em; text-transform: uppercase; color: var(--text-3);
  font-weight: 600; padding: .7em .8em; border-bottom: 1px solid var(--line-hi);
  background: var(--surface-hd);
}
table.tbl td { padding: .75em .8em; border-bottom: 1px solid var(--line); color: var(--text-2); }
table.tbl tr:hover td { background: var(--surface-2); }
table.tbl td.n { font-family: var(--font-mono); text-align: right; font-variant-numeric: tabular-nums; color: var(--text); }
/* L'en-tête d'une colonne numérique s'aligne comme ses valeurs —
   sinon le titre et les chiffres partent de deux bords opposés. */
table.tbl th.n { text-align: right; }
.tbl-wrap { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--radius); }

/* ligne de marché — le composant central de La Cache */
.item {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-3);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); transition: border-color .14s;
}
.item:hover { border-color: var(--line-hi); }
.item .ico {
  width: 36px; height: 36px; flex: none; display: grid; place-items: center;
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--radius-sm); color: var(--text-2);
}
/* break-word et NON anywhere : `anywhere` autorise la coupure à
   n'importe quel caractère et modifie en plus le calcul de largeur
   minimale — « Militaire » se coupait en « MILIT / AIRE ».
   `break-word` ne coupe que si le mot ne tient vraiment pas. */
.item .body { flex: 1; min-width: 0; overflow-wrap: break-word; }
.item .ttl { display: flex; align-items: center; gap: .5em; flex-wrap: wrap; font-weight: 600; font-size: .92rem; }
.item .meta { font-size: .74rem; color: var(--text-3); }
.item .price { text-align: right; }
.item .price b { font-family: var(--font-num); font-size: 1.1rem; font-variant-numeric: tabular-nums; }
.item .price small { display: block; font-size: .7rem; color: var(--text-3); }

/* palmarès */
.rank { display: flex; align-items: center; gap: var(--sp-3); padding: .55em 0; border-bottom: 1px solid var(--line); }
.rank:last-child { border-bottom: 0; }
.rank .pos { font-family: var(--font-mono); font-size: .76rem; color: var(--text-3); width: 24px; }
.rank .who { flex: 1; font-size: .88rem; }
.rank .val { font-family: var(--font-num); font-variant-numeric: tabular-nums; font-weight: 700; }
.rank.is-you .who { color: var(--accent); }

/* progression */
.bar { height: 6px; background: var(--surface-2); border: 1px solid var(--line); border-radius: 99px; overflow: hidden; }
.bar > i { display: block; height: 100%; background: var(--accent); }
.bar-ok > i { background: var(--ok); }
.bar-thick { height: 12px; border-radius: var(--radius-xs); }

.meter { display: flex; gap: var(--sp-1); }
.meter i { flex: 1; height: 10px; background: var(--surface-2); border: 1px solid var(--line); }
.meter i.on { background: var(--accent); border-color: var(--accent); }

/* fil d'activité */
.feed { display: flex; flex-direction: column; }
.feed li {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: .7em .2em; border-bottom: 1px solid var(--line);
  font-size: .86rem; color: var(--text-2); list-style: none;
}
.feed li:last-child { border-bottom: 0; }
.feed li b { color: var(--text); font-weight: 600; }
.feed li > .ic { color: var(--text-3); }
.feed li .t { margin-left: auto; font-family: var(--font-mono); font-size: .72rem; color: var(--text-3); white-space: nowrap; }
.feed li.is-new { background: var(--accent-soft); box-shadow: inset 2px 0 0 var(--accent); padding-left: .8em; }
.feed { padding: 0; margin: 0; }

/* timeline */
.tl { list-style: none; padding: 0; margin: 0; border-left: 1px solid var(--line-hi); }
.tl li { position: relative; padding: 0 0 var(--sp-4) var(--sp-5); }
.tl li::before {
  content: ""; position: absolute; left: -5px; top: 5px;
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--bg); border: 2px solid var(--line-hi);
}
.tl li.is-on::before { border-color: var(--accent); background: var(--accent); }
.tl .when { font-family: var(--font-mono); font-size: .72rem; color: var(--text-3); }

/* ---------- Alertes / toasts ---------- */

.alert {
  display: flex; gap: var(--sp-3); align-items: flex-start;
  padding: var(--sp-3) var(--sp-3); border-radius: var(--radius);
  border: 1px solid var(--line-hi);
  border-left: 3px solid var(--accent);
  background: var(--surface-2);
  font-size: .86rem; color: var(--text-2);
}
.alert b { color: var(--text); }
.alert > .ic { margin-top: var(--sp-1); color: var(--accent); }
.alert-ok     { border-left-color: var(--ok);     background: var(--ok-soft); }
.alert-warn   { border-left-color: var(--warn);   background: var(--warn-soft); }
.alert-danger { border-left-color: var(--danger); background: var(--danger-soft); }
.alert-info   { border-left-color: var(--info);   background: var(--info-soft); }
.alert-ok     > .ic { color: var(--ok); }
.alert-warn   > .ic { color: var(--warn); }
.alert-danger > .ic { color: var(--danger); }
.alert-info   > .ic { color: var(--info); }

.toast {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-3); border-radius: var(--radius);
  background: var(--surface-hi); border: 1px solid var(--line-hi);
  box-shadow: var(--shadow-lg); font-size: .86rem; max-width: 380px;
  flex-wrap: wrap;
}
/* Le composant ne se positionne pas lui-même. C'est au conteneur de
   l'application de le faire :
     position: fixed; right: var(--sp-4); bottom: var(--sp-4);
     z-index: var(--z-toast); display: flex; flex-direction: column;   */
/* Le bloc de texte occupe la largeur restante et la fermeture est
   plaquée au bord droit de la carte, quelle que soit la longueur du
   message. min-width:0 autorise la troncature au lieu de déborder. */
.toast > div { flex: 1; min-width: 0; }

/* Pas de cursor:help — le point d'interrogation en bulle bleue est
   un rendu système, non maîtrisable et hors identité. Le soulignement
   pointillé suffit à signaler qu'il y a quelque chose à survoler. */
.tip { position: relative; display: inline-block; border-bottom: 1px dotted var(--text-3); }
/* z-index explicite : une infobulle doit passer au-dessus de tout, y
   compris d'un voisin flex/grid porteur d'un z-index — sur ces
   éléments-là, z-index s'applique même sans position. */
.tip::after {
  content: attr(data-tip); position: absolute; bottom: 130%; left: 50%; transform: translateX(-50%);
  z-index: var(--z-tip);
  background: var(--surface-hi); color: var(--text); border: 1px solid var(--line-hi);
  padding: .45em .7em; border-radius: var(--radius-sm); font-size: .74rem;
  /* Pas de nowrap : une infobulle longue dépasse le viewport sur mobile
     et fait défiler toute la page horizontalement, alors même qu'elle
     est invisible (opacity: 0 occupe quand même la mise en page). */
  width: max-content; max-width: min(260px, 78vw); white-space: normal;
  /* display:none tant qu'elle n'est pas demandée. Un pseudo-élément
     absolu, même à opacity 0, compte dans le débordement de son parent
     positionné et fait défiler la page horizontalement. */
  display: none;
  pointer-events: none; box-shadow: var(--shadow-lg);
}
.tip:hover::after, .tip:focus-visible::after { display: block; }

kbd {
  font-family: var(--font-mono); font-size: .74rem;
  padding: .15em .45em; border: 1px solid var(--line-hi);
  border-bottom-width: 2px; border-radius: var(--radius-xs);
  background: var(--surface-2); color: var(--text-2);
}
pre.code {
  font-family: var(--font-mono); font-size: .78rem; line-height: 1.6;
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--radius); padding: var(--sp-3); overflow-x: auto; margin: 0;
  color: var(--text-2);
}
pre.code .c { color: var(--text-3); }
pre.code .s { color: var(--ok); }
pre.code .k { color: var(--accent); }

/* ---------- Overlays ---------- */

.modal {
  background: var(--surface-hi); border: 1px solid var(--line-hi);
  border-radius: var(--radius); box-shadow: var(--shadow-lg);
  max-width: 440px; position: relative;
}
.modal .hd { padding: var(--sp-4) var(--sp-4); border-bottom: 1px solid var(--line); display: flex; justify-content: space-between; align-items: center; }
.modal .bd { padding: var(--sp-4); font-size: .88rem; color: var(--text-2); }
.modal .ft { padding: var(--sp-3) var(--sp-4); border-top: 1px solid var(--line); display: flex; gap: var(--sp-2); justify-content: flex-end; background: var(--surface-hd); border-radius: 0 0 var(--radius) var(--radius); }

.menu {
  background: var(--surface-hi); border: 1px solid var(--line-hi);
  border-radius: var(--radius-sm); padding: var(--sp-1); min-width: 200px;
  box-shadow: var(--shadow-lg);
}
.menu a {
  display: flex; align-items: center; gap: .6em; justify-content: space-between;
  padding: .5em .65em; border-radius: var(--radius-xs);
  font-size: .85rem; color: var(--text-2); text-decoration: none;
}
.menu a:hover { background: var(--surface-2); color: var(--text); text-decoration: none; }
.menu a.is-danger { color: var(--danger); }
.menu hr { border: 0; border-top: 1px solid var(--line); margin: var(--sp-1) 0; }
.menu .hint { font-family: var(--font-mono); font-size: .7rem; color: var(--text-3); }

.acc { border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.acc details { border-bottom: 1px solid var(--line); }
.acc details:last-child { border-bottom: 0; }
.acc summary {
  padding: .85em 1em; cursor: pointer; font-size: .88rem; font-weight: 600;
  background: var(--surface); list-style: none;
  display: flex; align-items: center; justify-content: space-between;
}
.acc summary::-webkit-details-marker { display: none; }
.acc summary::after { content: "+"; font-family: var(--font-mono); color: var(--text-3); }
.acc details[open] summary::after { content: "−"; color: var(--accent); }
.acc .in { padding: 0 1em 1em; font-size: .85rem; color: var(--text-2); background: var(--surface); }

.empty {
  text-align: center; padding: var(--sp-6) var(--sp-4); color: var(--text-3);
  border: 1px dashed var(--line-hi); border-radius: var(--radius);
  background: var(--surface-2);
}
.empty .big { display: flex; justify-content: center; margin-bottom: var(--sp-2); color: var(--text-3); }
.empty .big .ic-lg { width: 34px; height: 34px; }

.skel { background: var(--surface-2); border-radius: var(--radius-xs); position: relative; overflow: hidden; }
.skel::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, var(--line-hi), transparent);
  animation: shim 1.4s infinite;
}
@keyframes shim { 0% { transform: translateX(-100%); } 100% { transform: translateX(100%); } }


/* ---------- Footer ---------- */

.foot {
  margin-top: var(--sp-7); padding: var(--sp-5) 0 var(--sp-8);
  border-top: 1px solid var(--line);
  display: flex; flex-wrap: wrap; gap: var(--sp-4); justify-content: space-between;
  font-size: .76rem; color: var(--text-3);
}

.hero { padding: var(--sp-7) 0 var(--sp-2); }
.hero .devise { font-family: var(--font-serif); font-style: italic; font-size: 1.05rem; color: var(--text-2); }

.skip {
  position: absolute; left: var(--sp-4); top: -60px; z-index: var(--z-toast);
  padding: var(--sp-2) var(--sp-4);
  background: var(--accent); color: var(--accent-ink);
  font-family: var(--font-ui); font-size: .82rem; font-weight: 700;
  border-radius: var(--radius-sm); text-decoration: none;
  transition: top var(--t-fast) var(--ease-out);
}
.skip:focus { top: var(--sp-2); }

/* ---------- Responsive ---------- */

/* ---------- Cibles tactiles ----------
   Sur pointeur grossier (doigt), tout élément interactif passe à 44px.
   Sur pointeur fin (souris), la densité du dashboard est préservée. */

/* ---------- Infobulle : accessible au clavier, pas seulement au survol ---------- */
.tip:focus-visible::after { opacity: 1; }

/* ---------- Mouvement réduit ----------
   Aucune animation décorative ne subsiste ; seuls les changements
   d'état instantanés restent, pour ne pas casser le retour visuel. */

/* ============================================================
   VOLUME 2 — couverture complète
   Composants absents du premier jet, alignés sur le périmètre
   d'un framework généraliste (Bootstrap 5.3 / Semantic UI) et
   sur les besoins réels visibles dans les outils LDY existants.
   ============================================================ */

/* ---------- Couleurs de données ----------
   DEUX RÉGIMES, selon le nombre de séries :

   1 série   -> var(--accent). Rien à distinguer, donc la couleur de
                marque. Vaut pour les courbes miniatures, les jauges,
                les cartes de densité et les classements.
   2+ séries -> --c1 … --c4, dans l'ordre, jamais recyclées.

   Pourquoi une rampe séparée plutôt que la palette de marque :
   dans 5 des 6 directions, --accent et --danger ont exactement la
   même valeur. Une série teintée en accent serait indiscernable
   d'un état d'erreur sur le même écran. Par ailleurs une palette
   catégorielle doit garantir la séparation de CHAQUE paire, y
   compris en vision daltonienne — une palette de marque est choisie
   pour l'identité, pas pour cette contrainte.

   Les couleurs de statut (--ok / --warn / --danger) restent la
   palette LDY et ne servent jamais de série de données.

   Validée deutan/protan/tritan sur les six surfaces, ΔE 22,5. */
:root {
  --c1: #2583bd;  --c2: #c38610;  --c3: #7b2fec;  --c4: #219a6d;

  /* grades — rampe séquentielle, A le plus clair */
  --gr-a: #eddba1;  --gr-b: #d4ba68;  --gr-c: #b79c48;  --gr-d: #8e7d4b;

  /* classes — catégoriel, aucune valeur commune avec un statut */
  --cls-mil: #9aa870;   /* Militaire   */
  --cls-ind: #e0955a;   /* Industriel  */
  --cls-civ: #7fb0c4;   /* Civil       */
  --cls-stl: #a98fe0;   /* Furtif      */
  --cls-cmp: #43d9a3;   /* Compétition */

  --c-other: #6b6b6b;
}

/* ---------- Groupe de champs (préfixe / suffixe / bouton accolé) ---------- */
.igroup { display: flex; align-items: stretch; }
.igroup > * { border-radius: 0; }
.igroup > :first-child { border-radius: var(--radius-sm) 0 0 var(--radius-sm); }
.igroup > :last-child  { border-radius: 0 var(--radius-sm) var(--radius-sm) 0; }
.igroup > * + * { margin-left: -1px; }
.igroup .input:focus { position: relative; z-index: 1; }
.igroup-addon {
  display: flex; align-items: center; white-space: nowrap;
  padding: 0 .8em; font-family: var(--font-mono); font-size: .82rem;
  color: var(--text-3); background: var(--surface-2);
  border: 1px solid var(--line-ctl);
}

/* ---------- Label flottant ---------- */
.float { position: relative; }
.float .input { padding-top: 1.35em; padding-bottom: .35em; }
.float > label {
  position: absolute; left: .9em; top: .95em;
  font-size: .9rem; color: var(--text-3); pointer-events: none;
  transition: transform var(--t-fast) var(--ease-out), font-size var(--t-fast);
  transform-origin: left top;
}
.float .input:focus + label,
.float .input:not(:placeholder-shown) + label {
  transform: translateY(-.72em) scale(.72); color: var(--accent);
}

/* ---------- Chips / saisie d'étiquettes ---------- */
.chips { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; }
.chip {
  display: inline-flex; align-items: center; gap: .45em;
  padding: .3em .5em .3em .7em;
  font-family: var(--font-mono); font-size: .78rem;
  background: var(--surface-2); border: 1px solid var(--line-ctl);
  border-radius: var(--radius-sm); color: var(--text-2);
}
.chip button {
  display: grid; place-items: center;
  background: none; border: 0; padding: var(--sp-1); cursor: pointer;
  color: var(--text-3); border-radius: var(--radius-xs);
}
.chip button:hover { color: var(--danger); background: var(--danger-soft); }
.chip-accent { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }

/* ---------- Autocomplétion ---------- */
.combo { position: relative; }
.combo-list {
  position: absolute; left: 0; right: 0; top: calc(100% + 4px);
  z-index: var(--z-drop);
  background: var(--surface-hi); border: 1px solid var(--accent);
  border-radius: var(--radius-sm); box-shadow: var(--shadow-lg);
  max-height: 220px; overflow-y: auto; padding: var(--sp-1);
}
.combo-list li {
  list-style: none; padding: .5em .6em; border-radius: var(--radius-xs);
  font-size: .86rem; color: var(--text-2); cursor: pointer;
  display: flex; justify-content: space-between; gap: 1em;
}
.combo-list li:hover, .combo-list li[aria-selected="true"] { background: var(--accent-soft); color: var(--accent); }
/* Le fond surligné éclaircit la ligne : le texte secondaire doit
   remonter d'un cran, sinon il tombe sous le seuil. */
.combo-list li:hover .muted, .combo-list li:hover .mono,
.combo-list li[aria-selected="true"] .muted,
.combo-list li[aria-selected="true"] .mono { color: var(--text-2); }
.combo-list li mark { background: none; color: var(--accent); font-weight: 700; }
.combo-list { margin: 0; }

/* ---------- Liste déroulante habillée ----------
   Un <select> natif ne se style pas : son panneau est peint par le
   système, seuls background et color des <option> le traversent. Dès
   qu'on veut le rendu de la maison (fond sombre, liseré écarlate,
   surlignage de marque), il faut un composant.

   Le <select> natif reste valable — et préférable sur mobile, où le
   sélecteur système est plus confortable. Ce composant est pour les
   écrans denses où la cohérence visuelle prime.

   Contrat d'accessibilité à respecter dans l'implémentation :
     bouton    role="combobox" aria-haspopup="listbox" aria-expanded
     panneau   role="listbox", chaque entrée role="option" aria-selected
     clavier   ↑ ↓ pour parcourir, Entrée valide, Échap ferme et rend
               le focus au bouton
   ------------------------------------------------------------ */
.selectmenu { position: relative; }
.selectmenu > .select {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-2); text-align: left; cursor: pointer;
  font-family: var(--font-body);
}
.selectmenu > .select::after {
  content: ""; width: 11px; height: 7px; flex: none;
  background: var(--caret) no-repeat center / contain;
  transition: transform var(--t-fast);
}
.selectmenu > .select[aria-expanded="true"] { border-color: var(--accent); }
.selectmenu > .select[aria-expanded="true"]::after { transform: rotate(180deg); }
.selectmenu .combo-list[hidden] { display: none; }

/* ---------- Compteur de caractères ---------- */
.counter { display: flex; justify-content: space-between; gap: 1em; font-size: .74rem; color: var(--text-3); }
.counter b { font-family: var(--font-mono); color: var(--text-2); }
.counter.is-over b { color: var(--danger); }

/* ---------- Liste de fichiers en cours d'envoi ---------- */
.filelist { display: flex; flex-direction: column; gap: var(--sp-2); }
.filerow {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface-2); font-size: .84rem;
  flex-wrap: wrap;
}
.filerow .n { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.item .meta, .rank .who, .feed li { overflow-wrap: break-word; }
.filerow .s { font-family: var(--font-mono); font-size: .72rem; color: var(--text-3); }
.filerow .bar { width: 96px; flex: none; }

/* ---------- Assistant par étapes ---------- */
.steps { display: flex; gap: 0; counter-reset: s; }
.steps li { list-style: none; flex: 1; position: relative; padding-top: var(--sp-6); text-align: center; font-size: .8rem; color: var(--text-3); }
.steps li::before {
  counter-increment: s; content: counter(s);
  position: absolute; top: 0; left: 50%; transform: translateX(-50%);
  width: 22px; height: 22px; display: grid; place-items: center;
  font-family: var(--font-mono); font-size: .72rem;
  border: 1px solid var(--line-ctl); border-radius: 50%;
  background: var(--surface); color: var(--text-3); z-index: 1;
}
.steps li::after {
  content: ""; position: absolute; top: 11px; left: -50%; width: 100%;
  height: 1px; background: var(--line-ctl);
}
.steps li:first-child::after { display: none; }
.steps li.is-done::before { border-color: var(--ok); color: var(--ok); }
.steps li.is-on::before { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.steps li.is-on { color: var(--text); font-weight: 600; }

/* ---------- Arborescence ---------- */
.tree, .tree ul { list-style: none; margin: 0; padding: 0; }
.tree ul { margin-left: var(--sp-3); border-left: 1px solid var(--line); padding-left: var(--sp-2); }
.tree li { padding: 1px 0; }
.tree summary, .tree .leaf {
  display: flex; align-items: center; gap: .5em;
  padding: .34em .5em; border-radius: var(--radius-xs);
  font-size: .85rem; color: var(--text-2); cursor: pointer; list-style: none;
}
.tree summary::-webkit-details-marker { display: none; }
.tree summary:hover, .tree .leaf:hover { background: var(--surface-2); color: var(--text); }
.tree summary .ic { color: var(--accent); transition: transform var(--t-fast); }
.tree details[open] > summary .ic { transform: rotate(90deg); }
.tree .meta { margin-left: auto; font-family: var(--font-mono); font-size: .7rem; color: var(--text-3); }

/* ---------- Tiroir latéral ---------- */
.drawer {
  width: 320px; max-width: 100%;
  background: var(--surface-hi); border: 1px solid var(--line-ctl);
  box-shadow: var(--shadow-lg); display: flex; flex-direction: column;
}
.drawer .hd { padding: var(--sp-4); border-bottom: 1px solid var(--line); display: flex; justify-content: space-between; align-items: center; }
.drawer .bd { padding: var(--sp-4); flex: 1; font-size: .88rem; color: var(--text-2); }
.drawer .ft { padding: var(--sp-3) var(--sp-4); border-top: 1px solid var(--line); display: flex; gap: var(--sp-2); }
.scrim { position: fixed; inset: 0; z-index: var(--z-overlay); background: rgba(0,0,0,.62); backdrop-filter: blur(2px); }

/* ---------- Popover ---------- */
.pop {
  position: relative;
  width: 260px; padding: var(--sp-3);
  background: var(--surface-hi); border: 1px solid var(--line-ctl);
  border-radius: var(--radius); box-shadow: var(--shadow-lg);
  font-size: .84rem; color: var(--text-2);
}
.pop .hd { font-family: var(--font-ui); font-size: .74rem; letter-spacing: .1em; text-transform: uppercase; color: var(--accent); margin-bottom: var(--sp-2); }
.pop::after {
  content: ""; position: absolute; top: -6px; left: 24px;
  width: 10px; height: 10px; background: var(--surface-hi);
  border-left: 1px solid var(--line-ctl); border-top: 1px solid var(--line-ctl);
  transform: rotate(45deg);
}

/* ---------- Indicateurs de chargement ---------- */
.spinner {
  width: 20px; height: 20px; flex: none;
  border: 2px solid var(--line-ctl); border-top-color: var(--accent);
  border-radius: 50%; animation: spin .7s linear infinite;
}
.spinner-lg { width: 34px; height: 34px; border-width: 3px; }
.dots { display: inline-flex; gap: var(--sp-1); }
.dots i { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); animation: bounce 1.2s infinite; }
.dots i:nth-child(2) { animation-delay: .15s; }
.dots i:nth-child(3) { animation-delay: .3s; }
@keyframes bounce { 0%,60%,100% { opacity: .25; } 30% { opacity: 1; } }

/* ---------- Bouton scindé ---------- */
.split { display: inline-flex; }
.split .btn:first-child { border-radius: var(--radius-sm) 0 0 var(--radius-sm); }
.split .btn:last-child { border-radius: 0 var(--radius-sm) var(--radius-sm) 0; margin-left: -1px; padding-inline: .6em; }

/* ---------- Bandeau d'annonce ---------- */
.banner {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-4);
  background: var(--accent-soft); border: 1px solid var(--accent);
  border-radius: var(--radius); font-size: .85rem; color: var(--text-2);
  flex-wrap: wrap;
}
.banner .ic { color: var(--accent); }
.banner .btn { margin-left: auto; }

/* ---------- Séparateur titré ---------- */
.divider { display: flex; align-items: center; gap: var(--sp-3); color: var(--text-3); }
.divider::before, .divider::after { content: ""; height: 1px; background: var(--line); flex: 1; }
.divider span { font-family: var(--font-ui); font-size: .68rem; letter-spacing: .16em; text-transform: uppercase; }

/* ---------- Citation / encart de lore ---------- */
.quote {
  margin: 0; padding: var(--sp-3) var(--sp-4);
  border-left: 3px solid var(--accent); background: var(--surface-2);
  font-family: var(--font-serif); font-style: italic; font-size: 1rem; color: var(--text-2);
}
.quote footer { margin-top: var(--sp-2); font-family: var(--font-ui); font-style: normal; font-size: .72rem; letter-spacing: .1em; text-transform: uppercase; color: var(--text-3); }

/* ---------- Score ---------- */
.rating { display: inline-flex; align-items: center; gap: .3em; color: var(--warn); }
.rating .ic { width: 15px; height: 15px; }
.rating .off { color: var(--text-4); }
.rating b { margin-left: .4em; font-family: var(--font-mono); font-size: .8rem; color: var(--text-2); }

/* ---------- État d'erreur ---------- */
.errstate {
  text-align: center; padding: var(--sp-6) var(--sp-4);
  border: 1px solid var(--danger); border-radius: var(--radius);
  background: var(--danger-soft); color: var(--text-2);
}
.errstate .ic-lg { color: var(--danger); width: 32px; height: 32px; margin: 0 auto var(--sp-2); }
.errstate code { font-family: var(--font-mono); font-size: .76rem; color: var(--text-3); }

/* ---------- Barre d'actions groupées ---------- */
.bulk {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  background: var(--accent-soft); border: 1px solid var(--accent);
  border-radius: var(--radius-sm); font-size: .84rem;
  flex-wrap: wrap;
}
.bulk .n { font-family: var(--font-mono); color: var(--accent); font-weight: 700; }
.bulk .sp { margin-left: auto; display: flex; flex-wrap: wrap; gap: var(--sp-2); }

/* ---------- Poignée de glisser-déposer ---------- */
.drag {
  /* --text-3 et non --text-4 : la poignée est un contrôle, elle doit
     rester perceptible (mesuré à 2,89:1 avec --text-4). */
  cursor: grab; color: var(--text-3); display: grid; place-items: center;
  width: 20px; flex: none; letter-spacing: 1px; line-height: .7;
  font-family: var(--font-mono); font-size: .8rem; user-select: none;
}
.drag:active { cursor: grabbing; color: var(--accent); }
.sortable .filerow.is-drag { border-color: var(--accent); background: var(--accent-soft); }

/* ---------- Onglets verticaux ---------- */
.vtabs { display: grid; grid-template-columns: 190px minmax(0, 1fr); gap: var(--sp-5); }
.vtabs .list { display: flex; flex-direction: column; gap: var(--sp-1); border-right: 1px solid var(--line); padding-right: var(--sp-3); }
.vtabs .list button {
  text-align: left; background: none; border: 0; cursor: pointer;
  padding: .55em .7em; border-radius: var(--radius-sm);
  font-family: var(--font-ui); font-size: .84rem; color: var(--text-3);
  border-right: 2px solid transparent; margin-right: -1px;
}
.vtabs .list button:hover { color: var(--text); background: var(--surface-2); }
.vtabs .list button.is-on { color: var(--accent); border-right-color: var(--accent); background: var(--accent-soft); }

/* ---------- Palette de commandes ---------- */
.palette {
  width: 100%; max-width: 460px;
  background: var(--surface-hi); border: 1px solid var(--line-ctl);
  border-radius: var(--radius); box-shadow: var(--shadow-lg); overflow: hidden;
}
.palette .q { display: flex; align-items: center; gap: var(--sp-2); padding: var(--sp-3) var(--sp-4); border-bottom: 1px solid var(--line); }
/* Le champ de la palette n'a pas de bordure propre : l'anneau de focus
   est porté par le conteneur, sinon il n'y en aurait aucun. */
.palette .q:focus-within { box-shadow: inset 0 0 0 2px var(--focus); }
.palette .q input { flex: 1; min-width: 0; background: none; border: 0; outline: none; color: var(--text); font-size: 1rem; font-family: var(--font-body); }
.palette .grp { font-family: var(--font-mono); font-size: .66rem; letter-spacing: .12em; text-transform: uppercase; color: var(--text-3); padding: var(--sp-2) var(--sp-4) var(--sp-1); }
.palette a { display: flex; align-items: center; gap: .6em; padding: .55em var(--sp-4); font-size: .86rem; color: var(--text-2); text-decoration: none; }
.palette a:hover, .palette a.is-on { background: var(--accent-soft); color: var(--accent); text-decoration: none; }
.palette a kbd { margin-left: auto; }

/* ---------- Formulaire d'authentification ---------- */
.auth { max-width: 360px; }
.auth .brand { justify-content: center; margin-bottom: var(--sp-5); }
.auth .col { gap: var(--sp-4); }

/* ---------- Pages d'erreur ----------
   Le code HTTP seul n'aide personne. Chaque page porte trois choses :
   ce qui s'est passé en langage humain, au moins une voie de sortie,
   et une ligne technique horodatée que l'utilisateur peut recopier au
   support. Le code couleur sépare la responsabilité :
     4xx client  -> --warn   (l'utilisateur peut agir)
     5xx serveur -> --danger (il ne peut qu'attendre ou signaler)
     maintenance -> --info   (prévu, borné dans le temps) */

.errpage {
  --err: var(--danger);
  text-align: center; padding: var(--sp-7) var(--sp-4);
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface);
}
.errpage-client { --err: var(--warn); }
.errpage-server { --err: var(--danger); }
.errpage-maint  { --err: var(--info); }

.errpage .code {
  font-family: var(--font-num); font-size: 3.4rem; line-height: 1;
  color: var(--err); letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
}
.errpage .ic-lg { color: var(--err); width: 26px; height: 26px; margin: 0 auto var(--sp-2); }
.errpage h3 { margin: var(--sp-2) 0 var(--sp-1); }
.errpage p { color: var(--text-2); font-size: .88rem; max-width: 44ch; margin: 0 auto var(--sp-4); }
.errpage .acts { display: flex; gap: var(--sp-2); justify-content: center; flex-wrap: wrap; }
.errpage .trace {
  margin-top: var(--sp-4); padding-top: var(--sp-3);
  border-top: 1px dashed var(--line);
  font-family: var(--font-mono); font-size: .7rem; color: var(--text-3);
  display: flex; gap: var(--sp-3); justify-content: center; flex-wrap: wrap;
}

/* Version compacte, pour la grille de référence */
.errpage-sm { padding: var(--sp-4) var(--sp-3); text-align: left; }
.errpage-sm .code { font-size: 1.9rem; }
.errpage-sm p { margin-bottom: var(--sp-3); font-size: .8rem; max-width: none; }
.errpage-sm .acts { justify-content: flex-start; }
.errpage-sm .trace { justify-content: flex-start; }

/* Bandeau de maintenance planifiée, posé au-dessus de l'application */
.maint-bar {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-4);
  background: var(--info-soft); border: 1px solid var(--info);
  border-radius: var(--radius); font-size: .85rem; color: var(--text-2);
  flex-wrap: wrap;
}
.maint-bar .ic { color: var(--info); }
.maint-bar > span { flex: 1; min-width: 0; }

/* ---------- Motif commun des barres à fermeture ----------
   Toute barre horizontale dont le dernier enfant est un bouton le
   plaque à droite. Évite d'avoir à le recorriger à chaque nouveau
   composant de ce type (toast, bandeau, maintenance…). */
.toast > .btn:last-child,
.banner > .btn:last-child,
.maint-bar > .btn:last-child,
.alert > .btn:last-child { margin-left: auto; flex: none; }
.maint-bar .when { font-family: var(--font-mono); color: var(--text); }

/* ---------- Carrousel (défilement par ancrage) ---------- */
.carousel { display: flex; gap: var(--sp-3); overflow-x: auto; scroll-snap-type: x mandatory; padding-bottom: var(--sp-2); }
.carousel > * { scroll-snap-align: start; flex: 0 0 240px; }

/* ---------- Tableaux : variantes ---------- */
table.tbl th.sort { cursor: pointer; user-select: none; }
table.tbl th.sort::after { content: "↕"; margin-left: .4em; opacity: .35; font-size: .9em; }
table.tbl th.sort[aria-sort="ascending"]::after  { content: "↑"; opacity: 1; color: var(--accent); }
table.tbl th.sort[aria-sort="descending"]::after { content: "↓"; opacity: 1; color: var(--accent); }
table.tbl-striped tbody tr:nth-child(odd) td { background: var(--surface-2); }
table.tbl-compact td, table.tbl-compact th { padding: .4em .6em; font-size: .78rem; }
.tbl-sticky { max-height: 260px; overflow: auto; }
.tbl-sticky thead th { position: sticky; top: 0; z-index: 1; }
table.tbl tr.is-sel td { background: var(--accent-soft); }
table.tbl td.pick, table.tbl th.pick { width: 34px; padding-right: 0; }

/* ---------- Graphiques ----------
   Traits fins, grille effacée, extrémités arrondies 4px, écart de
   2px entre segments empilés. Le texte porte des tokens de texte,
   jamais la couleur de série. */
.chart { display: flex; flex-direction: column; gap: var(--sp-3); }
.chart-hd { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3); }
.chart-hd :is(h1,h2,h3,h4,h5,h6) { font-size: .86rem; }
.chart-hd .sub { font-size: .74rem; color: var(--text-3); }

.legend { display: flex; flex-wrap: wrap; gap: var(--sp-3); font-size: .76rem; color: var(--text-2); }
.legend span { display: inline-flex; align-items: center; gap: .45em; }
.legend i { width: 10px; height: 10px; border-radius: 2px; flex: none; }

/* barres verticales */
.bars { display: flex; align-items: flex-end; gap: var(--sp-1); height: 132px; padding-top: var(--sp-3); }
/* column-reverse : la première série du markup se pose en BAS de la
   pile, comme le veut la convention. En column simple elle se
   retrouvait au sommet, ce qui inverse la lecture. */
.bars .col2 { flex: 1; display: flex; flex-direction: column-reverse; justify-content: flex-start; gap: var(--sp-1); height: 100%; }
.bars .col2 > i { display: block; border-radius: 0; }
.bars .col2 > i:last-child { border-radius: 4px 4px 0 0; }
/* L'axe est un frère de .bars, pas un enfant : sélecteur autonome. */
.bars-x { display: flex; gap: var(--sp-1); font-family: var(--font-mono); font-size: .66rem; color: var(--text-3); }
.bars-x span { flex: 1; text-align: center; }

/* barres horizontales — préférées dès que les libellés sont longs */
.hbars { display: flex; flex-direction: column; gap: var(--sp-2); }
.hbar { display: grid; grid-template-columns: 96px minmax(0, 1fr) auto; align-items: center; gap: var(--sp-3); font-size: .8rem; }
.hbar .t { color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hbar .track { height: 14px; background: var(--surface-2); border-radius: 4px; overflow: hidden; }
.hbar .track > i { display: block; height: 100%; border-radius: 4px; }
.hbar .v { font-family: var(--font-mono); font-size: .76rem; color: var(--text-2); }

/* courbe + aire, grille effacée */
.linechart { width: 100%; height: 150px; overflow: visible; }
.linechart .grid { stroke: var(--line); stroke-width: 1; }
.linechart .axis { fill: var(--text-3); font-family: var(--font-mono); font-size: 9px; }
/* Valeur de fin de série : plus lisible que les graduations. */
.linechart .axis-value { fill: var(--text-2); }
.linechart .serie { fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
/* Le rayon est porté par l'attribut r= dans le markup : la propriété
   CSS `r` n'est pas fiable hors Chromium. */
.linechart .pt { stroke: var(--surface); stroke-width: 2; }

/* anneau */
.donut { display: flex; align-items: center; gap: var(--sp-4); }
.donut svg { flex: none; transform: rotate(-90deg); }
.donut circle { fill: none; stroke-width: 14; }

/* courbe miniature dans une tuile */
.spark { width: 100%; height: 30px; overflow: visible; }
.spark path { fill: none; stroke: var(--accent); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.spark .area { fill: var(--accent-soft); stroke: none; }
.stat .delta { display: inline-flex; align-items: center; gap: .25em; font-family: var(--font-mono); font-size: .74rem; }
.stat .delta.up { color: var(--ok); }
.stat .delta.down { color: var(--danger); }

/* cellules de densité */
/* Utilitaires de série — évitent d'écrire var(--c1) en style inline
   dans le markup, et rendent le passage en JSX trivial.

   Chaque .fill-* pose `background` ET `fill` : le premier colore un
   élément HTML (barre, cellule), le second une forme SVG (point,
   secteur d'anneau). Chacun est inerte dans l'autre contexte, donc
   une seule classe suffit pour les deux — sans ça un <circle> ne
   reçoit rien et retombe sur le noir par défaut. */
.fill-c1 { background: var(--c1); fill: var(--c1); }   .stroke-c1 { stroke: var(--c1); }
.fill-c2 { background: var(--c2); fill: var(--c2); }   .stroke-c2 { stroke: var(--c2); }
.fill-c3 { background: var(--c3); fill: var(--c3); }   .stroke-c3 { stroke: var(--c3); }
.fill-c4 { background: var(--c4); fill: var(--c4); }   .stroke-c4 { stroke: var(--c4); }
.fill-accent { background: var(--accent); fill: var(--accent); }
.stroke-accent { stroke: var(--accent); }

/* ---------- Taxonomie : marqueurs ---------- */

/* Taille — information dimensionnelle, sans couleur. */
.size {
  white-space: nowrap;
  font-family: var(--font-mono); font-size: .66rem; font-weight: 700;
  letter-spacing: .06em; padding: .25em .5em;
  border: 1px solid var(--line-ctl); border-radius: var(--radius-xs);
  color: var(--text-2); background: var(--surface-2);
}

/* Grade — même géométrie que .size, la teinte porte l'ordre. */
.gr {
  white-space: nowrap;
  font-family: var(--font-mono); font-size: .66rem; font-weight: 700;
  letter-spacing: .06em; padding: .25em .5em;
  border: 1px solid currentColor; border-radius: var(--radius-xs);
  background: var(--surface-2);
}
.gr-a { color: var(--gr-a); }
.gr-b { color: var(--gr-b); }
.gr-c { color: var(--gr-c); }
.gr-d { color: var(--gr-d); }

/* Classe — catégoriel. Le libellé est toujours écrit en toutes lettres :
   la couleur seule ne doit jamais porter l'information. */
.cls {
  white-space: nowrap;
  font-family: var(--font-ui); font-size: .66rem; font-weight: 700;
  letter-spacing: .09em; text-transform: uppercase;
  padding: .3em .6em; border-radius: var(--radius-xs);
  border: 1px solid currentColor; background: var(--surface-2);
}
.cls-mil { color: var(--cls-mil); }
.cls-ind { color: var(--cls-ind); }
.cls-civ { color: var(--cls-civ); }
.cls-stl { color: var(--cls-stl); }
.cls-cmp { color: var(--cls-cmp); }

/* Assemblage canonique d'un composant : taille, grade, classe, dans
   cet ordre, séparés d'un demi-espace. */
.spec { display: inline-flex; align-items: center; gap: var(--sp-1); flex-wrap: wrap; }

.heat { display: grid; grid-template-columns: repeat(14, minmax(0, 1fr)); gap: var(--sp-1); }
.heat i { aspect-ratio: 1; border-radius: 2px; background: var(--surface-2); }

/* ============================================================
   F — PASSERELLE  ·  « Poste de commandement »
   ------------------------------------------------------------
   Synthèse demandée le 2026-08-01 : la console du Vanagandr (D)
   sous la signalétique militaire du vaisseau (A). Le corps de
   l'interface reste monospace, aligné sur une grille de
   caractères ; seuls les titres passent en Orbitron capitales,
   comme les inscriptions peintes sur la coque. Une passerelle,
   c'est exactement ça : des écrans de terminal encastrés dans du
   métal marqué au pochoir.

   Deux rôles distincts pour le rouge — c'est la règle centrale
   de cette direction :
     --accent        #ff3333  tout ce qui est TRACÉ (texte,
                              bordure, curseur) — 5,44:1 sur le fond
     --accent-solid  #ee0000  toute SURFACE PLEINE portant du
                              texte blanc — 4,53:1 avec #fff
   Le blanc ne passe pas sur le rouge vif (3,64:1). Utiliser
   --accent comme fond de bouton casserait la lisibilité.

   Fonts   Orbitron (titres) · JetBrains Mono (tout le reste)
   Usage   admin, consoles, tableaux de bord de commandement
   ============================================================ */

:root {
  --font-display: "Orbitron", sans-serif;      /* titres — repris de A */
  --font-body:    "Inter", system-ui, sans-serif;
  --font-ui:      "JetBrains Mono", ui-monospace, monospace;
  --font-mono:    "JetBrains Mono", ui-monospace, monospace;
  --font-serif:   "IBM Plex Serif", Georgia, serif;
  --font-num:     "JetBrains Mono", monospace;

  --display-weight: 800;      /* valeurs de A */
  --display-track: .06em;
  --display-case: uppercase;
  --btn-weight: 600;          /* valeurs de D */
  --btn-track: .04em;
  --btn-case: none;

  --radius: 0px;
  --radius-sm: 0px;
  --radius-xs: 0px;
  --mark-radius: 0px;
  --avatar-radius: 0px;

  /* ---------- Palette SOMBRE — direction "F · Passerelle" d'origine,
     désormais le thème PAR DÉFAUT du site (voir la conversation).
     ---------- */
  --bg:          #050505;
  --surface:     #0a0a0a;
  --surface-2:   #101010;
  --surface-hd:  #0d0d0d;
  --surface-hi:  #151515;

  --line:     #1b1b1b;
  --line-hi:  #2b2b2b;
  --line-ctl: #636363;   /* bordure de contrôle · 3:1 */

  --text:    #d6d3cc;
  --text-2:  #9a968d;
  --text-3:  #827e75;
  --text-4:  #605d55;

  --accent:       #ff3333;   /* tracé — 5,44:1 sur --surface */
  --accent-solid: #ee0000;   /* surface pleine — 4,53:1 avec du blanc */
  --accent-ink:   #ffffff;   /* texte sur surface pleine */
  --accent-soft:  rgba(255,51,51,.10);
  --focus:        #ff3333;

  --ok:     #7ec86a;  --ok-soft:     rgba(126,200,106,.09);
  --warn:   #d9a441;  --warn-soft:   rgba(217,164,65,.09);
  --danger: #ff3333;  --danger-soft: rgba(255,51,51,.10); --danger-ink: #ffffff;
  --info:   #6fa8c7;  --info-soft:   rgba(111,168,199,.09);

  --g1: #807c73; --g1-bg: #111111;
  --g2: #9a968d; --g2-bg: #161616;
  --g3: #6fa8c7; --g3-bg: #0c1519;
  --g4: #d9a441; --g4-bg: #1a1408;
  --g5: #ff3333; --g5-bg: #200606;

  --mark-bg: #140000;

  --input-bg: #000000;
  --input-bg-focus: #0a0000;

  --shadow: none;
  --shadow-lg: 0 0 0 1px var(--accent), 0 20px 50px rgba(0,0,0,.9);

  --caret: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='11' height='7' viewBox='0 0 11 7'><path d='M1 1l4.5 5L10 1' stroke='%23ff3333' stroke-width='1.4' fill='none'/></svg>");

  color-scheme: dark;
}

/* ---------- Palette CLAIRE (créée par nous, absente du fichier
   fourni) — activée en option via body.light-theme (voir
   /toggle-theme). Seules les couleurs changent ; police, formes et
   rythme d'espacement restent les mêmes dans les deux thèmes — c'est
   la typographie et les angles carrés qui font l'identité Passerelle,
   pas la seule teinte. Mêmes ratios de contraste WCAG que l'original,
   vérifiés par calcul (formule de luminance relative), pas estimés à
   l'œil :
     --text sur --bg      : 16.28:1 (original sombre : 15,79:1 équiv.)
     --text-3 sur --bg     : 5.24:1  (seuil AA texte normal : 4.5:1)
     --accent sur --bg     : 5.41:1  (cible originale : 5,44:1)
     --accent-solid + blanc: 5.89:1  (cible originale : 4,53:1)
   Un seul rouge (#cc0000) suffit ici pour les deux rôles — contrairement
   au sombre où --accent et --accent-solid doivent différer (le rouge vif
   sur blanc tombe sous les 4,5:1, précision dans le fichier original). ---------- */
body.light-theme {
  --bg:          #f7f5f2;
  --surface:     #ffffff;
  --surface-2:   #f0eee9;
  --surface-hd:  #f4f2ee;
  --surface-hi:  #e8e5df;

  --line:     #ded9d0;
  --line-hi:  #c9c3b7;
  --line-ctl: #8a8478;

  --text:    #1a1815;
  --text-2:  #4a4640;
  --text-3:  #6b665d;
  --text-4:  #8a8478;

  --accent:       #cc0000;
  --accent-solid: #cc0000;
  --accent-ink:   #ffffff;
  --accent-soft:  rgba(204,0,0,.08);
  --focus:        #cc0000;

  --ok:     #2e7d32;  --ok-soft:     rgba(46,125,50,.08);
  --warn:   #a06b00;  --warn-soft:   rgba(160,107,0,.08);
  --danger: #cc0000;  --danger-soft: rgba(204,0,0,.08); --danger-ink: #ffffff;
  --info:   #1a6a8a;  --info-soft:   rgba(26,106,138,.08);

  --g1: #6b665d; --g1-bg: #f0eee9;
  --g2: #4a4640; --g2-bg: #e8e5df;
  --g3: #1a6a8a; --g3-bg: #e6f0f4;
  --g4: #a06b00; --g4-bg: #f7ecd9;
  --g5: #cc0000; --g5-bg: #fbe6e6;

  --mark-bg: #fbe6e6;
  --input-bg: #ffffff;
  --input-bg-focus: #fff5f5;

  --shadow: 0 1px 2px rgba(0,0,0,.06);
  --shadow-lg: 0 0 0 1px var(--accent), 0 12px 30px rgba(0,0,0,.12);

  --caret: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='11' height='7' viewBox='0 0 11 7'><path d='M1 1l4.5 5L10 1' stroke='%23cc0000' stroke-width='1.4' fill='none'/></svg>");

  color-scheme: light;
}

/* ============================================================
   SURFACES PLEINES — toutes basculent sur --accent-solid pour
   que le texte blanc reste lisible. Ne jamais les repasser sur
   --accent : le contraste tomberait à 3,64:1.
   ============================================================ */
.btn-primary {
  --btn-bg: var(--accent-solid);
  --btn-bd: var(--accent-solid);
  --btn-fg: var(--accent-ink);
}
/* Survol dérivé par filtre, jamais une couleur écrite en dur : sinon
   le bouton cesse de suivre --accent-solid dès qu'on change la
   palette. */
.btn-primary:hover { filter: brightness(1.16); }
.badge-solid,
.count,
.seg button.is-on {
  background: var(--accent-solid); border-color: var(--accent-solid); color: var(--accent-ink);
}
.pager a.is-on {
  background: var(--accent-solid); border-color: var(--accent-solid); color: var(--accent-ink);
}
.check input:checked,
.toggle input:checked {
  background: var(--accent-solid); border-color: var(--accent-solid);
}
.skip { background: var(--accent-solid); color: var(--accent-ink); }
::selection { background: var(--accent-solid); color: var(--accent-ink); }

/* Le bouton de danger reste en contour : une action destructive ne
   doit pas ressembler à l'action principale. */
.btn-danger { --btn-fg: var(--accent); }

/* ============================================================
   TITRES — signalétique de coque (repris de A)
   Orbitron capitales sur un corps entièrement monospace.
   ============================================================ */
h1 { font-size: 2.1rem; }
h2 { font-size: 1.35rem; }
h3 { font-size: 1rem; }
.brand .name { font-size: .95rem; }

/* Le préfixe de prompt et le curseur restent en mono — mêlés à de
   l'Orbitron ils perdraient leur lecture de terminal. */
h2::before,
h3::before {
  content: "> "; color: var(--accent);
  font-family: var(--font-mono); font-weight: 400;
  letter-spacing: 0;
}
.panel-hd h3::before { content: "· "; }
h1::after {
  content: "_"; color: var(--accent);
  font-family: var(--font-mono); font-weight: 400; letter-spacing: 0;
  animation: blink 1.1s step-end infinite;
}

/* Filet écarlate sous les en-têtes — signature de A conservée */
.panel-hd { border-bottom-color: var(--accent); }
.topbar   { border-bottom: 1px solid var(--accent); }
.overline::before { width: 26px; height: 3px; }

/* ============================================================
   CORPS DE TERMINAL — hérité de D
   ============================================================ */
.btn-ghost::before { content: "["; margin-right: -.15em; opacity: .5; }
.btn-ghost::after  { content: "]"; margin-left: -.15em; opacity: .5; }

.input,
.select,
.textarea {
  font-family: var(--font-mono); font-size: .9rem;
  border-width: 0 0 1px 0; padding-left: .2em; padding-right: .2em;
  background: transparent;
}
.input:focus,
.select:focus,
.textarea:focus { background: transparent; border-bottom-color: var(--accent); }
/* Champ sans boîte : l'icône se cale sur le bord gauche réel du champ,
   et le retrait du texte se calcule sur la largeur d'icône, pas en em
   (les em suivent la taille de police du champ, pas celle de l'icône —
   c'est ce décalage qui faisait chevaucher la loupe et le texte). */
.input-ico > span { left: 0; }
.input-ico .input { padding-left: calc(var(--ic-md) + var(--sp-2)); }
.search .input { padding-left: calc(var(--ic-md) + var(--sp-3)); }
.select { padding-right: 1.6em; }

table.tbl { font-family: var(--font-mono); font-size: .78rem; }
table.tbl th {
  font-family: var(--font-mono); color: var(--accent);
  border-bottom-width: 2px; border-bottom-color: var(--accent);
}
table.tbl td { border-bottom-style: dotted; }

.stat .v { font-size: 1.6rem; font-weight: 700; letter-spacing: -.04em; }
.stat .k { font-family: var(--font-mono); }
.stat .k::before { content: "// "; color: var(--text-4); }

.nav a.is-on { background: var(--accent-soft); color: var(--accent); }
.nav a::before { content: "/"; color: var(--text-4); margin-right: -.15em; }

.feed li { font-family: var(--font-mono); font-size: .78rem; }

.panel,
.card,
.stat { border-color: var(--line-hi); }

/* ---------- Contrastes forcés (Windows, mode contraste élevé) ----------
   Le système remplace toutes les couleurs. On rétablit uniquement ce
   qui disparaîtrait : les bordures des surfaces dessinées en fond, et
   la visibilité du focus. */

/* ---------- Impression ----------
   Les fonds sombres ne s'impriment pas : on repasse en encre noire sur
   blanc et on supprime le chrome qui n'a aucun sens sur papier. */

/* ============================================================
   ADAPTATIONS CONDITIONNELLES  —  toujours après les composants
   Placées ici et non au fil du fichier : une règle de thème
   déclarée plus bas écraserait sinon la requête média.
   C'est exactement ce qui se passait — h1 restait à 2,1rem sur
   mobile parce que la surcharge de direction suivait le @media.
   ============================================================ */

@media (max-width: 1024px) { :root { --gutter: var(--sp-5); } }
@media (max-width: 640px)  { :root { --gutter: var(--sp-4); } }
@media (min-width: 1025px) { :root { --gutter: var(--sp-6); } }

@media (max-width: 640px) {
  h1 { font-size: 1.7rem; }
  h2 { font-size: 1.3rem; }
  /* la gouttière descend à --sp-4, jamais en dessous */
  /* Infobulle : ancrage à gauche plutôt qu'au centre, sinon elle sort
     de l'écran quand son déclencheur est près du bord droit. */
  .tip::after { left: 0; transform: none; max-width: calc(100vw - 2 * var(--gutter)); }
  .sec { padding-top: var(--sp-6); margin-top: var(--sp-6); }
  .panel { padding: var(--sp-3); }
  .panel-hd { margin: calc(var(--sp-3) * -1) calc(var(--sp-3) * -1) var(--sp-3); padding: var(--sp-3); }
  /* Ligne de marché : le prix et l'action passent sous le titre plutôt
     que de comprimer la colonne centrale jusqu'à la casser. */
  .item { flex-wrap: wrap; row-gap: var(--sp-2); }
  .item .body { flex: 1 1 100%; order: 2; }
  .item .ico { order: 1; }
  .item .price { order: 3; text-align: left; margin-left: auto; }
  .item > .btn { order: 4; }
  /* Les tailles de titre de la direction sont plus généreuses que la
     base : elles doivent redescendre elles aussi. */
  h1, .t-h1 { font-size: 1.6rem; }
  h2, .t-h2 { font-size: 1.25rem; }
  h3, .t-h3 { font-size: 1rem; }
  /* Échelle typographique : le libellé de gauche passe au-dessus. */
  /* Colonnes fixes qui ne tiennent plus. */
  .hbar { grid-template-columns: 1fr auto; }
  .hbar .t { grid-column: 1 / -1; }
  .steps { flex-wrap: wrap; gap: var(--sp-3); }
  .steps li { flex: 1 0 40%; }
  .kv { grid-template-columns: 1fr; }
  .kv dd { text-align: left; }
  .tabs { overflow-x: auto; }
}

@media (max-width: 640px) { .vtabs { grid-template-columns: 1fr; } .vtabs .list { border-right: 0; padding-right: 0; } }

@media (pointer: coarse) {
  /* .tree summary et .tree .leaf : leur remplissage de .34em donne
     30 px, sous le seuil tactile. Une arborescence se parcourt au
     doigt autant qu'à la souris — les deux doivent monter à 44 px. */
  .btn, .seg button, .nav a, .side a, .pager a, .tabs button,
  .vtabs .list button, .menu a, .combo-list li, .acc summary,
  .tree summary, .tree .leaf,
  .filerow, .rank {
    min-height: var(--tap);
  }
  /* Croix d'étiquette : 18 px de visuel, mais la zone tactile est
     étendue par un pseudo-élément. Agrandir le bouton lui-même
     déformerait l'étiquette ; la cible, elle, doit faire 44 px.
     (Apple HIG : étendre la zone au-delà des limites visibles.) */
  .chip button { position: relative; }
  .chip button::after {
    content: ""; position: absolute;
    inset: calc((var(--tap) - 18px) / -2);
  }
  /* La poignée de glissement est saisie au doigt : même traitement.
     Son glyphe ne fait que ~9 px de haut, l'extension doit donc être
     large pour atteindre les 44 px. */
  .drag { position: relative; }
  .drag::after { content: ""; position: absolute; inset: -18px; }
  .btn-sm { min-height: var(--tap); padding-inline: var(--sp-3); }
  .btn-ico { min-width: var(--tap); }
  .check input, .toggle input { transform: scale(1.15); transform-origin: left center; }
  .check, .toggle { min-height: var(--tap); }
  .menu a { min-height: var(--tap); }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
  .dot-live, .skel::after { animation: none !important; }
  .card:hover { transform: none; }
  .btn:active { transform: none; }
}

@media (forced-colors: active) {
  .btn, .card, .panel, .stat, .item, .badge, .chip,
  .input, .select, .textarea, .modal, .drawer, .pop, .menu {
    border: 1px solid ButtonBorder;
  }
  .btn-primary { forced-color-adjust: none; background: Highlight; color: HighlightText; }
  :focus-visible { outline: 2px solid Highlight; outline-offset: 2px; }
  .bar > i, .meter i.on, .hbar .track > i { forced-color-adjust: none; background: Highlight; }
  .dot, .skel::after { forced-color-adjust: none; }
}

@media print {
  :root {
    --bg: #fff; --surface: #fff; --surface-2: #fff;
    --surface-hd: #fff; --surface-hi: #fff;
    --text: #000; --text-2: #222; --text-3: #444; --text-4: #666;
    --line: #bbb; --line-hi: #999; --line-ctl: #666;
    --shadow: none; --shadow-lg: none;
  }
  body { background: #fff; color: #000; }
  .skip, .toast, .drawer, .scrim, .palette { display: none !important; }
  .panel, .card, .stat, .item { break-inside: avoid; border: 1px solid #999; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: .8em; color: #555; }
  .sec { break-before: auto; }
}

/* ============================================================
   UTILITAIRES  —  bloc final du fichier
   Espacement sur l'échelle --sp-*, alignement, couleur sémantique.
   Elles existent pour qu'aucun composant n'ait besoin d'un
   attribut style=.

   POURQUOI LES SÉLECTEURS SONT DOUBLÉS (.mt-3.mt-3)
   Une utilitaire doit l'emporter sur la règle du composant qu'elle
   corrige. `.ta-left` valait (0,1,0) et perdait contre `.kv dd`
   (0,1,1) : la classe était bien posée dans le markup et ne faisait
   rien, sans le moindre message d'erreur. Doubler la classe porte la
   spécificité à (0,2,0) sans toucher au markup — on écrit toujours
   class="ta-left". Le bloc reste en dernier pour l'emporter aussi à
   spécificité égale.
   ============================================================ */

/* Espacement — uniquement les pas de l'échelle */
.m-0.m-0 { margin: 0; }
.mt-0.mt-0 { margin-top: 0; }.mb-0.mb-0 { margin-bottom: 0; }
.mt-1.mt-1 { margin-top: var(--sp-1); }.mb-1.mb-1 { margin-bottom: var(--sp-1); }
.mt-2.mt-2 { margin-top: var(--sp-2); }.mb-2.mb-2 { margin-bottom: var(--sp-2); }
.mt-3.mt-3 { margin-top: var(--sp-3); }.mb-3.mb-3 { margin-bottom: var(--sp-3); }
.mt-4.mt-4 { margin-top: var(--sp-4); }.mb-4.mb-4 { margin-bottom: var(--sp-4); }
.mt-5.mt-5 { margin-top: var(--sp-5); }.mb-5.mb-5 { margin-bottom: var(--sp-5); }
.mt-6.mt-6 { margin-top: var(--sp-6); }.mb-6.mb-6 { margin-bottom: var(--sp-6); }
.mt-auto.mt-auto { margin-top: auto; }
.ml-auto.ml-auto { margin-left: auto; }

/* Flex */
.flex-1.flex-1 { flex: 1; min-width: 0; }
.between.between { justify-content: space-between; }
.center.center { justify-content: center; }
.start.start { align-items: flex-start; }
.gap-1.gap-1 { gap: var(--sp-1); }
.gap-2.gap-2 { gap: var(--sp-2); }
.gap-3.gap-3 { gap: var(--sp-3); }
.gap-5.gap-5 { gap: var(--sp-5); }

/* Grille */
.span-2.span-2 { grid-column: span 2; }

/* Texte */
.ta-left.ta-left { text-align: left; }
.ta-center.ta-center { text-align: center; }
.ta-right.ta-right { text-align: right; }
.text-accent.text-accent { color: var(--accent); }
.text-ok.text-ok { color: var(--ok); }
.text-warn.text-warn { color: var(--warn); }
.text-danger.text-danger { color: var(--danger); }
.text-info.text-info { color: var(--info); }

/* Séparateur horizontal — remplace les <hr> habillés à la main */
.rule.rule { border: 0; border-top: 1px solid var(--line); margin: var(--sp-4) 0; }
.rule-lg.rule-lg { margin: var(--sp-5) 0; }

/* ---------- Valeurs pilotées par la donnée ----------
   Une hauteur de barre ou une opacité de cellule est une DONNÉE, pas
   du style : elle ne peut pas vivre dans une feuille. Elle passe donc
   par une seule custom property, --v, lue par la règle du composant.
   Le markup ne porte jamais de déclaration CSS, seulement une valeur.
     <i class="fill-c1" style="--v:62%"></i>                          */
.bars .col2 > i { height: var(--v, 0%); }
.bar > i, .hbar .track > i { width: var(--v, 0%); }
.heat i { opacity: var(--v, 1); }
.spark.spark, .linechart.linechart { --v: initial; }

/* ============================================================
   SURCHARGES RESPONSIVES DES UTILITAIRES
   Doivent suivre le bloc utilitaires : à spécificité égale, c'est
   la dernière règle qui gagne. Placée plus haut, cette neutralisation
   était écrasée par l'utilitaire elle-même — la grille continuait de
   déborder en mono-colonne.
   ============================================================ */
@media (max-width: 640px) {
  /* `grid-column: span 2` crée une deuxième colonne implicite quand la
     grille n'en a plus qu'une, et fait déborder la page. */
  .span-2.span-2 { grid-column: auto; }
}

/* ============================================================
   AJOUTS SHADOW'S CODEX — positionnement du menu déroulant de navigation.
   ldy.css fournit .menu (habillage visuel du contenu) mais pas le
   mécanisme d'affichage/masquage au survol pour un déclencheur dans
   .nav — ajouté ici en réutilisant les tokens existants, rien de
   nouveau visuellement.
   ============================================================ */
.nav-dropdown { position: relative; }
.nav-dropdown-menu {
  display: none;
  position: absolute; top: 100%; left: 0;
  min-width: 220px; z-index: var(--z-drop);
}
.nav-dropdown:hover .nav-dropdown-menu,
.nav-dropdown:focus-within .nav-dropdown-menu { display: block; }

/* .seg ne stylait que <button> (bascule d'état côté JS) — étendu ici
   aux <a> pour nos cas de vraie navigation (changer d'URL), même
   rendu visuel, sans dupliquer les règles de couleur/état. */
.seg a {
  font-family: var(--font-ui); font-size: .76rem; font-weight: 600;
  letter-spacing: var(--btn-track); text-transform: var(--btn-case);
  padding: .42em .95em; border: 0; background: transparent;
  color: var(--text-3); text-decoration: none;
  border-radius: calc(var(--radius-sm) - 2px);
}
.seg a:hover { text-decoration: none; color: var(--text-2); }
.seg a.is-on { background: var(--accent); color: var(--accent-ink); }

/* .tabs ne stylait que <button> -- même raison/même traitement que
   .seg ci-dessus, pour nos onglets de navigation par URL. */
.tabs a {
  font-family: var(--font-ui); font-size: .84rem; font-weight: 600;
  padding: 0 0 .7em; margin-bottom: -1px;
  border: 0; border-bottom: 2px solid transparent; background: transparent;
  color: var(--text-3); text-decoration: none; cursor: pointer;
}
.tabs a:hover { color: var(--text-2); text-decoration: none; }
.tabs a.is-on { color: var(--text); border-bottom-color: var(--accent); }
