@charset "UTF-8";
/* =====================================================================
   CENTRE DE CONTRÔLE MAACO — centre.css
   Feuille maîtresse. Écrite à la main, servie telle quelle.
   Aucune chaîne de build, aucun CDN, aucune ressource distante.

   SOMMAIRE
     0.  Polices auto-hébergées
     1.  Jetons (couleurs, typographie, espacements, états)
     2.  Réinitialisation et base
     3.  Éléments bruts (une vue non stylée reste lisible)
     4.  Le poste : rail, en-tête, contenu, pied
     5.  Le bandeau de fraîcheur (règle n°1)
     6.  Blocs : cartes, panneaux, grilles
     7.  Valeurs : KPI, deltas, chasse fixe
     8.  États : pastilles, badges, formes
     9.  Tableaux
     10. Formulaires et boutons
     11. Tuiles de site, fils, listes
     12. Messages : flash, vide, échec, chargement
     13. Sparklines et jauges
     14. La carte vivante
     15. La porte (écran de connexion)
     16. Le mur (densité de grand écran)
     17. Utilitaires
     18. Mouvement réduit, impression, écrans étroits

   ─── LES TROIS RÈGLES DE LOIN, gravées dans les jetons ────────────────
   1. LE VIOLET DE MARQUE NE PORTE JAMAIS DE TEXTE. #61398F sur #020202
      plafonne à 2,5:1. Il reste la couleur d'identité — fonds, remplissages,
      halos, traits, points de carte. Tout texte « violet » est
      --violet-pale (10,4:1), servi par le jeton --texte-accent. Personne ne
      doit pouvoir réintroduire du texte en --violet par réflexe : aucune
      règle de cette feuille ne pose `color: var(--violet)`.
   2. --blanc-40 (3,3:1) EST INTERDIT SUR LE MUR, et n'est employé ici que
      pour du décor non textuel. --blanc-60 (6,5:1) est le plancher du
      texte secondaire, --blanc (17,9:1) celui du texte principal.
   3. JAMAIS D'ÉTAT CODÉ PAR LA SEULE COULEUR. Chaque état porte une FORME :
      disque plein = à jour, triangle = retardé, carré barré = muet. Le
      rouge textuel est éclairci à #ff6b78 (#e83d4d n'est qu'à 5,1:1).
   ===================================================================== */


/* =====================================================================
   0. POLICES AUTO-HÉBERGÉES — variable fonts, zéro Google CDN
   Un seul woff2 par famille couvre tous les poids (Montserrat 100–900,
   Caveat 400–700). Si les fichiers manquent, la pile système prend le
   relais et RIEN ne casse : c'est pour cela que chaque pile de polices
   de cette feuille nomme des polices système derrière la nôtre.
   ===================================================================== */
@font-face {
    font-family: 'Montserrat';
    src: url('../fonts/montserrat-var.woff2') format('woff2');
    font-weight: 100 900;
    font-style: normal;
    font-display: swap;
}
@font-face {
    font-family: 'Caveat';
    src: url('../fonts/caveat-var.woff2') format('woff2');
    font-weight: 400 700;
    font-style: normal;
    font-display: swap;
}


/* =====================================================================
   1. JETONS
   ===================================================================== */
:root {
    /* ── Palette MAACO, face sombre : noir 65 % / violet 25 % / blanc 10 % */
    --noir:     #020202;
    --noir-100: #0a0a0a;
    --noir-200: #111111;
    --noir-300: #1a1a1a;   /* cartes */
    --noir-400: #242424;   /* survol */
    --noir-500: #333333;   /* bordures */

    --violet:        #61398F;   /* IDENTITÉ — fonds, traits, points. JAMAIS du texte. */
    --violet-dark:   #4d2a72;
    --violet-medium: #7a4db0;
    --violet-light:  #9670c8;
    --violet-pale:   #c4aee4;   /* la seule déclinaison violette lisible en texte */

    --blanc:    #F0EEEE;
    --blanc-80: rgba(240, 238, 238, 0.80);
    --blanc-60: rgba(240, 238, 238, 0.60);
    --blanc-40: rgba(240, 238, 238, 0.40);   /* décor uniquement, jamais du texte de mur */
    --blanc-20: rgba(240, 238, 238, 0.20);
    --blanc-10: rgba(240, 238, 238, 0.10);
    --blanc-05: rgba(240, 238, 238, 0.05);

    --rouge: #e83d4d;
    --vert:  #22c55e;
    --jaune: #f59e0b;

    /* ── Fond du centre.
       #0d0b12, le noir légèrement violacé, est PRÉFÉRÉ au #020202 pur :
       sur LCD et surtout au vidéoprojecteur, le noir absolu écrase les
       dégradés en bandes. Sur dalle OLED, basculer --fond sur #020202
       depuis les paramètres (un jeton, deux valeurs selon le matériel). */
    --fond:        #0d0b12;
    --fond-relief: #131019;   /* rail, en-tête, bandeaux */
    --fond-carte:  #171320;   /* surface d'une carte */
    --fond-creux:  #0a0810;   /* fosses : entêtes de tableau, champs */
    --bord:        rgba(240, 238, 238, 0.10);
    --bord-fort:   rgba(240, 238, 238, 0.18);

    /* ── Rôles de texte. Aucune vue n'écrit une couleur littérale. */
    --texte:        var(--blanc);                    /* 16,9:1 sur --fond  — mesuré */
    --texte-second: var(--blanc-80);                 /* 11,7:1 */
    /* PLANCHER DU TEXTE. La charte donne --blanc-60 (6,5:1 sur #020202) ;
       mais notre fond est #0d0b12, plus clair de trois points, et le même
       60 % n'y vaut plus que 6,4:1 — sous la cible de 7:1. On monte donc à
       66 %, ce qui rend 7,2:1 mesurés. Le jour où --fond repasse en
       #020202 pour une dalle OLED, 60 % suffirait de nouveau ; 66 % reste
       juste dans les deux cas, alors qu'un jeton par matériel ne le
       serait pas. --blanc-40 (3,4:1) n'est JAMAIS du texte. */
    --texte-faible: rgba(240, 238, 238, 0.66);       /*  7,2:1 */
    --texte-accent: var(--violet-pale);              /*  9,8:1 — règle 1 */

    /* ── États. Couleur ET forme : voir §8. */
    --etat-ok:      #22c55e;   /* 9,1:1  · disque plein   · à jour */
    --etat-retarde: #f59e0b;   /* 9,6:1  · triangle       · retardé */
    --etat-muet:    #ff6b78;   /* ~7:1   · carré barré    · muet */
    --etat-neutre:  var(--blanc-60);
    --etat-ok-voile:      rgba(34, 197, 94, 0.14);
    --etat-retarde-voile: rgba(245, 158, 11, 0.14);
    --etat-muet-voile:    rgba(255, 107, 120, 0.14);
    --etat-accent-voile:  rgba(97, 57, 143, 0.22);

    /* ── Âge de la donnée. S'applique aux BLOCS, jamais aux textes :
       un chiffre d'hier reste parfaitement lisible, il est seulement
       reconnaissable au premier coup d'œil. */
    --donnee-fraiche: 1;
    --donnee-tiede:   0.88;
    --donnee-figee:   0.72;

    /* ── Géographie */
    --geo-terre:     #1c1726;
    --geo-trait:     rgba(196, 174, 228, 0.22);
    --geo-graticule: rgba(240, 238, 238, 0.06);
    --geo-ville:     #c4aee4;
    --geo-pays:      rgba(196, 174, 228, 0.45);   /* repli pays : contour pointillé, sans onde */
    --geo-braise:    rgba(196, 174, 228, 0.22);
    --geo-fantome:   rgba(196, 174, 228, 0.10);
    --geo-commerce:  #7ee0a8;                      /* un add_to_cart n'est pas une page vue */

    /* ── Typographie */
    --police:        'Montserrat', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
    --police-signe:  'Caveat', 'Segoe Script', cursive;   /* signature seule, JAMAIS une donnée */
    --police-code:   ui-monospace, 'Cascadia Mono', Consolas, 'Courier New', monospace;

    /* Échelle du POSTE (écran de bureau, 60 cm) : 13/15/18/24/32/48 */
    --t-xs: 12px;
    --t-sm: 13px;
    --t-md: 15px;
    --t-lg: 18px;
    --t-xl: 24px;
    --t-2xl: 32px;
    --t-3xl: 48px;

    /* ── Espacements 4/8 */
    --e-1: 4px;  --e-2: 8px;  --e-3: 12px; --e-4: 16px;
    --e-5: 24px; --e-6: 32px; --e-7: 48px; --e-8: 64px;

    /* ── Rayons */
    --r-xs: 4px; --r-sm: 8px; --r-md: 12px; --r-lg: 20px; --r-xl: 28px; --r-full: 999px;

    /* ── Ombres. --lueur sert les points de carte EN PEINTURE STATIQUE,
       jamais en animation (on n'anime que transform et opacity). */
    --ombre-sm: 0 2px 8px rgba(0, 0, 0, 0.45);
    --ombre-md: 0 6px 24px rgba(0, 0, 0, 0.50);
    --ombre-lg: 0 16px 48px rgba(0, 0, 0, 0.55);
    --lueur:    0 0 60px rgba(97, 57, 143, 0.45);

    /* ── Transitions */
    --t-rapide: 150ms ease;
    --t-base:   200ms ease;
    --t-douce:  300ms cubic-bezier(0.4, 0, 0.2, 1);
    --t-lente:  500ms cubic-bezier(0.4, 0, 0.2, 1);

    /* ── Grille du poste */
    --rail: 248px;

    /* ── Échelle du mur, réglable depuis le centre : 0,75 / 1 / 1,25.
       La même molette adapte un mur à une salle de 3 m ou de 8 m sans
       redéploiement. Sans effet hors de .mur. */
    --echelle: 1;

    color-scheme: dark;
}

