/**------- JR CONTROL v2 - design system du panneau d'administration.
 *
 *         Identite "Street Issue" validee par Redha (site v2, panneau v1) :
 *         noir asphalte, jaune JR signature, coins coupes au clip-path, grain.
 *         On la DENSIFIE selon les references Grafana Node Exporter et Kibana ;
 *         on ne la remplace pas.
 *
 *         REGLES DE CE FICHIER, valables pour tous les lots front :
 *         1. aucune valeur de couleur, d'espacement ou de typo en dur dans un
 *            composant. Tout passe par les jetons de :root. Un composant qui
 *            ecrit #EAB308 est refuse en revue : le jour ou la marque change,
 *            il reste jaune tout seul ;
 *         2. echelle d'espacement de 4 px, valeurs permises 4 8 12 16 20 24 32
 *            40. Rien d'autre. Un 13 px isole est ce qui fait qu'une interface
 *            "sent" l'approximation ;
 *         3. border-radius INTERDIT sauf cercles (pastilles, avatars ronds).
 *            Les coins se coupent, ils ne s'arrondissent pas ;
 *         4. zero ressource distante : aucun @import, aucune police
 *            telechargee au chargement, aucune image d'URL exterieure. Les
 *            deux seules polices servies le sont depuis /fonts/ (meme origine,
 *            deja en production) ;
 *         5. tabular-nums sur tout ce qui est chiffre. Une colonne de nombres
 *            qui danse au rafraichissement est un bug, pas un detail.
 *
 *         CONTRAT DE NOMMAGE : QUI POSSEDE QUOI.
 *
 *         Ce bloc decrivait un systeme qui n'existait pas. Il annoncait que ce
 *         fichier stylait les 16 composants (.viz, .legende, .jauge-c, .donut,
 *         .spark...) alors que `js/charts.js` injecte SA feuille, prefixee
 *         `jrv-`, et n'utilise aucune de ces classes. Une quarantaine de regles
 *         etaient donc mortes, deux chassis de carte coexistaient avec les memes
 *         valeurs recopiees a la main, et tout lot suivant lisait ici une
 *         consigne fausse. Elles sont supprimees, et la regle est ecrite :
 *
 *         UNE SOURCE PAR FAMILLE, JAMAIS DEUX POUR LA MEME.
 *
 *         js/charts.js, feuille injectee, prefixe `jrv-` :
 *           l'INTERIEUR des composants graphiques - SVG, traces, axes, legende
 *           statistique, crosshair, brush, infobulle, etats de composant, et le
 *           chassis de carte du composant lui-meme.
 *
 *         css/panel.css (ce fichier), sans prefixe :
 *           le CHASSIS DE PAGE - rail, onglets, segments, fil, barre globale,
 *           grille et colonnes, cartes NON graphiques, tableaux hors TABLE-D,
 *           modales, toasts, pastilles, zone rouge, ecran de connexion,
 *           squelettes de page, comparaison A/B ;
 *           les JETONS de :root (couleurs, espacements, typographies, coupes).
 *
 *         Le pont va dans UN SEUL SENS : charts.js LIT les jetons de ce fichier
 *         (avec un repli litteral si le CSS n'est pas charge). Ce fichier ne
 *         connait rien de charts.js et ne doit jamais cibler une classe `jrv-`.
 *
 *         Classes de page encore servies ici, et par qui :
 *           .card .card-tete .card-corps .card-pied   cartes non graphiques
 *           .bignum .delta                            compteur de page
 *           .grille .g1..g6 .col-* .rangee-compteurs  grille dense
 *           .tbl .kv .detail-ligne .filtre-pm .tag    tableaux hors TABLE-D
 *           .hm-cell .hm-echelle + rampe --hm0..hm4   heatmap de page (riders.js)
 *           .sq .sq-l .sq-v .sq-trace .sq-ligne       squelettes de page
 *           .etat-vide .etat-erreur .non-mesurable    etats de page
 *           .cmp .cmp-col .cmp-a .cmp-b .cmp-limite   comparaison A/B (chassis)
 *           .cmp-solo .cmp-barre .cmp-modes .cmp-etiq comparaison A/B (chassis)
 *           .cmp-hote                                 comparaison A/B (chassis)
 *           .cmp-hors .cmp-hors-titre .cmp-hors-texte ecran non comparable
 *           .carte-figee .carte-figee-tag/-txt        carte non comparable
 *           .carte-figee-rangee                       marqueur pose sur un groupe
 * -------*/

/* ===================================================================== */
/* 1. POLICES - meme origine uniquement                                   */
/* ===================================================================== */

/**------- Anton et Space Grotesk sont DEJA servies depuis /fonts/ et deja en
 *         production : on les reutilise, on ne les regenere pas.
 *         font-display: swap - le panneau doit etre lisible avant la fin du
 *         chargement des fichiers, meme sur une liaison lente. -------*/
@font-face {
  font-family: 'JRDisplay';
  src: url('../fonts/anton.woff2') format('woff2');
  font-display: swap;
}
@font-face {
  font-family: 'JRText';
  src: url('../fonts/grotesk.woff2') format('woff2');
  font-display: swap;
}

/**------- ECART ASSUME avec SPEC_design 1.4 : aucune `mono.woff2` n'est
 *         fabriquee ni chargee. Instruction du client : "pour la chasse fixe,
 *         utilise une pile systeme, ne fabrique aucun fichier de police".
 *         La pile ci-dessous couvre Windows, macOS et Linux sans un octet de
 *         reseau. Consequence a connaitre : la largeur de chasse varie d'un
 *         poste a l'autre, d'ou les largeurs FIXES imposees aux colonnes de
 *         chiffres (voir .legende) plutot que des largeurs en `ch`. -------*/

/* ===================================================================== */
/* 2. JETONS                                                              */
/* ===================================================================== */

:root {
  /* --- fonds et structure --- */
  --bg: #07080A;
  --panel: #0E1013;
  --panel2: #141719;
  --panel3: #191D22;
  --ink: #08090B;
  --line: #1E2228;
  --line2: #15181B;
  --survol: #101216;

  /* --- texte --- */
  --txt: #E7ECF2;
  --mut: #8A97A6;
  --dim: #5B6672;

  /* --- accents, roles FIXES --- */
  --yel: #EAB308;   /* signature JR : marque, selection, focus, tri actif. JAMAIS un etat */
  --blu: #0687D4;   /* information, liens, periode B */
  --grn: #1DB954;   /* OK, succes, periode A */
  --red: #E5484D;   /* erreur, danger, zone rouge. JAMAIS une serie ordinaire */
  --org: #F5A524;   /* avertissement, donnee perimee, seuil intermediaire */
  --vio: #8B5CF6;
  --cya: #22D3EE;
  --pnk: #F472B6;

  /* --- ordre chromatique des series multiples (affectation STABLE) ---
     le rouge n'arrive qu'en 8e : s'il apparait, le graphe est trop charge.

     CES HUIT JETONS NE SONT LUS PAR AUCUNE REGLE (verifie : `var(--s1)` a
     `var(--s8)` n'apparait nulle part dans le dossier front). Ils restent parce
     qu'ils DOCUMENTENT l'ordre, mais le proprietaire de cet ordre est
     `js/charts.js` (`ORDRE_SERIES`), qui lit les jetons de base --yel, --blu,
     --grn... directement : c'est lui qui peint les series, en attributs SVG.
     Toute regle CSS qui pretendrait affecter une couleur de serie depuis ici
     serait morte a la naissance - il y en avait douze, elles sont supprimees. */
  --s1: var(--yel);
  --s2: var(--blu);
  --s3: var(--grn);
  --s4: var(--vio);
  --s5: var(--org);
  --s6: var(--cya);
  --s7: var(--pnk);
  --s8: var(--red);

  /* --- opacites normalisees. Ecrites une fois, jamais retapees a la main --- */
  --op-aire-haut: 0.22;
  --op-aire-bas: 0.04;
  --op-bulle: 0.35;
  --op-survol: 0.14;
  --op-eteint: 0.12;

  /* --- espacement : base 4 px, liste fermee --- */
  --e1: 4px; --e2: 8px; --e3: 12px; --e4: 16px;
  --e5: 20px; --e6: 24px; --e7: 32px; --e8: 40px;

  /* --- typographies --- */
  --disp: 'JRDisplay', Impact, 'Arial Narrow', sans-serif;
  --body: 'JRText', 'Segoe UI', system-ui, sans-serif;
  --mono: 'JetBrains Mono', 'SF Mono', 'Cascadia Mono', Consolas, 'DejaVu Sans Mono', monospace;

  /* --- gabarit --- */
  --rail: 216px;
  --rail-reduit: 56px;
  --h-titre: 56px;
  --h-onglets: 38px;
  --h-barre: 46px;
  --pad-vue: var(--e5);
  --gouttiere: var(--e3);

  /* --- mouvement. Les donnees n'ont pas besoin de cirque --- */
  --t-etat: 120ms;
  --t-volet: 240ms;
  --courbe: cubic-bezier(.2,.7,.3,1);

  /* --- plans --- */
  --z-sticky: 20;
  --z-tooltip: 400;
  --z-modale: 200;
  --z-toast: 300;
  --z-grain: 999;
}

/* ===================================================================== */
/* 3. BASE                                                                */
/* ===================================================================== */

* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { height: 100%; }

body {
  background: var(--bg);
  color: var(--txt);
  font-family: var(--body);
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  overflow: hidden;   /* le defilement appartient a .vue, jamais au document */
}

/**------- grain : la texture qui separe ce panneau d'un tableau de bord
 *         generique. Tuile SVG en data-URI, donc aucune requete. -------*/
