/* ════════════════════════════════════════════════════════════════════════════
   gna.css — LA COUCHE DE COMPOSANTS DU FRONT DE GNÁ

   C'est le design system shadcn (jeu de jetons « neutral », en clair) porté en
   CSS nu : ni React, ni Tailwind, ni étape de build. Les gabarits sont des
   templates Go, ils écrivent des CLASSES, pas des utilitaires.

   TROIS RÈGLES QUI TIENNENT TOUT LE FICHIER :

   1. LES JETONS SONT RELEVÉS SUR LA RÉFÉRENCE DU CLIENT, pas inventés. Les
      valeurs du §1 ne se retouchent pas « pour voir » : elles se remesurent.
   2. CLAIR UNIQUEMENT. Il n'y a AUCUN bloc `prefers-color-scheme: dark` dans
      ce fichier, et il ne doit jamais y en avoir : le client l'a explicitement
      refusé. `color-scheme: light` au §2 force aussi les surfaces du navigateur
      (ascenseurs, contrôles natifs) à rester claires sur un poste en thème
      sombre — sans lui, les `<select>` du lundi matin seraient noirs.
   3. UNE VALEUR EN DUR DANS UN COMPOSANT EST UN BOGUE. Tout part de `:root`.

   Sommaire
     §1  jetons
     §2  socle et surfaces du navigateur
     §3  typographie
     §4  mise en page (coque, barre latérale, page)
     §5  boutons
     §6  champs de saisie (input, select, checkbox, switch, segmented)
     §7  pastilles
     §8  tableaux
     §9  arbre repliable (l'écran d'arbitrage)
     §10 cartes, séparateurs, avis, stats, infobulles, icônes
     §11 utilitaires
   ════════════════════════════════════════════════════════════════════════════ */

/* ── Polices ──────────────────────────────────────────────────────────────────
   AUTO-HÉBERGÉES, en woff2 variable. Pas de Google Fonts, pas de CDN : Gná est
   un outil interne qui doit démarrer et s'afficher sans réseau sortant. Les
   deux fichiers sont embarqués dans le binaire (`//go:embed`, statique.go).
   Geist et Geist Mono — SIL OFL 1.1, voir polices/LICENCE-Geist.txt.          */

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

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

/* ══ §1 JETONS ══════════════════════════════════════════════════════════════ */

