/* Metheore2 dashboard theme — recalé le 16/09/2026 sur la charte graphique
   Matvision (00_Marque/00_Charte et guidelines/20260908 - Charte - Direction
   artistique 2026.md + MV-guideline.pdf) : polices IBM Plex (auto-hébergées,
   voir @font-face ci-dessous), palette verte de marque, fond gris clair
   quadrillé ("le côté rigoureux" validé par Robert), sidebar en vert plein
   (gabarit "version forte" de la charte). Le mode sombre n'est pas couvert
   par la charte (pensée pour du print/web clair) : ses teintes sont une
   extrapolation raisonnable ancrée sur l'anthracite de marque plutôt qu'un
   simple filtre inversé. Comme avant, toute couleur de surface/texte/bordure
   passe par un token ci-dessous pour que le bloc sombre n'ait qu'à
   redéfinir les tokens, jamais traquer des littéraux. */

@font-face {
    font-family: 'IBM Plex Sans';
    src: url('../fonts/IBMPlexSans-Regular.ttf') format('truetype');
    font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
    font-family: 'IBM Plex Sans';
    src: url('../fonts/IBMPlexSans-SemiBold.ttf') format('truetype');
    font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
    font-family: 'IBM Plex Sans';
    src: url('../fonts/IBMPlexSans-Bold.ttf') format('truetype');
    font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
    font-family: 'IBM Plex Mono';
    src: url('../fonts/IBMPlexMono-Regular.ttf') format('truetype');
    font-weight: 400; font-style: normal; font-display: swap;
}

/* Transition douce entre deux pages de meme origine (soumission du filtre
   Periode/Matiere, clic dans le menu, etc.) - View Transitions API,
   progressive enhancement : les navigateurs qui ne la supportent pas
   encore ignorent la regle et naviguent comme avant, sans aucun risque. */
@view-transition {
    navigation: auto;
}

:root {
    /* Jetons de marque bruts (charte §5 "Jetons de couleur") */
    --mv-green: #008B29;
    --mv-green-light: #90E377;
    --mv-green-pale: #E8F4DF;
    --mv-anthracite: #303D3A;

    --ink: #303D3A;
    --muted: #66746F;
    --accent: #008B29;
    --accent-soft: #E8F4DF;
    --line: #DDDDDD;
    --card: #ffffff;

    --page-bg-solid: #F7F7F7;
    --grid-line: rgba(48, 61, 58, .09);
    --grid-size: 56px;
    --surface-2: #F1F2F0;       /* table header bg, checked-toggle bg */
    --surface-hover: #EEF7EE;   /* table row hover */
    --shadow-rgb: 48, 61, 58;
    --accent-rgb: 0, 139, 41;

    --alert-bg: #eef4f2;
    --alert-border: var(--line);
    --alert-color: var(--muted);
    --alert-error-bg: #FDEBEA;
    --alert-error-border: #F3A9A5;
    --alert-error-color: #A3372E;
    --alert-warning-bg: #FFF4E5;
    --alert-warning-border: #F0B429;
    --alert-warning-color: #8A5A00;
    --alert-success-bg: var(--accent-soft);
    --alert-success-border: #9BD4A8;
    --alert-success-color: #00742A;
}

:root[data-theme="dark"] {
    --ink: #EAF1EE;
    --muted: #93A5A0;
    --accent: #90E377;
    --accent-soft: #16301D;
    --line: #2A3A3F;
    --card: #1E2B27;

    --page-bg-solid: #141E1B;
    --grid-line: rgba(255, 255, 255, .045);
    --surface-2: #22302B;
    --surface-hover: #1C2A22;
    --shadow-rgb: 0, 0, 0;
    --accent-rgb: 144, 227, 119;

    --alert-bg: #1F2C2A;
    --alert-border: var(--line);
    --alert-color: var(--muted);
    --alert-error-bg: #3A1E1C;
    --alert-error-border: #6B2F2B;
    --alert-error-color: #FF9C93;
    --alert-warning-bg: #362A11;
    --alert-warning-border: #6E5218;
    --alert-warning-color: #F0C34D;
    --alert-success-bg: var(--accent-soft);
    --alert-success-border: #2E5C3B;
    --alert-success-color: #90E377;
}

* { box-sizing: border-box; }

html, body {
    margin: 0;
    padding: 0;
    font-family: 'IBM Plex Sans', -apple-system, BlinkMacSystemFont, sans-serif;
    color: var(--ink);
    background-color: var(--page-bg-solid);
    /* Fond gris clair quadrillé (charte §3.2 / §5) : grille fine à faible
       contraste, jamais posée sur une photo/graphique — uniquement le fond
       de page. */
    background-image:
        repeating-linear-gradient(0deg, var(--grid-line) 0, var(--grid-line) 1px, transparent 1px, transparent var(--grid-size)),
        repeating-linear-gradient(90deg, var(--grid-line) 0, var(--grid-line) 1px, transparent 1px, transparent var(--grid-size));
    transition: background-color .2s ease, color .2s ease;
}

h1, h2, h3 { font-family: 'IBM Plex Sans', sans-serif; font-weight: 400; margin: 0; }

a { color: inherit; text-decoration: none; }

.layout {
    display: flex;
    min-height: 100vh;
    align-items: stretch;
}

/* --- Sidebar ------------------------------------------------------------ */
/* Vert plein (charte §3.2 "fond vert principal, version forte") : reste
   l'ancrage fixe de la marque quel que soit le thème, comme l'était la
   navy avant elle.

   Refonte du 16/09/2026 : l'habillage précédent (panneaux translucides à
   coins très arrondis façon "glassmorphism", pastilles pleines pour l'état
   actif) a été remplacé par un langage plus instrumentation/rigueur —
   sections séparées par des filets fins, accent sur bordure gauche pour la
   nav (reprend le même motif que .section-header::before dans le contenu),
   police mono pour tout ce qui est étiquette/contrôle — plus proche de
   l'esprit "grille + précision" de la charte que d'un gabarit de dashboard
   générique. */