/* Poste plus large : on desserre, on n'ajoute pas de contenu. */
@media (min-width: 1600px) {
    :root { --rail: 272px; }
}


/* =====================================================================
   2. RÉINITIALISATION ET BASE
   ===================================================================== */
*, *::before, *::after { box-sizing: border-box; }

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

body {
    margin: 0;
    background: var(--fond);
    color: var(--texte);
    font-family: var(--police);
    font-weight: 500;
    font-size: var(--t-md);
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
ul, ol { margin: 0; padding: 0; }

/* Focus visible et généreux : le centre s'utilise aussi au clavier, et
   un liseré fin disparaît sur une dalle mal réglée. */
:focus-visible {
    outline: 2px solid var(--violet-light);
    outline-offset: 2px;
    border-radius: var(--r-xs);
}

::selection { background: var(--violet); color: var(--blanc); }

/* Barres de défilement du poste. (Sur le mur, toute barre est un bug.) */
* { scrollbar-color: var(--noir-500) transparent; scrollbar-width: thin; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--noir-500); border-radius: var(--r-full); }
::-webkit-scrollbar-thumb:hover { background: #444; }


/* =====================================================================
   3. ÉLÉMENTS BRUTS
   Une vue livrée sans une seule classe doit rester présentable : c'est la
   filet de sécurité de cette feuille, et il vaut mieux que n'importe
   quelle liste de classes devinées.
   ===================================================================== */
h1 { font-size: var(--t-xl);  font-weight: 700; line-height: 1.25; letter-spacing: -0.01em; }
h2 { font-size: var(--t-lg);  font-weight: 700; line-height: 1.3; margin-top: var(--e-6); }
h3 { font-size: var(--t-md);  font-weight: 700; line-height: 1.35; margin-top: var(--e-5); }
h4 { font-size: var(--t-sm);  font-weight: 700; text-transform: uppercase; letter-spacing: 0.12em;
     color: var(--texte-faible); margin-top: var(--e-4); }

/* Un titre juste après son conteneur n'a pas à pousser une marge. */
:where(section, article, .carte, .bloc, .panneau) > :where(h2, h3, h4):first-child { margin-top: 0; }

p { margin: 0 0 var(--e-3); }
p:last-child { margin-bottom: 0; }

a { color: var(--texte-accent); text-decoration-color: rgba(196, 174, 228, 0.35);
    text-underline-offset: 3px; transition: color var(--t-rapide); }
a:hover { color: var(--blanc); text-decoration-color: currentColor; }

strong, b { font-weight: 700; color: var(--texte); }
small { font-size: var(--t-sm); color: var(--texte-faible); }
hr { border: 0; border-top: 1px solid var(--bord); margin: var(--e-5) 0; }

code, kbd, samp, pre {
    font-family: var(--police-code);
    font-size: 0.92em;
}
code {
    background: var(--fond-creux);
    border: 1px solid var(--bord);
    border-radius: var(--r-xs);
    padding: 1px 6px;
    color: var(--texte-accent);
}
pre {
    background: var(--fond-creux);
    border: 1px solid var(--bord);
    border-radius: var(--r-sm);
    padding: var(--e-4);
    overflow: auto;
    margin: 0 0 var(--e-4);
}
pre code { background: none; border: 0; padding: 0; color: var(--texte-second); }

ul, ol { padding-left: 1.25em; }
ul.nu, ol.nu, ul[class], ol[class] { list-style: none; padding-left: 0; }
li { margin: 0 0 var(--e-1); }

dl { display: grid; grid-template-columns: auto 1fr; gap: var(--e-2) var(--e-4); }
dt { color: var(--texte-faible); }
dd { margin: 0; font-variant-numeric: tabular-nums; }

img, svg, video, canvas { max-width: 100%; }
img { height: auto; border-radius: var(--r-sm); }

abbr[title] { text-decoration: underline dotted; cursor: help; }
time { font-variant-numeric: tabular-nums; }


/* =====================================================================
   4. LE POSTE — rail, en-tête, contenu, pied
   Le gabarit pose déjà un socle de secours en ligne ; cette feuille est
   chargée après et a le dernier mot.
   ===================================================================== */
body.poste {
    display: grid;
    grid-template-columns: var(--rail) 1fr;
    min-height: 100vh;
    min-height: 100dvh;
}

/* ── Rail de navigation ─────────────────────────────────────────────── */
.poste-rail {
    background: var(--fond-relief);
    border-right: 1px solid var(--bord);
    padding: var(--e-5) 0 var(--e-6);
    display: flex;
    flex-direction: column;
    gap: 2px;
    position: sticky;
    top: 0;
    align-self: start;
    max-height: 100vh;
    max-height: 100dvh;
    overflow-y: auto;
}

/* La marque. Le violet est ici un FOND et un trait, jamais une encre. */
.poste-rail .marque {
    display: block;
    padding: 0 var(--e-5) var(--e-5);
    margin-bottom: var(--e-2);
    font-size: var(--t-lg);
    font-weight: 800;
    letter-spacing: 0.02em;
    line-height: 1.2;
    color: var(--texte);
    text-decoration: none;
    border-bottom: 1px solid var(--bord);
    position: relative;
}
.poste-rail .marque::before {
    content: "";
    position: absolute;
    left: 0; top: 2px; bottom: var(--e-5);
    width: 3px;
    background: var(--violet);
    border-radius: 0 var(--r-full) var(--r-full) 0;
}
.poste-rail .marque span { font-size: var(--t-sm); font-weight: 500; color: var(--texte-accent); }
.poste-rail .marque:hover { color: var(--texte); }

.nav-titre {
    padding: var(--e-5) var(--e-5) var(--e-2);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--blanc-40);
}

.nav-item {
    display: flex;
    align-items: center;
    gap: var(--e-2);
    padding: 9px var(--e-5);
    color: var(--texte-faible);
    text-decoration: none;
    font-size: 14px;
    font-weight: 500;
    border-left: 3px solid transparent;
    transition: background var(--t-rapide), color var(--t-rapide);
}
.nav-item:hover { background: var(--noir-400); color: var(--texte); }
.nav-item.actif {
    color: var(--texte);
    font-weight: 700;
    background: var(--etat-accent-voile);
    border-left-color: var(--violet-medium);
}
/* Un lien grisé qui annonce honnêtement « à venir » vaut mieux qu'un lien
   qui mène à un 404 devant un client. */
.nav-item.a-venir {
    color: rgba(240, 238, 238, 0.28);
    cursor: default;
}
.nav-item.a-venir::after {
    content: "à venir";
    margin-left: auto;
    font-size: 10px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: rgba(240, 238, 238, 0.22);
}
/* Compteur d'alertes accolé à une entrée de navigation. */
.nav-item .compteur {
    margin-left: auto;
    min-width: 20px;
    padding: 1px 6px;
    border-radius: var(--r-full);
    background: var(--etat-muet-voile);
    color: var(--etat-muet);
    font-size: 11px;
    font-weight: 700;
    text-align: center;
    font-variant-numeric: tabular-nums;
}