:root {
  /* Les couleurs, relevées telles quelles sur la référence. */
  --background: #ffffff;
  --foreground: #262626;
  --card: #ffffff;
  --card-foreground: #262626;
  --popover: #ffffff;
  --popover-foreground: #262626;
  --primary: #171717;
  --primary-foreground: #fafafa;
  --secondary: #f5f5f5;
  --secondary-foreground: #262626;
  --muted: #f5f5f5;
  --muted-foreground: #737373;
  --accent: #f5f5f5;
  --accent-foreground: #262626;
  --destructive: #dc2626;
  --border: #e5e5e5;
  --input: #e5e5e5;
  --ring: #a3a3a3;

  --sidebar: #fafafa;
  --sidebar-foreground: #262626;
  --sidebar-border: #e5e5e5;
  --sidebar-accent: #f5f5f5;
  --sidebar-primary: #171717;
  --sidebar-primary-foreground: #fafafa;

  --chart-1: #e76e50;
  --chart-2: #2a9d90;
  --chart-3: #274754;
  --chart-4: #e9c468;
  --chart-5: #f4a462;

  --radius: 0.625rem; /* 10px */
  --shadow: none;

  /* Dérivés d'échelle, exactement comme shadcn les dérive du rayon. */
  --radius-sm: calc(var(--radius) - 4px); /*  6px */
  --radius-md: calc(var(--radius) - 2px); /*  8px — boutons, champs */
  --radius-lg: var(--radius); /* 10px — cartes */
  --radius-xl: calc(var(--radius) + 4px); /* 14px */
  --radius-pilule: 26px;

  /* Deux couleurs de service DÉRIVÉES de la palette, jamais inventées.

     `--succes` n'existe pas dans le jeu shadcn : on la prend sur --chart-2,
     comme demandé. `--anneau-visible` est --ring poussé vers --foreground :
     --ring seul (#a3a3a3) donne 2,3:1 sur blanc, sous le 3:1 exigé d'un
     indicateur de focus. C'est le seul écart assumé avec la référence, et il
     est du côté de l'accessibilité. */
  --succes: var(--chart-2);
  --succes-doux: color-mix(in oklab, var(--chart-2) 12%, var(--background));
  --anneau-visible: color-mix(in oklab, var(--ring) 55%, var(--foreground));

  /* Métriques relevées sur la référence. */
  --h-controle: 32px; /* bouton, champ, cible tactile minimale */
  --h-controle-sm: 28px;
  --h-pastille: 20px;
  --h-entete-tableau: 40px;
  /* LA PREMIÈRE RANGÉE DE L'EN-TÊTE DE L'ARBRE, et elle vaut 45 et non 40 : elle porte le bouton
     de détail par magasin, un `btn-sm` de 28px dans 8px de marge intérieure. C'est un JETON parce
     que la seconde rangée s'y colle : tant que la valeur était devinée — `--h-entete-tableau` —,
     les deux rangées se chevauchaient de cinq pixels une fois l'en-tête épinglé, et le bas des
     cellules à cheval sur les deux tombait cinq pixels plus bas que celui des autres. Un décalage
     de bordure pile entre les colonnes réseau et le premier magasin (23 septembre 2026). */
  --h-entete-groupe: 45px;
  --pad-cellule: 8px;

  /* Deux largeurs de l'écran d'arbitrage. Elles sont ICI et pas dans le gabarit
     parce qu'elles décrivent des composants, pas une page : la colonne du pool
     doit tenir un crayon et trois chiffres, la bande de réglages doit tenir
     deux ou trois cartes de magasin. */
  --col-pool: 84px;
  /* LES TROIS COLONNES QUI N'APPARTIENNENT À AUCUN MAGASIN : pool, besoin réseau,
     manquant réseau. Elles précèdent les magasins et ne partent pas au défilement
     horizontal — ce sont celles qu'on lit avant de décider s'il vaut la peine
     d'ouvrir un produit. Les deux dernières sont plus étroites : elles portent des
     nombres, pas un bouton. */
  --col-reseau: var(--col-pool) 58px 58px;
  --largeur-reglages: 58rem;

  /* La barre latérale, et ce qu'elle laisse voir de la grille : la fenêtre moins
     la barre, les deux marges de page et les deux filets du cadre. La bande de
     réglages s'y borne — sinon elle dépasse à droite d'un portable de 13 pouces,
     et sa quatrième carte devient invisible sans qu'on sache qu'elle existe. */
  --largeur-barre: 220px;
  --gouttiere-grille: calc(var(--largeur-barre) + 2 * var(--esp-4) + 2px);

  /* Typographie. Le corps est à 14px, la grille à 12px : c'est la référence. */
  --police: "Geist", ui-sans-serif, -apple-system, "Segoe UI", Roboto, sans-serif;
  --police-mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  --texte-2xs: 10px;
  --texte-xs: 12px;
  --texte-sm: 13px;
  --texte: 14px;
  --texte-lg: 16px;
  --texte-xl: 18px;
  --texte-2xl: 22px;

  /* Espacements. Quatre pas suffisent ; un cinquième se discute. */
  --esp-1: 4px;
  --esp-2: 8px;
  --esp-3: 12px;
  --esp-4: 16px;
  --esp-6: 24px;
  --esp-8: 32px;

  --transition: 120ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* ══ §2 SOCLE ET SURFACES DU NAVIGATEUR ═════════════════════════════════════ */

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

:root {
  /* Clair, et rien d'autre : ce jeton commande les contrôles natifs, la barre
     de défilement par défaut et le fond de la page avant la première peinture. */
  color-scheme: light;
  accent-color: var(--primary);
  -webkit-text-size-adjust: 100%;
  tab-size: 2;
}

html,
body {
  height: 100%;
}

body {
  margin: 0;
  background: var(--background);
  color: var(--foreground);
  font-family: var(--police);
  font-size: var(--texte);
  line-height: 1.5;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

::selection {
  background: color-mix(in oklab, var(--primary) 16%, transparent);
  color: var(--foreground);
}

:root {
  scrollbar-width: thin;
  scrollbar-color: color-mix(in oklab, var(--foreground) 22%, transparent) transparent;
}

::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

::-webkit-scrollbar-track {
  background: transparent;
}

::-webkit-scrollbar-thumb {
  background: color-mix(in oklab, var(--foreground) 20%, transparent);
  border: 2px solid transparent;
  background-clip: content-box;
  border-radius: 999px;
}

::-webkit-scrollbar-thumb:hover {
  background: color-mix(in oklab, var(--foreground) 34%, transparent);
  background-clip: content-box;
}

/* L'ANNEAU DE FOCUS EST UNE RÈGLE GLOBALE, PAS UNE DÉCORATION PAR COMPOSANT.
   Écrit une fois ici, il ne peut pas manquer sur le composant qu'on aura
   oublié. Chaque composant le reprend tel quel ; aucun ne le retire. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible,
.focus-anneau:focus-visible {
  outline: 3px solid var(--anneau-visible);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

::placeholder {
  color: var(--muted-foreground);
  opacity: 1;
}

:disabled,
[aria-disabled="true"] {
  cursor: not-allowed;
}

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

hr {
  border: 0;
  border-top: 1px solid var(--border);
  margin: var(--esp-4) 0;
}

/* Les chiffres s'alignent en colonne partout où ils se comparent : c'est tout
   l'écran d'arbitrage. */
table,
input,
.num,
.mono,
.stat-valeur,
.tree {
  font-variant-numeric: tabular-nums;
}

/* ══ §3 TYPOGRAPHIE ═════════════════════════════════════════════════════════ */

h1,
h2,
h3,
h4 {
  margin: 0;
  font-weight: 600;
  letter-spacing: -0.012em;
  color: var(--foreground);
}

h1,
.t-titre {
  font-size: var(--texte-xl);
  line-height: 1.3;
}

h2,
.t-section {
  font-size: var(--texte-lg);
  line-height: 1.35;
}

h3 {
  font-size: var(--texte);
}

p {
  margin: 0;
}

a {
  color: var(--foreground);
  text-decoration-color: color-mix(in oklab, var(--foreground) 35%, transparent);
  text-underline-offset: 2px;
}

a:hover {
  text-decoration-color: currentColor;
}

a.lien-sourd {
  color: var(--muted-foreground);
  text-decoration: none;
}

a.lien-sourd:hover {
  color: var(--foreground);
  text-decoration: underline;
}

.t-sourdine,
.muted {
  color: var(--muted-foreground);
}

.t-petit {
  font-size: var(--texte-xs);
}

.t-aide {
  font-size: var(--texte-xs);
  line-height: 1.45;
  color: var(--muted-foreground);
}

.mono {
  font-family: var(--police-mono);
  font-size: 0.92em;
  letter-spacing: -0.01em;
}

.num {
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* ══ §4 MISE EN PAGE ════════════════════════════════════════════════════════ */

/* .coque : barre latérale fixe + colonne de contenu. Sous 900px la barre passe
   au-dessus et se replie : le portable de 13 pouces dégrade la grille, il ne
   devient pas une seconde interface. */
.coque {
  display: grid;
  grid-template-columns: var(--largeur-barre) minmax(0, 1fr);
  min-height: 100%;
}

.sidebar {
  background: var(--sidebar);
  color: var(--sidebar-foreground);
  border-right: 1px solid var(--sidebar-border);
  padding: var(--esp-3);
  display: flex;
  flex-direction: column;
  gap: var(--esp-4);
  position: sticky;
  top: 0;
  height: 100dvh;
  overflow-y: auto;
}

.sidebar-marque {
  display: flex;
  align-items: center;
  gap: var(--esp-2);
  height: var(--h-controle);
  padding: 0 var(--esp-2);
  font-size: var(--texte-lg);
  font-weight: 600;
  letter-spacing: -0.02em;
}

.sidebar-groupe {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.sidebar-titre {
  padding: 0 var(--esp-2);
  margin-bottom: var(--esp-1);
  font-size: var(--texte-2xs);
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted-foreground);
}

.sidebar-lien {
  display: flex;
  align-items: center;
  gap: var(--esp-2);
  min-height: var(--h-controle);
  padding: 0 var(--esp-2);
  border-radius: var(--radius-md);
  font-size: var(--texte-xs);
  font-weight: 500;
  color: var(--sidebar-foreground);
  text-decoration: none;
  transition: background-color var(--transition);
}

.sidebar-lien .ic {
  color: var(--muted-foreground);
}

.sidebar-lien:hover {
  background: var(--sidebar-accent);
}

.sidebar-lien[aria-current],
.sidebar-lien.actif {
  background: var(--sidebar-primary);
  color: var(--sidebar-primary-foreground);
}

.sidebar-lien[aria-current] .ic,
.sidebar-lien.actif .ic {
  color: inherit;
}

.sidebar-pied {
  margin-top: auto;
  font-size: var(--texte-xs);
  color: var(--muted-foreground);
}

/* La barre du haut : fil d'Ariane à gauche, actions à droite. */
.topbar {
  display: flex;
  align-items: center;
  gap: var(--esp-3);
  height: 48px;
  padding: 0 var(--esp-4);
  border-bottom: 1px solid var(--border);
  background: var(--background);
  position: sticky;
  top: 0;
  z-index: 30;
}

.topbar-actions {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--esp-2);
}

.page {
  padding: var(--esp-6) var(--esp-4) var(--esp-8);
  max-width: 1100px;
  margin: 0 auto;
  width: 100%;
}

.page-pleine {
  max-width: none;
}

/* ── L'ÉCRAN PLEIN : UN SEUL DÉFILEMENT, ET SEUL L'EN-TÊTE RESTE ──────────────
   L'en-tête des colonnes était `sticky` depuis le premier jour et ne tenait
   jamais. La cause n'est pas dans l'en-tête : un élément qui défile
   HORIZONTALEMENT — il le faut, pour sept magasins — est un conteneur de
   défilement dans les DEUX axes, et CSS n'offre pas de sortie (`overflow-x:
   auto` force `overflow-y` à `auto`). Un `top: 0` s'y accrochait donc à un
   cadre qui, faute de hauteur bornée, ne défilait jamais verticalement : c'est
   la page qui bougeait, et l'en-tête partait avec elle.

   LA SORTIE EST DE N'AVOIR QU'UN SEUL DÉFILEMENT, celui de la page de contenu,
   et d'y mettre TOUT — le titre, le bandeau, la barre d'outils et la grille.
   L'en-tête s'accroche alors à ce défilement-là, et le décor s'en va librement
   au-dessus de lui. C'est la différence avec la première tentative, qui
   épinglait le décor avec l'en-tête : sur un écran plein, tout ce qu'on épingle
   est de la hauteur prise à la grille EN PERMANENCE, pour des lignes qu'on lit
   au début d'une séance et plus jamais.

   Le décor reste calé à GAUCHE, parce que ce défilement porte aussi les sept
   magasins en largeur : sans ça le bandeau partirait avec les colonnes.

   SOUS 900px, RIEN DE TOUT ÇA. La barre latérale passe au-dessus et mange de la
   hauteur ; sur une fenêtre étroite, une page qui défile normalement vaut mieux
   qu'une grille haute de six lignes. Le portable de 13 pouces est au-dessus du
   seuil, il garde l'en-tête. */
@media (min-width: 901px) {
  .ecran-plein .coque {
    height: 100dvh;
    overflow: hidden;
  }

  .ecran-plein .colonne {
    display: flex;
    flex-direction: column;
    min-height: 0;
  }

  .ecran-plein .topbar {
    flex: none;
  }

  /* LE seul conteneur de défilement de l'écran, dans les deux axes. Sans marge
     intérieure : elle appartient aux blocs de décor, qui sont les seuls à en
     vouloir — la grille, elle, va d'un bord à l'autre. */
  .ecran-plein .page-pleine {
    flex: 1 1 auto;
    min-height: 0;
    padding: 0;
    overflow: auto;
  }

  /* LE CADRE PERD SON DÉFILEMENT, PAS SON TRAIT.
     Il portait les deux ; le défilement est monté d'un cran. Il a d'abord été
     mis en `display: contents`, donc effacé — et la grille s'est retrouvée à nu :
     « il n'y a plus de border noir sur la table des produits ; c'est clean quand
     c'est fixé en haut, mais sinon ce n'est pas très beau », 21 septembre 2026.
     Le trait dit où la table commence et où elle finit, et une table qui finit
     sans rien a l'air coupée.

     `width: max-content` ET `min-width: 100%` : le cadre épouse la grille, pas
     la fenêtre. Sans lui il ferait la largeur visible et la grille dépasserait
     par la droite — le filet tomberait au milieu des colonnes. */
  /* LE FILET PASSE AU-DESSUS DES CELLULES, IL N'EST PLUS SOUS ELLES.
     `clip-path` découpe au bon endroit — il crée un contexte d'empilement, pas
     un scrollport, donc l'en-tête continue de tenir. Mais il découpe à la BOÎTE
     DE BORDURE, c'est-à-dire que l'arrondi qu'il taille passe PAR-DESSUS le
     trait au lieu de passer autour. Or une cellule d'en-tête commence un pixel
     plus haut et un pixel plus à gauche, coin carré et fond blanc opaque : entre
     l'arc du trait et la découpe, c'est le blanc de la cellule qui gagnait. D'où
     le trou signalé le 21 septembre 2026 — « il y a un trou dans le coin de la
     bordure de chaque côté » — et d'où l'inefficacité de la découpe seule, qui
     range les coins sans changer qui peint en dernier.

     `overflow: hidden` réglerait les deux d'un coup, parce qu'il rogne les
     ENFANTS à la boîte de remplissage, donc en dedans du trait. Il reste
     interdit : il ferait de ce cadre un conteneur de défilement et l'en-tête
     cesserait de tenir, ce qui est tout ce que cet écran a coûté.

     Le trait quitte donc l'élément et devient une surcouche — au-dessus de tout
     ce que la grille empile (4 au plus, `.th-tete`), transparente aux clics,
     découpée par le MÊME `clip-path` que les cellules. Les quatre coins sont
     exacts parce que c'est le même arrondi qui taille le trait et ce qu'il
     entoure. */
  .ecran-plein .tree-cadre {
    position: relative;
    overflow: visible;
    width: max-content;
    min-width: 100%;
    max-height: none;
    border: 0;
    border-radius: var(--radius-lg);
    clip-path: inset(0 round var(--radius-lg));
  }

  .ecran-plein .tree-cadre::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 5;
    pointer-events: none;
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
  }

  .ecran-plein .ecran-tete,
  .ecran-plein .ecran-pied {
    position: sticky;
    left: 0;
    z-index: 2;
    width: 100%;
    padding: var(--esp-4) var(--esp-4) var(--esp-3);
    background: var(--background);
  }

  .ecran-plein .ecran-pied {
    padding: var(--esp-3) var(--esp-4) var(--esp-6);
  }
}

/* ── L'ÉCRAN D'ENTRÉE ────────────────────────────────────────────────────────
   C'est la seule page du service qu'on voit avant d'être quelqu'un, et la seule
   qu'on regarde en entier : partout ailleurs, un acheteur cherche un chiffre.
   Elle est donc CENTRÉE dans la fenêtre plutôt que collée en haut — une carte de
   360px posée sous une barre de navigation absente a l'air d'un reste de page. */
.page-etroite {
  max-width: 380px;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--esp-6);
  padding-block: var(--esp-8);
}

.marque {
  display: flex;
  flex-direction: column;
  gap: 2px;
  text-align: center;
}

/* Le nom porte la seule licence typographique du service : plus gros que tout le
   reste, resserré. Geist tient l'approche négative sans se refermer. */
.marque-nom {
  font-size: 30px;
  font-weight: 600;
  letter-spacing: -0.035em;
  line-height: 1.1;
}

.marque-sous {
  font-size: var(--texte-xs);
  color: var(--muted-foreground);
}

.marque-pied {
  margin: 0;
  text-align: center;
  font-size: var(--texte-2xs);
  color: color-mix(in oklab, var(--muted-foreground) 75%, var(--background));
}

/* L'étiquette d'un champ et son recours sur la même ligne : « mot de passe » à
   gauche, « oublié ? » à droite. Sous le bouton, le lien se lisait comme une
   seconde action ; ici il se lit comme ce qu'il est. */
.field-tete {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--esp-2);
}

/* LE FORMULAIRE D'ENTRÉE RESPIRE PLUS QUE LES AUTRES. `--esp-2` convient à une
   bande de réglages qu'on balaie ; ici il y a trois éléments et tout l'écran pour
   eux, et deux champs collés se lisent comme un seul. */
