/* Polaris Monitoring — feuille de style de l'écran.
   Reprise de la page inline de veille-webapp/veille_serveur_web.py (thème
   sombre, pastilles 🟢🟠🔴⚪, graphes SVG), plus la grille d'accueil (D10). */
:root { color-scheme: dark; }
body { margin: 0; background: #14161a; color: #d6d8dc;
       font: 13px/1.5 'Segoe UI', system-ui, sans-serif; }
/* L'en-tête (ligne d'état + onglets) est le MENU de l'application : toujours
   visible au défilement (demande du 2026-10-04, D10). Le fond est posé sur le
   conteneur — sans lui, les lignes du tableau défileraient EN TRANSPARENCE
   sous les onglets, dont le padding laisse le fond de page apparaître. */
.entete-app { position: sticky; top: 0; z-index: 40; background: #14161a;
              border-bottom: 1px solid #2a2e35; }
header { display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
         padding: 10px 16px; background: #1c1f24; border-bottom: 1px solid #2a2e35; }
h1 { font-size: 14px; font-weight: 600; margin: 0; }
.pastille { width: 14px; height: 14px; border-radius: 50%; background: #555;
            box-shadow: 0 0 6px rgba(0,0,0,.6); flex: none; }
.pastille.vert   { background: #3fb950; box-shadow: 0 0 8px #3fb95088; }
.pastille.orange { background: #d29922; box-shadow: 0 0 8px #d2992288; }
.pastille.rouge  { background: #f85149; box-shadow: 0 0 8px #f8514988; }
.pastille.gris   { background: #6e7681; }
.info { color: #9aa0a8; }
.info b { color: #d6d8dc; font-weight: 600; }
/* Sous-lignes d'une tuile (les trois sources de la tuile « Données AIRAC »,
   2026-09-26) : le JS les émettait déjà en `info sous`, sans que la classe existe
   nulle part — quatre encarts d'apparence identique se suivaient et rien ne disait
   que les trois derniers détaillaient le premier (constat de revue). */
.info.sous { padding-left: 1.4em; font-size: .92em; opacity: .85; }
/* Depuis le 2026-09-20 (D9 puis D15) les sélecteurs de fenêtre/niveau sont
   des <select> dans les lignes .filtres des onglets — la classe .fenetres a
   disparu, les boutons restants sont les onglets, Acquitter et Pause. */
.onglets button, button.acq, button.climat-maj, .filtres button {
  background: #22262c; color: #9aa0a8; border: 1px solid #2a2e35;
  border-radius: 4px; padding: 2px 10px; cursor: pointer; font: inherit; }
.onglets button.actif, .filtres button.actif {
  background: #2d5a8a; color: #fff; border-color: #2d5a8a; }
/* Padding bas depuis que la barre est sticky (2026-10-04) : collés au bord du
   conteneur, les onglets touchaient la première ligne qui défile dessous. */
.onglets { display: flex; gap: 4px; padding: 8px 16px; flex-wrap: wrap; }
table { border-collapse: collapse; width: 100%; }
th, td { text-align: left; padding: 5px 12px; border-bottom: 1px solid #2a2e35; }
th { color: #9aa0a8; font-weight: 600; font-size: 11px;
     text-transform: uppercase; letter-spacing: .04em; }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
.pastille.inline { display: inline-block; vertical-align: middle; width: 10px; height: 10px; }
tbody td:first-child { white-space: nowrap; }   /* dates : jamais coupées sur deux lignes */
.ok  { color: #3fb950; font-weight: 600; }
.ko  { color: #f85149; font-weight: 600; }
.orange { color: #d29922; font-weight: 600; }
section { padding: 12px 16px; }
h2 { font-size: 12px; font-weight: 600; color: #9aa0a8; margin: 0 0 4px;
     text-transform: uppercase; letter-spacing: .04em; }
svg { width: 100%; height: 190px; background: #191c21; border: 1px solid #2a2e35;
      border-radius: 6px; display: block; }
.legende { display: flex; gap: 14px; margin-top: 4px; color: #9aa0a8; flex-wrap: wrap; }
.legende i { display: inline-block; width: 10px; height: 10px; border-radius: 2px;
             margin-right: 4px; vertical-align: -1px; }
/* Accueil : grille de tuiles d'état */
.grille { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
          gap: 10px; }
.tuile { display: flex; align-items: center; gap: 10px; padding: 10px 12px;
         background: #1c1f24; border: 1px solid #2a2e35; border-radius: 6px;
         cursor: pointer; }
.tuile:hover { border-color: #2d5a8a; }
.tuile .titre { font-weight: 600; }
.tuile .texte { color: #9aa0a8; font-size: 12px; }
/* Lignes d'une tuile à l'accueil (`detail_accueil` — relais et tunnel, une par
   poste ; chantier relais_multi_poste, 2026-10-08). */
.tuile .sous-ligne { color: #9aa0a8; font-size: 12px; margin-top: 2px; }
/* Onglet Poste : puces de sélection de la machine (multi-poste, 2026-09-29).
   Réutilise `.tuile` — même objet visuel, même cycle de vie ; seul le
   caractère « choisi » est nouveau. */
.postes { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
.postePuce { font: inherit; color: inherit; text-align: left; }
.postePuce.choisi { border-color: #58a6ff; background: #1f2630; }

/* Onglet Relais assistant */
.bandeau-relais { display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
                  padding: 8px 12px; background: #1c1f24; border: 1px solid #2a2e35;
                  border-radius: 6px; margin-bottom: 10px; }

/* Onglet « Dev en cours » (plan avancement_chantiers, 2026-11-11) : une carte par
   poste actif, même objet visuel que les bandeaux ; la barre d'étapes est la seule
   pièce neuve — remplissage posé en style inline par ecran-chantier.js (pct serveur). */
.chantier-carte { padding: 10px 12px; background: #1c1f24; border: 1px solid #2a2e35;
                  border-radius: 6px; margin-bottom: 10px; }
.chantier-corps { display: grid; gap: 6px; margin-top: 6px; }
.chantier-tete { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* Repli (amelio_capi_estimation_tests, 2026-11-14) : la tête est le <summary> du pli.
   En flex, le marqueur natif disparaît — un triangle le remplace. Un chantier clos est
   estompé tant qu'il est replié. */
.chantier-pli > summary { cursor: pointer; list-style: none; }
.chantier-pli > summary::-webkit-details-marker { display: none; }
.chantier-pli > summary::before { content: '▸'; color: #9aa0a8; width: 10px; }
.chantier-pli[open] > summary::before { content: '▾'; }
.chantier-passe:not([open]) { opacity: 0.75; }
.chantier-phase { margin-left: auto; }
.chantier-barre { height: 10px; background: #22262c; border: 1px solid #2a2e35;
                  border-radius: 5px; overflow: hidden; }
.chantier-barre i { display: block; height: 100%; background: #2d5a8a; }
.chantier-muet { color: #d29922; }

/* Onglet Campagnes — les COULOIRS par joueur d'une étape E2E découpée (plan
   monitoring_plusplus, 2026-11-15) : une rangée sous la ligne de la campagne, un couloir
   par joueur sur une échelle commune. Positions et largeurs en % posées en style inline par
   ecran-campagnes.js (calculées par le serveur, couloirs.py). */
tr.couloirs-ligne > td { padding: 4px 12px 10px; background: #191c21; }
.couloirs { display: grid; gap: 3px; font-size: 12px; }
.couloirs-entete { color: #9aa0a8; margin-bottom: 2px; }
.couloir { display: grid; grid-template-columns: 26px 1fr minmax(120px, 22%); gap: 8px;
           align-items: center; }
.couloir-nom { color: #9aa0a8; font-variant-numeric: tabular-nums; }
.couloir-bout { color: #d6d8dc; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.couloir-piste { position: relative; height: 14px; background: #22262c;
                 border: 1px solid #2a2e35; border-radius: 3px; overflow: hidden; }
.bloc { position: absolute; top: 1px; bottom: 1px; min-width: 2px; border-radius: 2px;
        box-sizing: border-box; border-right: 1px solid #191c21; }
a.bloc { cursor: pointer; }
a.bloc:hover, .bloc:hover { filter: brightness(1.35); }
.bloc-vert   { background: #2f8a43; }
.bloc-saute  { background: #4a5059; }
.bloc-rouge, .bloc-erreur { background: #c03a34; }
.bloc-delai  { background: #d29922; }
.bloc-annule { background: #3a3f47; }
.bloc-cours  { background: #1f3550; outline: 1px solid #58a6ff; outline-offset: -1px; }
.bloc-cours i { display: block; height: 100%; background: #2d5a8a; }
.bloc-projete { background: repeating-linear-gradient(135deg, #2a3442 0 3px, #1f242c 3px 6px); }
.couloir-maintenant { position: absolute; top: 0; bottom: 0; width: 2px; margin-left: -1px;
                      background: #e3b341; pointer-events: none; }
.couloirs-axe { display: grid; grid-template-columns: 26px 1fr minmax(120px, 22%); gap: 8px;
                color: #9aa0a8; font-size: 11px; }
.couloirs-axe .graduations { position: relative; height: 14px; }
.couloirs-axe .graduations span { position: absolute; white-space: nowrap; }
.couloirs-axe .graduations .fin { right: 0; }
.couloirs-axe .graduations .maint { transform: translateX(-50%); color: #e3b341; }
.chantier-fil summary { cursor: pointer; color: #9aa0a8; font-size: 12px; }
.chantier-fil table { margin-top: 6px; font-size: 12px; }
/* Le tableau du plan de marche (D15) : un step par ligne — fini discret, en cours
   mis en avant, à faire estompé. */
.chantier-steps { font-size: 12px; }
.chantier-steps .step-etat { width: 18px; text-align: center; }
.step-fini .step-etat { color: #3fb950; }
.step-encours td { background: #1f2630; }
.step-encours .step-etat { color: #58a6ff; }
.step-afaire td { color: #9aa0a8; }
/* Réel ou écoulé au-delà de l'estimé — jugé par le serveur (`depasse`), distinct du
   « muet » (revue A4 : une classe par concept, règle 5). */
.step-depasse { color: #d29922; }
/* Sous-avancement du batch vivant sous un step en cours (lot 2, plan affinage_avancement). */
.step-batch td { padding: 1px 4px; border-top: none; }
.step-batch-detail { color: #9aa0a8; font-size: 11px; }
.step-standby { color: #d29922; font-size: 11px; }
.registre-testid { max-width: 420px; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; font-size: 11px; font-family: 'Cascadia Code', 'Consolas', monospace; }
.registre-delta { color: #e5534b; }
.filtres { display: flex; gap: 8px; align-items: center; margin-bottom: 8px; color: #9aa0a8; }
.filtres select { background: #22262c; color: #d6d8dc; border: 1px solid #2a2e35;
                  border-radius: 4px; padding: 2px 6px; font: inherit; }
tr.echange { cursor: pointer; }
tr.echange:hover td { background: #1c1f24; }
tr.detail td { background: #191c21; white-space: pre-wrap; font-size: 12px;
               padding: 10px 14px; }
tr.detail .q { color: #79c0ff; }
tr.detail .r { color: #d6d8dc; margin-top: 8px; }
tr.detail .e { color: #f85149; margin-top: 8px; }
.statut-ok { color: #3fb950; } .statut-error { color: #f85149; }
.statut-encours { color: #d29922; }
td.tronque { max-width: 520px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Onglet Logs (logstream, plan logstream_monitoring 2026-09-20) : journal en
   fonte mono, pre-wrap pour les tracebacks multi-lignes (une entrée journald),
   couleurs par NIVEAU classé côté serveur (D13) sur la palette existante. */
.logs { height: 70vh; overflow: auto; background: #191c21; border: 1px solid #2a2e35;
        border-radius: 6px; padding: 8px 10px;
        font: 12px/1.45 Consolas, 'Cascadia Mono', monospace;
        white-space: pre-wrap; overflow-wrap: anywhere; }
.logs .h { color: #9aa0a8; }                                     /* horodatage */
/* Classes nommées d'après le NIVEAU classé (revue MIN-4) ; info = défaut. */
.logs .erreur { color: #f85149; }
.logs .warning { color: #d29922; }
.logs .debug { color: #6e7681; }
.logs .saut { color: #d29922; font-style: italic; }              /* trou du flux */
/* Recherche texte (chantier logs_niveaux_recherche, 2026-09-20) : filtre
   d'affichage pur — les lignes non correspondantes sont masquées, jamais
   retirées (le flux et le curseur continuent derrière, D5). « masquee » et
   non « cache » : ce mot-là est pris par la comptabilité de jetons du relais
   (revue 🟡-2, vocabulaire greppable). */
.logs > div.masquee { display: none; }
.filtres input[type="search"] { background: #22262c; color: #d6d8dc;
  border: 1px solid #2a2e35; border-radius: 4px; padding: 2px 6px;
  font: inherit; width: 190px; }
#logsVide { margin: 0 0 4px; font-style: italic; }

/* ── Vigie — onglet Anomalies (plan vigie_logs, 2026-09-20) ────────────────── */
/* table-layout fixed + colgroup (index.html) : la colonne Anomalie absorbe et
   replie — sans lui, un gabarit de 240 caractères étirait la table et le
   rapport sortait de l'écran (retour de phase B n°3, 2026-09-21).
   overflow-wrap héritée : les paragraphes ET les <pre> du rapport replient
   aussi leurs jetons longs (chemins, URLs).
   `.vigie-table` et plus `#vueVigie table` depuis la parité D2 (2026-10-04) :
   la page d'env porte la MÊME table — la classe suit les deux copies du
   colgroup, le sélecteur d'onglet n'en couvrait qu'une. */
table.vigie-table { table-layout: fixed; }
col.vigie-col-pli { width: 26px; }
col.vigie-col-gravite { width: 105px; }
col.vigie-col-date { width: 135px; }
col.vigie-col-envs { width: 110px; }
col.vigie-col-n { width: 55px; }
col.vigie-col-statut { width: 85px; }
col.vigie-col-actions { width: 170px; }
.vigie-gabarit { font-family: ui-monospace, Consolas, monospace; font-size: 12px;
  overflow-wrap: anywhere; }
tr.vigie-ligne { cursor: pointer; }
/* Le td du détail est PREMIER enfant de sa row : sans ce normal, il hérite du
   nowrap « dates » de `tbody td:first-child` (l.33) et le rapport partait en
   une seule ligne de 5 600 px (retour n°3, sondé au computed style). */
tr.vigie-detail td { background: #11151c; white-space: normal; }
.vigie-depli { overflow-wrap: anywhere; }
.vigie-depli pre { background: #0d1117; border: 1px solid #242830; padding: 6px 8px;
  overflow-x: auto; font-size: 11px; white-space: pre-wrap; margin: 4px 0; }
.vigie-depli p { margin: 6px 0; }
/* Badges look ticketing (D10, chantier filtre_access_logs) — répliqués en CSS,
   pas de Bootstrap ici ; couleurs des classes bg-* de ticketing/models.py.
   Portée ÉLARGIE à #vueEnv le 2026-10-03 : la page d'un environnement rend les
   mêmes badges, par les mêmes helpers (`badgeGravite`, `badgeStatut`). Les
   sélecteurs étaient préfixés #vueVigie, donc les badges y seraient sortis nus
   — texte blanc sur fond sombre, sans pastille — sans qu'aucun test ne bronche. */
#vueVigie .badge, #vueEnv .badge { display: inline-block; padding: 2px 8px; border-radius: 10px;
  font-size: 11px; font-weight: 600; color: #fff; white-space: nowrap; }
#vueVigie .grav-blo, #vueEnv .grav-blo { background: #dc3545; }               /* Bloquant — bg-danger */
#vueVigie .grav-maj, #vueEnv .grav-maj { background: #ffc107; color: #212529; } /* Majeur — bg-warning */
#vueVigie .grav-min, #vueEnv .grav-min { background: #198754; }               /* Mineur — bg-success (VERT) */
#vueVigie .st-nouveau, #vueEnv .st-nouveau { background: #0d6efd; }           /* primary */
#vueVigie .st-analyse, #vueEnv .st-analyse { background: #0dcaf0; color: #212529; } /* info */
#vueVigie .st-acquitte, #vueEnv .st-acquitte { background: #6c757d; }         /* secondary */
#vueVigie .st-ignore, #vueEnv .st-ignore { background: #212529; }             /* dark */
#vueVigie .agent-vide, #vueEnv .agent-vide { color: #6e7681; }               /* gravité agent absente : « — » */
/* Logo Polaris rotatif = analyse en cours (D9) — même animation que l'indicateur
   de tâche de fond de l'app (design/js/tache-fond.js, @keyframes polaris-spin). */
@keyframes polaris-spin { to { transform: rotate(360deg); } }
.vigie-spin { width: 14px; height: 14px; vertical-align: middle; margin-right: 3px;
  animation: polaris-spin 1.1s linear infinite; }
/* Actions de l'onglet Anomalies : trois boutons outline-sm empilés et centrés
   (demande humaine 2026-09-21), largeur uniforme. Deux états seulement — bleu
   quand actif (`vg-on`), gris sinon (`vg-off`) — pas de couleur par rôle : le
   bouton d'analyse morphe (Analyser → « En attente » sablier → « Analyse » logo),
   Acquitter/Ignorer passent au gris quand le statut EST déjà le leur (clic =
   rouvrir). Pas de Bootstrap ici : outline répliqué à la main. */
.vigie-actions { display: flex; flex-direction: column; gap: 5px; align-items: center; }
.vg-btn { background: transparent; border: 1px solid currentColor; border-radius: 4px;
  padding: 3px 9px; min-width: 82px; text-align: center; font: inherit; font-size: 11px;
  line-height: 1.6; cursor: pointer; transition: background .12s, color .12s, border-color .12s; }
.vg-on  { color: #58a6ff; }                                     /* actif — bleu */
.vg-on:hover { background: #1f6feb; color: #fff; border-color: #1f6feb; }
.vg-off { color: #8b929c; border-color: #3a3f47; }              /* inactif — gris */
.vg-off:not(:disabled):hover { background: #3a3f47; color: #d6d8dc; } /* gris cliquable = rouvrir */
.vg-btn:disabled { cursor: default; }                           /* En attente / Analyse : figés */
/* Sablier animé = sujet en file (se retourne lentement, façon sablier réel).
   Unicode ⏳ : pas de Font Awesome ici, l'emoji porte le glyphe, la rotation
   l'anime — pendant que le logo Polaris (.vigie-spin ci-dessus) sert l'analyse. */
@keyframes vg-sablier { 0%, 42% { transform: rotate(0); } 58%, 100% { transform: rotate(180deg); } }
.vg-sablier { display: inline-block; animation: vg-sablier 1.8s ease-in-out infinite; }

footer { padding: 8px 16px 14px; font-size: 11px; }

/* ── Gyrophare : le signal d'ANOMALIE (chantier page_env_monitoring, 2026-10-03)
   Demande explicite de l'humain : il faut distinguer le voyant d'alerte du
   voyant d'état « au premier coup d'œil », et une simple différence de COULEUR
   n'y suffit pas — les deux puisent dans le même nuancier rouge/orange. D'où
   une FORME différente, et vue DE CÔTÉ : socle, dôme, lampe qui bat, reflet qui
   balaie. (Le premier jet montrait un faisceau tournant à plat, c'est-à-dire un
   gyrophare vu de dessus — cela se lit comme un radar.)

   Le dessin vient d'un SVG fourni par l'humain et a été RÉARRANGÉ pour cet
   écran : ses `defs` sont remontées une fois dans `index.html` (sinon autant
   d'`id` identiques que de gyrophares) et sa feuille de style est ici (sinon
   autant de copies des mêmes `@keyframes`). Chaque instance ne porte plus que
   sa géométrie — cf. `gyrophare()` dans ecran.js.

   La couleur passe par `currentColor` : une seule géométrie, deux couleurs,
   et ce sont celles des pastilles (#f85149 / #d29922) et non le rouge
   d'origine — un second rouge dans le même écran se lirait comme un troisième
   état.

   ⚠ MÉMOIRE D'INCIDENT (2026-10-03) : ce bloc s'est retrouvé FERMÉ trop tôt
   par une fin de commentaire oubliée d'une version précédente (les deux
   caractères étoile-barre, qu'on ne peut même pas citer ici — ils fermeraient
   ce commentaire-ci, et c'est EXACTEMENT ainsi que la première tentative de
   consigner l'incident l'a reproduit), et les douze lignes suivantes sont
   devenues du texte nu. Le parseur CSS les a lues comme une règle
   malformée et a AVALÉ le `@keyframes gyro-flash` qui suivait, en consommant
   son bloc comme s'il lui appartenait. Effet : le reflet balayait toujours
   (`gyro-sweep`, plus bas, survivait) mais la lampe ne clignotait plus — et
   aucune erreur nulle part, ni console, ni test. C'est l'humain qui l'a vu à
   l'écran. Un commentaire CSS mal fermé ne casse pas la règle d'après : il
   casse la SUIVANTE, en silence. */
@keyframes gyro-flash { 0%, 100% { opacity: 0; } 12% { opacity: 1; } 32% { opacity: .85; } 50% { opacity: 0; } }
@keyframes gyro-sweep { 0% { transform: translateX(-26px); } 50%, 100% { transform: translateX(42px); } }
/* Conteneur des définitions : hors flux et invisible, mais JAMAIS
   `display:none` — un `<defs>` dans un arbre non rendu ne résout plus les
   `url(#…)` qui le visent, et le gyrophare perdrait son découpe et son flou. */
.gyro-defs { position: absolute; width: 0; height: 0; overflow: hidden; }
/* 26 px sur une tuile. Un essai à 36 px (et 48 px sur la page, la taille du
   dessin d'origine) a été fait puis ÉCARTÉ par l'humain le 2026-10-03 : plus
   gros, le gyrophare pèse trop dans une tuile qui doit d'abord se lire. Le
   reflet qui balaie le dôme y est plus discret, et c'est accepté. */
.gyrophare { display: block; width: 26px; height: 26px; flex: none;
  /* Fond et filet DÉCLARÉS, pas hérités. La règle `svg` générique de cette
     feuille (plus haut : fond, bordure, coins arrondis) habille les GRAPHES et
     s'applique à tous les SVG : le gyrophare recevait donc sa petite plaque
     sombre par accident. Elle lui va bien — il a l'air posé sur un support —
     alors on la GARDE, mais en la disant ici. Sinon, le jour où quelqu'un
     retouche le style des graphes, le gyrophare change sans que personne ne
     comprenne pourquoi. */
  background: #191c21; border: 1px solid #2a2e35; border-radius: 6px; }
.gyrophare.rouge  { color: #f85149; }
.gyrophare.orange { color: #d29922; }
.gyrophare .g-pulse { animation: gyro-flash var(--gyro-periode, 1.2s) ease-in-out infinite; }
.gyrophare .g-sweep { animation: gyro-sweep var(--gyro-periode, 1.2s) ease-in-out infinite; }
/* Un gyrophare qui clignote est exactement ce qu'un lecteur sensible au
   mouvement demande de ne PAS subir : il reste allumé, fixe. L'information
   passe par la forme et la couleur, pas par le mouvement seul. */
@media (prefers-reduced-motion: reduce) {
  .gyrophare .g-pulse, .gyrophare .g-sweep { animation: none; }
  .gyrophare .g-pulse { opacity: .6; }
}
/* Sur la page d'un env, il est plus gros — il y a la place, et c'est l'objet
   que l'on vient regarder. Le conteneur est en `flex` et NON en ligne : un
   `<span>` en ligne ne grandit pas avec un enfant en `display:block`, et le
   gyrophare s'y trouvait écrasé à la hauteur d'une ligne de texte (vu à la
   capture, invisible en test).
   `#envVoyantAlertes` depuis le renommage du chantier assistance_humaine —
   ces deux règles étaient restées sur l'ancien id et MORTES (le gyrophare
   était retombé à 26 px : la régression mémorisée ci-dessus, revenue par la
   porte du renommage — trouvée par l'audit d'assertions du 2026-10-05, et
   l'E2E tient désormais la taille). */
#envVoyantAlertes { display: flex; align-items: center; }
#envVoyantAlertes .gyrophare { width: 34px; height: 34px; }
/* Sur une tuile d'accueil, il se pose en haut à droite, hors du flux : la
   tuile garde sa mise en page à deux colonnes (pastille d'état | textes). */
.tuile { position: relative; }
.tuile .gyrophare { position: absolute; top: 6px; right: 8px; }

/* ── Page d'un environnement (chantier page_env_monitoring, 2026-10-03) ───── */
.env-tete { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 6px; }
.env-tete h1 { font-size: 16px; text-transform: uppercase; letter-spacing: .06em; }
.env-tete button { background: #22262c; color: #9aa0a8; border: 1px solid #2a2e35;
                   border-radius: 4px; padding: 2px 10px; cursor: pointer; font: inherit; }
.env-tete button:hover { background: #2a2e35; color: #d6d8dc; }
/* Le voyant d'alerte : plus gros que les pastilles de tableau, et ANIMÉ en
   rouge/orange seulement (D8). Le serveur dit s'il doit s'animer
   (`voyant.anime`) — l'écran n'en rejuge pas, exactement comme pour les
   verdicts (AC-46). Un voyant vert qui clignote est un voyant qu'on apprend à
   ignorer : seules les mauvaises nouvelles bougent. */
/* Seul le voyant d'ÉTAT a une taille imposée : c'est une pastille. Le voyant
   d'anomalies est un conteneur qui se règle sur son gyrophare — l'y inclure
   (ce que faisait la version précédente de cette ligne) le bornait à 20 px et
   rognait le SVG, sans que rien ne le signale. */
#envVoyant { width: 20px; height: 20px; }
/* Le bloc d'anomalies est poussé à droite ; le bouton de retour le suit. */
.env-anomalies { margin-left: auto; display: flex; align-items: center; gap: 8px; }
/* Les faits de l'env, en liste de définitions : deux colonnes qui s'alignent
   toutes seules, et un libellé qui reste collé à sa valeur si la fenêtre est
   étroite. */
.env-faits { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
             gap: 4px 18px; margin: 10px 0 0; }
.env-faits div { border-bottom: 1px solid #2a2e35; padding: 4px 0; }
.env-faits dt { color: #9aa0a8; font-size: 11px; text-transform: uppercase;
                letter-spacing: .04em; font-weight: 600; }
.env-faits dd { margin: 2px 0 0; }
/* Le drapeau du pays configuré.
   TAILLE IMPOSÉE EN CSS, et ce n'est pas facultatif : la règle `svg` générique
   de cette feuille (plus haut : `width:100%; height:190px`) habille les GRAPHES
   et s'applique à **tous** les SVG de la page. Sans ces trois lignes, le
   drapeau remplissait sa cellule sur 190 px de haut, fond et coins arrondis
   compris — vu à la capture, et aucun test ne l'aurait dit.
   Le filet : sur fond sombre, la bande blanche du drapeau français n'a sinon
   pas de bord et le drapeau semble flotter. */
.drapeau { display: block; width: 24px; height: 16px; background: none;
           border: 1px solid #2a2e35; border-radius: 2px; }
.env-ligne { cursor: pointer; }
.env-ligne:hover td { background: #1c1f24; }
/* Les connectés ressortent du reste de la liste sans être criards : la
   pastille verte dit l'état, cette teinte dit le groupe. */
#corpsEnvUtilisateurs .usr-connecte td { background: #171c18; }
/* Dépliage des conversations : même langage que l'onglet Relais et la vigie —
   une ligne de détail qui s'insère sous la ligne cliquée. */
.usr-depli { background: #22262c; color: #9aa0a8; border: 1px solid #2a2e35;
             border-radius: 4px; padding: 1px 8px; cursor: pointer; font: inherit;
             white-space: nowrap; }
.usr-depli:hover { background: #2a2e35; color: #d6d8dc; }
/* `white-space: normal` REMIS explicitement : la ligne de détail reprend la
   classe `tr.detail`, qui porte `white-space: pre-wrap` pour le dépliage de
   l'onglet Relais (ligne 75). Hérité ici, il transformait les retours à la
   ligne et l'indentation du GABARIT JS en vrais sauts de ligne — 147 px par
   échange pour 39 px de contenu, mesuré au navigateur. Le `pre-wrap` est
   reposé plus bas sur `.conv-r`, le seul endroit qui en a besoin (une réponse
   d'assistant contient de vrais retours à la ligne). */
.conv { max-height: 55vh; overflow-y: auto; padding: 4px 0; white-space: normal; }
/* Repliée, une session n'est qu'une ligne : son filet gauche ne s'allume que
   lorsqu'elle est ouverte, pour que la liste se lise comme une liste. */
.conv-session { border-left: 2px solid transparent; padding-left: 10px; margin: 0 0 2px; }
.conv-session.ouverte { border-left-color: #2d5a8a; margin-bottom: 12px; }
/* L'en-tête est un BOUTON, mais il doit se lire comme un titre : pas de cadre,
   pas de fond — l'affordance vient du chevron et du survol. */
.conv-depli { display: block; width: 100%; text-align: left; cursor: pointer;
              background: none; border: 0; padding: 3px 0; font: inherit;
              color: #9aa0a8; font-size: 11px; text-transform: uppercase;
              letter-spacing: .04em; font-weight: 600; }
.conv-depli:hover { color: #d6d8dc; }
.conv-session.ouverte .conv-depli { color: #d6d8dc; margin-bottom: 4px; }
.conv-echange { margin-bottom: 8px; }
/* La question est la voix de l'utilisateur, la réponse celle de l'assistant :
   deux teintes, pour qu'on suive le fil sans lire les étiquettes. */
.conv-q { color: #d6d8dc; }
.conv-r { color: #9aa0a8; white-space: pre-wrap; overflow-wrap: anywhere;
          border-left: 2px solid #2a2e35; padding-left: 8px; margin-top: 3px; }

/* Compte connecté, poussé à droite du header (D1). `margin-left: auto` plutôt
   qu'un `justify-content: space-between` sur <header> : celui-ci est en
   `flex-wrap`, et l'espacement réparti aurait écarté la pastille du verdict. */
.compte { margin-left: auto; display: flex; align-items: center; gap: 8px; }
.compte button { background: #22262c; color: #9aa0a8; border: 1px solid #2a2e35;
                 border-radius: 4px; padding: 2px 10px; cursor: pointer; font: inherit; }
.compte button:hover { background: #2a2e35; color: #d6d8dc; }

/* ── Page de connexion (chantier page_env_monitoring, 2026-10-03, D1) ────────
   Même feuille que l'écran, pour que les deux pages se ressemblent et qu'il n'y
   ait qu'un seul endroit où vit le thème sombre. `connexion.html` est la seule
   page à porter `body.page-connexion`. */
.page-connexion { display: flex; min-height: 100vh; align-items: center; justify-content: center; }
.connexion { width: 280px; text-align: center; padding: 28px 24px 24px;
             background: #1c1f24; border: 1px solid #2a2e35; border-radius: 8px; }
.connexion h1 { font-size: 15px; margin: 10px 0 20px; }
.connexion form { display: flex; flex-direction: column; gap: 4px; text-align: left; }
.connexion label { color: #9aa0a8; font-size: 11px; text-transform: uppercase;
                   letter-spacing: .04em; font-weight: 600; }
.connexion input { background: #14161a; color: #d6d8dc; border: 1px solid #2a2e35;
                   border-radius: 4px; padding: 7px 9px; font: inherit; margin-bottom: 10px; }
.connexion input:focus { outline: none; border-color: #2d5a8a; }
.connexion button { background: #2d5a8a; color: #fff; border: 1px solid #2d5a8a;
                    border-radius: 4px; padding: 7px 10px; font: inherit; cursor: pointer; }
.connexion button:disabled { background: #22262c; border-color: #2a2e35; color: #9aa0a8;
                             cursor: default; }
/* Message d'échec : rouge de la pastille, pour que l'œil le trouve au même
   endroit du nuancier que les autres mauvaises nouvelles de l'outil. */
.connexion .erreur { color: #f85149; margin: 12px 0 0; font-size: 12px; }

/* ── Assistance humaine (chantier assistance_humaine, 2026-10-04) ────────────
   L'avatar du compte (header) et la conversation relayée de la page d'env.
   Les visages sont les SVG inline d'AVATARS_ASSISTANCE (ecran.js) — tracés
   dupliqués depuis l'application, cf. le commentaire là-bas. */
/* ⚠ La règle générique `svg { width:100%; height:190px; … }` (les GRAPHES,
   l.45) attrape tout SVG de la page et rendait les visages GÉANTS (retour de
   phase B, 2026-10-04) : les attributs width/height d'un SVG ne pèsent rien
   face à une propriété CSS. On reprend la main par sélecteur plus spécifique —
   taille, fond et bordure des graphes annulés. */
svg.avatar-op { width: 20px; height: 20px; background: none; border: none;
                border-radius: 50%; }
#compteAvatar svg.avatar-op { width: 22px; height: 22px; }
#compteAvatar { padding: 2px 4px; line-height: 0; }
#compteAvatar .avatar-op { display: block; }
/* Pas encore de visage choisi : grisé — le clic choisit. */
#compteAvatar.sans-choix .avatar-op { filter: grayscale(1) brightness(0.8); }

/* Une conversation se déplie SOUS LA LIGNE de sa demande, plusieurs à la fois
   (retour de Willyam en phase B du chantier traduction_chat_monitoring,
   2026-10-09 — elle vivait seule dans un cadre en bas de l'encart, `#envConv`).
   Sa ligne court sur les cinq colonnes ; le `normal` défait le nowrap « dates »
   de `tbody td:first-child` (l.46), dont sa cellule unique est la première. La
   ligne de la demande dépliée s'y soude : même fond, plus de filet entre elles.
   La zone reprend le nuancier de l'onglet Relais. */
tr.as-conv > td { white-space: normal; padding: 0 12px 12px; background: #191c21; }
tr.as-ligne.ouverte > td { background: #191c21; border-bottom-color: transparent; }
/* `contain: inline-size` : la conversation ne pèse rien dans la largeur des
   colonnes — sans lui, un long message élargirait les colonnes de la table à
   l'ouverture. Elle prend la largeur de sa ligne, et ses bulles s'y replient. */
.conv-zone { contain: inline-size; background: #1c1f24; border: 1px solid #2a2e35;
             border-radius: 8px; padding: 10px; }
.conv-zone .conv-tete { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.conv-zone .conv-tete button { background: #22262c; color: #9aa0a8;
                               border: 1px solid #2a2e35; border-radius: 4px;
                               padding: 2px 10px; cursor: pointer; font: inherit; }
.conv-zone .conv-tete button:hover { background: #2a2e35; color: #d6d8dc; }
.conv-zone .conv-tete .conv-raccrocher { margin-left: auto; color: #f85149;
                                         border-color: #5a2a2a; }
/* Sans « Raccrocher » (épisode relu, pas pris), c'est « Fermer » qui part à
   droite ; avec lui, « Fermer » se colle à sa suite. */
.conv-zone .conv-tete .conv-raccrocher[hidden] + .conv-fermer { margin-left: auto; }
.conv-messages { max-height: 320px; overflow-y: auto; display: flex;
                 flex-direction: column; gap: 6px; padding: 4px 2px; }
/* L'utilisateur assisté à gauche (bleu), l'opérateur à droite (vert) — même
   logique gauche/droite que le chat de l'application, vue de l'autre côté. Les
   cellules de TRADUCTION (`.conv-trad`, colonne de lecture du mode scindé) ont le
   MÊME rendu que leur bulle — couleurs, mise en forme, tête —, par ces mêmes
   règles et non par une copie (retour de Willyam en phase B du chantier
   traduction_chat_monitoring, 2026-10-09). Seul le placement leur est propre. */
.conv-msg, .conv-trad { max-width: 85%; padding: 6px 9px; border-radius: 6px;
                        line-height: 1.4; white-space: pre-wrap; overflow-wrap: anywhere; }
.conv-msg.uti { align-self: flex-start; }
.conv-msg.uti, .conv-trad.uti { background: #22304a; color: #d6e2f5; }
.conv-msg.ope { align-self: flex-end; }
.conv-msg.ope, .conv-trad.ope { background: #1f3a2a; color: #d9f0de;
                                display: flex; gap: 7px; align-items: flex-start; }
.conv-msg.ope .avatar-op,
.conv-trad.ope .avatar-op { border-radius: 50%; flex-shrink: 0; margin-top: 1px; }
/* Le robot — l'assistant automatique, dans les échanges PASSÉS de la session
   (retour de phase B du chantier traduction_chat_monitoring, 2026-10-09) : le
   format du direct, à droite comme l'opérateur, d'une teinte à part — un robot
   ne doit jamais passer pour un humain. */
.conv-msg.rob { align-self: flex-end; }
.conv-msg.rob, .conv-trad.rob { background: #262d38; color: #c9d1d9;
                                display: flex; gap: 7px; align-items: flex-start; }
.conv-msg.rob .robot, .conv-trad.rob .robot { flex-shrink: 0; }
/* En-têtes du contexte : « Avant la demande — … », puis « Assistance humaine ». */
.conv-separateur { align-self: stretch; text-align: center; font-size: 11px;
                   color: #6e7681; border-bottom: 1px solid #2a2e35; padding: 2px 0; }
/* Les bulles SYSTÈME : le verdict épinglé d'un échec (afficherEchecAssistance).
   Elles portaient aussi le contexte, en bloc grisé, jusqu'au 2026-10-09 — il est
   désormais en bulles (`.conv-msg.rob` ci-dessus). */
.conv-contexte { border-left: 2px solid #2a2e35; padding-left: 8px; font-size: 12px; }
/* Le gyrophare est devenu un BOUTON de navigation (2026-10-07) quand il porte
   une cible : le curseur doit le dire. */
.gyrophare[data-section]:not([data-section=""]) { cursor: pointer; }

/* Dépli de l'échange fautif sous une ligne de pouce bas (2026-10-07) : la
   question grisée (décor), la réponse en clair, le motif du retour en ambre. */
.pouce-echange td { background: #14161a; }
.pouce-echange .q { color: #9aa0a8; margin-bottom: 4px; }
.pouce-echange .r { white-space: pre-wrap; overflow-wrap: anywhere; color: #d6d8dc; }
.pouce-echange .fb { margin-top: 6px; color: #e0a742; }

.conv-saisie { display: flex; gap: 6px; margin-top: 8px; }
/* Le `display: flex` ci-dessus ÉCRASE le style UA de l'attribut [hidden]
   (mesuré 2026-10-07, chantier retours_monitoring_assistant) : la saisie
   restait visible sur une demande non actionnable malgré le `hidden` posé par
   convMajEnTete. Règle explicite — même piège pour tout futur bloc en flex. */
.conv-saisie[hidden] { display: none; }
.conv-saisie textarea { flex: 1; background: #14161a; color: #d6d8dc;
                        border: 1px solid #2a2e35; border-radius: 4px;
                        padding: 6px 8px; font: inherit; resize: vertical; }
.conv-saisie textarea:focus { outline: none; border-color: #2d5a8a; }
.conv-saisie button { background: #2d5a8a; color: #fff; border: 1px solid #2d5a8a;
                      border-radius: 4px; padding: 6px 14px; font: inherit;
                      cursor: pointer; align-self: flex-end; }

/* Traduction FR↔EN (chantier traduction_chat_monitoring, 2026-10-09) — le mode
   SCINDÉ : à GAUCHE la colonne de lecture en français (une vue), à DROITE
   l'original (ce qui s'échange et s'historise) — côtés inversés le jour même, C3
   révisée par Willyam en phase B. Chaque cellule de traduction PRÉCÈDE sa bulle
   dans le DOM : le placement automatique de la grille les pose sur la même ligne
   sans `grid-auto-flow: dense` (qui comblerait le premier trou venu, pas le
   bon) ; les en-têtes et les bulles système courent sur les deux colonnes. */
.conv-colonne { display: flex; gap: 6px; flex: 1; min-width: 0; }
/* Même piège que `.conv-saisie[hidden]` : `display: flex` écrase [hidden]. */
.conv-colonne[hidden] { display: none; }
.conv-saisie.scinde { gap: 10px; }
/* Un bouton de saisie est BLEU quand son champ porte un texte — il y a quelque chose
   à traduire ou à envoyer —, GRIS sinon (retour de Willyam en phase B, 2026-10-09 ;
   la classe `vide` est posée par `majBoutonSaisie`, ecran-env.js). L'ESTOMPAGE reste
   réservé à une traduction en vol (`:disabled`, `tradOccuper`) : gris = rien à faire,
   estompé = en cours. */
.conv-saisie button.vide { background: #22262c; color: #6e7681; border-color: #2a2e35;
                           cursor: default; }
.conv-saisie button:disabled { opacity: 0.5; cursor: progress; }
.conv-messages.scinde { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
                        column-gap: 10px; row-gap: 6px; align-items: start; }
.conv-messages.scinde > .conv-contexte,
.conv-messages.scinde > .conv-separateur { grid-column: 1 / -1; }
.conv-messages.scinde > .conv-msg { grid-column: 2; align-self: start; }
.conv-messages.scinde > .conv-msg.uti { justify-self: start; }
.conv-messages.scinde > .conv-msg.ope,
.conv-messages.scinde > .conv-msg.rob { justify-self: end; }
/* La cellule : le rendu de sa bulle (règles partagées, plus haut), en colonne 1,
   alignée comme sa bulle dans la sienne. Ses deux états se lisent sans changer de
   teinte : estompée pendant la traduction, liseré et texte ambre en échec. */
.conv-trad { grid-column: 1; align-self: start; }
.conv-trad.uti { justify-self: start; }
.conv-trad.ope, .conv-trad.rob { justify-self: end; }
.conv-trad.en-cours { opacity: 0.55; }
.conv-trad.echec { box-shadow: inset 0 0 0 1px #e0a742; color: #e0a742; }
.conv-zone .conv-tete .conv-bascule-traduction.actif { background: #1d2a3a; color: #d6d8dc;
                                                       border-color: #2d5a8a; }

/* Messages NON VUS (retour de Willyam en phase B, 2026-10-09 — « Messages non
   vus », ecran-env.js) : l'ambre de « ceci demande ton attention », sur quatre
   supports — le compteur de la ligne de la demande, le liseré de sa
   conversation, un point après chaque message neuf, la pastille flottante. */
.as-nouveaux { display: inline-block; margin-left: 6px; padding: 0 7px;
               border-radius: 9px; font-size: 11px; line-height: 18px;
               background: #3a2e14; color: #f0c674; border: 1px solid #6b5420; }
tr.as-ligne.nouveaux > td:first-child { box-shadow: inset 3px 0 0 #e0a742; }
.conv-zone.nouveaux { border-color: #6b5420; box-shadow: inset 3px 0 0 #e0a742; }
.conv-msg.neuf::after { content: ' ●'; color: #e0a742; }
#envNouveaux { position: fixed; left: 50%; bottom: 16px; transform: translateX(-50%);
               z-index: 50; background: #3a2e14; color: #f0c674;
               border: 1px solid #e0a742; border-radius: 16px; padding: 6px 14px;
               font: inherit; cursor: pointer; box-shadow: 0 4px 14px rgba(0, 0, 0, 0.45); }
#envNouveaux:hover { background: #4a3a18; }
/* Le premier non-vu est AU-DESSUS : la pastille passe en haut, sa flèche aussi. */
#envNouveaux.haut { top: 16px; bottom: auto; }

/* Onglet « Code » — indicateurs de code (chantier indicateurs_code_monitoring, 2026-10-13,
   ecran-code.js). Les graphiques reprennent le `svg` de l'écran (pleine largeur, 190 px) :
   empilés, aux marges fixes de `tracer`, leurs dates s'alignent. Les chiffres clés
   réutilisent `.tuile`, sans le curseur de lien — elles n'ouvrent rien. */
.code-sous-onglets { margin: 8px 0 4px; }
.code-methode { background: #1c1f24; border: 1px solid #2a2e35; border-radius: 6px;
                padding: 8px 12px; margin: 8px 0 10px; }
.code-methode h3, .code-carte h3 { font-size: 13px; font-weight: 600; margin: 0 0 4px; }
.code-methode p { margin: 4px 0; color: #9aa0a8; }
.code-chiffres { margin: 8px 0 14px; }
.code-chiffres .tuile { cursor: default; flex-direction: column; align-items: flex-start; gap: 2px; }
.code-chiffres .tuile:hover { border-color: #2a2e35; }
.code-chiffres .valeur { font-size: 18px; font-weight: 600; color: #d6d8dc; }
h2.code-section { margin-top: 18px; }
.code-carte { margin: 8px 0 18px; }
.code-carte .lecture { margin: 0 0 6px; }
.code-carte details { margin-top: 6px; color: #9aa0a8; }
.code-carte details summary { cursor: pointer; }
.code-barre { display: flex; gap: 2px; height: 10px; min-width: 160px; background: #191c21;
              border-radius: 3px; overflow: hidden; }
.code-barre span { display: block; height: 100%; }
td.code-barre-cellule { width: 30%; }
/* Bascule nombre / pourcentage (chantier kpi_pourcentage, 2026-10-23) : cartes basculables =
   tables à barres ET courbes dont la vue sert la variante `pct`. Les deux formes des textes
   sont dans le DOM, la classe `pct` de la carte montre l'une et cache l'autre (les courbes,
   elles, se retracent). Les boutons héritent du style `.filtres button` ; le flex de
   `.filtres` cohabite avec le float (c'est la boîte qui flotte, ses enfants qui flexent). */
.code-bascule { float: right; margin: 0 0 0 8px; }
.code-bascule button { padding: 0 8px; font-size: 12px; }
.code-basculable .code-mode-pct { display: none; }
.code-basculable.pct .code-mode-pct { display: inline; }
.code-basculable.pct .code-mode-n { display: none; }
td.code-chemin { font-family: ui-monospace, Consolas, monospace; font-size: 12px; word-break: break-all; }
.code-vide { padding: 12px 0; }
.code-pied { margin-top: 12px; font-size: 12px; }
/* Tableau des chantiers d'amélioration (plan composite_qualite_amelios, 2026-11-07) :
   en-têtes chiffrés cliquables (tri), lignes cliquables (dépli de la fiche), fiches faites ou
   obsolètes estompées. Le détail déplié rend le markdown de la fiche en TEXTE (pre-wrap). */
.code-amelios-rafraichir { margin: 0 0 8px; display: flex; gap: 10px; align-items: center; float: none; }
.code-amelios-rafraichir button[disabled] { opacity: .5; cursor: default; }
.code-amelios th.code-amelios-tri { cursor: pointer; user-select: none; white-space: nowrap; }
.code-amelios tr.code-amelios-ligne { cursor: pointer; }
.code-amelios tr.code-amelios-eteinte > td { opacity: .55; }
.code-amelios tr.code-amelios-detail > td { background: #1c1f24; }
.code-amelios-detail h4 { font-size: 12.5px; font-weight: 600; margin: 8px 0 2px; }
.code-amelios-texte { white-space: pre-wrap; color: #9aa0a8; font-size: 12.5px; }
/* Numéro de fiche (AM-07) et ligne de commande à copier (plan amelios_numeros_durees). */
.code-amelios td.code-amelios-numero { white-space: nowrap; font-family: ui-monospace, Consolas, monospace; }
.code-amelios-commande { margin: 8px 0 4px; display: flex; gap: 8px; align-items: center; }
.code-amelios-commande code { font-family: ui-monospace, Consolas, monospace; background: #0d1117;
  padding: 2px 6px; border-radius: 4px; user-select: all; }