/* ── Corps ──────────────────────────────────────────────────────────── */
.poste-corps {
    display: flex;
    flex-direction: column;
    min-width: 0;   /* sans quoi un tableau large fait déborder toute la grille */
}

.poste-entete {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--e-4);
    padding: var(--e-4) var(--e-6);
    border-bottom: 1px solid var(--bord);
    background: var(--fond-relief);
    position: sticky;
    top: 0;
    z-index: 20;
}
.poste-entete h1 { font-size: 22px; font-weight: 700; }
.poste-entete > div { display: flex; align-items: center; gap: var(--e-5); font-size: var(--t-sm); color: var(--texte-faible); }
#horloge-poste { font-weight: 700; color: var(--texte-second); letter-spacing: 0.02em; }

.poste-contenu {
    flex: 1;
    padding: var(--e-5) var(--e-6) var(--e-7);
    min-width: 0;
}

.poste-pied {
    padding: var(--e-4) var(--e-6);
    border-top: 1px solid var(--bord);
    color: var(--texte-faible);
    font-size: var(--t-sm);
}
/* Caveat n'apparaît qu'ici : une signature. Jamais sur une donnée —
   une écriture manuscrite lue à quatre mètres n'est pas lue. */
.poste-pied .signature,
.signature { font-family: var(--police-signe); font-size: 17px; color: var(--texte-accent); }

/* Barre d'actions d'un écran (boutons, filtres), sous le titre. */
.barre-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--e-3);
    margin-bottom: var(--e-5);
}
.barre-actions .pousse, .pousse { margin-left: auto; }


/* =====================================================================
   5. LE BANDEAU DE FRAÎCHEUR — règle n°1
   Écrit une fois dans le gabarit, hérité partout. Un écran qui ne déclare
   rien n'obtient pas un bandeau vide : il obtient un aveu en évidence.
   ===================================================================== */
.bandeau-fraicheur {
    display: flex;
    align-items: center;
    gap: var(--e-3);
    padding: 9px var(--e-6);
    font-size: var(--t-sm);
    line-height: 1.45;
    background: var(--fond-relief);
    border-bottom: 1px solid var(--bord);
    color: var(--texte-second);
    position: sticky;
    top: 57px;              /* sous l'en-tête */
    z-index: 19;
}
.bandeau-fraicheur .pastille {
    width: 9px; height: 9px;
    border-radius: 50%;
    flex: none;
    box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.04);
}
/* La forme accompagne toujours la couleur (règle 3). Le gabarit émet le
   glyphe ● ▲ ■ ; on lui donne juste la bonne teinte et une chasse stable. */
.bandeau-fraicheur > [aria-hidden]:not(.pastille) { font-size: 11px; line-height: 1; }

.fraicheur-fraiche .pastille { background: var(--etat-ok); }
.fraicheur-fraiche > [aria-hidden]:not(.pastille) { color: var(--etat-ok); }

.fraicheur-tiede { background: linear-gradient(to right, var(--etat-retarde-voile), transparent 60%), var(--fond-relief); }
.fraicheur-tiede .pastille { background: var(--etat-retarde); }
.fraicheur-tiede > [aria-hidden]:not(.pastille) { color: var(--etat-retarde); }

.fraicheur-figee { background: linear-gradient(to right, var(--etat-muet-voile), transparent 60%), var(--fond-relief); }
.fraicheur-figee .pastille { background: var(--etat-muet); }
.fraicheur-figee > [aria-hidden]:not(.pastille) { color: var(--etat-muet); }

.fraicheur-absente {
    background: linear-gradient(to right, var(--etat-muet-voile), transparent 70%), var(--fond-relief);
    color: var(--texte);
    border-bottom-color: rgba(255, 107, 120, 0.35);
}
.fraicheur-absente .pastille { background: var(--etat-muet); }
.fraicheur-absente > [aria-hidden]:not(.pastille) { color: var(--etat-muet); }
.fraicheur-absente strong { color: var(--etat-muet); }
.fraicheur-absente code { color: var(--texte-accent); }

/* Marque de fraîcheur DANS un bloc (chaque bloc a sa cadence : le direct
   à 10 s et l'instantané à 5 min ne se mélangent pas). */
.fraicheur {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
    font-size: var(--t-xs);
    font-weight: 500;
    color: var(--texte-faible);
    font-variant-numeric: tabular-nums;
}
.fraicheur::before {
    content: "●";
    font-size: 8px;
    line-height: 1;
    color: var(--etat-ok);
    transform: translateY(-2px);
}
.fraicheur[data-etat="tiede"]::before   { content: "▲"; color: var(--etat-retarde); }
.fraicheur[data-etat="figee"]::before   { content: "■"; color: var(--etat-muet); }
.fraicheur[data-etat="absente"]::before { content: "■"; color: var(--etat-muet); }
.fraicheur[data-etat="absente"] { color: var(--etat-muet); }

/* La mention du retrait accompagne TOUT total amputé d'un site muet. */
.retrait {
    display: inline-block;
    margin-left: var(--e-2);
    padding: 1px 8px;
    border-radius: var(--r-full);
    background: var(--etat-retarde-voile);
    color: var(--etat-retarde);
    font-size: var(--t-xs);
    font-weight: 700;
}

/* ── L'âge appliqué aux BLOCS, jamais aux textes ────────────────────── */
[data-fraicheur="fraiche"] { opacity: var(--donnee-fraiche); }
[data-fraicheur="tiede"]   { opacity: var(--donnee-tiede); }
[data-fraicheur="figee"],
[data-fraicheur="absente"] { opacity: var(--donnee-figee); }

/* Un bloc figé porte en plus une barre oblique : l'opacité seule pourrait
   passer pour un réglage d'écran. */
[data-fraicheur="figee"],
[data-fraicheur="absente"] {
    position: relative;
    background-image: repeating-linear-gradient(
        135deg,
        rgba(255, 107, 120, 0.05) 0 10px,
        transparent 10px 20px
    );
}
/* …mais le texte, lui, reprend toute son opacité : un chiffre d'hier reste
   parfaitement lisible, il est seulement reconnaissable. */
[data-fraicheur] .valeur,
[data-fraicheur] .kpi-valeur { opacity: 1; }


/* =====================================================================
   6. BLOCS — cartes, panneaux, grilles
   Alias volontaires : .carte, .bloc, .panneau, .encart, .boite désignent
   la même surface. Mieux vaut trois noms qui marchent qu'un seul qu'un
   autre écran n'aura pas deviné.
   ===================================================================== */
.carte, .bloc, .panneau, .boite, .encart {
    background: var(--fond-carte);
    border: 1px solid var(--bord);
    border-radius: var(--r-md);
    padding: var(--e-5);
    margin-bottom: var(--e-4);
}
.carte-entete, .bloc-entete, .panneau-entete {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--e-3);
    margin-bottom: var(--e-4);
    padding-bottom: var(--e-3);
    border-bottom: 1px solid var(--bord);
}
.carte-entete h2, .bloc-entete h2, .panneau-entete h2,
.carte-entete h3, .bloc-entete h3, .panneau-entete h3 { margin: 0; }

.carte-titre, .bloc-titre, .panneau-titre {
    font-size: var(--t-sm);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--texte-faible);
}
.carte-pied, .bloc-pied {
    margin-top: var(--e-4);
    padding-top: var(--e-3);
    border-top: 1px solid var(--bord);
    font-size: var(--t-sm);
    color: var(--texte-faible);
}

/* Carte cliquable (une tuile de site, une ligne de flotte). */
a.carte, .carte-lien { display: block; text-decoration: none; color: inherit; transition: border-color var(--t-rapide), background var(--t-rapide), transform var(--t-rapide); }
a.carte:hover, .carte-lien:hover { background: var(--noir-400); border-color: var(--bord-fort); color: inherit; }

/* Un accent violet en filet, jamais en encre. */
.carte-accent { border-left: 3px solid var(--violet); }
.carte-danger { border-left: 3px solid var(--etat-muet); }
.carte-attention { border-left: 3px solid var(--etat-retarde); }

/* ── Grilles ─────────────────────────────────────────────────────────── */
.grille { display: grid; gap: var(--e-4); }
.grille-2 { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }
.grille-3 { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.grille-4 { grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); }
.grille-auto { grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }

/* Deux colonnes principal / latéral, qui s'empilent d'elles-mêmes. */
.colonnes { display: grid; gap: var(--e-4); grid-template-columns: minmax(0, 2fr) minmax(280px, 1fr); }
@media (max-width: 1100px) { .colonnes { grid-template-columns: 1fr; } }