.page-etroite .card-corps .vstack {
  gap: var(--esp-4);
}

.page-etroite .field {
  gap: 6px;
}

.page-tete {
  display: flex;
  align-items: flex-start;
  gap: var(--esp-4);
  flex-wrap: wrap;
  margin-bottom: var(--esp-4);
}

.page-tete .page-actions {
  margin-left: auto;
  display: flex;
  gap: var(--esp-2);
  align-items: center;
}

.page-desc {
  margin-top: var(--esp-1);
  font-size: var(--texte-xs);
  color: var(--muted-foreground);
  max-width: 68ch;
}

.toolbar {
  display: flex;
  align-items: center;
  gap: var(--esp-2);
  flex-wrap: wrap;
  margin-bottom: var(--esp-3);
}

.toolbar-fin {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--esp-2);
}

.vstack {
  display: flex;
  flex-direction: column;
  gap: var(--esp-2);
}

.hstack {
  display: flex;
  align-items: center;
  gap: var(--esp-2);
  flex-wrap: wrap;
}

.hstack-serre {
  gap: var(--esp-1);
}

@media (max-width: 900px) {
  /* La barre passe au-dessus : la grille a toute la largeur, et la bande de
     réglages avec elle. */
  :root {
    --gouttiere-grille: calc(2 * var(--esp-4) + 2px);
  }

  .coque {
    grid-template-columns: minmax(0, 1fr);
  }

  .sidebar {
    position: static;
    height: auto;
    flex-direction: row;
    align-items: center;
    flex-wrap: wrap;
    border-right: 0;
    border-bottom: 1px solid var(--sidebar-border);
  }

  .sidebar-groupe {
    flex-direction: row;
    flex-wrap: wrap;
  }

  /* Le TITRE de groupe disparaît — il ne fait que nommer une liste qu'on voit —,
     mais PAS le pied : c'est lui qui porte le compte et la déconnexion. Les cacher
     laisserait quelqu'un sans aucun moyen de se déconnecter sur un portable, et
     « je n'arrive pas à sortir » ne ressemble pas à un défaut de mise en page. */
  .sidebar-titre {
    display: none;
  }

  .sidebar-pied {
    margin-left: auto;
    padding: 0;
    border-top: 0;
  }

  .page {
    padding: var(--esp-4) var(--esp-3) var(--esp-8);
  }
}

/* ══ §5 BOUTONS ═════════════════════════════════════════════════════════════ */

/* Géométrie relevée sur la référence : 32px de haut, 12px/500, 0 10px de
   remplissage, rayon 8px, AUCUNE ombre.

   `.btn` seul est la variante « default » de shadcn : primaire plein. La
   référence, elle, emploie surtout `.btn-outline` — transparent, bordure
   --border — et c'est lui qui vaut au pixel près pour la mesure ci-dessus. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: var(--h-controle);
  padding: 0 10px;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  background: var(--primary);
  color: var(--primary-foreground);
  font-family: inherit;
  font-size: var(--texte-xs);
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  box-shadow: var(--shadow);
  transition:
    background-color var(--transition),
    border-color var(--transition),
    color var(--transition),
    opacity var(--transition);
}

.btn .ic {
  flex: none;
  color: currentColor;
}

.btn:hover {
  background: color-mix(in oklab, var(--primary) 88%, var(--background));
}

.btn:active {
  background: color-mix(in oklab, var(--primary) 78%, var(--background));
}

.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.5;
  pointer-events: none;
}

.btn-secondary {
  background: var(--secondary);
  color: var(--secondary-foreground);
  border-color: transparent;
}

.btn-secondary:hover {
  background: color-mix(in oklab, var(--secondary) 70%, var(--foreground) 6%);
}

.btn-secondary:active {
  background: color-mix(in oklab, var(--secondary) 60%, var(--foreground) 10%);
}

.btn-outline {
  background: transparent;
  color: var(--foreground);
  border-color: var(--border);
}

.btn-outline:hover {
  background: var(--accent);
  color: var(--accent-foreground);
}

.btn-outline:active {
  background: color-mix(in oklab, var(--accent) 70%, var(--foreground) 8%);
}

.btn-ghost {
  background: transparent;
  color: var(--foreground);
  border-color: transparent;
}

.btn-ghost:hover {
  background: var(--accent);
}

.btn-ghost:active {
  background: color-mix(in oklab, var(--accent) 70%, var(--foreground) 8%);
}

.btn-destructive {
  background: var(--destructive);
  color: #fff;
  border-color: transparent;
}

.btn-destructive:hover {
  background: color-mix(in oklab, var(--destructive) 88%, #000);
}

.btn-destructive:active {
  background: color-mix(in oklab, var(--destructive) 78%, #000);
}

.btn-destructive:focus-visible {
  outline-color: color-mix(in oklab, var(--destructive) 60%, var(--foreground));
}

/* Tailles. LE CORPS DE TEXTE NE CHANGE PAS avec la taille du bouton : seule la
   boîte rétrécit. Un libellé à 10px dans une barre d'outils se lit mal, et on
   ne gagne rien à le rétrécir puisque c'est la hauteur qui fait la densité.
   `.btn-icon` reste carré et garde la cible tactile de 32px. */
.btn-sm {
  height: var(--h-controle-sm);
  padding: 0 8px;
}

.btn-icon {
  width: var(--h-controle);
  height: var(--h-controle);
  padding: 0;
}

.btn-icon.btn-sm {
  width: var(--h-controle-sm);
  height: var(--h-controle-sm);
}

.btn-bloc {
  width: 100%;
}

/* `.btn-groupe` colle des boutons en un seul bloc (filtres, bascules d'affichage). */
.btn-groupe {
  display: inline-flex;
}

.btn-groupe .btn {
  border-radius: 0;
  margin-left: -1px;
}

.btn-groupe .btn:first-child {
  border-top-left-radius: var(--radius-md);
  border-bottom-left-radius: var(--radius-md);
  margin-left: 0;
}

.btn-groupe .btn:last-child {
  border-top-right-radius: var(--radius-md);
  border-bottom-right-radius: var(--radius-md);
}

.btn-groupe .btn:focus-visible {
  z-index: 1;
}

/* ══ §6 CHAMPS DE SAISIE ════════════════════════════════════════════════════ */

.label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--texte-xs);
  font-weight: 500;
  line-height: 1;
  color: var(--foreground);
}

.label-requis::after {
  content: "·";
  color: var(--destructive);
}

/* `.field` empile étiquette, champ et aide, et sert d'ancre aux états. */
.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* L'aide sous un champ. ELLE EST SOUS, PAS À CÔTÉ : à côté, elle se lit comme
   une unité posée entre deux commandes — « 17 init. » ne veut rien dire. */
.field-aide {
  margin: 0;
  font-size: var(--texte-2xs);
  line-height: 1.4;
  color: var(--muted-foreground);
}

/* Une grille de cartes qui revient à la ligne : les réglages d'un magasin, un
   compte, tout ce qui se lit carte par carte. `auto-fill` et pas `auto-fit` :
   à deux magasins, `auto-fit` étirerait deux cartes sur toute la largeur. */
.grille-cartes {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--esp-4);
  align-items: start;
}

.field-inline {
  flex-direction: row;
  align-items: center;
  gap: var(--esp-2);
}

.input,
.textarea {
  height: var(--h-controle);
  width: 100%;
  padding: 0 10px;
  border: 1px solid var(--input);
  border-radius: var(--radius-md);
  background: var(--background);
  color: var(--foreground);
  font-family: inherit;
  font-size: var(--texte);
  line-height: 1.4;
  box-shadow: var(--shadow);
  caret-color: var(--foreground);
  transition:
    border-color var(--transition),
    background-color var(--transition);
}

.textarea {
  height: auto;
  min-height: 72px;
  padding: 8px 10px;
  resize: vertical;
}

.input:hover:not(:disabled),
.textarea:hover:not(:disabled) {
  border-color: color-mix(in oklab, var(--input) 60%, var(--foreground) 18%);
}

.input:disabled,
.textarea:disabled {
  background: var(--muted);
  color: var(--muted-foreground);
}

.input[aria-invalid="true"],
.textarea[aria-invalid="true"] {
  border-color: var(--destructive);
}

.input[aria-invalid="true"]:focus-visible {
  outline-color: color-mix(in oklab, var(--destructive) 60%, var(--foreground));
}

.input-sm {
  height: var(--h-controle-sm);
  padding: 0 8px;
  font-size: var(--texte-xs);
}

/* Un champ de nombre : chiffres alignés, largeur juste, et pas de petites
   flèches natives — elles ne se cliquent pas dans une grille dense. */
.input-num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  width: 4.5rem;
  -moz-appearance: textfield;
  appearance: textfield;
}

.input-num::-webkit-outer-spin-button,
.input-num::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.input-mono {
  font-family: var(--police-mono);
}

/* Le champ de recherche porte son icône à gauche. */
.recherche {
  position: relative;
  display: inline-flex;
  align-items: center;
}

.recherche .ic {
  position: absolute;
  left: 9px;
  color: var(--muted-foreground);
  pointer-events: none;
}

.recherche .input {
  padding-left: 31px;
  min-width: 220px;
}

/* Le `<select>` : flèche DESSINÉE (svg en fond), pas un glyphe unicode.
   La flèche est le seul endroit du fichier où une couleur est écrite en dur —
   un `background-image` ne connaît pas `currentColor`. C'est --muted-foreground
   (#737373) encodé ; si le jeton bouge, cette ligne bouge avec lui. */
.select {
  height: var(--h-controle);
  width: 100%;
  padding: 0 30px 0 10px;
  border: 1px solid var(--input);
  border-radius: var(--radius-md);
  background-color: var(--background);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23737373' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m4 6.5 4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 8px center;
  background-size: 16px 16px;
  color: var(--foreground);
  font-family: inherit;
  font-size: var(--texte);
  line-height: 1.4;
  appearance: none;
  cursor: pointer;
  box-shadow: var(--shadow);
  transition: border-color var(--transition);
}

.select:hover:not(:disabled) {
  border-color: color-mix(in oklab, var(--input) 60%, var(--foreground) 18%);
}

.select:disabled {
  background-color: var(--muted);
  color: var(--muted-foreground);
  cursor: not-allowed;
}