.sidebar {
    width: 280px;
    flex: 0 0 280px;
    background: linear-gradient(180deg, #00742A 0%, #00431A 100%);
    color: #EAF6EC;
    display: flex;
    flex-direction: column;
}
.sidebar-brand {
    padding: 1.7rem 1.5rem 1.35rem;
    border-bottom: 1px solid rgba(255, 255, 255, .14);
}
.sidebar-logo { width: 100%; max-width: 168px; height: auto; display: block; }

.sidebar-nav { display: flex; flex-direction: column; padding: .55rem .9rem; gap: .05rem; }
.sidebar-nav a {
    display: flex; align-items: center; gap: .65rem;
    padding: .58rem .65rem; margin-left: -1px;
    border-left: 2px solid transparent; border-radius: 0 7px 7px 0;
    font-weight: 500; font-size: .87rem;
    color: #C6E6CA; transition: background .15s, color .15s, border-color .15s;
}
.sidebar-nav a .nav-icon { width: 17px; height: 17px; flex: none; opacity: .8; transition: opacity .15s; }
.sidebar-nav a:hover { background: rgba(255, 255, 255, .06); color: #fff; }
.sidebar-nav a:hover .nav-icon { opacity: 1; }
.sidebar-nav a.active {
    border-left-color: var(--mv-green-light); background: rgba(255, 255, 255, .05);
    color: #fff; font-weight: 600;
}
.sidebar-nav a.active .nav-icon { opacity: 1; }
.sidebar-nav .sub-nav { display: flex; flex-direction: column; gap: .15rem; margin: .1rem 0 .3rem 1.1rem; }
.sidebar-nav .sub-nav a { font-size: .82rem; padding: .4rem .6rem; color: #B7DCBC; }
.sidebar-nav .sub-nav a.active { color: #fff; background: rgba(144, 227, 119, .18); }


.sidebar-panel {
    padding: 1.05rem 1.5rem 1.2rem;
    border-top: 1px solid rgba(255, 255, 255, .12);
}
.sidebar-panel-label {
    font-family: 'IBM Plex Mono', 'Courier New', monospace;
    font-size: .66rem; text-transform: uppercase; letter-spacing: .07em;
    color: #A9D6AF; font-weight: 600; margin-bottom: .5rem; display: block;
}
.period-input,
.period-select {
    background: rgba(255, 255, 255, .1); border: 1px solid rgba(255, 255, 255, .2);
    border-radius: 7px; color: #EAF6EC; padding: .4rem .5rem; font-size: .78rem;
    font-family: 'IBM Plex Mono', 'Courier New', monospace; color-scheme: dark;
}
.period-input:hover, .period-input:focus,
.period-select:hover, .period-select:focus { border-color: rgba(144, 227, 119, .65); outline: none; }
/* Le menu déroulant OUVERT (liste d'options) est rendu par le navigateur
   avec un fond clair quel que soit color-scheme sur le <select> fermé — sans
   ceci, le texte clair pensé pour le fond vert de la sidebar (#EAF6EC)
   devient un gris quasi invisible sur ce fond blanc. On fixe donc la couleur
   des <option> explicitement, indépendamment du <select> qui les contient. */
.period-select option {
    color: #17252B; background-color: #ffffff;
}

.period-form { display: flex; flex-direction: column; gap: .7rem; }
.period-group { display: flex; flex-direction: column; gap: .35rem; }
.period-group-label {
    font-family: 'IBM Plex Mono', 'Courier New', monospace; font-size: .66rem;
    text-transform: uppercase; letter-spacing: .05em; color: #A9D6AF;
}
.period-input { min-width: 0; }

/* Calendrier + heures/minutes en <select> plutôt qu'en <input type="date"/
   "time"> natifs : voir la note dans _period_filter.html — ceux-ci
   affichent MM/DD/YYYY et un sélecteur AM/PM dès que le navigateur/OS est en
   anglais, quel que soit le lang de la page. */
.period-date-field, .period-time-field { display: flex; align-items: center; gap: .35rem; }
.period-date-field .period-input-date { flex: 1; min-width: 0; cursor: pointer; }
.period-time-field .period-select { flex: 1; min-width: 0; padding: .4rem .35rem; }
.period-time-sep { color: #C6E6CA; font-family: 'IBM Plex Mono', monospace; font-size: .78rem; flex: none; }
.period-icon-btn, .period-icon-static {
    flex: none; width: 26px; height: 26px; border-radius: 6px;
    display: flex; align-items: center; justify-content: center;
    font-size: .82rem; line-height: 1;
}
.period-icon-btn {
    background: rgba(255, 255, 255, .1); border: 1px solid rgba(255, 255, 255, .2);
    color: #EAF6EC; cursor: pointer; padding: 0; transition: border-color .12s ease;
}
.period-icon-btn:hover { border-color: rgba(144, 227, 119, .65); }
.period-icon-static { color: #A9D6AF; }

.period-form button[type="submit"] {
    align-self: flex-end;
    background: rgba(255, 255, 255, .14); border: 1px solid rgba(255, 255, 255, .24);
    border-radius: 7px; color: #fff; cursor: pointer;
    font-family: 'IBM Plex Mono', 'Courier New', monospace; text-transform: uppercase;
    letter-spacing: .04em; font-weight: 600; font-size: .68rem; padding: .48rem .95rem;
    margin-top: .15rem;
}
.period-form button[type="submit"]:hover { background: var(--mv-green-light); border-color: var(--mv-green-light); color: #00380F; }

/* Popup calendrier (voir _period_filter.html) : carte claire/thémée plutôt
   que verte pleine, posée en position:fixed par le JS (donc jamais rognée
   par le débordement de la sidebar), lisible sur n'importe quel fond. */
.period-calendar-popup {
    z-index: 1000; background: var(--card); border: 1px solid var(--line); border-radius: 12px;
    box-shadow: 0 12px 30px -10px rgba(var(--shadow-rgb), .4); padding: .7rem .8rem .8rem;
    width: 232px; font-family: 'IBM Plex Sans', sans-serif;
}
.period-cal-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: .5rem; }
.period-cal-title { font-weight: 600; font-size: .85rem; color: var(--ink); text-transform: capitalize; }
.period-cal-nav {
    width: 22px; height: 22px; border-radius: 6px; border: 1px solid var(--line); background: var(--card);
    color: var(--muted); cursor: pointer; font-size: .85rem; line-height: 1; padding: 0;
}
.period-cal-nav:hover { border-color: var(--accent); color: var(--accent); }
.period-cal-weekdays {
    display: grid; grid-template-columns: repeat(7, 1fr); text-align: center;
    font-family: 'IBM Plex Mono', 'Courier New', monospace; font-size: .62rem; color: var(--muted);
    text-transform: uppercase; margin-bottom: .25rem;
}
.period-cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.period-cal-day {
    aspect-ratio: 1; border: none; background: none; border-radius: 6px; padding: 0;
    font-size: .78rem; color: var(--ink); cursor: pointer; font-family: 'IBM Plex Sans', sans-serif;
}
.period-cal-day:hover { background: var(--surface-hover); }
.period-cal-day.today { color: var(--accent); font-weight: 700; }
.period-cal-day.selected { background: var(--accent); color: #fff; font-weight: 700; }
.period-cal-day-empty { cursor: default; pointer-events: none; }

.period-links { display: flex; flex-direction: column; gap: .35rem; margin-top: .75rem; }
.period-links a {
    font-family: 'IBM Plex Mono', 'Courier New', monospace; font-size: .67rem;
    text-transform: uppercase; letter-spacing: .03em; color: #A9D6AF;
}
.period-links a:hover { color: #fff; }

.sidebar-caption { font-size: .74rem; line-height: 1.4; color: #B7DCBC; margin-top: .7rem; }
.sidebar-footnote {
    font-size: .78rem; color: #B7DCBC; display: flex; align-items: center; gap: .45rem;
    padding: .9rem 1.5rem; border-top: 1px solid rgba(255, 255, 255, .1);
}
.sidebar-footnote::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--mv-green-light); flex: none; }
.sidebar-spacer { flex: 1; }

/* --- Theme toggle --------------------------------------------------------*/
.theme-toggle {
    display: flex; align-items: center; justify-content: space-between; gap: .6rem;
    background: rgba(255, 255, 255, .09); border: 1px solid rgba(255, 255, 255, .16);
    border-radius: 999px; padding: .4rem .5rem .4rem .9rem;
    font-size: .8rem; font-weight: 600; color: #CFEAD3; cursor: pointer;
    width: 100%;
}
.theme-toggle:hover { border-color: rgba(144, 227, 119, .55); }
.theme-toggle .icon { font-size: 1rem; line-height: 1; }
.theme-toggle .track {
    width: 34px; height: 20px; border-radius: 999px; background: rgba(255, 255, 255, .18);
    position: relative; flex: none; transition: background .15s;
}
.theme-toggle .knob {
    position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%;
    background: #fff; transition: transform .18s ease;
}
:root[data-theme="dark"] .theme-toggle .track { background: var(--accent); }
:root[data-theme="dark"] .theme-toggle .knob { transform: translateX(14px); }

/* Same control, used inline in a light/dark-aware card (Paramètres) rather
   than the always-green sidebar - swap the hardcoded sidebar colors for
   theme tokens so it reads correctly in both modes. */
.settings-row {
    display: flex; align-items: center; justify-content: space-between; gap: 1.2rem; flex-wrap: wrap;
}
.settings-row .theme-toggle {
    width: auto; min-width: 190px;
    background: var(--surface-2); border-color: var(--line); color: var(--ink);
}
.settings-row .theme-toggle:hover { border-color: var(--accent); }
.settings-row .theme-toggle .track { background: var(--line); }

/* --- Main content --------------------------------------------------------*/
.main { flex: 1; min-width: 0; padding: 1.8rem 2.2rem 3rem; }

/* Bandeau de page (charte §4, gabarit 3 "titre seul, fond vert plein") :
   vert de marque, titre blanc, un halo elliptique très doux en clin d'œil
   à l'arceau du système (proportions exactes de la courbe non archivées à
   ce jour — voir 00_RECAP.md "MANQUE" — donc pas de tracé précis ici, un
   halo simple plutôt qu'une fausse précision). */
.hero {
    padding: 1.8rem 1.9rem; margin-bottom: 1.4rem; border-radius: 18px;
    background: linear-gradient(120deg, #00702A 0%, #00A030 100%);
    box-shadow: 0 12px 30px -12px rgba(0, 40, 15, .4);
    position: relative; overflow: hidden;
}
.hero::after {
    content: ""; position: absolute;
    width: 55%; height: 220%; right: -12%; top: -75%;
    background: rgba(255, 255, 255, .08);
    border-radius: 50%;
    pointer-events: none;
    animation: mv-hero-arc-in .5s ease-out both;
}
.hero::before {
    content: ""; position: absolute; top: 0; left: -35%; width: 30%; height: 100%;
    background: linear-gradient(100deg,
        transparent 0%, rgba(255, 255, 255, .14) 45%,
        rgba(255, 255, 255, .26) 50%, rgba(255, 255, 255, .14) 55%, transparent 100%);
    transform: skewX(-16deg);
    pointer-events: none;
    animation: mv-hero-sheen 1.2s ease-out .6s both;
}
.hero * { position: relative; }
/* Ligne de titre + pastille "Actualisé a ..." (deplacee depuis la sidebar,
   retour utilisateur du 21/09/2026 : "la derniere actualisation faudrait
   qu'elle aille en haut aussi") - meme bloc <div id="realtime-status"> et
   memes ids internes (#realtime-dot / #realtime-status-main) qu'avant,
   seul l'emplacement et l'habillage changent, donc aucun JS a toucher. */
.hero-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.hero-status {
    display: flex; align-items: center; gap: .5rem; flex: none;
    background: rgba(255, 255, 255, .14); border: 1px solid rgba(255, 255, 255, .22);
    border-radius: 999px; padding: .38rem .9rem .38rem .7rem; margin-top: .2rem;
    font-family: 'IBM Plex Mono', 'Courier New', monospace;
    animation: mv-hero-text-in .45s ease-out .28s both;
}
.hero-status .realtime-status-main { color: #DCF0DE; font-size: .74rem; letter-spacing: .01em; white-space: nowrap; }
.hero h1 {
    font-size: 2.1rem; color: #fff; font-weight: 400; display: flex; align-items: center; gap: .65rem;
    animation: mv-hero-text-in .45s ease-out .12s both;
}
.hero-icon { width: 32px; height: 32px; flex: none; }
.hero p {
    color: #D9F3DC; font-size: 1.02rem; margin: .4rem 0 0;
    animation: mv-hero-text-in .45s ease-out .2s both;
}
/* Animation d'entrée de page (charte DA 2026 §5, ajout du 17/09/2026) :
   "l'entrée de page est une animation courte (logo ou arceau, puis titre et
   visuel)" — l'arceau (le halo .hero::after ci-dessus) apparaît en premier,
   le titre (icône + texte) juste après, jamais sur une horloge qui boucle
   (both = joue une fois, reste sur l'état final). Uniquement opacity/
   transform, comme l'exige la même règle pour toute animation de marque ;
   prefers-reduced-motion coupe tout ci-dessous. */
@keyframes mv-hero-arc-in {
    from { opacity: 0; transform: scale(.85); }
    to { opacity: 1; transform: scale(1); }
}
@keyframes mv-hero-sheen {
    from { transform: translateX(0) skewX(-16deg); opacity: 0; }
    15%  { opacity: 1; }
    to   { transform: translateX(420%) skewX(-16deg); opacity: 0; }
}
@keyframes mv-hero-text-in {
    from { opacity: 0; transform: translateY(8px); }
    to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
    .hero::after, .hero::before, .hero h1, .hero p, .hero-status { animation: none; }
}

/* --- Barre de filtres en haut de page (periode + matiere selon la page -
   voir topbar_filters dans base.html et _period_filter.html) ------------ */
/* Intitule "Filtres" integre a la carte elle-meme (et non plus un titre de
   page au-dessus, voir retour utilisateur du 21/09/2026 : "on dirait que
   toute la page est un filtre... faudrait qui soit vraiment integre au
   bloc" - d'ou un en-tete de carte, separe du contenu par un filet, plutot
   que .section-header). Un seul titre general plutot qu'un par filtre,
   pour ne pas avoir a le retoucher a chaque nouveau filtre ajoute plus
   tard. */
.topbar-filter-bar {
    display: flex; flex-direction: column; gap: .9rem;
    background: var(--card); border: 1px solid var(--line); border-radius: 14px;
    padding: 1.1rem 1.4rem; margin-bottom: 1.4rem;
    box-shadow: 0 6px 18px -14px rgba(var(--shadow-rgb), .2);
}
.topbar-filter-bar-title {
    display: flex; align-items: center; gap: .6rem;
    font-size: .92rem; font-weight: 700; color: var(--ink);
    padding-bottom: .85rem; margin: 0; border-bottom: 1px solid var(--line);
}
.topbar-filter-bar-title::before { content: ""; width: 4px; height: 1.05rem; border-radius: 4px; background: var(--accent); flex: none; }
.topbar-filter-bar-row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 1.3rem 2rem; }
.topbar-filter-bar-row > * + * { border-left: 1px solid var(--line); padding-left: 2rem; }
.topbar-filter-period { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 1rem 1.4rem; }
.topbar-filter-bar .period-form { flex-direction: row; align-items: flex-end; flex-wrap: wrap; gap: 1rem; }
.topbar-filter-bar .period-group { flex: none; min-width: 172px; }
.topbar-filter-bar .period-links { flex-direction: column; gap: .3rem; margin-top: 0; align-self: center; }
.topbar-filter-bar .period-input,
.topbar-filter-bar .period-select {
    background: var(--surface-2); border: 1px solid var(--line); color: var(--ink);
    color-scheme: light;
}
.topbar-filter-bar select.period-select { width: 100%; }
.topbar-filter-bar .period-input:hover, .topbar-filter-bar .period-input:focus,
.topbar-filter-bar .period-select:hover, .topbar-filter-bar .period-select:focus { border-color: var(--accent); outline: none; }
.topbar-filter-bar .period-select option { color: var(--ink); background-color: var(--card); }
.topbar-filter-bar .period-group-label { color: var(--muted); }
.topbar-filter-bar .period-icon-btn { background: var(--surface-2); border: 1px solid var(--line); color: var(--ink); }
.topbar-filter-bar .period-icon-btn:hover { border-color: var(--accent); }
.topbar-filter-bar .period-icon-static { color: var(--muted); }
.topbar-filter-bar .period-time-sep { color: var(--muted); }
.topbar-filter-bar .period-form button[type="submit"] {
    background: var(--accent); border: 1px solid var(--accent); color: #fff;
}
.topbar-filter-bar .period-form button[type="submit"]:hover { background: #00742A; border-color: #00742A; }
.topbar-filter-bar .period-links a { color: var(--muted); }
.topbar-filter-bar .period-links a:hover { color: var(--accent); }
.topbar-filter-matiere { display: flex; flex-direction: column; gap: .4rem; min-width: 190px; }
.topbar-filter-export { display: flex; flex-direction: column; gap: .4rem; min-width: 160px; }
.topbar-filter-export-buttons { display: flex; gap: .5rem; }
.export-btn {
    display: inline-flex; align-items: center; justify-content: center;
    padding: .5rem .9rem; border-radius: 8px; border: 1px solid var(--accent);
    background: transparent; color: var(--accent); font-size: .85rem; font-weight: 600;
    text-decoration: none; cursor: pointer; transition: background .15s ease, color .15s ease;
}
.export-btn:hover { background: var(--accent); color: #fff; }
.comparaison-export-row { margin-top: .6rem; display: flex; gap: .6rem; justify-content: center; }
.topbar-filter-label {
    font-family: 'IBM Plex Mono', 'Courier New', monospace; font-size: .66rem;
    text-transform: uppercase; letter-spacing: .06em; color: var(--muted); font-weight: 600;
}
@media (max-width: 640px) {
    .topbar-filter-bar-row > * + * { border-left: none; padding-left: 0; }
}

.section-header { display: flex; align-items: center; gap: .85rem; margin: 1.9rem 0 1rem; }
.section-header::before { content: ""; width: 4px; height: 1.5rem; border-radius: 4px; background: var(--accent); }
.section-header h3 { font-size: 1.3rem; color: var(--ink); display: flex; align-items: center; gap: .55rem; }
.section-icon { width: 20px; height: 20px; flex: none; }

.kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: .9rem; }
.metric-card {
    background: var(--card); border: 1px solid var(--line); border-radius: 14px;
    padding: 1.1rem 1.2rem; box-shadow: 0 6px 18px -12px rgba(var(--shadow-rgb), .18);
    transition: transform .15s, box-shadow .15s, background-color .2s, border-color .2s;
}
.metric-card:hover { transform: translateY(-2px); box-shadow: 0 10px 22px -12px rgba(var(--shadow-rgb), .28); }
.metric-label {
    font-family: 'IBM Plex Mono', 'Courier New', monospace;
    color: var(--muted); font-weight: 600; text-transform: uppercase;
    letter-spacing: .04em; font-size: .72rem; margin-bottom: .35rem;
}
.metric-value { font-family: 'IBM Plex Sans', sans-serif; font-weight: 600; font-size: 1.65rem; color: var(--ink); }
.metric-delta { color: var(--accent); font-size: .82rem; font-weight: 600; margin-top: .25rem; }
/* Pulse bref quand une valeur de KPI vient de changer (retour utilisateur
   du 21/09/2026 : "pulse sur changement en temps reel") - classe posee/
   rejouee par MV.updateKpi (polish.js), jamais en boucle : une seule
   passe a chaque changement reel de valeur. */
.metric-value.kpi-pulse { animation: mv-kpi-pulse .9s ease-out; }
@keyframes mv-kpi-pulse {
    0%   { color: var(--accent); text-shadow: 0 0 14px rgba(var(--accent-rgb), .55); }
    100% { color: var(--ink); text-shadow: none; }
}
@media (prefers-reduced-motion: reduce) {
    .metric-value.kpi-pulse { animation: none; color: var(--accent); }
}

/* Fondu d'entree generique pour un contenu qui vient d'etre (re)charge -
   graphique reconstruit apres une actualisation/un changement de filtre
   (retour utilisateur du 21/09/2026 : "transitions douces au filtrage"),
   pose/rejoue par MV.fadeIn (polish.js). */
.fade-in-swap { animation: mv-fade-in-swap .35s ease both; }
@keyframes mv-fade-in-swap {
    from { opacity: 0; transform: translateY(4px); }
    to   { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
    .fade-in-swap { animation: none; }
}

/* Skeleton scintillant - utilise pendant une vraie attente reseau (ex. le
   premier poll du convoyeur en direct sur Vue d'ensemble, voir
   apercu.html), pas au premier affichage de la page puisque celle-ci est
   deja rendue cote serveur avec ses vraies valeurs (retour utilisateur du
   21/09/2026 : "skeleton loaders scintillants"). Boucle tant que visible -
   seule exception assumee a la regle "jamais sur une horloge qui boucle"
   du bandeau (§5) : ici la boucle dure le temps reel de l'attente, elle
   s'arrete des que la donnee arrive, ce n'est pas une decoration
   permanente. */
.skeleton-shimmer {
    position: relative; overflow: hidden; border-radius: 8px;
    background: var(--surface-2);
}
.skeleton-shimmer::after {
    content: ""; position: absolute; inset: 0; transform: translateX(-100%);
    background: linear-gradient(90deg, transparent, rgba(var(--shadow-rgb), .08), transparent);
    animation: mv-skeleton-sweep 1.4s ease-in-out infinite;
}
:root[data-theme="dark"] .skeleton-shimmer::after {
    background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .07), transparent);
}
@keyframes mv-skeleton-sweep {
    from { transform: translateX(-100%); }
    to   { transform: translateX(100%); }
}
@media (prefers-reduced-motion: reduce) {
    .skeleton-shimmer::after { animation: none; opacity: .5; transform: none; }
}

.metric-caveat {
    margin-top: .8rem; padding: .45rem .65rem; border-radius: 8px;
    background: var(--alert-warning-bg); border: 1px solid var(--alert-warning-border);
    font-size: .74rem; line-height: 1.35; font-weight: 600; color: var(--alert-warning-color);
}

.card {
    background: var(--card); border-radius: 14px; border: 1px solid var(--line);
    box-shadow: 0 6px 18px -14px rgba(var(--shadow-rgb), .2);
    padding: 1.1rem 1.2rem 1.3rem; margin-bottom: 1rem;
    transition: background-color .2s, border-color .2s;
    position: relative; overflow: hidden;
}
/* Halo au survol qui suit le curseur (retour utilisateur du 21/09/2026) -
   --mx/--my sont poses par static/js/polish.js (un seul listener delegue
   sur toute la page, aucun code a ajouter par carte/par page). Tres
   transparent + pointer-events:none : jamais genant meme quand il deborde
   visuellement sur un bouton/select de la carte. */
.card::before {
    content: ""; position: absolute; inset: 0; pointer-events: none;
    background: radial-gradient(220px circle at var(--mx, 50%) var(--my, 50%),
        rgba(var(--accent-rgb), .12), transparent 70%);
    opacity: 0; transition: opacity .3s ease;
}
.card:hover::before { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
    .card::before { transition: none; }
}
.card h4 {
    font-family: 'IBM Plex Sans', sans-serif; font-weight: 600; font-size: 1.05rem;
    margin: 0 0 .7rem;
}
.chart-wrap { position: relative; width: 100%; }
/* Loader par graphique (Distributions) — chaque histogramme arrive
   indépendamment ; ce calque occupe le .chart-wrap (position: relative
   ci-dessus) tant que sa requête n'est pas revenue, réutilise .scan-spinner
   (déjà défini plus bas pour la page Comparaison) plutôt qu'un second style
   de spinner. */
.chart-loading {
    position: absolute; inset: 0; display: flex; flex-direction: column;
    align-items: center; justify-content: center; gap: .6rem; text-align: center;
}
/* `display: flex` ci-dessus a la même spécificité que la règle par défaut
   du navigateur pour [hidden] mais vient d'une feuille de style auteur, qui
   gagne toujours sur la feuille par défaut du navigateur — sans cette
   ligne, loading.hidden = true (JS) ne masquait plus rien une fois le
   graphique chargé, d'où le spinner resté affiché par-dessus. */
.chart-loading[hidden] { display: none; }
.chart-loading-text { font-size: .82rem; color: var(--muted); }
.chart-load-error { font-size: .85rem; color: var(--alert-error-color); }
.cards-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 1rem; }
/* Fixed 3-per-row layout (Distributions) — wider cards than the auto-fit
   .cards-row above, which can pack in 4-6 narrow ones on a wide monitor. */
.cards-row-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; }
@media (max-width: 1400px) { .cards-row-3 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 900px) { .cards-row-3 { grid-template-columns: 1fr; } }
/* Carte qui prend toute la largeur de la grille (utilise par la carte
   "Probabilite moyenne par matiere" sur Distributions - peu de barres mais
   des libelles longs, cf le meme graphique en pleine largeur sur Vue
   d'ensemble ; en 1/3 de colonne les libelles se chevauchent). */
.cards-row-3 .card-span-all { grid-column: 1 / -1; }

/* --- Bouton d'info par graphique (explication + requête SQL réelle) -----*/
.chart-info-toggle {
    flex: none; margin-left: auto; width: 22px; height: 22px; border-radius: 50%;
    border: 1px solid var(--line); background: var(--card); color: var(--muted);
    font-family: 'IBM Plex Mono', 'Courier New', monospace; font-size: .72rem; font-weight: 700;
    line-height: 1; cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
    padding: 0; transition: color .12s ease, border-color .12s ease, background-color .12s ease;
}
.chart-info-toggle:hover,
.chart-info-toggle[aria-expanded="true"] { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }

.card-title-row { display: flex; align-items: center; justify-content: space-between; gap: .6rem; margin: 0 0 .7rem; }
.card-title-row h4 { margin: 0; }

.chart-info-panel {
    background: var(--surface-2); border: 1px solid var(--line); border-radius: 10px;
    padding: .85rem 1rem; margin: -.35rem 0 1rem; font-size: .85rem; color: var(--muted); line-height: 1.5;
}
.chart-info-panel p { margin: 0 0 .6rem; }
.chart-info-panel p:last-of-type { margin-bottom: 0; }
.chart-info-panel .chart-info-sql {
    background: var(--card); border: 1px solid var(--line); border-radius: 8px;
    padding: .7rem .85rem; margin: .6rem 0 0; overflow-x: auto;
    font-family: 'IBM Plex Mono', 'Courier New', monospace; font-size: .74rem; line-height: 1.55; color: var(--ink);
}
.chart-info-panel .chart-info-sql:last-child { margin-bottom: 0; }

.table-wrap { overflow-x: auto; border-radius: 12px; border: 1px solid var(--line); }
table.metheore-table { width: 100%; border-collapse: collapse; font-size: .92rem; background: var(--card); }
table.metheore-table thead th {
    font-family: 'IBM Plex Mono', 'Courier New', monospace;
    background: var(--surface-2); color: var(--muted); text-transform: uppercase;
    font-size: .7rem; letter-spacing: .05em; font-weight: 600;
    text-align: left; padding: .7rem .95rem; border-bottom: 1px solid var(--line);
    white-space: nowrap;
}
table.metheore-table tbody td { padding: .55rem .95rem; border-bottom: 1px solid var(--line); color: var(--ink); }
table.metheore-table tbody tr:last-child td { border-bottom: none; }
table.metheore-table tbody tr:hover td { background: var(--surface-hover); }
table.metheore-table tbody td:first-child, table.metheore-table thead th:first-child { padding-left: 1.1rem; }
table.metheore-table td.num, table.metheore-table th.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
table.metheore-table td.file-cell { font-weight: 700; color: var(--ink); white-space: nowrap; }

.alert {
    border-radius: 12px; padding: .8rem 1rem; font-size: .9rem;
    background: var(--alert-bg); border: 1px solid var(--alert-border); color: var(--alert-color);
}
.alert.alert-error { background: var(--alert-error-bg); border-color: var(--alert-error-border); color: var(--alert-error-color); }
.alert.alert-warning { background: var(--alert-warning-bg); border-color: var(--alert-warning-border); color: var(--alert-warning-color); }
.alert.alert-success { background: var(--alert-success-bg); border-color: var(--alert-success-border); color: var(--alert-success-color); }

/* --- Top-level tabs (Historique / Temps réel) ---------------------------- */
.top-tabs { display: flex; gap: .4rem; border-bottom: 1px solid var(--line); margin-bottom: 1.3rem; }
.top-tabs a {
    padding: .55rem 1.1rem; border-radius: 10px 10px 0 0; font-weight: 700;
    font-size: .95rem; color: var(--muted);
}
.top-tabs a:hover { color: var(--ink); }
.top-tabs a.active { color: var(--accent); border-bottom: 2px solid var(--accent); }

.inner-tabs { display: flex; gap: .4rem; margin-bottom: 1.3rem; flex-wrap: wrap; }
.inner-tabs a {
    padding: .5rem 1rem; border-radius: 999px; font-weight: 600; font-size: .85rem;
    color: var(--muted); background: var(--card); border: 1px solid var(--line);
}
.inner-tabs a:hover { color: var(--ink); }
.inner-tabs a.active { color: #fff; background: var(--accent); border-color: var(--accent); }
a.scan-link-disabled { pointer-events: none; opacity: .55; }

/* --- Realtime status bar -------------------------------------------------*/
.realtime-status {
    display: flex; align-items: center; gap: .7rem;
    margin: 0 0 1.4rem; padding: .55rem .9rem; border-radius: 10px;
    background: var(--card); border: 1px solid var(--line);
    box-shadow: 0 6px 18px -14px rgba(var(--shadow-rgb), .2); width: fit-content;
}
.realtime-dot { width: 10px; height: 10px; border-radius: 50%; background: #E34948;
    box-shadow: 0 0 0 0 rgba(227, 73, 72, .55);
    animation: realtime-pulse 1.8s infinite; flex: none;
}
.realtime-dot.is-paused { background: #8fa09c; animation: none; }
@keyframes realtime-pulse {
    0%   { box-shadow: 0 0 0 0 rgba(227, 73, 72, .55); }
    70%  { box-shadow: 0 0 0 8px rgba(227, 73, 72, 0); }
    100% { box-shadow: 0 0 0 0 rgba(227, 73, 72, 0); }
}
.realtime-status-main { font-weight: 700; font-size: .92rem; color: var(--ink); }
.realtime-status-sub { font-size: .8rem; color: var(--muted); }
.realtime-status-sep { width: 1px; height: 1.1rem; background: var(--line); }

/* --- Sensor tiles / gantt legend ----------------------------------------*/
.sensor-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: .9rem; margin-bottom: 1rem; }
.gantt-legend { display: flex; gap: 1.1rem; flex-wrap: wrap; margin-top: .7rem; font-size: .82rem; color: var(--muted); }
.gantt-legend .chip { display: inline-flex; align-items: center; gap: .4rem; }
.gantt-legend .swatch { width: 11px; height: 11px; border-radius: 3px; flex: none; }

/* --- Drift class toggles --------------------------------------------------*/
.class-toggles { display: flex; flex-wrap: wrap; gap: .5rem; margin-bottom: 1rem; }
.class-toggles label {
    display: inline-flex; align-items: center; gap: .4rem; font-size: .82rem;
    padding: .35rem .65rem; border-radius: 999px; border: 1px solid var(--line);
    cursor: pointer; user-select: none; color: var(--muted); font-weight: 600;
}
.class-toggles label.checked { color: var(--ink); border-color: var(--line); background: var(--surface-2); }
.class-toggles input { accent-color: var(--accent); }
.class-toggles .swatch { width: 9px; height: 9px; border-radius: 50%; flex: none; }

/* --- Scan loader (loader de marque MatVision, utilise partout ou un
   graphique/une tuile attend ses donnees - voir .chart-loading plus haut
   et les templates classifieur/comparaison/distributions/machine/svm qui
   posent tous <div class="scan-spinner"></div>, donc ce seul fichier CSS
   suffit a changer le loader partout). Idee reprise du site vitrine
   matvision.instawp.site (retour utilisateur du 21/09/2026 : "un loader
   avec le logo MatVision", puis correction le meme jour : pas l'icone
   capteur 3D avec un balayage, mais LE LOGO MV LUI-MEME avec ses deux
   traits en arc qui tournent autour - c'est le motif deja present dans le
   monogramme de marque, voir Charte_Graphique/00_Marque/01_Logos
   Matvision/Logo/03 Icone/*/MV-logo-icone-*.svg (pas encore dans ce
   depot - seul le logo complet "MATVISION" en toutes lettres existe sous
   static/img/). Les lettres M+V du monogramme restent fixes (calque
   ::before), les deux arcs qui les encadrent tournent en continu (calque
   ::after) - meme viewBox pour les deux calques donc ils restent
   parfaitement alignes pendant la rotation. Les deux SVG (lettres seules
   / arcs seuls) sont reencodes en data-URI ici plutot que charges depuis
   static/ : theme.css a deja son propre cache-busting (voir
   static_url() dans templates_config.py) alors qu'une URL statique vers
   un fichier source n'en aurait pas. */
.scan-loader { display: flex; flex-direction: column; gap: .8rem; }
.scan-loader[hidden] { display: none; }
.scan-loader-row { display: flex; align-items: center; gap: .9rem; }
.scan-spinner { width: 40px; height: 40px; flex: none; position: relative; }
.scan-spinner::before, .scan-spinner::after {
    content: ""; position: absolute; inset: 0;
    -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
    -webkit-mask-position: center; mask-position: center;
    -webkit-mask-size: contain; mask-size: contain;
}
/* Lettres M+V du monogramme - calque fixe, ne tourne pas. */
.scan-spinner::before {
    -webkit-mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMzUwLjEgMTIwMCI+PHBvbHlnb24gcG9pbnRzPSIzODguOTEgNTEyLjkzIDQ0Ny45OCA3ODAuNjQgNTMyLjI0IDc4MC42NCA1MzMuMTcgNzgwLjY0IDUzMi44OSA3NzkuMzMgNjY2Ljk1IDUxMi45MyA2NzAuNzQgNTEyLjkzIDY3MC43NCA3ODAuNjQgNzU1Ljk4IDc4MC42NCA3NTUuOTggNDE5LjQxIDcxMy42MSA0MTkuNDEgNjcwLjc0IDQxOS40MSA2MzEuNzIgNDE5LjQxIDUwNi40IDY2NS43NiA0NDguOTIgNDE5LjQxIDM0OS44OSA0MTkuNDEgMTY2LjExIDc4MC42NCAyNTAuNCA3ODAuNjQgMzg1LjExIDUxMi45MyAzODguOTEgNTEyLjkzIi8+PHBvbHlnb24gcG9pbnRzPSIxMDk5Ljc0IDQxOS40MSA5NjUuMDMgNjg3LjA3IDk2MS4xOSA2ODcuMDcgOTAyLjE2IDQxOS40MSA4MTYuOTcgNDE5LjQxIDkwMS4yMiA3ODAuNjQgMTAwMC4yNiA3ODAuNjQgMTE4NCA0MTkuNDEgMTA5OS43NCA0MTkuNDEiLz48L3N2Zz4=");
    mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMzUwLjEgMTIwMCI+PHBvbHlnb24gcG9pbnRzPSIzODguOTEgNTEyLjkzIDQ0Ny45OCA3ODAuNjQgNTMyLjI0IDc4MC42NCA1MzMuMTcgNzgwLjY0IDUzMi44OSA3NzkuMzMgNjY2Ljk1IDUxMi45MyA2NzAuNzQgNTEyLjkzIDY3MC43NCA3ODAuNjQgNzU1Ljk4IDc4MC42NCA3NTUuOTggNDE5LjQxIDcxMy42MSA0MTkuNDEgNjcwLjc0IDQxOS40MSA2MzEuNzIgNDE5LjQxIDUwNi40IDY2NS43NiA0NDguOTIgNDE5LjQxIDM0OS44OSA0MTkuNDEgMTY2LjExIDc4MC42NCAyNTAuNCA3ODAuNjQgMzg1LjExIDUxMi45MyAzODguOTEgNTEyLjkzIi8+PHBvbHlnb24gcG9pbnRzPSIxMDk5Ljc0IDQxOS40MSA5NjUuMDMgNjg3LjA3IDk2MS4xOSA2ODcuMDcgOTAyLjE2IDQxOS40MSA4MTYuOTcgNDE5LjQxIDkwMS4yMiA3ODAuNjQgMTAwMC4yNiA3ODAuNjQgMTE4NCA0MTkuNDEgMTA5OS43NCA0MTkuNDEiLz48L3N2Zz4=");
    background: var(--ink);
}
/* Les deux arcs du monogramme - calque qui tourne en continu ; meme
   viewBox que les lettres ci-dessus donc le centre de rotation
   (transform-origin: 50% 50%, le centre de la boite) correspond bien au
   centre visuel du monogramme complet. */
.scan-spinner::after {
    -webkit-mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMzUwLjEgMTIwMCI+PHBhdGggZD0iTTgyMS4zLDIzMS40MnYuMDRjMTIyLjA2LDAsMTgyLjUxLDEwNy42NSwxNTguNDcsMjU2LjI0aDIzLjUxYzQwLjMzLTE4NS4yMS0xOS44LTMyMS41OS0xNTUuOTQtMzIxLjU5LTkzLjk3LDAtMjAzLjU0LDY0Ljk5LTI5Ny43MSwxNjYuMWg0My40N2M3My45My02Mi40NiwxNTUuMzctMTAwLjc5LDIyOC4xOS0xMDAuNzlaIi8+PHBhdGggZD0iTTUyNy4zLDk2OC41OGgtLjA0Yy0xMjMuMTYsMC0xODMuNTctMTA5LjU3LTE1Ny44Mi0yNjAuMjhoLTIzLjMxYy00Mi4wOSwxODcuMjUsMTcuOTYsMzI1LjU5LDE1NS4wOCwzMjUuNTksOTUuMTksMCwyMDYuMzktNjYuNywzMDEuNDMtMTcwLjFoLTQyLjQ5Yy03NS4xNSw2NC43OS0xNTguNTEsMTA0Ljc5LTIzMi44NSwxMDQuNzlaIi8+PC9zdmc+");
    mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMzUwLjEgMTIwMCI+PHBhdGggZD0iTTgyMS4zLDIzMS40MnYuMDRjMTIyLjA2LDAsMTgyLjUxLDEwNy42NSwxNTguNDcsMjU2LjI0aDIzLjUxYzQwLjMzLTE4NS4yMS0xOS44LTMyMS41OS0xNTUuOTQtMzIxLjU5LTkzLjk3LDAtMjAzLjU0LDY0Ljk5LTI5Ny43MSwxNjYuMWg0My40N2M3My45My02Mi40NiwxNTUuMzctMTAwLjc5LDIyOC4xOS0xMDAuNzlaIi8+PHBhdGggZD0iTTUyNy4zLDk2OC41OGgtLjA0Yy0xMjMuMTYsMC0xODMuNTctMTA5LjU3LTE1Ny44Mi0yNjAuMjhoLTIzLjMxYy00Mi4wOSwxODcuMjUsMTcuOTYsMzI1LjU5LDE1NS4wOCwzMjUuNTksOTUuMTksMCwyMDYuMzktNjYuNywzMDEuNDMtMTcwLjFoLTQyLjQ5Yy03NS4xNSw2NC43OS0xNTguNTEsMTA0Ljc5LTIzMi44NSwxMDQuNzlaIi8+PC9zdmc+");
    background: var(--accent);
    animation: mv-orbit-spin 1.6s linear infinite;
    transform-origin: 50% 50%;
}
.scan-spinner-sm { width: 22px; height: 22px; }
@keyframes mv-orbit-spin {
    from { transform: rotate(0deg); }
    to   { transform: rotate(360deg); }
}
.scan-loader-text { min-width: 0; }
.scan-loader-main { font-weight: 700; font-size: .95rem; color: var(--ink); }
.scan-loader-sub { font-size: .82rem; color: var(--muted); margin-top: .15rem; }
.scan-progress { height: 8px; border-radius: 999px; background: var(--line); overflow: hidden; }
.scan-progress-bar { height: 100%; width: 2%; border-radius: 999px; background: var(--accent); transition: width .35s ease; }
.scan-loader.scan-loader-failed .scan-spinner::before { background: var(--alert-error-color); }
.scan-loader.scan-loader-failed .scan-spinner::after { animation: none; background: var(--alert-error-color); }
.scan-loader.scan-loader-failed .scan-progress-bar { background: var(--alert-error-color); }
.scan-loader-error { color: var(--alert-error-color); font-weight: 600; }

/* --- Export loader (retour utilisateur du 24/09/2026 : "un petit message
   en mode export en cours" pendant les exports CSV/Excel qui prennent du
   temps) - reutilise .scan-loader/.scan-spinner ci-dessus (le loader de
   marque MatVision) plutot qu'un nouveau style, juste positionne en toast
   fixe en bas a droite puisqu'un export se declenche depuis un bouton, pas
   depuis une zone de la page dediee a l'attente (contrairement a
   .chart-loading). Un seul exemplaire partage par toutes les pages avec un
   bouton Export (voir templates/base.html et static/js/export-download.js)
   plutot qu'une copie par bouton. */
.export-loader-overlay {
    position: fixed; right: 1.2rem; bottom: 1.2rem; z-index: 500;
    min-width: 260px; max-width: 320px;
    box-shadow: 0 12px 30px -10px rgba(var(--shadow-rgb), .35);
    animation: mv-fade-in-swap .25s ease both;
}

/* --- Comparison table / per-model donuts --------------------------------*/
.pie-card { text-align: center; }
.pie-card h4 { font-size: .92rem; }
.pie-card .chart-wrap { height: 190px; margin: 0 auto; }
.pie-card .pie-caption { font-size: .74rem; color: var(--muted); margin-top: .6rem; }

/* --- Collapsible "Erreurs et avertissements" table -----------------------*/
.events-details summary {
    cursor: pointer; font-weight: 700; font-size: .95rem; color: var(--ink);
    list-style: none; display: flex; align-items: center; gap: .5rem;
}
.events-details summary::-webkit-details-marker { display: none; }
.events-details summary::before {
    content: "▸"; color: var(--accent); font-size: .8rem; transition: transform .15s ease;
}
.events-details[open] summary::before { transform: rotate(90deg); }

/* --- Custom donut legend (chip grid, replaces Chart.js's own legend) ----*/
.donut-legend {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: .35rem .6rem; margin-top: 1rem; padding-top: .9rem; border-top: 1px solid var(--line);
}
.donut-legend .item {
    display: flex; align-items: center; gap: .5rem; font-size: .82rem; color: var(--muted);
    min-width: 0;
}
.donut-legend .swatch { width: 9px; height: 9px; border-radius: 50%; flex: none; }
.donut-legend .name { color: var(--ink); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.donut-legend .pct { font-variant-numeric: tabular-nums; font-weight: 600; color: var(--muted); flex: none; margin-left: .35rem; }

/* --- Convoyeur (Position) : animation canvas, voir static/js/conveyor.js -*/
.conveyor-wrap {
    position: relative; height: 230px; border-radius: 10px; overflow: hidden;
    background: var(--surface-2); border: 1px solid var(--line);
}
.conveyor-wrap canvas { display: block; width: 100%; height: 100%; cursor: default; }
/* Overlay "Production a l'arret" (retour operateur du 28/09/2026) : le
   convoyeur (rails/chevrons, et les pieces deja en cours de traversee)
   reste visible et anime derriere - seul ce message se superpose au
   centre quand aucune piece recente n'est detectee en direct, pour bien
   distinguer "surveille normalement, juste rien dessus la" d'une panne du
   dashboard. rgba(var(--shadow-rgb), ...) plutot qu'une couleur fixe :
   coherent avec les deux themes clair/sombre (voir --shadow-rgb, deja
   utilise pour les ombres portees plus haut dans ce fichier). */
.conveyor-idle-overlay {
    position: absolute; inset: 0; z-index: 3;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: .25rem; text-align: center; pointer-events: none;
    background: rgba(var(--shadow-rgb), .82);
}
/* Sans ce override, ce `display: flex` (spécificité de classe) bat toujours le
   `display: none` de l'attribut natif [hidden] défini par la feuille UA du
   navigateur : même spécificité, mais la feuille auteur passe après la feuille
   UA dans la cascade. Résultat : idle.hidden = true/false en JS n'avait aucun
   effet visuel, l'overlay "Production à l'arrêt" restait affiché en permanence. */
.conveyor-idle-overlay[hidden] {
    display: none;
}
.conveyor-idle-title { font-weight: 700; font-size: .98rem; color: #fff; letter-spacing: .01em; }
.conveyor-idle-sub { font-size: .78rem; color: rgba(255, 255, 255, .75); }
.conveyor-tooltip {
    position: absolute; pointer-events: none; z-index: 2;
    background: var(--ink); color: var(--card);
    font-size: .72rem; font-weight: 600; padding: .3rem .55rem; border-radius: 6px;
    white-space: nowrap; transform: translate(-50%, -130%);
    opacity: 0; transition: opacity .1s ease;
}
.conveyor-tooltip.visible { opacity: 1; }

/* --- Convoyeur : contrôles de rejeu (mode "replay", période historique) -*/
.conveyor-controls {
    display: flex; align-items: center; gap: .8rem;
    padding: .65rem .9rem; border-top: 1px solid var(--line);
}
.conveyor-play {
    flex: none; width: 30px; height: 30px; border-radius: 50%;
    border: 1px solid var(--line); background: var(--card); color: var(--ink);
    display: flex; align-items: center; justify-content: center; cursor: pointer;
    font-size: .8rem; line-height: 1; padding: 0; transition: border-color .12s ease, background .12s ease;
}
.conveyor-play:hover { border-color: var(--accent); background: var(--accent-soft); }
.conveyor-seek {
    flex: 1; appearance: none; -webkit-appearance: none; height: 3px;
    background: var(--line); border-radius: 999px; outline: none; cursor: pointer;
}
.conveyor-seek::-webkit-slider-thumb {
    appearance: none; -webkit-appearance: none; width: 13px; height: 13px; border-radius: 50%;
    background: var(--accent); border: 2px solid var(--card); box-shadow: 0 0 0 1px var(--accent); cursor: pointer;
}
.conveyor-seek::-moz-range-thumb {
    width: 13px; height: 13px; border-radius: 50%; background: var(--accent);
    border: 2px solid var(--card); box-shadow: 0 0 0 1px var(--accent); cursor: pointer;
}
.conveyor-time {
    flex: none; font-family: 'IBM Plex Mono', 'Courier New', monospace; font-size: .74rem;
    color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* --- Paramètres : éditeur de config (materials.json / thresholds.json /
   permissions.json — retour utilisateur du 21/09/2026, "je veux pouvoir
   modifier mes fichiers de config d'ici") -----------------------------------
   Formulaires "conviviaux" plutôt qu'un éditeur JSON brut : chaque fichier
   garde sa structure (mêmes clés, même nombre d'entrées - voir
   config_editor.py) et n'est réécrit qu'après validation. Les couleurs de
   fond alternées de .cfg-row/.cfg-perm-page reprennent --surface-2, comme
   les autres tableaux du dashboard (table.metheore-table), pour rester dans
   le même langage visuel plutôt qu'inventer un nouveau style de tableau. */
.cfg-hint { margin: 0 0 1rem; color: var(--muted); font-size: .84rem; line-height: 1.45; }
.cfg-subheading {
    font-weight: 700; font-size: .92rem; color: var(--ink);
    margin: 1.6rem 0 .3rem; padding-top: 1.2rem; border-top: 1px solid var(--line);
}

.cfg-table { display: flex; flex-direction: column; gap: .3rem; }
.cfg-table-head {
    display: grid; grid-template-columns: 28px 1fr 220px; gap: .8rem; align-items: center;
    padding: 0 .7rem .4rem; font-family: 'IBM Plex Mono', 'Courier New', monospace;
    font-size: .66rem; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); font-weight: 600;
}
.cfg-table-thresholds { grid-template-columns: none; }
.cfg-row {
    display: grid; grid-template-columns: 28px 1fr 220px; gap: .8rem; align-items: center;
    padding: .5rem .7rem; border-radius: 10px; background: var(--surface-2);
}
.cfg-table-thresholds .cfg-row {
    grid-template-columns: 14px 1fr 140px 46px; gap: .9rem;
}
.cfg-row + .cfg-row { margin-top: .3rem; }

.cfg-color {
    width: 26px; height: 26px; padding: 0; border: 1px solid var(--line); border-radius: 7px;
    background: none; cursor: pointer;
}
.cfg-swatch { width: 14px; height: 14px; border-radius: 50%; flex: none; }
.cfg-text, .cfg-select {
    width: 100%; background: var(--card); border: 1px solid var(--line); border-radius: 7px;
    color: var(--ink); padding: .45rem .6rem; font-size: .84rem; font-family: 'IBM Plex Sans', sans-serif;
}
.cfg-text:hover, .cfg-text:focus, .cfg-select:hover, .cfg-select:focus { border-color: var(--accent); outline: none; }
.cfg-threshold-label { font-size: .86rem; color: var(--ink); }
.cfg-threshold-range { width: 100%; accent-color: var(--accent); }
.cfg-threshold-value {
    font-family: 'IBM Plex Mono', 'Courier New', monospace; font-size: .8rem; color: var(--muted);
    text-align: right; font-variant-numeric: tabular-nums;
}

.cfg-cat-list { display: flex; flex-direction: column; gap: .3rem; }
.cfg-cat-row {
    display: grid; grid-template-columns: 44px 26px 1fr; gap: .8rem; align-items: center;
    padding: .5rem .7rem; border-radius: 10px; background: var(--surface-2);
}
.cfg-cat-reorder { display: flex; gap: .2rem; }
.cfg-move-btn {
    width: 20px; height: 20px; padding: 0; display: flex; align-items: center; justify-content: center;
    border: 1px solid var(--line); border-radius: 5px; background: var(--card); color: var(--muted);
    font-size: .6rem; cursor: pointer; line-height: 1;
}
.cfg-move-btn:hover { border-color: var(--accent); color: var(--accent); }

.cfg-actions { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; margin-top: 1.3rem; }
.cfg-save-btn {
    background: var(--accent); border: 1px solid var(--accent); color: #fff;
    border-radius: 8px; padding: .55rem 1.2rem; font-size: .84rem; font-weight: 600;
    cursor: pointer; flex: none;
}
.cfg-save-btn:hover { background: #00742A; border-color: #00742A; }
.cfg-actions .alert { padding: .5rem .85rem; font-size: .8rem; flex: 1; min-width: 220px; }

.cfg-perm-table { display: flex; flex-direction: column; gap: .2rem; }
.cfg-perm-head {
    display: grid; grid-template-columns: 1fr repeat(2, 110px); gap: .5rem; align-items: center;
    padding: 0 .7rem .5rem; font-family: 'IBM Plex Mono', 'Courier New', monospace;
    font-size: .66rem; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); font-weight: 600;
}
.cfg-perm-head span:not(:first-child) { text-align: center; }
.cfg-perm-page { border-radius: 10px; overflow: hidden; margin-bottom: .3rem; }
.cfg-perm-page-row, .cfg-perm-element-row {
    display: grid; grid-template-columns: 1fr repeat(2, 110px); gap: .5rem; align-items: center;
    padding: .55rem .7rem; background: var(--surface-2);
}
.cfg-perm-page-row { font-weight: 600; color: var(--ink); font-size: .88rem; }
.cfg-perm-element-row { background: var(--card); border: 1px solid var(--line); border-top: none; font-size: .82rem; color: var(--muted); }
.cfg-perm-page-label { }
.cfg-perm-element-label { padding-left: .6rem; }
.cfg-perm-checkbox { display: flex; align-items: center; justify-content: center; cursor: pointer; }
.cfg-perm-checkbox input { width: 16px; height: 16px; accent-color: var(--accent); cursor: pointer; }
.cfg-perm-checkbox.cfg-perm-locked { cursor: default; opacity: .55; }
.cfg-perm-checkbox.cfg-perm-locked input { cursor: default; }

/* --- État machine/capteurs ------------------------------------------------*/
.status-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: .9rem; margin-bottom: .4rem; }
.status-card {
    background: var(--card); border: 1px solid var(--line); border-radius: 14px;
    padding: 1rem 1.1rem; display: flex; align-items: center; gap: .7rem;
    box-shadow: 0 6px 18px -14px rgba(var(--shadow-rgb), .2);
}
.status-dot { width: 12px; height: 12px; border-radius: 50%; flex: none; }
.status-dot.ok { background: #4E9E8E; }
.status-dot.bad { background: #E34948; }
.status-dot.unknown { background: #8fa09c; }
.status-card-body { min-width: 0; }
.status-card-name { font-weight: 700; font-size: .95rem; color: var(--ink); text-transform: capitalize; }
.status-card-value { font-size: .8rem; color: var(--muted); margin-top: .15rem; }

@media (max-width: 900px) {
    .layout { flex-direction: column; }
    .sidebar { width: 100%; flex: none; }
    .main { padding: 1.2rem; }
}


/* --- Page de connexion (login.html, pas de sidebar) ---------------------*/
.login-page {
    min-height: 100vh; display: flex; flex-direction: column; align-items: center;
    justify-content: center; gap: 1.6rem; padding: 2rem 1rem;
    background: linear-gradient(180deg, #00742A 0%, #00431A 100%);
}
.login-logo { width: 100%; max-width: 220px; height: auto; }
.login-card {
    width: 100%; max-width: 360px;
    background: var(--card); border-radius: 16px; border: 1px solid var(--line);
    box-shadow: 0 20px 50px -20px rgba(0, 0, 0, .45);
    padding: 2rem 1.8rem 1.8rem;
}
.login-title { font-size: 1.4rem; font-weight: 600; margin: 0 0 .3rem; color: var(--ink); }
.login-subtitle { margin: 0 0 1.4rem; color: var(--muted); font-size: .88rem; }
.login-alert { margin-bottom: 1.1rem; }
.login-form { display: flex; flex-direction: column; }
.login-label {
    font-family: 'IBM Plex Mono', 'Courier New', monospace; font-size: .66rem;
    text-transform: uppercase; letter-spacing: .05em; color: var(--muted);
    margin: .7rem 0 .35rem;
}
.login-input {
    background: var(--surface-2); border: 1px solid var(--line); border-radius: 8px;
    color: var(--ink); padding: .6rem .75rem; font-size: .92rem;
    font-family: 'IBM Plex Sans', sans-serif;
}
.login-input:hover, .login-input:focus { border-color: var(--accent); outline: none; }
.login-submit {
    margin-top: 1.3rem; background: var(--accent); border: 1px solid var(--accent);
    border-radius: 8px; color: #fff; cursor: pointer; font-weight: 600; font-size: .92rem;
    padding: .68rem 1rem; transition: background .15s, border-color .15s;
}
.login-submit:hover { background: #00742A; border-color: #00742A; }

/* --- Utilisateur connecté + déconnexion (juste sous le logo, voir
   base.html - retour utilisateur du 21/09/2026 : trop dur à trouver tout
   en bas de la sidebar) ------------------------------------------------ */
.sidebar-user {
    padding: .8rem 1.5rem; border-bottom: 1px solid rgba(255, 255, 255, .14);
    display: flex; align-items: center; justify-content: space-between; gap: .6rem;
}
.sidebar-user-info { min-width: 0; }
.sidebar-user-name {
    font-size: .82rem; font-weight: 600; color: #fff;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sidebar-user-role { font-size: .7rem; color: #A9D6AF; text-transform: uppercase; letter-spacing: .04em; }
.sidebar-logout-form { margin: 0; flex: none; }
.sidebar-logout {
    background: rgba(255, 255, 255, .1); border: 1px solid rgba(255, 255, 255, .2);
    border-radius: 7px; color: #EAF6EC; cursor: pointer; font-size: .72rem; font-weight: 600;
    padding: .4rem .6rem; font-family: 'IBM Plex Sans', sans-serif;
}
.sidebar-logout:hover { border-color: rgba(144, 227, 119, .65); color: #fff; }


/* --- Page Comparaison (deux blocs indépendants côte à côte) -------------*/
.comparaison-columns {
    display: grid; grid-template-columns: 1fr 1fr; gap: 2rem 1.8rem; align-items: start;
}
@media (max-width: 1100px) { .comparaison-columns { grid-template-columns: 1fr; } }

/* Chaque bloc (filtres + resultats) est encadre dans sa propre boite
   coloree, pour qu'on sache toujours "qui est qui" une fois les 4 blocs
   affiches en grille 2x2 (retour utilisateur du 21/09/2026) - la couleur
   est portee par data-block-color (1 a 4, voir comparaison.html) et
   reutilisee a l'identique par la barre de navigation .comparaison-nav
   ci-dessous, pour que le lien "bouton -> bloc" soit evident. */
.comparaison-block {
    min-width: 0; border: 2px solid var(--block-accent, var(--accent)); border-radius: 14px;
    padding: 1.3rem 1.3rem 1.6rem; scroll-margin-top: 1rem;
}
.comparaison-block[data-block-color="1"] { --block-accent: #008B29; }
.comparaison-block[data-block-color="2"] { --block-accent: #1E6FD9; }
.comparaison-block[data-block-color="3"] { --block-accent: #C77700; }
.comparaison-block[data-block-color="4"] { --block-accent: #8E44AD; }
.comparaison-block-header { display: flex; align-items: center; gap: .6rem; margin-bottom: 1rem; }

/* Bouton Activer/Desactiver des blocs 3 et 4 - meme motif visuel
   (piste + curseur) que .theme-toggle dans la sidebar (voir plus haut),
   mais colore par --block-accent au lieu du vert fixe, et pousse a droite
   de l'entete du bloc via margin-left:auto. La checkbox reelle est rendue
   invisible (pas display:none, pour rester focusable au clavier) -
   :checked pilote le curseur en CSS pur, aucun etat a synchroniser en JS
   a part montrer/cacher le bloc (voir comparaison.html). */
.comparaison-block-toggle {
    display: inline-flex; align-items: center; gap: .5rem; margin-left: auto;
    font-size: .78rem; font-weight: 600; color: var(--muted); cursor: pointer; user-select: none;
}
.comparaison-toggle-input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.comparaison-block-toggle .track {
    width: 34px; height: 20px; border-radius: 999px; background: var(--line);
    position: relative; flex: none; transition: background .15s;
}
.comparaison-block-toggle .knob {
    position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%;
    background: #fff; box-shadow: 0 1px 2px rgba(0, 0, 0, .25); transition: transform .18s ease;
}
.comparaison-toggle-input:checked ~ .track { background: var(--block-accent, var(--accent)); }
.comparaison-toggle-input:checked ~ .track .knob { transform: translateX(14px); }
.comparaison-toggle-input:focus-visible ~ .track { outline: 2px solid var(--block-accent, var(--accent)); outline-offset: 2px; }
.comparaison-block-dot { width: 13px; height: 13px; border-radius: 50%; background: var(--block-accent, var(--accent)); flex: none; }
.comparaison-block-header h3 { font-size: 1.2rem; color: var(--block-accent, var(--accent)); }
.comparaison-block .cards-row-3 { grid-template-columns: repeat(2, 1fr); }
@media (max-width: 700px) { .comparaison-block .cards-row-3 { grid-template-columns: 1fr; } }

/* Barre de navigation horizontale : un bouton par bloc, meme couleur que
   son cadre, qui scroll juste vers le bloc correspondant (aucun etat
   cache/affiche - voir la question posee a l'utilisateur le 21/09/2026,
   reponse "les 4 toujours visibles"). */
.comparaison-nav { display: flex; flex-wrap: wrap; gap: .6rem; margin-bottom: 1.4rem; }
.comparaison-nav-btn {
    border: 2px solid var(--block-accent, var(--accent)); color: var(--block-accent, var(--accent));
    background: var(--card); border-radius: 999px; padding: .4rem 1rem; font-weight: 600;
    font-size: .85rem; cursor: pointer; transition: background .15s, color .15s;
}
.comparaison-nav-btn[data-block-color="1"] { --block-accent: #008B29; }
.comparaison-nav-btn[data-block-color="2"] { --block-accent: #1E6FD9; }
.comparaison-nav-btn[data-block-color="3"] { --block-accent: #C77700; }
.comparaison-nav-btn[data-block-color="4"] { --block-accent: #8E44AD; }
.comparaison-nav-btn:hover { background: var(--block-accent, var(--accent)); color: #fff; }
/* Bloc 3/4 pas encore actives : le bouton de nav reste visible (c'est le
   seul moyen de les activer, voir comparaison.html) mais se distingue en
   pointille/attenue, pour ne pas laisser croire que le bloc est deja la. */
.comparaison-nav-btn.is-off { border-style: dashed; opacity: .68; }
.comparaison-nav-btn.is-off:hover { opacity: 1; }

/* Le sélecteur de période (_period_picker_macro.html) est pensé pour la
   sidebar verte pleine (voir .period-input/.period-select
   plus haut, fond blanc translucide + texte clair) - ici il est posé sur
   une carte claire/thémée normale, donc on retourne ses couleurs vers les
   tokens habituels plutôt que de dupliquer tout le balisage. */
.comparaison-filters { margin-bottom: 1.2rem; }
/* Titre "Filtres" en tete de chaque bloc (retour utilisateur du
   21/09/2026), meme composant que .topbar-filter-bar-title ailleurs mais
   avec une marge basse propre puisqu'il n'est pas suivi d'un
   .topbar-filter-bar-row (juste le picker de periode + Matiere a plat). */
.comparaison-filters .topbar-filter-bar-title { margin-bottom: .9rem; }
.comparaison-filters .period-group,
.comparaison-filters .comparaison-matiere-group { margin-bottom: .8rem; }
.comparaison-filters .period-group:last-child,
.comparaison-filters .comparaison-matiere-group:last-child { margin-bottom: 0; }
.comparaison-filters .period-input,
.comparaison-filters .period-select {
    background: var(--surface-2); border: 1px solid var(--line); color: var(--ink);
    color-scheme: light;
}
.comparaison-filters select.period-select { width: 100%; }
.comparaison-filters .period-input:hover, .comparaison-filters .period-input:focus,
.comparaison-filters .period-select:hover, .comparaison-filters .period-select:focus { border-color: var(--accent); outline: none; }
.comparaison-filters .period-select option { color: var(--ink); background-color: var(--card); }
.comparaison-filters .period-group-label { color: var(--muted); }
.comparaison-filters .period-icon-btn { background: var(--surface-2); border: 1px solid var(--line); color: var(--ink); }
.comparaison-filters .period-icon-btn:hover { border-color: var(--accent); }
.comparaison-filters .period-icon-static { color: var(--muted); }
.comparaison-filters .period-time-sep { color: var(--muted); }

.comparaison-submit-row { display: flex; justify-content: center; margin: 1.6rem 0 2rem; }
.comparaison-submit {
    background: var(--accent); border: 1px solid var(--accent); border-radius: 8px;
    color: #fff; cursor: pointer; font-weight: 600; font-size: .95rem;
    padding: .75rem 1.8rem; transition: background .15s, border-color .15s;
}
.comparaison-submit:hover { background: #00742A; border-color: #00742A; }