body::after {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: var(--z-grain);
  opacity: .035;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence baseFrequency='.9'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)'/%3E%3C/svg%3E");
}

/**------- focus clavier visible PARTOUT. Un back-office se conduit au clavier
 *         quand on y passe la journee ; un focus invisible est une regression
 *         d'accessibilite doublee d'une perte de vitesse. -------*/
:focus-visible {
  outline: 1px solid var(--yel);
  outline-offset: 2px;
}

::selection { background: rgba(234,179,8,.3); color: #fff; }

::-webkit-scrollbar { width: 9px; height: 9px; }
::-webkit-scrollbar-track { background: #0A0C0E; }
::-webkit-scrollbar-thumb { background: #242A31; }
::-webkit-scrollbar-thumb:hover { background: var(--yel); }

/* --- coins coupes : 12 px cartes, 7 px boutons, 4 px pastilles --- */
.cut   { clip-path: polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 12px 100%, 0 calc(100% - 12px)); }
.cut-s { clip-path: polygon(0 0, calc(100% - 7px) 0, 100% 7px, 100% 100%, 7px 100%, 0 calc(100% - 7px)); }
.cut-x { clip-path: polygon(0 0, calc(100% - 4px) 0, 100% 4px, 100% 100%, 4px 100%, 0 calc(100% - 4px)); }

/* --- classes typographiques reutilisables --- */
.mono, .num, .kv-c, .lbl {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}
.num { text-align: right; }
.lbl { font-size: 9px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--mut); }
.micro { font-family: var(--mono); font-size: 8.5px; letter-spacing: 2px; color: var(--dim); text-transform: uppercase; }
.dim { color: var(--dim); }
.mut { color: var(--mut); }

/**------- `--c` : LA couleur de l'element courant, posee par le contexte et lue
 *         par le bord, le texte et la pastille.
 *
 *         Il n'y a plus de rampe `.s1..s8` ni de `[data-serie="n"]` ici, et ce
 *         n'est pas un oubli. Ces douze regles ne peignaient RIEN : elles
 *         posaient `--c` sur des elements dont aucun descendant ne le lisait.
 *         Les vues n'emettent ni `.s1` ni `data-serie` (verifie par balayage de
 *         `js/`), et le seul emetteur de `data-serie` est charts.js, sur les
 *         groupes SVG de ses traces - or charts.js peint en ATTRIBUTS SVG, que
 *         `--c` n'atteint pas, et sa feuille injectee n'utilise pas cette
 *         variable. Une regle qui ne peint rien est pire qu'absente : elle fait
 *         croire au lot suivant qu'un mecanisme existe.
 *
 *         `data-serie` reste ecrit par charts.js, en RANG ENTIER (contrat 7.3),
 *         parce qu'il sert a retrouver un trace par son rang ; la cle de serie,
 *         elle, vit sur `data-cle`. C'est du reperage, pas du style.
 *
 *         Aujourd'hui `--c` est pose par la comparaison A/B (section 19), seule
 *         a le lire. Toute famille qui voudra s'en servir la posera chez elle. -------*/

/* ===================================================================== */
/* 4. CHASSIS                                                             */
/* ===================================================================== */

#app { display: grid; grid-template-columns: var(--rail) 1fr; height: 100vh; }

/* --- rail des rubriques --- */
.rail {
  background: #0A0B0D;
  border-right: 1px solid var(--line);
  display: flex; flex-direction: column;
  overflow: hidden;
}
.rail-marque { padding: var(--e5) 18px var(--e4); border-bottom: 1px solid var(--line); }
.rail-marque .m {
  font-family: var(--disp); font-size: 25px; line-height: .95;
  text-transform: uppercase; letter-spacing: -.5px;
}
.rail-marque .m b { color: var(--yel); font-weight: inherit; }
.rail-marque .s { font-family: var(--mono); font-size: 8.5px; color: var(--dim); letter-spacing: 2px; margin-top: 5px; }

.rail-nav { flex: 1; overflow-y: auto; padding: var(--e2) 0; }
.rail-grp {
  font-family: var(--mono); font-size: 8.5px; letter-spacing: 2px; color: var(--dim);
  padding: var(--e4) 18px var(--e1); text-transform: uppercase;
}
.rail-item {
  display: flex; align-items: center; gap: 11px;
  height: 40px; padding: 0 18px;
  color: var(--mut); text-decoration: none; font-size: 13px;
  border-left: 2px solid transparent; cursor: pointer;
  transition: background var(--t-etat) var(--courbe), color var(--t-etat) var(--courbe);
}
.rail-item:hover { background: #0F1114; color: var(--txt); }
.rail-item.on { background: #101317; color: var(--yel); border-left-color: var(--yel); }
.rail-item svg { width: 15px; height: 15px; flex: 0 0 15px; }
/**------- icones : trait de 1.5, bouts CARRES. Des bouts arrondis sur des
 *         cartes a coins coupes, c'est deux dialectes dans la meme phrase. -------*/
.rail-item svg, .ic {
  fill: none; stroke: currentColor; stroke-width: 1.5;
  stroke-linecap: square; stroke-linejoin: miter;
}
.rail-pied {
  border-top: 1px solid var(--line); padding: var(--e3) 18px;
  font-family: var(--mono); font-size: 9.5px; color: var(--dim);
}
.rail-pied a { color: var(--red); text-decoration: none; }

/* --- colonne principale --- */
.principal { display: flex; flex-direction: column; overflow: hidden; min-width: 0; }

/* barre de titre */
.titre {
  height: var(--h-titre); flex: 0 0 var(--h-titre);
  border-bottom: 1px solid var(--line);
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 var(--e5); background: var(--panel);
}
.fil { font-family: var(--disp); font-size: 20px; text-transform: uppercase; letter-spacing: .4px; font-weight: 400; }
.fil a { color: var(--mut); text-decoration: none; }
.fil a:hover { color: var(--txt); }
.fil .sep { color: var(--dim); margin: 0 var(--e2); }
.fil .ici { color: var(--txt); }
.titre-droite { display: flex; align-items: center; gap: var(--e4); }
.pastille { display: flex; align-items: center; gap: 7px; font-family: var(--mono); font-size: 10px; color: var(--mut); }
.pastille i { width: 7px; height: 7px; border-radius: 50%; background: var(--grn); animation: pouls 2s infinite; }
.pastille.avert i { background: var(--org); }
.pastille.mort i { background: var(--red); animation: none; }
@keyframes pouls {
  0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgba(29,185,84,.5); }
  50% { opacity: .55; box-shadow: 0 0 0 6px rgba(29,185,84,0); }
}

/* onglets de sous-rubriques */
.onglets {
  height: var(--h-onglets); flex: 0 0 var(--h-onglets);
  display: flex; align-items: stretch; gap: 0;
  background: var(--bg); border-bottom: 1px solid var(--line);
  padding: 0 var(--e5); overflow-x: auto; scrollbar-width: none;
}
.onglets::-webkit-scrollbar { display: none; }
.onglet {
  display: flex; align-items: center; padding: 0 var(--e4);
  font-family: var(--mono); font-size: 10.5px; letter-spacing: 1.6px; text-transform: uppercase;
  color: var(--mut); text-decoration: none; cursor: pointer; white-space: nowrap;
  border-bottom: 2px solid transparent;
}
.onglet:hover { color: var(--txt); }
.onglet.on { color: var(--txt); border-bottom-color: var(--yel); }

/**------- sous-sous-rubriques : segmented control en tete de vue.
 *         `max-width` et defilement : la meme classe sert aux selecteurs
 *         "VENTILER PAR", qui comptent parfois six entrees. A 1920 px la recette
 *         a mesure 26 px de debordement, qui coupaient le dernier choix - un
 *         choix invisible est un choix qui n'existe pas. La barre de defilement
 *         est masquee comme celle des onglets : elle mangerait la moitie de la
 *         hauteur de 28 px. -------*/
.segments {
  display: inline-flex; flex-wrap: wrap; min-height: 28px;
  background: var(--panel3); padding: 2px; gap: 2px;
  max-width: 100%; overflow-x: auto; scrollbar-width: none;
}
.segments::-webkit-scrollbar { display: none; }
.segment {
  display: flex; align-items: center; height: 24px; padding: 0 var(--e3);
  font-family: var(--mono); font-size: 10px; letter-spacing: 1.2px; text-transform: uppercase;
  color: var(--mut); cursor: pointer; border: none; background: transparent;
  clip-path: polygon(0 0, calc(100% - 4px) 0, 100% 4px, 100% 100%, 4px 100%, 0 calc(100% - 4px));
}
.segment.on { background: var(--panel); color: var(--txt); }

/* ===================================================================== */
/* 5. BARRE GLOBALE (recherche, filtres, periode, comparaison, refresh)    */
/* ===================================================================== */

.barre {
  height: var(--h-barre); flex: 0 0 var(--h-barre);
  display: flex; align-items: center; gap: var(--e2);
  padding: 0 var(--e5); background: var(--panel);
  border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: var(--z-sticky);
}
.barre .espace { flex: 1; }

.recherche { position: relative; width: 260px; flex: 0 0 260px; }
.recherche input { height: 30px; padding: 0 var(--e2) 0 30px; font-size: 12.5px; }
.recherche svg { position: absolute; left: var(--e2); top: 50%; transform: translateY(-50%); width: 13px; height: 13px; color: var(--dim); }

/* pills de filtre */
.pills { display: flex; align-items: center; gap: var(--e1); flex-wrap: wrap; }
.pill {
  display: inline-flex; align-items: center; gap: 6px; height: 24px; padding: 0 var(--e2);
  background: var(--panel3); font-family: var(--mono); font-size: 10px; color: var(--txt);
  border: 1px solid transparent; cursor: pointer;
  clip-path: polygon(0 0, calc(100% - 4px) 0, 100% 4px, 100% 100%, 4px 100%, 0 calc(100% - 4px));
}
.pill b { color: var(--mut); font-weight: 400; }
.pill .x { color: var(--dim); padding: 0 2px; }
.pill .x:hover { color: var(--red); }
/* exclusion : le filtre retire de la donnee, bordure rouge */
.pill.exclu { border-color: var(--red); }
/**------- filtre desactive temporairement (clic long). Il reste VISIBLE a 40 %
 *         au lieu d'etre supprime : comparer avec et sans est le geste le plus
 *         frequent d'une enquete, et retaper un filtre efface est punitif. -------*/
.pill.off { opacity: .4; }

/* selecteur de periode */
.periode { display: flex; align-items: center; gap: 0; }
.periode .fleche {
  width: 26px; height: 30px; display: grid; place-items: center;
  background: transparent; border: 1px solid var(--line); color: var(--mut); cursor: pointer;
}
.periode .fleche:hover { color: var(--yel); border-color: var(--mut); }
.periode .val {
  height: 30px; padding: 0 var(--e3); display: flex; align-items: center; gap: var(--e2);
  background: var(--ink); border: 1px solid var(--line); border-left: 0; border-right: 0;
  font-family: var(--mono); font-size: 10.5px; color: var(--txt); cursor: pointer;
}
.periode .val:hover { border-color: var(--mut); }
.tz { font-family: var(--mono); font-size: 8.5px; color: var(--dim); letter-spacing: 1px; }

/* anneau de compte a rebours de l'auto-refresh */
.refresh { display: flex; align-items: center; gap: var(--e2); }
.anneau { width: 12px; height: 12px; }
.anneau circle { fill: none; stroke: var(--line); stroke-width: 2; }
.anneau .jauge { stroke: var(--yel); stroke-linecap: butt; transform: rotate(-90deg); transform-origin: 50% 50%; }

/* interrupteur de comparaison */
.bascule { display: inline-flex; align-items: center; gap: var(--e2); cursor: pointer; }
.bascule .piste { width: 28px; height: 14px; background: var(--panel3); position: relative; }
.bascule .piste::after {
  content: ''; position: absolute; inset: 2px auto 2px 2px; width: 10px; background: var(--dim);
  transition: transform var(--t-etat) var(--courbe), background var(--t-etat) var(--courbe);
}
.bascule.on .piste::after { transform: translateX(14px); background: var(--yel); }
.bascule span { font-family: var(--mono); font-size: 10px; letter-spacing: 1.4px; text-transform: uppercase; color: var(--mut); }
.bascule.on span { color: var(--txt); }

/* menu deroulant maison (periode, filtres, auto-refresh) */
.menu {
  position: absolute; z-index: var(--z-tooltip);
  background: var(--panel2); border: 1px solid var(--line);
  box-shadow: 0 6px 18px rgba(0,0,0,.5); padding: var(--e1) 0; min-width: 180px;
}
.menu-item {
  display: flex; align-items: center; justify-content: space-between; gap: var(--e4);
  padding: var(--e2) var(--e3); font-size: 12.5px; color: var(--mut); cursor: pointer;
}
.menu-item:hover { background: var(--panel3); color: var(--txt); }
.menu-item.on { color: var(--yel); }
.menu-sep { height: 1px; background: var(--line); margin: var(--e1) 0; }

/* ===================================================================== */
/* 6. VUE ET GRILLE DENSE                                                 */
/* ===================================================================== */

.vue { flex: 1; overflow-y: auto; overflow-x: hidden; padding: var(--pad-vue); }

/**------- grille de 12 colonnes, gouttiere 12 px. Les classes g1..g6 sont des
 *         raccourcis pour les rangees homogenes ; .col-n sert aux compositions
 *         asymetriques (la fiche rider : 3 / 5 / 4). -------*/
.grille { display: grid; gap: var(--gouttiere); grid-template-columns: repeat(12, minmax(0, 1fr)); }
.grille.serre { gap: var(--e2); }
.g1 { grid-template-columns: 1fr; }
.g2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.g3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.g4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.g5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.g6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
.col-1{grid-column:span 1}.col-2{grid-column:span 2}.col-3{grid-column:span 3}
.col-4{grid-column:span 4}.col-5{grid-column:span 5}.col-6{grid-column:span 6}
.col-7{grid-column:span 7}.col-8{grid-column:span 8}.col-9{grid-column:span 9}
.col-12{grid-column:span 12}
.pile { display: flex; flex-direction: column; gap: var(--gouttiere); min-width: 0; }
.rangee { display: flex; align-items: center; gap: var(--e2); flex-wrap: wrap; }

/**------- rangee de compteurs, hauteur 96 px : c'est la ligne superieure de
 *         Grafana Node Exporter, la premiere chose que le client regarde. -------*/
.rangee-compteurs { display: grid; gap: var(--gouttiere); grid-template-columns: repeat(6, minmax(0, 1fr)); margin-bottom: var(--gouttiere); }
.rangee-compteurs.n4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.rangee-compteurs.n5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }

/* ===================================================================== */
/* 7. CARTE PORTEUSE                                                      */
/* ===================================================================== */

.card {
  background: var(--panel); border: 1px solid var(--line);
  padding: var(--e4); position: relative; min-width: 0;
  display: flex; flex-direction: column;
  clip-path: polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 12px 100%, 0 calc(100% - 12px));
}
.card.avec-pied { padding-bottom: var(--e3); }
.card-tete {
  display: flex; align-items: center; justify-content: space-between; gap: var(--e2);
  min-height: 24px; margin-bottom: var(--e3);
}
.card-tete h3 {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: 2px;
  color: var(--mut); text-transform: uppercase; font-weight: 400;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.card-actions { display: flex; align-items: center; gap: var(--e2); opacity: 0; transition: opacity var(--t-etat) var(--courbe); }
.card:hover .card-actions,
.card:focus-within .card-actions { opacity: 1; }
.card-actions button {
  background: none; border: 0; color: var(--dim); cursor: pointer;
  width: 16px; height: 16px; display: grid; place-items: center; padding: 0;
}
.card-actions button:hover { color: var(--yel); }
.card-corps { flex: 1; min-height: 0; min-width: 0; }

/**------- pied "LIGNES ->" : la sortie obligatoire de tout composant. La regle
 *         d'or du client est qu'aucun graphe n'est un cul-de-sac ; ce lien est
 *         la matiere de cette regle. Il est cache au repos pour ne pas
 *         encombrer, mais TOUJOURS visible au focus clavier. -------*/
.card-pied {
  display: flex; align-items: center; justify-content: space-between; gap: var(--e2);
  margin-top: var(--e2); padding-top: var(--e2); border-top: 1px solid var(--line2);
  font-family: var(--mono); font-size: 8.5px; letter-spacing: 1.6px; color: var(--dim);
}
.lien-lignes {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: 1.4px; text-transform: uppercase;
  color: var(--dim); text-decoration: none; cursor: pointer;
  opacity: 0; transition: opacity var(--t-etat) var(--courbe), color var(--t-etat) var(--courbe);
}
.card:hover .lien-lignes, .lien-lignes:focus-visible { opacity: 1; }
.lien-lignes:hover { color: var(--yel); }

/* bandeau discret en tete de carte (k-anonymat, approximation assumee) */
.bandeau {
  font-family: var(--mono); font-size: 8.5px; letter-spacing: 1.2px;
  padding: var(--e1) var(--e2); margin-bottom: var(--e2);
  background: rgba(234,179,8,.08); color: var(--yel); border-left: 2px solid var(--yel);
}
.bandeau.info { background: rgba(6,135,212,.08); color: #67B9EC; border-left-color: var(--blu); }

/* ===================================================================== */
/* 8. BIGNUM - grand compteur                                             */
/* ===================================================================== */

.bignum {
  height: 96px; background: var(--panel); border: 1px solid var(--line);
  padding: var(--e3) var(--e4); position: relative; overflow: hidden;
  display: flex; flex-direction: column; justify-content: center; cursor: pointer;
}
.bignum::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--c, var(--yel)); }
.bignum:hover { background: var(--panel2); }
.bignum .l { font-family: var(--mono); font-size: 9px; letter-spacing: 1.6px; color: var(--mut); text-transform: uppercase; }
.bignum .v {
  font-family: var(--disp); font-size: 44px; line-height: 1.05; letter-spacing: -1px;
  margin-top: var(--e1); font-variant-numeric: tabular-nums;
}
.bignum .u { font-family: var(--mono); font-size: 10px; color: var(--mut); margin-left: 6px; letter-spacing: 1px; }
.bignum .d { font-size: 11.5px; color: var(--dim); margin-top: 2px; }
.bignum.compact { height: 72px; }
.bignum.compact .v { font-size: 36px; }
.bignum.geant .v { font-size: 56px; }
/* sparkline fantome calee en bas a droite */
.bignum .fantome { position: absolute; right: var(--e3); bottom: var(--e2); width: 72px; height: 28px; opacity: .4; pointer-events: none; }