.select-sm {
  height: var(--h-controle-sm);
  padding: 0 26px 0 8px;
  font-size: var(--texte-xs);
  background-position: right 6px center;
  background-size: 14px 14px;
}

.select-auto {
  width: auto;
}

/* La case à cocher. La coche est un SVG en fond, dans --primary-foreground
   encodé (#fafafa) : même raison que la flèche du select. */
.checkbox {
  appearance: none;
  flex: none;
  width: 16px;
  height: 16px;
  margin: 0;
  border: 1px solid var(--input);
  border-radius: var(--radius-sm);
  background-color: var(--background);
  background-repeat: no-repeat;
  background-position: center;
  background-size: 14px 14px;
  cursor: pointer;
  transition:
    background-color var(--transition),
    border-color var(--transition);
}

.checkbox:hover:not(:disabled) {
  border-color: color-mix(in oklab, var(--input) 50%, var(--foreground) 24%);
}

.checkbox:checked {
  background-color: var(--primary);
  border-color: var(--primary);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23fafafa' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M13 4.5 6.5 11.5 3.5 8.5'/%3E%3C/svg%3E");
}

.checkbox:indeterminate {
  background-color: var(--primary);
  border-color: var(--primary);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23fafafa' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M4 8h8'/%3E%3C/svg%3E");
}

.checkbox:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* L'INTERRUPTEUR — c'est lui qui porte la mise à l'offre.
   Le pouce est un dégradé radial, pas un pseudo-élément : un `<input>` est un
   élément remplacé, et ses `::before`/`::after` ne sont pas garantis. Le
   dégradé, lui, s'anime (background-position) et ne dépend de rien. */
.switch {
  appearance: none;
  flex: none;
  width: 36px;
  height: 20px;
  margin: 0;
  border: 1px solid transparent;
  border-radius: 999px;
  background-color: var(--input);
  background-image: radial-gradient(circle 8px at center, var(--background) 98%, transparent 100%);
  background-repeat: no-repeat;
  background-size: 20px 20px;
  background-position: 0 center;
  cursor: pointer;
  transition:
    background-color var(--transition),
    background-position var(--transition);
}

.switch:hover:not(:disabled) {
  background-color: color-mix(in oklab, var(--input) 70%, var(--foreground) 12%);
}

.switch:checked {
  background-color: var(--primary);
  background-position: 16px center;
}

.switch:checked:hover:not(:disabled) {
  background-color: color-mix(in oklab, var(--primary) 88%, var(--background));
}

.switch:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* `.choix` porte une case ou un interrupteur AVEC son libellé, et garantit la
   cible tactile de 32px qu'une case de 16px n'a pas. */
.choix {
  display: inline-flex;
  align-items: center;
  gap: var(--esp-2);
  min-height: var(--h-controle);
  font-size: var(--texte-xs);
  font-weight: 500;
  cursor: pointer;
  user-select: none;
}

.choix:has(:disabled) {
  color: var(--muted-foreground);
  cursor: not-allowed;
}

.choix-aide {
  display: block;
  font-weight: 400;
  font-size: var(--texte-2xs);
  color: var(--muted-foreground);
}

/* Le contrôle segmenté : des radios déguisés. C'est ce qu'il faut pour une
   consigne — les trois modes se voient D'UN COUP, là où un `<select>` les
   cache et ne montre que celui qui est déjà choisi. */
.segmented {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 2px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--muted);
}

.segmented-option {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 24px;
  padding: 0 8px;
  border-radius: var(--radius-sm);
  font-size: var(--texte-xs);
  font-weight: 500;
  color: var(--muted-foreground);
  white-space: nowrap;
  cursor: pointer;
  transition:
    background-color var(--transition),
    color var(--transition);
}

.segmented-option input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.segmented-option:hover {
  color: var(--foreground);
}

.segmented-option:has(:checked) {
  background: var(--background);
  color: var(--foreground);
  border: 1px solid var(--border);
  padding: 0 7px;
}

.segmented-option:has(:focus-visible) {
  outline: 3px solid var(--anneau-visible);
  outline-offset: 1px;
}

.segmented-sm .segmented-option {
  height: 20px;
  font-size: var(--texte-2xs);
}

/* ══ §7 PASTILLES ═══════════════════════════════════════════════════════════ */

/* Géométrie relevée : 20px de haut, 10px/500, 2px 6px, pilule.
   `.badge` seul est la variante « default » de shadcn (primaire plein) ; les
   pastilles de la référence sont des `.badge.badge-outline`. */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  height: var(--h-pastille);
  padding: 2px 6px;
  border: 1px solid transparent;
  border-radius: var(--radius-pilule);
  background: var(--primary);
  color: var(--primary-foreground);
  font-size: var(--texte-2xs);
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
  vertical-align: middle;
}

.badge .ic {
  width: 10px;
  height: 10px;
}

.badge-secondary {
  background: var(--secondary);
  color: var(--secondary-foreground);
}

.badge-outline {
  background: transparent;
  color: var(--foreground);
  border-color: var(--border);
}

.badge-destructive {
  background: color-mix(in oklab, var(--destructive) 10%, var(--background));
  color: color-mix(in oklab, var(--destructive) 85%, var(--foreground));
  border-color: color-mix(in oklab, var(--destructive) 35%, var(--background));
}

.badge-succes {
  background: var(--succes-doux);
  color: color-mix(in oklab, var(--succes) 70%, var(--foreground));
  border-color: color-mix(in oklab, var(--succes) 40%, var(--background));
}

.badge-muet {
  background: transparent;
  color: var(--muted-foreground);
  border-color: var(--border);
}

/* LES TROIS UNIVERS. Sur une liste de 151 produits, une pastille grise à chaque
   ligne ne dit rien de plus que l'en-tête du groupe : c'est la COULEUR qui fait
   d'« Homme » un repère qu'on attrape du coin de l'œil.

   Les teintes viennent des jetons `--chart-*`, jamais inventées (règle 1 du §1),
   et le texte est la teinte POUSSÉE VERS `--foreground` : `--chart-4` (#e9c468)
   sur un fond presque blanc donne 1,7:1, c'est-à-dire illisible. Mélangé à 55 %
   de la couleur de texte, il garde sa teinte et passe le seuil. */
/* LA MENTION DU MODE TEST. `--chart-5` est la seule teinte de la palette qu'aucun univers
   n'occupe et qui ne dit pas « succès » : elle ne peut se confondre avec rien. Le texte est
   poussé vers `--foreground` pour tenir le contraste, comme celui des univers. */
.badge-test {
  margin-left: var(--esp-2);
  vertical-align: middle;
  background: color-mix(in oklab, var(--chart-5) 22%, var(--background));
  color: color-mix(in oklab, var(--chart-5) 35%, var(--foreground));
  border-color: color-mix(in oklab, var(--chart-5) 55%, var(--background));
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.badge-univers {
  background: color-mix(in oklab, var(--univers) 14%, var(--background));
  color: color-mix(in oklab, var(--univers) 45%, var(--foreground));
  border-color: color-mix(in oklab, var(--univers) 32%, var(--background));
}

.badge-univers-homme {
  --univers: var(--chart-3);
}

.badge-univers-femme {
  --univers: var(--chart-1);
}

.badge-univers-mixte {
  --univers: var(--chart-4);
}

/* Une pastille qui n'est qu'un point coloré, pour une colonne d'état. */
.point {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 999px;
  background: var(--muted-foreground);
  flex: none;
}

.point-succes {
  background: var(--succes);
}

.point-alerte {
  background: var(--destructive);
}

/* ══ §8 TABLEAUX ════════════════════════════════════════════════════════════ */

.table-cadre {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--card);
  overflow: auto;
  max-height: var(--table-hauteur, none);
}

.table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-size: var(--texte-xs);
  color: var(--card-foreground);
}

.table thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  height: var(--h-entete-tableau);
  padding: 0 var(--pad-cellule);
  text-align: left;
  font-size: var(--texte-xs);
  font-weight: 500;
  color: var(--foreground);
  background: var(--card);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}

.table tbody td {
  height: 41px;
  padding: var(--pad-cellule);
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}

.table tbody tr:last-child td {
  border-bottom: 0;
}

.table tbody tr:hover td {
  background: var(--muted);
}

.table th.num,
.table td.num {
  text-align: right;
}

.table td.tronque {
  max-width: 0;
}

.table-fixe {
  table-layout: fixed;
}

.table-compacte tbody td {
  height: 33px;
  padding: 4px var(--pad-cellule);
}

.table .ligne-selectionnee td {
  background: var(--accent);
}

/* Une colonne d'actions : discrète, mais JAMAIS INVISIBLE.
   Elle était en `opacity: 0` jusqu'au survol. Sur une liste de comptes où le
   geste principal est « désactiver », une action qu'il faut découvrir en
   promenant la souris est une action qu'on ne trouve pas — et la ligne du
   dessus ne dit pas qu'il y en a une. Elle reste donc en sourdine, et le survol
   la ramène au premier plan. */
.table .actions-ligne {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--esp-1);
  white-space: nowrap;
  opacity: 0.55;
  transition: opacity var(--transition);
}

.table tr:hover .actions-ligne,
.table tr:focus-within .actions-ligne {
  opacity: 1;
}

/* ══ §9 ARBRE REPLIABLE ═════════════════════════════════════════════════════ */

/* L'ÉCRAN D'ARBITRAGE EST UNE MATRICE : les lignes sont une hiérarchie à quatre
   niveaux (produit › couleur › taille › sku), les colonnes sont les magasins.
   Les deux contraintes se contredisent dans un `<table>` — on ne peut pas
   replier une sous-arborescence de `<tr>` en CSS pur — et dans une liste de
   `<div>` imbriqués, où les colonnes ne s'alignent plus d'un niveau à l'autre.

   LA SORTIE EST `display: contents` : seules les CELLULES sont des éléments de
   la grille. Les emballages (`.node`, `.row`, `.node-body`) ne produisent aucune
   boîte, donc un sku au quatrième niveau tombe dans les mêmes colonnes que son
   produit. Replier, c'est rendre `.node-body` à `display: none`.

   L'ÉTAT EST UNE CASE À COCHER, pas du JavaScript. Le serveur rend `checked`
   quand il veut le nœud ouvert — c'est ce qui permet de rouvrir au bon endroit
   après un POST — et l'attribut `data-ouvert` fait la même chose pour un
   rendu entièrement piloté par le serveur. */

.tree-cadre {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--card);
  overflow: auto;
  max-height: var(--tree-hauteur, none);
}