.rangee { display: flex; flex-wrap: wrap; align-items: center; gap: var(--e-3); }


/* =====================================================================
   7. VALEURS — KPI, deltas
   font-variant-numeric: tabular-nums SUR TOUTE VALEUR QUI CHANGE. Sans
   chasse fixe, un compteur qui passe de 1 199 à 1 200 fait sauter la
   ligne, et à distance ce tremblement est la première chose que l'œil
   attrape.
   ===================================================================== */
.valeur, .kpi-valeur, .nombre, .chiffre, .tabulaire,
td.valeur, .montant, .compteur, .delta, .duree {
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
}

.kpi {
    background: var(--fond-carte);
    border: 1px solid var(--bord);
    border-radius: var(--r-md);
    padding: var(--e-4) var(--e-5);
    display: flex;
    flex-direction: column;
    gap: var(--e-1);
    min-width: 0;
}
.kpi-libelle, .kpi .libelle {
    font-size: var(--t-xs);
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--texte-faible);
}
.kpi-valeur, .kpi .valeur {
    font-size: var(--t-2xl);
    font-weight: 800;
    line-height: 1.05;
    letter-spacing: -0.02em;
    color: var(--texte);
}
.kpi-valeur .unite, .kpi .unite {
    font-size: 0.45em;
    font-weight: 600;
    color: var(--texte-faible);
    margin-left: 4px;
    letter-spacing: 0;
}
.kpi-note, .kpi .note { font-size: var(--t-xs); color: var(--texte-faible); }

/* Un KPI sans donnée n'affiche pas 0 : zéro n'est pas « je ne sais pas ». */
.kpi-valeur.sans-donnee, .valeur.sans-donnee, .sans-donnee {
    color: var(--texte-faible);
    font-weight: 600;
}

/* Deltas. Le signe porte l'information autant que la couleur. */
.delta { font-size: var(--t-sm); font-weight: 700; display: inline-flex; align-items: center; gap: 3px; }
.delta-hausse { color: var(--etat-ok); }
.delta-baisse { color: var(--etat-muet); }
.delta-plat   { color: var(--texte-faible); }
.delta-hausse::before { content: "▲"; font-size: 8px; }
.delta-baisse::before { content: "▼"; font-size: 8px; }
.delta-plat::before   { content: "—"; font-size: 10px; }

/* Une devise ne s'additionne jamais avec une autre : on l'affiche donc
   toujours, et en retrait pour que la valeur reste le sujet. */
.devise { font-size: 0.5em; font-weight: 600; color: var(--texte-faible); margin-left: 4px; }


/* =====================================================================
   8. ÉTATS — pastilles, badges, formes
   RÈGLE 3 : aucun état n'est codé par la seule couleur. Chaque pastille
   porte un glyphe — disque plein (à jour), triangle (retardé), carré
   barré (muet) — lisible en niveaux de gris comme par un daltonien.
   ===================================================================== */
.pastille, .puce {
    display: inline-block;
    width: 9px; height: 9px;
    border-radius: 50%;
    flex: none;
    background: var(--etat-neutre);
}

.etat {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 2px 10px 2px 8px;
    border-radius: var(--r-full);
    font-size: var(--t-xs);
    font-weight: 700;
    letter-spacing: 0.04em;
    white-space: nowrap;
    border: 1px solid transparent;
}
.etat::before { font-size: 9px; line-height: 1; }

.etat-ok, .etat.ok, .etat-actif, .etat-a-jour {
    background: var(--etat-ok-voile); color: var(--etat-ok); border-color: rgba(34, 197, 94, 0.35);
}
.etat-ok::before, .etat.ok::before, .etat-actif::before, .etat-a-jour::before { content: "●"; }

.etat-retarde, .etat.retarde, .etat-attention, .etat-suspendu {
    background: var(--etat-retarde-voile); color: var(--etat-retarde); border-color: rgba(245, 158, 11, 0.35);
}
.etat-retarde::before, .etat.retarde::before, .etat-attention::before, .etat-suspendu::before { content: "▲"; }

.etat-muet, .etat.muet, .etat-erreur, .etat-panne, .etat-injoignable {
    background: var(--etat-muet-voile); color: var(--etat-muet); border-color: rgba(255, 107, 120, 0.35);
}
.etat-muet::before, .etat.muet::before, .etat-erreur::before, .etat-panne::before, .etat-injoignable::before { content: "■"; }

.etat-neutre, .etat.neutre, .etat-inconnu, .etat-attente {
    background: var(--blanc-05); color: var(--texte-faible); border-color: var(--bord);
}
.etat-neutre::before, .etat.neutre::before, .etat-inconnu::before, .etat-attente::before { content: "○"; }

/* Badge neutre d'information (module, version, contrat, canal…). */
.badge, .etiquette, .puce-texte {
    display: inline-block;
    padding: 2px 9px;
    border-radius: var(--r-full);
    background: var(--blanc-05);
    border: 1px solid var(--bord);
    color: var(--texte-second);
    font-size: var(--t-xs);
    font-weight: 600;
    white-space: nowrap;
}
.badge-accent { background: var(--etat-accent-voile); border-color: rgba(97, 57, 143, 0.5); color: var(--texte-accent); }
.badge-absent { color: var(--blanc-40); border-style: dashed; }

/* Un site muet est estompé ET barré : deux signaux, pas un. */
.muet, .est-muet {
    opacity: 0.72;
    text-decoration: line-through;
    text-decoration-color: rgba(255, 107, 120, 0.55);
    text-decoration-thickness: 1px;
}


/* =====================================================================
   9. TABLEAUX
   Le tableau 12 px du backoffice source ne peut pas être repris tel quel.
   Ici : 14 px, lignes de 40 px, chiffres à droite et à chasse fixe.
   ===================================================================== */
.table-enveloppe, .tableau-enveloppe {
    overflow-x: auto;
    border: 1px solid var(--bord);
    border-radius: var(--r-md);
    background: var(--fond-carte);
}
.table-enveloppe > table, .tableau-enveloppe > table { border: 0; border-radius: 0; margin: 0; }

table, .tableau {
    width: 100%;
    border-collapse: collapse;
    font-size: 14px;
    background: var(--fond-carte);
    border: 1px solid var(--bord);
    border-radius: var(--r-md);
    overflow: hidden;
    margin-bottom: var(--e-4);
}
caption {
    caption-side: top;
    text-align: left;
    padding: var(--e-3) var(--e-4);
    font-size: var(--t-sm);
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--texte-faible);
}
thead th {
    position: sticky;
    top: 0;
    background: var(--fond-creux);
    text-align: left;
    font-size: var(--t-xs);
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--texte-faible);
    padding: var(--e-3) var(--e-4);
    border-bottom: 1px solid var(--bord-fort);
    white-space: nowrap;
    z-index: 1;
}
tbody td {
    padding: var(--e-3) var(--e-4);
    border-bottom: 1px solid var(--blanc-05);
    vertical-align: middle;
    color: var(--texte-second);
}
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover td { background: var(--noir-400); color: var(--texte); }
tbody td:first-child { color: var(--texte); font-weight: 600; }

/* Chiffres : à droite, chasse fixe. Une colonne de nombres alignés à
   gauche est illisible dès qu'on la compare. */
td.num, th.num, td.valeur, .col-num, td[data-num], th[data-num] {
    text-align: right;
    font-variant-numeric: tabular-nums;
}
td.actions, th.actions { text-align: right; white-space: nowrap; }
tfoot td { padding: var(--e-3) var(--e-4); border-top: 1px solid var(--bord-fort);
           font-weight: 700; color: var(--texte); background: var(--fond-creux); }

/* Ligne d'un site muet dans un tableau de flotte. */
tr.muet td, tr.est-muet td { opacity: 0.7; }
tr.selectionnee td, tr.actif td { background: var(--etat-accent-voile); }

/* Tri de colonne. */
th[aria-sort] { cursor: pointer; user-select: none; }
th[aria-sort]::after { content: "↕"; margin-left: 6px; opacity: 0.35; }
th[aria-sort="ascending"]::after  { content: "↑"; opacity: 1; color: var(--texte-accent); }
th[aria-sort="descending"]::after { content: "↓"; opacity: 1; color: var(--texte-accent); }


/* =====================================================================
   10. FORMULAIRES ET BOUTONS
   ===================================================================== */