/**------- chip de delta. La couleur ne suit PAS le signe mais la `direction`
 *         declaree par la metrique : une baisse des crashs est une bonne
 *         nouvelle, la peindre en rouge serait un contresens quotidien. -------*/
.delta {
  display: inline-flex; align-items: center; gap: 4px; padding: 2px 6px;
  font-family: var(--mono); font-size: 10px; font-variant-numeric: tabular-nums;
  background: var(--panel3); color: var(--mut);
  clip-path: polygon(0 0, calc(100% - 4px) 0, 100% 4px, 100% 100%, 4px 100%, 0 calc(100% - 4px));
}
.delta.bon { background: rgba(29,185,84,.14); color: #57D98A; }
.delta.mauvais { background: rgba(229,72,77,.14); color: #FF8D90; }

/* ===================================================================== */
/* 9. CE QUI RESTE DU CHROME DE GRAPHE                                    */
/* ===================================================================== */

/**------- 42 REGLES SUPPRIMEES ICI, ET C'EST LE POINT.
 *
 *         `.viz*`, `.crosshair`, `.brush`, `.brush-bord`, `.tooltip*`,
 *         `.swatch`, `.legende*`, `.jauge-c*`, `.jauge-l*`, `.seuil-*` :
 *         AUCUNE n'etait jamais posee sur un noeud. charts.js dessine tout cela
 *         lui-meme avec ses classes `jrv-*` et ses attributs SVG. Deux
 *         descriptions du meme composant, dont une seule s'applique, c'est la
 *         garantie qu'elles divergeront - et que le prochain lot corrigera la
 *         mauvaise. Verifie avant suppression : zero occurrence de ces classes
 *         dans js/, hors prefixe `jrv-`.
 *
 *         `.viz-trou` referencait en plus un motif SVG `#jr-hachure` que
 *         personne ne creait, alors que charts.js cree le sien avec un
 *         identifiant unique : la regle etait morte des DEUX cotes. -------*/

/* pied de graphe et note de carte : pose par vues/socle_moteur.js (piedCarte) */
.pas { font-family: var(--mono); font-size: 8.5px; color: var(--dim); letter-spacing: 1px; }

/* ===================================================================== */
/* 10. HEATMAP DE PAGE                                                    */
/* ===================================================================== */

/**------- rampe sequentielle jaune, 5 paliers sur fond sombre. Une rampe
 *         arc-en-ciel ferait croire a des categories la ou il y a une
 *         intensite. -------*/
:root {
  --hm0: #14161A;
  --hm1: #3E3610;
  --hm2: #6F5D0D;
  --hm3: #A8890A;
  --hm4: #EAB308;
}
/**------- La rampe et les cellules RESTENT : `vues/riders.js` construit sa
 *         propre matrice de cohortes avec `.hm-cell` et `.hm-echelle` (elle vit
 *         dans un tableau de page, pas dans un composant graphique). C'est donc
 *         du chassis de page, et ce fichier en est bien le proprietaire.
 *         `.heatmap` en revanche appartenait au composant : supprimee. -------*/
.hm-cell { height: 18px; min-width: 14px; background: var(--hm0); cursor: pointer; }
.hm-cell[data-c="1"] { background: var(--hm1); }
.hm-cell[data-c="2"] { background: var(--hm2); }
.hm-cell[data-c="3"] { background: var(--hm3); }
.hm-cell[data-c="4"] { background: var(--hm4); }
.hm-cell:hover { outline: 1px solid #fff; outline-offset: -1px; }
.hm-lig { font-size: 11.5px; color: var(--mut); width: 128px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hm-col { font-family: var(--mono); font-size: 8.5px; color: var(--dim); text-align: center; }
.hm-echelle { display: flex; align-items: center; gap: var(--e1); font-family: var(--mono); font-size: 8.5px; color: var(--dim); margin-top: var(--e2); }
.hm-echelle i { width: 10px; height: 10px; display: block; }

/**------- SUPPRIMES ICI : `.donut*`, `.barh*`, `.spark*`, `.histo-vol*`,
 *         `.map-monde*`, `.map-geo*`, `.bulle*`, `.trace*`, `.badge-live*`,
 *         `.map-note`. Tous ces composants sont dessines par charts.js, qui
 *         n'emet aucune de ces classes. `.histo-vol .fantome` et
 *         `.viz-trou` pointaient de surcroit sur le meme motif `#jr-hachure`
 *         inexistant. -------*/

/* ===================================================================== */
/* 11. TABLEAU DENSE DE PAGE                                              */
/* ===================================================================== */

.tbl-boite { overflow: auto; max-height: 100%; }
.tbl { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.tbl th {
  position: sticky; top: 0; z-index: 2; height: 30px;
  font-family: var(--mono); font-size: 9px; letter-spacing: 1.4px; text-transform: uppercase;
  color: var(--mut); font-weight: 400; text-align: left; white-space: nowrap;
  padding: 0 10px; background: var(--panel); border-bottom: 1px solid var(--line);
  cursor: pointer; user-select: none;
}
.tbl th.num, .tbl td.num { text-align: right; }
.tbl th:hover { color: var(--txt); }
.tbl th.tri { color: var(--txt); }
.tbl th .caret { display: inline-block; width: 8px; height: 8px; margin-left: 5px; color: var(--yel); }
.tbl td { height: 32px; padding: 0 10px; border-bottom: 1px solid var(--line2); vertical-align: middle; }
.tbl td.num { font-family: var(--mono); font-variant-numeric: tabular-nums; }
/* densite Grafana assumee : pas de zebrures, pas d'espace perdu */
.tbl tbody tr:hover td { background: var(--survol); }
.tbl tbody tr.cliquable { cursor: pointer; }
.tbl tbody tr.alerte td { box-shadow: inset 2px 0 0 var(--red); }
.tbl-pied {
  display: flex; align-items: center; justify-content: space-between; gap: var(--e3);
  padding-top: var(--e2); font-family: var(--mono); font-size: 9.5px; color: var(--dim);
}

/* chevron de depliage (explorateur) */
.chevron { width: 14px; color: var(--dim); cursor: pointer; transition: transform var(--t-volet) var(--courbe); }
tr.ouvert .chevron { transform: rotate(90deg); color: var(--yel); }
.detail-ligne { background: var(--ink); }
.detail-ligne td { padding: var(--e3) var(--e4); }
.kv { display: grid; grid-template-columns: 180px 1fr; gap: var(--e1) var(--e3); }
.kv-c { font-size: 10px; color: var(--mut); text-transform: uppercase; letter-spacing: 1px; padding-top: 2px; }
.kv-v { font-size: 12.5px; word-break: break-word; }
.kv-v pre { font-family: var(--mono); font-size: 11.5px; white-space: pre-wrap; color: var(--mut); }
/* boutons + / - de filtrage sur chaque valeur : la reference Kibana */
.filtre-pm { display: inline-flex; gap: 2px; margin-left: var(--e2); opacity: 0; transition: opacity var(--t-etat); }
.kv:hover .filtre-pm, .detail-ligne:hover .filtre-pm { opacity: 1; }
.filtre-pm button {
  width: 16px; height: 16px; background: var(--panel3); border: 0; color: var(--mut);
  font-family: var(--mono); font-size: 11px; line-height: 1; cursor: pointer;
}
.filtre-pm button:hover { background: var(--yel); color: #000; }

/* tags de statut : fond a 14 %, texte a 100 % */
.tag {
  display: inline-block; font-family: var(--mono); font-size: 9px; letter-spacing: 1px;
  padding: 3px 7px; text-transform: uppercase; background: var(--panel3); color: var(--mut);
  clip-path: polygon(0 0, calc(100% - 4px) 0, 100% 4px, 100% 100%, 4px 100%, 0 calc(100% - 4px));
}
.tag.g { background: rgba(29,185,84,.14); color: #57D98A; }
.tag.r { background: rgba(229,72,77,.14); color: #FF8D90; }
.tag.y { background: rgba(234,179,8,.14); color: var(--yel); }
.tag.b { background: rgba(6,135,212,.14); color: #67B9EC; }
.tag.o { background: rgba(245,165,36,.14); color: var(--org); }
.tag.v { background: rgba(139,92,246,.14); color: #B79BF8; }
.tag.d { background: var(--panel3); color: var(--dim); }

/* ===================================================================== */
/* 15. EXPLORATEUR (Kibana Discover)                                      */
/* ===================================================================== */

.explorateur {
  position: fixed; inset: 0; z-index: var(--z-modale);
  background: var(--bg); display: grid; grid-template-columns: 232px 1fr;
  transform: translateX(100%); transition: transform var(--t-volet) var(--courbe);
}
.explorateur.ouvert { transform: none; }
.explorateur.replie { grid-template-columns: 0 1fr; }
.champs { border-right: 1px solid var(--line); overflow-y: auto; padding: var(--e3); background: #0A0B0D; }
.champ {
  display: flex; align-items: center; gap: var(--e2); height: 26px; cursor: pointer;
  font-size: 12px; color: var(--mut); padding: 0 var(--e1);
}
.champ:hover { background: var(--panel3); color: var(--txt); }
.champ .t { font-family: var(--mono); font-size: 10px; color: var(--dim); width: 14px; flex: 0 0 14px; text-align: center; }
.champ .n { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* top 5 des valeurs d'un champ, deplie sous le champ */
.champ-top { padding: var(--e2) var(--e2) var(--e3); background: var(--ink); }
.champ-top .r { display: flex; align-items: center; gap: var(--e2); height: 20px; font-size: 11.5px; }
.champ-top .b { flex: 1; height: 4px; background: var(--panel3); }
.champ-top .b i { display: block; height: 100%; background: var(--yel); }
.expl-corps { display: flex; flex-direction: column; overflow: hidden; }
.expl-compteur { font-family: var(--mono); font-size: 10px; color: var(--mut); padding: var(--e2) var(--e5); }
.expl-table { flex: 1; overflow: auto; padding: 0 var(--e5) var(--e5); }

/* ===================================================================== */
/* 16. BOUTONS, CHAMPS, MODALES, TOASTS                                   */
/* ===================================================================== */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--e2);
  background: var(--yel); color: #000; border: none; padding: 12px 20px;
  font-family: var(--mono); font-size: 11px; font-weight: 700; letter-spacing: 2px;
  text-transform: uppercase; cursor: pointer; transition: filter var(--t-etat) var(--courbe);
  clip-path: polygon(0 0, calc(100% - 7px) 0, 100% 7px, 100% 100%, 7px 100%, 0 calc(100% - 7px));
}
.btn:hover { filter: brightness(1.12); }
.btn:disabled { opacity: .4; cursor: not-allowed; filter: none; }
.btn.b { background: var(--blu); color: #fff; }
.btn.r { background: var(--red); color: #fff; }
.btn.g { background: transparent; color: var(--mut); border: 1px solid var(--line); }
.btn.g:hover { color: var(--txt); border-color: var(--mut); filter: none; }
.btn.w { width: 100%; padding: 14px; }
.btn.sm { padding: 7px 12px; font-size: 9.5px; letter-spacing: 1.4px; }

.f { margin-bottom: var(--e3); }
.f label {
  display: block; font-family: var(--mono); font-size: 9.5px; letter-spacing: 1.6px;
  color: var(--mut); text-transform: uppercase; margin-bottom: 6px;
}
input, select, textarea {
  width: 100%; background: var(--ink); border: 1px solid var(--line); color: var(--txt);
  padding: 11px 12px; font-family: var(--body); font-size: 14px; outline: none;
}
input:focus, select:focus, textarea:focus { border-color: var(--yel); }
input::placeholder, textarea::placeholder { color: var(--dim); }
input.code { font-family: var(--mono); font-size: 24px; letter-spacing: 11px; text-align: center; }
textarea { resize: vertical; min-height: 96px; font-family: var(--mono); font-size: 12.5px; line-height: 1.6; }
.aide { font-size: 11px; color: var(--dim); line-height: 1.6; margin-top: 6px; }

.masque { position: fixed; inset: 0; background: rgba(0,0,0,.78); display: grid; place-items: center; z-index: var(--z-modale); padding: var(--e5); }
.modale {
  width: min(560px, 94vw); max-height: 88vh; overflow-y: auto;
  background: var(--panel); border: 1px solid var(--line); padding: var(--e6);
  clip-path: polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 12px 100%, 0 calc(100% - 12px));
}
.modale.etroite { width: min(420px, 94vw); }
.modale h2 { font-family: var(--disp); font-size: 22px; text-transform: uppercase; font-weight: 400; margin-bottom: 6px; }
.modale .sous { font-size: 12px; color: var(--mut); margin-bottom: var(--e4); }
.modale-pied { display: flex; justify-content: flex-end; gap: var(--e2); margin-top: var(--e5); }

.toasts { position: fixed; right: var(--e5); bottom: var(--e5); z-index: var(--z-toast); display: flex; flex-direction: column; gap: var(--e2); }
.toast {
  width: 320px; background: var(--panel2); border: 1px solid var(--line); border-left: 3px solid var(--grn);
  padding: var(--e3); font-size: 12.5px; display: flex; align-items: center; justify-content: space-between; gap: var(--e3);
  clip-path: polygon(0 0, calc(100% - 7px) 0, 100% 7px, 100% 100%, 7px 100%, 0 calc(100% - 7px));
}
.toast.err { border-left-color: var(--red); }
.toast.avert { border-left-color: var(--org); }
.toast button { background: none; border: 0; color: var(--yel); font-family: var(--mono); font-size: 9.5px; letter-spacing: 1.4px; cursor: pointer; text-transform: uppercase; }

.err { background: rgba(229,72,77,.12); border-left: 3px solid var(--red); color: #FFB4B6; padding: 10px 12px; font-size: 12.5px; margin-bottom: var(--e3); }
.ok  { background: rgba(29,185,84,.10); border-left: 3px solid var(--grn); color: #8DE9AE; padding: 10px 12px; font-size: 12.5px; margin-bottom: var(--e3); }

/* ===================================================================== */
/* 17. ECRAN DE CONNEXION (identite v1 conservee : elle fonctionnait)      */
/* ===================================================================== */

#login {
  position: fixed; inset: 0; display: grid; place-items: center; z-index: 100;
  background: radial-gradient(1200px 700px at 50% -10%, #15181C 0%, #07080A 60%);
}
.lbox { width: min(420px, 92vw); background: var(--panel); border: 1px solid var(--line); padding: 34px 30px; }
.lmark { font-family: var(--disp); font-size: 46px; line-height: .9; letter-spacing: -1px; text-transform: uppercase; }
.lmark b { color: var(--yel); font-weight: inherit; }
.lsub { font-family: var(--mono); font-size: 10px; color: var(--dim); letter-spacing: 2.5px; text-transform: uppercase; margin: var(--e2) 0 var(--e6); }
.enroll { background: var(--ink); border: 1px dashed var(--yel); padding: var(--e4); margin-bottom: var(--e3); }
.enroll .key { font-family: var(--mono); font-size: 15px; letter-spacing: 2px; color: var(--yel); word-break: break-all; background: #000; padding: 11px; margin: 10px 0; user-select: all; }
.enroll ol { margin: var(--e2) 0 0 17px; font-size: 12.5px; color: var(--mut); line-height: 1.75; }
/**------- le QR d'enrolement TOTP se pose sur du BLANC franc : une camera ne
 *         lit pas un code sur fond sombre, et tout le panneau est noir. -------*/
.enroll .qr { background: #fff; padding: 10px; margin: var(--e3) auto 6px; width: max-content; line-height: 0; }

/* ===================================================================== */
/* 18. ETATS                                                              */
/* ===================================================================== */

/**------- CHARGEMENT : des SQUELETTES a la forme du composant, JAMAIS une roue
 *         qui tourne. Une roue ne dit rien : ni ce qui arrive, ni combien.
 *         Un squelette montre la structure a venir, donc l'ecran ne saute pas
 *         quand la donnee se pose. La classe .spin de la v1 est supprimee et
 *         ne doit pas revenir. -------*/
.sq {
  background: var(--line2); position: relative; overflow: hidden;
}
.sq::after {
  content: ''; position: absolute; inset: 0; transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.06), transparent);
  animation: balayage 1200ms infinite;
}
@keyframes balayage { to { transform: translateX(100%); } }
.sq-l   { height: 10px; width: 60px; }
.sq-v   { height: 32px; width: 120px; margin-top: var(--e2); }
.sq-trace { height: 180px; width: 100%; }
.sq-ligne { height: 32px; width: 100%; margin-bottom: 2px; }
.sq-ligne:nth-child(even) { width: 70%; }
/* `.sq-cercle` et `.sq-long` supprimees : jamais posees. L'aveu de chargement
 * long appartient au composant, charts.js le rend dans `.jrv-etat`. */

/* VIDE : requete reussie, zero ligne. Jamais des axes nus sans message. */
.etat-vide {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--e3);
  min-height: 120px; padding: var(--e6); text-align: center;
}
.etat-vide svg { width: 20px; height: 20px; color: var(--dim); }
.etat-vide p { font-size: 12.5px; color: var(--dim); }

/**------- ERREUR : PAR CARTE. Les autres cartes de l'ecran vivent leur vie ;
 *         un timeout sur une metrique lourde ne doit pas noircir la page. -------*/
.card.etat-erreur { border-left: 3px solid var(--red); }
.erreur-corps { padding: var(--e4) 0; }
.erreur-corps p { font-size: 12.5px; color: var(--txt); margin-bottom: 6px; }
.erreur-corps .code { font-family: var(--mono); font-size: 10px; color: var(--dim); letter-spacing: 1px; }
.erreur-corps .btn { margin-top: var(--e3); }

/**------- PERIME : la valeur reste affichee A PLEINE OPACITE, avec l'heure de la
 *         mesure. Le badge appartient au composant (`.jrv-badge` de charts.js) :
 *         `.etat-perime` etait une seconde description du meme etat, jamais
 *         posee. Supprimee. La regle, elle, reste vraie : un trou fait croire a
 *         une panne, une date fait juger l'operateur. -------*/

/**------- NON MESURABLE : un etat de PREMIERE CLASSE, pas un bricolage.
 *         La carte existe, elle est hachuree, elle dit POURQUOI et QUI doit
 *         agir (APP / SERVEUR / ACCES). Un manque visible se corrige, un
 *         manque masque se perd - c'est exactement ce qui a coule la v1. -------*/
.non-mesurable {
  background: repeating-linear-gradient(45deg, var(--panel), var(--panel) 4px, #111417 4px, #111417 8px);
}
.non-mesurable .nm-corps { display: flex; gap: var(--e3); align-items: flex-start; padding: var(--e4) 0; }
.non-mesurable svg { width: 16px; height: 16px; flex: 0 0 16px; color: var(--org); }
.non-mesurable .nm-txt { font-size: 12.5px; color: var(--mut); line-height: 1.6; }
.non-mesurable .nm-txt b { color: var(--org); font-family: var(--mono); font-size: 10px; letter-spacing: 1.5px; display: block; margin-bottom: 4px; }
.tag-resp { margin-top: var(--e2); }

/* valeur absente : un tiret simple, jamais "null", jamais "N/A" */
.vide::before { content: '-'; color: var(--dim); }

/* ===================================================================== */
/* 19. COMPARAISON A / B                                                  */
/* ===================================================================== */

/**------- DEUX COLONNES, MEMES CARTES DUPLIQUEES DANS LE MEME ORDRE.
 *
 *         C'est la troisieme reference visuelle du client. La mecanique de
 *         montage vit dans `js/vues/socle.js` (chapitre 10) : le controleur
 *         enveloppe l'hote de la vue, monte la vue DEUX fois avec deux fenetres
 *         figees, et marque chaque colonne par `data-cmp-cote`.
 *
 *         En comparaison la palette de series est SUSPENDUE : colonne A
 *         monochrome verte, colonne B monochrome bleue. Sinon l'oeil compare des
 *         couleurs de series au lieu de comparer deux periodes. Cette suspension
 *         est appliquee par charts.js, qui LIT `data-cmp-cote` : ses couleurs
 *         sont des attributs SVG, qu'une variable CSS n'atteint pas.
 *         Les anciennes regles `.cmp-a .viz, .cmp-a .legende { --c: ... }`
 *         ciblaient des classes que charts.js n'emet pas : elles ne peignaient
 *         RIEN. Supprimees. La cascade `--c` sur la colonne reste, elle sert au
 *         chassis (compteur de page, liserets, entetes).
 * -------*/
.cmp { display: grid; grid-template-columns: 1fr 1fr; gap: var(--e5); align-items: start; }
/**------- SUPERPOSITION : une seule grille, les deux fenetres dans la MEME carte.
 *         Deux traces separes par 700 px de gouttiere ne se comparent pas a
 *         l'oeil : on ne voit ni l'ecart, ni le moment ou il se creuse. La
 *         disposition en colonnes reste le defaut - c'est la capture choisie par
 *         le client - mais elle ne peut pas etre la seule lecture possible. -------*/
.cmp-solo { grid-template-columns: 1fr; gap: var(--e3); }
/**------- rangee de tete : rappel de ce qu'on regarde, et bascule entre les deux
 *         lectures. Elle occupe la largeur entiere pour rester au meme endroit
 *         quelle que soit la disposition : une commande qui se deplace quand on
 *         s'en sert est une commande qu'on n'ose plus toucher. -------*/
.cmp-barre {
  grid-column: 1 / -1; display: flex; align-items: center; gap: var(--e3);
  flex-wrap: wrap; padding-bottom: var(--e2); border-bottom: 1px solid var(--line);
}
.cmp-etiqs { display: flex; gap: var(--e3); flex-wrap: wrap; }
.cmp-etiq {
  font-family: var(--mono); font-size: 10px; letter-spacing: 1.2px; text-transform: uppercase;
  color: var(--c); border-left: 2px solid var(--c); padding-left: var(--e2);
}
/* la bascule est poussee a droite : la lecture va de l'etiquette vers l'action */
.cmp-modes { margin-left: auto; }
/**------- les segments sont rendus par des <button> : sans cette neutralisation
 *         ils heritent du fond gris et de la bordure du navigateur, et le
 *         segment actif devient illisible (texte clair sur fond clair). -------*/
.cmp-modes button { background: none; border: 0; font: inherit; cursor: pointer; }
.cmp-col { min-width: 0; display: flex; flex-direction: column; gap: var(--e2); }
/**------- l'entete et l'hote de la vue sont DEUX noeuds : une vue vide son hote
 *         a chaque rendu, elle effacerait l'intitule de la colonne s'ils
 *         partageaient le meme parent. -------*/
.cmp-hote { min-width: 0; display: flex; flex-direction: column; gap: var(--gouttiere); }
.cmp-col > .entete {
  font-family: var(--mono); font-size: 10px; letter-spacing: 1.4px; text-transform: uppercase;
  padding-top: var(--e2); border-top: 2px solid var(--c); color: var(--c);
  position: sticky; top: 0; z-index: 1; background: var(--bg);
  padding-bottom: var(--e2); margin-bottom: var(--e1);
}
.cmp-a { --c: var(--grn); }
.cmp-b { --c: var(--blu); }
/**------- La declinaison d'opacite des series multiples n'est PAS ici : elle est
 *         appliquee par charts.js (`opaciteSerie`) sur le trace lui-meme. Les
 *         regles `.cmp [data-serie="n"] { opacity }` qui vivaient a cet endroit
 *         se seraient MULTIPLIEES avec la sienne : une deuxieme serie serait
 *         tombee a 0,49 au lieu de 0,7, une quatrieme a 0,06. Deux proprietaires
 *         pour le meme reglage, c'est un reglage faux.
 *         `[data-serie="n"]` reste declare plus haut (section 3) et porte la
 *         couleur de serie pour le balisage construit par les VUES : c'est un
 *         RANG entier, jamais une cle. -------*/
.cmp-limite {
  grid-column: 1 / -1; background: rgba(234,179,8,.08); border-left: 2px solid var(--yel);
  padding: var(--e2) var(--e3); font-family: var(--mono); font-size: 10px; color: var(--yel);
}

/**------- UNE SEULE NAVIGATION, ET ELLE APPARTIENT A LA PAGE.
 *
 *         Les six vues metier dessinent leur propre rangee d'onglets en tete de
 *         leur hote (helper `onglets()` de socle_moteur.js ; forme identique
 *         dans usage.js, moderation.js, securite.js, riders.js, popups.js et
 *         admin.js : `.onglets` en premier enfant, puis une `.rangee` portant
 *         les `.segments` de sous-sous-rubrique). Montee DEUX fois cote a cote,
 *         la vue dessine donc deux fois sa navigation : sur Usage, "MUSIQUE /
 *         NAVIGATION / ..." puis "LECTEURS / RADIOS / IMPORTS" apparaissaient
 *         chacune deux fois a l'ecran. La reference du client n'a qu'une barre,
 *         au-dessus des deux colonnes.
 *
 *         La navigation de colonne est donc neutralisee ici, et `shell.js`
 *         cesse en meme temps de masquer celle du chassis (`verifierNavDoublon`).
 *         Le chassis dessine deja les trois niveaux : rail, `#onglets`,
 *         `#segments-vue`. Un clic sur un onglet route normalement, et le
 *         controleur de comparaison relaie aux DEUX colonnes.
 *
 *         Deux precautions dans le selecteur, chacune pour un defaut precis :
 *           - il est limite a `.cmp-col`, donc le mode SUPERPOSE - qui n'a qu'une
 *             rangee et fonctionne - garde la sienne. Sans cette limite, il n'y
 *             aurait plus AUCUN onglet en superposition ;
 *           - il ne vise que les enfants DIRECTS de l'hote de colonne. Les
 *             `.segments` de ventilation ("VENTILER PAR") vivent plus bas, dans
 *             les sections de l'ecran : les masquer casserait les selecteurs.
 * -------*/
.cmp-col .cmp-hote > .onglets { display: none; }
/**------- la rangee de sous-sous-rubriques est celle qui SUIT immediatement les
 *         onglets ET qui porte un controle segmente. Les deux conditions sont
 *         necessaires : `riders.js` pose aussi une `.rangee` juste apres, la
 *         barre "TRIER PAR", qui n'est pas de la navigation et doit rester.
 *         Regle SEPAREE de la precedente a dessein : si un navigateur ne
 *         connaissait pas `:has()`, il jetterait cette regle seule, et non les
 *         deux - on retrouverait une rangee de segments en double, jamais une
 *         page sans onglets. -------*/
.cmp-col .cmp-hote > .onglets + .rangee:has(> .segments) { display: none; }

/**------- L'INTITULE DE COLONNE DOIT SE LIRE ENTIER, A TOUTE LARGEUR.
 *         Une comparaison dont on ne lit pas les dates ne compare rien. Il ne
 *         porte donc ni `nowrap` ni ellipse : il passe a la ligne plutot que de
 *         se faire couper, et `socle.js` en met une copie en infobulle. -------*/
.cmp-col > .entete { white-space: normal; line-height: 1.5; }

/* ===================================================================== */
/* 20. ZONE ROUGE (fiche rider)                                           */
/* ===================================================================== */

.zone-rouge {
  border: 1px solid var(--red); background: rgba(229,72,77,.05); padding: var(--e4);
  position: relative;
  clip-path: polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 12px 100%, 0 calc(100% - 12px));
}
.zone-rouge h3 { font-family: var(--mono); font-size: 10px; letter-spacing: 2px; color: var(--red); text-transform: uppercase; }
.zone-rouge p { font-size: 11.5px; color: var(--mut); margin: var(--e2) 0 var(--e4); }
.zone-rouge .btn.r { width: 100%; height: 44px; }
/* compte a rebours de l'elevation, alimente par API.surSudo() */
.sudo-restant {
  position: absolute; top: var(--e3); right: var(--e4);
  font-family: var(--mono); font-size: 10px; color: var(--org); font-variant-numeric: tabular-nums;
}

/* ===================================================================== */
/* 21. RESPONSIVE                                                         */
/* ===================================================================== */

/* grand ecran : on gagne des colonnes plutot que des marges */
@media (min-width: 2200px) {
  .g4 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .rangee-compteurs { grid-template-columns: repeat(8, minmax(0, 1fr)); }
}

/**------- 1440 px, LA LARGEUR D'UN PORTABLE COURANT. On ne peut pas supposer
 *         que le client aura un grand ecran.
 *
 *         Comparaison active a 1440 px, une colonne ne fait plus que 582 px
 *         (mesure de recette) : une grille de quatre cartes y donne des cases de
 *         135 px, ou un grand compteur ne peut pas tenir. On resserre donc les
 *         grilles A L'INTERIEUR d'une colonne, sans toucher a la grille de page :
 *         a 1920 px et au-dela, la colonne est assez large et rien ne change.
 *
 *         Le seuil porte sur la largeur de FENETRE et non sur celle de la
 *         colonne : ce fichier n'emploie aucune requete de conteneur, et la
 *         colonne vaut de toute facon la moitie de la zone de vue.
 *
 *         La specificite `.cmp-col .g4` (0,2,0) passe devant `.g4` (0,1,0) : les
 *         regles generales plus bas ne peuvent pas reprendre la main.
 * -------*/
@media (max-width: 1700px) {
  .cmp { gap: var(--e4); }
  .cmp-col .g3, .cmp-col .g4, .cmp-col .g5, .cmp-col .g6,
  .cmp-col .rangee-compteurs,
  .cmp-col .rangee-compteurs.n4, .cmp-col .rangee-compteurs.n5 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* colonne devenue trop etroite pour deux cartes : une seule, plutot que deux
 * illisibles. La comparaison reste en deux colonnes jusqu'a 900 px (voir plus bas). */
@media (max-width: 1150px) {
  .cmp-col .g2, .cmp-col .g3, .cmp-col .g4, .cmp-col .g5, .cmp-col .g6,
  .cmp-col .rangee-compteurs,
  .cmp-col .rangee-compteurs.n4, .cmp-col .rangee-compteurs.n5 { grid-template-columns: 1fr; }
}

/**------- ONGLETS ET SEGMENTS : ILS SE LISENT ENTIERS, OU ILS DEFILENT.
 *         Mesure de recette a 1440 px : "FOLLOW ME" s'affichait "FOLLOW M".
 *         L'interlettrage de 1,6 px coute ici plus de place que les lettres
 *         elles-memes : on le reduit avant de toucher au corps du texte, qui est
 *         deja a 10,5 px. Le defilement reste le filet, il ne remplace pas la
 *         mise en page. -------*/
@media (max-width: 1500px) {
  .onglet { padding: 0 var(--e3); letter-spacing: 1px; }
}

/* sous 1250 px : le rail se reduit aux icones */
@media (max-width: 1250px) {
  .onglet { padding: 0 var(--e2); letter-spacing: .5px; font-size: 10px; }
  #app { grid-template-columns: var(--rail-reduit) 1fr; }
  .rail-marque .m, .rail-marque .s, .rail-item span, .rail-grp, .rail-pied span { display: none; }
  .rail-item { justify-content: center; padding: 0; }
  .g4, .g5, .g6 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .rangee-compteurs { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* tablette : une colonne, comparaison empilee */
@media (max-width: 900px) {
  .grille, .g2, .g3, .g4, .g5, .g6 { grid-template-columns: 1fr; }
  [class*="col-"] { grid-column: 1 / -1; }
  .rangee-compteurs, .rangee-compteurs.n4, .rangee-compteurs.n5 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* comparaison empilee : deux colonnes de 400 px ne comparent plus rien.
   * L'entete de colonne cesse d'etre collante, sinon les deux se superposent. */
  .cmp { grid-template-columns: 1fr; }
  .cmp-col > .entete { position: static; }
  .explorateur { grid-template-columns: 1fr; }
  .champs { display: none; }
  .recherche { width: 160px; flex: 0 0 160px; }
}

/**------- SOUS 760 px : on ne fait pas semblant. Un tableau de 20 colonnes et
 *         une legende de quatre statistiques par serie ne tiennent pas sur un
 *         telephone ; pretendre le contraire produirait un ecran illisible que
 *         personne n'utiliserait. Le panneau le DIT, c'est plus honnete qu'une
 *         version mobile de facade. -------*/
.ecran-min { display: none; }
@media (max-width: 760px) {
  #app, #login { display: none !important; }
  .ecran-min {
    display: grid; place-items: center; position: fixed; inset: 0;
    padding: var(--e7); text-align: center; background: var(--bg);
  }
  .ecran-min .m { font-family: var(--disp); font-size: 34px; text-transform: uppercase; }
  .ecran-min .m b { color: var(--yel); font-weight: inherit; }
  .ecran-min p { font-size: 13px; color: var(--mut); margin-top: var(--e4); line-height: 1.7; max-width: 340px; }
  .ecran-min .micro { margin-top: var(--e5); }
}

/**------- mouvement reduit : on coupe le decoratif (grain anime, pulsations,
 *         balayage) et on garde ce qui porte de l'information (le squelette
 *         reste visible, il devient juste immobile). -------*/
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; }
  .sq::after { display: none; }
}

/* ===================================================================== */
/* 23. COMPARABILITE HONNETE                                              */
/*     (une valeur qui ne bouge pas n'est pas une valeur cassee)          */
/* ===================================================================== */

/**------- LE PRINCIPE DIRECTEUR DU PANNEAU, TENU PAR DEUX MOTIFS.
 *
 *         Deux colonnes identiques sans un mot d'explication se lisent comme
 *         une panne. C'est ce malentendu, pas un bug de calcul, qui a fait
 *         rejeter les deux versions precedentes. Le panneau doit donc toujours
 *         distinguer "cette valeur ne bouge pas" de "cette valeur est cassee".
 *
 *         Deux endroits ou le dire, deux motifs :
 *           .cmp-hors    l'ECRAN entier ne se compare pas (cockpit, securite) ;
 *           .carte-figee une CARTE ne se compare pas (metrique de temporalite
 *                        `etat` au catalogue : un effectif n'est pas un flux).
 *
 *         Tous deux en BLEU d'information, jamais en rouge ni en orange : le
 *         orange est deja pris par NON MESURABLE, qui veut dire "il manque
 *         quelque chose, quelqu'un doit agir". Ici rien ne manque et personne
 *         n'a rien a faire - la mesure est juste et complete, elle ne depend
 *         simplement pas de la periode. Reutiliser la couleur de l'alerte pour
 *         une information neutre, c'est reintroduire le malentendu par la
 *         peinture apres l'avoir chasse par le texte.
 * -------*/

/**------- AVIS D'ECRAN NON COMPARABLE.
 *         Pose par socle.js (`EcranComparable._batirHors`) au-dessus d'une vue
 *         montee UNE seule fois. Il prend la largeur entiere de l'enveloppe :
 *         c'est la premiere chose a lire, avant les chiffres. -------*/
.cmp-hors {
  grid-column: 1 / -1;
  background: rgba(6, 135, 212, .07);
  border-left: 2px solid var(--blu);
  padding: var(--e3) var(--e4);
  display: flex; flex-direction: column; gap: var(--e2);
}
.cmp-hors-titre {
  font-family: var(--mono); font-size: 10px; letter-spacing: 1.4px;
  text-transform: uppercase; color: var(--blu);
}
/* la phrase est faite pour etre LUE, pas dechiffree : proportionnelle,
 * interlignee, et assez large pour tenir sur deux lignes au plus. */
.cmp-hors-texte { font-size: 12px; line-height: 1.65; color: var(--txt); max-width: 78ch; }
.cmp-hors-suite { color: var(--mut); }

/**------- MARQUEUR DE CARTE FIGEE.
 *
 *         Pose par socle.js (`marquerFigee`) en TETE DU CORPS de la carte, au
 *         meme endroit que le bandeau de lacune - et surtout PAS en pastille
 *         flottante sur le coin : la recette a mesure qu'a 1440 px les
 *         pastilles posees ainsi recouvrent le titre de leur propre carte.
 *
 *         Discret mais lisible : une etiquette courte de la meme famille que le
 *         tag de NON MESURABLE, puis la phrase en clair. Le tag seul ne
 *         suffirait pas (personne ne sait ce que veut dire "ETAT"), la phrase
 *         seule se confondrait avec une note de bas de carte. -------*/
.carte-figee {
  display: flex; align-items: baseline; gap: var(--e2);
  margin-bottom: var(--e2); padding: 5px 8px;
  background: rgba(6, 135, 212, .07);
  border-left: 2px solid var(--blu);
}
.carte-figee-tag {
  flex: 0 0 auto;
  font-family: var(--mono); font-size: 9px; letter-spacing: 1.2px;
  text-transform: uppercase; color: var(--blu);
  background: rgba(6, 135, 212, .14); padding: 1px 5px;
}
.carte-figee-txt { font-size: 11px; line-height: 1.5; color: var(--mut); min-width: 0; }

/**------- variante RANGEE : le meme motif, mais pose au-dessus d'un groupe de
 *         cartes de meme nature plutot que dans chacune. Il prend la largeur
 *         entiere de sa grille, sinon il se retrouverait dans la premiere
 *         colonne comme s'il ne concernait que la premiere carte. -------*/
.carte-figee-rangee { grid-column: 1 / -1; margin-bottom: var(--e3); }

/**------- EN COLONNE ETROITE, LE MARQUEUR SE REPLIE AU LIEU DE POUSSER.
 *         Une colonne de comparaison a 1440 px fait moins de 600 px : garde en
 *         ligne, l'etiquette comprimerait la phrase jusqu'a un mot par ligne.
 *         C'est la seule regle de largeur ici ; la disposition d'ensemble n'est
 *         pas de mon ressort. -------*/
@media (max-width: 1500px) {
  .cmp-col .carte-figee { flex-direction: column; gap: 3px; }
}

/* ===================================================================== */
/* 24. TABLEUR UTILISATEURS                                               */
/* ===================================================================== */

/**------- LE TABLEUR EST UNE PAGE, PAS UNE CARTE.
 *
 *         Demande du client, mot pour mot : "un tableau excel ou je peux tout
 *         filtrer moi meme". Un tableur enferme dans une carte de 240 px de haut
 *         ne serait pas un tableur, ce serait un apercu. Il prend donc la
 *         hauteur disponible et defile a l'interieur, entete fige.
 *
 *         Il REUTILISE `.tbl` (bloc 11) pour la densite, la sticky et les tags :
 *         deux tableaux qui ne se ressemblent pas dans le meme panneau, c'est le
 *         debut du bricolage. Ce bloc n'ajoute que ce que `.tbl` ne sait pas
 *         faire : la ligne de filtres, les poignees de largeur, le pied
 *         totalisateur.
 *
 *         Bloc ajoute EN FIN DE FICHIER, hors des zones que les autres lots
 *         retouchent. Aucune valeur en dur : tout passe par les jetons. -------*/

.tableur { display: flex; flex-direction: column; gap: var(--e3); min-height: 0; height: 100%; }

/* ---- barre d'outils ---- */
.tblr-barre {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--e3); flex-wrap: wrap;
}
.tblr-classements { display: flex; align-items: center; gap: var(--e1); flex-wrap: wrap; min-width: 0; }
.tblr-outils { display: flex; align-items: center; gap: var(--e2); flex-shrink: 0; }
.tblr-etiq {
  font-family: var(--mono); font-size: 9px; letter-spacing: 1.4px;
  color: var(--dim); text-transform: uppercase; margin-right: var(--e1);
}

/* ---- la zone de defilement ---- */
.tblr-zone { flex: 1; min-height: 0; display: flex; }

/**------- 60 vh et non 100 % : la vue vit dans `#hote-vue`, dont la hauteur
 *         n'est pas contrainte par le chassis. Sans borne, le tableau pousse la
 *         page entiere et l'entete "figee" defile avec elle - donc n'est plus
 *         figee du tout. C'est le defaut classique de `position: sticky` dans un
 *         conteneur sans hauteur bornee. -------*/
.tblr-boite { flex: 1; max-height: 60vh; min-height: 200px; border: 1px solid var(--line); }

/* ---- le tableau ---- */
.tblr-tbl { table-layout: fixed; width: max-content; min-width: 100%; }

/**------- LE `position` DE CES CELLULES EST CRITIQUE, ET C'EST LUI QUI CASSAIT
 *         LA LIGNE DE FILTRES. Mesure au navigateur a 1280, 1920 et 2400.
 *
 *         La poignee de largeur est en `position: absolute` et exige un ancetre
 *         positionne. Ce bloc declarait donc `position: relative` sur le `th` -
 *         or cette regle a EXACTEMENT la meme specificite que
 *         `.tbl th { position: sticky }` du bloc 11 et arrive apres dans le
 *         fichier : elle l'ecrasait, en silence, sans qu'aucun outil ne le dise.
 *
 *         Deux degats, et le second se voyait a la premiere seconde :
 *           1. l'entete du tableur ne collait plus au defilement, alors que
 *              "l'entete reste fige quand on defile" est une exigence ecrite de
 *              la vue ;
 *           2. `top: 30px` de la rangee de filtres cessait d'etre un calage de
 *              collant pour devenir un DECALAGE RELATIF. Un decalage relatif ne
 *              libere aucune place : la rangee descendait de 30 px en laissant
 *              un trou derriere elle et en recouvrant la premiere ligne de
 *              donnees. Geometrie relevee a 1920 avant correction :
 *              entete 236 -> 266, rangee de filtres 296 -> 326 au lieu de
 *              266 -> 296, premiere ligne de donnees a 296. D'ou la bande vide
 *              sous les intitules et les champs qui flottent par-dessus les
 *              donnees.
 *
 *         La piste "le contenu ne tient pas dans 30 px" etait fausse : la boite
 *         de filtre mesure 22 px pour 30 px disponibles, elle depasse de -6 px,
 *         c'est-a-dire qu'elle rentre.
 *
 *         `sticky` ancre les descendants absolus aussi bien que `relative` : la
 *         poignee ne perd rien, et les deux rangees redeviennent collantes. -------*/
.tblr-tbl th { position: sticky; }
.tblr-th-nom {
  display: inline-block; max-width: calc(100% - 18px);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: middle;
}
.tblr-verrou { margin-left: var(--e1); vertical-align: middle; }
.tblr-tbl td { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/**------- POIGNEE DE LARGEUR. `touch-action: none` est obligatoire : sans elle,
 *         un ecran tactile interprete le glisser comme un defilement et les
 *         evenements `pointermove` n'arrivent jamais jusqu'a la poignee. -------*/
.tblr-poignee {
  position: absolute; top: 0; right: 0; width: 5px; height: 100%;
  cursor: col-resize; touch-action: none; z-index: 3;
}
.tblr-poignee:hover { background: var(--yel); }

/* ---- la ligne de filtres, deuxieme rangee collante ---- */

/**------- `top: 30px` = la hauteur exacte de `.tbl th` (bloc 11). Les deux
 *         rangees sont collantes : la seconde doit se poser SOUS la premiere,
 *         sinon elles se recouvrent au defilement. Si la hauteur d'entete change
 *         au bloc 11, cette valeur doit suivre.
 *
 *         `position: sticky` est REPETE ici alors qu'il vient deja du bloc 11.
 *         Ce n'est pas une redondance de confort : `top` sans `position: sticky`
 *         ne veut pas dire "cale a 30 px du haut", il veut dire "descends de
 *         30 px" - deux comportements opposes, pour la meme ligne de CSS. Le
 *         declarer sur place rend la regle vraie toute seule, quel que soit
 *         l'ordre des blocs, et c'est exactement l'accident qu'on repare. -------*/
.tblr-filtres th {
  position: sticky; top: 30px; z-index: 1; height: 30px; padding: 0 var(--e1) var(--e1);
  background: var(--panel); cursor: default; border-bottom: 1px solid var(--line);
}
.tblr-filtres th:hover { color: var(--mut); }
.tblr-filtre { display: flex; align-items: center; gap: 2px; }

/**------- surcharge de la regle globale `input, select, textarea` (100 % de
 *         large, 11 px de remplissage, 14 px de corps). Telle quelle, elle
 *         rendrait une ligne de filtres de 44 px de haut : plus haute que deux
 *         lignes de donnees, pour un champ qu'on remplit rarement. -------*/
.tblr-filtre .tblr-op,
.tblr-filtre .tblr-val {
  height: 22px; padding: 0 4px; font-family: var(--mono); font-size: 10.5px;
  background: var(--ink); border: 1px solid var(--line2); color: var(--txt);
  text-transform: none; letter-spacing: 0;
}
.tblr-filtre .tblr-op { width: 46px; flex: 0 0 46px; color: var(--mut); cursor: pointer; }
.tblr-filtre .tblr-val { flex: 1; min-width: 0; }
.tblr-filtre .tblr-val:focus, .tblr-filtre .tblr-op:focus { border-color: var(--yel); }
.tblr-filtre-off {
  font-family: var(--mono); font-size: 9px; letter-spacing: 1px;
  color: var(--dim); text-transform: uppercase; padding: 4px 0;
}

/* ---- corps ---- */
.tblr-vide { text-align: center; color: var(--dim); height: 120px; }

/* ---- total en pied, DANS le tableau : il doit rester colle a ses colonnes ---- */

/**------- collant en BAS, pas en haut. Un total qui defile hors de l'ecran
 *         oblige a redescendre pour le lire, et personne ne le fait : autant ne
 *         pas l'afficher. -------*/
.tblr-tbl tfoot td {
  position: sticky; bottom: 0; z-index: 2;
  background: var(--panel2); border-top: 1px solid var(--line); border-bottom: none;
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  color: var(--txt); height: 30px;
}
.tblr-total-etiq { font-size: 9px; letter-spacing: 1.4px; color: var(--yel); }

/* ---- pied de page : compte, portee du calcul, pagination ---- */
.tblr-pied {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--e3); flex-wrap: wrap;
  font-family: var(--mono); font-size: 9.5px; color: var(--dim);
}
.tblr-pied-g { display: flex; flex-direction: column; gap: var(--e1); min-width: 0; }
.tblr-pied-d { display: flex; align-items: center; gap: var(--e2); flex-shrink: 0; }
.tblr-note { font-family: var(--body); font-size: 11px; color: var(--dim); line-height: 1.5; }
.tblr-note-org { color: var(--org); display: flex; align-items: center; gap: var(--e2); flex-wrap: wrap; }

/* ---- selecteur de colonnes (dans la modale) ---- */
.tblr-grp { margin-bottom: var(--e4); }
.tblr-grp-tete {
  display: flex; align-items: center; justify-content: space-between;
  border-bottom: 1px solid var(--line); padding-bottom: var(--e1); margin-bottom: var(--e2);
}
.tblr-grp-nom { font-family: var(--mono); font-size: 9px; letter-spacing: 1.6px; color: var(--yel); }
.tblr-grp-tout {
  background: none; border: 0; color: var(--dim); cursor: pointer;
  font-family: var(--mono); font-size: 9px; letter-spacing: 1.4px; text-transform: uppercase;
}
.tblr-grp-tout:hover { color: var(--txt); }
.tblr-grp-liste { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: var(--e1) var(--e3); }
.tblr-case { display: flex; align-items: center; gap: var(--e2); font-size: 12.5px; cursor: pointer; }

/**------- la case a cocher doit echapper au `width: 100%` global des `input`,
 *         qui en ferait une case large comme sa colonne. -------*/
.tblr-case input[type="checkbox"] { width: 14px; flex: 0 0 14px; height: 14px; padding: 0; accent-color: var(--yel); }
.tblr-case:hover { color: var(--txt); }

/**------- ECRAN ETROIT : le tableur reste un tableau, il ne se transforme pas en
 *         liste de fiches. On rend la barre d'outils empilable et on laisse le
 *         defilement horizontal faire son travail - c'est ce qu'un tableur fait
 *         depuis toujours, et c'est ce que l'operateur attend. -------*/
@media (max-width: 1250px) {
  .tblr-barre { flex-direction: column; align-items: stretch; }
  .tblr-outils { justify-content: flex-end; }
  .tblr-boite { max-height: 55vh; }
}