.tree {
  display: grid;
  /* Une colonne de libellé, d'éventuelles colonnes fixes (le pool), puis un
     magasin par colonne. Le gabarit pose `style="--magasins: 4"`, et
     `--col-fixes: var(--col-reseau)` pour les trois colonnes réseau. */
  grid-template-columns:
    var(--col-tete, minmax(220px, 1.4fr))
    var(--col-fixes, )
    repeat(var(--magasins, 4), minmax(var(--col-magasin, 96px), var(--max-magasin, 1fr)));
  align-items: stretch;
  font-size: var(--texte-xs);
  min-width: max-content;
}

.tree-tete,
.node,
.row,
.node-body {
  display: contents;
}

.th,
.cell {
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 41px;
  padding: var(--pad-cellule);
  background: var(--card);
  border-bottom: 1px solid var(--border);
}

/* L'EN-TÊTE TIENT EN DEUX RANGÉES, ET ELLES NE COLLENT PAS AU MÊME ENDROIT : le
   nom du magasin en haut, ses cinq mesures juste dessous. Avec `top: 0` pour les
   deux, la seconde remontait SUR la première au premier défilement — deux
   rangées superposées, illisibles. La seconde colle donc à la hauteur de la
   première, qui est `--h-entete-tableau` par construction : `.th` porte cette
   hauteur en minimum, et le nom d'un magasin tient sur une ligne (`nowrap`). */
.th {
  z-index: 3;
  min-height: var(--h-entete-tableau);
  font-size: var(--texte-xs);
  font-weight: 500;
  color: var(--foreground);
  white-space: nowrap;
}

/* TROIS ÉTAGES DE PEINTURE, ET ILS COMPTENT QUAND TOUT DÉFILE ENSEMBLE : les
   cellules au fond, la colonne de tête au-dessus d'elles, le décor de l'écran
   par-dessus, et l'en-tête des colonnes au-dessus de tout — c'est lui qui reste,
   donc c'est lui qui doit couvrir ce qui passe dessous.

   LES DÉCLARATIONS DE POSITION SONT PORTÉES PAR `.tree`, ET C'EST DÉLIBÉRÉ.
   `[data-infobulle]` pose `position: relative` — un sélecteur d'attribut, donc la
   même spécificité qu'une classe, et il est écrit plus bas dans ce fichier. Il
   décollait donc silencieusement toute cellule d'en-tête portant une infobulle :
   la colonne « Pool » en porte une, et elle était la seule à s'en aller au
   défilement pendant que ses voisines tenaient. Deux classes valent mieux qu'un
   ordre de déclaration qu'un déplacement de bloc casserait sans bruit. */
.tree .th {
  position: sticky;
  top: var(--h-entete-groupe);
}

/* La première rangée, et les deux cellules qui enjambent les deux. */
.tree .th-groupe,
.tree .th-span2 {
  top: 0;
}

/* LA HAUTEUR EST IMPOSÉE, PAS SUBIE, parce que la seconde rangée s'y colle. Laissée au contenu,
   elle a changé le jour où le bouton de détail est arrivé dans cet en-tête — 41px devenus 45 —
   sans que rien ne suive, et les deux rangées épinglées se sont mises à se chevaucher. */
.tree .th-groupe {
  height: var(--h-entete-groupe);
}

/* UN MAGASIN N'EST PAS UNE COLONNE, C'EST UN GROUPE DE MESURES — offre, stock, besoin,
   proposé, manquant, pénurie. Le gabarit pose donc `--magasins` au nombre de magasins
   MULTIPLIÉ par le nombre de mesures, et l'en-tête du groupe s'étend sur `--mesures`.
   `repeat()` n'accepte pas de `calc()`, la multiplication se fait donc en Go.

   ET LES PISTES SONT EN `auto`, PAS EN `1fr`. Avec `min-width: max-content` sur la grille,
   `1fr` égalise TOUTES les pistes sur la plus large : une colonne qui ne porte qu'un « 3 »
   finit à 272px et la grille à 5 800px. Mesuré, pas supposé. */
.tree-mesures {
  --max-magasin: auto;
}

/* L'en-tête d'un magasin enjambe SES colonnes : six par défaut, dix quand il a
   ouvert ses colonnes de lecture. `--mesures-mag` est posée par l'îlot sur cet
   en-tête-là ; sans JavaScript elle n'existe pas et `--mesures` s'applique. */
.th-groupe {
  grid-column: span var(--mesures-mag, var(--mesures, 6));
  justify-content: center;
  font-weight: 600;
  gap: var(--esp-2);
}

/* Le nom prend la place, l'interrupteur se pose au bord droit. Sans le `1 1 auto`
   le nom serait centré sur la moitié gauche et l'icône collerait au titre. */
.th-groupe-nom {
  flex: 1 1 auto;
  text-align: center;
  padding-left: 22px; /* la largeur de l'interrupteur, pour que le nom reste centré */
}

.th-detail {
  flex: none;
  color: var(--muted-foreground);
}

.th-detail[aria-pressed="true"] {
  background: var(--secondary);
  color: var(--foreground);
}

/* ── LES COLONNES DE LECTURE ─────────────────────────────────────────────────
   Masquées par défaut, et masquées par `display: none` — pas par une largeur
   nulle. Une piste de zéro pixel resterait une piste, et les pistes sont
   comptées par `--magasins` pour TOUTE la grille : il n'y a pas moyen d'en
   annuler une par magasin. L'îlot ajoute les pistes, le CSS montre les cases.

   SANS JAVASCRIPT, RIEN NE S'OUVRE ET LA GRILLE RESTE JUSTE : aucune piste n'est
   réservée, aucune cellule n'est visible. C'est la dégradation qu'on veut — les
   cinq colonnes qui décident sont toujours là. */
.th-plus,
.cell-plus {
  display: none;
}

.tree[data-detail~="0"] .cell-mag-0,
.tree[data-detail~="1"] .cell-mag-1,
.tree[data-detail~="2"] .cell-mag-2,
.tree[data-detail~="3"] .cell-mag-3,
.tree[data-detail~="4"] .cell-mag-4,
.tree[data-detail~="5"] .cell-mag-5,
.tree[data-detail~="6"] .cell-mag-6 {
  display: flex;
}

/* Un filet à gauche du bloc de lecture : il dit que ces quatre colonnes sont
   d'une autre nature que les six qui décident. */
.cell-plus-debut {
  border-left: 1px dashed var(--border);
}

/* Les colonnes de lecture sont en retrait : elles expliquent, elles ne décident
   pas, et le regard doit continuer de tomber sur le manquant. */
.cell-plus {
  color: var(--muted-foreground);
}

.cell-plus .cell-chiffre {
  font-weight: 400;
}

/* La série des quatre semaines tient sur une ligne, en chasse fixe : on la lit
   comme une suite, pas comme quatre nombres. */
.series {
  font-family: var(--police-mono);
  font-size: var(--texte-2xs);
  letter-spacing: -0.03em;
  white-space: nowrap;
}

/* Le point qui dit « comptage partiel » : des lignes de ce niveau sont relevées
   et d'autres pas. Ni « on sait », ni « on ne sait rien » — la troisième. */
.cell-partiel {
  color: var(--muted-foreground);
  font-weight: 600;
}

.th-groupe,
.th-groupe-debut,
.cell-groupe {
  border-left: 1px solid var(--border);
}

.th-span2 {
  grid-row: span 2;
}

/* La colonne d'offre porte une COMMANDE, pas un chiffre : elle se centre au lieu de
   s'aligner à droite comme les mesures. */
.th-offre,
.cell-offre {
  justify-content: center;
}

/* La première colonne reste à l'écran quand on file vers le septième magasin.
   La position est sous `.tree` pour la même raison que celle de `.th`. */
.th-tete,
.cell-tete {
  z-index: 1;
  border-right: 1px solid var(--border);
}

.tree .th-tete,
.tree .cell-tete {
  position: sticky;
  left: 0;
}

.th-tete {
  z-index: 4;
}

.row:hover .cell {
  background: var(--muted);
}

.row:focus-within .cell {
  background: var(--muted);
}

/* Le déplieur. Le chevron pointe à droite fermé, en bas ouvert : c'est la
   même icône, tournée — donc un seul symbole à dessiner et aucune bascule de
   balisage à écrire dans le gabarit. */
.toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  flex: none;
  border-radius: var(--radius-sm);
  color: var(--muted-foreground);
}

.toggle .ic {
  transition: transform var(--transition);
}

.cell-tete {
  cursor: pointer;
  font-weight: 500;
  user-select: none;
}

.cell-tete.cell-fige {
  cursor: default;
}