form { margin: 0; }
fieldset { border: 1px solid var(--bord); border-radius: var(--r-md); padding: var(--e-4); margin: 0 0 var(--e-4); }
legend { padding: 0 var(--e-2); font-size: var(--t-sm); font-weight: 700; color: var(--texte-faible);
         text-transform: uppercase; letter-spacing: 0.1em; }

.champ, .groupe-champ { display: flex; flex-direction: column; gap: 6px; margin-bottom: var(--e-4); }

label { font-size: var(--t-sm); font-weight: 600; color: var(--texte-second); }
.aide, .indication, .champ-aide { font-size: var(--t-xs); color: var(--texte-faible); line-height: 1.5; }

input[type="text"], input[type="email"], input[type="password"], input[type="search"],
input[type="url"], input[type="number"], input[type="date"], input[type="datetime-local"],
input[type="time"], select, textarea {
    width: 100%;
    padding: 10px var(--e-3);
    background: var(--fond-creux);
    border: 1px solid var(--bord-fort);
    border-radius: var(--r-sm);
    color: var(--texte);
    font-family: inherit;
    font-size: var(--t-md);
    font-weight: 500;
    line-height: 1.4;
    transition: border-color var(--t-rapide), background var(--t-rapide);
}
input::placeholder, textarea::placeholder { color: var(--blanc-40); }
input:hover, select:hover, textarea:hover { border-color: rgba(240, 238, 238, 0.3); }
input:focus, select:focus, textarea:focus {
    outline: none;
    border-color: var(--violet-medium);
    background: var(--noir-200);
    box-shadow: 0 0 0 3px rgba(97, 57, 143, 0.35);
}
input[aria-invalid="true"], .champ-erreur input, .champ-erreur select {
    border-color: var(--etat-muet);
    box-shadow: 0 0 0 3px rgba(255, 107, 120, 0.18);
}
input:disabled, select:disabled, textarea:disabled, button:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}
textarea { min-height: 110px; resize: vertical; }
select {
    appearance: none;
    background-image: linear-gradient(45deg, transparent 50%, var(--texte-faible) 50%),
                      linear-gradient(135deg, var(--texte-faible) 50%, transparent 50%);
    background-position: right 16px center, right 11px center;
    background-size: 5px 5px, 5px 5px;
    background-repeat: no-repeat;
    padding-right: var(--e-6);
}
/* Un jeton ne s'affiche jamais en clair : le champ de saisie est en
   chasse fixe pour qu'un collage se relise, et rien n'est réaffiché. */
input.jeton, input[type="password"] { font-family: var(--police-code); letter-spacing: 0.04em; }

/* Cases et boutons radio, avec une zone de clic honnête. */
.case, .bascule {
    display: flex;
    align-items: flex-start;
    gap: var(--e-3);
    padding: var(--e-3);
    border: 1px solid var(--bord);
    border-radius: var(--r-sm);
    background: var(--blanc-05);
    cursor: pointer;
    margin-bottom: var(--e-2);
}
.case:hover, .bascule:hover { border-color: var(--bord-fort); background: var(--noir-400); }
.case input[type="checkbox"], .case input[type="radio"],
input[type="checkbox"], input[type="radio"] {
    width: 17px; height: 17px;
    flex: none;
    margin: 2px 0 0;
    accent-color: var(--violet-medium);
    cursor: pointer;
}
.case label { cursor: pointer; font-weight: 600; color: var(--texte); }

/* ── Boutons ────────────────────────────────────────────────────────── */
.bouton, .btn, button, input[type="submit"], input[type="button"] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--e-2);
    padding: 10px var(--e-4);
    border: 1px solid var(--bord-fort);
    border-radius: var(--r-sm);
    background: var(--noir-300);
    color: var(--texte);
    font-family: inherit;
    font-size: 14px;
    font-weight: 700;
    line-height: 1.2;
    text-decoration: none;
    cursor: pointer;
    white-space: nowrap;
    transition: background var(--t-rapide), border-color var(--t-rapide), color var(--t-rapide);
}
.bouton:hover, .btn:hover, button:hover, input[type="submit"]:hover {
    background: var(--noir-400);
    border-color: rgba(240, 238, 238, 0.3);
    color: var(--texte);
}

/* Bouton principal : le violet est ici un FOND, sous du blanc — c'est
   l'usage juste de la couleur de marque (13:1 en blanc sur #61398F). */
