/* ==========================================================================
   Socle de style.

   Les 22 variables ci-dessous sont reprises telles quelles de RégiVision v7.8 :
   elles portent deux ans d'habitudes de lecture, il n'y a aucune raison de les
   redécider. Ce qui change, c'est qu'on s'y tient — la v7.8 redéfinissait la
   même tuile de statistique à trois endroits.

   Police : pile système pour l'instant. La v7.8 charge « Plus Jakarta Sans »
   depuis Google Fonts ; on ne rouvre pas une dépendance à un tiers pour une
   démonstration qui doit tenir sans réseau. Embarquer la police dans le dépôt
   quand on voudra le rendu exact.
   ========================================================================== */

:root {
  /* ⚠️ LA LARGEUR DU VOLET EST UNE VARIABLE DEPUIS LE 08/09, et c'est ce qui
     rend la coque réglable. Elle était écrite 460px en dur à trois endroits :
     le volet, l'encart des pages, et la marge des commandes de Leaflet. Trois
     copies d'un même nombre, qu'il fallait tenir d'accord à la main.

     app.js l'écrit sur l'élément racine quand on tire la poignée, et la relit
     au démarrage. Les bornes, 320 et 640, vivent avec elle dans app.js : sous
     320 la liste des sites ne se lit plus, au-delà de 640 la scène n'a plus de
     place sur un portable. */
  --largeur-volet: 460px;
  /* La gouttière entre le volet et la scène, comptée deux fois dans les
     décalages : une fois à gauche du volet, une fois à sa droite. */
  --gouttiere-volet: 1.8rem;

  /* ⚠️ LA LARGEUR DU RAIL DES DOMAINES, ET ELLE EST MESURÉE. Deux colonnes de
     carrés, parce qu'elles sont PLUS COURTES qu'une seule : quatre rangées au
     lieu de sept. Mesuré le 15/09 sur les sept domaines de Villeneuve :

         1 colonne, carré 70 px + libellé ....... 894 px
         1 colonne, carré 44 px + libellé ....... 712 px
         1 colonne, carré 56 px SANS libellé .... 627 px
         2 colonnes, carré 67 px + libellé ...... 547 px

     La scène offre 708 px sur une fenêtre de 826, et 650 px sur un portable de
     1366 x 768 : la colonne unique AVEC libellé ne tient nulle part.

     ⚠️ ET LE PLAFOND EST LE NOMBRE DE DOMAINES, QUI EST UNE DONNÉE DU CLIENT.
     Une rangée vaut environ 111 px : sept domaines font quatre rangées, dix en
     font cinq (669 px, ça passe), douze en font six (780 px, ça ne passe
     plus). Le rail défile alors plutôt que de déborder. */
  /* ⚠️ 9,25 REM DEPUIS LE 15/09 AU SOIR, et non plus 11. Victor a choisi la
     forme « f » de la maquette : carres de domaine a 64 px et rail plus
     etroit, ce qui rend 28 px a la carte. Mesure sur le rendu : le rail passe
     de 736 a 630 px de haut avec ses deux interrupteurs, et tient donc sur un
     portable de 1366 x 768, ce qu'aucune des six formes essayees ne faisait.
     En rem et non en pixels : qui grossit sa police garde la proportion. */
  --largeur-rail: 9.25rem;
  /* La teinte de repli, pour une valeur dont le client n'a déclaré aucune
     couleur : Bourg-la-Vallée est exactement dans ce cas, exprès. */
  --teinte-domaine: var(--text-secondary);

  /* ⚠️ LA HAUTEUR RÉELLE DE LA PILULE, ÉCRITE PAR app.js, ET C'EST UNE MESURE.
     Le dégagement de la scène valait « 4rem », un nombre choisi une fois pour
     un nom de collectivité court et une police de 16 px. Mesuré le 15/09 dans
     une iframe, la marge qui restait sous la pilule :

        police de base 16 px .............. 4,8 px
        police de base 14 px .............. 2,0 px
        police de base 13 px ............. −0,4 px   ← l'en-tête passe dessous
        police de base 12 px ............. −2,8 px
        16 px, nom de collectivité long ... 0,0 px

     Deux déclencheurs indépendants, et aucun n'est exotique. La pilule porte
     des images en PIXELS FIXES (blason 22 px, symbole 24 px) dans un
     dégagement écrit en REM : les deux ne bougent pas ensemble. Et
     « Villeneuve-en-Plaine » est un nom court : le prochain client passe sous
     la barre sans que personne ne touche au code.

     La valeur ci-dessous n'est qu'un point de départ, celui relevé à 16 px.
     app.js la remplace par la hauteur mesurée dès que la pilule est dessinée,
     et à chaque fois qu'elle change. */
  --hauteur-pilule: 45px;
  /* Le haut de la pilule (0.9rem) plus sa hauteur, plus le même espace en
     dessous : la scène commence là, quelle que soit la pilule. */
  --dessous-pilule: calc(0.9rem + var(--hauteur-pilule) + 0.9rem);

  /* La hauteur MINIMALE de la bannière du mode support, pour qu'une phrase
     courte ne donne pas un filet écrasé. Ce n'est qu'un plancher : la bannière
     grandit librement au-delà, et la coque prend ce qu'elle laisse, sans que
     personne ne fasse la soustraction. Voir le commentaire de `body`. */
  --h-banniere-support: 1.8rem;

  --bg-primary: #ffffff;
  --bg-secondary: #f8fafc;
  --bg-tertiary: #f1f5f9;
  --accent-primary: #2563eb;
  --accent-secondary: #3b82f6;
  --accent-light: #dbeafe;
  /* La tuile TOTAL (arbitrages 2 et 17 du 23/09) : une ardoise qui ne dit
     rien d'autre, et une barre grise qui dit « pas un site ». Les trois
     chiffres colorés de la tuile y restent à 4,56:1 au moins. */
  --fond-total: #e5eaf1;
  --barre-total: #475569;
  --accent-green: #10b981;
  --accent-green-light: #d1fae5;
  --accent-amber: #f59e0b;
  --accent-amber-light: #fef3c7;
  --accent-red: #ef4444;
  --accent-red-light: #fee2e2;
  --text-primary: #1e293b;
  --text-secondary: #475569;
  /* 4,52:1 sur le pire de ses six fonds réels. Valait #94a3b8, soit 2,10:1 sur
     un badge et 2,56:1 sur blanc — voir le bloc « CONTRASTES » ci-dessous. */
  --text-muted: #586a84;
  --border-color: #e2e8f0;
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
  --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
  --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
  --radius: 12px;
  --radius-sm: 8px;

  /* CE QUI FLOTTE AU-DESSUS DE LA SCÈNE, depuis la coque du 07/09 : le volet,
     la pilule, les pages encartées. Une ombre plus large et plus douce que
     --shadow-lg, parce qu'ici elle ne souligne pas un relief de quelques
     pixels : elle DÉTACHE un objet d'un fond de carte qu'on doit continuer de
     lire dessous. */
  --shadow-flottant: 0 10px 30px rgba(22, 33, 58, 0.14);
  --radius-flottant: 14px;

  /* ======================================================================
     L'ÉCHELLE TYPOGRAPHIQUE : six crans, et rien entre eux.

     Décision D7 du 26/08/2026, appliquée le 14/09/2026. Avant elle, ce
     fichier fixait une taille de texte à 255 endroits avec 38 valeurs
     distinctes, de 10 px à 44 px. Entre 12,5 px et 12,8 px l'œil ne
     distingue rien : ce sont deux décisions à tenir au lieu d'une.

     Aucune de ces 38 valeurs n'avait été décidée. Elles se sont accumulées
     écran par écran, si bien que deux libellés de même fonction n'avaient
     pas la même taille selon l'écran. Et surtout, RIEN NE POUVAIT SE
     CHANGER GLOBALEMENT : remonter le texte du téléphone demandait de
     retoucher 255 endroits à la main, sans en oublier aucun.

     LE RANGEMENT S'EST FAIT VERS LE HAUT, ET C'EST UN ARBITRAGE DE VICTOR
     du 14/09. Chaque taille a rejoint le cran supérieur ou égal, jamais
     l'inférieur : 210 textes ont grandi, 3 ont rétréci. Au plus proche
     voisin, 145 des 255 déclarations se seraient posées sur le plancher de
     12 px, et `0.8rem`, la valeur la plus fréquente du fichier avec 44
     emplois, serait DESCENDUE à 12 px. L'audit d'interface du 08/09
     demandait l'inverse : « augmenter légèrement la typographie ».

     Les trois seules baisses sont les chiffres qui dépassaient le cran
     haut : le coût du parc à 44 px puis à 36 px, et le taux d'information
     à 41,6 px. Ils valent 32 px, soit 2,3 fois le texte courant.

     ⚠️ LE PLANCHER DE 12 PX N'EST PAS CELUI DES CHAMPS DE SAISIE. Sous
     16 px, iOS zoome la page dès qu'on touche un champ, et l'on en ressort
     avec une mise en page décalée. C'est une règle À PART, posée en FIN de
     fichier, et elle NE SE DÉDUIT PAS de l'échelle : un `select` en
     --taille-courant y reste trop petit.

     ⚠️ ET CETTE RÈGLE SE FAISAIT ÉCRASER DE DEUX FAÇONS. Une media query
     n'ajoute AUCUNE spécificité, donc à égalité c'est la dernière règle du
     fichier qui gagne ; et une classe bat un élément quel que soit l'ordre,
     donc `.commande-echelle select` battait `select`. Six règles la
     défaisaient. Le plancher est en fin de fichier ET en !important.

     Le banc refuse toute taille écrite en dur : `scripts/tests-web.mjs`,
     contrôle « l'échelle typographique ». */
  --taille-mention: 0.75rem;     /* 12 px : mentions et libellés accessoires */
  --taille-courant: 0.875rem;    /* 14 px : le texte courant de l'application */
  --taille-appuye: 1rem;         /* 16 px : ce qui doit se lire en premier */
  --taille-sous-titre: 1.25rem;  /* 20 px */
  --taille-titre: 1.5rem;        /* 24 px */
  --taille-vedette: 2rem;        /* 32 px : les chiffres de tête */

  /* ======================================================================
     CONTRASTES — les jetons ajoutés le 26/08/2026, et pourquoi.

     Référentiel : RGAA 4.1.2, critères 3.2 (texte, 4,5:1) et 3.3 (composants
     d'interface et éléments graphiques, 3:1). Ce n'est pas WCAG 2.2 : les
     clients sont des collectivités, et le décret n°2019-768 couvre les
     intranets des personnes morales de droit public.

     Onze paires sur dix-sept échouaient. Les jetons ci-dessous corrigent la
     cause au lieu de rattraper écran par écran — ce qui avait déjà été tenté
     le 25/08 en teignant le fond de #groupe-periode parce que « les champs se
     fondaient dans l'en-tête ».

     UNE COULEUR DE TEXTE ET UNE COULEUR GRAPHIQUE NE SONT PAS LA MÊME CHOSE.

     C'est la distinction que la palette d'origine ne faisait pas. --accent-green
     et --accent-amber servaient à la fois de teinte d'accent (une bordure, une
     jauge : seuil 3:1, parfois aucun) et de couleur de TEXTE sur des chiffres
     de 0,75 rem (seuil 4,5:1). Impossible de satisfaire les deux avec une seule
     valeur sans noircir des ornements. D'où trois jetons de TEXTE, distincts
     des accents, qui prolongent une pratique déjà présente dans ce fichier :
     .ton-vert et .ton-ambre écrivaient déjà #047857 et #b45309 en dur pour
     cette raison exacte, sans que ce soit nommé.

     Les valeurs sont les plus PROCHES des anciennes qui atteignent le seuil,
     teinte et saturation conservées : on corrige la conformité, on ne redessine
     pas la palette. Celle-ci relève de la décision D5 (trois familles
     étanches), qui viendra avec P2.3.

     Chacune est vérifiée sur TOUS ses fonds réels, survol et sélection compris
     — .ligne-site passe en --accent-light au survol, et c'est là que les
     marges sont les plus minces.
     ====================================================================== */

  /* Texte. Pire cas mesuré, sur blanc / bg-secondary / bg-tertiary /
     accent-light (survol) : */
  --texte-vert:  #046b4d;   /* 5,35:1  — remplace --accent-green (2,08:1) */
  --texte-ambre: #a04a08;   /* 4,95:1  — remplace --accent-amber (1,76:1) */
  --texte-rouge: #c81111;   /* 4,86:1  — remplace --accent-red   (3,60:1) */

  /* LA FRONTIÈRE D'UN COMPOSANT N'EST PAS UN FILET DÉCORATIF.
     --border-color valait 1,13:1 : invisible. Mais le 3.3 ne vise que la
     bordure qui permet d'IDENTIFIER un composant — un champ, un bouton, un
     menu. Porter --border-color à 3:1 aurait transformé chaque séparateur de
     ligne et chaque contour de carte en trait sombre, pour une exigence qui ne
     les concerne pas. On sépare donc les deux rôles : --border-color reste le
     filet, --border-champ devient la frontière. */
  --border-champ: #7c8ba1;  /* 3,16:1 sur le pire des trois fonds */

  /* LE BLEU DE TEXTE. Séparé de --accent-primary pour la même raison que le
     vert et l'ambre : #2563eb tient 5,17:1 sur blanc, mais 4,24:1 sur
     --accent-light — le fond de la tuile du taux, du badge bleu, de l'étiquette
     de code, et de TOUTE ligne survolée ou choisie. C'est là que les chiffres
     les plus regardés de l'application vivent. L'écart est d'un cheveu à l'œil
     et de tout au regard de la règle. */
  --texte-bleu: #1d5dea;    /* 4,52:1 sur --accent-light, 5,52:1 sur blanc */
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html, body {
  font-family: 'Plus Jakarta Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;
  background: var(--bg-secondary);
  color: var(--text-primary);
  height: 100vh;
  overflow: hidden;
}

/* ⚠️ LE CORPS EST UNE COLONNE, POUR QUE LA BANNIÈRE DU MODE SUPPORT PRENNE SA
   PLACE SANS QU'ON LA LUI COMPTE. Elle est le premier enfant visible quand un
   opérateur Arvitria entre, et la coque prend ce qui reste : c'est la mise en
   page qui fait la soustraction, pas nous.

   Deux versions ont été essayées et MESURÉES dans le navigateur le 11/09/2026.
   Décaler la pilule : la pilule descendait, mais la scène, en position absolue,
   ne voyait rien et la bannière lui mangeait 28,8 px en haut, boutons de zoom
   de la carte compris. Réserver une hauteur en `calc()` : juste sur un écran
   large, faux sur 390 px, où la phrase passe à deux lignes et fait 44 px pour
   28,8 réservés, donc 15,2 px du bas rognés. Ici la somme fait exactement la
   fenêtre : 44 + 736 sur 390 px, 28,8 + 751,2 sur 1200, et zéro débordement.

   Et tout nombre écrit ici se serait démenti au premier mot ajouté à la phrase
   de la bannière. */
body { display: flex; flex-direction: column; }

/* Les écrans de connexion gardent leurs 100vh : ils sont seuls à l'écran. */
.ecran-connexion { flex: none; }

[hidden] { display: none !important; }

button { font: inherit; cursor: pointer; }
input { font: inherit; }

.marque { font-size: var(--taille-titre); font-weight: 700; letter-spacing: -0.02em; }

/* ---- connexion --------------------------------------------------------- */

.ecran-connexion {
  /* ⚠️ min-height ET NON height, depuis le 12/09/2026. Avec une hauteur fixe
     et un contenu centré, tout ce qui dépasse est coupé en haut ET en bas, et
     rien ne défile pour aller le chercher. Sur un téléphone, le clavier
     logiciel réduit la fenêtre de moitié : la carte de connexion se serait
     retrouvée décapitée au moment précis où l'on tape dedans.
     `dvh` suit la barre d'adresse qui se rétracte ; `vh` reste pour les
     navigateurs qui l'ignorent, et l'ordre compte. */
  min-height: 100vh;
  min-height: 100dvh;
  display: grid;
  place-items: center;
  background: var(--bg-tertiary);
  padding: 1.5rem;
}

.carte-connexion {
  width: min(26rem, 100%);
  background: var(--bg-primary);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  padding: 2rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.carte-connexion .marque { font-size: var(--taille-vedette); }

/* ---- la marque Arvitria, à la connexion et au portail -------------------- */
/* L'EMBLÈME AVANT LE NOM, demandé par Victor le 12/09/2026. Les deux dessins
   sont deux fichiers et non un seul, pour la même raison qu'à la vitrine : ils
   ne grandissent pas ensemble. L'emblème porte la marque et supporte d'être
   gros ; le mot, lui, devient vite écrasant.
   ⚠️ `height: auto` sur les deux : les attributs width et height du HTML
   réservent la place avant le chargement, mais c'est le CSS qui décide de la
   taille rendue. Sans cela, un logo remplacé un jour par un dessin d'un autre
   rapport se déformerait sans que rien ne le signale. */
/* ⚠️ SUR UNE SEULE LIGNE DEPUIS LE 12/09/2026, demandé par Victor en regardant
   le portail : l'emblème posé AU-DESSUS du nom prenait la hauteur de deux
   blocs pour dire une seule chose, et repoussait les tuiles vers le bas. Côte
   à côte, l'emblème reste avant le nom, et la marque tient sur une ligne.
   Les deux ont maigri d'un cran au passage, même demande. */
.marque-arvitria {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 0.7rem;
  margin: 0 0 0.2rem;
}
.marque-arvitria .marque-embleme { width: 3.4rem; height: auto; }
.marque-arvitria .marque-mot { width: 9.5rem; height: auto; }

/* La connexion n'a pas de sous-titre, et c'est lui qui portait l'espace avant
   le premier champ. La marque le reprend quand un label la suit directement :
   le portail, lui, garde son « Choisissez une application » et n'est pas
   concerné par cette règle. */
/* ⚠️ `.carte-connexion` EN TÊTE DU SÉLECTEUR, ET C'EST NÉCESSAIRE. La règle
   `.carte-connexion label` plus bas pose 0,6 rem et a la même spécificité :
   sans ce préfixe, c'est elle qui gagne, parce qu'elle est écrite après.
   Mesuré : l'écart restait à 19 px au lieu des 32 attendus. */
.carte-connexion .marque-arvitria + label { margin-top: 2rem; }

/* Le portail n'a qu'une chose à faire et tout l'écran pour la faire : la marque
   y reste un cran plus grande qu'à la connexion, sans redevenir un bandeau. */
.marque-arvitria-portail .marque-embleme { width: 4rem; }
.marque-arvitria-portail .marque-mot { width: 11.2rem; }

.marque-sous-titre {
  color: var(--text-muted);
  font-size: var(--taille-appuye);
  margin-bottom: 1.25rem;
}

.carte-connexion label {
  font-size: var(--taille-courant);
  font-weight: 600;
  color: var(--text-secondary);
  margin-top: 0.6rem;
}

.carte-connexion input {
  border: 1px solid var(--border-champ);
  border-radius: var(--radius-sm);
  padding: 0.6rem 0.75rem;
  background: var(--bg-primary);
  color: var(--text-primary);
}

/* ---- l'oeil du mot de passe -------------------------------------------- */
/* Le champ garde son apparence : c'est le CADRE qui accueille le bouton, et le
   champ qui se réserve la place à droite pour ne pas écrire sous l'icône. */
.champ-avec-oeil { position: relative; display: flex; }
.champ-avec-oeil input { flex: 1; padding-right: 2.6rem; }

.bouton-oeil {
  position: absolute;
  top: 0;
  right: 0;
  height: 100%;
  width: 2.6rem;
  display: grid;
  place-items: center;
  border: 0;
  background: none;
  color: var(--text-muted);
  border-radius: var(--radius-sm);
}

.bouton-oeil:hover { color: var(--text-secondary); }
.bouton-oeil:focus-visible { outline: 2px solid var(--accent-primary); outline-offset: -2px; }

/* Traits, pas aplats : le dessin reste net à toute taille et suit la couleur
   du texte, sans qu'on ait deux fichiers à tenir. */
.bouton-oeil .oeil {
  width: 1.15rem;
  height: 1.15rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
}

/* Le bouton dit ce qu'il VA faire, jamais l'état où l'on est : au repos il
   propose d'afficher, donc il montre l'oeil ouvert. Une fois le mot de passe
   visible, il propose de le cacher, et montre l'oeil barré. */
.bouton-oeil .oeil-cacher { display: none; }
.bouton-oeil[aria-pressed="true"] .oeil-voir { display: none; }
.bouton-oeil[aria-pressed="true"] .oeil-cacher { display: block; }

.carte-connexion input:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 1px;
  border-color: var(--accent-primary);
}

/* L'action principale des DEUX ecrans pleine page : se connecter, et changer
   son mot de passe. La regle visait le seul #bouton-connexion, si bien que le
   second bouton naissait sans style — constate a l'ecran le 26/08. Une liste
   de selecteurs plutot qu'une regle recopiee : deux copies auraient diverge. */
#bouton-connexion,
#bouton-mot-de-passe {
  margin-top: 1.4rem;
  padding: 0.7rem;
  border: 0;
  border-radius: var(--radius-sm);
  background: var(--accent-primary);
  color: #fff;
  font-weight: 600;
}

#bouton-connexion:hover:not(:disabled),
#bouton-mot-de-passe:hover:not(:disabled) { background: var(--accent-secondary); }
#bouton-connexion:disabled,
#bouton-mot-de-passe:disabled { opacity: 0.6; cursor: progress; }

.indication {
  font-size: var(--taille-mention);
  color: var(--text-muted);
  line-height: 1.45;
  margin-top: 0.25rem;
}

/* ---- le choix de la collectivité ---------------------------------------- */
/* Un bloc NEUTRE, et c'est tout son propos : il posait sa question dans la
   boîte rouge des erreurs jusqu'au 12/09/2026. Fond gris, trait discret, aucun
   des codes du refus. Il naît juste sous le bouton qu'on vient de cliquer. */
.bloc-choix {
  margin-top: 0.9rem;
  padding: 0.8rem;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  background: var(--bg-tertiary);
}

.bloc-choix-titre {
  font-size: var(--taille-courant);
  font-weight: 600;
  color: var(--text-primary);
}

.bloc-choix-aide {
  font-size: var(--taille-courant);
  color: var(--text-muted);
  margin-bottom: 0.6rem;
}

.liste-choix { display: flex; flex-direction: column; gap: 0.4rem; }

/* Pleine largeur et hautes : ce sont les cibles du doigt autant que de la
   souris, et il n'y en a jamais plus de trois. */
.bouton-choix {
  width: 100%;
  min-height: 2.75rem;
  padding: 0.6rem 0.75rem;
  text-align: left;
  border: 1px solid var(--border-champ);
  border-radius: var(--radius-sm);
  background: var(--bg-primary);
  color: var(--text-primary);
  font-size: var(--taille-appuye);
  font-weight: 600;
}

.bouton-choix:hover:not(:disabled) {
  border-color: var(--accent-primary);
  background: var(--accent-light);
}

.bouton-choix:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 1px;
}

.bouton-choix:disabled { opacity: 0.6; cursor: progress; }

.message-erreur {
  margin-top: 0.9rem;
  padding: 0.6rem 0.75rem;
  border-radius: var(--radius-sm);
  background: var(--accent-red-light);
  color: #991b1b;
  font-size: var(--taille-courant);
}

/* ---- le portail --------------------------------------------------------- */
/* Même coquille que la connexion, .ecran-connexion et .carte-connexion : la
   carte s'élargit seulement, pour que deux tuiles tiennent côte à côte. Une
   tuile est un bouton (BâtiFocale, dans cette page) ou un lien (toute autre
   application, une page à part) : même forme, même geste au clavier. */
/* ⚠️ ÉLARGIE LE 12/09/2026, à la demande de Victor : « la fenêtre avec le choix
   des applications doit être en grand, avec les logos en gros ». C'est le
   premier écran qu'un client voit après s'être connecté, et il n'y a rien
   d'autre à y faire que choisir. */
.carte-portail { width: min(48rem, 100%); padding: 2.6rem; }

.portail-collectivite {
  color: var(--text-secondary);
  font-size: var(--taille-appuye);
  margin: -0.75rem 0 1rem;
}
.portail-collectivite:empty { display: none; }

/* ===========================================================================
   LE PORTAIL : DES CARTES VERTICALES, POSÉES SUR LEUR OMBRE.

   Arbitré par Victor le 13/09/2026, devant une grille d'intégrations dont il
   aimait l'allure. Ce qui a été retenu de cette image, et ce qui a été écarté :

   · RETENU, l'ombre à la place du trait. Les tuiles portaient une bordure de
     1 px, exactement comme un champ de saisie : c'est ce qui les faisait
     paraître datées, bien plus que leur disposition. Une carte qui flotte se
     lit comme quelque chose qu'on ouvre ; une carte encadrée, comme quelque
     chose qu'on remplit.
   · RETENU, le logo en grand sur un halo, et l'air autour.
   · ÉCARTÉ, la grille aux cases fantômes. Elle dit « il y en a cent »,
     quand cet écran dit « choisis une de tes deux ». Avec deux tuiles, elle
     aurait paru vide.
   · ÉCARTÉ, le halo lumineux sur un seul élément. Dans une image de marque il
     désigne le produit ; dans une interface il désigne le bon choix, et ce
     n'est pas à l'écran de le faire.

   ⚠️ L'OMBRE NE PORTE AUCUNE INFORMATION, ET C'EST UNE OBLIGATION, PAS UN
      GOÛT. Le RGAA 4.1.2 demande que ce qui se voit se voie aussi sans le
      relief : l'état de survol change AUSSI la couleur du chevron, et le focus
      pose un vrai contour, jamais une ombre de plus. Une ombre n'est pas un
      anneau de focus.
   =========================================================================== */
.tuiles-portail {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  /* 15 rem et non 18 : une carte verticale n'a plus à loger son texte sur une
     ligne, donc elle se contente de moins, et deux cartes tiennent côte à côte
     dès 34 rem au lieu de 40. */
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 1.25rem;
}

.tuile-portail {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
  width: 100%;
  /* ⚠️ 44 px DE HAUT AU MINIMUM, et on est très au-delà : une carte entière
     est la cible, il n'y a pas de petit bouton à viser dedans. */
  padding: 2rem 1.5rem 1.75rem;
  border: 0;
  border-radius: 1rem;
  background: var(--bg-primary);
  box-shadow: var(--shadow-sm), 0 8px 24px -12px rgba(22, 33, 58, 0.35);
  color: var(--text-primary);
  text-decoration: none;
  text-align: center;
  font: inherit;
  cursor: pointer;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.tuile-portail:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow), 0 18px 36px -16px rgba(22, 33, 58, 0.45);
}
.tuile-portail:hover .tuile-portail-chevron { color: var(--accent-primary); }

/* ⚠️ UN CONTOUR, PAS UNE OMBRE. Le survol se contentait d'une ombre et d'une
   bordure colorée ; au clavier, cela ne suffit pas, et une ombre portée ne
   passe aucun contrôle de contraste. Le décalage de 3 px détache l'anneau du
   bord arrondi, sans quoi il se confond avec la carte. */
.tuile-portail:focus-visible {
  outline: 3px solid var(--accent-primary);
  outline-offset: 3px;
}

/* Le halo : un disque très pâle aux couleurs de l'application, et rien
   d'autre. Il ne dit rien que l'œil doive lire, il pose le logo. */
.tuile-portail-halo {
  display: grid;
  place-items: center;
  width: 112px;
  height: 112px;
  flex: none;
  border-radius: 28px;
  background: radial-gradient(circle at 50% 45%, var(--halo), transparent 72%);
}

.tuile-portail-logo { width: 64px; height: 64px; flex: none; }

.tuile-portail-texte {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  min-width: 0;
  /* La ligne reste lisible : au-delà, l'œil perd le début de la suivante. */
  max-width: 18rem;
}
.tuile-portail-nom { font-weight: 700; font-size: var(--taille-sous-titre); }
.tuile-portail-sous-titre { font-size: var(--taille-courant); color: var(--text-muted); line-height: 1.4; }

/* En haut à droite, discret : il ne pèse pas dans la lecture, et il dit qu'on
   part. Il n'est plus en bout de ligne, puisqu'il n'y a plus de ligne. */
.tuile-portail-chevron {
  position: absolute;
  top: 0.9rem;
  right: 1.1rem;
  color: var(--border-color);
  font-size: var(--taille-titre);
  line-height: 1;
  transition: color 0.15s ease;
}

/* ⚠️ QUI REFUSE LE MOUVEMENT N'EN REÇOIT PAS. Le soulèvement au survol est un
   agrément ; pour qui a réglé son système contre les animations, c'est une
   gêne. Le changement de couleur et d'ombre, lui, reste : l'état doit se voir. */
@media (prefers-reduced-motion: reduce) {
  .tuile-portail { transition: none; }
  .tuile-portail:hover { transform: none; }
}

/* ---- la connexion et le portail au téléphone ---------------------------- */
/* Même coupure que partout ailleurs, 48 rem : BâtiFocale et Carto-Salles la
   partagent, et trois valeurs différentes dans un même produit se voient.
   Mesuré dans un cadre de 390 x 844 le 12/09/2026 : la carte tenait déjà sans
   débordement, mais deux choses n'allaient pas. */
@media (max-width: 48rem) {
  /* 1. Les marges intérieures mangeaient la largeur : 2 à 2,6 rem de chaque
     côté sur un écran de 390 px, c'est un quart de la place. « Patrimoine et
     interventions » passait sur deux lignes dans sa tuile. */
  .ecran-connexion { padding: 1rem; }
  .carte-connexion { padding: 1.5rem; }
  .carte-portail { padding: 1.5rem; }
  /* La carte verticale garde son air, mais moins : sur 390 px, 2 rem en haut
     et en bas repoussaient le second bouton sous la ligne de flottaison. */
  .tuile-portail { padding: 1.4rem 1rem 1.2rem; }
  .tuile-portail-halo { width: 88px; height: 88px; border-radius: 22px; }
  .tuile-portail-logo { width: 52px; height: 52px; }

  /* 2. Les cibles faisaient 42 px, mesurées. Sous le pouce, 48 px est le
     minimum confortable, et cet écran a la place de les donner : il n'y a que
     deux champs et un bouton. */
  .carte-connexion input,
  #bouton-connexion,
  #bouton-mot-de-passe,
  .bouton-choix { min-height: 3rem; }
  .champ-avec-oeil .bouton-oeil { width: 3rem; }
  /* La tuile est la plus grande cible de l'écran : elle ouvre une application. */
  .tuile-portail { min-height: 5rem; }
}

.portail-vide { color: var(--text-muted); font-size: var(--taille-appuye); }

.portail-pied {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 1.5rem;
  padding-top: 1rem;
  border-top: 1px solid var(--border-color);
}
.portail-compte { font-size: var(--taille-courant); color: var(--text-secondary); }

/* ---- l'écran « mot de passe » ------------------------------------------- */
/* Il réutilise ENTIÈREMENT la coquille de la connexion — .ecran-connexion et
   .carte-connexion — parce que c'est le même moment du parcours : plein écran,
   une seule chose à faire. Deux règles seulement lui sont propres. */

/* La règle des douze caractères, dite AVANT la saisie. Reliée au champ par
   aria-describedby : sans cela elle n'existerait que pour l'œil. */
.mdp-regle {
  margin: -0.5rem 0 0.9rem;
  font-size: var(--taille-courant);
  color: var(--text-secondary);
}

/* « Annuler » ne concurrence pas « Enregistrer » : il est discret, et il
   disparaît quand le changement est imposé (guide §8.3 — un bouton d'annulation
   ne doit jamais rivaliser avec l'action principale). */
#mot-de-passe-retour { margin-top: 0.6rem; width: 100%; }

/* ---- le lien d evitement ------------------------------------------------ */
/* RGAA 4.1.2, critere 12.7. Hors champ tant qu'on ne l'a pas atteint au
   clavier ; il descend alors en haut a gauche, au-dessus de tout.

   ⚠️ IL EST DEPLACE HORS DE L'ECRAN, PAS RETIRE. Un lien pose en display:none
   ne peut pas etre atteint a la tabulation : il n'eviterait donc rien, ce qui
   est l'inverse exact de sa raison d'etre. Il revient des qu'il prend le focus.

   z-index au-dessus des controles Leaflet, qui vivent a 1000 : sans cela il
   apparaitrait DERRIERE la carte. */
.lien-evitement {
  position: absolute;
  left: 0.5rem;
  top: -3rem;
  z-index: 1200;
  padding: 0.6rem 0.9rem;
  border-radius: var(--radius-sm);
  background: var(--accent-primary);
  color: #fff;
  font-size: var(--taille-courant);
  font-weight: 600;
  text-decoration: none;
  transition: top 0.12s ease;
}
.lien-evitement:focus { top: 0.5rem; }

/* La cible ne doit pas dessiner de contour : on lui donne le focus par
   programme, elle n'est pas un element interactif. */
#conteneur-principal:focus { outline: none; }

/* ---- coquille ---------------------------------------------------------- */

/* ⚠️ LA COQUE A CHANGÉ DE NATURE LE 07/09 (décisions 1 et 8).

   AVANT : trois bandes empilées. Un en-tête pleine largeur (marque, domaines,
   compte), une barre d'onglets (vues, période, affiner), puis le contenu. Les
   deux premières mangeaient 108 px de hauteur sur TOUTE la largeur, mesuré,
   pour porter une marque, trois vues et un nom de personne.

   APRÈS : une scène qui occupe l'écran, et deux objets qui flottent au-dessus.
   Un volet à gauche, qui porte la question et ses réponses ; une pilule en haut
   à droite, qui porte l'identité et la navigation.

   Ce que cela coûte, mesuré et assumé : la colonne faisait 400 px fixes, donc
   la carte disposait de 1 520 px sur un écran de 1 920. Avec le volet superposé
   à 460 px, la zone entièrement dégagée tombe à 1 430 px. Victor l'a retenu
   quand même, pour l'unité des trois vues. */
/* ⚠️ `flex: 1` ET `min-height: 0` FONT TOUT LE TRAVAIL. La coque prend ce que
   la bannière du mode support lui laisse, sans qu'aucune hauteur soit écrite :
   voir le commentaire de `body`, plus haut, et les mesures qui l'accompagnent.
   `min-height: 0` parce qu'un élément de colonne refuse par défaut de
   descendre sous la hauteur de son contenu, et la coque déborderait. */
.application {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  height: 100vh;
  overflow: hidden;
  /* ⚠️ EN COLONNE DEPUIS LE 17/09, À TOUTES LES LARGEURS. Le bloc téléphone le
     faisait déjà, et lui seul : porter la même mise en page partout supprime
     une divergence au lieu d'en créer une. Voir la note de `.pilule`. */
  display: flex;
  flex-direction: column;
}

/* ---- l'en-tête, pleine largeur ------------------------------------------ */
/* ⚠️ CE BLOC DÉFAIT LA PILULE FLOTTANTE DU 07/09, ET C'EST UNE DEMANDE DE
   VICTOR DU 17/09 : « le header doit prendre toute la largeur de la fenêtre. Le
   nom de l'appli et son logo doivent être à gauche. »

   Ce qu'on reprend, et ce qu'on rend. La pilule flottait en haut à droite pour
   laisser la scène entière au fond de carte, et ce gain est rendu : la barre
   reprend --haut-tete sur toute la largeur. Ce qu'elle apporte est un ancrage
   stable à gauche, là où l'œil cherche le nom d'un produit, et une navigation
   qui ne recouvre plus la carte, donc plus aucun cercle caché dessous.

   ⚠️ ET LES CALQUES DESCENDENT AVEC ELLE, SANS QUOI ILS PASSENT DESSOUS. Tous
   les enfants de `.application` sont en position ABSOLUE contre elle : la
   scène, le volet, les feuilles. La scène rejoint le flux ci-dessous, mais le
   volet reste absolu, et son `top` part donc du haut de la coque, c'est-à-dire
   du haut de la barre. C'est `--haut-tete` qui les tient d'accord, et c'est la
   raison pour laquelle c'est une variable et non trois nombres écrits à trois
   endroits.

   Le z-index reste au-dessus de tout : les contrôles de Leaflet sont à 1000, le
   volet du téléphone à 1001. */
:root { --haut-tete: 3.25rem; }

.pilule {
  position: relative;
  z-index: 1200;
  flex: none;
  display: flex;
  align-items: center;
  gap: 0.85rem;
  min-height: var(--haut-tete);
  padding: 0.35rem 1rem;
  background: var(--bg-primary);
  border: 0 solid var(--border-color);
  border-bottom-width: 1px;
  border-radius: 0;
  box-shadow: var(--shadow-sm);
  font-size: var(--taille-courant);
}

/* ⚠️ LE CLIENT ET LE COMPTE VONT À DROITE, ET LE FILET QUI LES PRÉCÈDE AVEC
   EUX. Sans cette poussée, les quatre natures de la barre (un produit, des
   vues, un client, une personne) se collent à gauche en une seule phrase, et
   l'identité se lit comme une cinquième vue.

   La poussée est posée sur les ONGLETS, et non sur le client : une marge
   automatique sur le client laisserait le filet qui le précède échoué au
   milieu de la barre. Les onglets absorbent l'espace à leur droite, et tout ce
   qui les suit part ensemble. */
.pilule .onglets { margin-right: auto; }


.pilule-marque { display: flex; align-items: center; gap: 0.4rem; font-weight: 700; }
/* 22 px : au-dessus des 24 px du symbole plein, on emploierait la version
   « favicon ». Ici la pilule est basse, et 22 px est le compromis relevé. */
.logo-marque { width: 30px; height: 30px; display: block; flex-shrink: 0; }

/* Un filet, pas un espace : la pilule aligne quatre choses de natures
   différentes : un produit, des vues, un client, une personne. Sans séparation,
   elles se lisent comme une seule phrase. */
.pilule-filet { width: 1px; height: 1rem; background: var(--border-color); flex: none; }

.pilule-client { display: flex; align-items: center; gap: 0.5rem; }
.pilule-collectivite { display: flex; align-items: center; gap: 0.4rem; }
.pilule-collectivite:empty { display: none; }

/* 30 px depuis le 08/09, et TOUJOURS la même hauteur que la marque du produit.
   Victor : « le logo de la ville et celui de BâtiFocale, ainsi que le texte,
   doivent être bien plus grands ». Les deux montent donc ensemble : dans une
   pilule, un blason plus haut que le symbole de l'application ferait passer le
   client pour l'éditeur, et l'inverse ferait de lui un accessoire.

   Une largeur automatique et non fixe : le fichier d'un client est le sien, et
   rien ne garantit qu'il soit carré. Un panorama essayé faisait 3,26:1. */
.logo-collectivite { height: 30px; width: auto; display: block; flex-shrink: 0; }
.nom-collectivite-texte { color: var(--text-secondary); white-space: nowrap; }

/* ---- le menu du compte ------------------------------------------------- */
.pilule-compte { position: relative; }

.bouton-compte {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  border: 0;
  background: none;
  color: var(--text-secondary);
  font-size: inherit;
  font-weight: 600;
  padding: 0.15rem 0.1rem;
  border-radius: var(--radius-sm);
}
.bouton-compte:hover { color: var(--text-primary); }
.bouton-compte .chevron { font-size: var(--taille-mention); opacity: 0.7; }
.nom-utilisateur { white-space: nowrap; }

.menu-compte {
  position: absolute;
  top: calc(100% + 0.55rem);
  right: 0;
  min-width: 12rem;
  display: flex;
  flex-direction: column;
  padding: 0.3rem;
  background: var(--bg-primary);
  border: 1px solid var(--border-color);
  border-radius: var(--radius);
  box-shadow: var(--shadow-flottant);
}

.element-menu {
  border: 0;
  background: none;
  color: var(--text-secondary);
  text-align: left;
  padding: 0.5rem 0.6rem;
  border-radius: var(--radius-sm);
  font-size: var(--taille-courant);
  white-space: nowrap;
}
.element-menu:hover { background: var(--bg-secondary); color: var(--text-primary); }
/* L'administration ouverte se remplit : c'est un écran comme un autre, et le
   menu doit dire lequel on regarde. */
.element-menu.actif { background: var(--accent-light); color: var(--texte-bleu); font-weight: 600; }

/* ---- le choix des données, db/68 --------------------------------------- */
/* Il reprend la forme du menu du compte : même bouton discret, même panneau,
   mêmes éléments. Deux menus qui ne se ressembleraient pas dans la même pilule
   se liraient comme deux mécanismes différents, alors que c'est le même geste. */
.pilule-modules { position: relative; }

.bouton-modules {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  border: 0;
  background: none;
  color: var(--text-secondary);
  font-size: inherit;
  padding: 0.15rem 0.1rem;
  border-radius: var(--radius-sm);
  white-space: nowrap;
}
.bouton-modules:hover { color: var(--text-primary); }
.bouton-modules .chevron { font-size: var(--taille-mention); opacity: 0.7; }
/* Le mot « Données » dit de QUOI on parle ; le libellé dit LEQUEL. Sans le
   premier, le bouton porte un nom de logiciel que rien n'explique ; sans le
   second, il ne dit pas où l'on en est. */
.bouton-modules-quoi { color: var(--text-secondary); opacity: 0.75; }
.libelle-modules { font-weight: 600; }

.menu-modules {
  position: absolute;
  top: calc(100% + 0.55rem);
  left: 0;
  min-width: 12rem;
  display: flex;
  flex-direction: column;
  padding: 0.3rem;
  background: var(--bg-primary);
  border: 1px solid var(--border-color);
  border-radius: var(--radius);
  box-shadow: var(--shadow-flottant);
  z-index: 1;
}

/* Le filet qui précède le bloc disparaît avec lui : un séparateur sans rien à
   séparer laisserait deux traits collés au milieu de la pilule. */
.pilule-filet.filet-masque { display: none; }

/* ⚠️ CE QUI N'A PAS DE SOURCE DISPARAÎT, ET PAR UNE CLASSE, PAS PAR `hidden`.
   La distinction n'est pas cosmétique : `site-factures` et `site-engagements`
   portent DÉJÀ `hidden`, posé et retiré par site.js selon qu'il y a des
   factures à montrer. Se servir de `hidden` ici mettrait deux mécanismes sur
   le même interrupteur, et le dernier à écrire gagnerait : un bloc financier
   réapparaîtrait sous un périmètre qui l'exclut, au premier rechargement de la
   fiche.

   `!important` parce que cette règle doit gagner contre celles qui RENDENT ces
   blocs, et elles portent des sélecteurs plus spécifiques. */
.source-absente { display: none !important; }

/* ---- les domaines, descendus dans le volet ----------------------------- */
/* Ils étaient centrés dans l'en-tête, entre la marque et le compte. Dans le
   volet ils s'alignent à gauche et passent à la ligne : la largeur y est de
   460 px, pas de 1 920. */
/* ⚠️ UNE LIGNE PAR DOMAINE, ET NON CINQ ÉTIQUETTES SERRÉES SUR DEUX RANGS.
   Relevé par Victor le 08/09 : « si je clique sur Domaine, il faut que les
   choix soient bien visibles, beaucoup plus grands que maintenant ».

   Ils étaient en 0,85 rem, collés par 0,3 rem, avec une case de 13 px : une
   cible de la taille d'un mot, pour le filtre le plus employé de
   l'application. En colonne, chaque domaine occupe toute la largeur du volet,
   et la ligne entière se clique parce que c'est un `label`.

   Le nombre de domaines vient de la base du client, jamais d'une constante :
   la colonne tient aussi bien pour trois que pour huit, ce qu'une rangée
   n'aurait pas fait. */
/* ⚠️ `minmax(0, 1fr)` ET NON `1fr`, ET CE N'EST PAS UNE PRÉCAUTION D'ÉCOLE.
   `1fr` vaut `minmax(auto, 1fr)`, et ce minimum `auto` est la largeur du
   CONTENU : une colonne refuse donc de descendre sous son mot le plus long.
   Mesuré le 17/09/2026 dans le rail : deux colonnes de 69,9 et 66,9 px plus
   4,8 px d'écart, soit 141,6 px de contenu dans une boîte de 131,6. Les dix
   px de trop fabriquaient une BARRE DE DÉFILEMENT HORIZONTALE, et « Voirie »,
   « Logistique » et « Nettoyage » sortaient du rail.

   ⚠️ ET LE RAIL NE DEMANDE POURTANT QUE `overflow-y: auto`. Une boîte dont un
   seul axe cesse d'être `visible` fait passer l'AUTRE en `auto` : la barre
   horizontale n'était pas demandée, elle était induite. La seule parade est
   donc d'empêcher le débordement, jamais de cacher la barre. */
.groupe-domaines {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.3rem;
}

/* ---- la bande de commandes, au-dessus de la scène ----------------------- */
/* ⚠️ ELLE OCCUPE UN VIDE QUI EXISTAIT DÉJÀ. Mesuré le 17/09/2026 sur les TROIS
   vues, carte, graphique et activité : les panneaux sont posés à 81 px du haut
   de `main`, et cette bande de 81 px à droite du volet ne portait rien. Elle
   n'enlève donc de place à personne.

   Elle reprend les mêmes bornes que la scène, `--largeur-volet` et
   `--gouttiere-volet`, plutôt que des nombres à elle : le jour où le volet
   change de largeur, la bande suit sans qu'on y pense.

   ⚠️ ELLE PASSE AU-DESSUS DU RAIL EN z-index, et c'est voulu : ses panneaux
   s'ouvrent vers le bas, par-dessus la scène, et doivent couvrir ce qu'ils
   recouvrent plutôt que se glisser dessous. */
.bande-commandes {
  position: absolute;
  top: 0.9rem;
  left: calc(var(--largeur-volet) + var(--gouttiere-volet));
  right: 1rem;
  z-index: 1200;
}

/* ⚠️ LE BLOC PERD SES MARGES DE VOLET, PARCE QU'IL N'EST PLUS DANS UN VOLET.
   Mesuré le 17/09 : il gardait 9,6 px d'écart entre ses rangées et le
   rembourrage d'un bloc de barre latérale, ce qui portait la bande à 88 px
   pour 68 disponibles, et elle mordait la carte de 21 px.

   ⚠️ ET ON NE DESCEND PAS LA SCÈNE POUR AUTANT. Baisser `--dessous-pilule`
   aurait rendu les 21 px, mais il positionne AUSSI le rail des domaines : sur
   un portable de 1366 x 768, le rail passerait de 621 à 600 px de haut pour
   630 px de contenu, et se mettrait à défiler. C'est exactement la marge que
   l'arbitrage du 15/09 avait mesurée pour tenir sur cet écran-là. */
.bande-commandes .volet-question {
  gap: 0;
  padding: 0;
}

/* Les exercices en boutons, première rangée de la bande.
   ⚠️ LA RANGÉE NE DÉFILE PAS ET NE PASSE PAS À LA LIGNE : cinq boutons de
   62 px font 310, et la bande en offre 765, mesuré le 17/09. Si un jour elle
   débordait, ce serait le signe qu'on a augmenté EXERCICES_EN_BOUTONS, pas
   qu'il faut une barre de défilement : Victor a refusé le défilement
   horizontal le 17/09, « ce n'est pas acceptable ». */
.boutons-exercice {
  position: relative;
  display: flex;
  gap: 0.3rem;
  margin-bottom: 0.4rem;
}

/* ⚠️ ELLE COMBLE LES ÉCARTS, ELLE NE SOULIGNE PAS. Posée derrière les boutons
   retenus, du premier au dernier, elle fait de la plage UNE capsule au lieu de
   plusieurs pastilles. C'est tout ce qu'on lui demande : dire que le geste
   étend une période, et non qu'il coche des cases qui s'allument toutes
   seules. Victor le 17/09, après essai : « des fois c'est déroutant ».

   ⚠️ ET ELLE NE COÛTE AUCUNE HAUTEUR, ce qui n'était pas négociable : la bande
   dispose de 3 px de marge avant de mordre sur la carte, mesuré. Une barre
   posée SOUS la rangée en aurait demandé une douzaine.

   Sa teinte est celle du bouton retenu, pas une de plus : deux bleus voisins
   se liraient comme deux états différents. */
.barre-plage {
  position: absolute;
  top: 0;
  bottom: 0;
  z-index: 0;
  border-radius: 999px;
  background: #dbeafe;
  border: 1px solid #93c5fd;
  transition: left .18s ease, width .18s ease;
  pointer-events: none;
}

/* ⚠️ `user-select: none` PARCE QUE LA RANGÉE SE GLISSE DEPUIS LE 18/09. Presser
   puis glisser sur des boutons qui portent du texte sélectionne ce texte, et
   l'écran se couvre de bleu pendant le geste. Le `preventDefault()` du
   `pointerdown` ne suffit pas sur tous les navigateurs. */
.bouton-exercice { user-select: none; }

.bouton-exercice {
  position: relative;
  padding: 0.28rem 0.7rem;
  border: 1px solid var(--border-color);
  border-radius: 999px;
  background: var(--bg-primary);
  font: inherit;
  font-size: var(--taille-mention);
  font-weight: 600;
  color: var(--text-secondary);
  cursor: pointer;
  transition: transform .15s cubic-bezier(.2, .8, .3, 1), box-shadow .15s ease;
}
/* ⚠️ IL SE SOULÈVE, IL NE SE CERNE PAS. Règle de Victor du 15/09, redemandée le
   17/09 : « je veux des boutons responsifs », c'est-à-dire « elles se déplacent
   un peu, effet d'ombre. Je n'aime pas le contour comme réponse. » */
.bouton-exercice:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-flottant), 0 8px 18px rgba(16, 24, 40, .12);
  border-color: color-mix(in srgb, var(--texte-bleu) 35%, white);
}
.bouton-exercice:active { transform: translateY(0); }
.bouton-exercice:focus-visible { outline: 3px solid var(--texte-bleu); outline-offset: 2px; }

/* ⚠️ PASTEL, ET NON LE BLEU PLEIN. Demandé par Victor le 17/09 : « je n'aime pas
   la couleur des boutons des années choisies, je veux des couleurs pastel ».

   ⚠️ ET L'ÉTAT NE SE DIT PAS QUE PAR LA COULEUR (RGAA 3.1). Le pastel seul
   serait un aplat clair de plus : l'exercice retenu est AUSSI le seul en gras
   plein et le seul à porter un liseré teinté. Contraste du texte sur le pastel :
   #1e40af sur #dbeafe vaut 8,1:1, très au-dessus des 4,5 exigés. */
.bouton-exercice[aria-pressed="true"] {
  background: #dbeafe;
  border-color: #93c5fd;
  color: #1e40af;
  font-weight: 700;
}
.bouton-exercice[aria-pressed="true"]:hover { border-color: #60a5fa; }

/* ⚠️ L'ANNÉE EN COURS EST EN POINTILLÉS, arbitrée par Victor le 17/09 contre la
   puce « en cours » et le soulignement. Le pointillé dit « pas fermé » sans un
   mot et sans coûter un pixel de largeur.

   ⚠️ ET IL SURVIT À LA SÉLECTION. Un pointillé qui disparaîtrait une fois
   l'année retenue s'effacerait exactement quand on s'en sert : c'est retenue
   que l'année en cours risque d'être comparée aux autres. Le liseré reste donc
   en tirets, et il s'épaissit au lieu de se remplir. */
/* ⚠️ LES TIRETS SE RÈGLENT PAR L'ÉPAISSEUR DU TRAIT, ET PAR RIEN D'AUTRE. CSS
   n'offre aucune propriété de longueur de tiret sur une bordure, et
   `border-image`, qui le permettrait, ne suit pas un `border-radius` : le
   bouton est une pastille, la piste est fermée. Or la longueur des tirets est
   PROPORTIONNELLE à `border-width` : passer de 1 à 2 px double la longueur des
   tirets ET des vides. Victor le 17/09 : « le pointillé doit être plus rare,
   car on peut le confondre avec le trait plein. »

   Le rembourrage se compense d'autant, sinon le bouton de l'année en cours
   serait plus large que ses cinq voisins et la rangée boiterait. */
.bouton-exercice.exercice-en-cours {
  border-width: 2px;
  border-style: dashed;
  border-color: #64748b;
  padding: calc(0.28rem - 1px) calc(0.7rem - 1px);
}
.bouton-exercice.exercice-en-cours[aria-pressed="true"] {
  border-width: 3px;
  border-color: #2563eb;
  padding: calc(0.28rem - 2px) calc(0.7rem - 2px);
}

/* ---- les logements, trois icônes à même la bande ------------------------ */
.bloc-logements { display: flex; align-items: center; gap: 0.35rem; }
.etiq-bande {
  font-size: var(--taille-mention);
  color: var(--text-muted);
  white-space: nowrap;
}
.segments-logements { display: inline-flex; gap: 0.25rem; }

.ico-logement {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  /* ⚠️ L'ICÔNE ET SON MOT SONT CÔTE À CÔTE, PAS EMPILÉS, et ce n'est pas une
     préférence. Empilés, le bouton faisait 39 px et la bande débordait de 6 px
     sur la carte ; les rattraper en rapetissant le texte m'a fait écrire
     0,56 rem, SOUS LE PLANCHER de l'échelle typographique, ce que le banc a
     refusé à juste titre. Côte à côte, le bouton fait la hauteur d'une ligne,
     le mot garde son jeton `--taille-mention`, et le rang rétrécit au lieu de
     grandir. Mesuré à l'écran dans les deux formes. */
  padding: 0.22rem 0.5rem;
  border: 1px solid var(--border-color);
  border-radius: 999px;
  background: var(--bg-primary);
  font: inherit;
  font-size: var(--taille-mention);
  line-height: 1.1;
  color: var(--text-muted);
  cursor: pointer;
  transition: transform .15s cubic-bezier(.2, .8, .3, 1), box-shadow .15s ease;
}
.ico-logement svg { width: 15px; height: 15px; flex: none; }
.ico-logement .barre-refus { stroke: #b91c1c; }
.ico-logement:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-flottant), 0 8px 18px rgba(16, 24, 40, .12);
}
.ico-logement:focus-visible { outline: 3px solid var(--texte-bleu); outline-offset: 2px; }
/* Même pastel que les exercices : un seul langage pour « c'est celui-là ». */
.ico-logement[aria-pressed="true"] {
  background: #dbeafe;
  border-color: #93c5fd;
  color: #1e40af;
  font-weight: 700;
}

/* ---- les deux rangs de la bande ---------------------------------------- */
.rang-periode { display: flex; align-items: center; gap: 0.35rem; }
.trait-bande {
  width: 1px;
  height: 20px;
  background: var(--border-color);
  margin: 0 0.15rem;
  flex: none;
}
/* ⚠️ ELLE SE CENTRE DANS LE VIDE QUI LUI EST ALLOUÉ, PAS SUR LA BANDE. Deux
   lectures de « au milieu de la zone grise », et c'est la seconde qui est la
   bonne : Victor l'a tranché le 17/09, et la mesure départage. Centrée sur la
   BANDE elle tombait à 934 px ; le vide entre le dernier bouton, à 675, et la
   pastille « Autre période », à 1395, a son milieu à 1035. Cent un pixels trop
   à gauche, et surtout posée sous les boutons au lieu de l'espace libre.

   `flex: 1` fait les deux à la fois : la boîte prend tout le vide, donc une
   largeur constante qui ne déplace jamais les boutons quand le texte s'allonge,
   et son contenu se centre dedans.

   ⚠️ ET CELA SUPPRIME LA RÈGLE DE REPLI qui vivait ici. Le centrage absolu
   pouvait chevaucher les boutons sous 930 px de bande, d'où un `@media` ;
   une boîte souple ne déborde jamais de son espace, il n'y a plus rien à
   replier.

   `min-width: 0` : sans lui, une boîte souple refuse de descendre sous son
   contenu, et le débordement reviendrait par la porte de derrière. */
.periode-lue {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  margin: 0;
  padding: 0 0.5rem;
  line-height: 1.1;
  /* ⚠️ TRANSPARENTE AUX CLICS, ET C'EST UNE RÉPARATION, PAS UN RÉGLAGE. Pour
     centrer le TEXTE, `centrerLaPeriode()` décale TOUTE la boîte, qui occupe
     le vide entier : décalée de 42 px, elle recouvrait le bouton « Document
     PDF » posé à sa droite le 24/09, et un vrai clic tombait sur elle sans
     rien faire. Vu par Victor (« le bouton n'est pas actif ») ; un clic par
     script, lui, passait, ce qui masquait le défaut. Ce n'est que du texte à
     lire : rien ne l'écoute, mesuré le même jour. */
  pointer-events: none;
}
.periode-lue .cle {
  font-size: var(--taille-mention);
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--text-muted);
}
/* ⚠️ LE SEUL GRAND CARACTÈRE DE LA BANDE, et c'est voulu : Victor le 17/09,
   « c'est une info fondamentale ». Rien d'autre ne monte à cette taille ici,
   sinon la période cesserait de se détacher. */
#periode-lue-valeur {
  /* ⚠️ UN CRAN DE L'ÉCHELLE, PAS UNE VALEUR EN DUR. Demandé par Victor le
     17/09, « agrandis la police de la valeur ». `--taille-sous-titre` vaut
     1,25 rem ; le cran au-dessus, 1,5 rem, coûterait quatre pixels de plus au
     rang, et la bande n'a que 3 px de marge avant de mordre sur la carte. */
  font-size: var(--taille-sous-titre);
  font-weight: 700;
  white-space: nowrap;
}

/* ---- l'agent regardé, après la période, sur l'écran Agents (23/09) -------
   Ailleurs il n'existe pas. Sur l'écran Agents, la période cède le vide qu'elle
   occupait : elle se range contre les boutons (« on peut décaler la période
   vers la gauche », Victor), et c'est l'agent qui prend l'espace libre. */
.agent-lu { display: none; }
/* ⚠️ RÉPARTIS SUR LA LARGEUR, demandé par Victor le 23/09 : « pour que ce
   soit plus aéré ». Les deux boîtes se partagent le vide à parts égales, et
   chacune centre son contenu dans sa moitié. Le filet les sépare toujours. */
body[data-ecran="activite"] .agent-lu {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 0.7rem;
  border-left: 1px solid var(--border-color);
}
/* Les deux lignes, mention et nom, à droite du pictogramme. */
.agent-lu-textes {
  display: flex; flex-direction: column; align-items: flex-start;
  min-width: 0; line-height: 1.1;
}
body[data-ecran="activite"] .periode-lue:not(.agent-lu) { flex: 1; }
/* ⚠️ C'EST LE NOM QUI CÈDE, JAMAIS LE TAUX : dans une bande étroite, le
   taux se coupait en « 79… » (vu le 23/09). Le pictogramme et le taux
   gardent leur largeur, le nom prend les points de suspension. */
#agent-lu-valeur {
  display: flex;
  align-items: center;
  font-size: var(--taille-sous-titre);
  font-weight: 700;
  white-space: nowrap;
  min-width: 0;
  max-width: 100%;
}
#agent-lu-nom { overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.agent-lu-taux { flex: none; }
.agent-lu-taux { color: var(--texte-bleu); margin-left: 0.7rem; }
/* Le pictogramme de l'atelier, devant le nom : à la hauteur du texte, pour
   que la bande garde ses 35 px. */
.agent-lu-logo:empty { display: none; }
/* Il occupe la hauteur des deux lignes, sans la dépasser : la bande garde
   ses 35 px (mesurés le 23/09). */
.agent-lu-logo { display: inline-flex; flex: none; }
.agent-lu-logo .carre-domaine { width: 2.1rem; height: 2.1rem; }
/* Sur une ligne, toujours : repliée, la mention épaississait la bande. */
.agent-lu .cle { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }

/* ---- le rail des domaines, à droite de la scène ------------------------- */
.rail-domaines {
  position: absolute;
  top: var(--dessous-pilule);
  right: 1rem;
  bottom: 0.9rem;
  width: var(--largeur-rail);
  z-index: 1100;
  display: flex;
  flex-direction: column;
  padding: 0.7rem 0.45rem;
  background: var(--bg-primary);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-flottant);
  box-shadow: var(--shadow-flottant);
  /* ⚠️ IL DÉFILE PLUTÔT QUE DE DÉBORDER. Sept domaines tiennent en 547 px,
     mais douze en demanderaient 780, et la scène n'en offre que 650 sur un
     portable. Un rail qui déborderait sortirait ses derniers boutons de
     l'écran, ce qui est exactement ce que cette disposition doit empêcher. */
  overflow-y: auto;
}

.rail-titre {
  margin: 0 0 0.5rem;
  text-align: center;
  font-size: var(--taille-mention);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--text-secondary);
}

.bouton-domaine {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  /* ⚠️ PLUS DE REMBOURRAGE HORIZONTAL, ET C'EST CE QUI REND LEUR TAILLE AUX
     CARRÉS. Le commentaire de `--largeur-rail` annonce « carrés de domaine à
     64 px », la forme choisie le 15/09 ; mesurés le 17/09 ils faisaient
     53,8 px, les 0,3 rem de chaque côté prenant 9,6 px sur une colonne de
     63,4. Les retirer rend le carré à 63,4 px, donc à la taille arbitrée, et
     laisse enfin la place à « Événement », qui demande 60,3 px et se coupait
     en « Événeme / nt ». Le rail, lui, NE S'ÉLARGIT PAS : ses 9,25 rem ont
     été choisis contre 11, mesure à l'appui, pour tenir sur un 1366 x 768. */
  padding: 0.5rem 0 0.45rem;
  border: 0;
  background: none;
  font: inherit;
  color: var(--text-primary);
  cursor: pointer;
  text-align: center;
  transition: transform .15s cubic-bezier(.2, .8, .3, 1);
  /* Un élément de grille porte `min-width: auto` : sans ce zéro, il repousse
     sa colonne à la largeur de son libellé et `minmax(0, 1fr)` ne sert à rien. */
  min-width: 0;
}

.carre-domaine {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  aspect-ratio: 1;
  border-radius: 16px;
  /* ⚠️ LE CARRÉ EST BLANC, arbitré par Victor le 15/09 entre le carré plein
     pastel et celui-ci. C'est un liseré teinté, pas un lavis, qui dit le
     domaine, et la teinte vient de la base (db/80). */
  background: var(--bg-primary);
  border: 2px solid color-mix(in srgb, var(--teinte-domaine) 30%, white);
  color: var(--teinte-domaine);
  box-shadow: var(--shadow-flottant);
  transition: box-shadow .15s ease;
}
.carre-domaine svg { width: 60%; height: 60%; }
/* L'émoji de repli, pour une valeur sans pictogramme embarqué : il occupe la
   même place, sans prétendre à la même finesse. */
.emoji-domaine { font-size: var(--taille-titre); line-height: 1; }

/* ⚠️ LE BOUTON SE SOULÈVE, IL NE SE CERNE PAS. Victor le 15/09 : « j'aime
   quand les tuiles sont encore plus responsives (elles se déplacent un peu,
   effet d'ombre). Je n'aime pas le contour comme réponse. » */
.bouton-domaine:hover { transform: translateY(-3px); }
.bouton-domaine:hover .carre-domaine { box-shadow: var(--shadow-flottant), 0 10px 22px rgba(16, 24, 40, .13); }
/* Le focus clavier GARDE son contour : c'est la seule chose qui dise où l'on
   est au clavier, et le RGAA 10.7 l'exige. Une ombre n'y suffit pas. */
.bouton-domaine:focus-visible { outline: 3px solid var(--texte-bleu); outline-offset: 2px; border-radius: 12px; }

/* ⚠️ LE LIBELLÉ RESTE AU PLANCHER DE L'ÉCHELLE, 0,75 rem. Victor veut un texte
   « tout petit » ; la séance du 14/09 a fait MONTER 210 textes pour tenir six
   crans, dont le plus bas vaut 12 px. Le petit se fait donc par le CONTRASTE,
   l'icône occupant 60 % d'un carré qui remplit la colonne. */
.nom-domaine {
  font-size: var(--taille-mention);
  font-weight: 500;
  line-height: 1.15;
  color: var(--text-muted);
  /* Le libellé vient de la base : « Moyens Généraux (Autres) » y tient déjà en
     trois lignes. Il doit pouvoir se couper DANS un mot, sinon « Logistique »
     seul suffit à repousser sa colonne. On ne tronque pas : un domaine se
     reconnaît à son nom entier, et le rail a la hauteur pour le porter. */
  overflow-wrap: anywhere;
}

/* ⚠️ ÉTEINT, LE BOUTON PASSE EN NUANCES DE GRIS, demandé par Victor le 15/09,
   et il perd aussi son ombre : la couleur ne dit donc pas l'état à elle seule,
   et le libellé reste lisible (RGAA 3.1). */
.bouton-domaine[aria-pressed="false"] { filter: grayscale(1); opacity: .5; }
.bouton-domaine[aria-pressed="false"] .carre-domaine,
.bouton-domaine[aria-pressed="false"]:hover .carre-domaine { box-shadow: none; }

@media (prefers-reduced-motion: reduce) {
  .bouton-domaine { transition: box-shadow .15s ease; }
  .bouton-domaine:hover { transform: none; }
}

/* L'icône du domaine, telle que la base la donne. line-height: 1 l'empêche de
   creuser la hauteur de la ligne, que les pictogrammes dépassent volontiers. */
/* 1,25 rem depuis le 08/09, pour rester à l'échelle de la ligne qui a grandi.
   line-height: 1 l'empêche de creuser la hauteur, que les pictogrammes
   dépassent volontiers. */
.icone-domaine { font-size: var(--taille-sous-titre); line-height: 1; }

.bouton-discret {
  border: 1px solid var(--border-champ);
  background: var(--bg-primary);
  color: var(--text-secondary);
  border-radius: var(--radius-sm);
  padding: 0.35rem 0.7rem;
  font-size: var(--taille-courant);
}

.bouton-discret:hover { background: var(--bg-tertiary); color: var(--text-primary); }

/* ---- les vues, dans la pilule ------------------------------------------ */
/* Trois boutons, et l'actif se REMPLIT au lieu de se souligner. Un soulignement
   suppose une barre : il dit « je suis sous cette rubrique » et ne se lit que
   par rapport à ses voisins. Dans une pilule, où les trois vues tiennent en
   180 px, le remplissage se voit d'un coup d'œil et survit au niveau de gris.
   RGAA 3.1 : l'état est aussi porté par aria-selected, posé par ouvrirEcran(). */

.onglets { display: flex; gap: 0.15rem; }

.onglet {
  border: 0;
  background: transparent;
  color: var(--text-secondary);
  padding: 0.3rem 0.7rem;
  font-size: var(--taille-courant);
  font-weight: 500;
  border-radius: 999px;
  white-space: nowrap;
}

.onglet:hover { color: var(--text-primary); background: var(--bg-secondary); }

.onglet.actif {
  background: var(--accent-primary);
  color: #fff;
  font-weight: 600;
}

/* ---- la question, en tête du volet ------------------------------------- */
/* ---- la question, en tête du volet ------------------------------------- */

.volet-question {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  padding-bottom: 0.85rem;
  border-bottom: 1px solid var(--border-color);
}

/* Un intertitre, pas un titre de page : il nomme la rangée sans prendre la
   place d'un h1. En capitales espacées et en petit, comme la maquette. */
.volet-titre {
  margin: 0;
  font-size: var(--taille-mention);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.ligne-puces { display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: center; }

/* LA PUCE. Elle dit ce qu'elle règle ET sa valeur, fermée : « Domaines · 3 sur
   5 ». C'est la moitié de la règle « jamais un filtre invisible ». */
.puce {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.32rem 0.7rem;
  border: 1px solid var(--border-champ);
  border-radius: 999px;
  background: var(--bg-primary);
  color: var(--text-secondary);
  font-size: var(--taille-courant);
  line-height: 1.3;
}

/* ---- le document PDF, 24/09/2026 -------------------------------------------- */
/* Un pictogramme seul, sans texte (Victor), à côté de « Autre période ». Il ne
   paraît que sur la fiche d'un site, sur le TOTAL et sur l'écran Agents quand
   un agent est affiché : ailleurs, il n'y a pas de document à produire, et un
   bouton qui ne fait rien fait douter des autres. « Tout le service » n'a pas
   de document, c'est celui d'une personne. Pendant la préparation, aria-busy
   le grise et bloque un second clic. */
.puce-document { padding: 0.28rem 0.5rem; color: var(--accent-primary); }
.puce-document svg { width: 1.25rem; height: 1.25rem; display: block; }
.puce-document[aria-busy="true"] { opacity: .5; cursor: progress; }
body:not([data-ecran="site"]):not([data-ecran="parc"]):not([data-ecran="activite"]) .puce-document { display: none; }
body[data-ecran="activite"]:has(#panneau-activite[data-vue="service"]) .puce-document { display: none; }

.puce:hover { background: var(--bg-secondary); color: var(--text-primary); }
.puce b { font-weight: 700; color: var(--text-primary); }
.puce-cle { color: var(--text-muted); }
.puce .chevron { font-size: var(--taille-mention); opacity: 0.6; }

/* Ouverte : la puce se remplit, comme l'onglet actif de la pilule. C'est le
   seul lien visuel entre le bouton et le panneau qu'il vient de déplier. */
.puce.ouverte {
  background: var(--text-primary);
  border-color: var(--text-primary);
  color: var(--bg-primary);
}
.puce.ouverte b, .puce.ouverte .puce-cle { color: var(--bg-primary); }

/* ⚠️ CONTRAINTE : une valeur qui n'est plus celle par défaut. Le trait est
   porté par la BORDURE et par le libellé, jamais par la seule couleur : la
   puce écrit « 3 sur 5 » ou « exclus » en toutes lettres (RGAA 3.1). */
.puce.contrainte { border-color: var(--accent-primary); }
.puce.contrainte:not(.ouverte) b { color: var(--texte-bleu); }

/* « + critère » : en pointillés, parce qu'il n'est pas un réglage posé mais une
   invitation à en poser un. */
.puce-ajout {
  border-style: dashed;
  border-color: var(--accent-primary);
  color: var(--texte-bleu);
  background: var(--accent-light);
}
.puce-ajout:hover { color: var(--texte-bleu); background: var(--accent-light); }

/* LE PANNEAU d'une puce. Il POUSSE la liste vers le bas au lieu de la
   recouvrir : c'est ce qui permet de se passer de fenêtre modale, donc du
   piège à focus et de l'attribut inert qui avaient figé l'écran le 28/08. */
.choix {
  padding: 0.7rem 0.8rem;
  background: var(--bg-secondary);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  /* Quatorze axes dépliés dépasseraient la hauteur du volet : le panneau
     défile en lui-même, la question et les totaux restent en place. */
  max-height: 22rem;
  overflow-y: auto;
}
/* UN FOND TRÈS CLAIR DERRIÈRE LES CHAMPS, pour que les champs BLANCS s'en
   détachent. Retour de Victor, 25/08 : ils se fondaient dans l'en-tête, lui
   aussi blanc — un champ sans contraste ne se lit pas comme un champ, et l'on
   ne devine pas qu'on peut y écrire. */
.groupe-periode {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: var(--taille-courant);
  color: var(--text-secondary);
  background: var(--bg-tertiary);
  padding: 0.3rem 0.55rem;
  border-radius: var(--radius);
}

/* ⚠️ DANS LE VOLET, LA PÉRIODE PASSE À LA LIGNE PLUTÔT QUE DE DÉFILER.
   Relevé par Victor le 08/09 : « dans la fenêtre du choix de la période, la
   case des années complètes sort de la zone, ce n'est pas joli ».

   La cause n'était pas une marge de trop. Deux champs de date natifs mesurent
   à eux seuls près de 300 px dans Chrome, sans qu'on puisse les rétrécir en
   dessous ; ajouter la liste des années dépassait les 460 px du volet, et le
   panneau se donnait une barre de défilement horizontale. Resserrer n'aurait
   fait que reculer l'échéance : la largeur des champs de date ne nous
   appartient pas.

   On laisse donc la ligne se replier, et la liste des années descend d'un
   cran. Elle y gagne : c'est un raccourci, pas une troisième borne. */
.choix .groupe-periode { flex-wrap: wrap; }
.choix .groupe-periode input { min-width: 0; }
/* Elle prend toute la largeur sur sa nouvelle ligne, et la marge qui la
   décalait du « au » n'a plus lieu d'être. */
.choix .choix-annee { flex: 1 1 100%; margin-left: 0; }
/* Et le panneau ne défile plus que verticalement. Si quelque chose y dépassait
   encore en largeur, c'est le contenu qu'il faudrait corriger, pas la vue. */
.choix { overflow-x: hidden; }

/* ---- « Autre période » : une fenêtre au milieu, pas un panneau ----------- */
/* ⚠️ LE PANNEAU S'OUVRAIT SOUS UNE PASTILLE DE DROITE, EN PLEINE LARGEUR ET
   ALIGNÉ À GAUCHE. Victor le 17/09 : « il est déroutant que le bouton soit à
   droite et les deux lignes qui s'ouvrent soient sur toute la longueur et
   alignées à gauche. Je préfère une fenêtre au milieu de l'écran, pas trop
   large. »

   ⚠️ ET ELLE NE VALIDE RIEN, PARCE QU'IL N'Y A RIEN À VALIDER. Les deux champs
   agissent en changeant, comme les domaines et les logements : refermer, c'est
   seulement cesser de regarder la commande. Victor le dit ainsi, « cela vaut
   validation de la période », et le code le tient déjà sans bouton.

   `position: fixed` et non `absolute` : la bande porte un `z-index` et un
   `overflow` qui enfermeraient la fenêtre dans ses 81 px de haut. */
#volet-choix-periode:not([hidden]),
#volet-choix-mesure:not([hidden]),
#fenetre-affiner:not([hidden]) {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 2000;
  width: min(26rem, calc(100vw - 2rem));
  max-height: none;
  padding: 1.1rem 1.2rem;
  background: var(--bg-primary);
  border-radius: var(--radius-flottant);
  box-shadow: var(--shadow-flottant), 0 24px 60px rgba(16, 24, 40, .22);
}
/* Le voile. Il assombrit sans flouter le texte de la fenêtre : le flou se pose
   sur CE QUI EST DERRIÈRE, jamais sur le panneau, sinon le contraste du texte
   dépendrait de ce qui passe dessous et le RGAA 3.2 ne serait plus tenable. */
#volet-choix-periode:not([hidden])::before,
#volet-choix-mesure:not([hidden])::before,
#fenetre-affiner:not([hidden])::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background: rgba(15, 23, 42, .28);
  backdrop-filter: blur(3px);
}
#fenetre-affiner:not([hidden]) {
  /* ⚠️ CELLE-CI PORTE ONZE AXES ET LEURS VALEURS, pas deux champs de date.
     Elle est plus large et elle defile en elle-meme, sinon elle sortirait
     de l ecran par le bas. */
  width: min(40rem, calc(100vw - 2rem));
  max-height: min(34rem, calc(100vh - 4rem));
  overflow-y: auto;
}
#volet-choix-periode:not([hidden]) .groupe-periode {
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* Les champs restent BLANCS : c'est leur contraste avec le fond clair qui les
   fait exister. Le navigateur le fait déjà par défaut — on l'écrit pour ne pas
   dépendre de ce défaut le jour où le fond changera. */
.groupe-periode input { background: var(--bg-primary); }

/* Sur grand écran, « Du … au … » précède le sélecteur sur la même ligne : le
   repère est là, et cette étiquette ferait un mot de plus dans une barre déjà
   dense. Le bloc @media la révèle, là où le sélecteur descend d'une ligne. */
.etiquette-annee { display: none; }


.groupe-periode input,
.choix-annee {
  border: 1px solid var(--border-champ);
  border-radius: var(--radius-sm);
  padding: 0.3rem 0.45rem;
  color: var(--text-primary);
}

.choix-annee {
  background: var(--bg-primary);
  font-size: var(--taille-courant);
  margin-left: 0.35rem;
}

/* ---- fraîcheur des données --------------------------------------------- */
/* Deux lignes serrées, comme dans RégiVision : l'intitulé s'efface, la date
   se lit. C'est la date qu'on vient chercher, pas le mot qui l'annonce. */
.dernier-pointage {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  line-height: 1.15;
  margin-right: 0.25rem;
}

.dernier-pointage-libelle { font-size: var(--taille-mention); color: var(--text-muted); }
.dernier-pointage-date { font-size: var(--taille-courant); font-weight: 600; color: var(--text-secondary); }

/* ---- corps ------------------------------------------------------------- */

/* LA SCÈNE. Elle occupe tout, et les deux objets flottants la recouvrent en
   partie. Ce n'est pas une largeur perdue : la carte a besoin de continuité
   géographique, et le fond qui passe SOUS le volet vaut mieux qu'un fond
   tronqué net à 460 px. */
/* ⚠️ DANS LE FLUX DEPUIS LE 17/09, ET PLUS EN POSITION ABSOLUE. La coque est
   devenue une colonne flexible pour porter la barre pleine largeur : la scene
   prend ce que la barre lui laisse, sans qu aucune hauteur soit ecrite. En
   absolu avec `inset: 0` elle serait repassee SOUS la barre, et la carte aurait
   perdu --haut-tete de haut sans que rien ne le dise. */
.conteneur-principal { position: relative; inset: auto; flex: 1 1 auto; min-height: 0; }

.panneau { display: none; position: absolute; inset: 0; overflow-y: auto; }
.panneau.actif { display: block; }

/* ⚠️ TOUTES LES VUES S'ENCARTENT À DROITE DU VOLET, LA CARTE COMPRISE DEPUIS
   LE 08/09. C'est la révision de la décision 1 du 07/09, « scène pleine, volet
   flottant », arbitrée par Victor en regardant l'écran :

     « sur la page de la carte, la carte est toujours centrée, comme si la
       grande zone de gauche n'existait pas. Il faut peut-être deux fenêtres
       distinctes avec la possibilité de modifier la largeur. »

   Le diagnostic était exact et la cause mécanique : le volet flottait EN
   ABSOLU au-dessus de la scène, si bien que Leaflet voyait toute la largeur de
   la fenêtre et centrait dessus. Le centre visuel de la carte tombait donc
   sous les 460 px du volet, à chaque cadrage.

   Deux colonnes règlent cela par construction, et non par une compensation
   qu'il aurait fallu redire à chaque appel de cadrage.

   Le décalage haut laisse passer la pilule, et il vaut sa hauteur MESURÉE :
   voir --hauteur-pilule dans :root. À gauche, la largeur du volet plus la
   gouttière. */
#panneau-carte.actif,
#panneau-graphique.actif,
#panneau-activite.actif,
#panneau-site.actif,
#panneau-admin.actif {
  /* ⚠️ LA DROITE LAISSE LA PLACE AU RAIL DES DOMAINES depuis le 15/09. Il ne
     se replie jamais : « il est important que les utilisateurs sachent à tout
     moment quels sont les domaines concernés ». Il coûte 17 % de la largeur de
     la carte, et c'est le prix assumé de cette demande. */
  inset: var(--dessous-pilule) calc(1rem + var(--largeur-rail) + var(--gouttiere-volet) / 2)
         0.9rem calc(var(--largeur-volet) + var(--gouttiere-volet));
  padding: 1.25rem 1.5rem;
  background: var(--bg-primary);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-flottant);
  box-shadow: var(--shadow-flottant);
}

/* L'administration n'est pas une vue : le volet n'y est pas, et la page reprend
   toute la largeur. Ses formulaires en avaient besoin bien avant la coque. */
body[data-ecran="admin"] #panneau-admin.actif { left: 1rem; }

/* L'écran Agents n'a plus la colonne de droite depuis le 23/09 : ni les
   domaines ni les logiciels n'y commandent quoi que ce soit, le taux
   d'information portant sur tout le temps de l'agent. Victor : « on peut alors
   masquer toute la partie droite ». Le panneau reprend la place. */
body[data-ecran="activite"] .rail-domaines { display: none; }
/* ET LE VOLET DE GAUCHE NON PLUS, depuis le 23/09 : « Où va son temps »
   descend sous les graphiques, pour leur rendre la largeur (Victor). */
body[data-ecran="activite"] .barre-laterale,
body[data-ecran="activite"] .poignee-largeur { display: none; }
@media (min-width: 48.0625rem) {
  body[data-ecran="activite"] #panneau-activite.actif { right: 1rem; left: 1rem; }
  /* La bande des années suivait le bord du volet : sans volet, elle
     commençait à 460 px du bord, devant du vide. */
  body[data-ecran="activite"] .bande-commandes { left: 1rem; }
}

/* L'écran du graphique empilé PAR DOMAINE n'a pas de volet non plus, depuis
   le 23/09 : sa liste redisait le rail de droite (« la partie gauche et droite
   sont en doublon », Victor). Par atelier et par site, la liste revient avec
   le volet : elle y est la seule légende des couleurs. */
body[data-ecran="graphique"].graphique-par-domaine .barre-laterale { display: none; }
@media (min-width: 48.0625rem) {
  body[data-ecran="graphique"].graphique-par-domaine #panneau-graphique.actif { left: 1rem; }
}

/* ⚠️ ET LE JOURNAL DE MÊME, SANS QUOI IL DEVIENT ILLISIBLE SUR UN TÉLÉPHONE.
   Le décalage à gauche vaut la largeur du volet, 460 px, plus la gouttière.
   Sur un cadre de 390 px, MESURÉ en défaisant la règle : le panneau tombe à
   **32 px de large** sur 800 de haut. Il ne disparaît pas, contrairement à ce
   que ce commentaire a d'abord affirmé, et c'est presque pire : il reste là,
   en bande verticale, et rien dans le code ne paraît fautif. Aucune suite ne
   le verrait. Même piège que celui payé sur les analyses le 09/09. */
body[data-ecran="journal"] #panneau-journal.actif { left: 1rem; }

/* ⚠️ LES ANALYSES S'ENCARTENT COMME L'ADMINISTRATION, ET POUR LA MÊME RAISON.
   Elles sont le second écran sans volet : la scène occupe donc toute la
   largeur, et sans ce cadrage le contenu démarrait à (0, 0), c'est-à-dire SOUS
   l'en-tête flottant. Vu à l'écran le 09/09 : le mot « Exercice » était coupé
   par le haut de la fenêtre, et le sélecteur passait derrière la barre des
   onglets. Aucune suite ne pouvait le voir.

   ⚠️ ET LA DROITE LAISSE LA PLACE AU RAIL, depuis le 25/09. Le panneau allait
   jusqu'à 1 rem du bord, et le rail des domaines, qui commande aussi les
   analyses, se posait PAR-DESSUS : la colonne « Gaz » du tableau des fluides
   était cachée pour tout utilisateur, à 1 440 comme à 1 800 px. Vu en
   capturant l'écran pour la plaquette. Même décalage que les autres vues. */
#panneau-analyses.actif {
  inset: var(--dessous-pilule) calc(1rem + var(--largeur-rail) + var(--gouttiere-volet) / 2)
         0.9rem calc(var(--largeur-volet) + var(--gouttiere-volet));
  padding: 1.25rem 1.5rem;
  background: var(--bg-primary);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-flottant);
  box-shadow: var(--shadow-flottant);
}
body[data-ecran="analyses"] #panneau-analyses.actif { left: 1rem; }

/* ⚠️ LE JOURNAL A SON PROPRE BLOC, ET CE N'EST PAS UNE REDONDANCE. Ajouté au
   milieu de la liste partagée plus haut, il en aurait chassé « admin » de la
   fin, et le contrôle « un écran sans volet cadre son panneau » serait tombé :
   il exige que le sélecteur de l'écran commence une ligne ET soit suivi de son
   accolade, précisément pour ne pas se satisfaire de la règle « left: 1rem »
   qui, elle, ne cadre rien. Mesuré : le contrôle est tombé au premier essai.
   Un bloc à lui satisfait les deux écrans, et c'est déjà ce que font les
   analyses. */
#panneau-journal.actif {
  inset: var(--dessous-pilule) 1rem 0.9rem calc(var(--largeur-volet) + var(--gouttiere-volet));
  padding: 1.25rem 1.5rem;
  background: var(--bg-primary);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-flottant);
  box-shadow: var(--shadow-flottant);
}

/* ⚠️ LES COMPTES ONT LEUR PROPRE BLOC, POUR LA RAISON ÉCRITE JUSTE AU-DESSUS :
   ajouté au milieu de la liste partagée, le sélecteur n'aurait plus commencé
   une ligne et le contrôle « un écran sans volet cadre son panneau » serait
   tombé. C'est le troisième écran à copier ces six lignes, et c'est le prix
   d'un contrôle qui refuse de se satisfaire d'une règle qui ne cadre rien. */
#panneau-comptes.actif {
  inset: var(--dessous-pilule) 1rem 0.9rem calc(var(--largeur-volet) + var(--gouttiere-volet));
  padding: 1.25rem 1.5rem;
  background: var(--bg-primary);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-flottant);
  box-shadow: var(--shadow-flottant);
}
body[data-ecran="comptes"] #panneau-comptes.actif { left: 1rem; }

/* ⚠️ LA CARTE GARDE SON CADRE, MAIS PAS LE REMBOURRAGE DES PAGES. Une page se
   lit avec des marges ; une carte les remplit. Le débordement est coupé pour
   que les tuiles suivent l'arrondi du cadre plutôt que d'en sortir aux
   angles. */
#panneau-carte.actif { padding: 0; overflow: hidden; }

/* ---- la poignée de largeur, entre le volet et la scène ------------------
   Une vraie commande, pas un filet : elle se saisit à la souris, se déplace aux
   flèches, et annonce ce qu'elle règle. Sans le rôle et les valeurs, un lecteur
   d'écran n'aurait qu'un séparateur muet (RGAA 7.1).

   ⚠️ ELLE NE VIT QUE SUR GRAND ÉCRAN. Sur téléphone le volet est une feuille
   qui monte du bas : il n'y a pas deux colonnes, donc rien à partager. Le bloc
   du téléphone la masque. */
.poignee-largeur {
  position: absolute;
  top: var(--dessous-pilule);
  bottom: 0.9rem;
  /* Centrée dans la gouttière : la moitié de sa largeur de part et d'autre. */
  left: calc(var(--largeur-volet) + 0.9rem + var(--gouttiere-volet) / 2 - 0.4rem);
  width: 0.8rem;
  z-index: 1150;
  border: 0;
  padding: 0;
  background: none;
  cursor: col-resize;
  /* Le doigt qui la tire ne doit pas faire défiler la page. */
  touch-action: none;
}
/* Le trait se voit au survol et au focus, pas au repos : au repos, la gouttière
   suffit à séparer les deux colonnes, et un trait permanent ajouterait une
   ligne verticale à un écran qui n'en demande pas. */
.poignee-largeur::before {
  content: '';
  position: absolute;
  inset: 0 calc(50% - 1.5px);
  border-radius: 999px;
  background: transparent;
}
.poignee-largeur:hover::before,
.poignee-largeur:focus-visible::before { background: var(--accent-primary); }
.poignee-largeur:focus-visible { outline: none; }
/* Pendant le glissement, tout l'écran garde le curseur de redimensionnement et
   cesse de sélectionner du texte : sans cela, tirer la poignée surligne la
   liste des sites au passage. */
body.largeur-en-cours { cursor: col-resize; user-select: none; }

.attente {
  padding: 2rem;
  color: var(--text-muted);
  font-size: var(--taille-appuye);
}

/* ---- carte --------------------------------------------------------------- */
/* La carte occupe tout son panneau : c'est elle qu'on regarde, les commandes
   flottent par-dessus plutôt que de lui prendre de la place. */

#panneau-carte { overflow: hidden; }

/* La carte et le bandeau des interventions se PARTAGENT la hauteur : le second
   pousse la première au lieu de la recouvrir. D'où une colonne flex ici, et
   plus un simple bloc. */
#panneau-carte.actif { display: flex; flex-direction: column; overflow: hidden; }

/* Le cadre de la carte : c'est LUI qui sert de repère aux commandes posées en
   absolu (métrique, échelle, légende), et non le panneau entier — sinon elles
   flotteraient au-dessus du bandeau. */
.zone-carte { position: relative; flex: 1; min-height: 0; }

#carte {
  position: absolute;
  inset: 0;
  background: var(--bg-tertiary);
}

/* ---- le bandeau des interventions --------------------------------------- */

/* IL MONTE DEPUIS LE BAS, PAR-DESSUS LA CARTE — ET C'EST carte.js QUI LE MÈNE.

   ⚠️ NE PAS REMETTRE D'ANIMATION CSS ICI. Quatre versions successives l'ont
   écrite dans cette feuille, et quatre fois le tableau a continué d'« apparaître
   d'un coup » à l'écran. Chacune corrigeait pourtant une cause réelle :

     1. le rectangle blanc — la carte rétrécissait avant que le bandeau n'arrive
        (corrigé : il sort du flux le temps de monter, et recouvre la carte) ;
     2. le bandeau montait VIDE — la requête met ~290 ms et les soixante lignes
        tombaient d'un bloc à la fin (corrigé : on remplit avant de montrer) ;
     3. la courbe — `cubic-bezier(.2,.9,.3,1)`, taillée pour la fiche, faisait
        parcourir 76 % des 291 px en 180 ms, et doubler la durée n'allongeait
        qu'une queue invisible (corrigé : une courbe qui étale le déplacement).

   Le point commun des quatre : le mouvement vivait ici, dans la feuille de
   style, là où un onglet piloté — dont l'horloge d'animation est gelée — ne
   permet PAS de vérifier qu'il part vraiment. Il vit désormais dans
   `animerBandeau()`, où l'objet Animation est en main : durée, état et fin s'y
   lisent à la milliseconde. Sa durée est `DUREE_BANDEAU_MS`, sa courbe
   `COURBE_BANDEAU`, toutes deux dans carte.js.

   ⚠️ LA LEÇON, PLUS GÉNÉRALE QUE CE BANDEAU : ce qu'on ne peut pas observer,
   on ne peut pas le corriger — on ne peut que le supposer, et quatre fois de
   suite si l'on s'entête. */

.interventions-filtre { display: flex; align-items: center; gap: 0.35rem; margin-left: auto; }
.interventions-filtre > span { font-size: var(--taille-mention); font-weight: 600; color: var(--text-secondary); }
.interventions-filtre select {
  border: 1px solid var(--border-champ);
  border-radius: var(--radius-sm);
  padding: 0.25rem 0.4rem;
  font-size: var(--taille-courant);
  background: var(--bg-primary);
  color: var(--text-primary);
}

/* Le défilement appartient au CORPS, jamais à tout le bandeau : l'en-tête doit
   rester lisible quand on descend dans le tableau, sinon on ne sait plus de
   quel site on lit les lignes. */
.interventions-corps { flex: 1; min-height: 0; overflow: auto; }

/* Une grille, pas un <table> : les colonnes gardent la même largeur d'une ligne
   à l'autre sans que le navigateur ait à mesurer tout le contenu. */
.interventions-ligne {
  display: grid;
  /* Neuf colonnes depuis le 30/08 : Personnel et Fournitures rejoignent la
     grille, et « État » cède 1 rem pour contenir l'élargissement. Sous la
     largeur totale, .interventions-corps fait défiler horizontalement. */
  grid-template-columns: 7.5rem minmax(12rem, 1fr) 9rem 6rem 6rem 5.5rem 6.5rem 6.5rem 7rem;
  align-items: center;
  gap: 0.75rem;
  padding: 0.35rem 0.9rem;
  font-size: var(--taille-courant);
  border-bottom: 1px solid var(--bg-tertiary);
}

.interventions-ligne:not(.interventions-entete-ligne):hover { background: var(--accent-light); }

.interventions-entete-ligne {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--bg-primary);
  border-bottom: 1px solid var(--border-color);
  padding: 0;
}

.col-titre {
  border: 0;
  background: none;
  text-align: left;
  padding: 0.45rem 0;
  font-size: var(--taille-mention);
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.col-titre:first-child { padding-left: 0.9rem; }
.col-titre:last-child { padding-right: 0.9rem; }
.col-titre:hover { color: var(--text-primary); }
.col-titre.trie { color: var(--texte-bleu); }
.col-titre.col-nombre { text-align: right; }

.col-ref { font-variant-numeric: tabular-nums; color: var(--texte-bleu); font-weight: 600; }
.col-libelle { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.col-etat { color: var(--text-secondary); }
.col-date { color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.col-nombre { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; }

.interventions-cliquable { cursor: pointer; }
.interventions-cliquable:focus-visible { outline: 2px solid var(--accent-primary); outline-offset: -2px; }

/* ---- le pied d'une liste : ce que la liste dit d'elle-même --------------- */
/*
   L'en-tête énonce le SITE — 860 interventions, 145 620 €. Ce pied énonce
   l'ÉCRAN — combien sont chargées, combien restent. Tant que les deux nombres
   diffèrent, la différence doit être ÉCRITE : c'est ce qui manquait, et c'est
   pourquoi on lisait 200 lignes en croyant les lire toutes.

   Il vit DANS le corps défilant, en dernier enfant : on le rencontre en
   arrivant au bout de la liste, à l'endroit exact où la question « c'est
   tout ? » se pose. Le coller en bas du bandeau l'aurait rendu permanent, donc
   ignoré.

   ⚠️ CES CLASSES SERVENT DEUX LISTES, et c'est voulu : le tableau des
   interventions, qui charge par pages depuis le serveur, et le tableau par
   équipement, coupé à trente lignes à l'écran depuis le 06/09. Elles portaient
   le préfixe `interventions-` tant qu'il n'y en avait qu'une. Deux jeux de
   styles jumeaux auraient fini par diverger d'un demi-caractère, et l'on aurait
   cherché la cause dans les données.

   ⚠️ --text-secondary, et surtout pas --text-muted : ce dernier rend 2,56:1 sur
   blanc, sous le seuil du critère RGAA 3.2. C'est le défaut relevé par l'audit
   du 26/08 ; on ne l'ajoute pas une fois de plus en attendant de le corriger
   partout (P1.2).
*/
.liste-pied {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.5rem 0.9rem;
  padding: 0.7rem 0.9rem 0.9rem;
  font-size: var(--taille-courant);
  color: var(--text-secondary);
}

.liste-pied-texte { flex: 1 1 auto; min-width: 0; }

/* Une action, pas un lien : elle charge des données, elle ne navigue pas. */
.liste-pied-action {
  flex: 0 0 auto;
  border: 1px solid var(--accent-primary);
  border-radius: var(--radius-sm);
  background: var(--bg-primary);
  color: var(--texte-bleu);
  font-size: var(--taille-courant);
  font-weight: 600;
  /* 2,25 rem : atteignable au doigt sur le bandeau du téléphone, où ce bouton
     est le seul moyen d'aller chercher ce qui manque. */
  min-height: 2.25rem;
  padding: 0.4rem 0.8rem;
}

.liste-pied-action:hover { background: var(--accent-light); }
.liste-pied-action:focus-visible { outline: 2px solid var(--accent-primary); outline-offset: 2px; }

/* ---- la fiche d'une intervention ---------------------------------------- */

/* ⚠️ ACCROCHÉE EN HAUT, ET NON CENTRÉE.
   Centrée, la boîte se replace verticalement à chaque changement de contenu :
   une fiche courte remonte, une longue redescend, et les chevrons ◀ ▶ se
   dérobent sous le curseur dès qu'on enchaîne les clics. Pire, le détail par
   agent arrive APRÈS le reste — la boîte sautait donc une seconde fois, toute
   seule, une demi-seconde après l'ouverture.
   Accrochée en haut, elle ne grandit que vers le bas : l'en-tête ne bouge
   jamais, quelle que soit la fiche. */
/* ⚠️ 1300, ET C'EST AU-DESSUS DE LA PILULE, QUI EST À 1200.
   Relevé par Victor le 08/09, capture à l'appui : « attention, ça passe sous le
   header ». La fiche d'une intervention montait à 1100 et passait donc sous la
   pilule, qui la recouvrait en haut à droite.

   ⚠️ CE N'ÉTAIT PAS QU'UN DÉFAUT D'ALLURE. La fiche rend l'arrière-plan `inert`
      à l'ouverture, précisément pour qu'on ne puisse plus agir dessus : la
      pilule restait donc PLEINEMENT VISIBLE, non voilée, au-dessus d'un écran
      volontairement neutralisé. Elle avait l'air cliquable et ne l'était pas.
      Une commande visible qui ne commande rien, la règle du 08/09.

   Le voile assombrissant fait partie de la modale : le monter monte les deux,
   et la pilule passe donc sous le voile comme le reste de l'écran. */
.modale {
  position: fixed;
  inset: 0;
  z-index: 1300;
  display: grid;
  justify-items: center;
  align-items: start;
  padding: 4vh 1.5rem 1.5rem;
}

.modale-fond { position: absolute; inset: 0; background: rgba(15, 23, 42, 0.45); }

.modale-boite {
  position: relative;
  width: min(46rem, 100%);
  /* Bornée par la place réellement disponible sous le bord haut, et non par la
     hauteur de la fenêtre : sinon la boîte déborde de 4vh par le bas. */
  max-height: calc(96vh - 4vh);
  display: flex;
  flex-direction: column;
  background: var(--bg-primary);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
  /* Le liseré vert de RégiVision, qui signale « fiche ouverte » d'un coup d'œil
     même quand la modale est à moitié hors écran sur un petit portable. */
  border-left: 4px solid var(--accent-green);
}

.modale-entete {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.7rem 1rem;
  border-bottom: 1px solid var(--border-color);
  background: var(--bg-secondary);
  flex-shrink: 0;
}

.modale-nav {
  border: 1px solid var(--border-champ);
  background: var(--bg-primary);
  color: var(--text-secondary);
  border-radius: var(--radius-sm);
  padding: 0.2rem 0.5rem;
  font-size: var(--taille-mention);
}
.modale-nav:hover:not(:disabled) { background: var(--accent-light); color: var(--texte-bleu); }
.modale-nav:disabled { opacity: 0.35; cursor: default; }

.modale-rang { font-size: var(--taille-courant); color: var(--text-muted); font-variant-numeric: tabular-nums; }
/* ⚠️ ELLE PORTE UN PICTOGRAMME DEPUIS LE 21/09/2026, plus seulement un émoji.
   La taille de police reste : c'est elle qui dimensionne l'émoji de repli.
   Le dessin, lui, a besoin d'une taille en propre, un SVG sans dimensions
   s'affichant à la taille par défaut du navigateur, soit 300 x 150 px. */
.modale-icone {
  font-size: var(--taille-sous-titre);
  margin-left: 0.3rem;
  display: inline-flex;
  align-items: center;
}
.modale-icone svg { width: 1.4em; height: 1.4em; }

/* LA PASTILLE DE DOMAINE, dans la premiere colonne du tableau des
   interventions en regie. Meme dessin que le rail, a la taille d'une ligne.

   ⚠️ UNE TAILLE EN PROPRE, comme pour la fiche : un SVG sans dimensions
      s'affiche a 300 x 150 px. Ici la cellule ferait toute la hauteur de la
      feuille. */
.pastille-domaine { display: inline-flex; align-items: center; }
.pastille-domaine svg { width: 1.5rem; height: 1.5rem; }
/* Le repli du repli : ni dessin ni emoji, on ecrit le code plutot que de
   laisser une cellule vide qui se lirait comme un domaine absent. */
.pastille-domaine-code {
  font-size: var(--taille-mention);
  font-weight: 700;
  color: var(--text-muted);
}
.modale-titre { font-size: var(--taille-sous-titre); font-weight: 700; }

.modale-fermer {
  margin-left: auto;
  border: 0;
  background: none;
  color: var(--text-muted);
  font-size: var(--taille-sous-titre);
  padding: 0.2rem 0.4rem;
  border-radius: var(--radius-sm);
}
.modale-fermer:hover { color: var(--text-primary); background: var(--bg-tertiary); }

/* Le défilement porte sur le CORPS : l'en-tête garde la référence et les
   chevrons à portée, même au bas d'un détail de cinquante lignes. */
.modale-corps { padding: 1rem 1.2rem 1.4rem; overflow-y: auto; }

.fiche-grille {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 0.9rem 1.5rem;
}

.fiche-champ { display: flex; flex-direction: column; gap: 0.15rem; margin-bottom: 0.2rem; }
.fiche-large { margin-bottom: 1.1rem; }

.fiche-intitule {
  font-size: var(--taille-mention);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.fiche-valeur { font-size: var(--taille-appuye); color: var(--text-primary); }
.fiche-fort .fiche-valeur { font-size: var(--taille-titre); font-weight: 700; }
.fiche-bleu .fiche-valeur { color: var(--texte-bleu); }
.fiche-ambre .fiche-valeur { color: var(--texte-ambre); }

.fiche-section { margin-top: 1.2rem; padding-top: 1rem; border-top: 1px solid var(--border-color); }

.fiche-titre-section {
  font-size: var(--taille-mention);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-secondary);
  margin-bottom: 0.7rem;
}

.fiche-avertissement {
  font-size: var(--taille-courant);
  line-height: 1.4;
  color: #b45309;
  background: var(--accent-amber-light);
  border-radius: var(--radius-sm);
  padding: 0.5rem 0.6rem;
  margin-bottom: 0.7rem;
}

.fiche-agents { background: var(--bg-secondary); border-radius: var(--radius-sm); padding: 0.7rem 0.8rem; }

.agent-bloc { border-bottom: 1px solid var(--border-color); }
.agent-bloc:last-child { border-bottom: 0; }

.agent-ligne {
  display: grid;
  grid-template-columns: 1.2rem 1fr auto;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.4rem 0.2rem;
  border: 0;
  background: none;
  text-align: left;
  font-size: var(--taille-courant);
}
.agent-ligne:hover { background: var(--accent-light); border-radius: var(--radius-sm); }
/* 0.7rem rendait le chevron déplié impossible à distinguer d'un tiret. */
.agent-chevron { color: var(--text-muted); font-size: var(--taille-courant); line-height: 1; }
.agent-nom { color: var(--text-primary); }
.agent-heures { color: var(--texte-bleu); font-weight: 600; font-variant-numeric: tabular-nums; }

.agent-jours { padding: 0.1rem 0 0.5rem 1.7rem; border-left: 2px solid var(--border-color); margin-left: 0.5rem; }
.agent-jour {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  font-size: var(--taille-courant);
  color: var(--text-secondary);
  padding: 0.12rem 0.2rem;
  font-variant-numeric: tabular-nums;
}

/* Le bouton d'ouverture, au pied de la fiche d'un site. */
.popup-action {
  display: block;
  width: 100%;
  margin-top: 0.5rem;
  padding: 0.35rem 0.5rem;
  border: 1px solid var(--accent-primary);
  border-radius: var(--radius-sm);
  background: var(--bg-primary);
  color: var(--texte-bleu);
  font-size: var(--taille-courant);
  font-weight: 600;
  text-align: left;
}
.popup-action:hover { background: var(--accent-light); }

/* ⚠️ `.commandes-carte` A DISPARU (P2.8).
   C'était un panneau flottant en haut à droite de la carte, en z-index 800 —
   au-dessus des 700 de Leaflet, sous les 1000 du bandeau d'erreur. Il portait
   « Métrique » et « Masquer sites génériques », deux commandes qui agissent sur
   la carte ET sur la liste, posées à l'endroit qui n'en montrait qu'une moitié.
   Elles sont descendues dans `.barre-liste`, au-dessus de la liste. Plus rien
   ne flotte au-dessus de la géographie que les contrôles de Leaflet. */

/* ⚠️ LA COMPENSATION DU VOLET A ÉTÉ RETIRÉE LE 13/09/2026, ET IL FAUT SAVOIR
   POURQUOI ELLE A EXISTÉ, SANS QUOI ELLE REVIENDRA.

   Elle disait : `.leaflet-top.leaflet-left { margin-left: calc(var(--largeur-volet)
   + var(--gouttiere-volet)) }`. Elle était juste le jour où elle a été écrite :
   les commandes de Leaflet passaient alors SOUS le volet et ne se voyaient
   plus, mesurées à 0,0, entièrement recouvertes.

   Ce qui a changé : la coque du 07/09 a décalé LE PANNEAU LUI-MÊME. Voir plus
   haut, `#panneau-carte.actif` porte déjà
   `inset: 4rem 1rem 0.9rem calc(var(--largeur-volet) + var(--gouttiere-volet))`.
   Le décalage était donc appliqué DEUX FOIS, et les commandes flottaient au
   milieu de la carte au lieu d'en longer le bord gauche.

   ⚠️ ET PERSONNE NE POUVAIT LE VOIR AUTREMENT QU'À L'ŒIL. Rien ne débordait,
   rien ne se recouvrait, aucune suite ne mesurait cet écart, et les deux
   règles sont justes chacune de son côté : c'est leur SOMME qui est fausse.
   Trouvé par Victor sur sa fenêtre, le 13/09.

   Mesuré avant et après, sur 1 891 px, en reproduisant le DOM avec la vraie
   feuille de style :

       avant   panneau à 489 px, pile de commandes à 978   écart 490 px
       après   panneau à 489 px, pile de commandes à 490   écart   1 px

   Et sur 390 px, rien ne bouge : la règle du téléphone la neutralisait déjà. */

/* L'échelle des cercles, dans la pile de commandes de Leaflet en haut à gauche.
   Elle reprend le fond blanc, le rayon et l'ombre des contrôles voisins pour
   qu'on la lise comme l'un d'eux, et non comme un panneau posé par-dessus. */
/* À DROITE DU « + », et non sous les autres commandes.
   Leaflet empile ses contrôles en les faisant flotter les uns sous les autres
   (float: left ; clear: both). On sort donc celui-ci du flot : posé en absolu
   dans le même cadre, il se place à côté du zoom au lieu d'attendre son tour
   dessous.

   Le sélecteur porte « .leaflet-top » pour une raison précise : la règle
   « .leaflet-top .leaflet-control { margin-top: 10px } » de Leaflet est plus
   spécifique qu'une simple classe, et elle décalait la pilule de 10 px vers le
   bas malgré un margin: 0. Mesuré, pas deviné.

   Les nombres : la barre de zoom va de 10 à 42 px, donc 50 laisse un intervalle
   de 8 px. Le « + » fait 30 px de haut à partir de 11 ; la pilule en fait 28 :
   12 la centre dessus. */
.leaflet-top .controle-echelle {
  position: absolute;
  top: 12px;
  left: 50px;
  margin: 0;
  float: none;
  clear: none;
  background: var(--bg-primary);
  border-radius: var(--radius-sm);
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.4);
  padding: 0.3rem 0.6rem;
}

.commande-echelle {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  cursor: pointer;
}

.commande-echelle > span {
  font-size: var(--taille-mention);
  font-weight: 600;
  color: var(--text-secondary);
  white-space: nowrap;
}

/* Une largeur fixe, et non 100 % : ce contrôle n'a pas de conteneur qui lui
   donne sa taille — il est aussi large que ce qu'on y met. */
.commande-echelle input[type="range"] {
  accent-color: var(--accent-primary);
  width: 8rem;
  cursor: pointer;
}

/* Les deux !important d'avant n'existaient que pour défaire l'empilement ;
   les étiquettes étant désormais en ligne par défaut, il n'y a plus rien à
   forcer. */
.commande-case input { accent-color: var(--accent-primary); cursor: pointer; }
.commande-case > span { font-weight: 500; }

/* La case des sites génériques (24/09/2026, variante D2 de Victor) : cochée,
   ils sont affichés ; son texte suit l'état. Elle remplace l'interrupteur à
   glissière du 23/09, dont le texte ne changeait pas. Plus grande que la case
   ordinaire : l'arbitrage 15 du 23/09 voulait la commande « bien plus
   visible », et cette exigence-là demeure. */
.commande-case.case-generiques { gap: 0.55rem; font-size: var(--taille-courant); }
.case-generiques input { width: 1.1rem; height: 1.1rem; margin: 0; flex: none; }
.case-generiques input:focus-visible { outline: 2px solid var(--accent-primary); outline-offset: 2px; }

.commande-echelle select {
  font: inherit;
  font-size: var(--taille-mention);
  color: var(--text-primary);
  background: var(--bg-primary);
  border: 1px solid var(--border-color, rgba(0, 0, 0, 0.2));
  border-radius: var(--radius-sm);
  padding: 0.1rem 0.25rem;
  cursor: pointer;
}

/* La valeur qui trie la liste, quand ce n'est pas une des trois colonnes
   fixes. Discrète mais lisible : c'est elle qui explique l'ordre. */
.chiffre-tri { font-weight: 700; color: var(--accent-primary); }


.legende-carte {
  position: absolute;
  bottom: 0.75rem;
  left: 0.75rem;
  z-index: 800;
  background: var(--bg-primary);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-sm);
  padding: 0.35rem 0.6rem;
  font-size: var(--taille-courant);
  color: var(--text-secondary);
}

/* ⚠️ MONTÉ AU CRAN DU VOILE LE 21/09/2026, comme `.attente-en-ligne` : voir le
      motif mesuré au bloc « l'attente ». Le `inline-flex` vient avec la taille
      et non pour elle : à 24 px d'icône contre 20 px de texte, un alignement
      sur la ligne de base décalait visiblement l'un par rapport à l'autre. */
.attente-carte {
  position: absolute;
  top: 0.75rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 900;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  background: var(--accent-light);
  color: var(--texte-bleu);
  border-radius: var(--radius-sm);
  padding: 0.35rem 0.8rem;
  font-size: var(--taille-sous-titre);
  font-weight: 600;
}
.attente-carte .icone-attente { font-size: var(--taille-titre); }

/* Le même bandeau, quand il porte un échec plutôt qu'une attente. Il s'efface
   tout seul au bout de quelques secondes : un fond de carte qui n'a pas pu se
   charger n'est pas une panne de l'application, et ne doit pas rester à
   l'écran comme si c'en était une. */
.attente-carte.attente-echec {
  background: var(--accent-amber-light);
  color: #b45309;
  max-width: min(34rem, 90%);
  text-align: center;
  font-weight: 500;
  line-height: 1.35;
}

/* Le cadre noir qui entourait un cercle après un clic est le contour de focus
   du navigateur. On ne le supprime pas purement et simplement : ce serait
   rendre la carte inutilisable au clavier, où ce contour est la seule chose
   qui dit où l'on se trouve. On le retire pour la souris, on le garde — et on
   l'habille — pour la navigation au clavier. */
.leaflet-container path.leaflet-interactive:focus { outline: none; }

.leaflet-container path.leaflet-interactive:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
}

/* ---- infobulle d'un site ------------------------------------------------- */

/* ---- l'étiquette de survol ---------------------------------------------- */
/* Deux niveaux de lecture : le nom, qui identifie, et la valeur, qui chiffre.
   La seconde s'efface un peu — on survole pour savoir OÙ l'on est, la valeur
   vient en complément. */
/* ---------------------------------------------------------------------------
   L'INFOBULLE DE SURVOL D'UN SITE.

   Elle ne dit que deux choses, décidé par Victor le 07/09 : le nom du site, et
   la valeur de la mesure choisie. Tout le reste attend le clic, où la fiche
   complète s'ouvre.

   ⚠️ LA HIÉRARCHIE EST INVERSÉE PAR RAPPORT À CE QU'ON ÉCRIRAIT SPONTANÉMENT.
      On survole un cercle parce qu'on veut SON NOMBRE ; le nom sert à savoir de
      qui il s'agit. Le nombre est donc gros et le nom discret, et non l'inverse.

   Le fond sombre plutôt que blanc : une infobulle claire posée sur un plan IGN
   clair se confond avec lui, et il faut alors une bordure pour l'en détacher,
   c'est-à-dire un trait de plus sur une carte qui en porte déjà beaucoup. Un
   fond sombre se détache sans bordure, sur le plan comme sur la vue aérienne.
   --------------------------------------------------------------------------- */
.leaflet-tooltip.survol-site,
.leaflet-tooltip:has(.survol-site) {
  background: rgba(22, 33, 58, 0.94);
  border: 0;
  box-shadow: 0 8px 24px -6px rgba(15, 23, 42, 0.45);
  border-radius: 12px;
  padding: 0.5rem 0.75rem;
  color: #ffffff;
}

/* La flèche de Leaflet, teintée du même fond : sans cela elle reste blanche et
   pend sous la bulle comme un défaut d'affichage. */
.leaflet-tooltip.survol-site.leaflet-tooltip-top::before,
.leaflet-tooltip:has(.survol-site).leaflet-tooltip-top::before {
  border-top-color: rgba(22, 33, 58, 0.94);
}

.survol-site {
  line-height: 1.25;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  white-space: nowrap;
}

/* Le nom : discret, en capitales espacées. C'est une étiquette, pas un titre. */
.survol-site strong {
  font-size: var(--taille-mention);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.72);
}

/* La valeur : ce qu'on est venu chercher. Les chiffres tabulaires empêchent le
   nombre de danser d'un site à l'autre quand la souris glisse. */
.survol-valeur {
  font-size: var(--taille-sous-titre);
  font-weight: 700;
  color: #ffffff;
  font-variant-numeric: tabular-nums;
}

.popup-site { min-width: 15rem; font-size: var(--taille-courant); }

.popup-titre {
  padding-bottom: 0.4rem;
  margin-bottom: 0.4rem;
  border-bottom: 1px solid var(--border-color);
  font-size: var(--taille-appuye);
}

/* Le code est un identifiant, pas un titre : il sert à retrouver le site dans
   un export, pas à le nommer. Il s'écrit donc plus petit que le nom, qui reste
   ce qu'on lit en premier. */
.popup-titre strong {
  color: var(--texte-bleu);
  font-size: var(--taille-mention);
  letter-spacing: 0.02em;
  vertical-align: 0.05em;
}

.popup-ligne {
  display: flex;
  justify-content: space-between;
  gap: 1.5rem;
  padding: 0.12rem 0;
  color: var(--text-primary);
  font-weight: 600;
}

/* PERSONNEL ET FOURNITURES NE SONT PAS DES TOTAUX DE PLUS : leur somme EST le
   coût total juste au-dessus. Les afficher au même niveau laisserait croire à
   cinq mesures indépendantes, et inviterait à les additionner toutes.
   D'où le décrochement — retrait et trait vertical, comme une accolade — plus
   parlant que le simple gris d'avant : un texte grisé se lit « indisponible »,
   pas « détail de la ligne précédente ». C'est ce que cette bulle disait par
   erreur. */
.popup-ligne.detail {
  color: var(--text-secondary);
  font-weight: 500;
  margin-left: 0.6rem;
  padding-left: 0.5rem;
  border-left: 2px solid var(--border-color);
}

.attente-en-ligne {
  background: var(--accent-light);
  color: var(--texte-bleu);
  border-radius: var(--radius-sm);
  padding: 0.3rem 0.7rem;
  font-size: var(--taille-courant);
  font-weight: 600;
}


.etiquette-code {
  font-size: var(--taille-mention);
  font-weight: 700;
  color: var(--texte-bleu);
  background: var(--accent-light);
  border-radius: var(--radius-sm);
  padding: 0.15rem 0.45rem;
}

/* ---- tuiles de chiffres, réutilisables ---------------------------------- */

.tuiles { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-bottom: 1.75rem; }

.tuile {
  flex: 1 1 9rem;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  background: var(--bg-primary);
  padding: 0.7rem 0.8rem;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.tuile-valeur { font-size: var(--taille-sous-titre); font-weight: 700; line-height: 1.2; }
.tuile-libelle { font-size: var(--taille-mention); color: var(--text-muted); }

.ton-bleu  { background: var(--accent-light); border-color: transparent; }
.ton-bleu  .tuile-valeur { color: var(--texte-bleu); }
.ton-vert  { background: var(--accent-green-light); border-color: transparent; }
.ton-vert  .tuile-valeur { color: #047857; }
.ton-ambre { background: var(--accent-amber-light); border-color: transparent; }
.ton-ambre .tuile-valeur { color: #b45309; }

/* ---- blocs -------------------------------------------------------------- */

.bloc { margin-bottom: 1.75rem; }

.bloc h2,
.bloc h3 {
  font-size: var(--taille-courant);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  margin-bottom: 0.6rem;
}

.vide { color: var(--text-muted); font-size: var(--taille-courant); padding: 0.5rem 0; }

/* ---- cartes annuelles --------------------------------------------------- */

.grille-annees {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(10.5rem, 1fr));
  gap: 0.6rem;
}

.carte-annee {
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  background: var(--bg-primary);
  padding: 0.7rem 0.8rem;
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas: 'annee jauge' 'chiffres jauge';
  gap: 0.4rem 0.7rem;
}

.carte-annee .annee {
  grid-area: annee;
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  font-size: var(--taille-appuye);
  font-weight: 700;
}

/* L'ANNÉE INCOMPLÈTE SE SIGNALE, PARCE QUE LA JAUGE INVITE À COMPARER.

   Les cartes sont alignées et portent chacune une jauge : « 2026 pèse moins que
   2025 » se lit tout seul. Or au 26/08 le dernier pointage datait du 12/06 —
   cinq mois et demi face à des années pleines, et rien ne le disait.

   Un mot, et pas une couleur d'alerte : ce n'est pas un défaut, c'est un fait.
   Le détail — jusqu'à quelle date — est dans son title. */
.annee-partielle {
  font-size: var(--taille-mention);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--texte-ambre);
  background: var(--accent-amber-light);
  border-radius: 999px;
  padding: 0.1rem 0.4rem;
  white-space: nowrap;
}

/* L'en-tête de colonnes des deux listes du Bilan. Du TEXTE, pas des boutons :
   ces listes sont ordonnées par coût et rien ne s'y réordonne — annoncer un tri
   qu'on ne rend pas serait pire que se taire. */
.ligne-liste-entete {
  background: var(--bg-secondary);
  border-bottom: 1px solid var(--border-color);
  font-size: var(--taille-mention);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--text-secondary);
}
.ligne-liste-entete:hover { background: var(--bg-secondary); }
/* La colonne du rang n a pas d intitule : sans cette regle, sa pastille grise
   laissait un rond vide au-dessus de la colonne des numeros. */
.ligne-liste-entete .rang { background: none; }
.ligne-liste-entete .cle,
.ligne-liste-entete .nombre.fort,
.ligne-liste-entete .part { color: inherit; font-weight: 700; }
.carte-annee .chiffres { grid-area: chiffres; display: flex; flex-direction: column; gap: 0.1rem; font-size: var(--taille-mention); color: var(--text-muted); }
.carte-annee .chiffres .fort { font-size: var(--taille-appuye); font-weight: 700; color: var(--text-primary); }

/* Une jauge verticale : la hauteur dit la part de l'année la plus chargée. */
.carte-annee .jauge {
  grid-area: jauge;
  width: 0.5rem;
  min-height: 3rem;
  background: var(--bg-tertiary);
  border-radius: 999px;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
}

.carte-annee .jauge span { width: 100%; background: var(--accent-secondary); border-radius: 999px; }

/* ---- listes ------------------------------------------------------------- */

.liste { border: 1px solid var(--border-color); border-radius: var(--radius-sm); overflow: hidden; }

.ligne-liste {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) 5rem 6rem 7rem;
  align-items: center;
  gap: 0.75rem;
  padding: 0.45rem 0.8rem;
  font-size: var(--taille-courant);
  border-bottom: 1px solid var(--border-color);
}

#site-operations .ligne-liste { grid-template-columns: 1.6rem 5.5rem minmax(0, 1fr) 7rem 5.5rem 7rem; }
/* Le tableau par site du parc porte deux colonnes de plus que celui par
   équipement, la surface et le coût au m². Le nom prend ce qui reste : c'est la
   seule colonne dont la largeur ne se prévoit pas. */
#parc-sites .ligne-liste { grid-template-columns: minmax(0, 1fr) 4rem 6.5rem 5rem 7rem 6rem 6.5rem; }
/* Ce qu'une colonne trouée ne dit pas d'elle-même : combien de lignes n'ont pas
   la donnée, et ce que cela change. Discrète, sous le tableau, avant le pied. */
.note-liste {
  margin: 0;
  padding: 0.5rem 0.8rem;
  font-size: var(--taille-courant);
  color: var(--text-muted);
  border-top: 1px solid var(--border-color);
}

/* Chaque ligne mène à la page du site : le curseur le dit, et le nom se
   souligne au survol comme dans le classement du volet. */
.ligne-liste-lien { cursor: pointer; }
.ligne-liste-lien:hover .libelle,
.ligne-liste-lien:focus-visible .libelle { color: var(--accent-primary); text-decoration: underline; }
.ligne-liste:last-child { border-bottom: 0; }
.ligne-liste:hover { background: var(--bg-secondary); }

.ligne-liste .libelle { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ligne-liste .cle { font-weight: 600; color: var(--texte-bleu); }
.ligne-liste .nombre { text-align: right; color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.ligne-liste .nombre.fort { color: var(--text-primary); font-weight: 600; }


/* ---- engagements et situations ------------------------------------------ */
/*
   ⚠️ CINQ COLONNES, ET LA JAUGE COMPTE POUR UNE. Le 09/09, une première
      version en portait six et coupait ses montants dans le volet : le numéro
      d'engagement est donc passé SOUS le nom du fournisseur plutôt que dans une
      colonne à lui.
*/
.liste-engagements {
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
/* ⚠️ LE FOURNISSEUR PASSE AVANT L'OBJET, ET C'EST UNE MESURE. Première version
      à 1.1fr contre 1.6fr : vu à l'écran le 09/09 sur le Centre Aquatique, cinq
      lignes de suite lisaient « TOTALENERGIES E... », donc cinq lignes
      indiscernables. La colonne qui identifie doit être plus large que celle
      qui décrit, jamais l'inverse. */
/* ⚠️ L'EN-TÊTE PARTAGE LA GRILLE DES LIGNES, ET CE N'EST PAS COSMÉTIQUE. Il
      héritait de la grille générique des listes, à cinq colonnes de largeurs
      différentes : mesuré le 09/09, « Engagé » tombait 15 px à côté du montant
      qu'il nomme, et sur téléphone 156 px à côté. Une colonne de nombres dont
      le titre ne surmonte pas les nombres n'est plus un tableau. */
/* ⚠️ IL Y A DEUX EN-TÊTES DANS CETTE LISTE, ET LE « > » EST CE QUI LES SÉPARE.
      Les situations se déplient DANS un engagement : sans le chevron, le
      sélecteur descendant attrapait aussi leur en-tête, à trois colonnes, et
      lui imposait la grille des engagements, à cinq. Mesuré à l'écran le
      10/09 : « Montant » tombait 232 px à gauche des montants sur grand écran,
      et 216 px sur téléphone, où « Date » et « Objet » disparaissaient de
      surcroît. C'est le défaut du 09/09 décrit juste au-dessus, refermé sur un
      en-tête et rouvert sur l'autre par un sélecteur trop large. */
/* ⚠️ SIX COLONNES, ET LE DOMAINE OUVRE LA LIGNE DEPUIS LE 21/09/2026 AU SOIR.
      Il s'intercalait entre l'objet et les nombres, posé là parce qu'il CLASSE
      et n'identifie pas. L'argument tombe depuis qu'il est une ICÔNE : une
      pastille de 16 px au milieu d'un tableau se cherche, en tête elle se
      balaye. C'est aussi la place qu'il occupe déjà sur les interventions en
      régie et sur les opérations.

      Sa colonne passe de 5,5 rem à 3 rem : elle ne porte plus un mot, elle
      porte un dessin. Les 2,5 rem rendus vont au fournisseur et à l'objet.

   ⚠️ MAIS SON EN-TÊTE, LUI, PORTE UN MOT, et 3 rem ne le tenaient pas. Mesuré
      le 25/09 : la cellule fait 48 px, « DOMAINE » en capitales espacées en
      demande 69 ; il débordait de 21 px et touchait « FOURNISSEUR », à toutes
      les largeurs d'écran, la colonne étant fixe. 4,75 rem font 76 px. Une
      largeur au contenu (`max-content`) ne marcherait pas : chaque ligne est
      sa propre grille, et les colonnes cesseraient de s'aligner. */
.liste-engagements .ligne-engagement,
.liste-engagements > .ligne-liste-entete {
  grid-template-columns: 4.75rem minmax(9rem, 1.5fr) minmax(0, 1.2fr) 6rem 6rem 7rem;
}
/* ⚠️ CENTRÉE, ET C'EST CE QUI FAIT LA COLONNE. Une icône alignée à gauche dans
      une cellule de 3 rem se lit comme un bout du fournisseur ; centrée, les
      pastilles forment une colonne qu'on balaye. */
.engagement-domaine {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--taille-mention);
  color: var(--text-secondary);
}

/* ---- les trois tableaux, et leur barre ---------------------------------- */
.engagements-tableaux { display: flex; flex-direction: column; gap: 1.5rem; }
.engagements-groupe { display: flex; flex-direction: column; gap: 0.5rem; }
.engagements-groupe-tete {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem 1rem;
}
.engagements-groupe-titre {
  font-size: var(--taille-appuye);
  font-weight: 700;
  margin: 0;
}
.engagements-groupe-compte { font-size: var(--taille-mention); color: var(--text-muted); }
.engagements-barre {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem 1rem;
  margin-bottom: 1rem;
}
/* ⚠️ AUCUNE TAILLE ICI, ET C'EST VOLONTAIRE. Le plancher de 16 px des champs
      de saisie, celui qui empêche iOS de zoomer la page à la mise au point, est
      posé UNE FOIS en fin de fichier sur `input, select, textarea`, en
      `!important`. Une taille écrite ici serait à la fois redondante et
      écrasée, et le banc la refuse. */
.engagements-recherche {
  flex: 1 1 18rem;
  max-width: 28rem;
}
.engagements-domaines {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.2rem 0.6rem;
}
.engagements-domaines-titre {
  font-size: var(--taille-mention);
  color: var(--text-muted);
}
.engagements-domaine {
  font-size: var(--taille-mention);
  cursor: pointer;
  margin-right: 0.4rem;
}
.engagements-domaine-case { cursor: pointer; }

/* L'en-tête devient une rangée de boutons de tri : ils ne doivent ni ressembler
   à des boutons, ni cesser d'en être. Le fond et la bordure partent, la cible
   et le focus restent. */
.entete-tri {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  text-transform: inherit;
  letter-spacing: inherit;
  cursor: pointer;
  text-align: inherit;
}
.nombre > .entete-tri { text-align: right; width: 100%; }
.entete-tri:hover { text-decoration: underline; }
.entete-tri-actif { color: var(--accent-primary); }
/* L'en-tête des situations partage la grille de ses lignes, jamais celle des
   engagements. Le fond ne se met QUE sur les lignes : l'en-tête tient le sien
   de `.engagement-situations .ligne-liste-entete`, plus bas. */
.liste-engagements .ligne-situation,
.engagement-situations > .ligne-liste-entete {
  grid-template-columns: 5.5rem minmax(0, 1fr) 6.5rem;
}
.liste-engagements .ligne-situation {
  background: var(--bg-secondary);
}

/* ⚠️ UN BOUTON, PAS UNE DIV : ce qui se déplie doit s'atteindre au clavier
      (RGAA 4.1.2). Il faut donc lui retirer l'apparence de bouton sans lui
      retirer sa nature, et surtout PAS son anneau de focus. */
.engagement-bascule {
  display: block;
  width: 100%;
  padding: 0;
  margin: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.engagement-bascule:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: -2px;
}
.ligne-engagement-ouverte { background: var(--accent-light); }

.engagement-identite { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
.engagement-tiers {
  font-weight: 600;
  color: var(--text-primary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.engagement-numero {
  font-size: var(--taille-mention);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.engagement-objet,
.situation-objet { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.engagement-objet { color: var(--text-secondary); }

/* ⚠️ UN LIBELLÉ COUPÉ SE LIT EN ENTIER QUAND ON OUVRE LA LIGNE, demandé par
   Victor le 14/09 : « il faut pouvoir visualiser les libellés en entier en
   cliquant dessus ». Sur sa capture, l'objet s'arrêtait à « SOUS TRAITANT DE
   PLAMON ET CIE /GROS ŒU… » et le reste n'était atteignable qu'au survol,
   c'est-à-dire jamais au doigt ni au clavier.

   ⚠️ ET C'EST DU CSS SEUL, SANS SECONDE INTERACTION, POUR UNE RAISON D'ACCÈS.
      La ligne d'un engagement EST déjà un `<button>`, celui qui déplie les
      situations : on ne peut pas y imbriquer un second bouton, le HTML
      l'interdit et le clavier s'y perdrait. Le geste qui ouvre les situations
      ouvre donc aussi le libellé, et il n'y a qu'un geste à apprendre.

   Les libellés de situations, eux, ne se coupent plus du tout : on ne les voit
   qu'après avoir cliqué, il n'y a plus rien à déplier. */
.ligne-engagement-ouverte .engagement-objet,
.situation-objet {
  overflow: visible;
  text-overflow: clip;
  white-space: normal;
  overflow-wrap: anywhere;
}

/* La mention de sous-traitance : ce que le libellé DIT, pas une liaison
   résolue. Voir db/51 : aucun rapprochement mesuré ne dépasse 23 %. */
.engagement-pastille {
  display: inline-block;
  margin-left: 0.4rem;
  padding: 0 0.35rem;
  border-radius: 999px;
  background: var(--accent-amber-light);
  color: #92400e;
  font-size: var(--taille-mention);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

/* ⚠️ UNE SECONDE PASTILLE, ET UNE COULEUR DIFFÉRENTE DE LA PREMIÈRE. La
      sous-traitance et l'avenant ne disent pas la même chose, et deux mentions
      identiques se confondraient d'un coup d'oeil. Ni l'une ni l'autre ne porte
      seule son information : le texte est écrit dedans (RGAA 3.1). */
.engagement-pastille-avenant { background: var(--accent-light); color: #1e40af; }

/* ⚠️ UNE TROISIÈME PASTILLE, ET ELLE EST GRISE EXPRÈS. L'ambre et le bleu
      disent « attention, lis autrement » ; le report dit « cette ligne ne
      compte pas », et le gris est la seule teinte qui le dise sans crier. Elle
      n'ajoute donc aucune couleur au langage de l'écran, qui tient sur un bleu
      et un violet depuis le 07/09.
      Contraste mesuré : #475569 sur #e2e8f0 vaut 6,15:1, au-delà du 4,5:1 que
      le RGAA 4.1.2 demande. Et la couleur ne porte rien seule, le mot
      « report » est écrit dans la pastille (RGAA 3.1). */
.engagement-pastille-report {
  background: var(--border-color);
  color: var(--text-secondary);
}

.engagement-part { display: flex; flex-direction: column; gap: 0.2rem; align-items: flex-end; }
.engagement-pourcentage {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--text-secondary);
}
/* ⚠️ LE DÉPASSEMENT SE VOIT, IL NE SE CACHE PAS. 1 087 engagements sur 20 484
      sont facturés au-delà de leur montant, pour 535 478 € : c'est ce qu'un
      directeur du Bâtiment vient chercher dans cet écran. La couleur ne porte
      pas seule l'information (RGAA 3.1) : le nombre est écrit à côté. */
.engagement-depasse { color: var(--accent-amber); }
.engagement-jauge {
  display: block;
  width: 100%;
  height: 0.3rem;
  border-radius: 999px;
  background: var(--border-color);
  overflow: hidden;
}
.engagement-jauge-pleine { display: block; height: 100%; background: var(--accent-primary); }
.engagement-jauge-depassee { background: var(--accent-amber); }

/* ⚠️ UNE FACTURE NE DOIT PAS SE LIRE COMME UN ENGAGEMENT, demandé par Victor
   le 14/09 : « il faut une distinction très claire entre les engagements et
   les factures ». Sur sa capture, les deux se suivaient sur des fonds presque
   identiques, et l'ensemble se lisait comme UNE liste de cinq lignes dont
   quatre portaient un montant qui ne s'additionne pas à la première.

   Trois marques plutôt qu'une, parce qu'aucune ne suffit seule :

     · un RETRAIT, qui dit l'appartenance : ces lignes sont celles de la ligne
       du dessus, pas ses voisines ;
     · un LISERÉ vertical continu, qui relie le paquet et le borne ;
     · un FOND franc, plus soutenu que celui des lignes ordinaires.

   ⚠️ LE LISERÉ N'EST PAS DÉCORATIF, ET C'EST POURQUOI IL Y A AUSSI UN RETRAIT.
      RGAA 4.1.2 : une information ne se donne jamais par la seule couleur. Qui
      ne distingue pas les bleus pâles lit quand même le décalage. */
.engagement-situations {
  border-top: 1px solid var(--border-color);
  border-left: 4px solid var(--accent-secondary);
  margin-left: 1.2rem;
  background: var(--bg-tertiary);
}
.engagement-situations .ligne-liste-entete { background: var(--bg-secondary); }
.nombre-negatif { color: var(--accent-amber); }

/* ⚠️ SUR TÉLÉPHONE, LA LISTE CESSE D'ÊTRE UNE GRILLE. À 390 px, cinq colonnes
      donnent 60 px chacune et coupent tous les montants. Chaque ligne devient
      donc deux étages : l'identité et l'objet en haut, les trois nombres en
      bas, alignés à droite. */
@media (max-width: 640px) {
  .liste-engagements .ligne-engagement {
    grid-template-columns: minmax(0, 1fr) 6rem 6rem;
    row-gap: 0.2rem;
  }
  .liste-engagements .ligne-engagement > .engagement-colonne-identite { grid-column: 1 / -1; }
  .liste-engagements .ligne-engagement > .engagement-objet {
    grid-column: 1 / -1;
    font-size: var(--taille-courant);
  }
  /* ⚠️ SUR TÉLÉPHONE LE DOMAINE REPASSE EN PREMIER SUR SA PROPRE LIGNE, et non
     en colonne de 3 rem : à 390 px, une icône seule en tête d'une grille à
     trois colonnes volerait 3 rem aux montants, qui n'en ont pas à donner. Il
     classe la ligne, il ne se compare pas aux nombres de l'étage du bas. */
  .liste-engagements .ligne-engagement > .engagement-domaine {
    grid-column: 1 / -1;
    justify-content: flex-start;
  }
  .liste-engagements .ligne-situation { grid-template-columns: 5rem minmax(0, 1fr) 6rem; }

  /* ⚠️ TROIS MONTANTS SANS ÉTIQUETTE NE SE LISENT PAS. Vu à l'écran le 09/09 :
        l'application masque TOUS les en-têtes de liste sur téléphone, ce qui
        convient à une liste dont les colonnes se devinent. Ici, « 110 548 € »
        et « 85 526 € » côte à côte ne disent pas lequel est engagé. On rend
        donc cet en-tête-là, et lui seul, en n'y gardant que les trois
        intitulés qui manquent : le fournisseur et l'objet, eux, sont déjà
        écrits en toutes lettres dans chaque ligne. */
  /* ⚠️ LE CHEVRON, ICI AUSSI, ET POUR UNE RAISON DE PLUS. Sans lui, cette
        exception s'appliquait à l'en-tête des situations : elle le rendait
        visible, lui donnait la grille à trois colonnes des engagements, et
        masquait ses deux premiers intitulés. Restait « MONTANT » seul, 216 px
        à gauche des montants, posé au-dessus de la colonne des dates.
        L'en-tête des situations reste donc masqué sur téléphone, comme tous
        les autres : ses lignes ne portent qu'UN nombre, et un nombre seul ne
        se confond avec rien. L'exception vaut pour les engagements parce que
        trois montants y sont côte à côte, pas parce qu'un tableau doit avoir
        un en-tête. */
  .liste-engagements > .ligne-liste-entete {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 6rem 6rem;
  }
  /* ⚠️ TROIS ET NON PLUS DEUX, DEPUIS LE 21/09/2026. La colonne « Domaine »
        s'est intercalée : à `-n+2`, c'est « Domaine » qui serait resté visible
        au-dessus de la colonne des montants engagés, et les trois intitulés de
        nombres auraient glissé d'un cran. Exactement le défaut du 09/09, un
        titre qui ne surmonte plus ce qu'il nomme. */
  .liste-engagements > .ligne-liste-entete > :nth-child(-n+3) { display: none; }
}

.rang {
  display: grid;
  place-items: center;
  width: 1.4rem; height: 1.4rem;
  border-radius: 999px;
  background: var(--bg-tertiary);
  color: var(--text-secondary);
  font-size: var(--taille-mention);
  font-weight: 700;
}

.rang-1 { background: var(--accent-amber-light); color: #b45309; }
.rang-2 { background: var(--bg-tertiary); color: var(--text-secondary); }
.rang-3 { background: #f5e0d0; color: #9a5b2b; }

.ligne-liste .barre {
  height: 0.4rem;
  background: var(--bg-tertiary);
  border-radius: 999px;
  overflow: hidden;
  min-width: 3rem;
}

.ligne-liste .barre span { display: block; height: 100%; background: var(--accent-secondary); border-radius: 999px; }

/* La colonne "Part" a remplacé la jauge : 5 rem suffisent à « 100,0 % ». */
#site-equipements .ligne-liste { grid-template-columns: minmax(0, 1fr) 5rem 7rem 5.5rem 7rem; }
.ligne-liste .part { color: var(--text-secondary); }

/* ---- feuille de réglages ----------------------------------------------- */

/* CENTRÉE, ET PLUS LARGE. Alignée à gauche avec 44 rem de large, elle laissait
   832 px de blanc sur une fenêtre de 1 536 — mesuré : 704 px de contenu, plus de
   la moitié de l écran vide. Un formulaire isolé dans un angle se lit comme un
   reste, pas comme un écran. */
.feuille {
  max-width: 56rem;
  margin: 1.75rem auto;
  padding: 1.5rem;
  background: var(--bg-primary);
  border: 1px solid var(--border-color);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}

.feuille h2 { font-size: var(--taille-sous-titre); font-weight: 700; }

.feuille-intro {
  color: var(--text-secondary);
  font-size: var(--taille-courant);
  line-height: 1.5;
  margin-bottom: 0.4rem;
}

.reglage {
  display: flex;
  gap: 0.7rem;
  align-items: flex-start;
  padding: 0.7rem 0.8rem;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  background: var(--bg-secondary);
  cursor: pointer;
}

.reglage:hover { border-color: var(--accent-secondary); }
.reglage input[type="checkbox"] { margin-top: 0.15rem; accent-color: var(--accent-primary); cursor: pointer; }
.reglage span { display: flex; flex-direction: column; gap: 0.15rem; }
.reglage strong { font-size: var(--taille-appuye); font-weight: 600; }
.reglage em { font-style: normal; font-size: var(--taille-courant); color: var(--text-muted); line-height: 1.45; }

/* Une option qui AFFINE la précédente, et qui doit se lire comme telle : en
   retrait, sans cadre à elle, rattachée par un filet à gauche. Elle se grise
   quand la case du dessus est décochée — voir majSousOptionLieux(). */
.reglage-sous-option {
  margin-left: 1.6rem;
  margin-top: -0.35rem;
  border: none;
  border-left: 2px solid var(--border-color);
  border-radius: 0;
  background: transparent;
}
.reglage-sous-option:hover { border-color: var(--accent-secondary); }
.reglage-sous-option strong { font-weight: 500; }
.reglage-sous-option:has(input:disabled) { opacity: 0.5; cursor: not-allowed; }
.reglage-sous-option:has(input:disabled) input { cursor: not-allowed; }

/* Même retrait dans le contrôle des couches de la carte. */
.leaflet-control-layers .sous-option { padding-left: 1.15rem; }

.reglage-texte { flex-direction: column; cursor: default; }

/* Champ de saisie ou sélecteur : même habillage, même largeur. */
.reglage-texte input[type="text"],
.reglage-texte select {
  border: 1px solid var(--border-champ);
  border-radius: var(--radius-sm);
  padding: 0.45rem 0.6rem;
  background: var(--bg-primary);
  color: var(--text-primary);
  width: 100%;
}

.effet {
  padding: 0.6rem 0.8rem;
  border-radius: var(--radius-sm);
  background: var(--accent-light);
  color: var(--texte-bleu);
  font-size: var(--taille-courant);
  font-weight: 600;
}

/* La liste des domaines : les mêmes lignes que les autres réglages, empilées.
   Elle reprend .reglage plutôt qu'une classe à elle — une case à cocher avec
   son titre et son explication, c'est exactement ce que .reglage habille. */
.liste-domaines { display: flex; flex-direction: column; gap: 0.6rem; }
/* « .reglage span » empile en colonne — c'est ce qu'il faut pour le bloc
   titre + explication, pas pour un pictogramme collé au libellé. */
.liste-domaines .icone-domaine { display: inline; }

/* ---- la feuille « Domaines », 15/09 --------------------------------------
   Une ligne par domaine : le carré tel qu'il paraît dans le rail, le libellé
   avec ses deux poids, la case de suivi ; et, pour l'éditeur seul, une seconde
   rangée qui porte le libellé, la couleur, le dessin et les flèches. */
.ligne-domaine {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0.6rem 0.7rem;
  border-radius: var(--radius-sm);
  background: var(--bg-secondary);
}
.ligne-domaine-haut { display: flex; align-items: center; gap: 0.8rem; }
/* ⚠️ LE CARRÉ EST CELUI DU RAIL, à la taille près. Dans le rail il remplit sa
   colonne (width: 100%) ; ici il lui faut une largeur, sans quoi il s'étale. */
.ligne-domaine-haut .carre-domaine { flex: 0 0 3rem; width: 3rem; }
.ligne-domaine-texte { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; flex: 1; }
.ligne-domaine-texte em { color: var(--text-muted); font-style: normal; font-size: var(--taille-mention); }
.ligne-domaine-suivi { flex: 0 0 auto; }

/* Le domaine qu'un import vient d'apporter : remonté en tête par app.js, et
   signalé ici. Le liseré ambre est celui de l'avertissement, pas celui de
   l'accent : il prévient, il ne met pas en valeur. */
.domaine-a-habiller { box-shadow: inset 3px 0 0 var(--accent-amber); }
.etiquette-a-habiller {
  margin-left: 0.5rem;
  padding: 0.05rem 0.4rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent-amber) 18%, transparent);
  color: var(--text-primary);
  font-size: var(--taille-mention);
  font-weight: 500;
}

.ligne-domaine-editeur { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
/* ⚠️ LE CHAMP EST BORNÉ, ET C'EST UNE MESURE D'ÉCRAN. Laissé libre de croître,
   il faisait 313 px sur une ligne et 608 sur la suivante, mesuré le 15/09 sur
   les sept : un bord droit en dents de scie, pour un contenu qui n'a jamais
   plus de 60 caractères. */
.champ-libelle-domaine { flex: 0 1 16rem; min-width: 8rem; }
/* ⚠️ ET LA GRILLE PREND SA PROPRE LIGNE. Ouverte dans la rangée, elle poussait
   le libellé, la couleur et le contraste au milieu d'un vide de 300 px, et le
   mot « buildings » se retrouvait au-dessus du champ qu'il ne décrit pas. */
.ligne-domaine-editeur .choix-dessin { flex: 1 0 100%; order: 9; }
.ligne-domaine-editeur input[type="color"] {
  width: 2.5rem; height: 2.1rem; padding: 0.15rem;
  border: 1px solid var(--border-color); border-radius: var(--radius-sm); background: var(--bg-primary);
}
.contraste-mesure { color: var(--text-muted); font-size: var(--taille-mention); font-variant-numeric: tabular-nums; }
/* ⚠️ LA MESURE REFUSÉE N'EST PAS DITE PAR LA SEULE COULEUR, RGAA 3.1. Le mot
   « refusé » l'accompagne, et l'enregistrement s'arrête de toute façon. */
.contraste-refuse {
  color: var(--accent-red);
  font-size: var(--taille-mention);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
.contraste-refuse::after { content: " refusé"; font-weight: 400; }

.choix-dessin summary { cursor: pointer; font-size: var(--taille-mention); color: var(--text-secondary); }
.grille-pictogrammes {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(2.4rem, 1fr));
  gap: 0.3rem;
  margin-top: 0.4rem;
  max-height: 14rem;
  overflow-y: auto;
}
.choix-pictogramme {
  display: flex; align-items: center; justify-content: center;
  aspect-ratio: 1; padding: 0.3rem;
  border: 1px solid var(--border-color); border-radius: var(--radius-sm);
  background: var(--bg-primary); cursor: pointer;
}
.choix-pictogramme svg { width: 100%; height: 100%; }
.choix-pictogramme[aria-pressed="true"] { border-color: var(--texte-bleu); border-width: 2px; }
.choix-pictogramme:focus-visible { outline: 3px solid var(--texte-bleu); outline-offset: 2px; }

/* Le récapitulatif : ce qu'on s'apprête à envoyer, avant de cliquer. */
.recapitulatif-domaines {
  padding: 0.6rem 0.8rem;
  border-radius: var(--radius-sm);
  border-left: 3px solid var(--texte-bleu);
  background: color-mix(in srgb, var(--texte-bleu) 7%, transparent);
  font-size: var(--taille-courant);
}
.recapitulatif-domaines ul { margin: 0.3rem 0 0; padding-left: 1.1rem; }
.recapitulatif-domaines li { line-height: 1.5; }

@media (max-width: 46rem) {
  .ligne-domaine-haut { flex-wrap: wrap; }
  .ligne-domaine-texte { flex-basis: 60%; }
}

/* L'avertissement n'est pas un « effet » : il ne mesure rien, il prévient d'une
   conséquence sur un AUTRE écran. D'où l'ambre plutôt que l'accent. */
.feuille-avertissement {
  padding: 0.6rem 0.8rem;
  border-radius: var(--radius-sm);
  border-left: 3px solid var(--accent-amber);
  background: color-mix(in srgb, var(--accent-amber) 10%, transparent);
  color: var(--text-primary);
  font-size: var(--taille-courant);
  line-height: 1.5;
}

.feuille-actions { display: flex; align-items: center; gap: 0.9rem; }

/* Visé par la classe et non par un identifiant : toute feuille de réglages a
   son bouton, et le premier écrit ne doit pas être le seul habillé. */
.feuille-actions button {
  border: 0;
  border-radius: var(--radius-sm);
  background: var(--accent-primary);
  color: #fff;
  font-weight: 600;
  padding: 0.55rem 1.1rem;
}

.feuille-actions button:hover:not(:disabled) { background: var(--accent-secondary); }
.feuille-actions button:disabled { opacity: 0.6; cursor: progress; }

.etat-enregistrement { font-size: var(--taille-courant); color: var(--text-muted); }

/* ---- l'émergence de la fiche d'un site ---------------------------------- */
/*
   La fiche pousse comme une fleur depuis le CENTRE DU CERCLE du site, en
   180 ms.

   ⚠️⚠️ CE QU'ON ANIME : LES ENFANTS DE LA FICHE, JAMAIS `.leaflet-popup`.

   C'est LA chose à savoir de ce bloc, et elle a coûté une version entière.

   Leaflet place chaque fiche avec un `transform: translate3d(390px, 449px, 0)`
   posé en ligne. On avait cru pouvoir animer la propriété INDÉPENDANTE `scale`
   sur ce même élément, en se disant qu'elle « se composerait » avec ce
   transform sans l'écraser. Elle ne l'écrase pas, en effet — elle le
   MULTIPLIE. L'ordre de composition est translate · rotate · scale · transform,
   donc l'échelle s'applique PAR-DESSUS la translation de Leaflet : à l'échelle
   0,08, la fiche se retrouvait à 0,08 x (390, 449) = (31, 36), c'est-à-dire
   dans le coin haut-gauche de la carte, d'où elle volait jusqu'au site.

   Mesuré : 221 px de trajet horizontal pendant l'animation. À l'écran, cela se
   voyait comme « la fenêtre arrive depuis la gauche » — et c'est exactement ce
   qui a été signalé.

   D'où la règle : on anime le WRAPPER et la POINTE, qui ne portent aucun
   transform. La fiche elle-même n'est plus touchée du tout, et Leaflet reste
   seul maître de sa position.

   CE QU'ON ANIME, ET SURTOUT PAS QUOI

   `scale` uniquement. C'est, avec `opacity`, l'une des deux propriétés que le
   navigateur traite sans refaire la mise en page : il confie la couche au
   processeur graphique, et la carte en dessous n'est ni recalculée ni
   redessinée. Animer `width`, `height`, `top` ou `left` forcerait un recalcul
   par image sur un sous-arbre lourd — c'est comme ça qu'on rend une carte
   poussive.

   LES DEUX ORIGINES, RELEVÉES ET NON CALCULÉES

   Le point visé est le centre du cercle dessiné pour le site. Mesure faite en
   comparant le point d'origine au cercle réel : la boîte de la fiche finit à
   y=535, le centre du cercle est à y=548. Treize pixels.

     · le wrapper occupe exactement la boîte de la fiche (mêmes haut, bas et
       largeur) : son origine est donc `50% calc(100% + 13px)` ;
     · la pointe vit dans un conteneur de 20 px remonté d'un pixel
       (`margin-top: -1px`), qui commence donc 1 px AU-DESSUS du bas de la
       boîte : le centre du cercle tombe 14 px sous son propre haut.

   ⚠️ TROIS VALEURS SE SONT SUCCÉDÉ AVANT CELLE-LÀ. 13 px déduits du
   `margin-bottom` et du `bottom` de Leaflet ; puis 19 px, la hauteur de la
   pointe, en croyant corriger une déduction par une mesure — mais on avait
   mesuré la POINTE, qui n'est pas le site ; puis 26 px, pointe + offset. La
   bonne réponse était la première, et aucune des trois n'avait été confrontée à
   la SEULE référence qui compte : le cercle dessiné à l'écran. Mesurer, oui —
   encore faut-il mesurer la bonne chose.
*/

/* LE FONDU DE LEAFLET EST ÉTEINT, ET C'EST LUI QUI DILUAIT TOUT.

   Leaflet fait apparaître ses fiches en 0,2 s d'opacité linéaire. Superposé à
   une croissance de 0,18 s, le résultat n'est pas une fleur qui s'ouvre : la
   première moitié du mouvement se joue pendant que la fiche est encore presque
   transparente, et l'on ne voit apparaître qu'une boîte déjà grande.

   Éteint, la fiche est opaque dès sa première image et c'est la CROISSANCE
   seule qui porte l'effet. Personne n'anime plus l'opacité : elle vaut 1.

   ⚠️ Effet de bord assumé : la fermeture devient instantanée au lieu de
   s'estomper en 0,2 s. On ferme une fiche parce qu'on en a fini avec elle. */
.leaflet-fade-anim .leaflet-popup { transition: none; }

/* LA FLEUR. On part d'un point, à l'endroit exact du site.

   `scale: 0.08` et non 0.75 : à trois quarts de sa taille, une fiche ne pousse
   pas, elle sursaute.

   ⚠️ On ne descend pas jusqu'à 0 : à l'échelle nulle le navigateur n'a plus de
   boîte à composer, et certaines cartes graphiques rendent la première image en
   noir. 0.08 est visuellement un point, et reste une boîte. */
/* LA DURÉE ET LA COURBE VIVENT ICI, ET NULLE PART AILLEURS.
   Trois règles s'en servent — le contenu de la fiche, sa pointe, et le bandeau
   des interventions — et elles DOIVENT rester d'accord : si la pointe durait
   plus que la fiche, elle finirait de pousser après elle. Une variable plutôt
   que deux copies qui dériveront.

   ⚠️ `FIN_ANIMATION_MS` dans carte.js doit rester SUPÉRIEURE à cette durée :
   c'est le filet qui retire la classe, et s'il tombait avant la fin il
   couperait l'animation en plein vol.

   ⚠️ LE BANDEAU N'EST PLUS ICI. Sa durée et sa courbe vivent dans carte.js
   (`DUREE_BANDEAU_MS`, `COURBE_BANDEAU`) parce que c'est le JavaScript qui mène
   son mouvement — voir la longue note au-dessus de `.panneau-interventions`. */
:root {
  --duree-apparition: 360ms;   /* la fiche d'un site */
  /* La fiche : départ très vif, arrêt net. Le mouvement est court (une échelle),
     et cette courbe lui donne le claquement qui convient. */
  --courbe-apparition: cubic-bezier(0.2, 0.9, 0.3, 1);
}

@keyframes fiche-emergence {
  from { scale: 0.08; }
  to   { scale: 1; }
}

/* ⚠️ LE CONTENU DE LA FICHE EST POSITIONNÉ, ET C'EST CE QUI PERMET DE LE
      VOILER. Le voile d'attente est en `position: absolute; inset: 0` : sans
      ancêtre positionné, il se serait accroché à `.leaflet-popup`, dont Leaflet
      pilote la boîte et la transformation. Il aurait alors débordé la fiche et
      suivi les translations de la carte.

   ⚠️ ET ELLE EST HORS DU `@media` QUI SUIT, ce qu'une première écriture avait
      raté : posée dedans, elle n'aurait pas existé sous « animations réduites »,
      et le voile y aurait débordé la fiche. Un réglage de MOUVEMENT ne doit
      jamais porter une règle de POSITIONNEMENT. */
.leaflet-popup-content { position: relative; }

@media (prefers-reduced-motion: no-preference) {
  .leaflet-popup.fiche-emerge .leaflet-popup-content-wrapper {
    transform-origin: 50% calc(100% + 13px);
    animation: fiche-emergence var(--duree-apparition) var(--courbe-apparition);
  }
  /* La pointe suit le même mouvement, depuis le même point du site. Sans elle,
     la fiche pousserait en laissant sa pointe immobile et pleine taille. */
  .leaflet-popup.fiche-emerge .leaflet-popup-tip-container {
    transform-origin: 50% 14px;
    animation: fiche-emergence var(--duree-apparition) var(--courbe-apparition);
  }
}

/* LE VOLET, décision 1 du 07/09. Commun aux trois vues, sans repli.
   460 px, la valeur de la maquette : la liste y porte un nom de site et trois
   nombres, et 400 px les serraient déjà. */
.barre-laterale {
  position: absolute;
  /* ⚠️ IL DESCEND SOUS LA BARRE, ET IL EST LE SEUL À DEVOIR LE FAIRE. Le volet
     reste en position absolue contre la coque, dont le haut est désormais le
     haut de l'en-tête pleine largeur : à 0.9rem, il passait DERRIÈRE elle, et
     ses premières lignes disparaissaient. La scène, elle, est repassée dans le
     flux et n'a besoin de rien. Voir la note de `.pilule`. */
  top: calc(var(--haut-tete) + 0.9rem);
  left: 0.9rem;
  bottom: 0.9rem;
  width: var(--largeur-volet);
  z-index: 1100;
  background: var(--bg-primary);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-flottant);
  box-shadow: var(--shadow-flottant);
  padding: 1rem;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

/* ⚠️ CE QUI DÉCRIT LA CARTE NE SURVIT PAS À LA CARTE. Les totaux, la liste et
   l'entrée du parc parlent des sites dessinés : sur « Dans le temps » ou
   « Agents », ils décriraient un écran qu'on ne regarde pas. La question, elle,
   reste, puisque c'est elle qui commande les trois vues.
   Masquage par l'écran courant, que ouvrirEcran() écrit sur le corps de page :
   l'attribut hidden ne tiendrait pas, carte.js réécrit ces nœuds à chaque
   calcul. */
/* ---- où va son temps, décision 15 ----------------------------------------
   Par site, les dix premiers avec leur jauge, le reste replié derrière une
   ligne qui dit ce qu'elle cache. */

.ou-va { margin: 1.5rem 0 1.2rem; padding-top: 1.2rem; border-top: 1px solid var(--border-color); }
/* Sous les graphiques, il a toute la largeur : deux colonnes de sites dès
   qu'il y a la place, plutôt qu'une liste étirée sur 1 400 px. */
@media (min-width: 64rem) {
  #panneau-activite .ou-va-liste { display: grid; grid-template-columns: 1fr 1fr; column-gap: 2.5rem; }
  #panneau-activite .ou-va-reste, #panneau-activite .ou-va-replier { grid-column: 1 / -1; }
  #panneau-activite .ou-va-detail { max-width: 44rem; }
}
.ou-va-tete { display: flex; align-items: baseline; gap: 0.8rem; flex-wrap: wrap; }
.ou-va-tete .volet-titre { margin: 0; }
.ou-va-domaine { margin: 0; font-size: var(--taille-courant); color: var(--text-secondary); }
.ou-va-domaine:empty { display: none; }

.ou-va-liste { display: flex; flex-direction: column; margin-top: 0.5rem; }

/* ⚠️ LA JAUGE EST SOUS LE NOM, PAS À CÔTÉ, ET C'EST LA LARGEUR DU VOLET QUI LE
   DÉCIDE. Un premier jet la mettait en colonne, entre le nom et les heures :
   dans 460 px, une fois le nom d'un site et deux nombres posés, il ne lui
   restait rien et elle disparaissait. Vu à l'écran le 07/09, alors que Victor
   venait justement de demander à pouvoir cliquer « sur les barres bleues ».

   Trois colonnes sur la première ligne, la jauge en travers sur la seconde. */
.ou-va-ligne {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: baseline;
  gap: 0.15rem 0.7rem;
  padding: 0.35rem 0.4rem;
  border: 0;
  background: none;
  text-align: left;
  font-size: var(--taille-courant);
  color: var(--text-primary);
  border-radius: var(--radius-sm);
}
.ou-va-nom { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ou-va-heures { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; }
.ou-va-part {
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-size: var(--taille-courant);
  color: var(--text-muted);
}

/* ⚠️ LA JAUGE EST RELATIVE AU PLUS GROS SITE, pas au total. Rapportée au total,
   la première ferait 13 % et les suivantes seraient des traits invisibles : on
   ne comparerait plus rien. Le pourcentage du total, lui, est écrit à droite,
   si bien que rien n'est porté par la seule longueur (RGAA 3.1). */
.ou-va-jauge {
  grid-column: 1 / -1;
  height: 4px;
  border-radius: 2px;
  background: var(--bg-tertiary);
  overflow: hidden;
}
.ou-va-jauge i { display: block; height: 100%; background: var(--accent-primary); }

/* La ligne de regroupement est un BOUTON : elle mène quelque part. */
.ou-va-reste { cursor: pointer; }
.ou-va-reste:hover { background: var(--bg-secondary); }
.ou-va-reste .ou-va-nom { font-style: italic; color: var(--texte-bleu); font-weight: 600; }
.ou-va-reste .ou-va-jauge i { background: var(--text-muted); }

.ou-va-replier {
  align-self: flex-start;
  margin-top: 0.3rem;
  border: 0;
  background: none;
  padding: 0.2rem 0.4rem;
  color: var(--texte-bleu);
  font-size: var(--taille-courant);
  font-weight: 600;
}
.ou-va-replier:hover { text-decoration: underline; }


/* ⚠️ LA LIGNE ENTIÈRE SE CLIQUE, pas seulement le nom ni seulement la jauge.
   Victor, le 07/09 : « cliquer sur les noms des sites OU sur les barres
   bleues ». Deux cibles séparées obligeraient à viser ; une seule se touche où
   qu'on la prenne, et le clavier n'a qu'un élément à parcourir. */
.ou-va-site { cursor: pointer; }
.ou-va-site:hover { background: var(--bg-secondary); }
.ou-va-site:hover .ou-va-nom { color: var(--texte-bleu); }

/* ---- le détail d'un site, dans le volet ---------------------------------- */
.ou-va-detail { display: flex; flex-direction: column; gap: 0.45rem; min-height: 0; }
.ou-va-detail-nom { margin: 0; font-size: var(--taille-appuye); line-height: 1.3; }
.ou-va-detail-resume { margin: 0; font-size: var(--taille-courant); color: var(--text-secondary); }
.ou-va-detail-jours { display: flex; flex-direction: column; overflow-y: auto; }

.jour-site {
  display: grid;
  grid-template-columns: 7.5rem 4.5rem 1fr;
  gap: 0.5rem;
  align-items: baseline;
  padding: 0.3rem 0.2rem;
  border-bottom: 1px solid var(--border-color);
  font-size: var(--taille-courant);
}
.jour-site-heures { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; }
/* Les références d'intervention : petites, et coupées plutôt qu'enroulées. Une
   journée peut en porter cinq, et cinq lignes par jour rendraient la liste
   illisible. Le titre de survol les donne en entier. */
/* ⚠️ UNE RÉFÉRENCE D'INTERVENTION SE CLIQUE, demandé par Victor le 07/09.
   Elle mène à la page du site, seul endroit où la fiche complète existe. Sans
   site connu, elle reste écrite en noir : une référence introuvable ne doit pas
   disparaître, sinon le compte affiché juste au-dessus ne retombe plus. */
.detail-interventions li { display: flex; flex-direction: column; gap: 0.1rem; }
.ref-intervention { font-weight: 600; font-variant-numeric: tabular-nums; }
a.ref-intervention { color: var(--texte-bleu); text-decoration: none; }
a.ref-intervention:hover { text-decoration: underline; }
.libelle-intervention {
  font-size: var(--taille-mention);
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.jour-site-refs {
  color: var(--text-muted);
  font-size: var(--taille-mention);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ⚠️ LES AUTRES JOURS S'ESTOMPENT, ILS NE DISPARAISSENT PAS. Les retirer ferait
   des trous que la frise emploie déjà pour dire « jour sans trace » : deux
   absences de sens différent, écrites pareil. */
.case-jour.estompee { opacity: 0.18; }
.case-jour.retenue { z-index: 1; }
.case-jour.retenue::after { box-shadow: 0 0 0 2px var(--text-primary); }

/* ---- la frise des semaines, décision 13 ----------------------------------

   ⚠️ LE RETOUR À LA LIGNE EST FAIT PAR LE NAVIGATEUR, PAS PAR LE JAVASCRIPT.
      Chaque semaine est une boîte insécable dans un conteneur en flex-wrap :
      le nombre de semaines par ligne suit la largeur disponible, et la coupe
      tombe toujours entre deux semaines. Rien à mesurer, rien à
      resynchroniser au redimensionnement.

   ⚠️ LA CASE FAIT 14 px, ET CE N'EST PAS UN CHOIX D'ESTHÉTIQUE. Elle doit
      pouvoir être VISÉE pour ouvrir le détail du jour. Une frise d'une seule
      ligne donnerait 4,9 px par case en jours ouvrés et 3,5 px avec les
      week-ends : trop peu pour une cible de pointage. Sur trois lignes, on
      tient les 14 px. */

.barre-frise {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 1rem;
  margin-bottom: 0.5rem;
}
/* ---- calendrier ou barres : deux boutons dessinés, à droite (23/09) ------ */
.choix-vue-jours {
  margin: 0 0 0 auto; padding: 0; border: 0;
  display: inline-flex; gap: 0.5rem;
}
.choix-vue-jours label { position: relative; cursor: pointer; }
.choix-vue-jours input { position: absolute; opacity: 0; pointer-events: none; }
.vue-jours-bouton {
  display: inline-flex; align-items: center; gap: 0.5rem;
  padding: 0.55rem 1rem;
  border: 2px solid var(--border-color); border-radius: 12px;
  background: var(--bg-primary); color: var(--text-secondary);
  font-weight: 600;
  box-shadow: var(--shadow-sm);
  transition: border-color .15s, background .15s, color .15s;
}
.vue-jours-bouton svg { width: 1.5rem; height: 1.5rem; flex: none; }
.choix-vue-jours label:hover .vue-jours-bouton { border-color: var(--texte-bleu); color: var(--text-primary); }
.choix-vue-jours input:checked + .vue-jours-bouton {
  border-color: var(--texte-bleu);
  background: color-mix(in srgb, var(--texte-bleu) 12%, var(--bg-primary));
  color: var(--texte-bleu);
}
.choix-vue-jours input:focus-visible + .vue-jours-bouton { outline: 3px solid var(--texte-bleu); outline-offset: 2px; }
.frise-compte { font-size: var(--taille-courant); color: var(--text-muted); }

/* L'ÉCART ENTRE SEMAINES FAIT 5 px, CELUI ENTRE JOURS ZÉRO.
   ⚠️ LES JOURS SE TOUCHENT, demandé par Victor le 07/09 : « il faut rapprocher
      les cases des jours entre elles, pour mieux voir les vides blancs ». Un
      jour sans trace se lisait mal quand chaque case était détachée de ses
      voisines ; collées, les journées pleines forment un bloc et le trou saute
      aux yeux. C'est aussi ce qui rend la bande du mois continue à l'intérieur
      d'une semaine, sans rien de plus. */
.frise { display: flex; flex-wrap: wrap; gap: 0.7rem 5px; margin-bottom: 1.1rem; }

/* La semaine : une boîte qu'on ne coupe pas, et dont les jours se touchent. */
.semaine { display: flex; gap: 0; }

/* ⚠️ CARRÉES, ET NON HAUTES : CORRECTION DEMANDÉE PAR VICTOR LE 07/09.
   « Je suis en train de transformer les petits cartons en courtes barres, ce
   qui s'approche trop de la représentation en histogrammes. »

   Il a raison, et le défaut venait d'une hauteur qui ne portait RIEN : la case
   faisait 26 px pour 13 de large, donc elle se lisait comme une barre, et une
   barre promet une quantité. Ici seule la COULEUR dit quelque chose. Une case
   carrée redevient un carton : un jeton d'état posé dans un calendrier, ce que
   l'histogramme juste en dessous ne fait pas.

   14 px, et non 13 : la case reste une cible de pointage, et c'est elle qui
   porte le clic depuis que les barres de l'histogramme sont descendues à 3 px. */
.case-jour {
  /* ⚠️ 22 PX DE LARGE DEPUIS LE 23/09, pour un rond de 18 : « agrandis la
     hauteur du calendrier, pour que ce soit visible » (Victor). */
  width: 22px;
  height: 27px;
  border: 0;
  /* La place de la bande, réservée en haut de la case. La bande elle-même est
     dessinée par ::before, qui seul peut franchir l'écart des semaines. */
  padding: 5px 0 0;
  /* ⚠️ DES RONDS DEPUIS LE 23/09, À L'ESSAI : « essayons des ronds à la place
     des rectangles » (Victor). Le rond est dessiné par ::after, la case
     restant un rectangle qui porte la bande du mois et le clic. Ce que le
     07/09 avait écrit reste vrai : deux ronds voisins laissent un blanc entre
     eux, que les rectangles réservaient aux jours sans trace. C'est pourquoi
     un jour sans trace garde un rond pâle, et non un vide. */
  border-radius: 0;
  background: transparent;
  position: relative;
}
.case-jour::after {
  content: "";
  position: absolute;
  top: 7px;
  left: 2px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--couleur-jour, var(--bg-tertiary));
}

/* ⚠️ LA BANDE DU MOIS EST CONTINUE, ET C'EST ::before QUI LA REND TELLE.
   Elle était portée par un border-top : les jours d'une semaine la
   prolongeaient bien, mais l'écart entre deux semaines la coupait, et Victor
   l'a relevé le 07/09. Un pseudo-élément, lui, peut DÉBORDER de sa case et
   franchir l'écart. La classe « pont » n'est posée que sur la dernière case
   d'une semaine dont la suivante commence dans le même mois : sans cette
   condition, la bande de janvier déborderait sur février.

   ⚠️ DEUX NUANCES DE LA MÊME ENCRE, pas deux couleurs. La bande sépare des
   mois, elle ne les qualifie pas : deux teintes différentes laisseraient croire
   que janvier et février ne sont pas de même nature. Et les couleurs sont
   toutes prises par les statuts des jours, juste en dessous.

   ⚠️ LES DEUX NUANCES DOIVENT ÊTRE VISIBLES TOUTES LES DEUX. Un premier essai
   opposait #64748b à #cbd5e1 : le clair disparaissait sur le fond blanc, et la
   bande semblait n'exister qu'un mois sur deux. */
.case-jour::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 5px;
}
.case-jour.pont::before { width: calc(100% + 5px); }
.case-jour.mois-pair::before   { background: #475569; }
.case-jour.mois-impair::before { background: #94a3b8; }

.case-jour:hover { z-index: 1; }
.case-jour:hover::after { box-shadow: 0 0 0 2px var(--text-primary); }
.case-jour:focus-visible { outline: 2px solid var(--texte-bleu); outline-offset: 1px; }
/* Hors période : la case existe pour que la semaine garde sa forme, mais elle
   ne promet rien. Sans elle, une semaine de trois cases donnerait à croire que
   la frise saute des jours. */
/* ---- le calendrier par mois, à l'essai (23/09) -----------------------------
   Une grille de mois, chacun en calendrier habituel. Les ronds portent le
   numéro du jour ; la bande du mois (::before) n'a plus d'objet, le titre du
   bloc dit le mois. */
/* ⚠️ SIX MOIS PAR RANGÉE, DEUX RANGÉES PAR ANNÉE (Victor, 23/09 : « fais
   de telle façon qu'on puisse mettre 6 mois sur une rangée »). Une année qui
   commence en janvier se lit donc janvier à juin, puis juillet à décembre.
   Les ronds rétrécissent pour tenir, jusqu'à 24 px environ ; en dessous, la
   grille passe à quatre, trois puis deux mois, plutôt que de rendre des ronds
   qu'on ne pourrait plus viser. C'est toujours le navigateur qui décide, par
   la largeur de la fenêtre : rien n'est mesuré en JavaScript. */
.frise.frise-mois {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 1.4rem 1.5rem;
}
@media (max-width: 75rem) { .frise.frise-mois { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (max-width: 60rem) { .frise.frise-mois { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 40rem) { .frise.frise-mois { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.mois-cal-titre {
  margin: 0 0 0.45rem; font-weight: 700; font-size: var(--taille-appuye);
  color: var(--text-primary);
}
.mois-cal-titre::first-letter { text-transform: uppercase; }
.mois-cal-grille { display: grid; gap: 3px; justify-items: center; }
.mois-cal-jsem {
  font-size: var(--taille-mention); font-weight: 600; color: var(--text-muted);
  padding-bottom: 0.15rem;
}
.frise-mois .case-jour {
  width: 100%; max-width: 2rem; height: auto; aspect-ratio: 1; padding: 0;
  display: grid; place-items: center;
}
.frise-mois .case-jour::before { display: none; }
.frise-mois .case-jour::after { top: 0; left: 0; width: 100%; height: 100%; }
.frise-mois .num-jour {
  position: relative; z-index: 1;
  font-size: var(--taille-mention); font-weight: 600;
  color: var(--couleur-texte-jour, var(--text-muted));
  pointer-events: none;
}
.frise-mois .case-hors .num-jour { color: var(--text-muted); opacity: 0.55; }

/* ---- l'infobulle d'une journée, au survol (23/09) ------------------------ */
.infobulle-jour {
  position: fixed; z-index: 3000; pointer-events: none;
  min-width: 13rem; max-width: 18rem;
  padding: 0.7rem 0.9rem;
  background: var(--bg-primary); color: var(--text-primary);
  border: 1px solid var(--border-color); border-radius: 12px;
  box-shadow: 0 10px 28px rgba(16, 24, 40, .18);
  font-size: var(--taille-courant);
}
.infobulle-jour p { margin: 0; }
.infobulle-jour-date { font-weight: 700; margin-bottom: 0.35rem !important; }
.infobulle-jour-date::first-letter { text-transform: uppercase; }
.infobulle-jour-verdict {
  display: flex; align-items: center; gap: 0.45rem;
  padding-bottom: 0.45rem; margin-bottom: 0.4rem !important;
  border-bottom: 1px solid var(--border-color);
  color: var(--text-secondary);
}
.infobulle-jour-ligne {
  display: flex; justify-content: space-between; gap: 1.2rem;
  padding: 0.12rem 0; font-variant-numeric: tabular-nums;
}
.infobulle-jour-ligne span { color: var(--text-secondary); }
.infobulle-jour-geste { margin-top: 0.45rem !important; color: var(--text-muted); font-size: var(--taille-mention); }

/* ---- pas encore de données (23/09) ----------------------------------------
   La période demandée va au-delà du dernier pointage : ces jours se montrent,
   hachurés, pour que la période se voie en entier, sans rien compter. */
.case-jour.case-future::after {
  background: repeating-linear-gradient(45deg, var(--bg-tertiary) 0 3px, var(--border-color) 3px 5px);
}
.case-jour.case-future::before { opacity: 0.45; }
.toile-activite .zone-future-fond { fill: var(--bg-secondary); }
.toile-activite .zone-future-trait { stroke: var(--border-color); stroke-width: 3; }
.toile-activite .zone-future-texte { fill: var(--text-muted); font-size: var(--taille-courant); font-weight: 600; }
.pastille-futur {
  background: repeating-linear-gradient(45deg, var(--bg-tertiary) 0 3px, var(--border-color) 3px 5px) !important;
}
.legende-futur { display: none; }
#panneau-activite[data-futur="oui"] .legende-futur { display: inline-flex; }
.case-hors { background: transparent; cursor: default; }
.case-hors::after { background: transparent; }
.case-hors:hover::after { box-shadow: none; }
.case-hors::before { background: none; }
.case-hors:hover { outline: none; }

/* Le nom du mois, posé sur la première case de ce mois. Il DÉBORDE de sa case,
   à dessein : treize pixels ne contiennent pas « janv. », et la bande doit
   dire ce qu'elle couvre plutôt que d'alterner sans rien nommer. */
.nom-mois {
  position: absolute;
  top: -1.05rem;
  left: 0;
  font-size: var(--taille-mention);
  font-weight: 600;
  color: var(--text-secondary);
  white-space: nowrap;
  pointer-events: none;
}
/* La place du nom, réservée en haut de chaque ligne de la frise. */
.frise .semaine { margin-top: 1.05rem; }

/* ---- la tête de l'écran Agents, décision 14 ------------------------------
   Une phrase, une barre, trois tuiles. Cinq tuiles et un anneau ont disparu :
   leurs nombres sont dans la phrase, reliés au lieu d'être juxtaposés. */


/* La barre : une ligne, trois segments, pas de bord entre eux. Un filet les
   séparerait comme s'ils étaient de natures différentes ; ce sont trois parts
   d'un même tout. */
.barre-temps {
  display: flex;
  height: 0.9rem;
  border-radius: 0.45rem;
  overflow: hidden;
  background: var(--bg-tertiary);
  margin-bottom: 0.45rem;
}
.segment-temps { display: block; height: 100%; }

.barre-temps-legende {
  display: flex;
  flex-wrap: wrap;
  /* ⚠️ ESPACÉE depuis le 23/09 : « il faut espacer les infos sous la barre
     horizontale » (Victor). Les trois termes se lisaient comme une seule
     ligne de texte. */
  gap: 0.6rem 2.4rem;
  margin-top: 0.5rem;
  font-size: var(--taille-courant);
  color: var(--text-secondary);
  margin-bottom: 0.9rem;
}
.barre-temps-legende:empty { display: none; }

/* ---- les parts du graphique, décision 10 ---------------------------------
   La liste du volet fait légende sur l'écran « Dans le temps ». Elle n'a de
   sens que là : ailleurs elle décrirait un dessin qu'on ne regarde pas. */
body:not([data-ecran="graphique"]) .barre-laterale > .parts-graphique { display: none; }
/* « Où va son temps » a quitté le volet le 23/09 : il vit dans le panneau
   Agents, et ne paraît donc nulle part ailleurs sans règle de plus. */

.parts-graphique { display: flex; flex-direction: column; gap: 0.5rem; min-height: 0; }
.liste-parts { display: flex; flex-direction: column; gap: 0.15rem; overflow-y: auto; }
/* Ce que la liste nomme, sous son titre : « Sites », « Ateliers des agents ». */
.parts-quoi { margin: -0.25rem 0 0; font-size: var(--taille-courant); color: var(--text-secondary); }
.parts-quoi:empty { display: none; }

/* Une ligne de part : la pastille de couleur, le nom, les heures. La même
   forme que la liste des sites, parce que c'est le même geste. */
/* ⚠️ DEUX COLONNES QUAND IL N'Y A PAS DE PASTILLE. Par site, une couleur
   désigne un rang dans sa colonne et non un site : la pastille a disparu, mais
   sa colonne de 0,75 rem était restée, et le NOM s'y écrasait. Vu à l'écran le
   07/09 : la liste n'affichait plus que des initiales, « E. 4 122 h ». */
/* ⚠️ DEUX COLONNES PARCE QU'IL N'Y A PAS DE PASTILLE, et non parce que la ligne
   ne se clique pas. Les deux étaient confondues jusqu'au 08/09, sous le nom
   « ligne-part-fixe » : rendre les sites cliquables retirait la classe, et le
   nom se décalait dans la colonne restée vide de la pastille. */
.ligne-part-sans-pastille { grid-template-columns: 1fr auto !important; }

/* La ligne qui mène quelque part le dit : le curseur, et le nom qui prend la
   couleur d'un lien au survol. Pas de soulignement permanent, la liste en
   porterait trente. */
.ligne-part-lien { cursor: pointer; }
.ligne-part-lien:hover .nom-part,
.ligne-part-lien:focus-visible .nom-part { color: var(--accent-primary); text-decoration: underline; }

.ligne-part {
  display: grid;
  grid-template-columns: 0.75rem 1fr auto;
  align-items: center;
  gap: 0.55rem;
  width: 100%;
  border: 0;
  background: none;
  text-align: left;
  padding: 0.4rem 0.45rem;
  border-radius: var(--radius-sm);
  font-size: var(--taille-courant);
  color: var(--text-primary);
}
.ligne-part:hover { background: var(--bg-secondary); }
.ligne-part .pastille-part { width: 0.75rem; height: 0.75rem; border-radius: 3px; }
.ligne-part .heures-part { font-variant-numeric: tabular-nums; color: var(--text-secondary); }
.ligne-part .nom-part { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ⚠️ ÉTEINTE, MAIS TOUJOURS LÀ. Le barré et la pastille creuse disent l'état ;
   le texte reste lisible, et le pied compte ce qui est mis de côté. Trois
   signaux plutôt qu'une seule couleur (RGAA 3.1). */
.ligne-part.eteinte .nom-part,
.ligne-part.eteinte .heures-part { color: var(--text-muted); text-decoration: line-through; }
.ligne-part.eteinte .pastille-part { background: none !important; box-shadow: inset 0 0 0 2px var(--border-champ); }

.parts-eteintes { margin: 0; font-size: var(--taille-courant); color: var(--text-muted); }
.parts-eteintes:empty { display: none; }

/* ---- le dossier d'un site a disparu du volet le 18/09/2026 --------------
   Décision 7 du 07/09 levée : choisir un site ne change plus le volet, qui
   garde l'ensemble des sites et la liste décroissante. Le détail d'un site est
   dans l'infobulle de la carte, et il n'y est qu'une fois. Les règles qui
   cachaient les cartons, la période et la liste sont parties avec lui.
   ================================================================== */

/* ---- la ligne « Toute la ville », en tête de la liste, 19/09 -------------
   Elle est une ligne de liste comme les autres, avec les mêmes trois nombres,
   et elle s'en distingue par un filet dessous plutôt que par une couleur : ce
   qui la sépare est sa PLACE dans le choix, pas une categorie. */
/* ⚠️ UN POCHAGE D'UNE AUTRE COULEUR, demandé par Victor le 22/09/2026. La ligne
   se distinguait par son trait et sa graisse, ce qui ne se voit qu'en comparant
   deux lignes voisines : « Toute la ville » n'est pas un site de plus dans la
   liste, c'est ce qui les contient tous.

   ⚠️ ET CE N'EST NI LE BLEU NI L'AMBRE, VOLONTAIREMENT. Le bleu de l'accent dit
      le SURVOL de cette même liste, et l'ambre dit ailleurs qu'une année est
      incomplète : reprendre l'un ou l'autre ferait dire deux choses à une
      couleur. Ce violet très pâle ne sert qu'ici.

   ⚠️ LE SURVOL GARDE LE DESSUS, sans quoi la seule ligne de la liste qui ne
      réagirait pas au survol serait justement celle qu'on vise le plus.

   ⚠️ ET LE FOND S'ÉCRIT SUR `.ligne-site.ligne-ville`, JAMAIS SUR
      `.ligne-ville` SEUL. `.ligne-site` pose `background: var(--bg-primary)`
      quatre cents lignes plus bas : à spécificité égale, la dernière règle du
      fichier gagne, et le pochage ne paraissait pas. C'est le piège déjà payé
      sur le plancher des champs de saisie, décrit en fin de fichier, et il se
      reconnaît à ceci qu'il ne se voit qu'à l'écran rendu. Deux classes valent
      (0,2,0) contre (0,1,0), et l'ordre cesse de décider.

   ⚠️ QUAND LA PAGE DU PARC EST OUVERTE, `.ligne-site.actif` L'EMPORTE, et c'est
      voulu : elle vaut aussi (0,2,0) et vient après. La ligne courante se lit
      alors comme toutes les autres lignes courantes. */
.ligne-site.ligne-ville {
  border-bottom: 2px solid var(--border-color);
  margin-bottom: 0.35rem;
  padding-bottom: 0.5rem;
  font-weight: 600;
  background: var(--fond-total);
  border-left-color: var(--barre-total);
}
/* 🔴 L'ARDOISE REMPLACE LE VIOLET À 7 % LE 23/09, ET LE MESURE EXPLIQUE POURQUOI.
      Le violet rendait rgb(246, 243, 248), à deux ou sept unités par canal du
      gris des panneaux : invisible, et Victor ne repérait pas la tuile. Il a
      choisi l'ardoise parmi quatre fonds (arbitrage 2) : le seul qui se repère
      sans rien vouloir dire d'autre, le violet désignant déjà « les sites qui
      pèsent » sur la carte. La barre de gauche est grise : la tuile n'est pas
      un site (arbitrage 17).

   ⚠️ LE SURVOL BLEUIT LA BORDURE ET GARDE L'ARDOISE. Le repère demandé ne doit
      disparaître à aucun moment, pas même sous la souris. Un fond plus foncé
      au survol était le premier jet ; mesuré le 23/09, AUCUN ne garde les trois
      chiffres de la tuile au-dessus de 4,5:1 (RGAA 3.2) : à #cbd5e1, le bleu
      tombait à 3,72:1. L'ardoise elle-même a été éclaircie pour la même raison,
      de #e2e8f0 (bleu à 4,48:1) à #e5eaf1 (pire texte à 4,56:1).

   ⚠️ ET LA SÉLECTION SE DIT PAR UN CONTOUR BLEU, PAS PAR UN FOND. Le style
      `.ligne-site.actif` (fond bleu clair) écrasait le fond propre de la tuile
      au moment précis où l'on est sur sa page. Trois classes, (0,3,0),
      l'emportent sur `.ligne-site.actif` quel que soit l'ordre du fichier. */
.ligne-site.ligne-ville:hover {
  background: var(--fond-total);
  border-color: var(--accent-secondary);
  border-left-color: var(--barre-total);
}
.ligne-site.ligne-ville.actif {
  background: var(--fond-total);
  border-color: var(--border-color);
  border-left-color: var(--barre-total);
  border-left-width: 3px;
  outline: 2px solid var(--accent-primary);
  outline-offset: -2px;
}

/* Un retour NOMMÉ, pas une croix : on sait où il ramène avant de l'employer. */
.lien-retour {
  align-self: flex-start;
  border: 0;
  background: none;
  padding: 0;
  color: var(--texte-bleu);
  font-size: var(--taille-courant);
  font-weight: 600;
}
.lien-retour:hover { text-decoration: underline; }

/* ⚠️ CE QUI DÉCRIT LA CARTE DISPARAÎT AILLEURS, ET C'EST TOUT.
   Les quatre chiffres, leur dépliant, la période et la phrase des premiers
   décrivent ce que les CERCLES montrent : ailleurs ils décriraient un écran
   qu'ils ne commandent pas. */
body:not([data-ecran="carte"]) .barre-laterale > .periode-active,
body:not([data-ecran="carte"]) .barre-laterale > .badges,
body:not([data-ecran="carte"]) .barre-laterale > .ventilation,
body:not([data-ecran="carte"]) .barre-laterale > .compte-sites { display: none; }

/* ⚠️ LA LISTE, ELLE, RESTE SUR L'ONGLET « SITES », depuis le 19/09/2026. Elle y
   change de rôle : sur la carte elle DÉCRIT ce qu'on voit, ici elle CHOISIT ce
   qu'on regarde. C'est la décision 4 du 18/09, et c'est aussi ce qui remplit la
   bande vide que Victor a signalée le même jour : 394 px, 21 % de son écran,
   parce que sept règles cachaient les enfants du volet et aucune le volet.

   ⚠️ NE PAS Y REMETTRE `.badges` : les quatre cartons du volet décrivent le
      PARC, et la fiche au centre décrit un SITE. Les deux à l'écran, ce sont
      deux séries de cartons aux mêmes mots portant des nombres différents.
      Arbitré par Victor le 19/09, option B, après le dossier retiré la veille
      pour exactement ce motif. */
body:not([data-ecran="carte"]):not([data-ecran="site"]):not([data-ecran="parc"])
  .barre-laterale > .barre-liste,
body:not([data-ecran="carte"]):not([data-ecran="site"]):not([data-ecran="parc"])
  .barre-laterale > .liste-sites { display: none; }

/* ---- les quatre chiffres du parc ---------------------------------------
   Ils remplacent le grand chiffre sur la page « Ensemble des sites », et sur
   elle seule : décision de Victor du 18/09/2026, parce que cette page et le
   volet de la carte décrivent le même périmètre et doivent le dire avec les
   mêmes mots.

   ⚠️ QUATRE DE FRONT ICI, contre une grille 2 x 2 dans le volet, et ce n'est
      pas une incohérence : la page fait 906 px de large, le volet 460. Mesuré,
      chaque carton reçoit 209 px dans les deux cas, ce qui est exactement la
      largeur à laquelle « 6 392 602 € » tient sur une ligne. */
.parc-chiffres { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.6rem; }
.parc-chiffres[hidden] { display: none; }

.parc-carton {
  border: 1px solid var(--border-champ);
  border-radius: var(--radius-sm);
  background: var(--bg-secondary);
  padding: 0.7rem 0.8rem;
  display: flex; flex-direction: column; gap: 0.1rem; min-width: 0;
}
.parc-carton-valeur { font-size: var(--taille-sous-titre); font-weight: 700; line-height: 1.15;
                      font-variant-numeric: tabular-nums; }
/* Les deux montants pèsent plus que les deux comptes : c'est l'argent qu'on
   vient chercher ici, et le cran au-dessus existe dans l'échelle. */
.parc-carton.gros .parc-carton-valeur { font-size: var(--taille-titre); }
.parc-carton-libelle { font-size: var(--taille-mention); color: var(--text-muted);
                       overflow-wrap: anywhere; }
/* ⚠️ PAS DE REHAUSSEMENT ICI, ET C'EST UNE MESURE QUI L'INTERDIT. Le libelle
   des deux gros cartons a ete porte a 14 px demi-gras le 19/09, comme sur le
   volet. Vu a l'ecran le jour meme : sur la FICHE d'un site, la colonne est
   etroite et « fonctionnement » se coupait au milieu du mot.

       « fonctionnement » a 12 px normal ......  84 px    tient
       « fonctionnement » a 14 px demi-gras ... 102 px

       fenetre 1600 px ... place 200 px ... entier
       fenetre 1280 px ... place 111 px ... entier
       fenetre 1250 px ... place 102 px ... juste
       fenetre 1220 px ... place  94 px ... COUPE, « fonctionnem / ent »

   Le volet de la carte, lui, offre 209 px : il garde le rehaussement, qui est
   ce que Victor avait demande en regardant l'infobulle. Voir
   `.badge.gros .badge-libelle` et `.popup-mesure-nom`.

   ⚠️ ET LA VRAIE CAUSE EST AILLEURS, non traitee : sur la meme fiche, le bloc
      d'identite tombe a 26 px de large et neuf de ses mots se coupent. Mesure
      faite avec ET sans le rehaussement, a l'identique : cette rangee etait
      cassee avant. La reparer rendrait la place, et le rehaussement pourrait
      alors revenir ici. */
.parc-carton-source { font-size: var(--taille-mention); font-weight: 600;
                      color: var(--accent-primary); }

/* Sous le volet replié, ou sur un écran étroit, quatre de front ne tiennent
   plus : la grille repasse en 2 x 2, comme dans le volet. */
@media (max-width: 1100px) {
  .parc-chiffres { grid-template-columns: 1fr 1fr; }
}

/* ---- l'attente ---------------------------------------------------------
   L'icône tourne d'un huitième de tour à la fois, sur les huit pastilles que
   `iconeAttente()` dessine : la rotation continue donne un flou, la rotation
   par crans donne l'impression d'un mécanisme, ce qui se lit mieux en petit.

   ⚠️ ELLE S'ARRÊTE SOUS `prefers-reduced-motion`, comme `fiche-emergence` et
      `barres-poussent`. L'information ne vit jamais dans la seule animation :
      le texte qui l'accompagne dit déjà ce qu'on attend. */
@keyframes attente-tourne { to { transform: rotate(360deg); } }

.icone-attente {
  width: 1em; height: 1em; flex: none;
  animation: attente-tourne 1s steps(8, end) infinite;
  color: var(--accent-primary);
}

/* ⚠️ À LA PLACE DES CHIFFRES QU'ELLE FAIT ATTENDRE, L'ICÔNE PREND LEUR TAILLE.
   Le `1em` ci-dessus la met à 14 px dans un cartouche ; ici elle monte au cran
   du titre et se voit de l'autre bout de l'écran.

   C'est tout l'écart entre une icône PRÉSENTE et une icône VUE : mesuré le
   18/09, la première version tournait bel et bien, à 14 px, en x = 1283, quand
   le chiffre absent que l'on fixait était en x = 480. Victor a dû le signaler
   deux fois, et il avait raison les deux fois. */
/* 🔴 ET DEPUIS LE 23/09, ELLE EST AU CENTRE DE LA FENÊTRE (configuration B de
   Victor, voir `ouvrirAttente()` dans dom.js) : une carte encadrée de bleu, et
   autour d'elle SEULEMENT une zone floue qui s'estompe vers l'extérieur. Le
   voile sur toute la page a été retiré le soir même, à sa demande : le reste de
   l'écran reste net. Le texte est foncé, et rien de tout cela ne prend de clic. */
/* ⚠️ AU NIVEAU 1190 : au-dessus du volet et du rail (1100), qui coupaient la
   carte en deux au premier essai ; sous la pilule et la bande des années
   (1200), qui restent lisibles : on voit l'année qu'on vient de demander. Les
   modales (1300) passent devant. */
.attente-fenetre {
  position: fixed;
  inset: 0;
  display: flex; align-items: center; justify-content: center;
  pointer-events: none;
  z-index: 1190;
}
.attente-fenetre[hidden] { display: none; }
.attente-fenetre-bloc { position: relative; }
/* Le halo déborde de la carte de 4 rem de chaque côté, floute ce qu'il couvre,
   et s'efface en ellipse : pas de bord net, donc pas de second cadre autour du
   premier. */
.attente-fenetre-halo {
  position: absolute;
  inset: -4rem -5rem;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  /* Blanchi à 20 % et non plus 45, le 23/09 au soir : à 45 %, on ne voyait
     à travers la carte translucide que ce halo blanc. */
  background: rgba(241, 245, 249, 0.2);
  -webkit-mask-image: radial-gradient(closest-side, #000 55%, transparent 100%);
  mask-image: radial-gradient(closest-side, #000 55%, transparent 100%);
}
.attente-fenetre-carte { position: relative; }
.attente-fenetre-carte {
  display: flex; align-items: center; gap: 1rem;
  padding: 1.4rem 2.2rem;
  /* Translucide, demandé par Victor le 23/09 au soir, puis plus encore le
     même soir : blanc à 40 % au lieu de 62. Le flou propre à la carte, allégé
     avec lui, garde le texte lisible quel que soit ce qui passe derrière. */
  background: rgba(255, 255, 255, 0.4);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  border: 2px solid var(--accent-primary);
  border-radius: var(--radius-md, 14px);
  box-shadow: var(--shadow-flottant);
  font-size: var(--taille-sous-titre); font-weight: 600;
  color: var(--text-primary);
}
.attente-fenetre-carte .icone-attente { font-size: var(--taille-vedette); }

/* Le voile d'attente : posé sur le PANNEAU, donc sur la zone même où le
   contenu va paraître. Il ne cache rien, il se superpose, et il laisse passer
   les clics pour qu'une attente ne devienne jamais un blocage. */
.voile-attente {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.7rem;
  font-size: var(--taille-sous-titre);
  color: var(--text-muted);
  background: rgba(255, 255, 255, 0.72);
  pointer-events: none;
  z-index: 5;
}
.voile-attente .icone-attente { font-size: var(--taille-titre); }

/* ⚠️ LES DEUX ATTENTES EN LIGNE SONT MONTÉES AU CRAN DU VOILE LE 21/09/2026,
      ET LE MOTIF EST UNE MESURE. Elles tournaient à 14 px d'icône et 12 px de
      texte, le plus petit cran de l'échelle, celui des libellés accessoires,
      quand le voile des panneaux Agents, Analyses et Graphique disait la même
      attente à 24 px et 20 px. Victor, sur l'infobulle de la carte : « il faut
      mettre l'icône et le texte en plus grands, car ils sont presque
      invisibles ». Il avait déjà dû le signaler deux fois le 18/09 sur les
      chiffres du parc, pour exactement la même raison.

      Cela termine la décision du 18/09, « une seule attente, six écrans qui la
      disent pareil » : elle portait sur la forme et sur le seuil de 300 ms, pas
      sur la taille, et c'est par ce trou que le défaut est passé. Les quatre
      formes d'attente de l'application disent désormais la même chose à la même
      taille. */
.attente-en-ligne {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: var(--taille-sous-titre);
}
.attente-en-ligne .icone-attente { font-size: var(--taille-titre); }
.attente-texte { font-size: var(--taille-sous-titre); color: var(--text-muted); }

@media (prefers-reduced-motion: reduce) {
  .icone-attente { animation: none; }
}

/* ---- tuiles de totaux -------------------------------------------------- */
/* Une seule définition, réutilisée partout où un chiffre est mis en avant. */

/* ⚠️ UNE GRILLE 2 x 2, ET DES COLONNES EN `1fr`, JAMAIS EN PIXELS.
   Décidé le 18/09/2026, quatre chiffres au lieu de trois. `1fr 1fr` donne aux
   quatre cartons la même largeur et les fait rétrécir ensemble quand on tire la
   poignée du volet, ce que demandait Victor. Mesuré : 207 px par carton à
   460 px de volet, contre 100 px si les quatre étaient de front, largeur à
   laquelle le seul mot « fonctionnement » ne tient déjà plus.

   ⚠️ ET LA GRILLE BOITE À TROIS CARTONS, un trou en bas à droite quand une
      source est éteinte. C'est connu, constaté sur la maquette du 18/09, et
      accepté : `1fr 1fr` garde alors deux cartons pleine largeur en haut et un
      en bas à gauche, ce qui reste lisible. */
.badges { display: flex; gap: 0.5rem; }

/* ⚠️ LA GRILLE NE VAUT QUE POUR LE VOLET, PAS POUR TOUS LES `.badges`.
   D'autres écrans portent le même conteneur avec trois cartons, et la règle
   posée sans portée les passait eux aussi en 2 x 2. Trouvé à l'écran le 18/09,
   pas à la relecture : le volet était juste, et le défaut vivait ailleurs dans
   la page. Sept cartons portent cette classe, quatre seulement sont ici. */
.barre-laterale > .badges { display: grid; grid-template-columns: 1fr 1fr; }
.barre-laterale > .badges > .badge[hidden] { display: none; }

.badge {
  flex: 1;
  border: 1px solid var(--border-champ);
  border-radius: var(--radius-sm);
  padding: 0.6rem 0.5rem;
  background: var(--bg-secondary);
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  text-align: left;
}

.badge:disabled { cursor: default; }
.badge:not(:disabled):hover { box-shadow: var(--shadow-sm); }

.badge-valeur { font-size: var(--taille-sous-titre); font-weight: 700; line-height: 1.2; }
.badge-libelle { font-size: var(--taille-mention); color: var(--text-muted); overflow-wrap: anywhere; }

/* ⚠️ LE LIBELLE DES DEUX GROS CHIFFRES PESE UN CRAN DE PLUS, DEPUIS LE 19/09.
   Demande de Victor : « mettre mieux en evidence le texte Investissement et
   fonctionnement sous les gros chiffres du haut ». Mesure faite sur la largeur
   REELLE du texte et non sur sa boite, qui remplit la colonne et ne dit rien :
   le mot occupait 78 px quand la mention qui le suit en occupe 135. Il etait la
   ligne la plus etroite ET la plus pale d'un bloc de trois.

   ⚠️ SEULS LES DEUX GROS, ET PAS LES QUATRE. « heures » et « interventions » ne
      sont pas vises par la demande, et les rehausser aussi effacerait la
      hierarchie que la classe `gros` etablit. Contraste mesure : 7,58:1 contre
      5,51, RGAA 4.1.2 critere 3.2, l'un et l'autre conformes. */
.badge.gros .badge-libelle {
  font-size: var(--taille-courant);
  font-weight: 600;
  color: var(--text-secondary);
}

/* Le libellé de source, sous le nom du chiffre. Il change avec les deux
   interrupteurs : « régie + ext », « régie », « externes ».

   ⚠️ IL NE SE DISTINGUE PAS PAR LA TAILLE, MAIS PAR LA COULEUR ET LA GRAISSE.
      Le premier jet portait 0.66rem, et le banc l'a refusé : toute taille passe
      par un jeton de l'échelle à six crans, sans quoi le texte du téléphone ne
      se règle plus depuis un seul endroit. Le cran en dessous de
      `--taille-mention` n'existe pas, et la règle est d'arrondir vers le haut.
      Le bleu de l'application et la graisse suffisent à le détacher du libellé
      qu'il suit. */
.badge-source { font-size: var(--taille-mention); font-weight: 600; color: var(--accent-primary); }
.badge-source:empty { display: none; }

/* ⚠️ LES TROIS CARTONS N'ONT PLUS DE COULEUR, ET C'EST UNE RÈGLE DE
   VOCABULAIRE, PAS UN GOÛT. Ils portaient un bleu, un vert et un ambre pour se
   distinguer les uns des autres ; leur libellé le faisait déjà.

   Le coût de ces trois teintes était ailleurs : l'application se sert des mêmes
   pour dire une ATTENTION. Le `#f59e0b` du carton « coût » est, sur l'écran
   Agents, celui de « il manque du temps » ; le `#22c55e` du carton « heures »
   est celui de « le compte y est ». Un coût sur fond ambre se lisait donc comme
   une alerte, et le mot « alerte » n'était écrit nulle part. Relevé le 08/09
   par un audit d'interface, vérifié dans activite.js, table `COULEURS`.

   La règle qui reste : bleu, vert, ambre et rouge disent un ÉTAT, jamais une
   catégorie. Une catégorie se dit par son nom.

   ⚠️ LE CARTON QUI SE CLIQUE DOIT LE DIRE AUTREMENT. Le coût déplie la
   ventilation, et c'était la couleur qui, seule, le laissait pressentir. Elle
   partie, il lui faut une marque à lui : voir .badge-action juste dessous. */
.badge-valeur { color: var(--text-primary); }

/* La marque d'une commande, sur le seul carton qui en est une. Le chevron
   tourne quand la ventilation est ouverte : il dit l'état, pas seulement
   l'existence du geste. `aria-expanded` le disait déjà à qui écoute ; il fallait
   le dire aussi à qui regarde. */
.badge-action { position: relative; cursor: pointer; }
.badge-action::after {
  content: '▾';
  position: absolute;
  top: 0.35rem;
  right: 0.45rem;
  font-size: var(--taille-mention);
  color: var(--text-muted);
}
.badge-action[aria-expanded="true"]::after { transform: rotate(180deg); }
.badge-action:not(:disabled):hover { border-color: var(--accent-primary); }
/* La rotation s'anime pour qui le veut bien, et saute pour les autres : c'est
   la convention déjà suivie par #annonce et par la fiche de la carte. L'état,
   lui, ne dépend jamais de l'animation. */
@media (prefers-reduced-motion: no-preference) {
  .badge-action::after { transition: transform 0.15s ease; }
}

.ventilation {
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  padding: 0.5rem 0.65rem;
  background: var(--bg-secondary);
  font-size: var(--taille-courant);
}

.ligne-ventilation {
  display: grid;
  grid-template-columns: 1fr auto 3rem;
  gap: 0.5rem;
  padding: 0.2rem 0;
  color: var(--text-secondary);
}

.ligne-ventilation .part { text-align: right; color: var(--text-muted); }

/* Le second niveau du dépliant : Personnel et Fournitures sous Régie. Le
   retrait et la taille disent la subordination sans ajouter de trait, et les
   deux lignes restent lisibles parce qu'elles gardent la même grille. */
.ligne-ventilation.retrait { padding-left: 1rem; font-size: var(--taille-mention); }

.compte-sites { font-size: var(--taille-courant); color: var(--text-muted); }

/* Le compte d'équipements du référentiel, sous la réponse d'un site (30/08).
   Discret : c'est un repère, pas un chiffre de période. */
.site-compte-equipements { margin: 0.4rem 0 0; font-size: var(--taille-courant); color: var(--text-muted); }
.site-compte-equipements:empty { display: none; }


/* ---- la liste des sites ------------------------------------------------- */

/* Elle porte trois choses depuis P2.8 — la mesure, la recherche, le masquage
   des sites génériques — là où elle n'en portait que deux. `wrap` plutôt qu'une
   largeur négociée : la colonne fait 400 px sur ce poste, mais rien ne le
   garantit, et une case à cocher rejetée hors du cadre serait une commande
   perdue. Elle passe à la ligne, elle ne disparaît pas. */
.barre-liste { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }

/* Anciennement `.tri-sites`, quand ce sélecteur ne commandait que l'ordre de la
   liste. Il dimensionne aussi les cercles depuis P2.8 : le nom de la classe
   suit, sans quoi la prochaine lecture croira que la carte a sa commande
   ailleurs. */
.reglage-mesure { display: flex; align-items: center; gap: 0.35rem; flex-shrink: 0; }
.reglage-mesure > span { font-size: var(--taille-mention); font-weight: 600; color: var(--text-secondary); }

.reglage-mesure select,
.recherche-sites input {
  border: 1px solid var(--border-champ);
  border-radius: var(--radius-sm);
  padding: 0.3rem 0.45rem;
  font-size: var(--taille-courant);
  background: var(--bg-primary);
  color: var(--text-primary);
}

/* ⚠️ UNE BASE DE 12 REM, ET NON `flex: 1` SEUL. La barre porte trois commandes
   sur 426 px utiles : « Mesure » et sa liste, la recherche, et la case des
   sites génériques. Avec une base à zéro, la recherche prenait ce qui restait,
   c'est-à-dire 46 px, et son invite « Nom ou code du site… » s'y lisait « Nor ».
   Mesuré à l'écran le 07/09, invisible au banc.

   La base la fait passer À LA LIGNE plutôt que se laisser écraser : la barre est
   déjà en `flex-wrap`, elle n'attendait qu'une raison de s'en servir. */
.recherche-sites { flex: 1 1 12rem; min-width: 0; display: flex; }
.recherche-sites input { width: 100%; }

/* min-height: 0 et le défilement porté par la LISTE, pas par la barre entière :
   sinon les totaux et la recherche partent vers le haut dès qu'on descend, et
   l'on cherche en aveugle. */
.liste-sites {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

/* Un BOUTON, pas une div : la ligne se clique, donc elle doit être atteignable
   au clavier et s'annoncer comme actionnable.

   ⚠️ SEULE FRONTIÈRE DE COMPOSANT LAISSÉE EN --border-color, et c'est un choix
   argumenté. Le RGAA 3.3 vise la frontière NÉCESSAIRE pour identifier le
   composant ; ici elle ne l'est pas : le liseré gauche de 3 px en
   --accent-primary (5,17:1 sur blanc, 6 px quand la ligne est choisie) et
   l'étiquette de code sur fond bleu identifient déjà la ligne sans ambiguïté.
   Passer le contour à --border-champ aurait donné cent quatre lignes cerclées
   de gris sombre — une grille de tableur — pour une exigence que le liseré
   satisfait déjà. */
.ligne-site {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-areas: 'code nom' 'code chiffres';
  align-items: center;
  gap: 0.15rem 0.55rem;
  text-align: left;
  width: 100%;
  padding: 0.45rem 0.6rem;
  border: 1px solid var(--border-color);
  border-left: 3px solid var(--accent-primary);
  border-radius: var(--radius-sm);
  background: var(--bg-primary);
}

.ligne-site:hover { background: var(--accent-light); }
.ligne-site:focus-visible { outline: 2px solid var(--accent-primary); outline-offset: 1px; }

/* La ligne choisie. Elle se distingue du simple survol : le survol dit « ma
   souris est là », la sélection dit « c'est ce site que la carte montre ».
   Un liseré plus épais et un fond franc, pour qu'on la retrouve d'un coup
   d'œil en descendant aux flèches. */
.ligne-site.actif {
  background: var(--accent-light);
  border-color: var(--accent-secondary);
  border-left-width: 6px;
}

.ligne-site-code {
  grid-area: code;
  align-self: center;
  background: var(--accent-primary);
  color: #fff;
  font-size: var(--taille-mention);
  font-weight: 700;
  padding: 0.15rem 0.35rem;
  border-radius: 4px;
}

.ligne-site-nom {
  grid-area: nom;
  font-size: var(--taille-courant);
  font-weight: 600;
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ligne-site-chiffres { grid-area: chiffres; display: flex; gap: 0.7rem; font-size: var(--taille-mention); }
.chiffre-int { color: var(--texte-bleu); }
.chiffre-h { color: var(--texte-vert); }
.chiffre-eur { color: var(--texte-ambre); }

.liste-vide { font-size: var(--taille-courant); color: var(--text-muted); padding: 0.5rem 0; }

/* ---- erreurs ----------------------------------------------------------- */

.bandeau-erreur {
  position: fixed;
  left: 50%;
  bottom: 1.25rem;
  transform: translateX(-50%);
  max-width: min(40rem, calc(100vw - 2rem));
  padding: 0.65rem 1rem;
  border-radius: var(--radius-sm);
  background: var(--accent-red-light);
  color: #991b1b;
  box-shadow: var(--shadow);
  font-size: var(--taille-courant);
  z-index: 1000;
}

/* Le MÊME bandeau, quand il annonce une réussite plutôt qu'un échec.
   Un second nœud aurait été un second endroit à tenir d'accord ; ici seule la
   teinte change, et signaler() retire la classe pour qu'une erreur survenant
   après un enregistrement ne s'affiche jamais en vert.
   ⚠️ La couleur n'est PAS seule à porter le sens : le texte dit « enregistré »
   (RGAA 3.1). Contraste vérifié — #14532d sur #d1fae5 rend 8,03:1. */
.bandeau-erreur.bandeau-succes {
  background: var(--accent-green-light);
  color: #14532d;
}

.note {
  margin-top: 0.6rem;
  font-size: var(--taille-mention);
  line-height: 1.5;
  color: var(--text-muted);
  max-width: 52rem;
}

/* ---------------------------------------------------------------- graphique */
.barre-graphique {
  display: flex; align-items: center; gap: 1.2rem; flex-wrap: wrap;
  margin-bottom: 0.9rem;
}

/* Trois boutons collés plutôt qu'un menu déroulant : on compare les pas en
   cliquant de l'un à l'autre, et un menu ajoute un geste à chaque essai. */
.segments { display: inline-flex; border: 1px solid var(--border-champ);
  border-radius: var(--radius-sm); overflow: hidden; background: var(--bg-primary); }
.segments button {
  border: 0; background: transparent; cursor: pointer;
  padding: 0.4rem 0.85rem; font-size: var(--taille-courant); font-weight: 600;
  color: var(--text-secondary); border-right: 1px solid var(--border-color);
}
.segments button:last-child { border-right: 0; }
.segments button:hover { background: var(--bg-tertiary); }
.segments button.actif { background: var(--accent-primary); color: #fff; }

/* ---- les barres poussent depuis le bas ----------------------------------
   Demandé par Victor le 07/09, repris de RégiVision.

   ⚠️ UNE SEULE TRANSFORMATION, sur le groupe qui porte toutes les barres, et
      non une par rectangle : il y en a jusqu'à sept mille en pas quotidien. Le
      point d'origine est posé sur la ligne de base par le dessin, si bien que
      la mise à l'échelle verticale les fait sortir du sol.

   ⚠️ ELLE NE PORTE QUE SUR LA PRÉSENTATION. Les hauteurs, les totaux écrits et
      l'infobulle sont à leur valeur finale dès le premier instant : c'est un
      « transform », rien d'autre. Un écran dont l'état dépendrait d'une horloge
      est le défaut que ce projet a payé quatre fois en août. */
@keyframes barres-poussent {
  from { transform: scaleY(0); }
  to   { transform: scaleY(1); }
}
.barres-poussent { animation: barres-poussent 420ms cubic-bezier(0.22, 1, 0.36, 1); }

/* ⚠️ QUI DEMANDE MOINS D'ANIMATION N'EN A PAS. Les barres apparaissent alors à
   leur hauteur, ce qui est l'état final : rien n'est perdu. */
@media (prefers-reduced-motion: reduce) {
  .barres-poussent { animation: none; }
}

/* ---- l'infobulle fixée --------------------------------------------------
   ⚠️ FIXÉE, ELLE CESSE DE FUIR DEVANT LE CURSEUR, sans quoi on ne pourrait
      jamais l'atteindre pour cliquer dedans. Elle reprend donc les événements
      de pointeur, que la bulle volante refuse. */
.bulle-graphique.fixee {
  pointer-events: auto;
  box-shadow: var(--shadow-flottant);
  border-color: var(--accent-primary);
}
/* Une part de la bulle fixée mène à son site, sur la période du seau. Le
   curseur le dit, et seulement quand elle est fixée : survolée, elle ne mène
   nulle part. */
.bulle-graphique.fixee .bulle-part { cursor: pointer; border-radius: var(--radius-sm); }
.bulle-graphique.fixee .bulle-part:hover { background: var(--bg-secondary); }

/* LA PÉRIODE PRÉCÉDENTE, décision 11 du 07/09.
   Pointillés et non trait plein : un trait plein se lirait comme une seconde
   donnée du même rang que les barres. Le pointillé dit « repère », pas
   « mesure ». Encre foncée plutôt qu'une couleur : les couleurs sont prises
   par les parts, et une de plus voudrait dire qu'il en existe une sixième. */
.ligne-precedente {
  fill: none;
  stroke: var(--text-primary);
  stroke-width: 2;
  stroke-dasharray: 5 4;
  stroke-linejoin: round;
  opacity: 0.75;
}

/* ⚠️ Vide depuis la décision 10 : la légende est descendue dans le volet. La
   règle reste, parce que le nœud reste, et qu'un jour le téléphone pourrait le
   reprendre. Elle ne dessine rien tant qu'il est vide. */
.legende-domaines { display: flex; gap: 0.9rem; flex-wrap: wrap; flex: 1; }
.legende-item { display: inline-flex; align-items: center; gap: 0.35rem;
  font-size: var(--taille-courant); color: var(--text-secondary); }
.pastille { width: 0.7rem; height: 0.7rem; border-radius: 3px; display: inline-block; }

/* Le cadre porte position: relative, pas la toile : la toile défile
   horizontalement en pas quotidien, et l'infobulle doit rester accrochée au
   cadre plutôt que de partir avec le défilement. */
.cadre-graphique { position: relative; }
.toile-graphique {
  overflow-x: auto; overflow-y: hidden;
  border: 1px solid var(--border-color); border-radius: var(--radius-sm);
  background: var(--bg-primary);
  /* Aucune marge intérieure : le SVG est dessiné à la largeur exacte du cadre,
     et six pixels de padding suffisaient à faire apparaitre une barre de
     defilement sur un graphique qui tenait deja. Le graphique porte ses
     propres marges internes. */
  padding: 0;
}
.histogramme { display: block; }
.histogramme .grille { stroke: var(--border-color); stroke-width: 1; }
.histogramme .axe { stroke: var(--text-muted); stroke-width: 1; }
/* ---- les tailles de texte du graphique ---------------------------------- */
/*
   ⚠️ CES CINQ TAILLES ONT ÉTÉ RELEVÉES LE 06/09, ET ELLES VIENNENT DE
   RÉGIVISION. Victor : « les histogrammes étaient beaucoup plus jolis ». La
   comparaison, faite dans le code des deux applications, tient dans un tableau :

       ce qu'on écrivait   RégiVision   ce qu'on écrit désormais
       graduations   11 px    14 px      14 px
       unité d'axe   11 px    16 px      15 px
       étiquettes     9 px    14 px      13 px
       seconde ligne  8 px    absente    10 px  (gardée : c'est notre ajout)
       total          10 px   13 px      13 px

   Presque moitié moins gros partout : c'était cela, la différence, et non un
   effet de bibliothèque.

   ⚠️ CE QUE CE CHANGEMENT COÛTE, ET QUE VICTOR A ARBITRÉ LE 06/09. La petite
   police venait de son arbitrage du 28/08 : il voulait que CHAQUE mois soit
   nommé sous sa barre, et seuls 9 px le permettaient. À 13 px, une longue
   période n'a plus la place de tout nommer. La règle est désormais celle de
   RégiVision : **alléger une étiquette sur deux ou trois plutôt que rétrécir
   la police**. Une étiquette lisible qui manque vaut mieux que douze illisibles.

   La seconde ligne, elle, reste : elle marque les changements d'année, et
   RégiVision n'a rien de tel.
*/
.histogramme .graduation { fill: var(--text-muted); font-size: var(--taille-courant); }
.histogramme .unite-axe { fill: var(--text-secondary); font-size: var(--taille-appuye); font-weight: 600; }
.histogramme .etiquette { fill: var(--text-secondary); font-size: var(--taille-courant); }
.histogramme .etiquette.faible { fill: var(--text-muted); font-size: var(--taille-mention); }
.histogramme .total-barre { fill: var(--text-primary); font-size: var(--taille-courant); font-weight: 700; }
.bulle-graphique {
  position: absolute; pointer-events: none; z-index: 5;
  background: var(--bg-primary); border: 1px solid var(--border-color);
  border-radius: var(--radius-sm); box-shadow: var(--shadow);
  padding: 0.5rem 0.7rem; font-size: var(--taille-courant); min-width: 11rem;
}
.bulle-graphique strong { display: block; margin-bottom: 0.35rem; color: var(--texte-bleu); }
.bulle-ligne { display: flex; align-items: center; gap: 0.4rem; padding: 0.1rem 0;
  color: var(--text-secondary); }
.bulle-ligne b { margin-left: auto; color: var(--text-primary); font-variant-numeric: tabular-nums; }
.bulle-total { border-top: 1px solid var(--border-color); margin-top: 0.3rem;
  padding-top: 0.3rem; font-weight: 700; color: var(--text-primary); }

/* ⚠️ LA NOTE DE COUVERTURE NE PARAÎT PLUS À L'ÉCRAN. Arbitrage de Victor du
   28/08, sur les deux tailles : « l'information en jaune n'est pas nécessaire
   pour l'utilisateur ».

   Ce qu'elle disait, et ce qu'on accepte de ne plus montrer : les heures
   pointées ne couvrent pas toutes les interventions retenues, parce que
   certaines n'ont aucun pointage et ne peuvent donc figurer sur aucune barre.
   C'est l'un des trois écarts de chiffres que le projet doit savoir expliquer.

   ⚠️ IL RESTE CALCULÉ. L'API rend toujours « couverture », et `poserCouverture`
   écrit toujours le texte : seul l'affichage est retiré, d'une ligne. Retirer
   la ligne le rend, ce qui n'aurait pas été vrai si l'on avait supprimé le
   calcul. La commande qui donne le chiffre, quand on en aura besoin pour
   préparer un rendez-vous : /api/v1/agregats/chronique, champ « couverture ».

   Les styles d'origine sont conservés en commentaire dans l'historique git ;
   les réécrire ici serait du code mort. */
/* ⚠️ LE MASQUAGE VISE LA NOTE DU GRAPHIQUE, ET ELLE SEULE. La règle du 28/08
   écrivait `.note-couverture { display: none; }` : elle emportait aussi
   #activite-note, l'avertissement « jours sans seuil » de l'écran Activité,
   qui posait `hidden = false` sans jamais reparaître. Constaté le 30/08. Les
   styles de la classe reviennent donc (repris du commit b7b035f), et seul
   l'élément du graphique reste éteint. */
#graphique-couverture { display: none; }
.note-couverture { margin-top: 0.7rem; font-size: var(--taille-courant); color: var(--text-muted); }
.note-couverture.avertissement {
  color: var(--text-secondary); background: var(--accent-amber-light);
  border-left: 3px solid var(--accent-amber);
  padding: 0.5rem 0.7rem; border-radius: var(--radius-sm);
}

/* La ligne de synthèse (30/08) : discrète, sous le graphique, dans les termes
   des barres. Rien à voir avec la note de couverture ci-dessus, qui reste
   masquée : celle-ci récapitule, celle-là expliquerait un écart. */
.synthese-graphique {
  margin: 0.5rem 0 0;
  font-size: var(--taille-courant);
  color: var(--text-muted);
}
.synthese-graphique:empty { display: none; }

/* ---- le panneau « Affiner » — P2.5 --------------------------------------
   Aucune couleur nouvelle : les jetons du 26/08 portent déjà les contrastes
   mesurés (RGAA 3.2 et 3.3), et en inventer une ici rouvrirait le relevé.
   ------------------------------------------------------------------------- */

/* ⚠️ « .bouton-affiner » A DISPARU LE 07/09. Le bouton est devenu la puce
   « + critère » et porte « .puce .puce-ajout ». La règle qui le dessinait en
   bouton carré à côté de la période n'avait plus d'objet.
   Son compte d'axes, lui, reste : voir « .compte-axes » juste dessous. */

/* Sur les écrans que les axes ne filtrent pas (l'activité par agent,
   l'administration), le bouton, ses pastilles et le bandeau agent disparaissent
   avec les domaines : un filtre visible qui ne filtre rien est pire qu'un
   filtre absent. L'écran est posé par app.js dans `data-ecran` du corps de
   page. Vu sur la recette le 06/09/2026. */
body[data-ecran="activite"] .puce-ajout,
body[data-ecran="activite"] .filtres-actifs,
body[data-ecran="activite"] .bandeau-agent,
body[data-ecran="admin"] .puce-ajout,
body[data-ecran="admin"] .filtres-actifs,
body[data-ecran="admin"] .bandeau-agent { display: none; }

/* L'accès du téléphone n'existe QUE là-bas : sur grand écran, le bouton
   « Affiner » de la barre d'onglets ouvre la fenêtre, et la feuille des filtres
   n'est même pas affichée. Le bloc @media le révèle. */
.acces-affiner { display: none; }

/* Le nombre d'axes contraints. Il ne s'affiche QUE s'il y en a — filtres.js
   vide le texte sinon — et le sélecteur `:empty` retire alors la pastille de la
   mise en page, pour qu'elle ne laisse pas un rond gris permanent. */
.compte-axes {
  background: var(--accent-primary);
  color: #fff;
  border-radius: 999px;
  font-size: var(--taille-mention);
  font-weight: 600;
  min-width: 1.15rem;
  padding: 0.05rem 0.35rem;
  text-align: center;
}
.compte-axes:empty { display: none; }

/* ---- les pastilles des filtres actifs -----------------------------------
   Sous la barre, hors du panneau : c'est ce qui interdit qu'un filtre agisse
   sans se montrer (D9). Elles restent visibles panneau fermé. */
.filtres-actifs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  padding: 0.5rem 1.25rem;
  background: var(--bg-secondary);
  border-bottom: 1px solid var(--border-color);
}

.pastille-filtre {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  background: var(--accent-light);
  color: var(--text-primary);
  border: 1px solid var(--accent-secondary);
  border-radius: 999px;
  padding: 0.2rem 0.55rem;
  font-size: var(--taille-courant);
  cursor: pointer;
}

.pastille-filtre:hover { background: #fff; }
/* La croix est plus grande que le texte : c'est elle qu'on vise, et une cible
   de 10 px au doigt se manque. */
.pastille-croix { font-size: var(--taille-appuye); line-height: 1; color: var(--text-secondary); }

/* Le bandeau du filtre agent. Il DIT que les chiffres ont changé de sens, donc
   il se place au-dessus d'eux et non dans le panneau qu'on vient de fermer.
   L'ambre plutôt que le rouge : ce n'est pas une erreur, c'est une précision
   sans laquelle on lirait mal. */
.bandeau-agent {
  margin: 0;
  padding: 0.5rem 1.25rem;
  background: var(--accent-amber-light);
  color: var(--text-primary);
  font-size: var(--taille-courant);
  border-bottom: 1px solid var(--accent-amber);
}

/* Le titre d'un groupe d'agents dans le panneau. Ce n'est PAS une case : on ne
   coche pas un atelier ici, l'axe « Atelier » le fait et ne filtre pas la même
   chose. Il se distingue donc des valeurs par sa graisse et son retrait. */
.groupe-agents {
  margin: 0.5rem 0 0.15rem;
  font-size: var(--taille-mention);
  font-weight: 600;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
.groupe-agents:first-of-type { margin-top: 0.25rem; }

/* ---- ce qui reste de la fenêtre ------------------------------------------

   ⚠️ LA FENÊTRE MODALE A DISPARU LE 07/09, décision 6. « Affiner » était un
   dialogue posé au-dessus de l'écran, avec son fond sombre, son piège à focus
   et l'attribut inert sur les frères. Ces trois-là ont coûté un écran
   entièrement figé en ligne le 28/08 : inert est héréditaire, il avait été posé
   sur un ancêtre de la fenêtre, et plus rien ne répondait au clic.

   Les axes vivent maintenant dans un panneau de la question, qui pousse la
   liste au lieu de la recouvrir. Il ne reste ici que le pied, qui porte
   « Tout effacer », et que mobile.js déplace toujours dans la feuille des
   filtres du téléphone. */
.fenetre-affiner-pied {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.7rem 0 0;
  margin-top: 0.4rem;
  border-top: 1px solid var(--border-color);
}

.groupe-axes { overflow-y: auto; padding: 0.25rem 0; }

/* ---- un axe -------------------------------------------------------------
   Replié par défaut : quatorze axes dépliés feraient une page, et l'on ne
   cherche presque jamais dans plus d'un ou deux à la fois. */
.axe { border-bottom: 1px solid var(--border-color); }
.axe:last-child { border-bottom: none; }

.axe-entete {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  background: none;
  border: none;
  padding: 0.6rem 1rem;
  font-size: var(--taille-appuye);
  color: var(--text-primary);
  text-align: left;
  cursor: pointer;
}
.axe-entete:hover { background: var(--bg-tertiary); }
.axe-nom { flex: 1; }

/* Le compte de valeurs cochées SUR L'AXE REPLIÉ. Sans lui, replier un axe
   cacherait ce qu'il contraint, ce que D9 interdit. */
.axe-compte {
  background: var(--accent-primary);
  color: #fff;
  border-radius: 999px;
  font-size: var(--taille-mention);
  font-weight: 600;
  padding: 0.05rem 0.4rem;
}
.axe-chevron { color: var(--text-muted); font-size: var(--taille-mention); }

.axe-corps { padding: 0 1rem 0.7rem; display: flex; flex-direction: column; gap: 0.1rem; }

.axe-recherche {
  border: 1px solid var(--border-champ);
  border-radius: var(--radius-sm);
  padding: 0.35rem 0.5rem;
  font-size: var(--taille-courant);
  margin-bottom: 0.35rem;
  width: 100%;
}

.valeur {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.25rem 0.15rem;
  font-size: var(--taille-courant);
  color: var(--text-secondary);
  cursor: pointer;
}
.valeur:hover { background: var(--bg-tertiary); }
.valeur input { accent-color: var(--accent-primary); cursor: pointer; }
.valeur-nom { flex: 1; }

/* Le code, ajouté seulement quand deux valeurs portent le même libellé. Plus
   discret que le libellé : il départage, il ne se lit pas en premier. */
.valeur-code { color: var(--text-muted); font-size: var(--taille-mention); }

/* Le nombre d'interventions. En chiffres tabulaires : sans cela, une colonne de
   nombres de largeurs différentes ne s'aligne pas, et l'ordre décroissant cesse
   de se lire d'un coup d'œil. */
.valeur-nombre {
  color: var(--text-muted);
  font-size: var(--taille-courant);
  font-variant-numeric: tabular-nums;
}

/* Une valeur cochée qui ne porte plus rien sur la période. Elle RESTE cochable,
   c'est ce qui permet de la retirer et de comprendre pourquoi l'écran est vide.
   L'italique la distingue sans recourir à la seule couleur (RGAA 3.1). */
.valeur-absente .valeur-nom { font-style: italic; color: var(--text-muted); }

.axe-plus,
.axe-attente {
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: var(--taille-courant);
  padding: 0.3rem 0.15rem;
  text-align: left;
}
.axe-plus { color: var(--accent-primary); cursor: pointer; text-decoration: underline; }
.axes-vide { padding: 1rem; color: var(--text-muted); font-size: var(--taille-courant); }

/* ---- les deux boutons d'un axe ------------------------------------------ */
/*
   « Cocher les N trouvées » et « Effacer cet axe », posés le 06/09 en reprise
   des boutons d'axe de RégiVision. Elle en a deux par axe, « Tout » et
   « Aucun » ; mesuré, son « Tout » ne change aucun résultat, puisqu'un axe tout
   coché et un axe vide filtrent pareil. Voir `actionsDAxe` dans filtres.js.

   Ils sont ENCADRÉS, contrairement au lien « Voir les N autres » juste
   au-dessous : celui-ci déplie la même liste, ceux-ci modifient le filtre. Deux
   effets différents ne se présentent pas de la même façon.
*/
/* ---- les en-têtes de domaine dans un axe -------------------------------- */
/*
   Repris de RégiVision, qui coiffe son axe « corps d'état » par domaine. Chez
   elle, cet axe est nommé dans le code ; ici la décision se prend sur les
   DONNÉES, à l'exécution, et vaut pour n'importe quel axe. Voir
   `grouperParDomaine` dans filtres.js.

   Un en-tête, pas un titre de section : il sépare des cases à cocher, il ne
   commande rien. D'où le texte discret et le filet, plutôt qu'une graisse qui
   le ferait passer pour un bouton au milieu de lignes cliquables. */
.axe-groupe {
  margin: 0.6rem 0 0.15rem;
  padding: 0.15rem 0.15rem 0.2rem;
  border-bottom: 1px solid var(--border-color);
  font-size: var(--taille-mention);
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--text-secondary);
}
.axe-groupe:first-child { margin-top: 0; }

.axe-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  padding: 0.4rem 0.15rem 0.2rem;
}
.axe-action {
  border: 1px solid var(--border-champ);
  border-radius: var(--radius-sm);
  background: var(--bg-primary);
  color: var(--texte-bleu);
  font-size: var(--taille-courant);
  font-weight: 600;
  /* 2 rem : atteignable au doigt dans la feuille du téléphone, où ce panneau
     est une feuille tirée du bas et non une fenêtre. */
  min-height: 2rem;
  padding: 0.3rem 0.65rem;
  cursor: pointer;
}
.axe-action:hover { background: var(--accent-light); }
.axe-action:focus-visible { outline: 2px solid var(--accent-primary); outline-offset: 2px; }

/* ---- écran Activité par agent ------------------------------------------- */
/* La barre de choix d'un écran : un contenu qui dépend d'un objet qu'on
   désigne. Elle venait de .barre-bilan, retirée en P2.2 avec l'écran Bilan —
   ce qui laisse Activité par agent seul de sa forme. Le jour où un troisième
   écran en voudra une, c'est ici qu'il faudra la reprendre. */
.barre-activite {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: 0.9rem;
  margin-bottom: 1rem;
}
.barre-activite label { display: flex; flex-direction: column; gap: 0.25rem; }
.barre-activite label > span { font-size: var(--taille-mention); font-weight: 600; color: var(--text-secondary); }
.barre-activite select, .barre-activite input[type="search"] {
  min-width: 12rem; padding: 0.4rem 0.6rem;
  border: 1px solid var(--border-champ); border-radius: var(--radius-sm);
  background: var(--bg-primary); color: var(--text-primary); font-size: var(--taille-courant);
}
.barre-activite #activite-agent { min-width: 20rem; }
/* Le second « Agent » est là pour les lecteurs d'écran, qui annoncent alors la
   liste ; l'afficher deux fois à l'œil ne dirait rien de plus. */
/* Visible pour les technologies d'assistance, invisible à l'œil.

   ⚠️ NI display: none NI visibility: hidden : l'un et l'autre retirent le nœud
   de l'arbre d'accessibilité, donc un lecteur d'écran ne lirait plus rien. Il
   faut que l'élément occupe une place réelle, fût-elle d'un pixel. */
.hors-ecran,
.barre-activite .invisible {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

/* ---- le message de confirmation, vu ET entendu -------------------------- */
/*
   ⚠️ CE NŒUD EXISTAIT DÉJÀ, ET IL ÉTAIT INVISIBLE À L'ŒIL.

   `#annonce` porte depuis le 26/08 les comptes rendus de l'application, et
   `annoncer()` l'écrit à douze endroits : « 172 lignes exportées », « 78 sites
   avec activité », « les 1 030 équipements sont affichés ». Tout cela était
   prononcé par un lecteur d'écran et vu par personne : la moitié accessible du
   travail était faite, la moitié visible ne l'était pas.

   Décision de Victor du 06/09, en reprise du `toast` de RégiVision : on rend
   visible le texte qui existe, plutôt que d'ajouter un second mécanisme à
   côté. Un seul appel, un seul texte, vu et entendu : les deux ne peuvent pas
   diverger, puisqu'il n'y en a qu'un. C'est la règle de la maison, et c'est ce
   qui manquait à la page d'un site avant le 27/08, où deux endroits disaient
   le même chiffre et ont fini par différer de 660 interventions.

   ⚠️ OPACITÉ, ET SURTOUT PAS `display: none` NI `visibility: hidden`. Les deux
   retirent le nœud de l'arbre d'accessibilité : la région cesserait d'être
   annoncée, et l'on aurait rendu visible en cassant l'audible. L'opacité, elle,
   ne touche pas à cet arbre. Un contrôle du banc l'interdit désormais.

   ⚠️ IL SE POSE AU-DESSUS DU BANDEAU D'ERREUR, jamais à sa place. L'erreur
   garde le créneau du bas, à 1,25 rem : c'est le message le plus important, et
   il ne doit jamais être recouvert par un compte rendu.
*/
#annonce {
  position: fixed;
  left: 50%;
  bottom: 4.5rem;
  /* Le décalage de départ fait monter le bandeau plutôt que le faire
     apparaître sur place : un mouvement court dit d'où vient le message. */
  transform: translateX(-50%) translateY(0.75rem);
  max-width: min(32rem, calc(100vw - 2rem));
  padding: 0.6rem 1rem;
  border-radius: var(--radius-sm);
  background: var(--text-primary);
  color: var(--bg-primary);
  box-shadow: var(--shadow-lg);
  font-size: var(--taille-courant);
  /* Sous le bandeau d'erreur, qui est à 1000. */
  z-index: 999;
  opacity: 0;
  /* Il ne prend jamais un clic : il flotte au-dessus de la page, et un
     message transitoire qui avale un geste est un défaut, pas une aide. */
  pointer-events: none;
}
#annonce.visible { opacity: 1; transform: translateX(-50%) translateY(0); }

/* ⚠️ ON N'ANIME QUE LE GLISSEMENT, JAMAIS L'OPACITÉ. C'est la même leçon que
   `annoncer()` porte déjà dans dom.js, et que la carte a payée quatre fois en
   août : **ne pas faire dépendre un état OBSERVABLE d'une horloge qu'on ne
   contrôle pas.**

   Mesuré le 06/09, dans un onglet piloté : avec `transition: opacity`, la
   classe `visible` était posée, la règle `#annonce.visible { opacity: 1 }`
   gagnait le cascade, `getAnimations()` ne rendait AUCUNE animation en cours,
   et l'opacité calculée restait à 0 pendant cinq secondes entières. Le même
   relevé avec `transition: none` rendait 1 immédiatement. Le message était
   donc écrit, annoncé, et invisible.

   En n'animant que `transform`, l'opacité bascule d'un coup : si le compositeur
   ne produit aucune image, le message est quand même là, simplement sans son
   glissement. Le décor peut manquer, jamais l'information. */
@media (prefers-reduced-motion: no-preference) {
  #annonce { transition: transform 0.2s ease; }
}
.navigation-agent { display: flex; align-items: center; gap: 0.4rem; }
.navigation-agent button {
  border: 1px solid var(--border-champ); border-radius: var(--radius-sm);
  background: var(--bg-primary); color: var(--text-secondary);
  padding: 0.35rem 0.6rem; cursor: pointer; font-size: var(--taille-courant);
}
.navigation-agent button:hover:not(:disabled) { background: var(--bg-tertiary); }
.navigation-agent button:disabled { opacity: 0.35; cursor: default; }
.navigation-agent span { font-size: var(--taille-courant); color: var(--text-muted);
  font-variant-numeric: tabular-nums; min-width: 4rem; text-align: center; }

/* ⚠️ ELLES NE S'ÉTIRENT PLUS, ET C'EST LA DÉCISION 14 QUI L'IMPOSE. La rangée
   en portait NEUF et « flex: 1 1 8rem » les répartissait bien. Il en reste
   TROIS : la même règle leur donnait un tiers de l'écran chacune, soit des
   cartes de 500 px pour y écrire « 183 ». Vu à l'écran le 07/09.
   Elles gardent donc une largeur de lecture et s'alignent à gauche. */
.tuiles-activite { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-bottom: 1.1rem; }
.tuiles-activite .tuile { flex: 0 1 11rem; }
/* Trois tuiles portent une couleur, et une seule raison : ce sont les trois
   termes de la même phrase, les heures dues se répartissant en pointées, en
   absences, et en heures dont on ne sait rien.
   ⚠️ Les deux premières classes ne servent plus depuis la décision 14, où les
      tuiles « Pointées », « Congés » et « Sans information » ont laissé place à
      la phrase et à la barre. Les couleurs, elles, sont reprises telles quelles
      par la barre : c'est le même vocabulaire, il ne doit pas se dédoubler. */
.tuile-pointee   .tuile-valeur { color: var(--texte-bleu); }
.tuile-absence   .tuile-valeur { color: #6b7280; }
.tuile-sans-info .tuile-valeur { color: #b45309; }
.tuile-alerte    .tuile-valeur { color: var(--texte-rouge); }

/* ---- les antennes du logiciel financier ---------------------------------
   136 lignes à trancher, rangées par ce qu'elles coûtent de se tromper. La
   mise en page suit cette idée : le POIDS d'abord, à gauche et en chiffres
   alignés, puis le nom, puis la cible, puis l'état.

   ⚠️ UNE GRILLE ET NON UN TABLEAU : chaque ligne porte deux listes déroulantes
      et deux boutons, ce qu'une cellule de tableau tient mal. Les colonnes sont
      fixes pour que l'œil descende en ligne droite. */
.antennes-barre {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  margin: 0.8rem 0 0.6rem;
}
.antennes-recherche { flex: 1 1 16rem; display: flex; }
.antennes-recherche input {
  width: 100%;
  padding: 0.45rem 0.6rem;
  border: 1px solid var(--border-champ);
  border-radius: var(--radius-sm);
  font-size: var(--taille-appuye);
}

.antennes-liste {
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  overflow: hidden;
  /* Cent trente-six lignes ne tiennent pas sous les yeux : la liste défile en
     elle-même, et le compte au-dessus reste en place. */
  max-height: 32rem;
  overflow-y: auto;
}

.antenne-ligne {
  display: grid;
  grid-template-columns: 6.5rem 9rem minmax(0, 1fr) 6rem auto;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0.7rem;
  border-bottom: 1px solid var(--border-color);
  font-size: var(--taille-courant);
}
.antenne-ligne:last-child { border-bottom: 0; }
.antenne-ligne:hover { background: var(--bg-secondary); }

.antenne-euros {
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--text-primary);
}
.antenne-nom { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.antenne-cible { display: flex; gap: 0.4rem; min-width: 0; }
.antenne-cible select {
  flex: 1 1 0;
  min-width: 0;
  padding: 0.3rem 0.4rem;
  border: 1px solid var(--border-champ);
  border-radius: var(--radius-sm);
  background: var(--bg-primary);
  font-size: var(--taille-courant);
}
.antenne-etat { font-size: var(--taille-courant); color: var(--text-secondary); }
.antenne-actions { display: flex; gap: 0.35rem; }
.antenne-actions button { padding: 0.25rem 0.5rem; font-size: var(--taille-courant); }

/* ⚠️ L'ÉTAT SE VOIT PAR UN LISERÉ, PAS PAR UN FOND. Un fond teinté sur 136
   lignes ferait un damier ; et la couleur ne porte rien seule, puisque l'état
   est écrit en toutes lettres dans sa colonne (RGAA 3.1). */
.antenne-confirmee { border-left: 3px solid var(--accent-green); }
.antenne-sans_objet { border-left: 3px solid var(--border-champ); }
.antenne-proposee { border-left: 3px solid var(--accent-primary); }
.antenne-a_faire { border-left: 3px solid var(--accent-amber); }

@media (max-width: 60rem) {
  .antenne-ligne { grid-template-columns: 5.5rem minmax(0, 1fr); grid-auto-flow: row; }
  .antenne-cible { grid-column: 1 / -1; }
  .antenne-actions { grid-column: 1 / -1; }
}

/* ---- les deux lectures du taux d'information ---------------------------- */
/*
   ⚠️ LE TAUX DIT TOUJOURS QUELLE LECTURE IL MONTRE. Chez RégiVision le même
   nombre change selon un clic invisible, et son intitulé ne bouge pas : deux
   captures du même agent s'y contredisent sans qu'on sache pourquoi. Ici la
   bascule est SOUS le chiffre, en toutes lettres, et le fond change avec elle.

   La seconde lecture teinte le bloc en ambre, EN PLUS du texte. La couleur
   seule ne dit rien ; elle sert à ce qu'on remarque de loin qu'on n'est plus
   sur la lecture par défaut. Contraste vérifié : #92400e sur #fef3c7 rend
   8,15:1, très au-dessus du seuil du critère RGAA 3.2.

   ⚠️ ET C'EST BIEN UNE ATTENTION, PAS UNE CATÉGORIE : la règle du 08/09 tient,
   l'ambre dit ici « tu n'es pas sur la lecture par défaut ». */
.taux-vedette.restreint { background: #fef3c7; }
.taux-vedette.restreint .taux-valeur { color: #92400e; }
.taux-vedette.restreint .taux-intitule,
.taux-vedette.restreint .taux-assiette { color: #92400e; }


/* ---- la tête de l'écran Agents, refaite le 08/09 ------------------------
   Victor : « beaucoup trop de texte dans la partie supérieure, personne ne lira
   et cela ne donne pas envie de regarder la suite ».

   Cinq blocs empilés en sont devenus trois : le taux en vedette avec sa
   bascule, deux compteurs, la barre. La phrase de trente-deux mots a disparu,
   l'encadré aussi, et la définition du taux est passée derrière un bouton. */
.tete-agent {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: 0.9rem;
  margin-bottom: 1rem;
}

/* Le chiffre de l'écran. Assez grand pour être lu d'un coup en parcourant les
   agents à la flèche, ce qui est le geste réel de cet écran. */
.taux-vedette {
  flex: 1 1 18rem;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.35rem;
  padding: 0.9rem 1.1rem;
  background: var(--accent-light);
  border-radius: var(--radius-sm);
}
.taux-valeur {
  font-size: var(--taille-vedette);
  font-weight: 700;
  line-height: 1;
  color: var(--texte-bleu);
  font-variant-numeric: tabular-nums;
}
.taux-intitule {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: var(--taille-courant);
  font-weight: 600;
  color: var(--text-secondary);
}
/* L'assiette : sur quoi le taux est calculé. Elle suit la lecture choisie, et
   c'est ce qui empêche un dénominateur écrit sous un taux qui varie. */
.taux-assiette { margin: 0; font-size: var(--taille-courant); color: var(--text-secondary); }
.taux-assiette:empty { display: none; }

/* Le bouton qui déplie la définition. Rond, discret, et de la taille d'une
   vraie cible malgré sa petitesse apparente. */
.bouton-aide {
  width: 1.4rem;
  height: 1.4rem;
  padding: 0;
  border: 1px solid var(--border-champ);
  border-radius: 50%;
  background: var(--bg-primary);
  color: var(--text-secondary);
  font-size: var(--taille-courant);
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
}
.bouton-aide:hover { background: var(--bg-tertiary); color: var(--text-primary); }

/* ⚠️ LE BOUTON NE DOIT PAS PORTER LA CASSE NI LA GRAISSE DE SON VOISIN. Il se
      pose à côté d'intitulés en capitales espacées (« COÛT COMPLET 2025 ») et de
      clés de carte d'identité en petit : sans ces trois lignes, il héritait de
      `text-transform` et son point d'interrogation devenait un caractère de
      titre, deux fois trop lourd pour ce qu'il est. */
.bouton-aide {
  text-transform: none;
  letter-spacing: normal;
  vertical-align: middle;
  flex: none;
}

/* LE TEXTE DÉPLIÉ A DISPARU LE 21/09/2026, avec les sept boutons qui
   l'ouvraient. Victor : « ce n'est pas la place où on indique ce type
   d'infos ». Les six textes attendent le menu d'aide, rangés dans
   `..\BatiFocale\documentation\textes-aide-en-attente.md` avec les trois
   acquis d'écran qu'il faudra reprendre, dont celui-ci, payé le 10/09 sur
   2 560 px : un texte déplié dans une rangée `flex-wrap` en devient une
   COLONNE, et `flex: 0 0 100%` ne le corrige pas. */

/* ⚠️ TOUJOURS DES BOUTONS RADIO, HABILLÉS EN SEGMENTS. La sémantique reste
   celle de deux réponses exclusives ; seule l'apparence change. L'entrée elle
   même est sortie de l'écran, et c'est le libellé qui porte l'état. */
.choix-lecture {
  display: inline-flex;
  border: 1px solid var(--border-champ);
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--bg-primary);
  padding: 0;
  margin: 0;
}
.choix-lecture label {
  display: flex;
  align-items: center;
  /* 2,25 rem : la même hauteur atteignable au doigt que les autres commandes
     de l'application. Une case à cocher nue fait 13 px. */
  min-height: 2.25rem;
  padding: 0 0.85rem;
  font-size: var(--taille-courant);
  font-weight: 600;
  color: var(--text-secondary);
  border-right: 1px solid var(--border-color);
  cursor: pointer;
}
.choix-lecture label:last-of-type { border-right: 0; }
.choix-lecture input { position: absolute; opacity: 0; pointer-events: none; }
.choix-lecture label:hover { background: var(--bg-tertiary); }
/* ⚠️ LE FOCUS SE VOIT, ET C'EST OBLIGATOIRE : l'entrée étant masquée, sans
   cette règle on parcourrait les deux choix au clavier sans savoir où l'on est
   (RGAA 10.7). */
.choix-lecture input:focus-visible + span { outline: 2px solid var(--accent-primary); outline-offset: 3px; }
.choix-lecture label:has(input:checked) { background: var(--accent-primary); color: #fff; }

/* La définition, dépliée par le bouton. Elle explique ce que mesure la lecture
   retenue, et elle change avec elle. */
.choix-lecture-phrase {
  margin: 0;
  font-size: var(--taille-courant);
  color: var(--text-secondary);
  max-width: 34rem;
}

/* ⚠️ L'ANNEAU DE RÉPARTITION A DISPARU LE 07/09, décision 14. Il montrait les
   trois mêmes parts que la barre qui le remplace, sur un cercle où l'œil doit
   estimer des angles : 72 %, 19 % et 8 % s'y comparaient mal. Il occupait en
   outre une tuile carrée pour trois nombres déjà écrits à côté.
   Ce qu'il portait de bon est gardé : les pourcentages restent du TEXTE, dans
   la légende de la barre, et la couleur ne porte rien seule (RGAA 3.1). */

/* Le calendrier et son panneau de détail, côte à côte. Le panneau se replie
   sous le graphique en écran étroit plutôt que de le comprimer. */
.cadre-activite { display: flex; gap: 0.9rem; align-items: flex-start; }
.toile-activite {
  flex: 1 1 auto; min-width: 0;
  overflow-x: auto; overflow-y: hidden;
  border: 1px solid var(--border-color); border-radius: var(--radius-sm);
  background: var(--bg-primary);
}
.toile-activite .histogramme { display: block; cursor: pointer; }
.toile-activite .grille { stroke: var(--border-color); stroke-width: 1; }
.toile-activite .axe { stroke: var(--text-muted); stroke-width: 1; }
.toile-activite .graduation { fill: var(--text-muted); font-size: var(--taille-mention); }
.toile-activite .mois { fill: var(--text-secondary); font-size: var(--taille-mention); }
.toile-activite .annee { fill: var(--text-primary); font-size: var(--taille-mention); font-weight: 700; }
.toile-activite .separateur { stroke: var(--border-color); stroke-width: 1; stroke-dasharray: 2 3; }
/* Le seuil est un TRAIT et non une zone : il marque une limite, pas une plage,
   et une bande colorée laisserait croire à une tolérance qui n'existe pas. */
.toile-activite .seuil { stroke: #1e293b; stroke-width: 1.4; }
#activite-curseur { fill: var(--accent-primary); opacity: 0.1; pointer-events: none; }

/* ⚠️ « flex: 0 0 17rem » NE SUFFIT PAS À BORNER CE PANNEAU, et l'oublier a
   coûté un débordement horizontal de toute la scène. Mesuré le 07/09 : le
   panneau faisait 1 319 px dans un conteneur de 1 014, parce que le libellé
   d'une intervention y est écrit sans retour à la ligne et que la base flex
   n'est pas un maximum. `max-width` et `min-width: 0` le tiennent vraiment. */
.detail-jour {
  flex: 0 0 17rem;
  max-width: 17rem;
  min-width: 0;
  border: 1px solid var(--border-color); border-radius: var(--radius-sm);
  background: var(--bg-primary); box-shadow: var(--shadow-sm);
}
.detail-jour * { min-width: 0; }
.detail-jour header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 0.55rem 0.7rem; border-bottom: 1px solid var(--border-color);
  font-size: var(--taille-courant); font-weight: 700;
}
.detail-jour header button {
  border: 0; background: none; cursor: pointer;
  font-size: var(--taille-sous-titre); line-height: 1; color: var(--text-muted); padding: 0 0.2rem;
}
.detail-jour header button:hover { color: var(--text-primary); }
#activite-detail-corps { padding: 0.6rem 0.7rem; }
.detail-verdict { display: flex; align-items: center; gap: 0.4rem;
  font-size: var(--taille-courant); font-weight: 600; margin-bottom: 0.5rem; }
.detail-ligne { display: flex; justify-content: space-between; gap: 0.6rem;
  font-size: var(--taille-courant); padding: 0.15rem 0; color: var(--text-secondary); }
.detail-ligne .valeur { color: var(--text-primary); font-variant-numeric: tabular-nums; }
.detail-ferie .valeur { color: #7c3aed; font-weight: 600; }
.detail-titre-liste { margin: 0.6rem 0 0.3rem; font-size: var(--taille-mention);
  font-weight: 700; color: var(--text-secondary); }
.detail-interventions { margin: 0; padding-left: 1.1rem; max-height: 14rem; overflow-y: auto; }
.detail-interventions li { font-size: var(--taille-mention); color: var(--text-secondary); padding: 0.05rem 0; }

/* ⚠️ LA MARGE EST PASSÉE AU-DESSOUS, parce que la légende est passée au-dessus
   de ce qu'elle explique le 08/09. Laissée en haut, elle collait à la frise et
   s'écartait des commandes dont elle n'a rien à dire. */
.legende-activite { display: flex; flex-wrap: wrap; gap: 0.5rem 1rem; margin: 0.2rem 0 0.6rem; }
.legende-activite .legende-item { font-size: var(--taille-courant); }
.trait-seuil { display: inline-block; width: 0.9rem; height: 0; border-top: 2px solid #1e293b; }

/* Le rappel des trois mesures du temps. Discret mais présent : c'est la seule
   réponse à « pourquoi ce total n'est pas celui du bandeau ? », et l'absence
   de réponse coûte la confiance dans les deux autres écrans. */
.note-mesure {
  margin-top: 0.8rem; font-size: var(--taille-courant); color: var(--text-secondary);
  background: var(--bg-tertiary); border-left: 3px solid var(--accent-primary);
  padding: 0.55rem 0.75rem; border-radius: var(--radius-sm);
}

@media (max-width: 60rem) {
  .cadre-activite { flex-direction: column; }
  .detail-jour { flex: 1 1 auto; width: 100%; }
}

/* =========================================================================
   LE TÉLÉPHONE
   =========================================================================

   Tout ce qui suit ne vaut qu'en dessous de 48 rem (768 px) : téléphones et
   petites tablettes en portrait. Au-dessus, pas une ligne ne s'applique et
   l'écran de bureau est exactement celui d'avant.

   CE QUI NE TENAIT PAS, ET POURQUOI

   La barre latérale faisait 400 px FIXES. Sur un écran de 390 px, elle prenait
   toute la largeur et la carte était écrasée à zéro : on arrivait sur une liste
   de sites sans jamais voir la carte. C'est ce que montrait la capture du
   24/08.

   LE PARTI RETENU, arbitré par Victor le 24/08 :

     · la carte occupe TOUT l'écran ;
     · la liste et les totaux montent depuis le bas, en trois positions ;
     · les filtres et la période vivent dans une feuille qu'on ouvre ;
     · l'onglet « Activité par agent » disparaît — données nominatives.

   ⚠️ UNE MESURE D'ÉCRAN, PAS UN CONTRÔLE D'ACCÈS. Masquer l'onglet Activité
   empêche de l'ouvrir par mégarde dans un couloir ; cela n'empêche personne
   d'appeler l'API. Le vrai cloisonnement est côté serveur, et il ne bouge pas
   d'ici.
   ========================================================================= */

@media (max-width: 48rem) {

  /* ---- LA PILULE SE MET À PLAT ------------------------------------------ */
  /*
     Sur 390 px, un objet flottant à coins ronds posé en haut à droite serait
     une bande de plus au-dessus de la carte, avec des marges en prime. Elle
     redevient donc ce qu'était l'en-tête : une barre pleine largeur, collée en
     haut, sans ombre ni arrondi.

     D'où le retour de la coque en colonne flexible : la pilule et la barre
     compacte reprennent le flux, et la scène occupe ce qui reste. Sur grand
     écran, rien de tout cela ne s'applique.
  */
  .application { display: flex; flex-direction: column; }

  .pilule {
    position: static;
    flex: none;
    gap: 0.5rem;
    padding: 0.4rem 0.6rem;
    border-width: 0 0 1px;
    border-radius: 0;
    box-shadow: var(--shadow-sm);
  }

  .conteneur-principal { position: relative; inset: auto; flex: 1; min-height: 0; }

  /* SUR 390 PX, LES DEUX MARQUES RESTENT, LES DEUX NOMS ÉCRITS S'EFFACENT.

     La pilule répond à deux questions, quelle application et chez quel client,
     et elle a quatre façons de le faire : le symbole BâtiFocale, son nom écrit,
     le logo de la collectivité, son nom écrit. Les quatre faisaient 583 px dans
     un cadre de 386 : mesuré, cela débordait de 197 px.

     ⚠️ MASQUÉS À L'ŒIL, PAS AUX LECTEURS D'ÉCRAN. display:none les retirerait
     de l'arbre d'accessibilité, et le logo de la collectivité, dont l'alt est
     vide précisément parce que le nom est écrit à côté, deviendrait le seul
     porteur d'une identité que plus rien n'annoncerait. */
  .pilule-marque .marque,
  .nom-collectivite-texte {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  /* La fraîcheur des données et les filets s'effacent : ce sont les premières
     choses dont on se passe le temps d'un coup d'œil à la carte. */
  .dernier-pointage,
  .pilule-filet { display: none; }

  /* Le compte reste, et prend la place qui reste : c'est lui qui porte la
     déconnexion, et la déconnexion ne se cherche pas. */
  .pilule-compte { margin-left: auto; }
  .nom-utilisateur {
    max-width: 7rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    display: inline-block;
  }

  /* Le menu s'ouvre vers le bas depuis la barre, et non depuis un objet
     flottant : il doit rester dans l'écran, d'où la butée à droite. */
  .menu-compte { right: 0; min-width: 11rem; }

  /* ---- les vues -------------------------------------------------------- */
  /* Elles gardent leur forme de pilule, mais se partagent la largeur restante :
     trois boutons de 0,82 rem tiennent, un quatrième ne tiendrait pas.
     ⚠️ « Agents » disparaît sur téléphone, décision D4 : données nominatives.
     C'est une mesure d'écran, PAS un contrôle d'accès. */
  .onglets { gap: 0.15rem; }
  .onglet { padding: 0.3rem 0.55rem; font-size: var(--taille-courant); }
  .onglet[data-onglet="activite"] { display: none; }

  /* ---- la question descend dans la feuille des filtres ------------------ */
  /*
     Tout ce que la question porte (domaines, période, mesure, logements, axes)
     est déplacé par mobile.js dans la feuille « Filtres », qui est l'endroit où
     l'on va déjà chercher ce qui restreint l'affichage. Le conteneur resterait
     donc vide, avec son trait de séparation et son espacement : une ligne creuse
     en tête du panneau qui monte du bas.

     ⚠️ Cette règle ne fait que garantir qu'il ne réapparaît pas si le
     déplacement n'a pas eu lieu. Ce n'est pas elle qui range les commandes. */
  .volet-question { display: none; }

  /* ⚠️ LA RÈGLE QUI REMETTAIT `margin-left: 0` ICI A DISPARU AVEC CELLE QU'ELLE
     ANNULAIT, le 13/09/2026. Elle existait parce que le grand écran portait une
     compensation de volet qu'il fallait défaire sur téléphone ; la compensation
     étant retirée, il n'y a plus rien à défaire.

     Mesuré avant de la retirer : sur 390 px, le panneau et la pile de commandes
     sont tous deux à 0 px, avec ou sans elle. Elle ne rangeait rien. */


  /* =======================================================================
     LA BARRE DU BAS, ET LE PLANCHER DE LECTURE.

     Arbitré par Victor le 13/09/2026, devant quatre publications sur la
     conception mobile : « la version téléphone ressemble trop à celle PC, or
     la conception doit être complètement différente ».

     ⚠️ ELLE L'ÉTAIT PAR CONSTRUCTION, ET C'EST MESURÉ. Cette feuille compte
        5 686 lignes, dont HUIT blocs pour le téléphone, et `mobile.js` ne
        conçoit rien : il déménage les nœuds du grand écran. La version
        téléphone ÉTAIT la version PC repliée.

     Ce que ce bloc change : la navigation quitte le haut de l'écran pour la
     ZONE DU POUCE. Sur un téléphone tenu d'une main, le haut de l'écran est
     l'endroit le plus difficile à atteindre, et c'est là que vivaient les
     quatre onglets.
     ======================================================================= */
  .barre-onglets {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    /* ⚠️ 1200, ET NON 40. La feuille qui monte du bas est a 1001, elle meme
       posee au-dessus des commandes de Leaflet qui sont a 1000. Une barre de
       navigation que le panneau recouvre n est plus une navigation. Vu a
       l ecran le 13/09 en reproduisant le demenagement. */
    z-index: 1200;
    display: flex;
    align-items: stretch;
    gap: 0.25rem;
    padding: 0.35rem 0.5rem;
    /* ⚠️ L'ENCOCHE DU BAS, SANS LAQUELLE LA BARRE PASSE SOUS LE TRAIT DE
       L'IPHONE. `env()` vaut zéro partout ailleurs : la ligne ne coûte rien
       aux appareils qui n'en ont pas. */
    padding-bottom: calc(0.35rem + env(safe-area-inset-bottom, 0px));
    background: var(--bg-primary);
    border-top: 1px solid var(--border-color);
    box-shadow: 0 -6px 18px -12px rgba(22, 33, 58, 0.5);
  }

  /* La pilule perd ses onglets : elle ne garde que la marque, la collectivité
     et le compte. C'est mobile.js qui les déplace, pas cette feuille. */
  .barre-onglets .onglets {
    flex: 1 1 auto;
    display: flex;
    gap: 0.25rem;
  }

  .barre-onglets .onglet {
    flex: 1 1 0;
    /* ⚠️ 56 px, ET CE N'EST PAS LE 48 px HABITUEL. Une barre de navigation se
       vise en marchant, souvent d'un pouce, parfois avec des gants. Les 48 px
       arbitrés pour le reste de l'application sont un plancher de confort ;
       ici c'est la cible la plus utilisée de l'écran. */
    min-height: 56px;
    padding: 0.3rem 0.2rem;
    /* 14 px : un libellé d'onglet n'est pas du texte courant, mais en dessous
       il cesse de se lire d'un coup d'œil au soleil. */
    font-size: var(--taille-courant);
    font-weight: 600;
    border-radius: var(--radius-sm);
  }
  .barre-onglets .onglet.actif { background: var(--accent-light); color: var(--accent-primary); }

  .barre-onglets .bouton-filtres {
    flex: 0 0 auto;
    min-height: 56px;
    border-radius: var(--radius-sm);
  }

  /* ⚠️ ET LA SCÈNE LUI LAISSE LA PLACE. Une barre fixe qui recouvre la fin
     d'une liste est un défaut qu'on ne voit qu'en bas de page, donc jamais en
     recette. 56 px de barre, ses marges, et l'encoche. */
  .conteneur-principal {
    padding-bottom: calc(4.5rem + env(safe-area-inset-bottom, 0px));
  }

  /* =======================================================================
     LE PLANCHER DE LECTURE DU TÉLÉPHONE.

     Arbitré le même jour : le plancher monte SUR TÉLÉPHONE SEULEMENT. Le
     grand écran garde sa densité, qui convient à un outil d'analyse où l'on
     compare beaucoup de lignes.

     ⚠️ 16 px SUR UN CHAMP N'EST PAS UN GOÛT, C'EST UN COMPORTEMENT DU
        NAVIGATEUR. Sous 16 px, Safari iOS ZOOME la page quand le doigt entre
        dans un champ, et n'en ressort pas seul. Mesuré sur cette feuille avant
        correction : sept commandes de saisie étaient en dessous, six listes
        déroulantes et un champ.

     Les 255 déclarations de taille de cette feuille sont passées aux jetons le
     14/09/2026 : voir L'ÉCHELLE TYPOGRAPHIQUE dans `:root`. Ce bloc ne pose
     donc plus que le plancher du TEXTE COURANT.

     ⚠️ LE PLANCHER DES CHAMPS DE SAISIE A ÉTÉ DÉPLACÉ EN FIN DE FICHIER, et
        il ne faut pas le ramener ici. Posé à cet endroit, il était défait par
        SIX règles, dont une qui laissait un menu déroulant à 12 px. Le détail
        du mécanisme est écrit avec le bloc, tout en bas.
     ======================================================================= */
  body,
  .liste-sites,
  .ligne-liste,
  .site-appui,
  .analyse-note { font-size: var(--taille-appuye); }

  /* ---- la barre compacte ----------------------------------------------- */
  .barre-mobile {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.6rem;
    background: var(--bg-primary);
    border-bottom: 1px solid var(--border-color);
  }

  .bouton-filtres {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    border: 1px solid var(--border-champ);
    border-radius: var(--radius-sm);
    background: var(--bg-primary);
    color: var(--text-primary);
    /* 2,2 rem de haut : en dessous, une cible tactile se rate. */
    padding: 0.5rem 0.7rem;
    min-height: 2.2rem;
    font-size: var(--taille-courant);
    font-weight: 600;
  }

  .compte-filtres {
    background: var(--accent-primary);
    color: #fff;
    border-radius: 999px;
    padding: 0.05rem 0.4rem;
    font-size: var(--taille-mention);
    font-variant-numeric: tabular-nums;
  }
  .compte-filtres:empty { display: none; }

  /* LA RECHERCHE SE REPLIE DERRIÈRE UNE LOUPE.
     Elle prenait ~300 px des 390 de la barre — plus des trois quarts — pour
     un geste occasionnel, pendant que la période, elle, n'y tenait pas.
     Repliée : largeur nulle, mais le champ reste dans le document, avec ses
     écouteurs et sa valeur. On ne le recrée pas à chaque ouverture — même
     principe que les commandes déplacées plutôt que recopiées. */
  .bouton-recherche {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 2.2rem;
    min-height: 2.2rem;
    padding: 0 0.5rem;
    font-size: var(--taille-appuye);
    line-height: 1;
    border: 1px solid var(--border-champ);
    border-radius: var(--radius-sm);
    background: var(--bg-primary);
    color: var(--text-primary);
    cursor: pointer;
    flex: 0 0 auto;
  }

  /* La loupe se marque quand la recherche est OUVERTE : sans cela, rien ne
     distingue le bouton qui a servi de celui qui attend. */
  .barre-mobile[data-recherche="ouverte"] .bouton-recherche {
    background: var(--bg-tertiary);
    border-color: var(--text-muted);
  }

  /* LA PASTILLE DE PÉRIODE, à droite de la barre — les 255 px libérés par la
     loupe. Elle ne rétrécit pas quand la recherche s'ouvre : c'est la
     recherche qui prend ce qui reste. Une période tronquée ne vaudrait rien. */
  .pastille-periode {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    margin-left: auto;
    padding: 0.35rem 0.6rem;
    min-height: 2.2rem;
    font-size: var(--taille-courant);
    font-weight: 700;
    white-space: nowrap;
    border: 1px solid var(--border-champ);
    border-radius: var(--radius-sm);
    background: var(--bg-tertiary);
    color: var(--text-primary);
    cursor: pointer;
    flex: 0 0 auto;
  }

  .recherche-mobile {
    flex: 0 0 0;
    width: 0;
    min-width: 0;
    overflow: hidden;
    opacity: 0;
    transition: flex-basis 0.15s ease, opacity 0.15s ease;
  }
  .barre-mobile[data-recherche="ouverte"] .recherche-mobile {
    flex: 1 1 auto;
    width: auto;
    opacity: 1;
  }
  .recherche-mobile input {
    width: 100%;
    min-height: 2.2rem;
    border: 1px solid var(--border-champ);
    border-radius: var(--radius-sm);
    padding: 0.4rem 0.6rem;
    /* 16 px, et pas moins : en dessous, iOS ZOOME sur le champ à la mise au
       point et l'on se retrouve avec une page décalée dont on ne sait plus
       revenir. C'est le piège mobile le plus classique. */
    font-size: var(--taille-appuye);
  }

  /* ---- la feuille des filtres ------------------------------------------ */
  .voile-filtres {
    position: fixed;
    inset: 0;
    z-index: 1100;
    background: rgba(15, 23, 42, 0.45);
  }

  .feuille-filtres {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    /* Au-dessus de tout le reste : c'est elle qu'on vient d'ouvrir. */
    z-index: 1101;
    max-height: 80vh;
    overflow-y: auto;
    background: var(--bg-primary);
    border-top-left-radius: 1rem;
    border-top-right-radius: 1rem;
    box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.18);
    padding: 0 0.9rem 0.9rem;
  }

  /* L'en-tête reste collé en haut, comme le pied reste collé en bas : sur un
     petit téléphone la feuille défile, et la croix partait hors de vue. On se
     retrouvait alors avec une feuille qu'on ne savait plus refermer autrement
     qu'en revenant tout en haut. Constaté sur un cadre de 557 px. */
  .feuille-filtres-entete {
    position: sticky;
    top: 0;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 0.6rem;
    padding-bottom: 0.5rem;
    background: var(--bg-primary);
    border-bottom: 1px solid var(--border-color);
  }

  .feuille-filtres-corps { display: flex; flex-direction: column; gap: 0.9rem; }

  /* Le pied reste COLLÉ EN BAS de la feuille : elle défile quand les réglages
     sont nombreux, et un bouton qu'il faut aller chercher en bas d'un défilement
     ne se trouve pas. */
  .feuille-filtres-pied {
    position: sticky;
    bottom: 0;
    margin-top: 0.9rem;
    padding-top: 0.7rem;
    background: var(--bg-primary);
    border-top: 1px solid var(--border-color);
  }

  .bouton-valider {
    width: 100%;
    /* 2,9 rem : une cible qu'on atteint du pouce sans regarder. */
    min-height: 2.9rem;
    border: 0;
    border-radius: var(--radius-sm);
    background: var(--accent-primary);
    color: #fff;
    font-size: var(--taille-appuye);
    font-weight: 600;
  }
  .bouton-valider:active { background: var(--accent-secondary); }

  /* Les commandes déplacées ici reprennent la pleine largeur : ce sont des
     cibles tactiles, plus des éléments d'une barre d'outils. */
  /* ⚠️ SUR TÉLÉPHONE, LE RAIL DEVIENT UNE GRILLE DANS LA FEUILLE. Il ne peut
     pas rester une colonne à droite : la scène y occupe toute la largeur, et
     11 rem en prendraient le tiers. Les carrés restent des carrés, et le
     nombre de colonnes suit la place. */
  .rail-domaines { display: none; }
  .feuille-filtres .groupe-domaines {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(5rem, 1fr));
    gap: 0.4rem;
  }
  .feuille-filtres .rail-titre { display: none; }
  /* ⚠️ LES DEUX DATES SUR UNE SEULE LIGNE — relevé par Victor sur son téléphone
     le 28/08. Le retour à la ligne libre plaçait « Du [date] au » sur une ligne
     et « [date] [Année…] » sur la suivante : les deux bornes d'une même période
     ne se lisaient plus ensemble, et le sélecteur d'année se retrouvait collé à
     la seconde date comme s'il la réglait.

     Une grille de trois colonnes tient les deux champs côte à côte, et la place
     ainsi rendue permet d'écrire « Année complète » en toutes lettres au-dessus
     du sélecteur — un mot que « Année… » seul laissait deviner. */
  .feuille-filtres .groupe-periode {
    display: grid;
    grid-template-columns: auto 1fr auto 1fr;
    align-items: center;
    gap: 0.4rem 0.45rem;
  }
  /* Le raccourci d'année passe sous les dates, sur toute la largeur : c'est une
     commande d'un autre genre, elle ne s'aligne pas avec les bornes. */
  .feuille-filtres .groupe-periode .hors-ecran-mobile,
  .feuille-filtres .groupe-periode .choix-annee { grid-column: 1 / -1; }
  .feuille-filtres .groupe-periode .etiquette-annee {
    display: block;
    grid-column: 1 / -1;
    font-size: var(--taille-courant);
    color: var(--text-secondary);
    margin-top: 0.25rem;
  }
  .feuille-filtres .groupe-periode input,
  .feuille-filtres .choix-annee { font-size: var(--taille-appuye); padding: 0.45rem 0.5rem; }

  /* ⚠️ UNE FEUILLE, DEUX VUES — ET C'EST LA FEUILLE DE STYLE QUI CHOISIT.
     mobile.js pose `data-vue` ; on cache ici ce que la vue ne demande pas.

     ⚠️ LA VUE « PÉRIODE » EST ÉCRITE PAR EXCLUSION, ET C'EST DÉLIBÉRÉ.
     Une première version nommait le groupe à cacher — `#groupe-domaines` — et
     laissait passer tout le reste : la feuille montrait encore « Métrique » et
     « Masquer sites génériques », que mobile.js y déménage aussi. Le doublon
     était réduit, pas supprimé. La règle dit donc « ce groupe-là, et rien
     d'autre » : ce qui viendra plus tard dans cette feuille — les onze axes de
     P2.5, par exemple — n'aura pas à venir s'ajouter ici pour être caché.

     `display: none` et non `hidden` : l'attribut `hidden` de #groupe-domaines
     appartient à ouvrirEcran() dans app.js, qui l'éteint sur les écrans où les
     domaines ne filtrent rien. Deux écrivains sur le même attribut finiraient
     par se contredire ; ici chacun garde le sien. */
  #feuille-filtres[data-vue="periode"] #feuille-filtres-corps > *:not(#groupe-periode) {
    display: none;
  }
  #feuille-filtres[data-vue="filtres"] #groupe-periode,
  #feuille-filtres[data-vue="filtres"] #groupe-axes,
  #feuille-filtres[data-vue="filtres"] .fenetre-affiner-pied { display: none; }

  /* LA TROISIÈME VUE — « Affiner », 28/08. Écrite PAR EXCLUSION comme celle de
     la période : elle montre les axes et leur pied, et rien d'autre. Ce qui
     viendra plus tard dans cette feuille n'aura pas à venir s'ajouter ici. */
  #feuille-filtres[data-vue="affiner"] #feuille-filtres-corps
    > *:not(#groupe-axes):not(.fenetre-affiner-pied) { display: none; }

  /* ---- l'écran Graphique sur téléphone — relevés de Victor, 28/08 --------

     ⚠️ LA LÉGENDE SUR UNE SEULE LIGNE. Elle se répartissait librement et
     prenait deux ou trois lignes sur 390 px, chacune volée à la hauteur du
     graphique — sur un écran où c'est précisément la hauteur qui manque. Une
     ligne unique qui défile la ramène à 20 px, quel que soit le nombre de
     domaines déclarés : un client qui en aurait dix ne perdrait pas six lignes.

     `-webkit-overflow-scrolling` n'est pas repris : le défilement inertiel est
     le défaut sur iOS depuis longtemps, et la propriété est obsolète. */
  .legende-domaines {
    flex-wrap: nowrap;
    overflow-x: auto;
    gap: 0.6rem;
    padding-bottom: 0.15rem;
  }
  .legende-domaines .legende-item { flex: 0 0 auto; font-size: var(--taille-mention); }
  .legende-domaines .pastille { width: 0.6rem; height: 0.6rem; }

  /* La barre du graphique s'empile : les trois boutons de pas d'abord, la
     légende ensuite, sans que l'une écrase l'autre. */
  .barre-graphique { gap: 0.4rem; }


  /* ---- « Affiner » sur téléphone ---------------------------------------
     ⚠️ IL A SON PROPRE BOUTON DEPUIS LE 28/08, et c'est un arbitrage renversé.
     P2.5 n'en voulait pas : « pas de quatrième commande dans une barre qui en
     porte déjà trois ». Victor l'a essayé sur son téléphone et l'a renversé.
     L'essai a montré ce que la maquette taisait : cinq cases de domaine suivies
     de dix axes font une feuille qu'on parcourt sans savoir où l'on est.

     Le bouton du GRAND ÉCRAN reste masqué : il vit dans la barre d'onglets, qui
     n'a pas la place ici. C'est `#bouton-affiner-mobile` qui prend le relais, et
     les deux mènent aux mêmes nœuds. */
  .puce-ajout { display: none; }

  /* ⚠️ L'ACCÈS AUX AXES EST DANS LA FEUILLE, PAS DANS LA BARRE. Arbitrage du
     28/08 après essai : « il sera rarement utilisé, il ne faut pas qu'il soit
     trop visible ». Un quatrième bouton dans la barre du haut aurait pris la
     place des trois commandes courantes ET se serait proposé trop.

     Discret, donc : bouton plein largeur en bas de la feuille « Filtres »,
     sans fond appuyé. Il ne paraît que dans cette vue-là. */
  /* ⚠️ `display` D'ABORD. La première version posait le padding et la bordure
     sans jamais lever le `display: none` du bloc général : le bouton n'existait
     alors NULLE PART, ni sur grand écran où c'est voulu, ni sur téléphone où
     c'est tout l'objet. Signalé par Victor le 28/08, « je ne trouve nulle part
     le bouton Affiner ». Habiller un élément qu'on n'a pas révélé ne se voit
     pas à la relecture : les deux déclarations sont vraies séparément. */
  .acces-affiner {
    display: block;
    padding: 0.6rem 0 0;
    border-top: 1px solid var(--border-color);
  }
  #feuille-filtres[data-vue="periode"] .acces-affiner,
  #feuille-filtres[data-vue="affiner"] .acces-affiner { display: none; }

  #bouton-affiner-mobile {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    width: 100%;
    justify-content: flex-start;
    padding: 0.6rem 0.7rem;
    font-size: var(--taille-appuye);
  }

  /* ⚠️ LES PASTILLES RESTENT MASQUÉES, ET IL FAUT SAVOIR CE QU'ON PERD. Une
     bande de pastilles au-dessus de la carte mangerait la hauteur qu'on vient
     de lui rendre. Ce qui tient la règle « jamais un filtre invisible » ici, ce
     sont les DEUX comptes : « Filtres » dit les domaines, « Affiner » dit les
     axes, et chaque feuille permet de les retirer. C'est le traitement que les
     domaines ont toujours eu sur téléphone. */
  .filtres-actifs { display: none; }

  /* Le pied « Tout effacer » suit les axes dans la feuille : sans lui, trois
     axes cochés ne se défont qu'un par un, en rouvrant chacun. */
  .feuille-filtres .fenetre-affiner-pied {
    border-top: 1px solid var(--border-color);
    padding: 0.6rem 0 0;
    justify-content: flex-start;
  }

  /* Les axes occupent la feuille comme les domaines : pleine largeur, et des
     cibles au doigt. La fenêtre, elle, ne s'ouvre jamais ici.

     ⚠️ PLUS DE TRAIT DE SÉPARATION depuis le 28/08 : il séparait les cases de
     domaine des axes, qui ne partagent plus la même vue. Un filet qui ne
     sépare plus rien devient un bord flottant en haut d'une feuille. */
  .feuille-filtres .groupe-axes { max-height: none; overflow: visible; }
  .feuille-filtres .axe-entete { padding: 0.7rem 0.2rem; font-size: var(--taille-appuye); }
  .feuille-filtres .axe-corps { padding: 0 0.2rem 0.6rem; }
  .feuille-filtres .valeur { padding: 0.45rem 0.15rem; font-size: var(--taille-appuye); }
  .feuille-filtres .valeur input { width: 1.1rem; height: 1.1rem; }
  /* 16 px : en deçà, iOS zoome la page dès qu'on touche le champ, et l'on
     ressort avec une carte agrandie qu'on n'a pas demandée. */
  .feuille-filtres .axe-recherche { font-size: var(--taille-appuye); }

  /* ---- la carte prend toute la place ----------------------------------- */
  .conteneur-principal { position: relative; display: block; }
  .panneau { height: 100%; }
  #panneau-carte.actif { display: block; }
  .zone-carte { position: absolute; inset: 0; }

  /* ⚠️ L'ENCART DES DEUX COLONNES EST ANNULÉ ICI, ET C'EST OBLIGATOIRE.
     La règle de bureau décale les panneaux de la largeur du volet plus la
     gouttière : sur un écran de 390 px, cela donne une largeur négative et le
     panneau disparaît. Le volet, sur téléphone, n'est pas une colonne mais une
     feuille qui monte du bas : il n'y a rien à décaler.

     ⚠️ ELLE VAUT POUR LES CINQ PANNEAUX, et pas seulement pour la carte que le
     08/09 vient d'y ajouter. Les quatre autres portaient déjà ce décalage sans
     annulation ; je n'ai pas pu vérifier sur un vrai téléphone ce que cela y
     donnait, et je ne prétends donc pas corriger un défaut que je n'ai pas
     mesuré. Ce qui est sûr : à partir d'ici, aucun des cinq ne se décale. */
  #panneau-carte.actif,
  #panneau-graphique.actif,
  #panneau-activite.actif,
  #panneau-site.actif,
  #panneau-admin.actif,
  #panneau-journal.actif {
    inset: 0;
    padding: 0.9rem 1rem;
    border: 0;
    border-radius: 0;
    box-shadow: none;
  }
  #panneau-carte.actif { padding: 0; }

  /* ---- la barre latérale devient un panneau qui monte du bas ----------- */
  /*
     Trois positions, portées par un attribut `data-etat` que mobile.js écrit :

       replie  — on ne voit que la poignée : la carte est entière
       moitie  — les totaux et le haut de la liste
       plein   — la liste, presque tout l'écran

     ⚠️ LA POSITION EST UN `transform`, PAS UNE HAUTEUR. La hauteur est fixe
     (88 % de l'écran) et c'est la translation qui change. Animer une hauteur
     relayouterait la carte à chaque image ; une translation ne coûte qu'une
     couche au processeur graphique.

     ⚠️ L'ÉTAT PAR DÉFAUT EST « replié », ET IL EST DANS LA RÈGLE DE BASE. Si le
     JavaScript ne s'exécutait pas, le panneau serait donc replié et la carte
     visible — un écran amoindri, jamais un écran vide.
  */
  .barre-laterale {
    position: absolute;
    /* ⚠️ top: auto ANNULE LE VOLET FLOTTANT. La règle de base pose les quatre
       côtés pour tenir la hauteur de l'écran ; ici la hauteur est fixe et c'est
       la translation qui place la feuille. Sans cette annulation, top et height
       se contredisent et le panneau ne bouge plus. */
    top: auto;
    left: 0;
    right: 0;
    /* Au-dessus des barres de contrôle de Leaflet, qui sont à 1000 : sinon les
       boutons + et − du zoom transpercent le panneau dès qu'il monte. */
    z-index: 1001;
    width: auto;
    min-width: 0;
    /* ⚠️ ELLE NE DESCEND PLUS JUSQU EN BAS, depuis la barre du 13/09/2026.
       `bottom: 0` la posait PAR-DESSUS la navigation : replier le panneau
       rendait la carte, mais les quatre onglets restaient dessous, hors
       d atteinte. 4,3 rem est la hauteur mesuree de la barre, ses marges et
       son filet compris. */
    bottom: calc(4.3rem + env(safe-area-inset-bottom, 0px));
    height: 88%;
    border: 0;
    border-top: 1px solid var(--border-color);
    border-radius: 1rem 1rem 0 0;
    box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.15);
    padding: 0 0.8rem 0.8rem;
    transform: translateY(calc(100% - 3.6rem));
    transition: transform 220ms cubic-bezier(0.4, 0, 0.2, 1);
    /* Le doigt fait défiler la LISTE, pas la page derrière. */
    overscroll-behavior: contain;
  }

  .barre-laterale[data-etat="moitie"] { transform: translateY(45%); }
  .barre-laterale[data-etat="plein"]  { transform: translateY(0); }

  /* Pendant qu'on tire au doigt, on suit le doigt : pas de transition, sinon
     le panneau traîne derrière lui d'un cinquième de seconde. */
  .barre-laterale[data-glisse="oui"] { transition: none; }

  .poignee-panneau {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.3rem;
    width: 100%;
    /* Collée en haut du panneau : elle reste sous le doigt quand la liste
       défile derrière elle. */
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--bg-primary);
    border: 0;
    padding: 0.7rem 0 0.5rem;
    color: var(--text-secondary);
    font-size: var(--taille-courant);
    font-weight: 600;
    /* Le doigt qui tire la poignée ne doit pas faire défiler la page. */
    touch-action: none;
  }

  .poignee-trait {
    width: 2.5rem;
    height: 0.25rem;
    border-radius: 999px;
    background: var(--border-color);
  }

  /* La recherche de la barre latérale : elle est déjà dans la barre compacte,
     celle d'ici ferait doublon. La mesure et le masquage, eux, restent — ils
     n'existent nulle part ailleurs sur téléphone depuis P2.8, où ils ont quitté
     la feuille « Filtres » en même temps que le panneau qui les portait.
     16 px : en dessous, iOS zoome la page à la mise au point du champ. */
  .barre-laterale .recherche-sites { display: none; }
  .barre-laterale .reglage-mesure select { font-size: var(--taille-appuye); }
  /* Sur 390 px, la mesure prend la ligne et la case à cocher passe dessous. */
  .barre-laterale .reglage-mesure { flex: 1; min-width: 0; }
  .barre-laterale .reglage-mesure select { flex: 1; min-width: 0; }
  .barre-laterale .commande-case { font-size: var(--taille-courant); }

  /* Les tuiles de totaux tiennent sur une ligne, en plus petit. */
  .badges { gap: 0.35rem; }
  .badge { padding: 0.45rem 0.5rem; }
  .badge-valeur { font-size: var(--taille-appuye); }
  .badge-libelle { font-size: var(--taille-mention); }

  /* ---- le bandeau des interventions ------------------------------------ */
  /* Il partageait la hauteur avec la carte (40 %). Sur téléphone il la
     recouvre entièrement : deux moitiés de rien ne valent pas une vue. */
  .panneau-interventions {
    position: absolute;
    inset: 0;
    /*
       ⚠️ AU-DESSUS DE 1000, ET C'EST LEAFLET QUI L'IMPOSE. Ses barres de
       contrôle — le zoom, le choix du fond — vivent dans `.leaflet-top` et
       `.leaflet-bottom`, à z-index 1000. À 600, elles TRANSPERÇAIENT le
       bandeau : on voyait les boutons + et − flotter par-dessus le tableau.
       Constaté à l'écran le 24/08.
    */
    z-index: 1002;
    flex: 1 1 auto;
  }

  /* L'en-tête du bandeau : le nom du site et son résumé passent à la ligne
     plutôt que de se comprimer en colonnes illisibles. */
  .interventions-entete { flex-wrap: wrap; gap: 0.3rem 0.6rem; padding: 0.5rem 0.6rem; }

  /* Le bouton de retour se voit : c'est la seule sortie quand le bandeau
     recouvre toute la carte. */
  .bouton-retour {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    min-height: 2.4rem;
    padding: 0.4rem 0.8rem;
    font-weight: 600;
    border-color: var(--accent-primary);
    color: var(--texte-bleu);
  }
  .bouton-retour .bouton-retour-mot { display: inline; }

  /* Le curseur d'échelle, une fois rangé dans le menu des calques : il reprend
     la largeur du panneau au lieu de ses 200 px d'origine. */
  .echelle-dans-calques { padding: 0.35rem 0.2rem 0.1rem; }
  .echelle-dans-calques .commande-echelle {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: var(--taille-courant);
  }
  .echelle-dans-calques input[type="range"] { flex: 1; min-width: 7rem; }
  .interventions-entete strong { font-size: var(--taille-appuye); }
  .interventions-entete select { font-size: var(--taille-appuye); }

  /*
     LE TABLEAU DEVIENT UNE LISTE DE FICHES.

     Sept colonnes sur 387 px, cela donne trois colonnes visibles et un
     ascenseur horizontal : on ne lit plus rien et l'on ne sait plus ce qu'on
     regarde. Chaque intervention occupe donc trois lignes, dans l'ordre où on
     les cherche :

        BA0089/14                        3 438 €
        tonte terrain
        En cours de traitement  24/02/2026  136 h

     Les lignes sont des `div`, pas un `<table>` : la mise en grille suffit,
     sans toucher à une ligne de JavaScript. Les cellules sont visées par leur
     RANG, seul moyen de distinguer les deux dates et les deux nombres, qui
     partagent leurs classes.
  */
  .interventions-corps { overflow-x: hidden; }

  .interventions-ligne.interventions-cliquable {
    display: grid;
    grid-template-columns: auto 1fr auto;
    gap: 0.1rem 0.5rem;
    padding: 0.6rem 0.7rem;
    align-items: baseline;
  }

  .interventions-ligne.interventions-cliquable > :nth-child(1) { grid-area: 1 / 1; font-weight: 600; }
  /* Le coût TOTAL est la 9e cellule depuis l'ajout de Personnel et Fournitures
     (30/08). Les rangs suivent COLONNES dans site.js : toute insertion là-bas
     se répercute ici. */
  .interventions-ligne.interventions-cliquable > :nth-child(9) { grid-area: 1 / 3; text-align: right; font-weight: 600; }
  /*
     Le libellé passe à la ligne, mais PAS indéfiniment.

     Ces textes viennent du client et certains sont des courriels entiers : un
     seul faisait dix lignes et repoussait les suivantes hors de l'écran. Trois
     lignes suffisent à reconnaître une intervention ; le texte complet est dans
     sa fiche, à un doigt de là.
  */
  .interventions-ligne.interventions-cliquable > :nth-child(2) {
    grid-area: 2 / 1 / auto / span 3;
    white-space: normal;
    line-height: 1.35;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    line-clamp: 3;
    overflow: hidden;
  }
  .interventions-ligne.interventions-cliquable > :nth-child(3) { grid-area: 3 / 1; font-size: var(--taille-courant); color: var(--text-muted); }
  .interventions-ligne.interventions-cliquable > :nth-child(4) { grid-area: 3 / 2; font-size: var(--taille-courant); color: var(--text-muted); }
  .interventions-ligne.interventions-cliquable > :nth-child(6) { grid-area: 3 / 3; text-align: right; font-size: var(--taille-courant); color: var(--text-muted); }
  /* La date de fin disparaît : elle vaut « — » pour toute intervention non
     terminée, et la fiche la porte quand elle existe. Personnel et Fournitures
     (7e et 8e cellules) disparaissent aussi : trois lignes par fiche est la
     limite lisible à 390 px, et la ventilation se lit dans la fiche de
     l'intervention. */
  .interventions-ligne.interventions-cliquable > :nth-child(5),
  .interventions-ligne.interventions-cliquable > :nth-child(7),
  .interventions-ligne.interventions-cliquable > :nth-child(8) { display: none; }

  /*
     L'EN-TÊTE DE COLONNES RESTE, SOUS UNE AUTRE FORME.

     Le masquer aurait supprimé le tri — la seule façon de réordonner la liste.
     Il devient donc une bande de petits boutons qui passent à la ligne : on
     touche « Coût » ou « Heures » comme avant, sans grille à respecter.
  */
  .interventions-entete-ligne {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem;
    padding: 0.4rem 0.6rem;
  }
  .interventions-entete-ligne .col-titre {
    flex: 0 0 auto;
    width: auto;
    padding: 0.3rem 0.5rem;
    font-size: var(--taille-mention);
    border: 1px solid var(--border-champ);
    border-radius: 999px;
  }

  /* ---- les commandes flottantes de la carte ---------------------------- */
  /*
     ⚠️ CE BLOC EST VIDE DEPUIS P2.8, ET C'EST LE RÉSULTAT RECHERCHÉ.

     Il portait quatre règles pour un panneau flottant — « Métrique » et
     « Masquer sites génériques » — qui, sur 390 px, couvrait le quart supérieur
     de la carte et chevauchait le curseur d'échelle. Le téléphone s'en était
     tiré en le déménageant dans la feuille des filtres (mobile.js), et ces
     règles le remettaient d'aplomb là-bas.

     Le panneau n'existe plus : ses deux commandes vivent dans `.barre-liste`,
     avec la liste qu'elles ordonnent, sur les deux tailles d'écran. Un cas
     particulier de moins pour le téléphone, et une règle générale de plus.
  */

  /*
     LE CURSEUR D'ÉCHELLE DISPARAÎT DU TÉLÉPHONE, ET C'EST UN CHOIX.

     Il vit dans un contrôle Leaflet en haut à gauche, sous le zoom, et fait
     200 px de large — la moitié de l'écran. C'est un réglage de confort : il
     grossit ou rapetisse les cercles quand ils se chevauchent. Sur un petit
     écran, on obtient le même résultat en zoomant, geste que le doigt connaît
     déjà. On rend donc la place à la carte.
  */
  .controle-echelle { display: none; }

  /* =====================================================================
     L'ÉCRAN BILAN SUR TÉLÉPHONE.

     Jusqu'au 26/08, ce bloc ne traitait QUE la carte. Le Bilan y était du
     bureau comprimé, et le résultat était mesurable : les deux listes
     déroulaient 450 px de colonnes fixes dans un conteneur de 324, sous un
     `overflow: hidden`. La colonne COÛT allait de 366 à 478 px quand le cadre
     s'arrêtait à 348 : elle était coupée, sans ascenseur, sans aucun geste pour
     l'atteindre. La colonne LIBELLÉ, elle, était écrasée à zéro pixel.

     Autrement dit : sur l'écran qui répond à « combien a coûté ce bâtiment ? »,
     le coût était invisible et le nom de l'opération aussi.

     LE MOTIF EST CELUI DU BANDEAU DES INTERVENTIONS, réutilisé plutôt que
     réinventé (guide §9.1) : la ligne de tableau devient une fiche de deux ou
     trois lignes, le libellé prend toute la largeur, et le nombre qu'on vient
     chercher — le coût — se pose à droite, là où l'œil le trouve d'abord.
     ===================================================================== */

  /* 1,5 rem de chaque côté sur 390 px, c'était 48 px rendus au fond blanc. */
  /* ---- les deux listes deviennent des fiches --------------------------- */

  /* L'EN-TETE DE COLONNES DISPARAIT ICI, ET LUI SEUL.
     Une fiche n'a pas de colonnes a coiffer, et chaque valeur y porte deja son
     unite — « 35 int. », « 104 h », « 2 426 € ». Le garder aurait pose une
     rangee de six mots en capitales au-dessus de chaque liste, pour ne rien
     rapporter a rien. Sur grand ecran il reste, la ou il y a des colonnes. */
  .ligne-liste-entete { display: none; }

  #site-operations .ligne-liste,
  #site-equipements .ligne-liste {
    grid-template-columns: auto auto 1fr;
    gap: 0.15rem 0.5rem;
    padding: 0.6rem 0.7rem;
    align-items: baseline;
  }

  /* Les cellules sont visées par leur RANG : elles partagent leurs classes
     — deux « nombre », un « fort » — et le rang est le seul moyen de les
     distinguer. Même solution que pour le bandeau, pour la même raison. */

  /* Opérations : rang · code · coût / libellé / interventions · heures */
  #site-operations .ligne-liste > :nth-child(1) { grid-area: 1 / 1; }
  #site-operations .ligne-liste > :nth-child(2) { grid-area: 1 / 2; }
  #site-operations .ligne-liste > :nth-child(6) { grid-area: 1 / 3; text-align: right; }
  #site-operations .ligne-liste > :nth-child(3) {
    grid-area: 2 / 1 / auto / span 3;
    white-space: normal;
    line-height: 1.35;
    /* Ces libellés viennent du client et montent à 1 752 caractères — des
       courriels entiers. Trois lignes suffisent à reconnaître une opération. */
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    line-clamp: 3;
    overflow: hidden;
  }
  #site-operations .ligne-liste > :nth-child(4),
  #site-operations .ligne-liste > :nth-child(5) {
    grid-row: 3;
    text-align: left;
    font-size: var(--taille-courant);
  }
  #site-operations .ligne-liste > :nth-child(4) { grid-column: 1; }
  #site-operations .ligne-liste > :nth-child(5) { grid-column: 2; }

  /* Équipements, en fiche : libellé · coût / interventions · heures · part.
     Même découpe que la fiche d'opération juste au-dessus — le coût en haut à
     droite, les chiffres secondaires en dessous. Deux listes voisines qui se
     lisent de deux façons différentes coûtent une hésitation à chaque passage.

     La jauge a disparu partout, et pas par manque de place : sur les données
     réelles, onze barres sur vingt-cinq faisaient moins de 3 px et six moins de
     1 px. La part écrite la remplace, et elle tient en cinq caractères. */
  #site-equipements .ligne-liste > :nth-child(1) {
    grid-area: 1 / 1 / auto / span 2;
    white-space: normal;
    line-height: 1.35;
  }
  #site-equipements .ligne-liste > :nth-child(5) {
    grid-area: 1 / 3;
    text-align: right;
    font-weight: 700;
  }
  #site-equipements .ligne-liste > :nth-child(3),
  #site-equipements .ligne-liste > :nth-child(4),
  #site-equipements .ligne-liste > :nth-child(2) {
    grid-row: 2;
    font-size: var(--taille-courant);
    text-align: left;
  }
  #site-equipements .ligne-liste > :nth-child(3) { grid-column: 1; }
  #site-equipements .ligne-liste > :nth-child(4) { grid-column: 2; }
  #site-equipements .ligne-liste > :nth-child(2) { grid-column: 3; text-align: right; }

  /* Le coût reste le nombre le plus lourd de la fiche, dans les deux listes. */
  #site-operations .ligne-liste > :nth-child(6),
  #site-equipements .ligne-liste > :nth-child(5) { font-weight: 700; }

  /* Les cartes annuelles tiennent à deux par rang plutôt qu'à une seule. */
  .grille-annees { grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr)); }
}

/* Ces trois-là se cachent à toutes les tailles quand leur attribut `hidden`
   est posé : sur grand écran ils n'ont aucune raison d'exister, et sur petit
   écran c'est mobile.js qui décide. La règle est hors media query parce que la
   feuille peut rester ouverte quand on fait pivoter l'appareil. */
.feuille-filtres[hidden],
.voile-filtres[hidden],
.barre-mobile[hidden] { display: none !important; }

/* LA POIGNÉE N'EXISTE QUE SUR LE TÉLÉPHONE.
   Sur grand écran, la barre latérale est une colonne fixe : il n'y a rien à
   tirer, et le bouton « Voir la liste des sites » s'y affichait en tête de
   colonne, au-dessus d'une liste déjà visible. Constaté à l'écran le 24/08. */
.poignee-panneau { display: none; }
@media (max-width: 48rem) { .poignee-panneau { display: flex; } }

/* ⚠️ ET LA POIGNÉE DE LARGEUR DISPARAÎT SUR TÉLÉPHONE, où le volet est une
   feuille qui monte du bas : il n'y a pas deux colonnes, donc rien à partager.
   La laisser afficherait une commande qui ne commande rien, ce que la règle du
   08/09 interdit. */
@media (max-width: 48rem) { .poignee-largeur { display: none; } }

/* Sur grand écran, le bandeau ne recouvre rien : la croix seule suffit, et un
   mot de plus encombrerait une barre déjà chargée. */
.bouton-retour-mot { display: none; }

/* LA PERIODE, POSEE A COTE DES CHIFFRES QU'ELLE DATE.
   Assez lisible pour qu'on ne puisse pas lire les nombres sans elle — c'est
   tout son role — sans peser autant qu'eux : elle date, elle ne mesure pas.
   D'ou le petit corps en majuscules espacees plutot qu'une grosse phrase. */
.periode-active {
  margin: 0 0 0.5rem;
  font-size: var(--taille-appuye);
  font-weight: 700;
  color: var(--text-primary);
}

/* LA PERIODE SUR LA LIGNE DU NOM, 21/09/2026.

   ⚠️ `margin-left: auto` LA POUSSE A DROITE SANS RIEN CALCULER, et c'est ce
      qui la garde a sa place quand le nom du site fait quarante caracteres et
      passe a la ligne : elle suit, au lieu de sortir du cadre.

   ⚠️ ET ELLE MONTE D'UN CRAN, PAS DE DEUX. Le nom du site est au cran
      « sous-titre » ; la periode le rejoint, sans le depasser. Un repere de
      lecture ne doit pas se lire avant ce qu'il repere. */
.periode-du-titre {
  margin: 0 0 0 auto;
  font-size: var(--taille-sous-titre);
  white-space: nowrap;
}

/* MAJUSCULE À LA PREMIÈRE LETTRE, ET À ELLE SEULE.
   Le texte était en capitales sur toute sa longueur : trop lourd, retour de
   Victor le 25/08. Et le français ne met pas de majuscule aux noms de mois —
   « 5 Novembre » serait fautif, et « Au » en milieu de phrase se verrait.
   ::first-letter plutôt qu'une transformation en JavaScript : periodeEnClair()
   rend un fragment de phrase, réutilisable ailleurs au fil du texte. */
.periode-active::first-letter { text-transform: uppercase; }


/* ==========================================================================
   LA PAGE D'UN SITE — P2.2, décisions D3 et D4.

   ⚠️ DESSINÉE À 390 px D'ABORD, ET C'EST L'INVERSE DE TOUT LE RESTE DE CE
   FICHIER. Les autres écrans sont écrits pour le bureau puis rattrapés dans le
   bloc « téléphone » de la fin — 478 lignes qui, jusqu'au 26/08, ne traitaient
   que la Carte, si bien que le Bilan y déroulait 450 px de colonnes dans un
   conteneur de 324 et coupait la colonne des coûts.

   Ici les règles de base SONT celles du téléphone. Le grand écran n'ajoute
   qu'une largeur maximale et deux colonnes là où la place existe. Rien à
   rattraper : il n'y a rien à défaire.
   ========================================================================== */

#panneau-site { padding: 0.9rem 0.75rem 3rem; overflow: auto; }

.site-barre {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-bottom: 1rem;
}

.site-nom {
  font-size: var(--taille-sous-titre);
  font-weight: 700;
  line-height: 1.25;
  /* Le nom peut faire quarante caractères — « Groupe scolaire des Manèges,
     bâtiment B ». Il passe à la ligne plutôt que de pousser la barre hors de
     l'écran : c'est le titre de la page, il ne se tronque pas. */
  min-width: 0;
  flex: 1 1 12rem;
}

/* ---- 1. LA RÉPONSE ------------------------------------------------------
   Un chiffre dominant et sa phrase. Le rapport de taille entre le coût et ce
   qui l'entoure EST la hiérarchie : c'est le seul endroit de l'application où
   un nombre est écrit trois fois plus grand que son voisin, et c'est voulu —
   la question du responsable en déplacement est « combien a coûté ce
   bâtiment ? ». */
/* ---- la rangee du haut d'une fiche, 19/09/2026 ---------------------------
   Relevé sur la maquette PowerPoint de Victor, diapositive de 33,9 x 19,1 cm :
   la zone Identite fait 15 cm de large, la zone des Donnees principales 5,8.
   Soit 2,6 contre 1, et non le 50/50 que ma maquette HTML avait dessine.

   ⚠️ LA COLONNE DE DROITE EST ETROITE ET HAUTE, donc les quatre chiffres y sont
      EMPILES et non en grille 2 x 2. C'est le deuxieme des trois ecarts que
      j'avais faux en relisant la maquette, et il decide de la forme des
      cartons : `.parc-chiffres` repasse en une colonne quand elle est ici.

   ⚠️ ET LA PAGE DU PARC GARDE SES QUATRE DE FRONT. Elle n'a pas d'identite a
      poser a cote : sa vignette et son adresse n'existent pas. Lui imposer la
      meme rangee laisserait une colonne vide sur 2,6. */
.site-reponse {
  padding: 1rem 0.9rem 1.1rem;
  margin-bottom: 1.5rem;
  background: var(--bg-secondary);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
}

/* ⚠️ LA COLONNE DES TUILES EST FIXE DEPUIS LE 21/09/2026, ET C'EST MESURE.
   Victor : « il y a encore du vide a droite des gros chiffres ». Releve sur
   l'ecran rendu, fenetre 1600 px, avec ses quatre tuiles et ses vrais
   libelles :

       la plus large des quatre a besoin de ... 158 px
       la colonne en offrait ................. 231 px
       soit de vide .......................... 73 px

   Les proportions 2,6fr / 1fr donnaient cette colonne au prorata de la
   largeur, c'est-a-dire sans rapport avec ce qu'elle porte. 11 rem valent
   176 px : 18 px de marge sur le plus large, et ils ne bougent plus.

   ⚠️ ET CA REPARE UN DEFAUT QUE LE FICHIER DECRIVAIT SANS LE TRAITER. La note
      de « .parc-carton-libelle » mesurait « fenetre 1220 px, place 94 px,
      COUPE, fonctionnem / ent ». Mesure du 21/09 au meme endroit, volet large :
      la colonne tombait a 102 px AVANT, elle tient 176 px APRES. Le libelle ne
      se coupe plus, et le rehaussement que cette note avait du renoncer a poser
      redevient possible.

   ⚠️ « 1fr » ET NON « minmax(0, 1fr) », et la difference n'est pas cosmetique.
      Le minimum automatique d'un fr vaut auto : la colonne ne peut pas tomber
      sous son contenu. Avec minmax(0, 1fr), essaye le 21/09, l'identite tombait
      a 198 px a fenetre 1150. C'est exactement ce que la note de la reparation
      des 26 px designe comme le coupable, et je l'avais reintroduit.

   ⚠️ ET LA RANGEE SE PLAFONNE, ce qu'elle ne faisait pas. « .site-contenu »
      perd son max-width au-dela de 1200 px, pour que le tableau des
      interventions s'etale. La photo prenant desormais ce qui reste, elle
      atteignait 1221 x 733 px a fenetre 2400, plus haute que la zone utile. Le
      plafond ne porte que sur cette rangee : le tableau garde toute la largeur,
      mesure a 1683 px au meme endroit. */
/* ⚠️ LA PAGE TOTAL PREND LA MÊME GRILLE QUE LA FICHE D'UN SITE, DEPUIS LE
   24/09/2026. Ces règles ne visaient que body[data-ecran="site"] ; le TOTAL,
   ouvert sous data-ecran="parc", retombait sur la disposition par défaut :
   les quatre chiffres en rangée au-dessus, le blason en pleine largeur.
   Victor : « précisément la même chose que pour tout autre site ». */
body:is([data-ecran="site"], [data-ecran="parc"]) .site-reponse {
  display: grid;
  grid-template-columns: 1fr 11rem;
  gap: 1rem 0.8rem;
  align-items: start;
  max-width: 68rem;
}
/* L'identite d'abord, a gauche : c'est le « quoi » et le « ou », et il se lit
   avant le « combien » sur une fiche qu'on ouvre pour reconnaitre un site. */
body:is([data-ecran="site"], [data-ecran="parc"]) .site-reponse > .site-identite { grid-column: 1; grid-row: 1; }
/* Les quatre chiffres tiennent la hauteur de la colonne, empiles. */
body:is([data-ecran="site"], [data-ecran="parc"]) .site-reponse > .parc-chiffres {
  grid-column: 2; grid-row: 1 / span 4;
  grid-template-columns: 1fr;
}
/* Tout le reste reste dans la colonne large, sous l'identite. */
body:is([data-ecran="site"], [data-ecran="parc"]) .site-reponse > .periode-active,
body:is([data-ecran="site"], [data-ecran="parc"]) .site-reponse > .site-composition,
body:is([data-ecran="site"], [data-ecran="parc"]) .site-reponse > .site-compte-equipements { grid-column: 1; }

/* ⚠️ LE REPLI SUIT LE BLOC, ET PLUS LA FENETRE, DEPUIS LE 21/09/2026.
   C'est « la vraie cause, non traitee » que la note de « .parc-carton-libelle »
   nommait sans pouvoir l'atteindre : les points de bascule etaient poses sur la
   largeur de la FENETRE, or la largeur reelle de cette rangee depend aussi du
   volet lateral, que l'utilisateur redimensionne a la souris. Un @media ne peut
   pas voir une largeur qu'il ne mesure pas.

   Mesure du 21/09, fenetre 1150 px et volet large, donc HORS de l'ancien
   @media (max-width: 1100px) :

       avant ... identite 266 px, tuiles ECRASEES a 102 px
       apres ... la rangee s'empile, photo 387 x 232, tuiles 2 x 2 sur 387

   Le seuil de 46 rem est la somme de ce que la rangee demande : 18 rem de base
   pour le visuel, 13 rem pour les faits, 11 rem pour les tuiles, plus les deux
   ecarts et les marges internes.

   ⚠️ LA CONTAINMENT A ETE VERIFIEE AVANT D'ETRE POSEE. container-type implique
      contain: layout, qui fait du bloc le referent des descendants en
      position: fixed. Releve dans le DOM le 21/09 : les SEPT elements fixes de
      l'application, dont « #feuille-detail » et « #modale-intervention »,
      vivent tous HORS de « .site-contenu ». Rien ne change pour eux. */
.site-contenu { container-type: inline-size; container-name: fiche; }

@container fiche (max-width: 46rem) {
  body:is([data-ecran="site"], [data-ecran="parc"]) .site-reponse { grid-template-columns: 1fr; }
  body:is([data-ecran="site"], [data-ecran="parc"]) .site-reponse > .site-identite,
  body:is([data-ecran="site"], [data-ecran="parc"]) .site-reponse > .parc-chiffres { grid-column: 1; grid-row: auto; }
  body:is([data-ecran="site"], [data-ecran="parc"]) .site-reponse > .parc-chiffres { grid-template-columns: 1fr 1fr; }
}

.site-cout {
  font-size: var(--taille-vedette);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -0.02em;
  color: var(--text-primary);
}

/* ⚠️ L'INTITULÉ DU GRAND CHIFFRE, posé le 10/09. Il n'en avait pas, et sur la
      page du parc le nombre affiché était la RÉGIE SEULE : 1 277 939 € pour
      2025, au-dessus de 6 967 490 € d'investissement écrits en petit. En
      capitales espacées, comme les titres de bloc : c'est une étiquette, pas
      une phrase, et elle ne doit pas se lire avant le nombre qu'elle nomme. */
.site-cout-intitule {
  font-size: var(--taille-mention);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-top: 0.25rem;
}

.site-appui { font-size: var(--taille-appuye); color: var(--text-secondary); margin-top: 0.15rem; }
.site-reponse .periode-active { margin-top: 0.35rem; }

.site-composition {
  display: flex;
  gap: 1.5rem;
  margin-top: 0.9rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--border-color);
}
/* ⚠️ VIDE, ELLE PRENAIT ENCORE DE LA PLACE ET TIRAIT UN TRAIT. Depuis que
   « personnel » et « fournitures » ont quitte la fiche d'un site, le 21/09/2026,
   cette boite y est vide : elle gardait pourtant ses 0,9 rem de marge, ses
   0,75 rem de remplissage et son filet du haut, soit 27 px de blanc surmontes
   d'une ligne horizontale qui ne separait plus rien. Sur le PARC, ou elle porte
   toujours ses trois termes, rien ne change. */
.site-composition:empty { display: none; }
.site-composant { display: flex; flex-direction: column; gap: 0.1rem; }
.site-composant-valeur { font-size: var(--taille-appuye); font-weight: 600; }
.site-composant-libelle { font-size: var(--taille-mention); color: var(--text-muted); }

/* ---- LA CARTE D'IDENTITÉ, dans le même encadré que la réponse -----------
   Elle occupe le vide de droite relevé le 08/09, sans ouvrir un bloc de plus :
   la surface et le coût au m² qualifient le coût écrit juste au-dessus, ils ne
   racontent pas autre chose.

   ⚠️ EN COLONNES QUI SE REPLIENT, et non en grille fixe. Le nombre de lignes
      varie : l'adresse n'existe pas partout, et « sur 66 sites mesurés » ne
      paraît que sur le parc. Une grille à trois colonnes aurait laissé un trou
      là où une ligne manque. */
.site-identite {
  display: flex;
  flex-wrap: wrap;
  /* ⚠️ 2 rem ENTRE LE VISUEL ET LES FAITS, C'ETAIT 32 px DONNES A DU VIDE.
     Victor, 21/09 : « on peut meme reduire la distance entre la zone de texte
     et les gros chiffres ». 1,2 rem suffisent a separer deux blocs dont l'un
     est une image bordee, et les 13 px rendus vont a la photo. */
  gap: 0.9rem 1.2rem;
  margin-top: 0.9rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--border-color);
}
.site-identite-ligne { display: flex; flex-direction: column; gap: 0.1rem; }
.site-identite-cle {
  font-size: var(--taille-mention);
  color: var(--text-muted);
}
.site-identite-valeur { font-size: var(--taille-appuye); font-weight: 600; }
/* La précision est une phrase, pas une étiquette : elle dit QUELLE surface est
   comptée et sur quoi le ratio divise. La mettre en gris et en petit la range
   au bon niveau de lecture, sous la valeur qu'elle qualifie. */
.site-identite-precision { font-size: var(--taille-mention); color: var(--text-muted); }

/* ---- 2. OÙ C'EST --------------------------------------------------------
   Basse, et volontairement : c'est un repère, pas une carte. Lui donner de la
   hauteur inviterait à l'explorer, alors qu'elle ne se manipule pas. */
.bloc-vignette { margin-bottom: 1.5rem; }
.site-vignette {
  height: 9rem;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--bg-secondary);
}
.site-vignette-vide {
  height: auto;
  padding: 0.6rem 0.75rem;
}
/* Rien ne s'y clique : le curseur ne doit pas promettre le contraire. */
.site-vignette .leaflet-container { cursor: default; }

/* LA PHOTO DU SITE, 21/09/2026.
   Elle occupe la case du visuel, la vignette de carte ne paraissant que pour
   les 42 sites sur 104 qui n'ont pas de photo.

   ⚠️ `object-fit: contain` DEPUIS LE 22/09/2026, ET C'EST UN RENVERSEMENT.
      Victor, devant l'ecran : « au lieu de decouper l'image, je prefere reduire
      sa taille de telle facon qu'elle puisse entrer dans le cadre alloue a
      l'image ».

      Ce que `cover` achetait reste vrai et se paye maintenant : les 62 photos
      ont 60 formats differents, chacune recadree a part (mesure du 18/09), donc
      le fond laisse desormais des bandes de largeurs differentes d'une fiche a
      l'autre. L'argument qui l'emporte est ailleurs : une photo de batiment
      sert a RECONNAITRE le lieu, et `cover` coupait justement ce qui depasse,
      c'est-a-dire le toit ou un pignon. Une bande de fond ne trompe personne,
      une facade amputee si.

   ⚠️ LE FOND EST CELUI DU CADRE, PAS DU BLANC. Sans quoi les bandes se
      liraient comme un defaut de chargement plutot que comme du cadre.

   ⚠️ ET ELLE NE SE CLIQUE PAS. Une photo illustre ; un plan est une surface de
      travail. Arbitre le 21/09, voir db/95. Le curseur reste donc celui du
      texte, et aucun survol ne promet une action. */
.site-photo {
  width: 100%;
  height: 100%;
  /* ⚠️ PLUS DE min-height : C'EST LE CADRE QUI DECIDE, par son rapport. Ce
     plancher de 16 rem se battait avec aspect-ratio et gagnait, ce qui
     redonnait au cadre une hauteur fixe et ramenait la coupe variable. */
  object-fit: contain;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  background: var(--bg-secondary);
  display: block;
}
.site-photo[hidden] { display: none; }

/* Le blason du client, à la place de la photo sur la page du Total (arbitrage
   3 du 23/09), sur l'ardoise qui désigne le Total dans les deux listes. */
.site-photo.site-blason {
  background: var(--fond-total);
  padding: 1.25rem;
  box-sizing: border-box;
}

/* L'image fabriquée d'un site générique (arbitrage 4 du 23/09) : un cadre
   pointillé, qui se lit comme une absence de bâtiment et non comme une photo.
   Gris neutre : l'ardoise est réservée au Total, le violet aux sites qui
   pèsent sur la carte. Mesuré le 23/09 : le texte est à 9,45:1 sur son fond,
   le pictogramme à 6,92:1. */
.site-panier {
  height: 100%;
  min-height: 9rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
  border: 1px dashed #94a3b8;
  border-radius: var(--radius-sm);
  background: var(--bg-tertiary);
  color: #334155;
  text-align: center;
  padding: 0.75rem;
}
.site-panier[hidden] { display: none; }
.site-panier svg { width: 2.25rem; height: 2.25rem; color: #475569; }
.site-panier-mot { font-size: var(--taille-courant); font-weight: 600; }
.site-panier-couverture { font-size: var(--taille-mention); }

/* ---- 6. LES INTERVENTIONS ---------------------------------------------- */
.site-interventions-barre {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-bottom: 0.5rem;
}
.site-interventions-barre h2 { margin: 0; }
.site-interventions-barre .interventions-filtre { margin-left: auto; }

/* Le tableau garde sa grille et son défilement horizontal PROPRE : sept
   colonnes ne tiennent pas dans 390 px, et le corps de la page ne doit jamais
   défiler latéralement. C'est la règle qui manquait au Bilan avant le 26/08. */
#site-interventions {
  overflow-x: auto;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
}

@media (min-width: 900px) {
  #panneau-site { padding: 1.25rem 1.5rem 3rem; }
  /* Une colonne de lecture, pas toute la largeur : une ligne de texte de
     1 800 px ne se lit pas. Le tableau des interventions, lui, s'échappe de
     cette contrainte — voir juste dessous. */
  .site-contenu { max-width: 68rem; }
  .site-cout { font-size: var(--taille-vedette); }
  .site-vignette { height: 12rem; }
  /* La réponse et la vignette côte à côte : elles répondent à deux questions
     différentes — combien, et où — et il y a la place pour les deux. */
  .site-reponse { margin-bottom: 1rem; }
}

@media (min-width: 1200px) {
  .site-contenu { max-width: none; }
}

/* ===========================================================================
   LE MODULE ANALYSES, et sa première analyse : l'énergie.

   Rien de neuf visuellement : la barre reprend celle de l'écran Agents, la
   tuile de tête reprend le taux vedette, le tableau reprend les nombres
   tabulaires des listes. Un module qui inventerait sa propre allure ferait
   croire à une autre application.
   =========================================================================== */
.barre-analyses {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: 0.9rem;
  margin-bottom: 1rem;
}
.barre-analyses label { display: flex; flex-direction: column; gap: 0.25rem; }
.barre-analyses label > span {
  font-size: var(--taille-mention); font-weight: 600; color: var(--text-secondary);
}
.barre-analyses select {
  min-width: 8rem; padding: 0.4rem 0.6rem;
  border: 1px solid var(--border-champ); border-radius: var(--radius-sm);
  background: var(--bg-primary); color: var(--text-primary); font-size: var(--taille-courant);
}

.tete-analyse { margin-bottom: 1.2rem; }
.titre-analyse { font-size: var(--taille-appuye); font-weight: 600; margin: 0 0 0.5rem; }
/* LE CHIFFRE DE L'ÉCRAN. Un seul, et le premier lu. */
.analyse-vedette {
  margin: 0 0 0.4rem;
  font-size: var(--taille-vedette); font-weight: 700; line-height: 1.1;
  color: var(--text-primary); font-variant-numeric: tabular-nums;
}
.analyse-note {
  margin: 0.15rem 0; font-size: var(--taille-courant); color: var(--text-secondary);
}
.analyse-suite {
  margin-top: 1.5rem; font-size: var(--taille-courant); color: var(--text-secondary);
  font-style: italic;
}

.tableau-analyse { width: 100%; border-collapse: collapse; font-size: var(--taille-courant); }
.tableau-analyse th, .tableau-analyse td {
  padding: 0.4rem 0.6rem; border-bottom: 1px solid var(--border-color);
  text-align: left; vertical-align: baseline;
}
.tableau-analyse thead th {
  font-size: var(--taille-mention); font-weight: 600; color: var(--text-secondary);
  border-bottom: 2px solid var(--border-champ); white-space: nowrap;
}
/* Les nombres s'alignent à droite et se comparent colonne par colonne : sans
   chiffres à chasse fixe, deux montants de même longueur ne se superposent pas
   et l'œil ne peut plus les ranger. */
.tableau-analyse .nombre { text-align: right; font-variant-numeric: tabular-nums; }
.tableau-analyse tbody th { font-weight: 400; }
.tableau-analyse .code-site {
  display: inline-block; min-width: 4.5rem;
  font-weight: 600; color: var(--text-primary);
}
.tableau-analyse .nom-site { color: var(--text-secondary); }
.tableau-analyse tbody tr:hover { background: var(--bg-secondary); }


/* ===========================================================================
   LES ENGAGEMENTS À ARBITRER.

   Le premier bloc d'ÉCRITURE de BâtiFocale, et la forme le dit : une bascule
   qui montre laquelle des deux positions est tenue, et le montant retenu
   souligné dans la ligne plutôt que recopié dans une colonne de plus.

   ⚠️ LES CIBLES TACTILES SONT À 44 px ICI, et non aux 35 px du reste de
      l'application. Une bascule qui déplace des euros ne doit pas se rater du
      doigt. Le passage général de 35 à 48 px est arbitré mais pas fait ; quand
      il le sera, cette règle deviendra inutile et devra partir.
   =========================================================================== */
.bloc-arbitrage { margin-top: 2.5rem; }

.arbitrage-barre {
  display: flex; align-items: flex-end; gap: 1rem;
  margin: 0.75rem 0 1rem; flex-wrap: wrap;
}
.arbitrage-barre label { display: flex; flex-direction: column; gap: 0.25rem; }
.arbitrage-barre label > span {
  font-size: var(--taille-mention); font-weight: 600; color: var(--text-secondary);
}

.tableau-arbitrage td { vertical-align: middle; }

/* Trois lignes dans une cellule : qui, quoi, et de quoi il retourne. */
.arbitrage-tiers, .arbitrage-objet, .arbitrage-appui { display: block; }
.arbitrage-tiers { font-weight: 600; }
.arbitrage-objet {
  color: var(--text-secondary);
  /* Un objet de facture fait parfois 180 signes : on en montre deux lignes et
     l'on coupe, plutôt que de laisser une ligne du tableau occuper l'écran. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.arbitrage-appui { font-size: var(--taille-mention); color: var(--text-secondary); margin-top: 0.15rem; }

/* ⚠️ CE QUI COMPTE SE VOIT SUR LE NOMBRE LUI-MÊME. Sans cela, la colonne
   « ce qui compte » serait la seule à porter l'information, et l'œil qui suit
   les montants ne saurait pas lequel des deux entre dans les totaux. */
.tableau-arbitrage .compte {
  font-weight: 700; color: var(--text-primary);
  box-shadow: inset 0 -2px 0 var(--accent-primary);
}

.arbitrage-choix { display: flex; gap: 0.3rem; align-items: center; flex-wrap: wrap; }
.bascule {
  min-height: 44px; padding: 0 0.7rem;
  border: 1px solid var(--border-champ); border-radius: 999px;
  background: var(--bg-primary); color: var(--text-secondary);
  font-size: var(--taille-courant); cursor: pointer;
}
.bascule:hover { border-color: var(--accent-primary); }
.bascule.actif {
  background: var(--accent-primary); border-color: var(--accent-primary);
  color: #fff; font-weight: 600;
}
.arbitrage-clos input { width: 20px; height: 20px; cursor: pointer; }

/* Une ligne décidée à la main se distingue d'une ligne qui suit la règle :
   sans cela, on ne saurait pas ce qu'on a déjà tranché. */
.tableau-arbitrage tr.arbitre { background: var(--bg-secondary); }
.tableau-arbitrage tr.arbitre .arbitrage-tiers::after {
  content: ' · arbitré'; font-weight: 400; font-size: var(--taille-mention); color: var(--accent-primary);
}

/* Sur téléphone, les deux colonnes de décision passent sous le reste : à
   390 px, six colonnes dont deux de commandes ne tiennent pas, et c'est le
   défaut déjà mesuré le 09/09 sur le tableau voisin. */
@media (max-width: 640px) {
  .tableau-arbitrage thead { display: none; }
  .tableau-arbitrage tr { display: grid; grid-template-columns: 1fr auto; gap: 0.2rem 0.6rem; }
  .tableau-arbitrage td { border-bottom: 0; padding: 0.2rem 0; }
  .tableau-arbitrage td:first-child { grid-column: 1 / -1; }
  .tableau-arbitrage tr { border-bottom: 1px solid var(--border-color); padding: 0.6rem 0; }
}

/* ===========================================================================
   L'ÉCRAN DES ANALYSES SUR TÉLÉPHONE.

   ⚠️ VU À 390 px LE 09/09 : à six colonnes, chaque montant se coupait en deux,
   « 81 781 » sur une ligne et « € » sur la suivante. Une colonne de nombres
   qu'on ne peut plus comparer d'un coup d'œil ne sert plus à rien.

   Le détail par énergie disparaît donc, et il ne manque pas : c'est le cadrage
   de Victor du 30/08, « sur téléphone on CONSULTE, on ne paramètre pas ». La
   question qu'on se pose en marchant est « lequel coûte le plus, et est-il cher
   au mètre carré » : trois colonnes y répondent. Le détail se lit sur grand
   écran, ou dans l'export, qui les porte toutes.
   =========================================================================== */
@media (max-width: 48rem) {
  /* Le panneau reprend toute la place : pas de volet à contourner. */
  #panneau-analyses.actif {
    inset: 3.5rem 0.5rem 0.5rem 0.5rem;
    padding: 0.9rem 0.8rem;
  }
  /* Les colonnes d'énergie sont celles au-delà de la troisième. */
  .tableau-analyse th:nth-child(n+4),
  .tableau-analyse td:nth-child(n+4) { display: none; }
  .tableau-analyse th, .tableau-analyse td { padding: 0.5rem 0.35rem; }
  .tableau-analyse .code-site { min-width: 0; display: block; }
  .tableau-analyse .nom-site { display: block; font-size: var(--taille-mention); }
  /* Un montant ne se coupe jamais en deux : c'est ce qui rendait la colonne
     illisible. */
  .tableau-analyse .nombre { white-space: nowrap; }
  .analyse-vedette { font-size: var(--taille-vedette); }
}

/* ---- les factures d'entreprise, sur la fiche d'un site et sur le parc -----
   Trois postes côte à côte, jamais additionnés : c'est la décision du 08/09,
   et la disposition la rend visible. Un total les aurait fondus. */
.factures-postes {
  display: flex; flex-wrap: wrap; gap: 0.9rem; margin: 0.6rem 0;
}
.facture-poste {
  flex: 1 1 9rem;
  display: flex; flex-direction: column; gap: 0.15rem;
  padding: 0.7rem 0.9rem;
  background: var(--bg-secondary); border-radius: var(--radius-sm);
}
.facture-valeur {
  font-size: var(--taille-sous-titre); font-weight: 700; color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}
.facture-intitule { font-size: var(--taille-mention); color: var(--text-secondary); }

/* ---- les motifs de rattachement, la passe 2 ------------------------------
   Ils reprennent la disposition des antennes, ligne par ligne : c'est le même
   geste, sur le même écran, et deux allures pour un même travail feraient
   croire à deux outils. Deux ajouts seulement. */

/* La ligne où l'on ÉCRIT un motif, plutôt que d'en choisir un. Elle se
   distingue par son fond, sans quoi elle se lirait comme un motif existant
   dont le nom serait vide. */
.antenne-ligne.antenne-neuf { background: var(--bg-secondary); }
.champ-motif {
  padding: 0.35rem 0.5rem;
  border: 1px solid var(--border-champ); border-radius: var(--radius-sm);
  background: var(--bg-primary); color: var(--text-primary);
  font-size: var(--taille-courant); font-family: inherit;
}
/* ⚠️ LA LIGNE DU RESTE A SA PROPRE GRILLE, ET C'EST NÉCESSAIRE. Celle des
   antennes a cinq colonnes, dont deux pour une cible que ces lignes-là n'ont
   pas : le libellé se retrouvait dans une colonne étroite et sortait en
   « FAC. FS050945AA250… ». Or c'est LUI qu'on lit pour écrire un motif. Vu à
   l'écran le 09/09. */
#motifs-reste .antenne-ligne {
  grid-template-columns: 6.5rem minmax(0, 1fr) auto;
}
/* Le libellé passe à la ligne plutôt que d'être coupé : jusqu'à 121
   caractères, et chacun peut porter le mot qui fait la règle. */
#motifs-reste .antenne-nom { white-space: normal; line-height: 1.35; }
#motifs-reste-titre { margin-top: 1.6rem; }


/* ── La bannière du mode support ───────────────────────────────────────────
 *
 * ⚠️ OCRE, ET NON ROUGE. Le rouge dit « quelque chose ne va pas » ; ici tout va
 * bien, c'est seulement un état qu'il ne faut pas oublier. Un bandeau rouge
 * permanent cesse d'être lu en deux jours, et il aurait rendu l'écran alarmant
 * pour un client qui l'aperçoit par-dessus l'épaule de l'éditeur.
 *
 * Collée en haut, pleine largeur, au-dessus de tout : elle ne doit pas pouvoir
 * défiler hors de vue, sinon elle ne prévient plus de rien. */
.banniere-support {
  position: sticky;
  top: 0;
  z-index: 2000;
  margin: 0;
  /* ⚠️ UNE HAUTEUR CONNUE, PARCE QUE LA PILULE DOIT S'EN ÉCARTER. Sur grand
     écran, `.pilule` est en position absolue à 0.9rem du haut : elle ignore le
     flux, donc la bannière lui passait DESSUS et rognait « données au
     12/06/2026 ». Vu par Victor sur sa capture du 11/09/2026.

     La hauteur est déclarée ici et reprise là-bas par la même variable :
     un nombre magique écrit à deux endroits finit toujours par ne plus
     s'accorder. Sur téléphone, la pilule reprend le flux (`position: static`)
     et se place d'elle-même sous la bannière : aucune règle n'y est utile. */
  min-height: var(--h-banniere-support);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.35rem 1rem;
  background: #f6e7c9;
  color: #7a4a00;
  border-bottom: 1px solid #d8b877;
  font-size: var(--taille-courant);
  font-weight: 600;
  text-align: center;
}

@media (max-width: 48rem) {
  /* Le nom du client reste, le reste de la phrase se replie : c'est le nom qui
     évite l'erreur, pas la phrase. */
  .banniere-support { font-size: var(--taille-mention); padding-inline: 0.6rem; }
}

/* ── L'écran « Qui a changé quoi » ─────────────────────────────────────────
 *
 * Une liste, pas un tableau : les lignes sont des `div` en grille, ce qui
 * permet de replier les colonnes sur téléphone sans casser la structure. Même
 * choix que le tableau des interventions, et pour la même raison.
 */
.modif-barre {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 1rem;
  margin-bottom: 0.6rem;
}

.modif-filtre {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: var(--taille-courant);
  color: var(--text-muted);
}

.modif-liste {
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  max-height: 34rem;
  overflow-y: auto;
}

/* Cinq colonnes. La quatrième, « qui », ne rétrécit pas : c'est la réponse à
   la question que l'écran pose. */
.modif-ligne {
  display: grid;
  grid-template-columns: auto minmax(8rem, 1fr) auto auto minmax(0, 1.4fr);
  align-items: baseline;
  gap: 0.3rem 0.9rem;
  padding: 0.45rem 0.8rem;
  font-size: var(--taille-courant);
}

.modif-entete {
  font-size: var(--taille-mention);
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--text-muted);
  padding-inline: 0.8rem;
}

/* ⚠️ UN HORODATAGE NE SE COUPE JAMAIS EN DEUX : coupé, il se relit deux fois.
   Défaut mesuré à 390 px le 09/09 sur une autre liste. */
.modif-quand {
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
}

.modif-objet { font-weight: 600; }

.modif-operation {
  font-size: var(--taille-mention);
  padding: 0.05rem 0.4rem;
  border-radius: 999px;
  background: var(--bg-tertiary);
  color: var(--text-muted);
  white-space: nowrap;
}

/* Quatre gestes, des teintes discrètes. La couleur dit le geste, elle ne crie
   pas : rien ici n'est une alerte. La modification garde le gris de base, qui
   est le cas courant ; l'entrée prend l'ocre de la bannière de mode support,
   pour que les deux marques du même mécanisme se reconnaissent entre elles. */
.modif-creation { background: #e7f3ec; color: #1d6b3f; }
.modif-suppression { background: #fdecec; color: #9b2c2c; }
.modif-acces { background: #f6e7c9; color: #7a4a00; }

.modif-auteur { white-space: nowrap; }

/* ⚠️ « Arvitria » À CÔTÉ DU NOM, ET NON À LA PLACE. Le client doit lire les
   deux : qui est venu, et pour le compte de qui. Une pastille sobre, dans
   l'ocre du mode support, pour que la ligne du journal et la bannière que voit
   l'opérateur disent visiblement la même chose. */
.modif-editeur {
  margin-left: 0.35rem;
  font-size: var(--taille-mention);
  font-weight: 600;
  padding: 0.05rem 0.35rem;
  border-radius: 999px;
  background: #f6e7c9;
  color: #7a4a00;
  white-space: nowrap;
}
.modif-quoi { color: var(--text-muted); }

/* Le détail, replié. Il occupe toute la largeur sous sa ligne. */
.modif-detail { grid-column: 1 / -1; margin-top: 0.25rem; }
.modif-detail-resume {
  font-size: var(--taille-courant);
  color: var(--text-muted);
  cursor: pointer;
}

.modif-valeurs {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) minmax(0, 1fr);
  gap: 0.15rem 0.8rem;
  margin: 0.4rem 0 0.2rem;
  padding: 0.5rem 0.7rem;
  background: var(--bg-tertiary);
  border-radius: var(--radius-sm);
  font-size: var(--taille-courant);
}

.modif-champ { font-weight: 600; }
/* Les valeurs peuvent être longues : elles se coupent, mais AVEC des points de
   suspension. Une valeur tronquée en silence se lit comme une valeur. */
.modif-avant,
.modif-apres {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.modif-avant { color: var(--text-muted); text-decoration: line-through; }

@media (max-width: 48rem) {
  /* ⚠️ ON RÉDUIT LE NOMBRE DE COLONNES, ON NE LES RÉTRÉCIT PAS. Cinq colonnes
     sur 390 px donneraient cinq colonnes illisibles. Trois rangs : quand et
     geste, l'objet, puis qui et ce qui a changé. */
  .modif-entete { display: none; }

  .modif-ligne {
    grid-template-columns: auto minmax(0, 1fr);
    row-gap: 0.15rem;
  }

  .modif-objet { grid-column: 1 / -1; }
  .modif-auteur { grid-column: 1; }
  .modif-quoi { grid-column: 2; }

  .modif-valeurs { grid-template-columns: minmax(0, 1fr); }
  .modif-avant, .modif-apres { white-space: normal; }

  /* ⚠️ 16 px sur le menu du filtre : en dessous, iOS zoome à la mise au point
     et laisse une page décalée dont on ne sait plus revenir. */
  #modif-filtre-objet { font-size: var(--taille-appuye); min-height: 2.2rem; }
}

/* ---- l'écran des comptes ------------------------------------------------ */
/* Cinq colonnes, même forme que le journal des modifications : deux listes du
   même produit qui se dessinent différemment se lisent deux fois. */
/* ⚠️ DES LARGEURS QUI NE DÉPENDENT PAS DU CONTENU, et c'est la seule façon
   d'obtenir un tableau. Chaque ligne est sa PROPRE grille : avec des colonnes
   en `auto`, deux lignes voisines calculent deux largeurs différentes et rien
   ne s'aligne. Vu à l'écran le 12/09/2026, sur la ligne de son propre compte,
   dont le profil est un texte là où les autres portent une liste déroulante :
   la ligne entière glissait de 90 px vers la droite. Le banc ne voit pas cela,
   et l'en-tête mentait sur la position de ses propres colonnes. */
.compte-ligne {
  display: grid;
  grid-template-columns: minmax(12rem, 2fr) 12rem 11rem 8.5rem minmax(17rem, 1fr);
  align-items: center;
  gap: 0.35rem 1rem;
  padding: 0.55rem 0.8rem;
  font-size: var(--taille-appuye);
}

/* Les comptes sont un tableau, pas un formulaire : la feuille prend la largeur
   qu'on lui a libérée en masquant le volet. */
#panneau-comptes .feuille { max-width: 78rem; }

.compte-entete {
  font-size: var(--taille-mention);
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--text-muted);
  padding-inline: 0.8rem;
}

/* L'adresse porte l'identité, le nom l'explique : deux lignes, deux poids. */
.compte-adresse { display: flex; flex-direction: column; min-width: 0; }
.compte-adresse strong { font-weight: 600; overflow-wrap: anywhere; }
.compte-adresse em { font-style: normal; font-size: var(--taille-courant); color: var(--text-muted); }

.compte-profil { max-width: 100%; }
.compte-profil-fixe { color: var(--text-secondary); }

.compte-etats { display: flex; flex-wrap: wrap; gap: 0.3rem; align-items: center; }

/* ⚠️ LA COULEUR DIT UN ÉTAT, JAMAIS UNE CATÉGORIE, et un état seul ne se lit
   pas : chaque pastille porte son mot. Un compte désactivé reste lisible en
   noir et blanc, et pour un daltonien. */
.compte-etat-actif { color: var(--text-muted); font-size: var(--taille-courant); }
.compte-etat-inactif {
  font-size: var(--taille-courant); font-weight: 600;
  color: var(--accent-red); background: var(--accent-red-light);
  padding: 0.05em 0.45em; border-radius: var(--radius-sm);
}
.compte-etat-attente {
  font-size: var(--taille-courant);
  color: #7c5200; background: var(--accent-amber-light);
  padding: 0.05em 0.45em; border-radius: var(--radius-sm);
}
.compte-etat-operateur {
  font-size: var(--taille-courant);
  color: var(--accent-primary); background: var(--accent-light);
  padding: 0.05em 0.45em; border-radius: var(--radius-sm);
}

/* Un horodatage ne se coupe jamais en deux, même règle que le journal. */
.compte-quand { white-space: nowrap; font-variant-numeric: tabular-nums; color: var(--text-muted); font-size: var(--taille-courant); }
.compte-jamais { font-style: italic; }

.compte-gestes { display: flex; flex-wrap: wrap; gap: 0.35rem; justify-content: flex-end; }
.compte-geste { white-space: nowrap; }

.compte-geste {
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  background: var(--bg-primary);
  color: var(--text-secondary);
  font: inherit;
  font-size: var(--taille-courant);
  padding: 0.3rem 0.6rem;
  cursor: pointer;
}
.compte-geste:hover { border-color: var(--accent-primary); color: var(--text-primary); }
.compte-geste:focus-visible { outline: 2px solid var(--accent-primary); outline-offset: 1px; }

.compte-cest-vous { font-size: var(--taille-courant); color: var(--text-muted); font-style: italic; }

/* La réinitialisation naît SOUS la ligne du compte, et non dans une fenêtre :
   on doit voir à qui l'on change le mot de passe pendant qu'on le tape. */
.compte-reinitialisation {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  padding: 0.7rem 0.9rem;
  background: var(--bg-tertiary);
  border-left: 3px solid var(--accent-primary);
}
.compte-reinitialisation label { font-size: var(--taille-courant); color: var(--text-secondary); }
.compte-mdp {
  border: 1px solid var(--border-champ);
  border-radius: var(--radius-sm);
  padding: 0.45rem 0.6rem;
  min-width: 14rem;
  background: var(--bg-primary);
  color: var(--text-primary);
}

/* Au téléphone, cinq colonnes deviennent cinq lignes : un tableau ne se lit
   pas à 390 px, et la coupure est celle de toute la maison. */
@media (max-width: 48rem) {
  .compte-entete { display: none; }
  .compte-ligne {
    grid-template-columns: 1fr;
    gap: 0.4rem;
    padding: 0.8rem;
  }
  .compte-gestes { justify-content: flex-start; }
  /* 48 px, la meme cible que la connexion et les tuiles du portail : trois
     valeurs differentes dans un meme produit se sentent sous le pouce. */
  .compte-geste { min-height: 3rem; padding-inline: 0.9rem; }
  .compte-mdp { min-height: 3rem; width: 100%; }
}

/* Le mot de passe proposé : le champ prend la place, les deux boutons suivent. */
.mdp-propose { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; }
.mdp-propose input {
  flex: 1 1 14rem;
  min-width: 0;
  border: 1px solid var(--border-champ);
  border-radius: var(--radius-sm);
  padding: 0.45rem 0.6rem;
  background: var(--bg-primary);
  color: var(--text-primary);
  font-family: var(--police-mono, ui-monospace, monospace);
}
/* Ce qu'on va devoir dicter se lit mieux dans une police à chasse fixe : le
   « 1 » ne se confond pas avec le « l », ni le « 0 » avec le « O ». */
.compte-mdp { font-family: var(--police-mono, ui-monospace, monospace); }

.compte-avertissement {
  flex-basis: 100%;
  margin: 0.2rem 0 0;
  font-size: var(--taille-courant);
  color: var(--text-muted);
}

/* Un geste armé : il dit qu'il attend une confirmation, et il le dit par le mot
   autant que par la couleur. Voir le pavé de comptes.js : six écritures que
   personne n'avait demandées, le 12/09/2026. */
.compte-geste-arme {
  border-color: var(--accent-red);
  color: var(--accent-red);
  background: var(--accent-red-light);
  font-weight: 600;
}
.compte-profil-choix { display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: center; }
.compte-profil-choix select { min-width: 0; flex: 1 1 9rem; }
.compte-geste-appliquer {
  border-color: var(--accent-primary);
  color: var(--accent-primary);
  background: var(--accent-light);
  font-weight: 600;
}

/* ---- le rail : les domaines a 64 px, et les deux sources -----------------
   Forme « f » de la maquette du 15/09, mesuree sur le rendu et non estimee.
   Voir la maquette 2026-09-15-boutons-de-source.html, dossier « propositions » du depot batifocale. */
.rail-domaines .carre-domaine { max-width: 64px; margin: 0 auto; }

.filet-rail { border: 0; border-top: 1px solid var(--border-color); margin: 0.75rem 0.15rem 0.6rem; }

.groupe-sources { display: flex; flex-direction: column; gap: 0.3rem; }

/* ⚠️ UNE LIGNE, PAS UN CARRE A LEGENDE, et c'est le libelle qui l'impose : il
   appartient au client (`libelle_regie`, `libelle_depenses`) et peut faire
   trente caracteres. Mesure sur maquette : sous un carre de 64 px,
   « Interventions regie » se replie deja en deux lignes. */
.bouton-source {
  display: flex; flex-direction: row; align-items: center; gap: 0.5rem;
  width: 100%; padding: 0.3rem; border: 0; background: none;
  cursor: pointer; text-align: left;
}
.bouton-source .carre-domaine {
  width: 32px; max-width: 32px; flex: 0 0 32px; border-radius: 10px;
  --teinte-domaine: var(--texte-bleu);
}
.bouton-source .nom-domaine { text-align: left; }

/* ---- les ateliers de l'écran Agents, en boutons à bascule (23/09) -------- */
.choix-ateliers { display: flex; flex-direction: column; gap: 0.3rem; }
.choix-ateliers-intitule {
  font-size: var(--taille-mention); font-weight: 700; color: var(--text-muted);
  text-transform: uppercase; letter-spacing: 0.04em;
}
.groupe-ateliers { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.bouton-atelier {
  display: inline-flex; align-items: center; gap: 0.45rem;
  padding: 0.3rem 0.7rem 0.3rem 0.35rem;
  border: 1px solid var(--border-color); border-radius: 999px;
  background: var(--bg-primary); color: var(--text-primary);
  font: inherit; font-size: var(--taille-courant); cursor: pointer;
}
.bouton-atelier .carre-domaine { width: 1.7rem; height: 1.7rem; }
/* ⚠️ ALLUMÉ ET ÉTEINT DOIVENT SE DISTINGUER DE LOIN (Victor, 23/09 : « le
   contraste n'est pas assez clair »). Allumé : cadre bleu épais, fond teinté,
   nom en gras, comme les domaines du rail. Éteint : pointillés, fond gris,
   pictogramme en gris, nom barré. Trois indices et non la seule couleur
   (RGAA 3.1). */
.bouton-atelier[aria-pressed="true"] {
  border: 2px solid var(--texte-bleu);
  background: color-mix(in srgb, var(--texte-bleu) 10%, var(--bg-primary));
}
.bouton-atelier[aria-pressed="true"] .nom-domaine { font-weight: 700; }
.bouton-atelier[aria-pressed="false"] {
  border: 2px dashed var(--border-color);
  background: var(--bg-secondary); color: var(--text-muted);
}
.bouton-atelier[aria-pressed="false"] .carre-domaine { filter: grayscale(1); opacity: 0.5; }
.bouton-atelier[aria-pressed="false"] .nom-domaine { text-decoration: line-through; }
.bouton-atelier:focus-visible { outline: 3px solid var(--texte-bleu); outline-offset: 2px; }

/* ---- les domaines des interventions d'un agent (23/09) ------------------- */
.domaines-agent { display: flex; flex-direction: column; gap: 0.4rem; min-width: 14rem; }
.domaines-agent-titre {
  margin: 0; font-size: var(--taille-mention); font-weight: 700; color: var(--text-muted);
  text-transform: uppercase; letter-spacing: 0.04em;
}
.domaines-agent-liste { display: flex; flex-direction: column; gap: 0.3rem; }
.domaine-agent {
  display: grid; grid-template-columns: 1.9rem 1fr auto 3.2rem; align-items: center; gap: 0.5rem;
  font-variant-numeric: tabular-nums;
}
.domaine-agent .carre-domaine { width: 1.9rem; height: 1.9rem; }
/* Le domaine dominant, son pictogramme en grand (Victor, 23/09). */
.domaine-agent.dominant { grid-template-columns: 2.8rem 1fr auto 3.2rem; }
.domaine-agent.dominant .carre-domaine { width: 2.8rem; height: 2.8rem; }
.domaine-agent-nom { color: var(--text-primary); }
.domaine-agent-heures { font-weight: 700; text-align: right; }
.domaine-agent-part { color: var(--text-secondary); text-align: right; }

/* ---- le taux d'information par atelier, pour le service (23/09) --------- */
.taux-atelier {
  display: grid; grid-template-columns: 1.9rem 1fr auto 5.5rem; align-items: center; gap: 0.5rem;
  font-variant-numeric: tabular-nums;
}
.taux-atelier .carre-domaine { width: 1.9rem; height: 1.9rem; }
.taux-atelier-nom { color: var(--text-primary); }
.taux-atelier-valeur { font-weight: 700; text-align: right; }
.taux-atelier-masque { color: var(--text-muted); font-size: var(--taille-mention); text-align: right; }
.taux-atelier-agents { color: var(--text-secondary); text-align: right; }


/* ⚠️ LA PLACE DE LA BARRE VERTICALE EST RÉSERVÉE, depuis le 23/09. Sans elle,
   la barre apparaissait quand la page s'allongeait, la toile perdait sa
   largeur, et l'histogramme dessiné pour l'ancienne largeur défilait de
   côté : vu par Victor sur 2026. */
#panneau-activite { scrollbar-gutter: stable; }

/* ---- « heures dues » ici, et les 1 607 heures légales (23/09) ------------ */
.dues-explication {
  margin: 0.2rem 0 1rem; padding: 0.9rem 1.2rem;
  border-left: 3px solid var(--texte-bleu); border-radius: 0 8px 8px 0;
  background: var(--bg-secondary); color: var(--text-secondary);
  font-size: var(--taille-courant); max-width: 60rem;
}
.dues-explication p { margin: 0 0 0.55rem; }
.dues-explication p:last-child { margin-bottom: 0; }
.dues-explication-titre { font-weight: 700; color: var(--text-primary); }

/* ---- calendrier ou barres, jamais les deux (23/09) ----------------------- */
#panneau-activite[data-jours="calendrier"] #activite-toile { display: none; }
#panneau-activite[data-jours="barres"] #activite-frise,
#panneau-activite[data-jours="barres"] .barre-frise .commande-case,
#panneau-activite[data-jours="barres"] #frise-compte { display: none; }

/* Les codes d'intervention du détail d'un site s'ouvrent (23/09). */
.lien-intervention {
  border: 0; background: none; padding: 0; margin-right: 0.4rem;
  font: inherit; color: var(--texte-bleu); cursor: pointer; text-decoration: underline;
}
.lien-intervention:focus-visible { outline: 2px solid var(--texte-bleu); outline-offset: 2px; }
#activite-retour-service { margin: 0 0 0.6rem; }

/* ---- tout le service, à l'ouverture de l'écran Agents (23/09) ------------
   Les jours sont ceux d'une personne : ni tuiles de jours, ni frise, ni
   barres pour le service. La phrase d'invitation en tient la place. */
#panneau-activite[data-vue="service"] .tuiles-activite,
#panneau-activite[data-vue="service"] .barre-frise,
#panneau-activite[data-vue="service"] #activite-legende,
#panneau-activite[data-vue="service"] #activite-frise,
#panneau-activite[data-vue="service"] .cadre-activite { display: none; }
.invite-agent {
  margin: 1rem 0; padding: 0.75rem 1rem; border-radius: 10px;
  background: var(--bg-secondary); color: var(--text-secondary);
}
/* Grisé là où il ne commande rien : Agents et Heures de régie (23/09). */
.bouton-source:disabled { opacity: 0.4; cursor: not-allowed; }
.bouton-source:disabled:hover .carre-domaine { box-shadow: none; }
.bouton-source:hover .carre-domaine { box-shadow: var(--shadow-flottant), 0 6px 14px rgba(16, 24, 40, .12); }
.bouton-source:focus-visible { outline: 3px solid var(--texte-bleu); outline-offset: 2px; border-radius: 12px; }

/* Allume : le cadre se remplit. C'est la FORME qui dit le choix, pas la
   teinte, sans quoi le rail compterait dix couleurs et la couleur cesserait
   de vouloir dire « domaine ». */
.bouton-source[aria-pressed="true"] .carre-domaine {
  border-color: var(--texte-bleu);
  background: color-mix(in srgb, var(--texte-bleu) 8%, white);
}
.bouton-source[aria-pressed="true"] .nom-domaine { color: var(--text-primary); font-weight: 600; }

/* ⚠️ ETEINT, LE DESSIN PALIT ET LE LIBELLE GARDE SON CONTRASTE. C'est
   l'arbitrage 2 du 15/09, mesure : `opacity: .5` sur le bouton entier met le
   libelle a 2,07:1, tres en dessous du seuil, et un bouton non retenu n'est
   pas un bouton desactive. */
.bouton-source[aria-pressed="false"] .carre-domaine { filter: grayscale(1); opacity: .5; }
.bouton-source[aria-pressed="false"] .nom-domaine { color: var(--text-muted); }

/* ---- le grand chiffre de l'infobulle, 18/09 -----------------------------
   La mesure choisie, en gros, juste sous le nom du site : c'est par elle que la
   carte classe et dimensionne, et on ne doit pas avoir à la chercher.

   ⚠️ DEUX CHIFFRES SOUS « COUT TOTAL », ET ILS PARTAGENT LA LARGEUR. `1fr 1fr`
      plutot qu'un flex : deux colonnes egales gardent les nombres alignes a
      gauche quelle que soit leur longueur, alors qu'un flex fait sauter le
      second selon que le premier porte six chiffres ou quatre.

   ⚠️ MAIS UNE COLONNE NE DESCEND PLUS SOUS SA MENTION, depuis le 24/09. Le
      fonctionnement compte de nouveau les fluides, et son ratio passe à trois
      chiffres sur les sites qui chauffent un bassin : « 172 €/m²/an bâtiment
      seul » demandait 139 px dans une colonne de 131, et débordait dans
      l'intervalle jusqu'à coller au ratio voisin. La mention ne se replie pas
      (règle du 19/09, voir carte.js) : c'est donc l'infobulle qui s'élargit,
      de 271 à 281 px sur le Centre Aquatique, sous son plafond de 320. Les
      colonnes implicites servent le cas d'un seul chiffre, qu'une répétition
      fixe à deux couperait en deux. */
.popup-mesure {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(max-content, 1fr);
  gap: 0.6rem;
  padding: 0.5rem 0 0.15rem;
}
.popup-mesure-valeur strong {
  display: block;
  /* 24 px, mesure le 18/09 : « 204 338 EUR » y occupe 84 px des 156 d'une
     colonne, et « 1 474 976 EUR », le plus long du jeu, 103. On monte d'un cran
     parce que la place existe, pas parce que le cran d'au-dessus existe. */
  font-size: var(--taille-titre);
  font-weight: 700;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}
/* Le nom de la mesure sous son nombre, comme la source sous les cartons du
   volet : sept mesures sont offertes et trois s'ecrivent en euros.

   ⚠️ IL PESE UN CRAN DE PLUS DEPUIS LE 19/09, ET C'EST UNE DEMANDE DE VICTOR :
      « mettre mieux en evidence le texte Investissement et fonctionnement sous
      les gros chiffres du haut ». Mesure faite avant de choisir, sur la largeur
      REELLE du texte et non sur sa boite : le mot occupait 78 px quand le ratio
      qui le suit en occupe 135. Il etait la ligne la plus etroite ET la plus
      pale d'un bloc de trois, alors qu'il dit ce qu'on regarde.

      UN CRAN DE L'ECHELLE, PAS DES CAPITALES. Trois autres traitements ont ete
      montres : les capitales espacees portent « INVESTISSEMENT » a 113 px, soit
      plus large que le nombre au-dessus qui en fait 108, pour un mot qui n'est
      qu'une etiquette ; et remonter le mot AU-DESSUS du nombre inversait
      l'ordre de lecture des quatre ecrans deux jours apres l'avoir fixe.
      Contraste mesure : 7,58:1 contre 5,51, RGAA 4.1.2 critere 3.2. */
.popup-mesure-nom {
  display: block;
  color: var(--text-secondary);
  font-size: var(--taille-courant);
  font-weight: 600;
}

/* ⚠️ LE RATIO AU M2 EST PASSE SOUS LE GROS CHIFFRE LE 19/09, depuis la tete de
   zone qui le portait. Il y a donc DEUX nombres empiles, le montant COMPLET et
   un ratio calcule sur le BATIMENT SEUL, et ils ne se divisent pas l'un
   l'autre : mesure du 19/09, sur 9 des 66 sites de 2024 le montant depasse la
   base du ratio de plus de 20 %, et jusqu'a 437 %.

   ⚠️ « batiment seul » TIENT SUR LA MEME LIGNE QUE LE RATIO, jamais en dessous.
      Victor, le 19/09 : « sinon on pourrait croire que tous les chiffres des
      sous-totaux concernent uniquement le batiment ». Une mention seule sur sa
      ligne se lit comme un en-tete de ce qui suit, or les sous-totaux sont
      complets. `white-space: nowrap` est donc ce qui tient la decision : mesure,
      « 27 €/m²/an batiment seul » occupe 135 px des 179 d'une colonne. */
.popup-mesure-ratio {
  display: block;
  color: var(--text-muted);
  font-size: var(--taille-mention);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ---- l'infobulle en trois zones, 15/09 ---------------------------------- */
.popup-zone { margin-top: 0.5rem; }
.popup-zone-activite { margin-top: 0.35rem; }
.popup-zone + .popup-zone { border-top: 1px solid var(--border-color); padding-top: 0.45rem; }
.popup-zone-tete {
  display: flex; align-items: baseline; gap: 0.6rem;
  padding: 0.12rem 0; color: var(--text-primary); font-weight: 600;
}
.popup-zone-nom { flex: 1; }
.popup-zone-total { font-variant-numeric: tabular-nums; }
/* Le ratio se lit a cote du nombre dont il est le quotient : c'est ce qui le
   rend verifiable de tete. */
.popup-zone-ratio {
  color: var(--text-muted); font-weight: 500; font-size: var(--taille-mention);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* ⚠️ LE PERIMETRE PASSE SOUS LE RATIO, ET IL N'EST PAS DECORATIF. Depuis le
   17/09 le ratio porte le BATIMENT SEUL quand le montant reste complet : sur le
   cimetiere en 2024, la zone affiche 44 574 EUR et son ratio porte 8 302. Sans
   cette mention, ce sont deux nombres justes que le voisinage rend faux.

   Il tient sur sa propre ligne plutot qu'a la suite du ratio : la tete de zone
   est une rangee flex, et le panneau d'une fiche ne fait que 281 px par
   colonne, mesure le 16/09. « 41,2 EUR/m2 batiment seul, par an » sur une seule
   ligne y ecraserait le nom de la zone. */
.popup-zone-perimetre {
  display: block; text-align: right;
  font-variant-numeric: normal; font-weight: 400;
}
/* Le compte d'une ligne : il suit le libelle, jamais le montant, pour qu'on ne
   lise pas « 39 » comme une partie de « 50 886 € ». */
.popup-compte {
  margin-left: 0.4rem; padding: 0 0.35rem;
  border-radius: 999px; background: var(--bg-secondary);
  color: var(--text-secondary); font-weight: 600;
  font-size: var(--taille-mention); font-variant-numeric: tabular-nums;
}

/* ===========================================================================
   LE VISUEL DANS LA CARTE D'IDENTITE, 16/09/2026, REPARE LE 21/09/2026

   ⚠️ L'IDENTITE N'EST PAS UNE SEULE RANGEE DE FAITS. Elle porte un visuel a
      gauche, photo ou vignette de carte, et ses lignes a droite.

   ---------------------------------------------------------------------------
   🔴 LE BLOC TOMBAIT A 26 PX DE LARGE, ET NEUF MOTS S'Y COUPAIENT EN DEUX
   ---------------------------------------------------------------------------

   Mesure de Victor, les 19 et 21/09 : « Bleuets », « plancher », « logements »
   et « batiment » coupes au milieu. Les 474 controles etaient verts.

   ⚠️ LA CAUSE N'ETAIT PAS UNE VALEUR MAL CHOISIE, C'ETAIT LE REFERENT. Deux
      grilles imbriquees, `.site-reponse` en `2.6fr 1fr` et celle-ci en
      `15rem 1fr`, dont les points de bascule etaient poses sur la largeur de
      la FENETRE. Or la largeur reelle de ce bloc depend aussi du volet
      lateral, qui fait 460 px par defaut et QUE L'UTILISATEUR REDIMENSIONNE A
      LA SOURIS. Entre les deux paliers, la premiere colonne prenait ses 15 rem
      et la seconde recevait ce qui restait, soit presque rien. Aucune valeur
      ne corrigeait ca : un `@media` ne peut pas voir une largeur qu'il ne
      mesure pas.

   ⚠️ ET `minmax(0, 1fr)` EST CE QUI L'AUTORISAIT. Le zero en minimum dit
      explicitement « cette colonne a le droit de tomber a rien ». Il avait ete
      pose pour eviter un debordement, et il a remplace un debordement visible
      par un ecrasement silencieux.

   LA REPARATION : un `flex-wrap` dont les deux enfants portent une base
   minimale. Quand la place manque, ils NE S'ECRASENT PAS, ils passent l'un
   sous l'autre, et le repli suit la largeur du BLOC, pas celle de la fenetre.
   C'est la meme mecanique que `.site-identite` de base, ligne 6105, a laquelle
   on revient apres l'avoir quittee pour une grille.
   =========================================================================== */
.site-identite {
  align-items: stretch;
}
/* ⚠️ LES LIGNES SONT DANS UNE BOITE A ELLES, et c'est la condition du repli :
   un `flex-wrap` replie ses ENFANTS DIRECTS, et les quatre lignes en etaient.
   Elles se seraient donc repliees une a une autour de la photo. */
/* ⚠️ LES PROPORTIONS ONT ÉTÉ INVERSÉES LE 21/09/2026, ET C'EST UNE SEULE
   CORRECTION POUR DEUX REMARQUES DE VICTOR : « agrandir la photo » et
   « réduire cet espace ».

   Les deux ne faisaient qu'une. Les faits prenaient la plus grosse part de la
   largeur, alors qu'ils portent trois lignes courtes, « Adresse », « Quartier »,
   « Surface de plancher » : le texte s'alignait à gauche et laissait derrière
   lui une bande vide qui allait jusqu'aux cartons. La photo, elle, était bornée
   à 15 rem. Donner la grande part au visuel remplit la bande ET agrandit la
   photo, sans toucher à la colonne des cartons.

   ⚠️ LES MINIMUMS NE BOUGENT PAS, et c'est ce qui garde la réparation des
      26 px : quand la place manque, les deux passent l'un sous l'autre au lieu
      de s'écraser. Ce sont les BASES qui comptent, jamais les proportions. */
/* ⚠️ CETTE COLONNE NE GRANDIT PLUS DEPUIS LE 21/09, flex-grow passe a zero.
   Elle porte trois lignes courtes, dont la plus large est la precision de la
   surface, « (8 634 m2 + 85 m2 de logements) ». Sa base de 13 rem les tient.
   Tout ce qu'elle recevait en plus devenait du blanc entre le texte et les
   tuiles, celui-la meme que Victor signale. Ce qui reste va au visuel, qui lui
   a toujours de quoi l'employer. */
.site-identite > .site-identite-faits {
  flex: 0 1 13rem;
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
  min-width: 0;
}
/* ⚠️ LE VISUEL PREND TOUTE LA HAUTEUR DES LIGNES, et c'est ce qui evite le
   trou blanc sous lui quand le site porte quatre lignes au lieu de deux.
   `align-items: stretch` sur le parent, et rien a calculer ici. */
/* ⚠️ AGRANDIE UNE SECONDE FOIS LE 21/09/2026, Victor : « agrandis l'image, car
   il reste de la place ». Mesure prise sur sa capture, fenetre large : la photo
   occupait 445 x 238 px, et la colonne des faits laissait environ 120 px de
   blanc a sa droite, ses trois lignes etant courtes.

   La part de largeur passe de 2 a 3 contre 1, et la hauteur minimale de 12 a
   16 rem. La hauteur compte autant que la largeur ici : la rangee prend la
   hauteur du plus grand de ses deux enfants, donc sans ce plancher la photo ne
   depassait jamais la hauteur du texte a cote d'elle. */
/* ⚠️ LE CADRE PORTE UN RAPPORT, ET NON PLUS UNE HAUTEUR FIXE, 21/09/2026.
   Victor : « je ne veux pas tronquer la photo en fonction de la largeur de la
   fenetre ». Le mot qui compte est EN FONCTION DE LA LARGEUR : la hauteur etait
   figee a 16 rem pendant que la largeur suivait la fenetre, si bien que la part
   coupee changeait a chaque redimensionnement.

   Un rapport fixe rend la coupe independante de la fenetre. Reste a choisir
   lequel, et les 63 photos l'ont dit elles-memes :

       rapport minimum ......... 1,00     (une seule, carree)
       premier quartile ........ 1,49
       MEDIANE ................. 1,67
       troisieme quartile ...... 1,84
       maximum ................. 2,29

   5 / 3 vaut 1,667, la mediane au millieme pres. Le cadre passe de 1,34 a
   1,67 : avant, il coupait presque toutes les photos ; apres, la moitie d'entre
   elles perdent moins de quelques pour cent.

   ⚠️ ET CE RAPPORT COMPTE ENCORE PLUS DEPUIS LE PASSAGE EN `contain` le
      22/09/2026. Il ne decide plus de ce qui se perd, il decide de ce qui
      RESTE VIDE : une photo dont le rapport est proche de 1,67 remplit
      presque tout le cadre, et les bandes de fond ne se voient qu'aux
      extremes, 1,04 et 2,29. Choisir la mediane etait deja le bon calcul, il
      se trouve qu'il sert maintenant l'inverse de ce pour quoi il a ete fait.

   ⚠️ align-self: start ANNULE LE STRETCH DU PARENT, sans quoi le rapport ne
      s'appliquerait pas : « .site-identite » est en align-items: stretch, et
      une hauteur imposee par le parent l'emporte sur aspect-ratio.

   Mesure a fenetre 1600 px : la photo passe de 343 x 256 a 436 x 262, soit
   27 % de largeur en plus. */
.site-identite > .site-visuel {
  flex: 1 1 18rem;
  aspect-ratio: 5 / 3;
  align-self: start;
  display: flex;
}
.site-identite > .site-visuel > * { flex: 1; min-width: 0; }
/* ⚠️ ET LA VIGNETTE DOIT SUIVRE LE CADRE, elle que 42 sites sur 104 affichent
   faute de photo. Sa hauteur est fixee a 12 rem plus haut, ce qui l'emportait
   sur l'etirement : mesure le 21/09, elle rendait 436 x 192 dans un cadre de
   436 x 262, soit 70 px de blanc dessous. */
.site-identite > .site-visuel > .site-vignette { height: auto; }

/* ===========================================================================
   LES TUILES DE LA FICHE D'UN SITE, 16/09/2026

   Quatre tuiles de nature, puis un filet, puis deux d'une autre nature. Chacune
   est un BOUTON : elle ouvre le detail de ce qu'elle annonce.

   ⚠️ minmax(0, 1fr) ET NON 1fr, et ce zero n'est pas cosmetique : sans lui, un
      contenu long elargit sa colonne et les tuiles cessent d'etre egales. Vu le
      15/09 sur la premiere maquette.
   =========================================================================== */
.site-tuiles { margin-top: 1.25rem; }
.site-tuiles-rangee {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1rem;
  align-items: start;
}
.site-tuiles-rangee.deuxieme { grid-template-columns: repeat(2, minmax(0, 1fr)); }

/* ⚠️ LE FILET DIT QUE LA NATURE CHANGE, il ne decore pas. Voir le commentaire
   d'index.html : sans lui, « Par annee » se lit comme un cinquieme poste. */
/* Le coût de la période, sous les quatre tuiles (arbitrage 20 du 23/09) : leur
   addition, en une phrase, à la taille du texte qu'on lit en premier. */
.site-tuiles-cout {
  margin: 0.9rem 0 0;
  font-size: var(--taille-appuye); font-weight: 600;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}

.site-tuiles-filet {
  display: flex; align-items: center; gap: 0.9rem;
  margin: 1.25rem 0 0.9rem;
  color: var(--text-muted); font-size: var(--taille-mention);
  text-transform: uppercase; letter-spacing: 0.05em;
}
.site-tuiles-filet::before, .site-tuiles-filet::after {
  content: ''; height: 1px; background: var(--border-color); flex: 1;
}

.site-tuile {
  position: relative;
  display: block; width: 100%; text-align: left;
  background: var(--bg-carte, #fff);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md, 14px);
  padding: 1rem 1.1rem;
  font: inherit; color: inherit; cursor: pointer;
  /* ⚠️ LA TUILE SE SOULEVE, ELLE NE SE CERNE PAS. Victor le 15/09 : « j'aime
     quand les tuiles sont encore plus responsives, elles se deplacent un peu,
     effet d'ombre. Je n'aime pas le contour comme reponse. » */
  transition: transform .18s cubic-bezier(.2, .8, .3, 1), box-shadow .18s ease;
}
.site-tuile:hover {
  transform: translateY(-3px);
  box-shadow: 0 2px 4px rgba(16, 24, 40, .06), 0 14px 32px rgba(16, 24, 40, .12);
}
.site-tuile:active { transform: translateY(-1px); }
/* ⚠️ LE FOCUS GARDE SON CONTOUR, et ce n'est pas une contradiction : le survol
   repond a la souris, le focus est la seule chose qui dise ou l'on est au
   clavier. RGAA 10.7 l'exige, et une ombre ne s'y substitue pas. */
.site-tuile:focus-visible { outline: 3px solid var(--accent-primary); outline-offset: 3px; }
/* ⚠️ UN MOUVEMENT SE REFUSE. Le systeme sait dire qu'on n'en veut pas. */
@media (prefers-reduced-motion: reduce) {
  .site-tuile { transition: box-shadow .18s ease; }
  .site-tuile:hover, .site-tuile:active { transform: none; }
}

/* ⚠️ UNE TUILE A ZERO SE MONTRE, ELLE NE DISPARAIT PAS. Mesure du 15/09 : 10
   sites sur 86 n'ont qu'une tuile garnie ou aucune. « Ce site n'a rien depense
   en investissement » est une information ; son absence n'en est pas une. Elle
   se pose a plat sur le fond au lieu d'y flotter. */
.site-tuile.vide { background: var(--bg-secondary); }
.site-tuile.vide .site-tuile-somme { color: var(--text-muted); }
.site-tuile.vide .site-tuile-ouvre { visibility: hidden; }
.site-tuile.vide:hover { transform: translateY(-1px); box-shadow: none; }

.site-tuile-titre {
  margin: 0 0 0.6rem;
  font-size: var(--taille-mention); font-weight: 700;
  letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--text-secondary);
  /* Deux lignes de titre ne doivent pas decaler les sommes entre elles. */
  min-height: 2.4em;
}
.site-tuile-somme {
  margin: 0; font-size: var(--taille-titre); font-weight: 700;
  line-height: 1.1; font-variant-numeric: tabular-nums;
}
.site-tuile-jauge {
  height: 5px; border-radius: 999px; background: var(--bg-secondary);
  margin-top: 0.6rem; overflow: hidden;
}
.site-tuile-jauge > i { display: block; height: 100%; border-radius: 999px; background: var(--accent-primary); }

/* ---- les batons de la tuile « Par annee », 17/09 ------------------------- */
/* ⚠️ CE SONT LES BATONS QUI ONT EMPORTE LA DECISION DE LA TUILE, pas l'inverse.
   Sur la maquette du 16/09 ils montraient d'un coup d'oeil qu'ARTS n'a connu
   AUCUN investissement avant 2024 : un fait qu'aucune colonne de nombres ne
   donne aussi vite.

   Ils prennent la place de la jauge des quatre autres tuiles, et la meme
   hauteur de ligne : les cinq tuiles doivent rester d'egale hauteur, sans quoi
   la rangee se decale et l'oeil lit un ordre qui n'existe pas. */
.site-tuile-batons {
  display: flex; align-items: flex-end; gap: 3px;
  height: 2.4rem; margin-top: 0.6rem;
}
/* Le fut est la COLONNE ENTIERE, le baton la part remplie. Sans le fut, des
   barres de hauteurs differentes ne partagent pas la meme ligne de base et le
   graphique se lit a l'envers. */
.site-baton-fut {
  flex: 1 1 0; display: flex; align-items: flex-end;
  height: 100%; background: var(--bg-secondary); border-radius: 2px;
}
.site-baton {
  display: flex; flex-direction: column; width: 100%; border-radius: 2px;
  overflow: hidden; background: var(--accent-primary);
}
/* Les zones d'un baton de tuile, dans l'ordre des categories declarees. Le
   fond du baton reste visible quand il n'y en a aucune, ce qui arrive sur une
   annee sans donnee : une colonne vide se lit alors comme vide, pas comme
   absente. */
.site-baton-zone { display: block; width: 100%; }
/* ⚠️ L'ANNEE EN COURS SE DISTINGUE, ELLE NE DISPARAIT PAS. Un baton de cinq
   mois et demi a cote d'annees pleines se lit comme un effondrement : vu a
   l'ecran le 17/09, la tuile annoncait « -96 % » sur COUBERTIN en comparant
   2021 a 2026. La tendance ecarte donc cette annee, et la hachure dit pourquoi
   son baton est court. L'effacer aurait cache un chiffre juste. */
.site-baton-en_cours { outline: 1px dashed var(--accent-amber); outline-offset: 1px; }
/* 🔴 UNE ANNEE SANS DONNEE N'EST PAS UNE ANNEE A ZERO EURO. Mesure le 22/09 :
   la derniere ecriture comptable est du 31/12/2025, le dernier pointage de
   regie du 12/06/2026. Un baton a zero, sans marque, se lit « ce site n'a rien
   consomme », et un site qui ne consomme pas d'electricite pendant six mois
   n'existe pas. Le fut garde sa place et se hachure : l'annee EXISTE, c'est sa
   donnee qui manque. */
.site-baton-fut:has(.site-baton-sans_donnee) {
  background: repeating-linear-gradient(
    45deg, var(--bg-tertiary), var(--bg-tertiary) 3px,
    var(--bg-secondary) 3px, var(--bg-secondary) 6px);
}

.site-tuile-part { margin: 0.35rem 0 0; color: var(--text-muted); font-size: var(--taille-mention); }
/* ⚠️ LE RATIO D'UNE TUILE, 21/09/2026, DESCENDU DES DEUX ZONES QUI ONT DISPARU.
   Victor : « je propose de garder les tuiles du bas et d'y ajouter les
   ratios ». Il y gagne en finesse : les zones en rendaient DEUX, un par poste,
   les tuiles en rendent QUATRE, un par nature de depense.

   ⚠️ LE PERIMETRE PASSE SOUS LE RATIO, ET J'AI ESSAYE L'INVERSE D'ABORD. Sur
      la meme ligne, il tenait a fenetre 1600 px, la tuile faisant alors 209 px
      pour 143 px de texte. Mesure a l'ecran a 1050 px, la ou la grille passe a
      deux colonnes : la tuile tombe a 151 px et la phrase se repliait sur
      TROIS lignes, coupee au milieu de « batiment seul ».

      Sous le ratio, et les deux sans repli, la hauteur vaut deux lignes
      partout : 32 px a 1600 px comme a 1050. Une ligne de plus qu'au mieux,
      jamais trois au pire, et la phrase ne se coupe nulle part. */
.site-tuile-ratio {
  margin: 0.15rem 0 0;
  color: var(--text-secondary);
  font-size: var(--taille-mention);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.site-tuile-perimetre {
  display: block;
  color: var(--text-muted);
  font-variant-numeric: normal;
}
.site-tuile-appui { margin: 0.5rem 0 0; color: var(--text-secondary); font-size: var(--taille-mention); }

/* ---- la ventilation d'une tuile, 24/09/2026 ------------------------------ */
/* Une barre de repartition, puis une ligne par domaine : voir
   `poserVentilations()`. Le filet la separe de ce que la tuile disait deja.

   ⚠️ LES MONTANTS ET LES PARTS S'ALIGNENT EN COLONNES, demande de Victor.
   La colonne des parts a une largeur FIXE, en `ch` et chiffres tabulaires :
   c'est ce qui aligne aussi les montants d'une tuile a sa voisine, leur bord
   droit tombant au meme endroit quand les deux tuiles ont la meme largeur.

   ⚠️ ET LA TUILE DESCEND A 151 PX, mesure du 21/09 a fenetre 1050. Le nom se
   coupe alors avec son libelle entier au survol, et sous 170 px de ventilation
   la colonne des parts s'efface : le montant, lui, ne se coupe jamais. */
.site-tuile-ventil {
  container-type: inline-size;
  margin: 0.75rem 0 0; padding-top: 0.6rem;
  border-top: 1px solid var(--border-color);
}
.site-ventil-quoi {
  margin: 0 0 0.4rem;
  font-size: var(--taille-mention); font-weight: 700;
  letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--text-muted);
}
.site-ventil-barre {
  display: flex; gap: 2px; height: 8px;
  border-radius: 3px; overflow: hidden;
}
.site-ventil-barre > span { display: block; min-width: 2px; }
.site-ventil-lignes {
  display: grid;
  grid-template-columns: 0.6rem minmax(0, 1fr) auto 5.2ch;
  gap: 0.25rem 0.45rem; align-items: center;
  margin-top: 0.5rem;
  font-size: var(--taille-mention);
  font-variant-numeric: tabular-nums;
}
.site-ventil-pastille { width: 0.6rem; height: 0.6rem; border-radius: 2px; }
.site-ventil-nom {
  color: var(--text-secondary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.site-ventil-montant { text-align: right; font-weight: 600; white-space: nowrap; }
.site-ventil-part { text-align: right; color: var(--text-muted); white-space: nowrap; }
@container (max-width: 170px) {
  .site-ventil-lignes { grid-template-columns: 0.6rem minmax(0, 1fr) auto; }
  .site-ventil-part { display: none; }
}

.site-tuile-ouvre {
  margin: 0.75rem 0 0; color: var(--accent-primary);
  font-size: var(--taille-mention); font-weight: 600;
}

/* ⚠️ LES TUILES BASCULENT SUR LE BLOC, ET PLUS SUR LA FENETRE, 21/09/2026.
   Meme referent faux que la rangee du haut, un bloc plus bas, et meme
   reparation : la largeur reelle de cette grille depend du volet lateral, que
   l'utilisateur redimensionne a la souris, et qu'un @media ne mesure pas.

   Mesure a l'ecran, fenetre 900 px et volet large : « .site-contenu » ne fait
   plus que 168 px, et les quatre tuiles y tenaient encore de front, a 76 px
   chacune. Aucun des deux @media ne s'etait declenche, la FENETRE faisant
   900 px.

   ⚠️ LES SEUILS VIENNENT DE CE QU'UNE TUILE DEMANDE, pas d'une taille d'ecran.
      Mesure : sous 160 px, le titre se coupe et la ligne du ratio se replie.
      Quatre tuiles et leurs trois ecarts demandent donc 4 x 160 + 3 x 16, soit
      688 px ou 43 rem ; deux tuiles et leur ecart, 336 px ou 21 rem.

   Ce que cela donne, releve a l'ecran :

       fenetre 1600 ... bloc 883 ... 4 colonnes ... tuile 209 px
       fenetre 1300 ... bloc 568 ... 2 colonnes ... tuile 276 px
       fenetre 1150 ... bloc 418 ... 2 colonnes ... tuile 201 px
       fenetre  900 ... bloc 168 ... 1 colonne  ... tuile 168 px

   ⚠️ LE BLOC A 168 px A FENETRE 900 EST UN DEFAUT A PART, et il preexiste :
      c'est toute la fiche qui est ecrasee par le volet a cette combinaison, pas
      seulement ses tuiles. Ce qui change ici est que la grille cesse d'y
      aggraver le mal. */
@container fiche (max-width: 43rem) {
  .site-tuiles-rangee { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@container fiche (max-width: 21rem) {
  .site-tuiles-rangee,
  .site-tuiles-rangee.deuxieme { grid-template-columns: minmax(0, 1fr); }
}

/* ===========================================================================
   LA FEUILLE DE DETAIL, 16/09/2026

   ⚠️ ELLE RECOUVRE L'ECRAN, ELLE NE SE DEPLIE PAS SOUS LES TUILES. Decision 7
      de Victor, mesuree sur maquette : 1 405 px de hauteur de page pour la
      feuille, 1 691 px pour le depliage, 3 267 px pour les blocs empiles
      d'avant. Un depliage pousse tout ce qui suit, et l'on perd la tuile qu'on
      vient de cliquer.

   ⚠️ ET ELLE EST PLUS LARGE QUE LA MODALE D'UNE INTERVENTION, 76 rem contre
      46. Ce n'est pas un gout : la feuille porte des tableaux a six colonnes,
      la modale une fiche a deux. Leur donner la meme largeur aurait fait
      defiler horizontalement sur un ecran de bureau, ce que la decision 9
      reserve au telephone.
   =========================================================================== */
.feuille-detail {
  position: fixed; inset: 0; z-index: 1250;
  display: grid; justify-items: center; align-items: start;
  padding: 3vh 1.5rem 1.5rem;
}
.feuille-fond { position: absolute; inset: 0; background: rgba(15, 23, 42, 0.45); }
.feuille-boite {
  position: relative;
  width: min(76rem, 100%);
  max-height: 94vh;
  display: flex; flex-direction: column;
  background: var(--bg-primary);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
.feuille-tete {
  display: flex; align-items: flex-start; gap: 1rem;
  padding: 1.1rem 1.4rem 0.6rem;
}
.feuille-titres { flex: 1; min-width: 0; }
.feuille-titre { margin: 0; font-size: var(--taille-titre); font-weight: 700; }
.feuille-compte { margin: 0.2rem 0 0; color: var(--text-secondary); }
.feuille-detail .periode-active { margin: 0 1.4rem; }
.feuille-commandes {
  display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: flex-end;
  padding: 0.75rem 1.4rem;
  border-bottom: 1px solid var(--border-color);
}
.feuille-commandes:empty { display: none; }
.feuille-commandes label { display: flex; flex-direction: column; gap: 0.2rem; }
.feuille-commandes label > span {
  color: var(--text-muted); font-size: var(--taille-mention);
  text-transform: uppercase; letter-spacing: 0.04em; font-weight: 700;
}
/* ⚠️ C'EST LE CORPS QUI DEFILE, PAS LA BOITE. Sans cela la tete et les
   commandes partent vers le haut des qu'on descend dans un tableau de deux
   cents lignes, et l'on ne sait plus ce qu'on regarde ni comment le trier. */
/* 🔴 LE REMPLISSAGE DU HAUT EST SORTI DU CONTENEUR QUI DEFILE, LE 21/09/2026,
   ET C'EST LA CAUSE DE DEUX DEFAUTS QUE VICTOR A SIGNALES COMME DISTINCTS :
   « un bug d'affichage avec l'en-tete du tableau des interventions » et
   « d'autres bugs d'affichage avec la 1re ligne des tableaux ». C'en est un
   seul, et il touche TOUTES les feuilles.

   ⚠️ UN EN-TETE EN `top: 0` SE COLLE AU HAUT DE LA BOITE DE REMPLISSAGE du
      conteneur qui defile, jamais au haut de ce qu'on voit. Avec
      `padding-top: 1rem`, il se figeait donc SEIZE PIXELS TROP BAS, et les
      lignes traversaient cette bande en restant visibles AU-DESSUS de lui.
      C'est la tranche de ligne que Victor voit flotter sur ses deux captures.

   ⚠️ CE N'ETAIT PAS `border-collapse`, que j'ai soupconne d'abord. Mesure faite
      hors de l'application, sur une copie exacte de ces regles, avec et sans
      fusion des bordures : le meme ecart de 16 px des deux cotes. Puis, avec le
      correctif ci-dessous :

          AVANT = 16 px          APRES = 0 px

   L'espace du haut ne disparait pas, il change de nature : de remplissage qui
   RESTE, il devient un espace qui DEFILE, comme le contenu. A l'oeil, rien ne
   bouge tant qu'on n'a pas fait defiler. */
.feuille-corps { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 0 1.4rem 1.4rem; }
.feuille-corps::before { content: ''; display: block; height: 1rem; }

/* ⚠️ LE SOUS-TITRE D'UNE FEUILLE, 21/09/2026. La feuille « Investissement,
   externe » porte DEUX tableaux depuis qu'elle reprend celui des engagements :
   les bons de commande, puis le groupe sans bon, qui n'a ni engagé ni
   pourcentage et ne peut donc pas entrer dans le premier. Sans un titre entre
   les deux, on lit la seconde grille comme la suite de la première et l'on
   cherche ses colonnes manquantes. */
.feuille-sous-titre {
  margin: 1.6rem 0 0.5rem;
  font-size: var(--taille-appuye);
  font-weight: 600;
  color: var(--text-secondary);
}
.feuille-tableau { width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums; }
.feuille-tableau th {
  position: sticky; top: 0; z-index: 1;
  background: var(--bg-primary);
  text-align: left; font-size: var(--taille-mention);
  text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--text-muted); font-weight: 700;
  padding: 0.45rem 0.6rem; border-bottom: 1px solid var(--border-color);
}
.feuille-tableau td { padding: 0.45rem 0.6rem; border-bottom: 1px solid var(--bg-secondary); }
.feuille-tableau td.nombre, .feuille-tableau th.nombre { text-align: right; }
.feuille-tableau tfoot td {
  font-weight: 700; border-top: 2px solid var(--border-color); border-bottom: 0;
}
.feuille-vide { color: var(--text-muted); margin: 1rem 0; }

/* ---- la liste des interventions d'une part du graphique (23/09) ----------
   « Plus moderne, plus visuel » : une jauge pour les heures, un libellé sur
   deux lignes, la référence en étiquette sous le site, un chevron qui dit que
   la ligne s'ouvre. Le surlignage de la ligne entière suit le pointeur. */
.tableau-part td { padding: 0.7rem 0.75rem; vertical-align: middle; }
.tableau-part tbody tr { cursor: pointer; transition: background-color 0.12s; }
.tableau-part tbody tr:hover, .tableau-part tbody tr:focus-visible { background: var(--bg-secondary); }
.tableau-part tbody tr:focus-visible { outline: 2px solid var(--accent-primary); outline-offset: -2px; }
.part-heures { display: flex; flex-direction: column; gap: 0.3rem; min-width: 6.5rem; }
.part-heures-valeur { font-weight: 700; color: var(--text-primary); }
.part-jauge {
  display: block; height: 0.4rem; width: 100%;
  background: var(--bg-secondary); border-radius: 999px; overflow: hidden;
}
.part-jauge > i { display: block; height: 100%; background: var(--accent-primary); border-radius: 999px; }
.part-site { display: flex; flex-direction: column; gap: 0.25rem; align-items: flex-start; }
/* Le site a sa largeur : sans elle, le libellé prend toute la place et un nom
   de site s'étale sur trois lignes à 800 px (vu à l'écran le 23/09). */
.tableau-part .part-site-nom { min-width: 12rem; }
.part-site-nom { font-weight: 600; color: var(--text-primary); }
.part-reference {
  font-size: var(--taille-mention); color: var(--text-secondary);
  background: var(--bg-secondary); border-radius: 999px; padding: 0.05rem 0.5rem;
  font-variant-numeric: tabular-nums;
}
.part-libelle {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden; color: var(--text-secondary); line-height: 1.4;
}
.part-ouvrir { color: var(--text-muted); font-size: var(--taille-sous-titre); }
.tableau-part tbody tr:hover .part-ouvrir { color: var(--accent-primary); }

/* ---- les batons empiles de la feuille « Par annee » ---------------------- */
/* ⚠️ EMPILES, ET NON COTE A COTE. Un baton par annee, coupe en deux : le
   fonctionnement dessous, l'investissement dessus. C'est ce qui montre d'un
   coup d'oeil qu'un site n'a connu aucun investissement avant telle annee, le
   fait qui avait emporte la decision de la tuile le 16/09. */
.feuille-batons {
  display: flex; align-items: flex-end; gap: 0.75rem;
  height: 9rem; margin-bottom: 0.4rem;
}
.feuille-baton { flex: 1 1 0; display: flex; flex-direction: column; justify-content: flex-end; height: 100%; }
.feuille-pile {
  display: flex; flex-direction: column; justify-content: flex-end;
  width: 100%; border-radius: 3px 3px 0 0; overflow: hidden;
  background: var(--bg-secondary);
}
.feuille-part-invest { background: var(--accent-primary); }
.feuille-part-fonct { background: color-mix(in srgb, var(--accent-primary) 40%, white); }
.feuille-baton-valeur, .feuille-baton-an {
  text-align: center; font-size: var(--taille-mention); font-variant-numeric: tabular-nums;
}
.feuille-baton-valeur { margin-top: 0.3rem; font-weight: 600; }
.feuille-baton-an { color: var(--text-muted); }
.feuille-legende {
  display: flex; gap: 1.2rem; justify-content: center;
  margin-bottom: 1rem; color: var(--text-secondary); font-size: var(--taille-mention);
}
.feuille-legende span { display: inline-flex; align-items: center; gap: 0.35rem; }
.feuille-legende i { width: 0.7rem; height: 0.7rem; border-radius: 2px; display: inline-block; }
.pastille-invest { background: var(--accent-primary); }
.pastille-fonct { background: color-mix(in srgb, var(--accent-primary) 40%, white); }

/* La jauge d'un engagement : ce qu'on vient y chercher est le RESTE, et la
   barre le montre sans qu'on ait a diviser deux colonnes de tete. */
.jauge-engagement {
  min-width: 4rem; height: 6px; border-radius: 999px;
  background: var(--bg-secondary); overflow: hidden;
}
.jauge-engagement > i { display: block; height: 100%; background: var(--accent-primary); }
/* Un report affiche un montant engage qui ne compte nulle part, voir db/79 :
   sa ligne s'efface pour qu'on ne l'additionne pas de l'oeil. */
.feuille-tableau tr.ligne-report td { color: var(--text-muted); }

/* ---- le libelle d une intervention : plie, et depliable ------------------ */
/* ⚠️ CE NE SONT PAS DES LIBELLES METIER, CE SONT DES COURRIELS RECOPIES. Mesure
   sur les 24 739 qui en portent un : 10 255 commencent par « Bonjour », 121
   portent un numero de telephone. Le plus long fait plusieurs paragraphes, et
   une seule ligne de tableau y suffirait a pousser le cout hors de l ecran.

   ⚠️ ON PLIE L AFFICHAGE, ON NE COUPE PAS LA DONNEE. Victor le 17/09 : « j ai
   besoin de visualiser les libelles en entier. Si jamais on le tronque, il faut
   pouvoir aussi les deployer. » Le texte ENTIER est dans le DOM des le premier
   rendu : la recherche du navigateur le trouve, un lecteur d ecran le lit, un
   copier-coller l emporte. Seul l affichage est borne a deux lignes. */
.objet-cadre { max-width: 34rem; }
/* ⚠️ LES DEUX TEXTES COEXISTENT DANS LE DOCUMENT, et l on montre l un ou
   l autre. Le plie est calcule par plierLeLibelle() : premier point suivi d un
   blanc, ou 80 caracteres. L entier attend a cote, ce qui rend la recherche du
   navigateur et les lecteurs d ecran fiables : un texte fabrique au clic ne
   serait trouve par aucun des deux. */
.objet-texte { display: none; white-space: pre-wrap; }
.objet-ouvert .objet-plie { display: none; }
.objet-ouvert .objet-texte { display: block; }
.objet-deplier {
  margin-top: 0.15rem; padding: 0;
  background: none; border: 0; color: var(--accent-primary);
  font: inherit; font-size: var(--taille-mention); font-weight: 600;
  text-decoration: underline; cursor: pointer;
}

/* Une ligne d intervention s ouvre : elle est un bouton, meme quand elle a l air
   d une ligne de tableau. Meme geste que le tableau de la page. */
.feuille-tableau tr.ligne-intervention { cursor: pointer; }
.feuille-tableau tr.ligne-intervention:hover,
.feuille-tableau tr.ligne-intervention:focus-visible { background: var(--accent-light); }

/* ---- les trois feuilles de depense externe ------------------------------- */
/* ⚠️ LE GROUPE « SANS BON DE COMMANDE » PORTE SON NOM ET SA COULEUR. Decision du
   16/09 : ce n est pas un defaut de saisie qu on cache, c est un fait de
   gestion. Mesure sur 2024 : 3 389 lignes et 2 118 199 EUR, soit 17,7 % de la
   depense externe, ne portent aucun engagement. L ocre le signale sans crier :
   une ligne sans bon n est pas une erreur, c est une ligne a expliquer. */
.feuille-tableau tr.ligne-sans-bon td { background: color-mix(in srgb, var(--texte-ambre) 7%, white); }
.feuille-tableau tr.ligne-sans-bon td:first-child { font-weight: 600; }

/* La nature d un fluide porte ses bons en dessous, en retrait : une nature
   chapeaute, elle ne s ajoute pas a ce qu elle chapeaute. */
.feuille-tableau tr.ligne-nature td { font-weight: 700; border-top: 1px solid var(--border-color); }
.feuille-tableau tr.ligne-sous td:first-child { padding-left: 1.6rem; color: var(--text-secondary); }

/* ---- les groupes qui se deplient, 21/09/2026 ---------------------------- */
/* La porte est un bouton, et elle ne doit pas en avoir l'air : le fond et la
   bordure partent, la cible et le focus restent. */
.groupe-porte {
  display: flex; align-items: baseline; gap: 0.4rem;
  background: none; border: 0; padding: 0;
  font: inherit; color: inherit; text-align: left;
  cursor: pointer; width: 100%;
}
.groupe-porte:hover { text-decoration: underline; }
/* ⚠️ LE CHEVRON A UNE LARGEUR FIXE, sans quoi le libelle se decale d'un pixel
   ou deux entre l'etat ferme et l'etat ouvert, les deux caracteres n'ayant pas
   la meme chasse. Un titre qui bouge au clic se lit comme un defaut. */
.groupe-chevron { flex: none; width: 0.8em; color: var(--text-muted); }
.feuille-tableau tr.ligne-groupe td { font-weight: 600; }
.sous-ligne { display: flex; flex-direction: column; gap: 0.1rem; }
.sous-ligne-tete {
  font-size: var(--taille-mention);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
/* La ligne qui dit ce qui manque n'est pas une donnee : elle ne se clique pas,
   et elle se lit comme une note. */
.feuille-tableau tr.ligne-manque td {
  font-style: italic; color: var(--text-muted); font-size: var(--taille-mention);
}

/* ---- la liste des agents, sous le tableau de la feuille Regie ------------ */
/* ⚠️ A PART, ET SOUS LE TABLEAU. Decision 11 du 16/09 : un regroupement PAR
   AGENT est interdit, et la mesure le dit : 50,6 % des interventions pointees
   occupent plusieurs agents, jusqu a douze. Sur ARTS en 2024, la somme des
   interventions par agent fait 79 pour 46 reelles. La liste est donc une
   lecture a cote, jamais une facon de decouper le tableau. */
.feuille-agents { margin-top: 1.6rem; }
.feuille-sous-titre {
  margin: 0 0 0.5rem; font-size: var(--taille-courant); font-weight: 700;
}
.jauge-agent {
  min-width: 6rem; height: 6px; border-radius: 999px;
  background: var(--bg-secondary); overflow: hidden;
}
.jauge-agent > i { display: block; height: 100%; background: var(--accent-primary); }

/* ⚠️ SUR TELEPHONE LE TABLEAU DEFILE, IL NE SE REPLIE PAS. Decision 9, mesuree :
   un tableau a six colonnes refuse de descendre sous 502 px, meme dans un
   conteneur de 340. Le comprimer de force aurait casse chaque cellule sur
   quatre lignes. ⚠️ ET CE POINT RESTE A EPROUVER SUR UN VRAI TELEPHONE :
   l'emulation de largeur ne prend pas dans le navigateur integre. */
@media (max-width: 46rem) {
  .feuille-detail { padding: 0; }
  .feuille-boite { width: 100%; max-height: 100vh; border-radius: 0; }
  .feuille-corps { overflow: auto; }
  .feuille-tableau { min-width: 34rem; }
}


/* ---- la feuille « par année », quatre onglets, 22/09/2026 ----------------
 *
 * ⚠️ LES TROIS ZONES DU TOTAL GARDENT LE LANGAGE DU GRAPHIQUE D'AVANT :
 *    l'investissement est le bleu de l'accent, le fonctionnement le même bleu
 *    à 40 %. C'est ce que la feuille montrait depuis le 17/09, et le changer
 *    aurait fait réapprendre une lecture pour rien. Seule la troisième zone
 *    est neuve, les fluides ayant quitté le fonctionnement le 22/09.
 *
 * ⚠️ ET ELLES SONT DES JETONS, PAS DES VALEURS ÉCRITES DANS LE JS. Une couleur
 *    de domaine vient de la base ; ces trois-là sont de l'écran, donc du CSS,
 *    et le JS les lit par `var()`. Deux écritures d'une même teinte finiraient
 *    par se répondre de travers en thème sombre.
 */
:root {
  --zone-invest: var(--accent-primary);
  --zone-fluide: #c98a1e;
  --zone-fonct: color-mix(in srgb, var(--accent-primary) 40%, white);
}

.feuille-onglets {
  display: flex; gap: 2px; flex-wrap: wrap;
  border-bottom: 1px solid var(--border-color); margin: 0 0 1rem;
}
.feuille-onglet {
  appearance: none; background: none; border: none; font: inherit;
  padding: 0.5rem 0.85rem 0.55rem; color: var(--text-muted); cursor: pointer;
  border-bottom: 2.5px solid transparent; margin-bottom: -1px;
  border-radius: 6px 6px 0 0;
}
.feuille-onglet:hover { color: var(--text-primary); background: var(--bg-secondary); }
.feuille-onglet[aria-selected="true"] {
  color: var(--accent-primary); border-bottom-color: var(--accent-primary); font-weight: 600;
}
.feuille-onglet:focus-visible { outline: 2px solid var(--accent-primary); outline-offset: 2px; }
.feuille-quoi { margin: 0 0 0.9rem; color: var(--text-muted); font-size: var(--taille-mention); }

/* ---- les pastilles, qui ne commandent que le dessin ---------------------- */
.feuille-pastilles { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0 0 1rem; }
.feuille-pastille {
  display: inline-flex; align-items: center; gap: 0.45rem; cursor: pointer;
  border: 1px solid var(--border-color); background: var(--bg-primary);
  border-radius: 999px; padding: 0.3rem 0.8rem 0.3rem 0.55rem;
  font: inherit; font-size: var(--taille-mention); line-height: 1;
  color: var(--text-primary); transition: border-color 0.13s, background 0.13s;
}
.feuille-pastille svg { width: 1.05rem; height: 1.05rem; flex: 0 0 auto; }
.feuille-puce { width: 0.55rem; height: 0.55rem; border-radius: 3px; flex: 0 0 auto; }
.feuille-pastille:hover { border-color: var(--text-muted); }
.feuille-pastille:focus-visible { outline: 2px solid var(--accent-primary); outline-offset: 2px; }
/* Éteinte, elle reste LISIBLE : un libellé qu'on ne déchiffre plus ne se
   rallume pas, faute de savoir ce qu'il porte. Le trait pointillé dit l'état,
   la couleur ne fait que s'effacer. */
.feuille-pastille[aria-pressed="false"] {
  color: var(--text-muted); background: var(--bg-secondary); border-style: dashed;
}
.feuille-pastille[aria-pressed="false"] svg,
.feuille-pastille[aria-pressed="false"] .feuille-puce { opacity: 0.32; }

/* ---- le graphique -------------------------------------------------------- */
.feuille-graphe { position: relative; margin: 0 0 0.5rem; }
.feuille-chart { width: 100%; height: auto; display: block; overflow: visible; }
.feuille-zone { transition: opacity 0.12s; }
.feuille-graphe.survole .feuille-zone { opacity: 0.34; }
.feuille-graphe.survole .feuille-zone.vive { opacity: 1; }
.feuille-viser { cursor: pointer; }
/* L'année qui n'est pas pleine garde sa place et son cadre : l'effacer
   cacherait un chiffre juste, la laisser nue la ferait comparer aux autres. */
.feuille-cadre-creux {
  fill: color-mix(in srgb, var(--accent-amber) 7%, white);
  stroke: color-mix(in srgb, var(--accent-amber) 35%, white);
  stroke-dasharray: 4 4;
}
.feuille-chart-axe { display: grid; margin: 0.4rem 0 0; }
.feuille-chart-col { display: flex; flex-direction: column; align-items: center; gap: 0.1rem; }
.feuille-chart-valeur {
  font-size: var(--taille-courant); font-weight: 600; color: var(--text-secondary);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* ⚠️ L'ANNÉE PASSE DEVANT LE MONTANT, demandé par Victor le 22/09 : « je veux
   que les années soient plus visibles sous les histogrammes ». Elle était au
   plus petit cran et en gris clair, sous un montant plus gros : on lisait six
   montants et on cherchait à quelle année ils allaient. Elle monte d'un cran et
   passe à l'encre pleine, le montant descend d'un ton. */
.feuille-chart-an {
  font-size: var(--taille-appuye); font-weight: 600; color: var(--text-primary);
  text-align: center; white-space: nowrap;
}
.feuille-chart-etat {
  font-size: var(--taille-mention); color: #8a5a12; text-align: center;
}
/* Un montant coupé en deux par un retour à la ligne se relit deux fois. */
.feuille-tableau td.nombre, .feuille-tableau th.nombre { white-space: nowrap; }

/* ---- l'infobulle, à côté de la colonne et jamais dessus ------------------ */
.feuille-bulle {
  position: absolute; pointer-events: none; z-index: 5;
  transform: translate(0, -50%); min-width: 12rem;
  background: var(--bg-primary); border: 1px solid var(--border-color);
  border-radius: 10px; padding: 0.7rem 0.8rem;
  box-shadow: 0 1px 2px rgba(20, 35, 48, 0.06), 0 8px 24px rgba(20, 35, 48, 0.12);
}
.feuille-bulle.a-gauche { transform: translate(-100%, -50%); }
/* Fixée au clic, elle devient atteignable : sans cela on ne pourrait jamais
   cliquer dedans, elle fuirait devant le curseur. Même geste que le graphique
   du parc, arbitré le 07/09. */
.feuille-bulle.fixee { pointer-events: auto; border-color: var(--text-muted); }
.feuille-bulle b {
  display: block; font-size: var(--taille-mention); letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted); font-weight: 700; margin: 0 0 0.45rem;
}
.feuille-bulle-ligne {
  display: flex; align-items: center; gap: 0.5rem; margin: 0.18rem 0;
  font-size: var(--taille-mention); white-space: nowrap;
}
.feuille-bulle-ligne .nom { flex: 1; }
.feuille-bulle-ligne .val { font-variant-numeric: tabular-nums; font-weight: 600; }
.feuille-bulle-ligne .pct {
  color: var(--text-muted); font-variant-numeric: tabular-nums;
  width: 2.5rem; text-align: right;
}
.feuille-bulle-total {
  display: flex; justify-content: space-between; font-weight: 600;
  font-size: var(--taille-mention);
  border-top: 1px solid var(--border-color); margin-top: 0.5rem; padding-top: 0.45rem;
}
.feuille-bulle-avert {
  margin: 0.45rem 0 0; font-size: var(--taille-mention); white-space: normal;
  color: #8a5a12; background: var(--accent-amber-light);
  border-radius: 6px; padding: 0.35rem 0.45rem;
}

/* Une ligne d'année qui n'est pas pleine, dans le tableau. */
.ligne-annee-creuse td {
  color: #8a5a12;
  background: color-mix(in srgb, var(--accent-amber) 6%, white);
}

/* ⚠️ SUR TÉLÉPHONE, LES ONGLETS DÉFILENT PLUTÔT QUE DE SE REPLIER SUR QUATRE
   LIGNES. Quatre onglets repliés mangeraient la moitié de l'écran avant le
   premier chiffre, et c'est justement le défilement qu'on a voulu éviter en
   choisissant des onglets. */
@media (max-width: 640px) {
  .feuille-onglets {
    flex-wrap: nowrap; overflow-x: auto; scrollbar-width: thin;
  }
  .feuille-onglet { white-space: nowrap; }
  .feuille-bulle { min-width: 10rem; font-size: var(--taille-mention); }
}

/* ===========================================================================
   LE PLANCHER DES CHAMPS DE SAISIE SUR TÉLÉPHONE, ET POURQUOI IL EST ICI

   Sous 16 px, Safari iOS ZOOME la page dès que le doigt entre dans un champ,
   et n'en ressort pas seul : on se retrouve à lire une application décalée,
   sans avoir rien demandé. Ce n'est pas un goût, c'est un comportement du
   navigateur, et il ne se négocie pas.

   ⚠️ CE PLANCHER ÉTAIT DÉFAIT PAR SIX RÈGLES, mesuré le 14/09/2026 en
      simulant la cascade sélecteur par sélecteur. Il vivait au milieu du
      @media du téléphone et ne visait que les ÉLÉMENTS `input` et `select` :

        .commande-echelle select        12 px
        .interventions-filtre select    14 px
        .reglage-mesure select          14 px  (hors barre latérale)
        .barre-activite select          14 px
        .barre-activite input[type=…]   14 px
        .antenne-cible select           14 px

   Deux mécanismes distincts, et c'est ce qui rendait le défaut coriace :

     · UNE MEDIA QUERY N'AJOUTE AUCUNE SPÉCIFICITÉ. À spécificité égale, la
       dernière règle du FICHIER gagne, dans un @media ou hors de lui. C'est
       ce qui rendait 14 px à `.barre-analyses select`, nommé des deux côtés.
     · UNE CLASSE BAT UN ÉLÉMENT, QUEL QUE SOIT L'ORDRE. `.commande-echelle
       select` vaut (0,1,1) contre (0,0,1) pour `select` : déplacer le bloc
       n'y changeait rien, et énumérer les six aurait été une liste à tenir à
       jour à chaque écran neuf.

   D'OÙ LE `!important`, QUI EST ICI À SA PLACE. Il ne rattrape pas un dégât,
   il dit ce qui est vrai : la taille d'un champ sur téléphone n'est pas une
   préférence de mise en page, c'est la condition pour que la page ne saute
   pas sous le doigt. Le fichier s'en sert déjà ainsi pour `.source-absente`.
   `npm run banc` vérifie qu'il est là, et qu'aucune autre règle de taille ne
   passe !important pour le défaire.
   =========================================================================== */
@media (max-width: 48rem) {
  input,
  select,
  textarea,
  .champ { font-size: var(--taille-appuye) !important; }
}