.node-state {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.node > .node-body {
  display: none;
}

.node:has(> .node-state:checked) > .node-body,
.node[data-ouvert] > .node-body {
  display: contents;
}

.node:has(> .node-state:checked) > .row .toggle .ic,
.node[data-ouvert] > .row .toggle .ic {
  transform: rotate(90deg);
}

.node > .node-state:focus-visible ~ .row .toggle {
  outline: 3px solid var(--anneau-visible);
  outline-offset: 1px;
}

/* Les quatre niveaux. L'indentation est portée par la cellule de tête, la
   graisse dit la profondeur, et le sku passe en chasse fixe : un code article
   se lit caractère par caractère, pas comme un mot. */
.row[data-niveau="produit"] .cell {
  font-weight: 500;
  border-top: 1px solid var(--border);
}

.row[data-niveau="produit"] .cell-tete {
  padding-left: var(--pad-cellule);
  font-size: var(--texte-sm);
  font-weight: 600;
}

.row[data-niveau="couleur"] .cell-tete {
  padding-left: 30px;
}

.row[data-niveau="taille"] .cell-tete {
  padding-left: 52px;
  font-weight: 400;
}

.row[data-niveau="sku"] .cell {
  color: var(--muted-foreground);
}

.row[data-niveau="sku"] .cell-tete {
  padding-left: 74px;
  font-family: var(--police-mono);
  font-weight: 400;
  letter-spacing: -0.02em;
}

.row[data-niveau="sku"]:hover .cell {
  color: var(--foreground);
}

/* Quatre états de ligne, qui répondent tous à la même question : est-ce que
   l'acheteur peut quelque chose ici ? Une ligne hors offre, en stop ou DORMANTE
   s'efface — elle ne disparaît pas, elle cesse de réclamer l'attention.

   DORMANT EST LE PLUS LÉGER DES TROIS, à la demande de l'équipe achats : un
   produit que personne n'a mis à l'offre, une couleur sans stock nulle part et
   hors offre partout. Ce sont des lignes qu'on peut vouloir réveiller, pas des
   lignes mortes — la teinte les met en retrait, elle ne les barre pas. */
.row[data-etat="hors-offre"] .cell,
.row[data-etat="stop"] .cell {
  color: var(--muted-foreground);
  background: color-mix(in oklab, var(--muted) 45%, var(--card));
}

.row[data-etat="dormant"] .cell {
  color: var(--muted-foreground);
  background: color-mix(in oklab, var(--muted) 70%, var(--card));
}

/* LE SURVOL REPASSE DEVANT. Sans cette reprise, une ligne effacée perdait son
   retour au survol : les deux règles ont la même spécificité, et celle de l'état
   est écrite après. Pointer une ligne doit l'allumer, quel que soit son état —
   c'est ce qui dit qu'elle est encore cliquable. */
.row[data-etat="hors-offre"]:hover .cell,
.row[data-etat="stop"]:hover .cell,
.row[data-etat="dormant"]:hover .cell,
.row[data-etat="hors-offre"]:focus-within .cell,
.row[data-etat="stop"]:focus-within .cell,
.row[data-etat="dormant"]:focus-within .cell {
  background: var(--muted);
}

/* Une ligne de tableau fermée s'efface, comme une ligne d'arbre hors offre :
   elle ne disparaît pas — son historique reste à consulter — elle cesse de
   réclamer l'attention. */
.table tbody tr[data-etat="ferme"] td {
  color: var(--muted-foreground);
  background: color-mix(in oklab, var(--muted) 45%, var(--card));
}

.row[data-etat="manque"] .cell-tete {
  box-shadow: inset 3px 0 0 var(--destructive);
}

/* Le rang de service d'un magasin : un chiffre, pas une pastille. Il se lit en
   colonne, du haut vers le bas, et une pastille par ligne ferait un chapelet. */
/* L'écran des données : le récapitulatif d'un job, borné.
   Trois lignes suffisent à reconnaître ce qui s'est passé ; le texte entier
   reste dans l'attribut `title`. Sans borne, la colonne du récapitulatif — quinze
   lignes sur la séance de calcul — écrase les cinq colonnes de chiffres. */
/* La bande de réglages tant qu'elle se charge.
   ELLE GARDE SA HAUTEUR : sans minimum, la bande s'ouvre sur un titre seul puis
   grandit d'un coup quand la réponse arrive, et le produit d'en dessous saute
   sous le curseur. Une hauteur approchée vaut mieux qu'un saut exact. */
.reglages-attente {
  min-height: 248px;
}

.recap {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  max-width: 44ch;
  overflow: hidden;
}

.rang {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--muted-foreground);
}

/* ── CE QUI PART, PAR MAGASIN ─────────────────────────────────────────────────
   Trois grandeurs de rang décroissant, et la typographie le dit : les PIÈCES en
   chiffres pleins, les colis en sourdine et précédés d'un « ≈ » parce que
   l'estimation n'est pas un compte, le nombre de commandes en pastille et
   seulement quand il y en a plus d'une.

   DEUX GRANDEURS, PAS TROIS. Le nombre de commandes y a figuré une heure, en
   pastille : sur une bande qui doit tenir sur une ligne à sept magasins, un
   troisième élément la casse — et le découpage n'est pas une décision
   d'arbitrage, il se lit au moment de publier. Il est dans l'infobulle.

   AUCUNE COULEUR D'ALERTE ICI non plus. La version précédente affichait
   « 12/10 » en rouge, ce qui se lisait comme une limite franchie ; le plafond du
   magasin est une règle de découpage, et trois commandes au lieu d'une ne sont
   pas un incident. Le rouge de cet écran est réservé au manquant d'arbitrage. */
.charges {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--esp-2);
}

.charge {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  height: var(--h-pastille);
  padding: 2px 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius-pilule);
  font-size: var(--texte-2xs);
  white-space: nowrap;
}

.charge-magasin {
  color: var(--muted-foreground);
}

.charge-pieces {
  font-size: var(--texte-xs);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.charge-colis,
.charge-vide {
  color: var(--muted-foreground);
  font-variant-numeric: tabular-nums;
}

/* ── La colonne du pool ───────────────────────────────────────────────────────
   Elle porte un chiffre à tous les niveaux, et AU NIVEAU COULEUR elle porte en
   plus le déplieur des réglages. Le crayon ne peut pas aller dans la cellule de
   tête : celle-ci est un `<label for>` qui commande le repli, et un second label
   à l'intérieur serait une imbrication invalide que le navigateur ignorerait.
   D'où la commande à gauche, le chiffre à droite, dans la même case. */
.cell-pool {
  justify-content: space-between;
  gap: var(--esp-1);
}

/* ── Les bandes qui traversent la grille ──────────────────────────────────────
   L'en-tête d'un univers et les réglages d'une couleur occupent la LIGNE
   ENTIÈRE — sept magasins à cinq mesures plus trois colonnes réseau, soit près
   de deux mille pixels. Leur
   contenu est donc ancré au bord gauche visible : sans ça il partait hors de
   l'écran dès le premier défilement horizontal, comme l'a fait la bande de
   réglages jusqu'au 20 septembre 2026.

   ET SURTOUT : AUCUN `overflow` ICI. Un `overflow: hidden` sur la cellule ferait
   d'elle le conteneur de défilement le plus proche, et l'ancrage n'aurait plus
   rien à quoi s'accrocher — le titre repartait vers la gauche exactement comme
   sans ancrage, ce qui ressemblait à un ancrage qui « ne marche pas ». */
.cell-bande {
  padding: 0;
}

.ancre-gauche {
  position: sticky;
  left: 0;
  display: flex;
  align-items: center;
  gap: var(--esp-2);
  padding: 0 var(--pad-cellule);
}

/* La bande de réglages se pose sur un fond, pour que les cartes des magasins
   s'en détachent : quatre cartes blanches sur du blanc feraient quatre cadres
   flottants au milieu de la grille. */
.row[data-niveau="reglages"] .cell {
  background: var(--secondary);
}

/* L'en-tête d'univers. Il SÉPARE, il ne titre pas : la pastille dit lequel, le
   compte dit combien, et le trait du dessus fait le reste. */
.row[data-niveau="univers"] .cell {
  min-height: 34px;
  background: var(--secondary);
  border-top: 1px solid var(--border);
}

.row[data-niveau="univers"]:hover .cell {
  background: var(--secondary);
}

/* ── La bande de réglages d'une ref. co. ──────────────────────────────────────
   Consigne et seuil initial sont des réglages de SEMAINE, pas des gestes du
   lundi (§6). Ils tenaient sur UNE ligne de deux mille pixels, magasin après
   magasin, libellés mêlés aux champs — « 50 17 init. couv. 24,8 ». Une carte
   par magasin, bornée en largeur et ancrée à gauche : ce sont exactement les
   mêmes commandes, à une largeur où elles se lisent. */
.reglages {
  position: sticky;
  left: 0;
  width: min(var(--largeur-reglages), 100dvw - var(--gouttiere-grille));
  padding: var(--esp-3) var(--pad-cellule) var(--esp-4);
}

.reglages-titre {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--esp-2);
  margin: 0 0 var(--esp-3);
  font-size: var(--texte-xs);
  font-weight: 500;
}

/* ── L'ÉCRAN DU RÉSEAU ────────────────────────────────────────────────────────
   Neuf colonnes, trois blocs de sens, et un pied qui totalise. L'en-tête tient en
   DEUX rangées comme celui de l'arbitrage, et elles ne collent pas au même
   endroit : les blocs en haut, les colonnes juste dessous. */
.table-reseau .th-blocs th {
  top: 0;
  height: var(--h-pastille);
  padding-top: var(--esp-2);
  font-size: var(--texte-2xs);
  font-weight: 500;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--muted-foreground);
  border-bottom: 0;
}

/* LA SECONDE RANGÉE COLLE À LA HAUTEUR DE LA PREMIÈRE, et cette hauteur est
   IMPOSÉE plutôt que subie — c'est la leçon payée le même jour sur l'en-tête de
   l'arbitrage, où la valeur d'ancrage était devinée et a dérivé de cinq pixels
   le jour où un bouton est arrivé dans la rangée du haut. */
.table-reseau thead tr:last-child th {
  top: calc(var(--h-pastille) + var(--esp-2));
}

/* Les trois blocs se séparent par un filet, pas par une couleur : ce sont des
   groupes de colonnes, pas des catégories. */
/* Les trois blocs commencent aux colonnes 2, 5 et 7. */
.table-reseau .th-blocs th:not(:first-child),
.table-reseau thead tr:last-child th:nth-child(2),
.table-reseau thead tr:last-child th:nth-child(5),
.table-reseau thead tr:last-child th:nth-child(7),
.table-reseau tbody td:nth-child(2),
.table-reseau tbody td:nth-child(5),
.table-reseau tbody td:nth-child(7),
.table-reseau tfoot td:nth-child(2),
.table-reseau tfoot td:nth-child(5),
.table-reseau tfoot td:nth-child(7) {
  border-left: 1px solid var(--border);
}

/* LE NOM NE SE REPLIE PAS. Sur une fenêtre étroite, « Paris - Petits Carreaux »
   tombait sur trois lignes, le rang passait au-dessus du nom, et les lignes
   doublaient de hauteur — un tableau de comparaison dont les lignes n'ont plus la
   même taille ne se compare plus. Le cadre défile horizontalement, ce qu'il sait
   déjà faire, et les chiffres restent alignés. */
.table-reseau tbody th,
.table-reseau tfoot th {
  height: 41px;
  padding: var(--pad-cellule);
  font-weight: 500;
  text-align: left;
  white-space: nowrap;
  border-bottom: 1px solid var(--border);
}

/* La colonne du magasin reste à l'écran quand on file vers la dernière colonne,
   comme la colonne de tête de l'arbre : un tableau qui défile et dont on ne sait
   plus quelle ligne on lit ne sert à rien. */
.table-reseau tbody th,
.table-reseau tfoot th,
.table-reseau thead th:first-child {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--card);
  border-right: 1px solid var(--border);
}

.table-reseau thead th:first-child {
  z-index: 3;
}

.table-reseau tfoot th {
  background: var(--secondary);
}

.reseau-magasin {
  display: table-cell;
}