.bouton-primaire, .btn-primaire, .bouton.primaire, button[type="submit"], input[type="submit"] {
    background: linear-gradient(135deg, #61398F 0%, #7a4db0 100%);
    border-color: var(--violet-medium);
    color: #FFFFFF;
}
.bouton-primaire:hover, .btn-primaire:hover, .bouton.primaire:hover,
button[type="submit"]:hover, input[type="submit"]:hover {
    background: linear-gradient(135deg, #6d41a1 0%, #8a5ac2 100%);
    border-color: var(--violet-light);
    color: #FFFFFF;
}

.bouton-fantome, .btn-fantome { background: transparent; }
.bouton-danger, .btn-danger {
    background: rgba(232, 61, 77, 0.16);
    border-color: rgba(255, 107, 120, 0.45);
    color: var(--etat-muet);
}
.bouton-danger:hover, .btn-danger:hover { background: rgba(232, 61, 77, 0.26); color: #ffb3ba; }

.bouton-petit, .btn-petit { padding: 5px var(--e-3); font-size: var(--t-xs); }
.bouton-grand, .btn-grand { padding: 13px var(--e-5); font-size: var(--t-lg); }
.bouton-bloc, .btn-bloc { width: 100%; }

/* Groupe de bascules (période 7j / 30j / 90j, humains / robots / total). */
.segments { display: inline-flex; border: 1px solid var(--bord-fort); border-radius: var(--r-sm); overflow: hidden; }
.segments > * {
    padding: 7px var(--e-4);
    border: 0;
    border-radius: 0;
    background: transparent;
    color: var(--texte-faible);
    font-size: var(--t-sm);
    font-weight: 600;
    text-decoration: none;
}
.segments > * + * { border-left: 1px solid var(--bord); }
.segments > .actif, .segments > [aria-current] {
    background: var(--etat-accent-voile);
    color: var(--texte);
    font-weight: 700;
}


/* =====================================================================
   11. TUILES DE SITE, FILS, LISTES
   ===================================================================== */
.mosaique { display: grid; gap: var(--e-4); grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); }

.tuile-site {
    display: grid;
    gap: var(--e-3);
    background: var(--fond-carte);
    border: 1px solid var(--bord);
    border-radius: var(--r-md);
    padding: var(--e-4);
    text-decoration: none;
    color: inherit;
    /* Couleur d'accent propre au site, posée en ligne par la vue :
       style="--accent-site:#7a4db0". Un filet, jamais une encre. */
    border-top: 3px solid var(--accent-site, var(--violet));
    transition: background var(--t-rapide), border-color var(--t-rapide);
}
.tuile-site:hover { background: var(--noir-400); }
.tuile-site .entete { display: flex; align-items: center; justify-content: space-between; gap: var(--e-2); }
.tuile-site .nom { font-size: var(--t-lg); font-weight: 700; color: var(--texte); }
.tuile-site .adresse { font-size: var(--t-xs); color: var(--texte-faible); word-break: break-all; }
.tuile-site .mesures { display: flex; gap: var(--e-5); flex-wrap: wrap; }
.tuile-site .mesure { display: flex; flex-direction: column; }
.tuile-site .mesure .valeur { font-size: var(--t-xl); font-weight: 800; line-height: 1.1; }
.tuile-site .mesure .libelle { font-size: 11px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--texte-faible); }
/* Une tuile de site muet est estompée, barrée, et le dit en toutes lettres. */
.tuile-site.muet, .tuile-site.est-muet { text-decoration: none; opacity: 0.72; border-top-color: var(--etat-muet); }
.tuile-site.muet .nom { text-decoration: line-through; text-decoration-color: rgba(255, 107, 120, 0.6); }

/* Fil d'arrivées / journal / dernières vues. */
.fil, .journal, .liste-fil { list-style: none; padding: 0; margin: 0; }
.fil > li, .journal > li, .liste-fil > li {
    display: flex;
    align-items: baseline;
    gap: var(--e-3);
    padding: 7px 0;
    border-bottom: 1px solid var(--blanc-05);
    font-size: 14px;
    margin: 0;
}
.fil > li:last-child, .journal > li:last-child { border-bottom: 0; }
.fil .heure, .journal .heure, .fil time {
    flex: none;
    font-variant-numeric: tabular-nums;
    color: var(--texte-faible);
    font-size: var(--t-sm);
    min-width: 52px;
}
.fil .lieu { font-weight: 700; color: var(--texte); }
.fil .site { color: var(--texte-accent); font-size: var(--t-sm); }
.fil .detail { color: var(--texte-faible); font-size: var(--t-sm); margin-left: auto;
               overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Une arrivée qui vient d'être insérée par centre.js. Fondu d'opacité
   seul : on n'anime jamais autre chose que transform et opacity. */
.fil > li.nouvelle { animation: apparition 400ms ease both; }
@keyframes apparition { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }

/* Paire libellé / valeur, très employée dans les fiches. */
.paires { display: grid; grid-template-columns: auto 1fr; gap: var(--e-2) var(--e-5); font-size: 14px; }
.paires dt, .paires .libelle { color: var(--texte-faible); }
.paires dd, .paires .valeur { margin: 0; text-align: right; font-variant-numeric: tabular-nums; }

/* Un jeton ne se montre jamais en clair : « ****…a3f9 ». */
.empreinte, .jeton-masque {
    font-family: var(--police-code);
    font-size: var(--t-sm);
    letter-spacing: 0.06em;
    color: var(--texte-faible);
    background: var(--fond-creux);
    border: 1px solid var(--bord);
    border-radius: var(--r-xs);
    padding: 1px 7px;
}


/* =====================================================================
   12. MESSAGES — flash, vide, échec, chargement
   ===================================================================== */
.flash, .message, .alerte {
    display: flex;
    align-items: flex-start;
    gap: var(--e-3);
    padding: var(--e-3) var(--e-4);
    margin: 0 0 var(--e-4);
    border: 1px solid var(--bord);
    border-left: 3px solid var(--violet-medium);
    border-radius: var(--r-sm);
    background: var(--fond-carte);
    color: var(--texte);
    font-size: 14px;
}
.flash-succes, .message-succes, .alerte-succes { border-left-color: var(--etat-ok); background: var(--etat-ok-voile); }
.flash-erreur, .message-erreur, .alerte-erreur, .alerte-critique { border-left-color: var(--etat-muet); background: var(--etat-muet-voile); }
.flash-avertissement, .message-avertissement, .alerte-avertissement,
.flash-attention, .alerte-attention { border-left-color: var(--etat-retarde); background: var(--etat-retarde-voile); }
.flash-info, .message-info, .alerte-info { border-left-color: var(--violet-medium); }

/* L'écran vide. Le vide est DIT, en toutes lettres et sans drame — et
   un chiffre positif accompagne toujours un zéro. */
.vide, .etat-vide, .aucun-resultat {
    padding: var(--e-7) var(--e-5);
    text-align: center;
    color: var(--texte-faible);
    border: 1px dashed var(--bord-fort);
    border-radius: var(--r-md);
    background: var(--blanc-05);
}
.vide .titre, .etat-vide .titre { display: block; font-size: var(--t-lg); font-weight: 700; color: var(--texte-second); margin-bottom: var(--e-2); }

/* Échec d'un rafraîchissement. centre.js pose .depasse sur le bloc et
   n'efface JAMAIS son contenu : le dernier état connu reste affiché,
   signalé comme tel. Un écran vidé par une erreur réseau ment davantage
   qu'un écran qui montre des chiffres datés et le dit. */
.depasse, [data-echec="1"] { position: relative; }
.bandeau-echec {
    display: flex;
    align-items: center;
    gap: var(--e-2);
    margin-bottom: var(--e-3);
    padding: 6px var(--e-3);
    border-radius: var(--r-xs);
    background: var(--etat-retarde-voile);
    border: 1px solid rgba(245, 158, 11, 0.35);
    color: var(--etat-retarde);
    font-size: var(--t-xs);
    font-weight: 700;
}
.bandeau-echec::before { content: "▲"; font-size: 9px; }

/* Chargement. Une barre indéterminée en transform, pas un tourniquet. */
.chargement, .squelette {
    position: relative;
    overflow: hidden;
    background: var(--blanc-05);
    border-radius: var(--r-xs);
    min-height: 14px;
    color: transparent;
}
.chargement::after, .squelette::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(90deg, transparent, var(--blanc-10), transparent);
    transform: translateX(-100%);
    animation: balayage 1400ms ease-in-out infinite;
}
@keyframes balayage { to { transform: translateX(100%); } }

.en-cours { opacity: 0.55; pointer-events: none; transition: opacity var(--t-base); }


/* =====================================================================
   13. SPARKLINES ET JAUGES
   ===================================================================== */
.sparkline, .courbe-mini { display: block; width: 100%; height: 34px; overflow: visible; }
.sparkline path, .courbe-mini path { fill: none; stroke: var(--accent-site, var(--violet-light)); stroke-width: 1.5; vector-effect: non-scaling-stroke; }
.sparkline .aire, .courbe-mini .aire { fill: rgba(97, 57, 143, 0.28); stroke: none; }
/* La portion non couverte d'un site muet est HACHURÉE, pas mise à zéro :
   « je n'ai pas la donnée » n'est pas « il n'y a eu personne ». */
.sparkline .trou, .courbe .trou { fill: url(#hachure); stroke: none; }

.jauge {
    height: 8px;
    border-radius: var(--r-full);
    background: var(--blanc-10);
    overflow: hidden;
}
.jauge > span, .jauge .remplissage {
    display: block;
    height: 100%;
    background: linear-gradient(90deg, var(--violet) 0%, var(--violet-medium) 100%);
    border-radius: var(--r-full);
    transition: width var(--t-douce);
}
.jauge-ok > span { background: var(--etat-ok); }
.jauge-attention > span { background: var(--etat-retarde); }
.jauge-danger > span { background: var(--etat-muet); }

/* Progression d'un premier import, avec son pourcentage lisible à côté. */
.progression { display: flex; align-items: center; gap: var(--e-3); }
.progression .jauge { flex: 1; }
.progression .pourcent { font-variant-numeric: tabular-nums; font-weight: 700; min-width: 46px; text-align: right; }

/* ── Série en barres, sans SVG ni bibliothèque ────────────────────────
   Les contrôleurs servent des séries [{libelle, valeur}] (24 h par heure,
   30 jours, 14 jours de CA). La vue pose la hauteur en ligne :
     <li style="--h:62%"><span></span><em>08:00</em></li>
   Une hauteur nulle laisse un TRAIT visible : « personne à cette heure »
   n'est pas « je n'ai pas la donnée » — l'absence, elle, se hachure. */
.serie, .histogramme, .barres {
    display: flex;
    align-items: flex-end;
    gap: 2px;
    height: 96px;
    list-style: none;
    padding: 0;
    margin: 0 0 var(--e-2);
}
.serie > li, .histogramme > li, .barres > li {
    flex: 1 1 0;
    min-width: 2px;
    height: 100%;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    margin: 0;
    position: relative;
}
.serie > li > span, .histogramme > li > span, .barres > li > span {
    display: block;
    height: var(--h, 0%);
    min-height: 2px;
    border-radius: 2px 2px 0 0;
    background: linear-gradient(180deg, var(--violet-medium) 0%, var(--violet) 100%);
}
.serie > li.creux > span { background: var(--blanc-10); }
.serie > li.absent > span {
    /* Portion non couverte : hachurée, jamais mise à zéro. */
    height: 100%;
    background: repeating-linear-gradient(135deg, var(--blanc-10) 0 4px, transparent 4px 8px);
}
.serie > li > em, .histogramme > li > em {
    position: absolute;
    bottom: -18px;
    left: 50%;
    transform: translateX(-50%);
    font-style: normal;
    font-size: 10px;
    color: var(--texte-faible);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.serie-legende {
    display: flex;
    justify-content: space-between;
    font-size: var(--t-xs);
    color: var(--texte-faible);
    font-variant-numeric: tabular-nums;
    margin-top: var(--e-2);
}

/* ── Raccourcis vers les sous-écrans d'un site ───────────────────────── */
.raccourcis { display: grid; gap: var(--e-3); grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); list-style: none; padding: 0; }
.raccourcis li { margin: 0; }
.raccourci {
    display: block;
    padding: var(--e-3) var(--e-4);
    border: 1px solid var(--bord);
    border-radius: var(--r-sm);
    background: var(--blanc-05);
    text-decoration: none;
    color: inherit;
    transition: background var(--t-rapide), border-color var(--t-rapide);
}
.raccourci:hover { background: var(--noir-400); border-color: var(--bord-fort); color: inherit; }
.raccourci .libelle { display: block; font-weight: 700; color: var(--texte); }
.raccourci .detail { display: block; font-size: var(--t-xs); color: var(--texte-faible); }
.raccourci.a-venir { opacity: 0.45; pointer-events: none; border-style: dashed; }
.raccourci.a-venir .libelle::after { content: " · à venir"; font-weight: 500; color: var(--texte-faible); font-size: var(--t-xs); }

/* ── Absences déclarées ──────────────────────────────────────────────
   Un module absent n'est PAS affiché à zéro : il est dit absent, avec sa
   raison. Un « 0 commande » sur un site sans boutique est un mensonge. */
.absences { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--e-2); }
.absence {
    display: flex;
    align-items: baseline;
    gap: var(--e-2);
    padding: var(--e-2) var(--e-3);
    border-left: 2px solid var(--blanc-20);
    background: var(--blanc-05);
    border-radius: 0 var(--r-xs) var(--r-xs) 0;
    font-size: var(--t-sm);
    color: var(--texte-faible);
    margin: 0;
}
.absence::before { content: "○"; font-size: 9px; color: var(--blanc-40); }
.absence strong { color: var(--texte-second); }


/* =====================================================================
   14. LA CARTE VIVANTE
   Fond vectoriel auto-hébergé, tracé au runtime depuis du GeoJSON lon/lat
   par la MÊME fonction de projection que celle qui place les visiteurs.
   Aucune tuile distante, aucune bibliothèque cartographique.
   ===================================================================== */
.carte-monde, .carte-scene {
    position: relative;
    width: 100%;
    background: radial-gradient(120% 90% at 50% 40%, #14101d 0%, #0a0810 70%);
    border: 1px solid var(--bord);
    border-radius: var(--r-md);
    overflow: hidden;
}
.carte-monde svg, .carte-scene svg { display: block; width: 100%; height: auto; }

/* Couche de fond, tracée UNE fois et jamais repeinte. */
.geo-fond path { fill: var(--geo-terre); stroke: var(--geo-trait); stroke-width: 1; stroke-linejoin: round; }
.geo-graticule line, .geo-graticule path { stroke: var(--geo-graticule); stroke-width: 1; fill: none; }
.geo-etiquette {
    fill: var(--blanc-60);
    font-family: var(--police);
    font-size: 20px;
    font-weight: 600;
    paint-order: stroke;
    stroke: rgba(0, 0, 0, 0.75);
    stroke-width: 4px;
}

/* Couche des présences : un disque par ville active. Aucune animation ;
   l'opacité est recalculée par UN setInterval toutes les 30 s. */
.geo-point { fill: var(--geo-ville); transition: opacity 400ms ease; }
.geo-point-commerce { fill: var(--geo-commerce); }
/* Repli pays : cercle VIDE, contour pointillé, aucune onde. Il dit
   « quelque part au Sénégal », pas « à Dakar ». */
.geo-point-pays {
    fill: none;
    stroke: var(--geo-pays);
    stroke-width: 2;
    stroke-dasharray: 4 4;
}
/* Rémanence : la carte n'est jamais vide. Braises du jour, fantômes du mois. */
.geo-braise  { fill: var(--geo-braise); }
.geo-fantome { fill: var(--geo-fantome); }
.geo-compte {
    fill: #0a0810;
    font-family: var(--police);
    font-weight: 800;
    font-size: 15px;
    text-anchor: middle;
    dominant-baseline: central;
    font-variant-numeric: tabular-nums;
}

/* L'onde. UNE itération, 1800 ms, l'élément est détruit à animationend.
   Sous 1200 ms l'œil perçoit un scintillement nerveux de loin ; au-delà
   de 2500 ms les ondes se chevauchent. */
@keyframes ping {
    from { transform: scale(0.35); opacity: 0.85; }
    to   { transform: scale(3.40); opacity: 0; }
}
.geo-onde {
    fill: none;
    stroke: var(--geo-ville);
    stroke-width: 2;
    transform-box: fill-box;
    transform-origin: center;
    animation: ping 1800ms cubic-bezier(0, 0, 0.2, 1) 1 both;
    /* will-change posé sur l'onde ACTIVE uniquement, retiré à la
       destruction : un will-change permanent sur 200 points épingle 200
       couches GPU — l'optimisation devient une fuite. */
    will-change: transform, opacity;
}
.geo-onde-commerce { stroke: var(--geo-commerce); }

/* Le bandeau sous la carte : le zéro n'est jamais seul. */
.carte-bandeau {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--e-4);
    padding: var(--e-3) var(--e-4);
    border-top: 1px solid var(--bord);
    background: var(--fond-relief);
    font-size: 14px;
    color: var(--texte-second);
}
.carte-bandeau .accent { color: var(--texte-accent); font-weight: 700; }
.carte-bandeau .non-localises { color: var(--texte-faible); }

/* Mode REJEU — étiqueté au point qu'on ne peut pas le confondre avec le
   direct : le mot en 44 px, une teinte de fond décalée, un liseré qui
   court sur tout le pourtour. Ce sont de VRAIES données, rejouées. */
.carte-monde.rejeu { background: radial-gradient(120% 90% at 50% 40%, #1a1226 0%, #0c0812 70%); }
.carte-monde.rejeu::after {
    content: "";
    position: absolute;
    inset: 0;
    border: 2px solid var(--violet-medium);
    border-radius: var(--r-md);
    pointer-events: none;
}
.rejeu-etiquette {
    position: absolute;
    top: var(--e-4); left: var(--e-5);
    display: flex;
    align-items: baseline;
    gap: var(--e-3);
    font-weight: 800;
    letter-spacing: 0.14em;
    color: var(--texte-accent);
    text-shadow: 0 2px 12px #000;
    z-index: 2;
}
.rejeu-etiquette .mot { font-size: 44px; line-height: 1; }
.rejeu-etiquette .horloge { font-size: 32px; font-variant-numeric: tabular-nums; color: var(--blanc-80); }

/* Collecteur arrêté, ou tous les sites muets : la carte se DÉSATURE et
   le dit. Zéro n'est pas « je ne sais pas ». */
.carte-monde.figee { filter: saturate(0.15); }
.carte-monde.figee .geo-point, .carte-monde.figee .geo-braise { fill: var(--blanc-40); }

/* Mode nuit du mur : 22 h – 6 h, luminosité 60 %, ondes coupées. */
.carte-monde.nuit { filter: brightness(0.6); }
.carte-monde.nuit .geo-onde { display: none; }


/* =====================================================================
   15. LA PORTE — écran de connexion
   La seule page atteignable sans session. Aucun chiffre, aucune donnée
   client, et un message d'erreur volontairement pauvre.
   ===================================================================== */
body.porte {
    display: grid;
    grid-template-rows: 1fr auto;
    min-height: 100vh;
    min-height: 100dvh;
    /* Le violet en halo, comme sur le site : une identité, pas une encre. */
    background:
        radial-gradient(80% 55% at 50% -10%, rgba(97, 57, 143, 0.30) 0%, transparent 62%),
        radial-gradient(60% 50% at 90% 110%, rgba(97, 57, 143, 0.14) 0%, transparent 60%),
        var(--fond);
}

.porte-scene { display: grid; place-items: center; padding: var(--e-6) var(--e-4); }

.porte-carte {
    width: 100%;
    max-width: 420px;
    background: var(--fond-carte);
    border: 1px solid var(--bord-fort);
    border-radius: var(--r-lg);
    padding: var(--e-6);
    box-shadow: var(--ombre-lg);
}

.porte-marque { text-align: center; margin-bottom: var(--e-6); }
.porte-marque .logo {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 56px; height: 56px;
    margin-bottom: var(--e-3);
    border-radius: var(--r-md);
    background: linear-gradient(135deg, #61398F 0%, #7a4db0 100%);
    color: #FFFFFF;
    font-size: 22px;
    font-weight: 800;
    letter-spacing: 0.02em;
    box-shadow: var(--lueur);
}
.porte-marque .nom { display: block; font-size: var(--t-xl); font-weight: 800; letter-spacing: 0.02em; }
.porte-marque .sous-titre { display: block; font-size: var(--t-sm); color: var(--texte-accent); letter-spacing: 0.14em; text-transform: uppercase; }

.porte-carte .bouton-bloc { margin-top: var(--e-5); padding: 12px; font-size: var(--t-md); }

/* Le refus. Un seul message, toujours le même : jamais « cet email
   n'existe pas », qui dirait à un attaquant quels comptes chercher. */
.porte-refus {
    display: flex;
    align-items: flex-start;
    gap: var(--e-2);
    margin-bottom: var(--e-4);
    padding: var(--e-3);
    border: 1px solid rgba(255, 107, 120, 0.4);
    border-radius: var(--r-sm);
    background: var(--etat-muet-voile);
    color: var(--etat-muet);
    font-size: 14px;
    font-weight: 600;
}
.porte-refus::before { content: "■"; font-size: 9px; line-height: 1.7; flex: none; }

.porte-pied {
    padding: var(--e-4) var(--e-5);
    text-align: center;
    font-size: var(--t-xs);
    color: var(--texte-faible);
    line-height: 1.8;
}
.porte-pied a { color: var(--texte-faible); }
.porte-pied a:hover { color: var(--texte-accent); }


/* =====================================================================
   16. LE MUR — densité de grand écran
   Le mur lui-même n'est pas dans cette tranche ; sa densité est posée ici
   pour que les composants soient déjà justes le jour où il arrive.
   Plancher absolu 24 px en 600. Rapport ~6:1 entre le plus grand et le
   plus petit — c'est lui qui rend le mur lisible d'un coup d'œil ; une
   hiérarchie molle 14/16/20 donne une bouillie grise à quatre mètres.
   Toile logique 1920 × 1080 : on change d'ÉCHELLE, jamais de CONTENU.
   ===================================================================== */
body.mur {
    font-size: calc(24px * var(--echelle));
    font-weight: 600;
    line-height: 1.35;
    overflow: hidden;   /* toute barre de défilement sur un mur est un bug */
}
.mur-panneau { padding: calc(28px * var(--echelle)); }
.mur-titre   { font-size: calc(48px * var(--echelle)); font-weight: 800; letter-spacing: -0.01em; }
.mur-libelle { font-size: calc(28px * var(--echelle)); font-weight: 600; color: var(--texte-faible); text-transform: uppercase; letter-spacing: 0.1em; }
.mur-valeur  { font-size: calc(128px * var(--echelle)); font-weight: 800; line-height: 0.95; letter-spacing: -0.03em; font-variant-numeric: tabular-nums; }
.mur-valeur.petite { font-size: calc(96px * var(--echelle)); }
.mur-ligne   { font-size: calc(36px * var(--echelle)); font-weight: 600; }
.mur-note    { font-size: calc(24px * var(--echelle)); color: var(--texte-faible); }

/* Sur le mur, --blanc-40 est INTERDIT — 3,4:1 ne survit ni à la lumière
   ambiante ni à la dérive d'un projecteur. Tout ce qui serait passé en
   décor sur le poste remonte au plancher de texte. */
body.mur .aide, body.mur .note, body.mur .mur-note, body.mur .badge-absent,
body.mur .kpi-note, body.mur .fraicheur, body.mur .nav-titre { color: var(--texte-faible); }

/* Le mur n'a pas de souris : rien ne se révèle au survol, aucune
   infobulle, aucune fenêtre. */
body.mur [title] { pointer-events: none; }
body.mur .bandeau-fraicheur { font-size: calc(24px * var(--echelle)); padding: calc(12px * var(--echelle)) calc(28px * var(--echelle)); }

/* Barre de progression de la rotation des panneaux : le seul mouvement
   autorisé hors direct, avec le rejeu étiqueté. */
.mur-rotation { height: 4px; background: var(--blanc-10); }
.mur-rotation > span { display: block; height: 100%; background: var(--violet-medium); transform-origin: left; }


/* =====================================================================
   17. UTILITAIRES
   ===================================================================== */
.sr-only, .visuellement-cache {
    position: absolute !important;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}
.cache, [hidden] { display: none !important; }

.faible, .secondaire, .note { color: var(--texte-faible); }
.accent { color: var(--texte-accent); }
.ok      { color: var(--etat-ok); }
.attention { color: var(--etat-retarde); }
.danger, .erreur { color: var(--etat-muet); }

.gras { font-weight: 700; }
.tres-gras { font-weight: 800; }
.majuscules { text-transform: uppercase; letter-spacing: 0.12em; font-size: var(--t-xs); font-weight: 700; }
.centre-texte { text-align: center; }
.droite { text-align: right; }
.nowrap { white-space: nowrap; }
.tronque { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.mt-0 { margin-top: 0; }      .mt-2 { margin-top: var(--e-2); }
.mt-4 { margin-top: var(--e-4); } .mt-6 { margin-top: var(--e-6); }
.mb-0 { margin-bottom: 0; }   .mb-2 { margin-bottom: var(--e-2); }
.mb-4 { margin-bottom: var(--e-4); } .mb-6 { margin-bottom: var(--e-6); }

.pile { display: flex; flex-direction: column; gap: var(--e-3); }
.ligne { display: flex; align-items: center; gap: var(--e-3); flex-wrap: wrap; }
.entre { justify-content: space-between; }
.defilable { overflow-x: auto; }


/* =====================================================================
   18. MOUVEMENT RÉDUIT, IMPRESSION, ÉCRANS ÉTROITS
   ===================================================================== */

/* L'information n'est JAMAIS portée par le seul mouvement : quand les
   animations tombent, le point monte simplement en intensité, et le fil
   des arrivées porte la même information en texte — consultable après
   coup, ce qu'une onde évanouie n'est pas. */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
    .geo-onde { display: none; }
    .geo-point { transition: opacity 400ms ease; }
}
/* Réglage explicite du centre : un mur n'a pas d'utilisateur pour régler
   son système d'exploitation. */
html[data-mouvement="aucun"] .geo-onde,
html[data-mouvement="reduites"] .geo-onde { display: none; }
html[data-mouvement="aucun"] .chargement::after,
html[data-mouvement="aucun"] .squelette::after { animation: none; }

@media (max-width: 900px) {
    body.poste { grid-template-columns: 1fr; }
    .poste-rail {
        position: static;
        max-height: none;
        flex-direction: row;
        flex-wrap: wrap;
        border-right: 0;
        border-bottom: 1px solid var(--bord);
        padding: var(--e-3);
        gap: var(--e-1);
    }
    .poste-rail .marque { width: 100%; border-bottom: 0; padding-bottom: var(--e-3); }
    .poste-rail .marque::before { display: none; }
    .nav-titre { width: 100%; padding: var(--e-2) 0 0; }
    .nav-item { border-left: 0; border-radius: var(--r-full); padding: 6px var(--e-3); }
    .nav-item.a-venir::after { display: none; }
    .poste-entete, .bandeau-fraicheur, .poste-contenu, .poste-pied { padding-left: var(--e-4); padding-right: var(--e-4); }
    .bandeau-fraicheur { position: static; }
}

@media print {
    body { background: #fff; color: #111; }
    .poste-rail, .barre-actions, .bouton, .btn, button { display: none !important; }
    .carte, .bloc, .panneau, table { border-color: #ccc; background: #fff; }
    /* La date de valeur suit le papier : un rapport imprimé sans sa date
       de valeur est exactement le total silencieusement faux qu'on
       refuse. */
    .bandeau-fraicheur, .fraicheur { display: flex !important; color: #444; background: none; }
}

/* =====================================================================
   TROIS CLASSES REMONTÉES AU PREMIER NIVEAU
   ---------------------------------------------------------------------
   .nom, .sous-titre et .adresse n'existaient que sous un parent
   (.porte-marque, .tuile-site). Les écrans de détail d'un site —
   site/tableau-de-bord.php puis site/synchronisation.php — les emploient
   NUES : elles étaient donc sans effet, et ces titres tombaient à la
   taille du texte courant. L'écran restait lisible, ce qui est
   précisément pourquoi le défaut est passé inaperçu.

   Les valeurs reprennent celles de .tuile-site, pour que le nom d'un
   site s'affiche pareil qu'on le lise dans la flotte ou sur sa fiche.
   Les règles imbriquées existantes restent prioritaires : rien ne change
   là où elles s'appliquaient déjà.
   ===================================================================== */

.nom        { font-size: var(--t-lg); font-weight: 700; color: var(--texte); }
.sous-titre { font-size: var(--t-sm); color: var(--texte-accent); letter-spacing: 0.14em; text-transform: uppercase; }
.adresse    { font-size: var(--t-xs); color: var(--texte-faible); word-break: break-all; }