.reseau-magasin .rang {
  display: inline-block;
  min-width: 16px;
  margin-right: var(--esp-2);
  text-align: right;
}

.reseau-nom {
  display: inline-flex;
  align-items: baseline;
  gap: var(--esp-2);
}

/* LE PIED N'EST PAS UNE LIGNE DE PLUS : il totalise, donc il se détache par le
   haut et par la graisse — jamais par une couleur, qui en ferait un état. */
.table-reseau tfoot th,
.table-reseau tfoot td {
  height: 41px;
  padding: var(--pad-cellule);
  font-weight: 600;
  background: var(--secondary);
  border-top: 1px solid var(--border);
  border-bottom: 0;
}

.table-reseau .th-serie {
  width: 1%;
  white-space: nowrap;
}

/* LES QUATRE SEMAINES, EN SVG RENDU PAR LE SERVEUR. Une seule teinte : ce sont
   quatre échantillons d'une même grandeur, pas quatre catégories, et les colorier
   différemment ferait chercher un sens qui n'existe pas.

   ET CETTE TEINTE EST L'ENCRE SOURDE, PAS UNE COULEUR DE GRAPHIQUE. Les cinq
   jetons `--chart-*` sont PRIS : ils portent les univers (`--chart-3` est Homme,
   §1348). Les réemployer ici donnerait une seconde signification à une couleur qui
   en a déjà une, sur un écran qui montrera un jour la ventilation par univers —
   et « Homme » se mettrait à vouloir dire « ventes » une ligne plus bas. */
.barres {
  display: inline-block;
  vertical-align: middle;
  margin-right: var(--esp-2);
  fill: var(--muted-foreground);
}

/* LE SOCLE SE VOIT TOUJOURS, barres nulles comprises : quatre barres absentes sur
   du blanc se lisent « le graphique n'a pas chargé », un trait de base se lit
   « zéro ». Une absence n'est pas un vide, ici comme ailleurs. */
.barres-socle {
  stroke: var(--border);
  stroke-width: 1;
}

.reseau-provenance {
  max-width: 100ch;
  margin-top: var(--esp-3);
}

/* ── LA BARRE DE FILTRES ──────────────────────────────────────────────────────
   Un seul formulaire en GET : l'adresse EST l'état, donc elle se met en favori,
   se colle dans un message et revient telle quelle après une saisie. */
.filtres {
  row-gap: var(--esp-2);
}

.filtres-trait {
  width: 1px;
  height: 18px;
  margin: 0 var(--esp-1);
  background: var(--border);
}

/* Le compte et l'effacement se replient ENSEMBLE : séparés, le compte poussait
   « Effacer » seul sur une deuxième ligne dès 1 100 pixels. */
.filtres-fin {
  display: inline-flex;
  align-items: center;
  gap: var(--esp-2);
  margin-left: auto;
  white-space: nowrap;
}

/* LE JETON : UN INTERRUPTEUR QUI SE LIT COMME UNE ÉTIQUETTE. Le système avait un
   contrôle segmenté — des radios, donc un choix EXCLUSIF — et des pastilles, qui
   ne se cliquent pas. Un filtre est ni l'un ni l'autre : plusieurs jetons tiennent
   ensemble, et chacun s'allume seul. Il reprend la géométrie et les états de
   `.segmented-option` pour que les deux se ressemblent là où ils se croisent. */
.jeton {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: var(--h-controle-sm);
  padding: 0 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-pilule);
  background: var(--background);
  font-size: var(--texte-xs);
  font-weight: 500;
  color: var(--muted-foreground);
  white-space: nowrap;
  cursor: pointer;
  transition:
    background-color var(--transition),
    color var(--transition),
    border-color var(--transition);
}

/* La case est RETIRÉE DE L'ŒIL, PAS DU DOCUMENT : elle reste focalisable au
   clavier et c'est elle qui porte l'état. Même technique que `.segmented-option`. */
.jeton input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.jeton:hover {
  color: var(--foreground);
  border-color: var(--ring);
}

/* ALLUMÉ, IL S'INVERSE. Un jeton coché doit se distinguer d'un jeton survolé sans
   qu'on ait à comparer : le fond plein le fait à 16:1, là où un simple
   changement de bordure demande de regarder les deux. */
.jeton:has(:checked) {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--primary-foreground);
}

.jeton:has(:focus-visible) {
  outline: 3px solid var(--anneau-visible);
  outline-offset: 2px;
}

/* ── LE REGISTRE : UNE LIGNE PAR MAGASIN, LE RÉSEAU EN TÊTE ───────────────────
   C'ÉTAIT UNE CARTE PAR MAGASIN, et c'est ce qui rendait la bande illisible.
   `repeat(auto-fill, minmax(200px, 1fr))` : à quatre magasins sur un portable les
   cartes se replient en 3 + 1, à sept en 4 + 3, et plus aucune colonne ne s'aligne
   d'une carte à l'autre — comparer une variation entre deux magasins devenait un
   balayage en zigzag. Chaque carte répétait en plus ses trois étiquettes : douze
   mots pour dire trois choses, vingt et un à sept magasins.

   Le registre les dit UNE FOIS, en tête de colonne, et sa largeur ne dépend plus du
   nombre de magasins — il grandit vers le bas. À sept magasins c'est trois lignes de
   plus et zéro pixel de large en plus.

   LES CINQ PISTES SONT PARTAGÉES PAR LA LIGNE RÉSEAU ET LES LIGNES MAGASIN, et c'est
   tout l'argument : l'option « hérité » d'un magasin tombe à l'aplomb exact de la
   consigne dont elle hérite. La hiérarchie est dans la géométrie, pas dans une
   phrase d'explication. (23 septembre 2026.) */
.registre {
  display: grid;
  /* BORNÉE, ET PAS SEULEMENT PAR LA BANDE. Étalée sur les 58 rem de `.reglages`, la
     colonne du nom prenait 229 px pour des noms de 140 et la dernière 250 px pour
     une mention d'âge : les commandes s'éloignaient les unes des autres sans que
     rien n'occupe l'écart. Cinquante rem est la largeur où les cinq pistes se
     touchent — et elle ne change pas avec le nombre de magasins. */
  max-width: 50rem;
  grid-template-columns: minmax(96px, 1fr) 224px 5rem 5rem minmax(0, 1.1fr);
  align-items: center;
  column-gap: var(--esp-3);
  row-gap: 4px;
  font-size: var(--texte-xs);
}

/* LES FORMULAIRES NE SONT PAS DES BOÎTES. Chaque ligne du registre porte trois
   formulaires distincts — la consigne, la variation, le seuil initial — et ils
   deviendraient chacun UNE case de la grille, poussant les colonnes suivantes hors
   de leur piste. `display: contents` les efface en tant que boîtes et laisse leurs
   champs retomber dans les pistes. Les champs cachés ne comptent pas : `type=hidden`
   ne produit aucune boîte. */
.registre-champ {
  display: contents;
}

.registre-nom {
  padding-left: var(--esp-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* La ligne du réseau ne se distingue ni par un fond ni par un filet de couleur :
   elle est en tête, elle porte le seul mot que les autres n'ont pas — « aucune » au
   lieu de « hérité » —, et son nom dit ce qu'elle fait. Les magasins sont décalés
   d'un cran, ce qui suffit à les dire dessous. */
.registre-nom-reseau {
  padding-left: 0;
  font-weight: 600;
}

/* « vaut partout » reste SUR LA LIGNE : posé dessous, il faisait de la ligne réseau
   la seule haute de 35 px là où les autres en font 28, et la colonne des consignes
   perdait le rythme qu'elle existe pour tenir. */
.registre-nom-reseau .t-aide {
  font-weight: 400;
}

/* LES CHAMPS DE NOMBRE REMPLISSENT LEUR PISTE, et c'est ce qui fait tenir la colonne :
   laissés à leur largeur propre (4,5 rem) dans une piste de 5, ils se calaient à
   gauche pendant que l'en-tête se calait à droite — huit pixels d'écart, sur la seule
   colonne de l'écran où l'on compare des nombres. Le suffixe « % » suit : il se pose
   sur le bord intérieur DU CHAMP, et un conteneur plus large que le champ l'aurait
   posé dehors, entre deux commandes, sans dire à laquelle il appartient. */
.registre .champ-suffixe,
.registre .input-num {
  width: 100%;
}

.registre-nombre {
  text-align: right;
}

.registre-tiret {
  text-align: right;
}

.registre-note {
  display: flex;
  align-items: center;
  gap: var(--esp-1);
  min-width: 0;
}

.registre-filet {
  grid-column: 1 / -1;
  height: 1px;
  margin: var(--esp-1) 0 var(--esp-2);
  background: var(--border);
}

/* L'OCCASION : le seul endroit de l'écran qui signale un geste à faire plutôt qu'un
   état. Elle ne s'affiche que quand tous les magasins portent la même consigne à la
   main et que le réseau n'en a aucune — 118 ref. co. sur 802. */
.registre-occasion {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--esp-2);
  margin-top: var(--esp-2);
  padding: var(--esp-2) var(--esp-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--background);
  color: var(--muted-foreground);
}

.registre-occasion strong {
  color: var(--foreground);
  font-weight: 600;
}

/* UNE VALEUR HÉRITÉE RESTE ÉCRITE, EN SOURDINE. Un champ vide dirait « il n'y a pas
   de variation ici », ce qui est faux dès que le réseau en pose une. Le gris est
   --muted-foreground, 4,7:1 sur blanc : en sourdine, pas en dessous du lisible. */
.input-herite {
  color: var(--muted-foreground);
}

.champ {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0;
}

/* Une étiquette au-dessus de sa commande, pas à côté : à côté, elle se lisait
   comme une unité posée entre deux champs — « 17 init. » ne veut rien dire. */
.champ-etiquette {
  font-size: var(--texte-2xs);
  font-weight: 500;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--muted-foreground);
}

.champ-ligne {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--esp-2);
  cursor: pointer;
}

/* Le suffixe d'unité est DANS le champ, posé sur son bord intérieur : dehors, il
   flottait entre deux commandes sans dire à laquelle il appartenait. */
.champ-suffixe {
  position: relative;
  display: inline-flex;
}

.champ-suffixe .input {
  padding-right: 20px;
}

.champ-suffixe::after {
  content: attr(data-suffixe);
  position: absolute;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  font-size: var(--texte-2xs);
  color: var(--muted-foreground);
  pointer-events: none;
}

.champ-note {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--esp-1);
  margin: 0;
}

/* Le contrôle segmenté prend toute la largeur de sa carte et répartit ses
   options : quatre consignes de largeurs différentes feraient quatre colonnes
   qui ne s'alignent pas d'une carte à l'autre. */
.segmented-bloc {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  width: 100%;
}

.segmented-bloc .segmented-option {
  justify-content: center;
  min-width: 0;
  padding: 0 4px;
}

.segmented-bloc .segmented-option:has(:checked) {
  padding: 0 3px;
}

/* La valeur d'une cellule, et son commentaire dessous. */
.cell-valeur {
  display: flex;
  flex-direction: column;
  gap: 1px;
  line-height: 1.25;
  min-width: 0;
}

.cell-chiffre {
  font-size: var(--texte-sm);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}

.cell-chiffre-manque {
  color: var(--destructive);
}

.cell-detail {
  font-size: var(--texte-2xs);
  color: var(--muted-foreground);
  font-variant-numeric: tabular-nums;
}

.cell-vide {
  color: color-mix(in oklab, var(--muted-foreground) 45%, var(--background));
}

.th.num,
.cell.num {
  justify-content: flex-end;
  text-align: right;
}

/* Une valeur empilée dans une colonne de nombres s'aligne à droite elle aussi :
   sinon la même colonne a deux bords, et l'œil ne peut plus comparer. */
.cell.num .cell-valeur {
  align-items: flex-end;
  text-align: right;
}

/* Les saisies d'une cellule. `.cell-actions` reste en place ; `.au-survol` ne
   se montre qu'au survol ou au focus, pour les commandes secondaires. */
.cell-actions {
  display: flex;
  align-items: center;
  gap: var(--esp-1);
  margin-left: auto;
}

.au-survol {
  opacity: 0;
  transition: opacity var(--transition);
}

.row:hover .au-survol,
.row:focus-within .au-survol {
  opacity: 1;
}

/* ══ §10 CARTES, SÉPARATEURS, AVIS, STATS, INFOBULLES, ICÔNES ═══════════════ */

.card {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--card);
  color: var(--card-foreground);
  box-shadow: var(--shadow);
}

.card-tete {
  display: flex;
  align-items: flex-start;
  gap: var(--esp-3);
  padding: var(--esp-4);
  border-bottom: 1px solid var(--border);
}

.card-tete .card-actions {
  margin-left: auto;
  display: flex;
  gap: var(--esp-2);
}

.card-titre {
  font-size: var(--texte);
  font-weight: 600;
  letter-spacing: -0.01em;
}

.card-desc {
  margin-top: 2px;
  font-size: var(--texte-xs);
  color: var(--muted-foreground);
}

.card-corps {
  padding: var(--esp-4);
}

.card-corps-nu {
  padding: 0;
}

.card-pied {
  display: flex;
  align-items: center;
  gap: var(--esp-2);
  padding: var(--esp-3) var(--esp-4);
  border-top: 1px solid var(--border);
}

.separator {
  height: 1px;
  width: 100%;
  background: var(--border);
  border: 0;
  margin: var(--esp-3) 0;
}

.separator-vertical {
  width: 1px;
  height: 20px;
  margin: 0 var(--esp-1);
  background: var(--border);
  flex: none;
}

/* Les avis. Quatre tons, et le ton est la seule chose qui change : la
   géométrie est la même, pour qu'un avis ne ressemble jamais à un bouton. */
.alert {
  display: flex;
  align-items: flex-start;
  gap: var(--esp-2);
  padding: 10px var(--esp-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--card);
  font-size: var(--texte-xs);
  line-height: 1.45;
}

.alert .ic {
  flex: none;
  margin-top: 1px;
  color: var(--muted-foreground);
}

.alert-titre {
  display: block;
  font-weight: 500;
  margin-bottom: 2px;
}

.alert-erreur {
  border-color: color-mix(in oklab, var(--destructive) 35%, var(--background));
  background: color-mix(in oklab, var(--destructive) 6%, var(--background));
  color: color-mix(in oklab, var(--destructive) 80%, var(--foreground));
}

.alert-erreur .ic {
  color: var(--destructive);
}

.alert-succes {
  border-color: color-mix(in oklab, var(--succes) 35%, var(--background));
  background: var(--succes-doux);
  color: color-mix(in oklab, var(--succes) 65%, var(--foreground));
}

.alert-succes .ic {
  color: var(--succes);
}

.alert-info {
  background: var(--muted);
}

/* Le mode test, sur les écrans où il a un coût : la teinte de la mention de la
   barre latérale (`--chart-5`), pour qu'on reconnaisse la même chose aux deux
   endroits — et qu'elle ne ressemble ni à une erreur ni à un succès. */
.alert-test {
  border-color: color-mix(in oklab, var(--chart-5) 45%, var(--background));
  background: color-mix(in oklab, var(--chart-5) 10%, var(--background));
  color: color-mix(in oklab, var(--chart-5) 30%, var(--foreground));
  margin-bottom: var(--esp-4);
}

.alert-test .ic {
  color: color-mix(in oklab, var(--chart-5) 70%, var(--foreground));
}

/* Le bandeau de séance : quelques chiffres, un par tuile. */
.stats {
  display: flex;
  align-items: stretch;
  gap: var(--esp-4);
  flex-wrap: wrap;
  padding: var(--esp-3) var(--esp-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--card);
  margin-bottom: var(--esp-4);
}

.stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 96px;
}

.stat-valeur {
  font-size: var(--texte-2xl);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

.stat-libelle {
  font-size: var(--texte-2xs);
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted-foreground);
}

.stat-muet .stat-valeur {
  color: var(--muted-foreground);
  font-weight: 500;
}

.stat-alerte .stat-valeur {
  color: var(--destructive);
}

.stat-fin {
  margin-left: auto;
  align-self: center;
}

/* L'infobulle, en CSS pur, par attribut. Elle ne remplace JAMAIS un libellé
   accessible : c'est un complément, pas le seul porteur de l'information. */
[data-infobulle] {
  position: relative;
}

[data-infobulle]::after {
  content: attr(data-infobulle);
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  translate: -50% 0;
  z-index: 60;
  max-width: 260px;
  width: max-content;
  padding: 5px 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--popover);
  color: var(--popover-foreground);
  font-family: var(--police);
  font-size: var(--texte-2xs);
  font-weight: 400;
  line-height: 1.4;
  text-align: left;
  white-space: normal;
  text-transform: none;
  letter-spacing: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--transition);
}

[data-infobulle]:hover::after,
[data-infobulle]:focus-visible::after {
  opacity: 1;
}

[data-infobulle][data-infobulle-position="bas"]::after {
  bottom: auto;
  top: calc(100% + 6px);
}

/* Les icônes. Un seul jeu, dessiné à la main dans icones.svg : trait 1,5px,
   currentColor, grille de 16. Pas d'emoji, pas de glyphe unicode — un emoji
   n'a ni la couleur du texte, ni la même taille d'une machine à l'autre. */
.ic {
  width: 16px;
  height: 16px;
  flex: none;
  color: inherit;
}

.ic-14 {
  width: 14px;
  height: 14px;
}

.ic-20 {
  width: 20px;
  height: 20px;
}

.ic-sourdine {
  color: var(--muted-foreground);
}

/* Une icône demandée sous un nom qui n'existe pas se voit, plutôt que de
   laisser un trou muet dans l'interface. */
.ic-inconnue {
  outline: 1px dashed var(--destructive);
  outline-offset: -1px;
}

/* L'état vide. Il DIT ce qu'il faut faire ; « aucun résultat » tout seul laisse
   le lecteur devant un écran blanc. */
.vide-etat {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--esp-2);
  padding: var(--esp-8) var(--esp-4);
  text-align: center;
  color: var(--muted-foreground);
  font-size: var(--texte-xs);
}

.vide-etat .ic {
  width: 20px;
  height: 20px;
  color: color-mix(in oklab, var(--muted-foreground) 60%, var(--background));
}

.vide-etat-titre {
  font-size: var(--texte);
  font-weight: 500;
  color: var(--foreground);
}

/* ══ §11 UTILITAIRES ════════════════════════════════════════════════════════ */

/* La liste est COURTE et le reste : chaque utilitaire ajouté ici est un
   composant qu'on n'a pas nommé. */

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.tronque {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pousse {
  margin-left: auto;
}

.inline-form {
  display: inline-flex;
  align-items: center;
  gap: var(--esp-1);
  margin: 0;
}

.nowrap {
  white-space: nowrap;
}

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

@media print {
  .sidebar,
  .topbar,
  .cell-actions,
  .page-actions {
    display: none !important;
  }

  .tree-cadre,
  .table-cadre {
    max-height: none;
    overflow: visible;
  }
}

/* ══ LE PASSAGE DE COMMANDE ═════════════════════════════════════════════════
   Une cellule d'action dans un tableau reste une cellule : `display: flex` sur
   un `td` le sort du modèle de tableau et décale toute la ligne. */
.table td.td-action {
  text-align: right;
  white-space: nowrap;
}

.commande-prevue + .commande-prevue {
  margin-top: var(--esp-6);
}

.commande-prevue-titre {
  display: flex;
  align-items: baseline;
  gap: var(--esp-2);
  flex-wrap: wrap;
  margin-bottom: var(--esp-2);
}

/* ══ LA MATRICE DES PRODUITS ════════════════════════════════════════════════
   Un produit est un groupe de lignes : sa propre ligne d'en-tête, puis ses
   versions dans l'ordre d'écoulement. Une version archivée reste à son rang,
   mais s'efface : elle ne compte plus dans les calculs, elle compte encore
   dans l'ordre qu'on lit. */
.matrice-compte {
  margin-bottom: var(--esp-4);
}

.matrice-univers {
  margin: var(--esp-6) 0 var(--esp-2);
}

.table-matrice .matrice-parent th {
  text-align: left;
  font-weight: 500;
  background: var(--muted);
  padding-top: 8px;
  padding-bottom: 8px;
}

.matrice-nom {
  margin-right: var(--esp-2);
}

.table-matrice .matrice-version td:first-child {
  padding-left: var(--esp-4);
}

.table-matrice .matrice-version[data-etat="archivee"] td {
  color: var(--muted-foreground);
}

.matrice-geste {
  display: inline-flex;
  gap: 2px;
}

.filtres-case {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--texte-xs);
  color: var(--muted-foreground);
}

.filtres .select {
  width: auto;
  min-width: 160px;
}
