/*
 * Fondation visuelle — BVM Location.
 *
 * Source des valeurs : design-reference/A-tokens.md — le prototype EN LIGNE, capturé le
 * 26/08/2026 (arbitrage marque de Yanis du 26/08 : #d9532a / #3b4147). La structure des
 * composants reste celle de docs/BVM-reference-visuelle.md.
 *
 * Deux écarts assumés par rapport aux valeurs du prototype :
 *   1. l'échelle typographique part de 15px et non de 13px — décision du 11/08/2026,
 *      réaffirmée le 26/08 (§ « Typographie » ci-dessous) ;
 *   2. les polices sont servies localement, jamais depuis Google Fonts.
 *
 * Décision structurante à ne pas casser (référence §3.1) : AUCUNE ombre sur une surface fixe.
 * Les trois ombres déclarées ci-dessous ne valent que pour des surfaces flottantes — modale,
 * panneau latéral, toast. Cartes, panneaux, tableaux et barres se séparent par une bordure.
 *
 * Les sélecteurs d'élément (h1, table, form, button…) portent la fondation sur les 68 gabarits
 * sans qu'aucun ait à être touché ; les classes ne servent qu'aux motifs qu'un élément HTML ne
 * peut pas désigner seul (pastille d'état, panneau, en-tête de page).
 */

/* ---------------------------------------------------------------------------
 * Polices — servies localement. Aucune requête sortante.
 * Instrument Sans et JetBrains Mono, statiques : un fichier par graisse,
 * copiées de @fontsource (subset latin).
 * `local(...)` d'abord : un poste qui a déjà la police ne télécharge rien.
 * ------------------------------------------------------------------------ */

@font-face {
  font-family: 'Instrument Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src:
    local('Instrument Sans'),
    url('/public/fonts/instrument-sans-latin-400-normal.woff2') format('woff2');
}

@font-face {
  font-family: 'Instrument Sans';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src:
    local('Instrument Sans Medium'),
    url('/public/fonts/instrument-sans-latin-500-normal.woff2') format('woff2');
}

@font-face {
  font-family: 'Instrument Sans';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src:
    local('Instrument Sans SemiBold'),
    url('/public/fonts/instrument-sans-latin-600-normal.woff2') format('woff2');
}

@font-face {
  font-family: 'Instrument Sans';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src:
    local('Instrument Sans Bold'),
    url('/public/fonts/instrument-sans-latin-700-normal.woff2') format('woff2');
}

@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src:
    local('JetBrains Mono'),
    url('/public/fonts/jetbrains-mono-latin-400-normal.woff2') format('woff2');
}

@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src:
    local('JetBrains Mono Medium'),
    url('/public/fonts/jetbrains-mono-latin-500-normal.woff2') format('woff2');
}

@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src:
    local('JetBrains Mono SemiBold'),
    url('/public/fonts/jetbrains-mono-latin-600-normal.woff2') format('woff2');
}

/* ---------------------------------------------------------------------------
 * Les jetons — nommés par rôle, jamais par teinte.
 * ------------------------------------------------------------------------ */

:root {
  /* Texte — référence §1.1 */
  --texte-principal: #12181d;
  --texte-secondaire: #3b4750;
  --texte-attenue: #8d979e;
  --texte-action: #5f6c75; /* libellés de contrôle */
  --texte-accent: #d9532a; /* seul accent du fichier */
  --texte-accent-survol: #b8401d;
  --texte-micro-libelle: #657077; /* capitales d'en-tête de colonne — AA depuis le 29/09/2026 (Q5) : 4,82:1 sur --surface-secondaire, 4,52:1 au survol (--surface-accentuee) ; #8d979e donnait 2,83:1 */
  --texte-inverse: #fff; /* 32 */

  /* Surfaces — référence §1.2 */
  --fond-application: #f5f4f0;
  --surface: #fff; /* 112 */
  --surface-secondaire: #faf9f7; /* ligne d'en-tête de tableau */
  --surface-accentuee: #fdefe9; /* entrée de navigation active */
  --surface-survol-bouton: #faf9f7; /* style-hover */
  --surface-survol-ligne: #faf9f7; /* style-hover */
  --fond-inverse: #3b4147; /* toast */
  --voile-modale: rgba(20, 28, 36, 0.38); /* 4 */

  /* Bordures — référence §1.4 : quatre niveaux, c'est ce qui porte la profondeur */
  --bordure-surface: #e4e1d9; /* carte, panneau, barre */
  --bordure-saisie: #e4e1d9; /* champ de saisie */
  --bordure-bouton: #e4e1d9; /* bouton secondaire */
  --separateur-interne: #efece5; /* lignes d'un même panneau */
  --anneau-focus: #d9532a; /* accent — visibilité (constat G-M4) */

  /* Sémantiques — référence §1.3. Fond très clair + texte foncé, jamais d'aplat saturé. */
  --danger-texte: #b3261e;
  --danger-texte-fort: #8f261e; /* 15 */
  --danger-fond: #fbeceb;
  --danger-fond-ligne: #fdf3f2; /* 6 — ligne de tableau en anomalie */
  --danger-bordure: #eec5c1;

  --succes-texte: #1f7a4d;
  --succes-texte-fort: #1f7a4d;
  --succes-fond: #e9f4ed;
  --succes-bordure: #bcdcc8;

  --attention-texte: #986412; /* AA depuis le 29/09/2026 (Q5) : 4,57:1 sur --attention-fond ; #a86f14 donnait 3,85:1 */
  --attention-texte-fort: #6b5312; /* 8 */
  --attention-fond: #fcf3e2;
  --attention-bordure: #ecd9ae; /* 14 */
  --attention-accent: #d99a2b; /* pastille pleine, cercle « ! » */

  --information-texte: #2a5d8f;
  --information-fond: #e9f0f7;
  --information-bordure: #cdd9e2;

  --inspection-texte: #6b4fa8;
  --inspection-fond: #f0ecf9;

  --maintenance-texte: #b3261e; /* = danger */
  --maintenance-fond: #fbeceb;

  /* Référence §2.1 (entrée de navigation sans écran cible) et §2.13 (segment inactif).
     Mesuré à deux endroits du prototype, absent de cette feuille jusqu'au 24/08/2026. */
  --texte-inactif: #a6afb8;
  /* Menu foncé — décision de Yanis du 25/08/2026, teintes du prototype en ligne (26/08).
     Les contrastes sont MESURÉS en composant les rgba sur le fond, pas choisis à l'œil —
     menu-fonce.test.ts refait exactement ce calcul :
       texte au repos  rgba(255,255,255,0.78) sur #3b4147 composé #d4d5d7 → 7,03:1 (AAA)
       texte actif     #ffffff sur fond actif composé #494e54           → 8,40:1 (AAA)
       titre de groupe rgba(255,255,255,0.6)  sur #3b4147 composé #b1b3b5 → 4,91:1 (AA)
     `--menu-texte` : le prototype sert 0.65, relevé à 0.78 — 0.65 compose à 5,47:1,
     sous le seuil AAA 7:1 gardé par menu-fonce.test.ts. */
  --menu-fond: #3b4147;
  --menu-texte: rgba(255, 255, 255, 0.78);
  --menu-texte-actif: #fff;
  --menu-fond-actif: rgba(255, 255, 255, 0.07);
  --menu-bordure: rgba(255, 255, 255, 0.1);
  /* Titre de groupe du menu — 4,91:1 sur #3b4147 (AA) ; le jeton partagé
     `--texte-micro-libelle` tombait à 3,47:1 sur ce fond. */
  --menu-titre-groupe: rgba(255, 255, 255, 0.6);
  --neutre-texte: #5c6875;
  --neutre-fond: #eef1f4; /* 118 */
  --terminal-texte: #98a2ad; /* état clos, accompagné du barré */
  --terminal-fond: #eef1f4;

  /* Typographie — référence §1.5, échelle remontée de 13px à 15px de base (× 1,154).
     Les rapports du prototype sont conservés ; c'est un outil utilisé huit heures par jour,
     et la densité se tient par les espacements, pas en rapetissant le texte. */
  --police-texte:
    'Instrument Sans', system-ui, -apple-system, 'Segoe UI', roboto, helvetica, arial, sans-serif;
  --police-chiffres:
    'JetBrains Mono', ui-monospace, 'Cascadia Mono', 'Consolas', 'Liberation Mono', monospace;

  --taille-titre-page: 22px; /* prototype 19px, 23 occurrences */
  --taille-titre-section: 17px; /* non mesuré — invention assumée, voir la spec */
  --taille-chiffre-synthese: 20px; /* prototype 17px */
  --taille-base: 15px; /* prototype 13px */
  --taille-controle: 14.5px; /* prototype 12.5px, 103 occurrences */
  --taille-cellule: 14px; /* prototype 12px, 103 occurrences */
  --taille-note: 13px; /* prototype 11.5px, 69 occurrences */
  --taille-pastille: 12.5px; /* prototype 11px, 52 occurrences */
  --taille-entete-colonne: 12px; /* prototype 10.5px, 46 occurrences */
  --taille-micro: 11.5px; /* prototype 10px, 14 occurrences */

  --hauteur-ligne: 1.45; /* feuille globale du prototype */
  --graisse-normale: 400;
  --graisse-appuyee: 500; /* 45 */
  --graisse-forte: 600; /* 211 — graisse dominante */
  --graisse-chiffre: 700; /* 23, toujours en Mono */
  --interlettrage-capitales: 0.06em; /* 23 occurrences sur 40 ; jamais de capitales sans */

  /* Espacement — référence §1.6, granularité de 2px entre 4 et 16, laissé à l'échelle
     mesurée : c'est lui qui porte la densité, il ne suit pas la remontée typographique. */
  --espace-1: 4px;
  --espace-2: 6px;
  --espace-3: 8px; /* gap dominant, 54 occurrences */
  --espace-4: 10px;
  --espace-5: 12px;
  --espace-6: 14px; /* rythme vertical dominant, 34 occurrences */
  --espace-7: 16px; /* axe horizontal des surfaces, quasi invariant */
  --espace-8: 20px;
  --espace-9: 24px;

  /* Rayons — référence §1.7 */
  --rayon-controle: 3px; /* boutons, champs */
  --rayon-surface: 4px; /* cartes, panneaux */
  --rayon-etiquette: 2px;
  --rayon-pastille: 3px; /* pastilles d'état et modales */
  --rayon-cercle: 50%;

  /* Hauteurs de contrôle — référence §1.8 */
  --hauteur-controle: 32px; /* 34 — hauteur de référence */
  --hauteur-controle-compact: 26px;
  /* Cible tactile — hors du prototype, qui ne vise que le poste de bureau (§4.4).
     44px est le plancher de la règle 2.5.5 des WCAG, retenu par Yanis le 25/08/2026. */
  --hauteur-cible-tactile: 44px;

  /* Mise en page — référence §1.6 */
  --largeur-barre-laterale: 208px;

  /* Ombres — référence §1.9 : six dans tout le prototype, toutes sur du flottant. */
  --ombre-modale: 0 16px 48px rgba(20, 28, 36, 0.24);
  --ombre-panneau-lateral: -8px 0 24px rgba(20, 28, 36, 0.12);
  --ombre-toast: 0 8px 24px rgba(20, 28, 36, 0.3);
}

/* ---------------------------------------------------------------------------
 * Socle
 * ------------------------------------------------------------------------ */

*,
*::before,
*::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--fond-application);
  color: var(--texte-principal);
  font-family: var(--police-texte);
  font-size: var(--taille-base);
  line-height: var(--hauteur-ligne);
}

a {
  color: var(--texte-accent);
  text-decoration: none;
}

a:hover {
  color: var(--texte-accent-survol);
  text-decoration: underline;
}

:focus-visible {
  outline: 2px solid var(--anneau-focus);
  outline-offset: 0;
}

[x-cloak] {
  display: none;
}

/* ---------------------------------------------------------------------------
 * Coque d'application : barre latérale fixe + zone de contenu.
 *
 * La barre supérieure du prototype (§2.2) n'est pas posée : ses trois contenus —
 * recherche globale, alertes, identité de l'utilisateur — n'existent dans aucune route
 * livrée, et les inventer serait inventer une fonctionnalité.
 * ------------------------------------------------------------------------ */

.appli {
  display: grid;
  grid-template-columns: var(--largeur-barre-laterale) minmax(0, 1fr);
  /* Rangée du haut en `auto` et non en 50px : sur une page publique, la barre supérieure
     n'est pas rendue du tout, et une rangée vide de 50px flotterait au-dessus de l'écran de
     connexion. En `auto`, l'absence d'élément vaut zéro. Spec du 24/08, arbitrage 3. */
  grid-template-rows: auto minmax(0, 1fr);
  min-height: 100vh;
}

.appli--sans-menu {
  grid-template-columns: minmax(0, 1fr);
}

.appli--sans-menu .barre-laterale {
  display: none;
}

/* Sans menu, il n'y a qu'une colonne : le contenu y retombe. */
.appli--sans-menu .contenu {
  grid-column: 1;
}

/* Collée à la fenêtre (recette du 23/09/2026, r04 : « qu'elle descende avec nous ») : haute d'un
   écran, calée en haut de sa cellule de grille et `sticky`, elle reste visible quand la page
   défile ; plus haute que l'écran, elle défile seule (référence §2.1, « défilement propre »).
   Sous 640px, le repli du 21/09 la remet dans le flux (voir la passe petits écrans). */
.barre-laterale {
  grid-row: 1 / 3;
  position: sticky;
  top: 0;
  align-self: start;
  height: 100vh;
  background: var(--menu-fond);
  border-right: 1px solid var(--menu-bordure);
  padding: var(--espace-6) 0;
  overflow-y: auto;
}

.marque {
  margin: 0 0 var(--espace-6);
  padding: 0 var(--espace-5);
  font-size: var(--taille-controle);
  font-weight: var(--graisse-forte);
  color: var(--menu-texte-actif);
  letter-spacing: 0.01em;
}

/* Bouton « Menu » (21/09/2026) : il n'existe qu'au-dessous de 640px, où il replie le menu.
   Au-dessus, `display: none` le retire aussi de l'arbre d'accessibilité. */
.menu-bascule {
  display: none;
  align-items: center;
  gap: var(--espace-2);
  height: var(--hauteur-controle);
  padding: 0 var(--espace-4);
  border: 1px solid var(--menu-bordure);
  border-radius: var(--rayon-controle);
  background: transparent;
  color: var(--menu-texte-actif);
  font-size: var(--taille-controle);
  font-weight: var(--graisse-forte);
  cursor: pointer;
}

.menu-bascule:hover {
  background: var(--menu-fond-actif);
}

/* Tiroir du menu (décision de Yanis du 28/09/2026, spec 2026-09-28-menu-tiroir-telephone.md).
   Au poste, `.menu-tiroir` n'est que la colonne du menu : son voile (celui du journal de la fiche
   client, réemployé) et sa tête à croix n'existent qu'au téléphone. Masqués ici par la feuille et
   non par Alpine : sans script, le voile ne peut rien intercepter. */
.barre-laterale .tiroir-voile {
  display: none;
}

.menu-tiroir .tiroir-tete {
  display: none;
}

/* La croix : même dessin que le bouton « Menu », carrée. */
.menu-fermer {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--hauteur-controle);
  height: var(--hauteur-controle);
  padding: 0;
  border: 1px solid var(--menu-bordure);
  border-radius: var(--rayon-controle);
  background: transparent;
  color: var(--menu-texte-actif);
  cursor: pointer;
}

.menu-fermer:hover {
  background: var(--menu-fond-actif);
}

/* `<nav>` reste sans attribut : trois suites de tests l'extraient par `/<nav>(.*?)<\/nav>/`,
   et les entrées par `/<a href="[^"]*">([^<]*)<\/a>/`. Aucune classe ne peut y être posée. */
.barre-laterale nav {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 0 var(--espace-5);
}

.barre-laterale nav a {
  padding: var(--espace-2) var(--espace-5);
  border-left: 2px solid transparent; /* réserve du filet — l'entrée active ne décale rien */
  border-radius: var(--rayon-controle);
  font-size: var(--taille-controle);
  color: var(--menu-texte);
}

/* Entrée active : le marqueur `.nav-actif` est un FRÈRE posé par main.eta devant le lien
   actif — les regex de 4 suites interdisent tout attribut sur les `<a>` du menu. */
.barre-laterale nav .nav-actif + a {
  background: var(--menu-fond-actif);
  color: var(--menu-texte-actif);
  border-left-color: var(--texte-accent);
}

.barre-laterale nav a:hover {
  background: var(--menu-fond-actif);
  color: var(--menu-texte-actif);
  text-decoration: none;
}

.contenu {
  grid-column: 2;
  padding: var(--espace-8) var(--espace-9) 40px;
  min-width: 0;
}

/* ---------------------------------------------------------------------------
 * Barre supérieure — référence §2.2. Hauteur 50px, surface blanche, bordure basse.
 *
 * DEUX contenus sur trois : la recherche globale et l'identité. La cloche d'alertes du
 * prototype n'est pas posée — décider ce qu'elle compte serait inventer une fonctionnalité
 * (décision de Yanis, 24/08/2026). Spec : docs/specs/2026-08-24-barre-superieure.md.
 * ------------------------------------------------------------------------ */

.barre-superieure {
  grid-column: 2;
  display: flex;
  align-items: center;
  gap: var(--espace-7);
  height: 50px;
  padding: 0 var(--espace-8);
  background: var(--surface);
  border-bottom: 1px solid var(--bordure-surface);
}

.recherche-globale {
  flex: 1;
  max-width: 460px;
}

/* Le ressort de la barre : il EXISTE dans le gabarit depuis la fondation v2, mais aucune
   règle ne le faisait pousser — l'identité se posait donc juste après la recherche, au
   milieu de la barre, et le vide restait à droite. Il pousse maintenant (Yanis, 27/08/2026 :
   « aligne le nom de la personne à droite »). Réglé ICI et non sur `.ressort` en général :
   `.entete-page .ressort` a déjà le sien, et une règle globale changerait les écrans
   d'un coup. */
.barre-superieure .ressort {
  flex: 1;
}

/* Le champ de recherche n'est pas un champ de formulaire de page : il ne porte ni libellé
   au-dessus, ni marge de bloc, et son fond le distingue de la barre qui le porte. */
.recherche-globale input {
  width: 100%;
  max-width: none;
  margin: 0;
  background: var(--surface-secondaire);
  font-size: var(--taille-base);
}

.identite {
  display: flex;
  align-items: center;
  gap: var(--espace-3);
}

.identite-pastille {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  flex: none;
  border-radius: var(--rayon-cercle);
  background: var(--texte-accent);
  color: var(--texte-inverse);
  font-size: var(--taille-note);
  font-weight: var(--graisse-forte);
}

/* Nom et rôle calés à DROITE (décision de Yanis, 27/08/2026) : le rôle, plus court,
   s'alignait sous le début du nom et laissait un bord droit en dents de scie. */
.identite > span:last-child {
  text-align: right;
}

.identite-nom {
  display: block;
  font-size: var(--taille-cellule);
  font-weight: var(--graisse-forte);
  line-height: 1.2;
}

.identite-role {
  display: block;
  font-size: var(--taille-micro);
  color: var(--texte-attenue);
  line-height: 1.2;
}

.deconnexion {
  margin: 0;
}

/* Bouton-icône carré : hauteur héritée de la règle `button` générique
   (32px bureau, 44px tactile), largeur alignée dessus. */
.deconnexion button {
  width: var(--hauteur-controle);
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* ---------------------------------------------------------------------------
 * Titres et en-tête de page — référence §2.3
 * ------------------------------------------------------------------------ */

h1 {
  margin: 0 0 var(--espace-6);
  font-size: var(--taille-titre-page);
  font-weight: var(--graisse-forte);
  line-height: 1.25;
}

/* Le prototype ne montre aucun titre de section (23 `<h1>`, zéro `<h2>`). Les tailles de
   `h2` et `h3` sont donc dérivées et non mesurées — invention assumée, voir la spec. */
h2 {
  margin: var(--espace-8) 0 var(--espace-3);
  font-size: var(--taille-titre-section);
  font-weight: var(--graisse-forte);
}

h3 {
  margin: var(--espace-6) 0 var(--espace-3);
  font-size: var(--taille-base);
  font-weight: var(--graisse-forte);
}

/* En-tête de page : titre, pastilles, mention, ressort, puis les actions — l'action
   primaire toujours en dernier (référence §3.7). */
.entete-page {
  display: flex;
  align-items: center;
  gap: var(--espace-5);
  margin-bottom: var(--espace-6);
  flex-wrap: wrap;
}

.entete-page h1 {
  margin: 0;
}

.entete-page .ressort {
  flex: 1;
}

/* Recette de Yanis du 21/09/2026 : les boutons d'une fiche partageaient la ligne du titre et du
   client, et passaient à la ligne un par un dès que le nom était long. La mention qui suit le titre
   (le client d'une facture, d'un contrat) prend la place qui reste et se raccourcit, nom complet au
   survol ; les gestes gardent leur largeur et restent groupés à droite. */
.entete-page > h1 + .mention {
  flex: 1 1 0;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* L'hôte de dialog est en `display: contents` : c'est son bouton qui est l'élément de la rangée. */
.entete-page > .dialogue-hote > button,
.entete-page > form,
.entete-page > .bouton,
.entete-page > button {
  flex: none;
}

/* La mention remplit déjà la ligne : le ressort qui la suit n'a plus rien à pousser. */
.entete-page > h1 + .mention + .ressort {
  display: none;
}

/* Quand les gestes ne tiennent plus sur la ligne du titre (cinq gestes d'un devis signé à
   1280 px), celui qui passe à la ligne reste à droite, sous les autres, au lieu de tomber seul
   sous le titre. Tant que la rangée tient, le ressort ou la mention ont déjà pris la place
   libre et la marge automatique vaut zéro (revue du bureau du 29/09/2026, arbitrage 4). */
.entete-page > .dialogue-hote:last-child > button,
.entete-page > form:last-child,
.entete-page > .bouton:last-child,
.entete-page > button:last-child {
  margin-left: auto;
}

.mention {
  color: var(--texte-attenue);
  font-size: var(--taille-note);
}

/* Micro-libellé en capitales : jamais de capitales sans interlettrage (référence §1.5). */
.micro-libelle {
  font-size: var(--taille-micro);
  font-weight: var(--graisse-forte);
  text-transform: uppercase;
  letter-spacing: var(--interlettrage-capitales);
  color: var(--texte-micro-libelle);
}

/* ---------------------------------------------------------------------------
 * Chiffres et identifiants — référence §3.2 : tout ce qui est un chiffre ou un
 * identifiant est en Mono. C'est la raison d'être de la police secondaire.
 * ------------------------------------------------------------------------ */

time,
.identifiant,
.chiffres {
  font-family: var(--police-chiffres);
}

.identifiant {
  color: var(--texte-accent);
  font-weight: var(--graisse-appuyee);
  /* Un code ne se coupe pas à son tiret (« BVM4S004- / 01 » au téléphone, Logistique,
     finition du 28/09/2026) : c'est la colonne qui s'élargit, le tableau défile déjà. */
  white-space: nowrap;
}

td.chiffres,
th.chiffres {
  text-align: right;
}

/* L'en-tête d'une colonne chiffrée est un micro-libellé, pas un chiffre : il garde la police de
   la rangée d'en-tête (§2.6 — le prototype n'a aucune capitale en Mono ; revue du bureau du
   29/09/2026, arbitrage 1). Seul l'alignement à droite suit la colonne. */
th.chiffres {
  font-family: var(--police-texte);
}

/* Une valeur absente dite en mots (« jamais », « aucun ») là où l'on attend un chiffre — cellule
   `.chiffres` ou valeur d'un bloc d'indicateurs : c'est un mot, pas un chiffre — il reprend la
   police du texte (§3.2 ; finition du 28/09/2026, étendue au bureau le 29/09/2026). */
.valeur-en-mots {
  font-family: var(--police-texte);
}

.chiffre-synthese {
  font-family: var(--police-chiffres);
  font-size: var(--taille-chiffre-synthese);
  font-weight: var(--graisse-chiffre);
}

/* ---------------------------------------------------------------------------
 * Panneau et carte — référence §2.5. Un seul motif de surface, deux usages,
 * et aucune ombre : la bordure est le seul séparateur.
 * ------------------------------------------------------------------------ */

.panneau {
  background: var(--surface);
  border: 1px solid var(--bordure-surface);
  border-radius: var(--rayon-surface);
  overflow: hidden;
}

.carte {
  background: var(--surface);
  border: 1px solid var(--bordure-surface);
  border-radius: var(--rayon-surface);
  padding: var(--espace-6) var(--espace-7);
}

/* Rangée de cartes — `.grille-cartes` était posée par deux gabarits et stylée NULLE PART.
   Sans règle, la classe valait `display: block` : les quatre chiffres du tableau de bord
   se rendaient en bandes pleine largeur qui se chevauchaient, à 1280px comme à 375px.
   `auto-fit` et non un nombre fixe : c'est la LARGEUR DISPONIBLE qui décide du nombre de
   colonnes, pas le gabarit. Quatre cartes tiennent sur une ligne dans la colonne de
   contenu du bureau, trois pour le bilan, et elles s'empilent d'elles-mêmes quand la
   place manque — sans qu'une seule `@media` ait à le dire. */
.grille-cartes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--espace-6);
  margin-bottom: var(--espace-6);
}

.panneau-entete {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--espace-3);
  padding: var(--espace-4) var(--espace-7);
  border-bottom: 1px solid var(--separateur-interne);
  font-weight: var(--graisse-forte);
}

.panneau-corps {
  padding: var(--espace-6) var(--espace-7);
}

/* Titre de panneau centré : sans la marge du h2 global (spec 2026-09-14-logistique-blocs, arbitrage 2). */
.panneau-entete h2 {
  margin: 0;
}

/* Un tableau dans un panneau n'a pas sa propre surface : le panneau la porte déjà. Sans
   cette reprise, on voit deux bordures et deux arrondis emboîtés. */
.panneau table {
  border: none;
  border-radius: 0;
}

/* ---------------------------------------------------------------------------
 * Fiche à deux colonnes — le corps principal, et une colonne latérale de 400px pour ce
 * qui accompagne sans être l'objet de la page. En dessous du seuil des tableaux, les deux
 * colonnes se remettent l'une sous l'autre : 400px de côté sur un écran de 900 n'en
 * laisseraient que 500 à l'essentiel.
 * ------------------------------------------------------------------------ */
.deux-colonnes {
  display: grid;
  /* 380px et non 400 : valeur des maquettes de la refonte (README, motif 4). */
  grid-template-columns: minmax(0, 1fr) 380px;
  gap: var(--espace-6);
  align-items: start;
}

/* Logistique en quatre colonnes (spec 2026-09-28-logistique-kanban, arbitrages 1 et 4) : une
   colonne par état d'intervention, à parts égales. Elles s'empilent au seuil des tableaux
   (`@media` 900px). */
.tableau-logistique {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--espace-6);
  align-items: start;
}

.pile-colonne {
  display: flex;
  flex-direction: column;
  gap: var(--espace-6);
  min-width: 0;
}

/* Le rythme vertical d'une surface est porté par son padding : le premier et le dernier
   enfant ne rajoutent pas leur propre marge par-dessus. */
.carte > :first-child,
.panneau-corps > :first-child {
  margin-top: 0;
}

.carte > :last-child,
.panneau-corps > :last-child {
  margin-bottom: 0;
}

/* Les fiches livrées empilent des `<section>` : elles respirent entre elles, pas dedans. */
.contenu > section,
.contenu > .carte,
.contenu > .panneau,
.contenu > form,
.contenu > table,
.contenu > .tableau-logistique {
  margin-bottom: var(--espace-6);
}

/* Barre de filtres d'une liste : une rangée de champs, alignés sur leur base. */
.filtres {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--espace-5) var(--espace-6);
}

.filtres label {
  margin-bottom: 0;
}

/* Des formulaires posent le libellé en FRÈRE de son champ — `<label for>` + `id` — parce
   qu'un `hx-target` vise le champ par son identifiant et qu'envelopper le champ dans son
   libellé le ferait disparaître. Le couple est alors réuni dans un `.champ`, qui redevient
   UN élément de la rangée : sans lui, le libellé et son champ dérivent chacun de leur côté.

   La règle n'est PAS préfixée par `.filtres` (07/09/2026) : `.champ` est un composant, pas un
   modificateur de contexte. Elle ne l'était qu'à moitié, et 8 de ses 14 sites — sous `.chips`
   ou hors des deux — ne recevaient rien. Mesuré à 1440 px dans Chromium : le MÊME balisage
   rendait `flex`/colonne/gap 4px/écart 8px sous `.filtres`, et `block`/écart 10px ailleurs. */
.champ {
  display: flex;
  flex-direction: column;
  gap: var(--espace-1);
}

/* C'est le `gap` qui espace le couple, plus la marge du libellé : sans cette ligne, les deux
   s'additionnent et le champ s'écarte au lieu de se rapprocher. `.filtres label` le faisait
   déjà pour la barre de filtres ; `.champ` doit se suffire à lui-même partout ailleurs.
   Mesuré à 1440 px : 14px sous `.chips` et 18px hors contexte, contre les 8px de `.filtres`. */
.champ > label {
  margin-bottom: 0;
}

/* Un groupe de cases est trop haut pour tenir dans la rangée sans y creuser un vide : huit
   cases empilées font 200px, et `align-items: flex-end` colle alors tout le reste en bas.
   Il prend donc sa propre ligne, où ses cases s'alignent horizontalement.
   Le `<legend>` reste hors du flux flex — c'est le navigateur qui le place. */
.filtres fieldset {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--espace-2) var(--espace-6);
  flex-basis: 100%;
  margin-bottom: 0;
}

.filtres fieldset label {
  display: flex;
  align-items: center;
  gap: var(--espace-2);
}

/* Une case à cocher se lit À CÔTÉ de son libellé, jamais dessous : `row-reverse` la ramène
   devant lui sans toucher à l'ordre du DOM, où le `<label for>` précède son champ. */
.champ:has(> input[type='checkbox']) {
  flex-direction: row-reverse;
  align-items: center;
  justify-content: flex-end;
  gap: var(--espace-3);
}

/* ---------------------------------------------------------------------------
 * Tableau de liste — référence §2.6. Les gabarits livrés rendent du HTML
 * sémantique (`table`/`thead`/`th`) là où le prototype empilait des `div` en grille :
 * seul le système visuel est repris, jamais la structure du DOM.
 * ------------------------------------------------------------------------ */

table {
  width: 100%;
  border-collapse: collapse;
  background: var(--surface);
  border: 1px solid var(--bordure-surface);
  border-radius: var(--rayon-surface);
  overflow: hidden;
  font-size: var(--taille-cellule);
}

thead th {
  background: var(--surface-secondaire);
  border-bottom: 1px solid var(--separateur-interne);
  padding: var(--espace-3) var(--espace-7);
  text-align: left;
  font-size: var(--taille-entete-colonne);
  font-weight: var(--graisse-forte);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--texte-micro-libelle);
  white-space: nowrap;
}

tbody td {
  padding: 9px var(--espace-7);
  border-bottom: 1px solid var(--separateur-interne);
  vertical-align: middle;
}

tbody tr:last-child td {
  border-bottom: none;
}

/* Référence §3.5 : le survol change le fond, jamais la bordure ni la taille — c'est la
   condition pour qu'un tableau dense reste lisible pendant qu'on le parcourt. */
tbody tr:hover {
  background: var(--surface-survol-ligne);
}

/* Ligne en anomalie (§2.6) et ligne d'outils déjà utilisée par les gabarits de lignes. */
tr.anomalie {
  background: var(--danger-fond-ligne);
}

caption {
  caption-side: bottom;
  padding-top: var(--espace-3);
  text-align: left;
  font-size: var(--taille-note);
  color: var(--texte-attenue);
}

/* Mention de comptage sous le panneau (§2.6). */
.comptage {
  margin-top: var(--espace-3);
  font-size: var(--taille-note);
  color: var(--texte-attenue);
}

/* ---------------------------------------------------------------------------
 * Formulaire — référence §2.8
 * ------------------------------------------------------------------------ */

fieldset {
  margin: 0 0 var(--espace-6);
  padding: var(--espace-6) var(--espace-7);
  background: var(--surface);
  border: 1px solid var(--bordure-surface);
  border-radius: var(--rayon-surface);
}

legend {
  padding: 0 var(--espace-2);
  font-size: var(--taille-entete-colonne);
  font-weight: var(--graisse-forte);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--texte-micro-libelle);
}

/* Les gabarits enveloppent leur champ dans le `<label>` : `<label>Ville <input …></label>`.
   Le libellé se pose donc au-dessus par le bloc, et non à côté comme dans le prototype. */
label {
  display: block;
  margin-bottom: var(--espace-4);
  font-size: var(--taille-cellule);
  color: var(--texte-secondaire);
}

label input[type='checkbox'],
label input[type='radio'] {
  margin-right: var(--espace-3);
  vertical-align: baseline;
}

input,
select,
textarea {
  display: block;
  margin-top: var(--espace-1);
  width: 100%;
  max-width: 460px;
  height: var(--hauteur-controle);
  padding: 0 var(--espace-3);
  background: var(--surface);
  border: 1px solid var(--bordure-saisie);
  border-radius: var(--rayon-controle);
  color: var(--texte-principal);
  font-family: inherit;
  font-size: var(--taille-controle);
}

input[type='checkbox'],
input[type='radio'] {
  display: inline-block;
  width: auto;
  height: auto;
  margin-top: 0;
}

/* Référence §3.2 : les champs qui portent un chiffre ou une date sont en Mono. */
input[type='date'],
input[type='number'],
input[inputmode='decimal'] {
  font-family: var(--police-chiffres);
  max-width: 200px;
}

textarea {
  height: auto;
  min-width: 100%;
  min-height: 64px;
  max-width: 100%;
  padding: var(--espace-3) var(--espace-4);
  line-height: var(--hauteur-ligne);
  resize: vertical;
}

select {
  padding-right: var(--espace-2);
}

/* Libellé posé en FRÈRE de son champ (`<label for>` + champ) dans un dialog : la marge du
   libellé l'éloignait de son champ (14px) plus que du champ précédent (0) — « Commentaire »
   collait au champ « Motif » du dialog d'échec. Le libellé se rapproche de SON champ, et le
   champ reprend l'écart avant le suivant (finition du 28/09/2026). */
dialog label[for] {
  margin-bottom: 0;
}

dialog label[for] + :is(input, select, textarea) {
  margin-bottom: var(--espace-6);
}

/* ---------------------------------------------------------------------------
 * Boutons — référence §2.4. Le bouton nu est le secondaire ; le primaire se
 * demande. Un seul primaire par écran, en dernier (§3.7).
 * ------------------------------------------------------------------------ */

/* `nowrap` (21/09/2026, recette de Yanis) : la hauteur est fixe, un libellé coupé en deux lignes
   débordait du bouton (« Intégrer au contrat… » dans une cellule de 115 px). Le libellé tient sur
   une ligne ; c'est la colonne ou la rangée qui s'élargit. */
button,
.bouton {
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
  height: var(--hauteur-controle);
  padding: 0 var(--espace-5);
  background: var(--surface);
  border: 1px solid var(--bordure-bouton);
  border-radius: var(--rayon-controle);
  color: var(--texte-action);
  font-family: inherit;
  font-size: var(--taille-controle);
  cursor: pointer;
}

button:hover,
.bouton:hover {
  background: var(--surface-survol-bouton);
  text-decoration: none;
}

button.primaire,
.bouton.primaire {
  padding: 0 var(--espace-6);
  background: var(--texte-accent);
  border: none;
  color: var(--texte-inverse);
  font-weight: var(--graisse-forte);
}

button.primaire:hover,
.bouton.primaire:hover {
  background: var(--texte-accent-survol);
}

button.compact,
.bouton.compact {
  height: var(--hauteur-controle-compact);
  padding: 0 var(--espace-3);
  font-size: var(--taille-entete-colonne);
  font-weight: var(--graisse-forte);
}

/* Le prototype ne montre aucun bouton désactivé (§4.4) — invention assumée, voir la spec. */
button:disabled,
.bouton[aria-disabled='true'] {
  color: var(--texte-attenue);
  border-color: var(--bordure-saisie);
  background: var(--surface-secondaire);
  cursor: default;
}

/* Barre d'actions d'une page ou d'un panneau. */
.actions {
  display: flex;
  align-items: center;
  gap: var(--espace-3);
  flex-wrap: wrap;
}

/* ---------------------------------------------------------------------------
 * Pastille d'état — référence §2.7 et §1.3, codage du brief §4.
 *
 * `.badge` est déjà posée par 66 sites des gabarits livrés : elle EST la pastille.
 * Le fond neutre est le défaut — c'est aussi le codage « brouillon » du brief.
 * Les classes `.etat-*` portent le nom EXACT de la valeur d'énumération de
 * `src/db/schema.ts`, ce qui permet d'écrire `class="badge etat-<%= u.etat %>"`.
 * ------------------------------------------------------------------------ */

.badge {
  display: inline-block;
  padding: 2px 9px;
  border-radius: var(--rayon-pastille);
  background: var(--neutre-fond);
  color: var(--neutre-texte);
  font-size: var(--taille-pastille);
  font-weight: var(--graisse-forte);
  white-space: nowrap;
  vertical-align: baseline;
}

/* Unités — `etat_unite`, la liste du brief moins « en transit » : les deux états de transit
   ont été supprimés de l'énumération le 07/08/2026 (schema.ts), le bleu est donc porté par
   les documents « émis / en cours » et par aucun état d'unité. */
.etat-disponible {
  background: var(--succes-fond);
  color: var(--succes-texte-fort);
}

/* `etat_prospect` (RG-CRM-09) : ses cinq statuts n'avaient AUCUNE règle avant le bloc du
   11/08/2026 (PRO-04, arbitrage 11). Chacun rejoint un groupe sémantique déjà posé, sans jeton
   nouveau — `jamais_contacte` excepté, qui reste sur le fond neutre de `.badge` : c'est le bon
   codage pour « rien n'a encore eu lieu », et son absence de règle est délibérée. */
.etat-chez_client,
.etat-pas_de_reponse {
  background: var(--attention-fond);
  color: var(--attention-texte);
}

.etat-attente_inspection {
  background: var(--inspection-fond);
  color: var(--inspection-texte);
}

.etat-maintenance {
  background: var(--maintenance-fond);
  color: var(--maintenance-texte);
}

/* Pastille inversée — arbitrage Yanis 26/08/2026 : ne partage plus les jetons danger
   avec « en maintenance », qui y passe, elle. */
.etat-hors_service {
  background: var(--fond-inverse);
  color: var(--texte-inverse);
}

/* Référence §3.6 : un état terminal se barre — c'est le second signal, distinct de
   l'opacité réduite qui, elle, dit « inactif ». */
.etat-sortie_parc,
.etat-refuse,
.etat-annule,
.etat-abandonne,
.etat-ecarte,
.etat-expire {
  background: var(--terminal-fond);
  color: var(--terminal-texte);
  text-decoration: line-through;
}

/* Documents — « émis / en cours » en bleu. (`cloture_proposee` et `partiellement_payee`,
   logées ici faute de couleur propre au brief §4, ont été retirées de leurs énumérés le
   22/09/2026 et le 20/08/2026 — docs/specs/2026-08-11-style-boucle-coeur.md, arbitrage 1.)
   `en_cours` est, lui, l'état d'un CONTRÔLE commencé (`etat_controle`) : un travail engagé et
   non figé, ce que cette même famille code déjà pour `ouvert`, `emis` et `en_transformation`
   (docs/specs/2026-08-11-checklists-et-controle-de-sortie.md, arbitrage 11). Son second état,
   `valide`, est déjà servi par la famille verte ci-dessous. */
.etat-emis,
.etat-emise,
.etat-transmise,
.etat-en_transformation,
.etat-echange_en_cours,
.etat-en_cours,
.etat-ouvert {
  background: var(--information-fond);
  color: var(--information-texte);
}

/* Documents — « signé / validé » en vert. */
.etat-signe,
.etat-payee,
.etat-solde,
.etat-cloture,
.etat-converti,
.etat-valide {
  background: var(--succes-fond);
  color: var(--succes-texte-fort);
}

/* Documents — « en retard / échu » en rouge. La facture dont la copie courriel n'est pas partie
   prend la même teinte (recette r01 du 23/09/2026) : c'est un geste à faire, pas une information. */
.etat-echu,
.etat-echue,
.etat-en_retard,
.etat-non-envoyee {
  background: var(--danger-fond);
  color: var(--danger-texte);
}

/* Variante étiquette (§2.7) : plus petite et moins arrondie que la pastille d'état,
   distinction délibérée du prototype. */
.etiquette {
  display: inline-block;
  padding: 1px var(--espace-2);
  border-radius: var(--rayon-etiquette);
  background: var(--neutre-fond);
  color: var(--neutre-texte);
  font-size: var(--taille-micro);
  font-weight: var(--graisse-forte);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* Variante « forcé » (§2.7) : le seul badge du prototype qui porte une bordure. */
.force {
  display: inline-block;
  padding: 1px 5px;
  border: 1px solid var(--information-bordure);
  border-radius: var(--rayon-etiquette);
  background: var(--surface-accentuee);
  color: var(--texte-accent);
  font-size: var(--taille-micro);
  font-weight: var(--graisse-forte);
}

/* ---------------------------------------------------------------------------
 * Bandeaux — référence §2.9. Les classes existantes des gabarits livrés
 * (`avertissement`, `alerte`, `signal`, `bandeau`, `erreur`, `error`) reçoivent ici
 * leur style ; aucune n'est renommée.
 * ------------------------------------------------------------------------ */

.avertissement,
.bandeau,
.erreur,
.error {
  /* `position: relative` et non `display: flex` : le bandeau de mode dégradé de
     `layouts/main.eta` mêle du texte et un lien. En flex, chaque nœud deviendrait un élément
     de flux et la phrase éclaterait en colonnes. Le pictogramme se pose donc en absolu, et le
     texte garde son flux. Spec du 24/08/2026, arbitrage 9. */
  position: relative;
  display: block;
  margin: 0 0 var(--espace-6);
  padding: var(--espace-4) var(--espace-6);
  padding-left: calc(var(--espace-6) + 17px + var(--espace-4));
  border-radius: var(--rayon-surface);
  font-size: var(--taille-cellule);
}

.avertissement {
  background: var(--attention-fond);
  border: 1px solid var(--attention-bordure);
  color: var(--attention-texte-fort);
}

.bandeau {
  background: var(--succes-fond);
  border: 1px solid var(--succes-bordure);
  color: var(--succes-texte-fort);
}

.erreur,
.error {
  background: var(--danger-fond);
  border: 1px solid var(--danger-bordure);
  color: var(--danger-texte-fort);
}

/* Le cercle de 17px du §2.9, posé par le CSS : aucun des 113 gabarits livrés n'a à changer
   pour l'obtenir. Le glyphe est une constante littérale — aucune donnée n'entre ici. */
.avertissement::before,
.bandeau::before,
.erreur::before,
.error::before {
  content: '!';
  position: absolute;
  left: var(--espace-6);
  top: calc(var(--espace-4) + 1px);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 17px;
  height: 17px;
  border-radius: var(--rayon-cercle);
  background: var(--attention-accent);
  color: var(--texte-inverse);
  font-size: var(--taille-micro);
  font-weight: var(--graisse-chiffre);
}

.bandeau::before {
  content: '¹3';
  background: var(--succes-texte);
}

.erreur::before,
.error::before {
  background: var(--danger-texte);
}

/* `alerte` et `signal` sont posées sur des `strong`/`span` en ligne, dans une cellule :
   elles colorent le texte, elles n'encadrent rien. */
.alerte {
  color: var(--danger-texte);
  font-weight: var(--graisse-forte);
}

.signal {
  color: var(--attention-texte);
  font-size: var(--taille-note);
}

/* Les MÊMES classes posées sur un `<p>` de fiche portent un message, pas un mot : elles y
   reçoivent le traitement de bandeau. Le sélecteur est qualifié par l'élément — sans quoi
   chaque cellule de tableau qui emploie `.alerte` en ligne hériterait d'un encadré.
   Spec du 24/08/2026, arbitrage 1. */
p.alerte,
p.signal {
  position: relative;
  display: block;
  margin: 0 0 var(--espace-4);
  padding: var(--espace-4) var(--espace-6);
  padding-left: calc(var(--espace-6) + 17px + var(--espace-4));
  border-radius: var(--rayon-surface);
  font-size: var(--taille-cellule);
  font-weight: var(--graisse-normale);
}

p.alerte {
  background: var(--danger-fond);
  border: 1px solid var(--danger-bordure);
  color: var(--danger-texte-fort);
}

p.signal {
  background: var(--attention-fond);
  border: 1px solid var(--attention-bordure);
  color: var(--attention-texte-fort);
}

p.alerte strong,
p.signal strong {
  font-weight: var(--graisse-forte);
}

/* Le titre d'un avertissement qui doit se lire « en gros » (décision D du 23/09/2026 : « Aucun
   bon de commande » à la signature) : sur sa propre ligne, à la taille d'un titre de section. */
p.alerte .titre-alerte,
p.signal .titre-alerte {
  display: block;
  font-size: var(--taille-titre-section);
}

p.alerte::before,
p.signal::before {
  content: '!';
  position: absolute;
  left: var(--espace-6);
  top: calc(var(--espace-4) + 1px);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 17px;
  height: 17px;
  border-radius: var(--rayon-cercle);
  color: var(--texte-inverse);
  font-size: var(--taille-micro);
  font-weight: var(--graisse-chiffre);
}

p.alerte::before {
  background: var(--danger-texte);
}

/* Recette r01 du 23/09/2026 : l'envoi d'une facture qui n'est pas parti se dit « en gros » en
   tête de fiche — le titre du bandeau à la taille d'un titre de section. */
.alerte-envoi-titre {
  margin: 0 0 var(--espace-2);
  font-size: var(--taille-titre-section);
  font-weight: var(--graisse-forte);
}

/* Réseau faible (spec 2026-09-28-reseau-faible.md) : le bandeau que `src/public/js/reseau.js` pose
   en tête de `main`, ou du dialog d'où part l'action. Aux couleurs de `p.alerte` ; collé en haut
   de l'écran pour rester lu quand le geste part de la barre du bas ; au-dessus de celle-ci (3),
   sous le tiroir du menu (20). « Réessayer » à la hauteur d'une cible tactile. */
.bandeau-reseau {
  position: sticky;
  top: 0;
  z-index: 4;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--espace-3) var(--espace-5);
  margin: 0 0 var(--espace-4);
  padding: var(--espace-4) var(--espace-6);
  border: 1px solid var(--danger-bordure);
  border-radius: var(--rayon-surface);
  background: var(--danger-fond);
  color: var(--danger-texte-fort);
  font-size: var(--taille-cellule);
}

.bandeau-reseau p {
  flex: 1 1 16em;
  margin: 0;
}

.bandeau-reseau button {
  height: var(--hauteur-cible-tactile);
}

p.signal::before {
  background: var(--attention-accent);
}

/* ---------------------------------------------------------------------------
 * Listes de définition des fiches
 * ------------------------------------------------------------------------ */

dl {
  display: grid;
  /* `max-content` sans plancher : les 160px du plancher étaient pris au libellé
     « Client » et manquaient à l'adresse, qui se repliait dans une colonne de
     400px. Le libellé ne prend désormais que sa propre largeur. */
  grid-template-columns: max-content minmax(0, 1fr);
  gap: var(--espace-1) var(--espace-5);
  margin: 0;
  font-size: var(--taille-cellule);
}

dt {
  color: var(--texte-attenue);
}

dd {
  margin: 0;
  color: var(--texte-principal);
}

/* ---------------------------------------------------------------------------
 * Surfaces flottantes — les seules à porter une ombre (référence §1.9, §3.1).
 * Aucune n'est utilisée par un gabarit livré à ce jour ; elles sont posées ici pour
 * que le premier écran qui en aura besoin ne réinvente pas l'ombre sur une carte.
 * ------------------------------------------------------------------------ */

.voile {
  position: fixed;
  inset: 0;
  background: var(--voile-modale);
  z-index: 40;
}

.modale {
  background: var(--surface);
  border-radius: var(--rayon-pastille);
  overflow: hidden;
  box-shadow: var(--ombre-modale);
  max-height: 86vh;
  z-index: 50;
}

.panneau-lateral {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: 380px;
  background: var(--surface);
  border-left: 1px solid var(--bordure-surface);
  box-shadow: var(--ombre-panneau-lateral);
  overflow-y: auto;
  z-index: 41;
}

.toast {
  position: fixed;
  bottom: var(--espace-8);
  left: 50%;
  transform: translateX(-50%);
  max-width: 520px;
  padding: var(--espace-4) 18px;
  background: var(--fond-inverse);
  border-radius: var(--rayon-surface);
  color: var(--texte-inverse);
  font-size: var(--taille-controle);
  box-shadow: var(--ombre-toast);
  z-index: 70;
}

/* ---------------------------------------------------------------------------
 * Le champ complet — référence §4.4. Le prototype ne montre NI champ obligatoire
 * marqué, NI texte d'aide, NI message d'erreur : « le mot obligatoire apparaît 7 fois en
 * prose, aucun champ n'est montré en erreur ». Ces trois-là sont donc dessinés ici pour la
 * première fois. Le libellé reste enveloppant, comme dans les gabarits livrés.
 *
 * La distinction porte tout : une ERREUR empêche d'enregistrer et se code en rouge ; un
 * SIGNAL n'empêche rien et se code en ambre. Les fiches livrées rendent déjà des signaux
 * (« Formats invalides ») sans jamais rien bloquer — c'est ce codage-là qu'on prolonge.
 * ------------------------------------------------------------------------ */

.obligatoire {
  margin-left: 2px;
  color: var(--danger-texte);
}

.champ-aide {
  display: block;
  margin-top: var(--espace-1);
  font-size: var(--taille-note);
  color: var(--texte-attenue);
}

.champ-erreur {
  display: block;
  margin-top: var(--espace-1);
  font-size: var(--taille-note);
  font-weight: var(--graisse-forte);
  color: var(--danger-texte);
}

label.en-erreur input,
label.en-erreur select,
label.en-erreur textarea {
  border-color: var(--danger-bordure);
  background: var(--danger-fond-ligne);
}

/* ---------------------------------------------------------------------------
 * État vide — référence §4.4 : « aucun ; les seuls Aucun… du prototype sont des lignes de
 * données de démonstration, pas un état vide de composant ».
 *
 * Un état vide dit DEUX choses : que la liste est vide, et pourquoi. « Le filtre ne rend
 * rien » et « rien n'existe encore » n'appellent pas la même action — le gabarit choisit le
 * texte, le composant ne fait que lui donner sa place.
 * ------------------------------------------------------------------------ */

.etat-vide {
  padding: 40px var(--espace-7);
  text-align: center;
  color: var(--texte-secondaire);
}

.etat-vide-titre {
  margin: 0 0 var(--espace-2);
  font-size: var(--taille-controle);
  font-weight: var(--graisse-forte);
  color: var(--texte-principal);
}

/* Un état vide posé DANS un bloc (« Aucun document. », « Aucune intervention ce jour-là. ») n'est
   qu'une ligne de ce bloc : texte secondaire, aligné à gauche, 6 px de marge. Au téléphone
   d'abord (finition du 28/09/2026 : ~350 px gagnés sur la fiche du passage), au poste depuis la
   décision de Yanis du 29/09/2026 (revue du bureau, Q1 : planning, une tournée vide mesurait
   136 px contre 90 px pour une tournée d'une intervention). Un état vide qui EST la page — hors
   de tout bloc — garde la forme centrée ci-dessus. */
.carte .etat-vide,
.panneau .etat-vide {
  padding: var(--espace-2) 0;
  text-align: left;
}

/* Le panneau colle son contenu aux bords (§2.5) : un état vide qui en est l'enfant direct reprend
   le retrait horizontal des surfaces, sans quoi son texte touche la bordure (recherche : 1 px). */
.panneau > .etat-vide {
  padding: var(--espace-4) var(--espace-7);
}

.carte .etat-vide-titre,
.panneau .etat-vide-titre {
  margin: 0;
  font-size: var(--taille-cellule);
  font-weight: var(--graisse-normale);
  color: var(--texte-secondaire);
}

/* ---------------------------------------------------------------------------
 * Attente HTMX — référence §4.4 : « aucun indicateur de chargement, aucun squelette ; le
 * prototype est synchrone ». L'application ne l'est pas.
 *
 * Le squelette garde la HAUTEUR des lignes qu'il remplace : sans quoi la page saute au
 * moment où la réponse arrive, et le curseur se retrouve ailleurs qu'où on l'avait posé.
 * ------------------------------------------------------------------------ */

.chargement {
  display: flex;
  align-items: center;
  gap: var(--espace-3);
  padding: var(--espace-4) var(--espace-7);
  font-size: var(--taille-note);
  color: var(--texte-attenue);
}

.rotule {
  width: 14px;
  height: 14px;
  border: 2px solid var(--bordure-saisie);
  border-top-color: var(--texte-accent);
  border-radius: var(--rayon-cercle);
  animation: rotation 0.7s linear infinite;
}

.squelette-ligne {
  display: flex;
  align-items: center;
  gap: var(--espace-7);
  padding: 9px var(--espace-7);
  border-bottom: 1px solid var(--separateur-interne);
}

.squelette {
  height: 11px;
  border-radius: var(--rayon-etiquette);
  background: var(--separateur-interne);
  animation: pulsation 1.4s ease-in-out infinite;
}

@keyframes rotation {
  to {
    transform: rotate(360deg);
  }
}

@keyframes pulsation {
  0%,
  100% {
    opacity: 1;
  }

  50% {
    opacity: 0.45;
  }
}

/* ---------------------------------------------------------------------------
 * Contrôle segmenté — référence §2.13. Pour un choix TERNAIRE nommé : le verdict d'un point
 * de contrôle de retour, conforme / non conforme / sans objet. Les classes portent ces trois
 * noms plutôt qu'un index (spec du 24/08, arbitrage 4).
 * ------------------------------------------------------------------------ */

.segmente {
  display: inline-flex;
  gap: var(--espace-1);
}

.segment {
  height: 24px;
  padding: 0 9px;
  background: var(--surface);
  border: 1px solid var(--bordure-surface);
  border-radius: 5px;
  color: var(--texte-inactif);
  font-size: var(--taille-pastille);
  font-weight: var(--graisse-forte);
  cursor: pointer;
}

.segment:hover {
  background: var(--surface-survol-bouton);
}

.segment[aria-pressed='true'].conforme {
  background: var(--succes-fond);
  border-color: var(--succes-bordure);
  color: var(--succes-texte-fort);
}

.segment[aria-pressed='true'].non-conforme {
  background: var(--danger-fond);
  border-color: var(--danger-bordure);
  color: var(--danger-texte-fort);
}

.segment[aria-pressed='true'].sans-objet {
  background: var(--neutre-fond);
  border-color: var(--bordure-saisie);
  color: var(--neutre-texte);
}

/* ---------------------------------------------------------------------------
 * Colonne triable — demande du brief §4 (« colonnes triables »), que le prototype ne
 * dessine pas : ses en-têtes ne portent ni indicateur, ni `cursor:pointer`. §4.4 le notait
 * comme « non montré » — il faut lire « non dessiné », pas « non voulu ».
 *
 * Le tri est porté par un `<button>` DANS le `<th>` : un `<th>` cliquable n'est pas
 * atteignable au clavier (spec du 24/08, arbitrage 6). L'état de tri vit sur le `<th>`, en
 * `aria-sort`, avec `none` explicite pour une colonne non triée (arbitrage 7).
 * ------------------------------------------------------------------------ */

thead th.triable {
  padding: 0;
}

thead th.triable button {
  display: flex;
  align-items: center;
  gap: var(--espace-2);
  width: 100%;
  height: auto;
  padding: var(--espace-3) var(--espace-7);
  background: transparent;
  border: none;
  border-radius: 0;
  color: inherit;
  font: inherit;
  text-transform: inherit;
  letter-spacing: inherit;
  cursor: pointer;
}

thead th.triable button:hover {
  background: var(--surface-accentuee);
}

thead th.triable.chiffres button {
  justify-content: flex-end;
}

.tri-fleche {
  flex: none;
  color: var(--bordure-bouton);
}

thead th[aria-sort='ascending'] button,
thead th[aria-sort='descending'] button,
thead th[aria-sort='ascending'] .tri-fleche,
thead th[aria-sort='descending'] .tri-fleche {
  color: var(--texte-accent);
}

/* ---------------------------------------------------------------------------
 * Compteur — référence §2.7, troisième variante de la pastille : Mono, plus petite, moins
 * arrondie. Elle porte un NOMBRE, là où `.badge` porte un état et `.etiquette` une
 * catégorie. Sa variante d'alerte reprend le fond de danger.
 * ------------------------------------------------------------------------ */

.compteur {
  display: inline-block;
  padding: 1px var(--espace-2);
  border-radius: var(--rayon-controle);
  background: var(--neutre-fond);
  color: var(--texte-action);
  font-family: var(--police-chiffres);
  font-size: var(--taille-entete-colonne);
  font-weight: var(--graisse-chiffre);
}

.compteur.en-alerte {
  background: var(--danger-fond);
  color: var(--danger-texte);
}

/* ---------------------------------------------------------------------------
 * Fil d'Ariane — référence §2.3 (12px du prototype = `--taille-cellule`), rendu par la coque
 * en tête du contenu, sur le fond de page (docs/specs/2026-09-28-fil-d-ariane-et-navigation.md).
 * Le séparateur est du CONTENU CSS : la coque n'écrit que les maillons, la barre oblique ne
 * pollue ni les tests ni les lecteurs d'écran. Le dernier maillon est la page courante.
 * ------------------------------------------------------------------------ */

.fil-ariane {
  font-size: var(--taille-cellule);
  color: var(--texte-attenue);
}

.fil-ariane ol {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  list-style: none;
  margin: 0 0 var(--espace-3);
  padding: 0;
}

.fil-ariane li + li::before {
  content: '/';
  margin: 0 var(--espace-2);
}

.fil-ariane a {
  color: var(--texte-attenue);
}

.fil-ariane a:hover {
  color: var(--texte-accent-survol);
}

.fil-ariane [aria-current='page'] {
  color: var(--texte-action);
  font-weight: var(--graisse-normale);
}

/* ---------------------------------------------------------------------------
 * Barre de taux — employée par `pages/pilotage.eta` (ex-`pilotage-bilan`) depuis le bloc pilotage du
 * 08/08/2026 SANS avoir jamais eu de règle : le gabarit rendait deux `<span>` nus, donc
 * invisibles. Son commentaire annonce pourtant « du CSS dont la largeur vient déjà calculée
 * du serveur » — la largeur arrivait bien, la feuille manquait.
 *
 * Une seule teinte, l'accent : une magnitude à série unique n'a pas besoin d'une seconde
 * couleur, et un taux d'échec est une MESURE, pas un état — le chiffre à côté porte le sens.
 * ------------------------------------------------------------------------ */

.barre {
  display: inline-block;
  width: 100%;
  max-width: 220px;
  height: 8px;
  vertical-align: middle;
  border-radius: var(--rayon-etiquette);
  background: var(--separateur-interne);
  overflow: hidden;
}

.barre-remplie {
  display: block;
  height: 100%;
  border-radius: var(--rayon-etiquette);
  background: var(--texte-accent);
}

/* ---------------------------------------------------------------------------
 * Petits écrans — la PREMIÈRE `@media` de cette feuille.
 *
 * Le prototype n'en porte aucune : « Adaptation aux petits écrans : aucune. Zéro
 * `@media`, largeurs de barre latérale et de modales en pixels fixes. Le prototype vise
 * un poste de bureau, exclusivement » (référence visuelle §4.4). Rien de ce qui suit
 * n'est donc lu quelque part : tout y est arbitré, et la spec dit pourquoi.
 * Spec : docs/specs/2026-08-24-passe-mobile.md.
 *
 * Deux seuils, chacun posé sur une MESURE prise sur les écrans rendus, pas sur une
 * famille d'appareils :
 *   - 900px — au-dessous, un tableau de liste ne tient plus dans la colonne de contenu
 *     (686px de tableau pour 556px disponibles, à 768px de fenêtre) et c'est la PAGE
 *     qui se met à glisser latéralement.
 *   - 640px — au-dessous, la barre latérale de 212px prend plus de la moitié de la
 *     largeur et ne laisse que 163px au contenu (à 375px de fenêtre).
 * ------------------------------------------------------------------------ */

@media (max-width: 900px) {
  /* Index Clients (contrat d'écran du 27/08/2026) : la carte défile, l'identifiant
     et sa case de sélection restent visibles — sinon on défile sans savoir de
     quelle ligne il s'agit. La rangée de chips défile plutôt que de se replier. */
  .liste-dense .colonne-selection,
  .liste-dense .colonne-epinglee {
    position: sticky;
    z-index: 2;
    background: var(--surface);
  }

  .liste-dense thead .colonne-selection,
  .liste-dense thead .colonne-epinglee {
    background: var(--surface-secondaire);
  }

  .liste-dense .colonne-selection {
    left: 0;
  }

  .liste-dense .colonne-epinglee {
    left: var(--largeur-colonne-selection);
  }

  /* `.index-dense .chips` et non `.chips` : la règle générale des chips est déclarée
     PLUS BAS dans la feuille, et à spécificité égale c'est elle qui gagnerait. */
  /* C-34 — écrans du terrain : la ligne se REPLIE en deux niveaux au lieu de
     fuir à droite. Identité au premier niveau, colonnes secondaires en seconde
     ligne atténuée, actions dessous en pleine largeur. Les en-têtes de tri
     disparaissent : à 390px une rangée d'en-têtes de tableau n'est pas un
     contrôle utilisable, et l'écran est d'abord là pour agir. */
  .liste-terrain,
  .liste-terrain tbody,
  .liste-terrain tr,
  .liste-terrain td {
    display: block;
    width: auto;
  }

  /* Le plancher de 760px de `.liste-dense` est fait pour un VRAI tableau : gardé ici, il
     rendrait la carte plus large que l'écran et la seconde ligne déborderait au lieu de se
     replier. Une ligne repliée n'a plus de colonnes à tenir. */
  /* Spécificité assumée : `table.liste-dense { min-width: 760px }` et
     `.panneau--defilant > table { display: table }` sont déclarées PLUS BAS dans la feuille et
     gagneraient à spécificité égale. Une ligne repliée n'a plus de colonnes à tenir — le
     plancher de largeur la ferait déborder au lieu de la replier. */
  .panneau--defilant > table.liste-terrain {
    display: block;
    min-width: 0;
  }

  .liste-terrain thead {
    display: none;
  }

  .liste-terrain tbody tr {
    padding: var(--espace-4) var(--espace-5);
  }

  .liste-terrain tbody td {
    padding: 0;
    border: none;
  }

  /* Tout ce qui n'est ni l'identifiant ni les actions tient sur UNE ligne. */
  /* Texte à lire dehors : `--texte-secondaire` et non `--texte-attenue` (≈ 2,9 : 1 sur blanc,
     sous le seuil de 4,5 : 1 — mesuré à la passe de finition du 28/09/2026). */
  .liste-terrain tbody td:not(:first-child):not(.cellule-actions) {
    display: inline;
    font-size: var(--taille-note);
    color: var(--texte-secondaire);
  }

  /* Le séparateur ferme la valeur précédente (`::after`, espace insécable devant) au lieu
     d'ouvrir la suivante : un « · » ne commence plus jamais une ligne (finition du 28/09/2026). */
  .liste-terrain
    tbody
    td:not(:first-child):not(.cellule-actions):has(~ td:not(.cellule-actions):not(:empty))::after {
    content: '\a0·';
  }

  /* Une cellule vide (le fournisseur d'une sortie de stock) ne laisse pas un « · · » dans la
     carte (défauts du 29/09/2026) : elle disparaît, et le séparateur ne l'annonce pas. */
  .liste-terrain tbody td:not(:first-child):empty {
    display: none;
  }

  /* `display: block` ci-dessus a repris la cellule d'actions : on la remet en flex, sinon
     les boutons ne peuvent pas se partager la largeur. */
  .liste-terrain tbody td.cellule-actions {
    display: flex;
    justify-content: stretch;
    margin-top: var(--espace-3);
  }

  /* Un bouton qu'on vise avec des gants prend la largeur qu'il peut. */
  .liste-terrain .cellule-actions form,
  .liste-terrain .cellule-actions form > button,
  .liste-terrain .cellule-actions > button {
    flex: 1;
    justify-content: center;
  }

  .index-dense .chips {
    flex-wrap: nowrap;
    overflow-x: auto;
  }

  .index-dense .chips > * {
    flex: none;
  }

  /* Le tableau devient sa propre zone de défilement : c'est LUI qui glisse, jamais la
     page. `display: block` en fait une boîte de bloc ; ses lignes restent rendues par un
     tableau anonyme, la mise en page des colonnes ne change pas.
     L'arrondi passe par `overflow: hidden` au-dessus de ce seuil ; une même boîte ne peut
     pas découper et défiler à la fois, `auto` l'emporte donc ici. */
  table {
    display: block;
    overflow-x: auto;
  }

  /* 400px de colonne latérale n'en laisseraient que 500 à l'essentiel : les deux colonnes
     se remettent l'une sous l'autre au même seuil que les tableaux. */
  .deux-colonnes {
    grid-template-columns: minmax(0, 1fr);
  }

  /* Quatre colonnes de Logistique n'y laisseraient pas 200px par carte : elles s'empilent. */
  .tableau-logistique {
    grid-template-columns: minmax(0, 1fr);
  }

  /* Liste des contrôles au doigt (28/09/2026, spec 2026-09-28-controle-ko-degats-liste,
     arbitrage 8). La ligne repliée est une CARTE qu'on touche en entier : le pseudo-élément de
     l'action principale (`.action-carte`) la recouvre, le code de l'unité et « Remettre en
     service » restent des cibles à part, posées au-dessus. */
  .liste-controles tbody tr {
    position: relative;
  }

  .liste-controles tbody tr:has(.action-carte) {
    cursor: pointer;
  }

  .liste-controles .action-carte::after {
    content: '';
    position: absolute;
    inset: 0;
  }

  /* Au poste, `.liste-dense` étend le lien de l'UNITÉ sur la ligne et pose la cellule d'actions
     (et le lien enveloppé d'un `<span>`) au-dessus : repliée, la carte appartient à l'action. */
  .liste-controles tbody tr > td > a::after {
    content: none;
  }

  .liste-controles.liste-dense tbody td.cellule-actions,
  .liste-controles tbody td.cellule-actions > span > a.action-carte {
    position: static;
  }

  .liste-controles tbody td:first-child a,
  .liste-controles .dialogue-hote > button {
    position: relative;
    z-index: 1;
  }

  /* Ce que disait l'en-tête de colonne, masqué au poste : « Dernier contrôle : jamais »,
     « 3 / 61 points renseignés ». Et du texte à lire, pas des notes : 14px. */
  .liste-terrain .libelle-replie {
    display: inline;
    font-family: var(--police-texte);
  }

  /* Une immatriculation absente ne laisse pas un « · · » dans la ligne repliée. */
  .liste-controles tbody td:has(> .identifiant:empty) {
    display: none;
  }

  /* Journal des comptes : exception nominative à C-34 (décision de Yanis du 30/09/2026, spec
     2026-09-30-journal-des-comptes, arbitrage 11). Écran de lecture ouvert aussi au téléphone :
     chaque ligne devient une carte à deux niveaux — « Compte · Geste », puis « Changement · Quand
     · Auteur » en note — au lieu de faire défiler le tableau de côté. Patron de `.liste-terrain`,
     réordonné par `order` : l'ordre du DOM reste celui des colonnes du poste. Spécificité
     assumée, pour les mêmes raisons que `.liste-terrain` (plancher de 760px, `display: table`). */
  .panneau--defilant > table.liste-journal {
    display: block;
    min-width: 0;
  }

  .liste-journal tbody {
    display: block;
  }

  .liste-journal thead {
    display: none;
  }

  .liste-journal tbody tr {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    padding: var(--espace-4) var(--espace-5);
    border-bottom: 1px solid var(--separateur-interne);
  }

  /* `table.` : `.liste-dense tbody td` (rembourrage vertical) est déclarée PLUS BAS dans la
     feuille et gagnerait à spécificité égale — chaque cellule repliée prenait 16px de plus. */
  table.liste-journal tbody td {
    padding: 0;
    border: none;
    white-space: normal;
  }

  .liste-journal tbody td.journal-compte {
    order: 1;
  }

  .liste-journal tbody td.journal-geste {
    order: 2;
  }

  /* Le saut de niveau : une rangée vide pleine largeur, posée entre le geste et le changement. */
  .liste-journal tbody tr::before {
    content: '';
    order: 3;
    flex-basis: 100%;
    height: var(--espace-1);
  }

  .liste-journal tbody td.journal-changement,
  .liste-journal tbody td.journal-quand,
  .liste-journal tbody td.journal-auteur {
    font-size: var(--taille-note);
    color: var(--texte-secondaire);
  }

  .liste-journal tbody td.journal-changement {
    order: 4;
  }

  .liste-journal tbody td.journal-quand {
    order: 5;
  }

  .liste-journal tbody td.journal-auteur {
    order: 6;
  }

  /* Le séparateur ferme la valeur précédente, comme sur `.liste-terrain` : jamais en tête de ligne. */
  .liste-journal tbody td.journal-compte::after,
  .liste-journal tbody td.journal-changement::after,
  .liste-journal tbody td.journal-quand::after {
    content: '\a0·\a0';
    font-weight: var(--graisse-normale);
    color: var(--texte-secondaire);
  }

  /* « Autre modification » n'a pas de changement : la cellule vide disparaît avec son séparateur. */
  .liste-journal tbody td.journal-changement:empty {
    display: none;
  }

  /* « Reprendre » est enveloppé d'un `<span>` : il prend la largeur comme « Contrôler ». */
  .liste-controles .cellule-actions > span {
    display: flex;
    flex: 1;
  }

  .liste-controles .cellule-actions > span > .bouton {
    flex: 1;
    justify-content: center;
  }

  .liste-controles.liste-terrain tbody td:not(:first-child):not(.cellule-actions),
  .liste-controles .badge {
    font-size: var(--taille-cellule);
  }

  .liste-controles .cellule-actions .bouton.compact {
    font-size: var(--taille-controle);
  }

  /* Le filtre tient en UNE rangée : la liste déroulante prend la place qui reste au lieu de
     faire glisser la rangée de côté (470px dans 317 mesurés). */
  .index-dense .chips.filtres-controles {
    flex-wrap: wrap;
  }

  .index-dense .chips.filtres-controles > select {
    flex: 1 1 0;
    min-width: 0;
  }

  /* Deuxième passe du 28/09/2026 (spec 2026-09-28-technicien-telephone-2, arbitrages 6 et 8) :
     la carte EST le geste. L'action reste dans la page — un lien, ou le bouton d'un POST —
     avec son nom accessible et son pseudo-élément qui couvre la carte ; son dessin de bouton
     disparaît, un chevron dit que la carte s'ouvre. Les cartes d'une file sont séparées par un
     filet : le panneau les encadre déjà. */
  .liste-controles tbody tr + tr {
    border-top: 1px solid var(--separateur-interne);
  }

  .liste-controles .action-carte-libelle {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .liste-controles .action-carte-chevron {
    display: block;
  }

  /* Spécificité assumée : elle doit passer devant `.bouton`, `.primaire` et les règles de la
     cellule d'actions repliée (pleine largeur), toutes déclarées ailleurs. */
  .liste-controles tbody td.cellule-actions .action-carte,
  .liste-controles tbody td.cellule-actions > span > a.action-carte {
    flex: none;
    justify-content: center;
    width: var(--hauteur-cible-tactile);
    height: var(--hauteur-cible-tactile);
    padding: 0;
    border-color: transparent;
    background: transparent;
    color: var(--texte-secondaire);
  }

  .liste-controles tbody td.cellule-actions form,
  .liste-controles tbody td.cellule-actions > span {
    flex: none;
  }

  /* Le formulaire de « Contrôler » (un POST) ne doit pas être le bloc conteneur du
     pseudo-élément de son bouton. Au poste, `.liste-dense tbody td > form` le pose en
     `position: relative` pour qu'il passe au-dessus du lien étendu. Replié, ce même
     positionnement bornait la zone touchable au carré de 44 px du chevron : un tap sur la carte
     ne faisait rien (recette du 28/09/2026 sur `d7e2b21d`). Statique, le pseudo-élément couvre
     la cellule d'actions posée sur la carte (« À contrôler »), ou la ligne (« En maintenance »,
     dont « Remettre en service » reste au-dessus), comme celui de « Reprendre ».
     `z-index: auto` (défauts du 29/09/2026, spec 2026-09-29-defauts-ecrans-terrain) : le
     formulaire est un élément FLEX de la cellule, et un élément flex tient compte de son
     `z-index` même statique. Le `z-index: 1` du poste le peignait au-dessus du code de l'unité,
     qui ne s'ouvrait plus depuis la carte « À contrôler ». */
  .liste-controles tbody td.cellule-actions > form {
    position: static;
    z-index: auto;
  }

  .liste-controles .action-carte:focus-visible {
    outline: none;
  }

  .liste-controles .action-carte:focus-visible::after {
    outline: 2px solid var(--anneau-focus);
    outline-offset: -2px;
  }

  /* Sans autre geste que l'action principale, la cellule d'actions ne prend pas une rangée de
     plus : elle se pose sur toute la carte, le chevron centré à droite — son pseudo-élément
     couvre alors la cellule, donc la carte. Le code de l'unité reste au-dessus (`z-index`). */
  .liste-controles.liste-dense tbody td.cellule-actions:not(:has(.dialogue-hote)) {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    margin: 0;
    padding: 0 var(--espace-3);
  }

  /* La place du chevron, sur toutes les cartes (`:has` ne s'imbrique pas dans `:has`). */
  .liste-controles tbody tr {
    padding-right: calc(var(--espace-5) + var(--hauteur-cible-tactile));
  }

  /* La colonne épinglée du poste (collante, `z-index: 2`) n'a rien à tenir dans une carte, et
     elle couvrait la carte autour du code : un tap à côté du code n'ouvrait rien. Seul le lien
     de l'unité reste au-dessus. */
  .liste-controles tbody td.colonne-epinglee {
    position: static;
  }

  /* « Remettre en service » garde sa rangée, le chevron à sa droite. */
  .liste-controles tbody td.cellule-actions:has(.dialogue-hote) {
    justify-content: space-between;
  }
}

@media (max-width: 640px) {
  /* Une seule colonne, et trois rangées : le menu, la barre supérieure, le contenu. */
  .appli {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto auto minmax(0, 1fr);
  }

  /* La mention d'une fiche (catégorie d'une unité, client d'un contrat) prend sa propre ligne
     sous le titre : à côté, il ne lui restait que « BVM1… » à 390 px (défauts du 29/09/2026,
     spec 2026-09-29-defauts-ecrans-terrain). Elle se raccourcit encore si elle ne tient pas. */
  .entete-page > h1 + .mention {
    flex-basis: 100%;
  }

  .barre-laterale {
    grid-row: auto;
    grid-column: 1;
    position: static;
    height: auto;
    padding: var(--espace-5) 0;
    border-right: 0;
    border-bottom: 1px solid var(--menu-bordure);
    overflow-y: visible;
  }

  /* Le menu se REPLIE derrière le bouton « Menu » (21/09/2026), et s'ouvre en TIROIR posé
     par-dessus la page, sous un voile (décision de Yanis du 28/09/2026, qui renverse le dépli
     dans la barre du 21/09). Replié, il ne coûte qu'une rangée : la marque et le bouton. */
  .barre-laterale-tete {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--espace-4);
    padding: 0 var(--espace-5);
  }

  .marque {
    margin: 0;
    padding: 0;
  }

  .menu-bascule {
    display: inline-flex;
  }

  /* Le tiroir : la colonne du menu de bureau, à sa largeur, collée au bord gauche et haute de
     l'écran. Le bouton « Menu », à droite de sa rangée, reste hors du panneau et passe au-dessus
     du voile : il referme aussi (arbitrage 2). Voile à 20, tiroir à 21, comme le journal. */
  .menu-tiroir {
    display: none;
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: 21;
    width: var(--largeur-barre-laterale);
    padding: var(--espace-5) 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    background: var(--menu-fond);
    border-right: 1px solid var(--menu-bordure);
  }

  .barre-laterale--ouverte .menu-tiroir {
    display: block;
  }

  .barre-laterale--ouverte .tiroir-voile {
    display: block;
  }

  .menu-tiroir .tiroir-tete {
    display: flex;
    margin-bottom: var(--espace-4);
    padding: 0 var(--espace-5);
  }

  .barre-laterale--ouverte .menu-bascule {
    position: relative;
    z-index: 22;
  }

  /* La page dessous ne défile pas tant que le tiroir est ouvert (arbitrage 4). */
  html:has(.barre-laterale--ouverte) {
    overflow: hidden;
  }

  .barre-superieure,
  .contenu {
    grid-column: 1;
  }

  .barre-superieure {
    gap: var(--espace-5);
    padding: 0 var(--espace-5);
  }

  /* Un `<input>` porte une largeur intrinsèque d'une vingtaine de caractères qui
     l'empêche de rétrécir sous elle : sans ce `min-width`, la barre déborde. */
  .recherche-globale,
  .recherche-globale input {
    min-width: 0;
  }

  /* Le rôle cède la place ; le nom et la pastille restent — l'identité de la session ne
     doit pas disparaître de l'écran où l'on saisit. */
  .identite-role {
    display: none;
  }

  .contenu {
    padding: var(--espace-7) var(--espace-5) var(--espace-9);
  }
}

/* ---------------------------------------------------------------------------
 * Cibles tactiles — troisième et dernière `@media` de la feuille.
 *
 * Le seuil ne porte PAS sur la largeur mais sur le POINTEUR : `pointer: coarse` désigne
 * l'appareil dont l'entrée principale est un doigt — téléphone, tablette, écran tactile —
 * quelle que soit sa taille. Un poste de bureau à la souris n'entre jamais ici et garde
 * les 32px de la référence visuelle §1.8, qui fait autorité sur le visuel et ne dit rien
 * du tactile. Spec : docs/specs/2026-08-25-cibles-tactiles.md.
 *
 * Mesuré avant, sur un iPhone 13 émulé : tout est sous 44px — 13px pour une case à cocher,
 * 32px pour les boutons et les champs, 33px pour une entrée de menu.
 * ------------------------------------------------------------------------ */

@media (pointer: coarse) {
  /* Les boutons et les champs portent une `height` fixe, pas une `min-height` : c'est elle
     qu'il faut reprendre, une `min-height` ne l'emporterait pas. */
  button,
  .bouton,
  input:not([type='checkbox']):not([type='radio']),
  select {
    height: var(--hauteur-cible-tactile);
  }

  /* Le bouton « Menu » de la coque porte sa propre `height` de classe, qui l'emportait sur la
     règle d'élément ci-dessus : 32px mesurés au téléphone le 28/09/2026 (spec
     2026-09-28-tournee-telephone.md). */
  .menu-bascule {
    height: var(--hauteur-cible-tactile);
  }

  /* La croix du tiroir (28/09/2026) : même cible, carrée. */
  .menu-fermer {
    width: var(--hauteur-cible-tactile);
    height: var(--hauteur-cible-tactile);
  }

  /* La case à cocher ne grossit pas jusqu'à 44px — elle deviendrait un carré énorme à côté
     de son texte. Elle passe à une taille visible, et c'est son LIBELLÉ qui porte la cible :
     c'est déjà lui qu'un appui active. Les gabarits écrivent tous
     `<label><input type="checkbox" /> Texte</label>`. */
  input[type='checkbox'],
  input[type='radio'] {
    width: 22px;
    height: 22px;
  }

  label:has(> input[type='checkbox']),
  label:has(> input[type='radio']) {
    display: flex;
    align-items: center;
    min-height: var(--hauteur-cible-tactile);
  }

  /* Le menu : `min-height` et non `height`, une entrée sur deux lignes devant pouvoir
     grandir plutôt que déborder de sa boîte. */
  .barre-laterale nav a,
  .barre-laterale .menu-aide {
    display: flex;
    align-items: center;
    min-height: var(--hauteur-cible-tactile);
  }

  /* Le titre d'une famille repliée est un contrôle (spec 2026-09-14) : même cible que
     les entrées qu'il dévoile. */
  .barre-laterale nav .nav-famille > .nav-groupe-titre {
    display: flex;
    align-items: center;
    min-height: var(--hauteur-cible-tactile);
  }

  /* Onglets et contrôle segmenté : posés par le bloc composants, pas encore rendus par un
     gabarit. Les couvrir ici coûte deux sélecteurs et évite un trou le jour où ils servent. */
  .onglet,
  .segment {
    min-height: var(--hauteur-cible-tactile);
  }

  /* Le bouton appareil photo d'un point de contrôle (22/09/2026) : un carré au doigt. Sélecteur
     qualifié par son conteneur (28/09/2026, spec 2026-09-28-fiche-controle-navigation,
     arbitrage 10) : la règle de la fiche, déclarée plus bas à poids égal, l'écrasait — d'où les
     32 px mesurés sur les 61 boutons. Même raison pour les choix du bilan, restés à 40 px. */
  .point-photos .bouton-photo {
    width: var(--hauteur-cible-tactile);
    height: var(--hauteur-cible-tactile);
  }

  .carte.carte-bilan .bilan-choix label {
    min-height: var(--hauteur-cible-tactile);
  }

  /* Le dépli de la fiche d'identité est une action à lui seul. */
  .fiche-identite > summary {
    min-height: var(--hauteur-cible-tactile);
  }

  /* Un lien d'action dans une cellule de tableau est une cible à lui seul, contrairement à
     un lien dans une phrase — voir l'arbitrage 2 de la spec. */
  tbody td a {
    display: inline-flex;
    align-items: center;
    min-height: var(--hauteur-cible-tactile);
  }

  /* Un lien seul dans son paragraphe, ou posé dans une barre de filtres à côté du bouton
     « Filtrer », est une ACTION — pas un mot dans une phrase. Le rembourrage VERTICAL
     l'agrandit sans rien déplacer : sur un élément en ligne, il ne pousse pas la ligne, il
     n'étend que la boîte touchée. 12px de part et d'autre d'une ligne de 20px font 44.

     `a:only-child` ne compte que les ÉLÉMENTS : `<p>Voir la <a>fiche</a></p>` est donc
     visé lui aussi. C'est sans conséquence, et c'est la raison du rembourrage plutôt que
     d'une hauteur — une hauteur y casserait la phrase, un rembourrage ne s'y voit pas. */
  form > a,
  p > a:only-child {
    padding-block: var(--espace-5);
  }

  /* Le bouton de déconnexion est carré : sa largeur suit sa hauteur tactile. */
  .deconnexion button {
    width: var(--hauteur-cible-tactile);
  }

  /* Les rangées de filtres (`.chips`) : la règle des chips, déclarée plus bas, tenait leurs
     listes, champs et boutons à 26px — corrigé pour les contrôles le 28/09 (arbitrage 8), resté
     ailleurs (parc, stock : 26px mesurés, passe de finition du 28/09). Sélecteurs qualifiés pour
     l'emporter sur elle. Et la croix des dialogs, carrée : 32px mesurés dans le constat. */
  .chips select:not([multiple]),
  .chips input:not([type='checkbox']):not([type='radio']):not([type='hidden']),
  .chips > button:not(.chip),
  .chips > .bouton:not(.chip) {
    height: var(--hauteur-cible-tactile);
  }

  /* Le lien d'un en-tête de panneau (« Feuille de route », 22px) est une action à lui seul :
     même rembourrage que les liens d'action ci-dessus. */
  .panneau-entete > a {
    padding-block: var(--espace-5);
  }

  /* Le nom d'une pièce jointe (19px) ouvre le fichier : il prend la hauteur d'une cible, sa
     ligne de liste grandit avec lui (le rembourrage seul s'arrêtait à 43px). */
  li > a[href^='/pieces-jointes/'] {
    display: inline-flex;
    align-items: center;
    min-height: var(--hauteur-cible-tactile);
  }

  .panneau-entete > .dialogue-fermer {
    width: var(--hauteur-cible-tactile);
    height: var(--hauteur-cible-tactile);
  }

  /* La zone de saisie a déjà 64px de haut ; la reprendre à 44 la RÉDUIRAIT. */
  textarea {
    height: auto;
  }
}

/* ===========================================================================
 * REFONTE 2026-08-25 — les motifs du dossier `design_handoff_refonte_ui/`.
 *
 * Les maquettes portent leurs styles EN LIGNE : 352 attributs `style=` sur la seule fiche
 * devis. Ce bloc en fait des classes, en reprenant les jetons du `:root` — les valeurs en
 * dur des maquettes SONT les jetons (`#e2e6ea` est `--bordure-surface`, `#98a2ad` est
 * `--texte-micro-libelle`, et ainsi de suite).
 *
 * Contrat de portage : `design_handoff_refonte_ui/README.md`, section « huit motifs ».
 * Spec : docs/specs/2026-08-25-refonte-fondation.md.
 * ======================================================================== */

/* --- Motif 1 : titres de groupe du menu -----------------------------------
 * Le `<nav>` de `layouts/main.eta` reste SANS attribut — quatre suites l'extraient par
 * regex — et le menu est un seul `<nav>`. Le titre de groupe vit DEDANS : c'est le
 * `summary` du dépliant de sa famille (section « Familles repliées », qui ajuste son
 * rembourrage de contrôle). Le style porte sur la classe, jamais sur la balise. */
.nav-groupe-titre {
  margin: 0;
  padding: var(--espace-5) var(--espace-4) var(--espace-1);
  font-size: var(--taille-micro);
  font-weight: var(--graisse-forte);
  text-transform: uppercase;
  letter-spacing: var(--interlettrage-capitales);
  color: var(--menu-titre-groupe);
}

/* --- Motif 3 : bande de contexte ------------------------------------------
 * Remplace les phrases « Rattachement / En-tête » rendues en paragraphes. Une rangée
 * d'items « micro-libellé + valeur », et un total en Mono poussé à droite. */
.bande-contexte {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--espace-9) 28px;
  margin-bottom: var(--espace-5);
  padding: var(--espace-4) var(--espace-7);
  background: var(--surface);
  border: 1px solid var(--bordure-surface);
  border-radius: var(--rayon-surface);
}

.bande-contexte dt,
.bande-contexte .micro-libelle {
  display: block;
  margin-bottom: 1px;
}

.bande-contexte dd,
.bande-contexte .valeur {
  margin: 0;
  font-size: var(--taille-cellule);
  font-weight: var(--graisse-appuyee);
}

/* Le total ferme la bande, aligné à droite et en Mono comme tout chiffre (§3.2). */
.bande-contexte-total {
  margin-left: auto;
  text-align: right;
}

.bande-contexte-total .chiffre-synthese {
  display: block;
}

/* --- Motif 5 : pile d'actions repliables ----------------------------------
 * `<details>` natif : zéro JavaScript, donc rien à porter côté Alpine, et le repli
 * survit à un rendu serveur. Les gestes dangereux ferment la pile, en rouge. */
.actions-pile {
  display: flex;
  flex-direction: column;
}

.actions-pile > details {
  border-top: 1px solid var(--separateur-interne);
}

.actions-pile > details:first-child {
  border-top: 0;
}

/* Le marqueur natif se supprime PARTOUT, comme la maquette (helmet global) : des
 * dépliants vivent aussi hors de la pile (avoirs, envois, correction d'un commentaire)
 * et cumulaient triangle natif + chevron. Le chevron reste le seul indicateur. */
summary {
  list-style: none;
  cursor: pointer;
}

summary::-webkit-details-marker {
  display: none;
}

details[open] > summary .chevron {
  transform: rotate(90deg);
}

.actions-pile summary {
  display: flex;
  align-items: center;
  gap: var(--espace-3);
  padding: 9px var(--espace-7);
  color: var(--texte-action);
  font-size: var(--taille-cellule);
  font-weight: var(--graisse-appuyee);
  cursor: pointer;
  list-style: none;
}

.actions-pile summary::-webkit-details-marker {
  display: none;
}

.actions-pile summary:hover {
  background: var(--surface-survol-bouton);
}

/* Le chevron pivote à l'ouverture — la seule animation du bloc, et elle est en CSS pur. */
.actions-pile .chevron {
  flex: none;
  transition: transform 0.15s ease;
}

.actions-pile details[open] > summary .chevron {
  transform: rotate(90deg);
}

.actions-pile .action-corps {
  padding: var(--espace-1) var(--espace-7) var(--espace-5) 36px;
}

.actions-pile details.dangereuse summary {
  color: var(--danger-texte);
}

.actions-pile details.dangereuse summary:hover {
  background: var(--danger-fond);
}

.bouton.dangereux {
  border-color: var(--danger-bordure);
  color: var(--danger-texte);
}

.bouton.dangereux:hover {
  background: var(--danger-fond);
}

/* --- Motif 6 : tableaux denses --------------------------------------------
 * Zébrage, chiffres à droite en Mono, et une rangée d'outils sous la ligne qu'elle sert. */
/* L'index dense (archétype 2, bloc 2) ne zèbre pas : la référence en ligne
   sépare ses lignes au filet seul — l'exclusion vit ici, pas en surcharge,
   pour ne pas jouer à la spécificité contre le survol. */
table:not(.liste-dense) > tbody tr:nth-child(even) {
  background: var(--surface-secondaire);
}

tbody tr:hover {
  background: var(--surface-survol-ligne);
}

/* Une cellule qui porte deux niveaux : la valeur, puis sa précision en dessous. */
.cellule-secondaire {
  display: block;
  font-size: var(--taille-pastille);
  color: var(--texte-attenue);
}

/* Un tableau dans un panneau défile dans lui-même, à toute largeur — et non seulement
   sous 900px comme le posait la passe petits écrans. */
.panneau > table,
.carte > table {
  display: block;
  overflow-x: auto;
}

/* Tableau posé directement dans un panneau (spec 2026-09-14-logistique-blocs, arbitrage 12) : en
   `display: block`, sa boîte de tableau anonyme ne s'étire pas — bande d'en-tête et filets de
   ligne s'arrêtaient au milieu du panneau. Il redevient un tableau pleine largeur, et c'est le
   panneau qui défile quand il déborde. Sans `:has`, le panneau garde son `overflow: hidden` et
   un tableau plus large que lui est rogné au lieu de défiler. */
.panneau > table {
  display: table;
  overflow-x: visible;
}

.panneau:has(> table) {
  overflow-x: auto;
}

/* --- Motif 7 : formulaires en grille --------------------------------------
 * Les champs se rangent en deux à quatre colonnes plutôt qu'un par ligne. Le nombre de
 * colonnes se déduit de la place : un gabarit n'a pas à le fixer. */
.grille-champs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--espace-5) var(--espace-6);
  align-items: end;
}

.grille-champs > .pleine-largeur {
  grid-column: 1 / -1;
}

/* Une puce cochable : la case disparaît, c'est le libellé qui porte l'état et la cible. */
.puces-cochables {
  display: flex;
  flex-wrap: wrap;
  gap: var(--espace-3);
}

.puces-cochables label {
  display: inline-flex;
  align-items: center;
  gap: var(--espace-2);
  height: var(--hauteur-controle-compact);
  margin: 0;
  padding: 0 var(--espace-5);
  background: var(--surface);
  border: 1px solid var(--bordure-bouton);
  border-radius: 13px;
  font-size: var(--taille-cellule);
  cursor: pointer;
}

.puces-cochables label:hover {
  background: var(--surface-survol-bouton);
}

.puces-cochables input:checked + span {
  font-weight: var(--graisse-forte);
  color: var(--texte-accent);
}

/* La barre d'actions d'un formulaire : le renoncement à gauche, l'engagement à droite.
   Trop large pour sa rangée, elle passe à la ligne (défauts du 29/09/2026) : alignée à droite,
   une rangée qui déborde sort par la GAUCHE, et « Annuler » était coupé de 11 px à 390 px dans
   « Remettre en service l'unité … ». Tant qu'elle tient, rien ne change. */
.barre-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--espace-6);
  margin-top: var(--espace-6);
}

/* --- Motif 8 : le bandeau d'information ----------------------------------
 * Quatrième famille, à côté d'alerte, signal et succès. Sa pastille porte un « i ». */
.information {
  background: var(--information-fond);
  border-color: var(--information-bordure);
  color: var(--information-texte);
}

p.information::before {
  content: 'i';
  background: var(--information-texte);
}

/* --- Motif 6, suite : la rangée de totaux d'une liste ----------------------
 * Les listes rendaient leurs totaux par état en `<ul><li>` — une puce par état, dans le
 * flux. La maquette en fait une rangée de cartes : l'état, son compte en Mono, son montant.
 * Ce n'est pas la grille de cartes du tableau de bord : ici les items sont petits et
 * nombreux, et ils tiennent sur une ligne. */
.totaux-etats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--espace-5);
  margin-bottom: var(--espace-5);
  padding: 0;
  list-style: none;
}

.totaux-etats li {
  flex: 1 1 160px;
  padding: var(--espace-4) var(--espace-7);
  background: var(--surface);
  border: 1px solid var(--bordure-surface);
  border-radius: var(--rayon-surface);
}

.totaux-etats .micro-libelle {
  display: block;
  margin-bottom: var(--espace-1);
}

.totaux-etats .nombre {
  font-family: var(--police-chiffres);
  font-size: var(--taille-chiffre-synthese);
  font-weight: var(--graisse-chiffre);
}

.totaux-etats .montant {
  margin-left: var(--espace-3);
  font-size: var(--taille-note);
  color: var(--texte-attenue);
}

/* ===========================================================================
 * INDEX DENSE (archétype 2) — bloc 2, spec 2026-08-26-clients-pilote
 *
 * L'écran pilote Clients reconstruit sur `design-reference/dom/clients.html` :
 * ligne de titre à compteur, phrase d'intention, chips de filtre, carte-liste
 * dense au lien étendu, pied de liste. Valeurs 100 % en jetons.
 * ======================================================================== */

/* Compteur de la ligne de titre — « N fiche(s) » en Mono atténué. Distinct de
   `.compteur`, le grand chiffre des états vides. */
.compteur-de-page {
  font-family: var(--police-chiffres);
  font-size: var(--taille-note);
  color: var(--texte-attenue);
}

/* La recherche logée dans la ligne de titre : un px de gabarit de colonne,
   comme les 208px de la barre — 220px du prototype × l'échelle, arrondi.
   Hauteur héritée de la règle `input` générique. */
.entete-page input[type='search'] {
  width: 240px;
  margin: 0;
}

/* Phrase d'intention — le contenu réel de l'écran, sous la ligne de titre. */
.intention {
  margin: 0 0 var(--espace-6);
  font-size: var(--taille-controle);
  color: var(--texte-secondaire);
}

/* Rangée de chips de filtre.
   `flex-end` (21/09/2026, retour de Yanis) : la rangée mêle des `.champ` — libellé AU-DESSUS de
   son champ — à des chips, un bouton et un lien d'une seule hauteur. Alignés en haut, ceux-ci
   tombaient face aux libellés et non face aux champs. Alignés en bas, tout contrôle de la rangée
   partage la même ligne de pied. */
.chips {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--espace-2);
  margin-bottom: var(--espace-5);
}

/* Une rangée, une hauteur : champs de saisie, bouton et lien prennent la hauteur compacte des
   chips et des listes déroulantes, sinon leurs pieds s'alignent mais leurs milieux non. */
.chips input:not([type='checkbox']):not([type='radio']),
.chips > button,
.chips > .bouton {
  height: var(--hauteur-controle-compact);
  margin: 0;
}

.chips input:not([type='checkbox']):not([type='radio']) {
  width: auto;
  min-width: 0;
}

/* Le second balisage de la rangée : le libellé ENVELOPPE son champ (`<label>Client <select>`),
   sans `.champ`. Il en prend la mise en colonne, sinon le champ se range tantôt à côté, tantôt
   sous son libellé selon la place, et les pieds de la rangée se décalent. */
.chips > label:not(.chip) {
  display: flex;
  flex-direction: column;
  gap: var(--espace-1);
  margin: 0;
}

.chips > a:not(.chip):not(.bouton) {
  display: inline-flex;
  align-items: center;
  height: var(--hauteur-controle-compact);
  padding: 0 var(--espace-2);
}

/* Chip cochable : un `<label>` qui contient sa case — la case reste visible,
   les e2e la cochent. */
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--espace-2);
  height: var(--hauteur-controle-compact);
  margin: 0;
  padding: 0 var(--espace-4);
  background: var(--surface);
  border: 1px solid var(--bordure-bouton);
  border-radius: var(--rayon-controle);
  font-size: var(--taille-controle);
  cursor: pointer;
}

/* Le `gap` de la chip porte l'espacement : sans cette reprise, la marge posée
   par `label input[type='checkbox']` doublerait l'écart case–libellé. */
.chip input[type='checkbox'] {
  margin-right: 0;
  accent-color: var(--texte-accent);
}

/* Chip active — maquette : fond night, texte blanc. */
.chip:has(> input:checked) {
  background: var(--fond-inverse);
  border-color: var(--fond-inverse);
  color: var(--texte-inverse);
}

/* La carte défile en interne, le body jamais. Le motif 6 `.panneau > table`
   fait déjà du tableau sa propre zone de défilement, mais il ne peut pas
   porter le plancher de largeur ci-dessous : `min-width` élargit la boîte de
   bloc du tableau, que l'`overflow: hidden` du panneau rognerait — c'est donc
   le panneau qui glisse. Dans une section `panneau` titrée, l'enveloppe porte
   cette seule classe : avec `panneau`, deux cadres s'emboîteraient (spec
   2026-09-14-logistique-blocs, arbitrage 11). */
.panneau--defilant {
  overflow-x: auto;
}

/* Dans le panneau défilant, le tableau redevient un vrai tableau : le motif 6
   (`.panneau > table` → `display: block`) le mettait en boîte de bloc, dont le
   CONTENU de tableau anonyme ne s'étire pas — la carte se peignait sur ~890px
   dans une boîte de 1180. Même spécificité que le motif 6, cette règle gagne
   par sa position ; elle vaut aussi sous 900px (la règle `table` de la passe
   petits écrans est moins spécifique), où c'est le panneau qui glisse. */
.panneau--defilant > table {
  display: table;
  overflow-x: visible;
  width: 100%;
}

/* Le lien de ligne se lit comme du texte : la maquette garde la raison sociale
   sombre, seul le fond de ligne signale le survol (référence §3.5 — le survol
   change le fond, jamais autre chose). Le lien étendu ci-dessous rend toute la
   ligne cliquable, l'accent reste au code. */
.liste-dense tbody a,
.liste-dense tbody a:hover {
  color: var(--texte-principal);
  text-decoration: none;
}

/* Densité de l'index (correction 4 de l'archétype) : colonnes tenues quand la
   carte défile, rembourrage vertical resserré à 8px. Dans un `.panneau`, la
   règle `.panneau table` retire déjà bordure et arrondi propres. */
table.liste-dense {
  min-width: 760px;
}

.liste-dense tbody td {
  padding-top: var(--espace-3);
  padding-bottom: var(--espace-3);
}

/* La raison sociale porte le lien et s'appuie ; l'ancre reste sans classe
   (`rendu.test.ts` fige `<a href="/clients/{id}">`). */
.liste-dense tbody td:has(> a) {
  font-weight: var(--graisse-appuyee);
}

/* Lien étendu : la ligne entière est la cible (archétype 2) — CSS pur, le
   lien reste le seul élément interactif. Le survol de ligne existe déjà. */
.liste-dense tbody tr {
  position: relative;
}

.liste-dense tbody tr > td > a::after {
  content: '';
  position: absolute;
  inset: 0;
}

/* Cellule d'actions d'une ligne cliquable (28/08/2026) : elle se pose AU-DESSUS du
   recouvrement de ligne. Sans ça, un clic dans son vide ouvre la fiche au lieu de ne rien
   faire — et sur une cellule qui porte un champ de saisie, la frappe en cours serait perdue.
   Les boutons et les liens y gagnaient déjà, étant peints après le pseudo-élément ; le VIDE
   entre eux, lui, revenait au lien de ligne.

   `.cellule-saisie` (07/09/2026) porte le MÊME besoin sans la mise en page des gestes : une
   cellule qui demande à l'opérateur de taper ou de choisir garde le modèle de table — ni
   `flex`, ni `white-space: nowrap` — mais doit elle aussi passer au-dessus du recouvrement.
   Mesuré : sans cette règle, le vide de la colonne « Chiffrage » de `degats-liste` renvoie
   `elementFromPoint` sur le lien de ligne `/unites/…`. C'est la seule propriété qu'elle
   apporte : une cellule de saisie ne s'aligne pas à droite, elle se lit comme un formulaire. */
.liste-dense tbody td.cellule-actions,
.liste-dense tbody td.cellule-saisie {
  position: relative;
}

/* Lignes à PLUSIEURS liens (bloc 4 — prospects, avoirs, référentiel des
   catégories) : le lien secondaire s'enveloppe d'un `<span>` dans le gabarit —
   hors du sélecteur d'extension ci-dessus — et se positionne pour peindre
   AU-DESSUS du recouvrement de ligne, sans quoi il serait inatteignable à la
   souris. */
.liste-dense tbody td > span > a {
  position: relative;
}

/* Un identifiant ne se coupe jamais (« DEV-0341 » cassé après le tiret rend
   la colonne illisible) : la colonne s'élargit, la carte défile au besoin. */
.liste-dense .identifiant {
  white-space: nowrap;
}

/* Un identifiant qui EST un lien garde l'accent de `.identifiant` : la règle
   « lien de ligne en texte sombre » ne vaut que pour le libellé principal. */
.liste-dense tbody a.identifiant,
.liste-dense tbody .identifiant a,
.liste-dense tbody a.identifiant:hover,
.liste-dense tbody .identifiant a:hover {
  color: var(--texte-accent);
}

/* Total sous la carte — l'archétype interdit la liste sans total. */
.pied-de-liste {
  margin: var(--espace-3) 0 0;
  font-size: var(--taille-note);
  color: var(--texte-attenue);
}

/* ---------------------------------------------------------------------------
 * INDEX CLIENTS — contrat d'écran du 27/08/2026
 *
 * Ce que le contrat ajoute au patron pilote : compteurs de chips, colonne de
 * sélection, barre d'actions groupées, colonnes épinglées au défilement, pied
 * paginé. Deux mécanismes, chacun pour ce qu'il sait faire :
 *  - la VISIBILITÉ de la barre et le COMPTE de la sélection sont en CSS pur
 *    (`:has()` déjà employé par `.chip`, et un compteur CSS) — donc justes
 *    après un échange HTMX, sans réinitialisation de script ;
 *  - « tout sélectionner » reste un geste, porté par Alpine dans le gabarit.
 * ------------------------------------------------------------------------ */

/* Compteur de chip — calculé côté serveur, jamais codé en dur (correction 7
   de l'archétype). Il suit la couleur du texte de la chip active. */
.chip-compteur {
  font-family: var(--police-chiffres);
  color: var(--texte-attenue);
}

/* Chip qui est un LIEN (pilotage des prospects, 28/08/2026) : même objet, autre mécanique.
   Là où l'index Clients porte ses filtres dans un formulaire HTMX, le pilotage les porte dans
   l'URL — la chip est donc un `<a>`, et l'actif se marque à `aria-current`, comme un onglet.
   Le rendu, lui, ne change pas d'un pouce : c'est le même filtre rapide. */
a.chip {
  color: var(--texte-principal);
  text-decoration: none;
}

a.chip[aria-current='page'] {
  background: var(--fond-inverse);
  border-color: var(--fond-inverse);
  color: var(--texte-inverse);
}

a.chip[aria-current='page'] .chip-compteur {
  color: var(--texte-inverse);
}

.chip:has(> input:checked) .chip-compteur {
  color: var(--texte-inverse);
}

/* Filtre en liste déroulante posé dans la rangée de chips (le groupe) : la règle
   générique des champs l'étire à 460px et le fait tomber sur une deuxième ligne —
   dans une rangée de chips il se dimensionne à son contenu. */
.chips select {
  height: var(--hauteur-controle-compact);
  width: auto;
  min-width: 0;
  margin: 0;
  padding: 0 var(--espace-3);
  font-size: var(--taille-controle);
}

/* Filtre client ou chantier à propositions (`partials/filtre-a-propositions.eta`, spec
   2026-09-23, arbitrage 12). La liste flotte sous son champ : fond de surface, bordure de
   surface et AUCUNE ombre — référence §1.9, six ombres dans le prototype et aucune pour une
   liste ; la bordure est le seul séparateur (§3.1). Son z-index la pose au-dessus des colonnes
   collantes des tableaux (1 et 2), sous le tiroir (20) et les modales (40 et plus). Elle prend
   la largeur de son contenu, bornée aux 460px d'un champ. */
.filtre-propositions {
  position: relative;
}

.propositions {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 10;
  width: max-content;
  min-width: 100%;
  max-width: 460px;
  max-height: 320px;
  margin: var(--espace-1) 0 0;
  padding: var(--espace-1) 0;
  overflow-y: auto;
  list-style: none;
  background: var(--surface);
  border: 1px solid var(--bordure-surface);
  border-radius: var(--rayon-surface);
}

.propositions [role='option'] {
  padding: var(--espace-2) var(--espace-4);
  font-size: var(--taille-controle);
  cursor: pointer;
}

/* Référence §3.5 : le survol change le fond, rien d'autre. L'option active au clavier prend le
   fond de l'entrée active du menu, plus marqué que le survol : c'est elle qu'Entrée choisira. */
.propositions [role='option']:hover {
  background: var(--surface-survol-ligne);
}

.propositions [role='option'][aria-selected='true'] {
  background: var(--surface-accentuee);
}

/* Lignes d'information (« D'autres … correspondent », « Aucun … ne correspond ») : lues dans
   la liste, jamais choisies. */
.propositions [role='option'][aria-disabled='true'] {
  color: var(--texte-secondaire);
  background: none;
  cursor: default;
}

.proposition-detail,
.proposition-mention {
  color: var(--texte-secondaire);
  font-size: var(--taille-note);
}

/* Filtre appliqué : une chip qui n'est pas cochable — seul son lien « Retirer » agit. */
.chip.filtre-actif {
  cursor: default;
}

/* Barre d'actions groupées : absente tant que rien n'est coché — `:has()`, déjà employé
   par `.chip`, donc juste après un échange HTMX sans réinitialisation de script. Le
   NOMBRE, lui, est écrit par Alpine : un compteur CSS ne voit que les éléments qui le
   PRÉCÈDENT dans le document, et la barre est au-dessus du tableau — mesuré, il restait
   à zéro. La visibilité reste en CSS : c'est elle qui doit survivre à tout. */

.barre-selection {
  display: flex;
  align-items: center;
  gap: var(--espace-4);
  margin-bottom: var(--espace-4);
  padding: var(--espace-3) var(--espace-7);
  background: var(--surface-accentuee);
  border: 1px solid var(--bordure-bouton);
  border-radius: var(--rayon-controle);
  font-size: var(--taille-controle);
}

.zone-selection:not(:has(.case-selection:checked)) .barre-selection {
  display: none;
}

/* Le nombre de la barre : chiffres tabulaires, pour qu'il ne saute pas en changeant. */
.barre-selection-compte {
  font-family: var(--police-chiffres);
  font-weight: var(--graisse-forte);
}

/* Largeurs de colonnes FIXES pour l'index Clients. Laissé à l'algorithme automatique, le
   tableau distribue la largeur au CONTENU : une raison sociale longue écrase la ville, qui
   se replie sur trois lignes, et la colonne d'action finit hors du viewport à 1440 — ce que
   la règle 2 du socle interdit. Fixées, les colonnes tiennent toujours, et c'est la
   cellule qui replie, jamais la grille qui se déforme.
   Les pourcentages portent sur les `th` de la première rangée, seuls lus en `fixed`. Neuf
   colonnes depuis le 29/09/2026 : « Archiver » a quitté la ligne (décision de Yanis, revue du
   bureau, Q3) ; ses 10 % vont à la raison sociale (+6) et à la ville (+4). */
.liste-dense--clients {
  table-layout: fixed;
  /* En dessous, la carte défile (mobile) : le plancher garde des colonnes lisibles. */
  min-width: 1080px;
}

.liste-dense--clients th:nth-child(1) {
  width: var(--largeur-colonne-selection);
}

.liste-dense--clients th:nth-child(2) {
  width: 7%;
}

.liste-dense--clients th:nth-child(3) {
  width: 27%;
}

.liste-dense--clients th:nth-child(4) {
  width: 6%;
}

.liste-dense--clients th:nth-child(5) {
  width: 17%;
}

.liste-dense--clients th:nth-child(6) {
  width: 9%;
}

.liste-dense--clients th:nth-child(7) {
  width: 11%;
}

.liste-dense--clients th:nth-child(8) {
  width: 10%;
}

.liste-dense--clients th:nth-child(9) {
  width: 9%;
}

/* Les en-têtes de cet index sont longs (« RETARD DE PAIEMENT », « CONTRATS OUVERTS ») :
   en largeur fixe ils débordaient les uns sur les autres. Ils se replient sur deux lignes
   plutôt que de se chevaucher — la rangée d'en-tête est la seule à y gagner en hauteur. */
.liste-dense--clients thead th {
  overflow: hidden;
}

.liste-dense--clients thead th.triable button {
  white-space: normal;
  text-align: left;
}

.liste-dense--clients thead th.triable.chiffres button {
  text-align: right;
}

.liste-dense .colonne-ville {
  /* Un nom de ville ne se casse pas à ses traits d'union : « Saint-Laurent-de-Mure »
     replié sur trois lignes triple la hauteur de la ligne. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Un texte libre long d'une cellule — valeur unique — garde deux lignes au plus, puis se tronque ;
   il se lit entier au survol (`title`) ou sur la fiche (§2.6, §3.11 ; patron des cartes du
   planning). Index du parc à 1440 px : une unité occupait jusqu'à 100 px (catégorie sur quatre
   lignes), 49 unités 3 914 px ; deux lignes au plus : 58 px, 3 183 px. Mesurés et écartés : une
   seule ligne à borne fixe (débord de 590 px à 1280) ou à `max-width: 0` (catégorie réduite à
   « BUNG-BU… », la place allant aux colonnes courtes). Revue du bureau du 29/09/2026, arbitrage 5. */
.texte-deux-lignes {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

/* Un montant, une date ou un compte ne se coupent jamais en deux lignes : « 2 088,00 € »
   replié sous son propre symbole double la hauteur de la ligne et casse la densité de
   l'archétype (7px de rembourrage, ~33px de haut). La carte défile au besoin. */
.liste-dense .chiffres {
  white-space: nowrap;
}

/* Colonne de sélection : la plus étroite possible. Sa largeur sert aussi de
   décalage à la colonne épinglée ci-dessous — une seule valeur, jamais deux
   à tenir en phase. */
.liste-dense {
  --largeur-colonne-selection: calc(var(--espace-9) + var(--espace-4));
}

.liste-dense .colonne-selection {
  width: var(--largeur-colonne-selection);
  padding-right: 0;
}

/* Les contrôles d'une ligne passent AU-DESSUS du lien étendu : sans ça, le
   recouvrement `a::after { inset: 0 }` les rendrait inatteignables.
   28/08/2026 — la règle ne visait que les cases à cocher et les formulaires. Un champ de
   SAISIE posé dans une cellule (le montant affecté d'un règlement) tombait dessous : on
   cliquait dedans, la fiche s'ouvrait. Elle vise maintenant tout contrôle de formulaire. */
.liste-dense tbody td > input,
.liste-dense tbody td > select,
.liste-dense tbody td > textarea,
.liste-dense tbody td > form {
  position: relative;
  z-index: 1;
}

/* Défilement horizontal de la carte (mobile) : l'identifiant reste visible,
   sinon on défile sans savoir de quelle ligne il s'agit. La case de sélection
   l'accompagne — la retirer casserait le geste de masse là où il sert le plus
   (socle commun, « colonne identifiant épinglée »). */
/* Le comportement en petit écran (colonnes épinglées, chips défilantes) vit
   dans l'unique seuil 900px de la feuille, plus bas : un seuil de largeur ne se
   déclare qu'une fois, `petits-ecrans.test.ts` en tient la liste exacte. */

/* Pied paginé — « N à M sur T » à gauche, les pas de page à droite. Variante
   du pied de liste : les autres index gardent le pied simple. */
.pied-de-liste--pagine {
  display: flex;
  align-items: center;
  gap: var(--espace-4);
  flex-wrap: wrap;
}

.pied-de-liste--pagine .ressort {
  flex: 1;
}

/* Un pied posé à même un panneau (index clients : le panneau colle sa table aux bords) reprend le
   retrait horizontal des surfaces (§1.6) : « 1 à 19 sur 19 » touchait la bordure (x = 233 px contre
   249 px aux autres index). Le panneau n'est pas changé en carte : son rembourrage aurait resserré
   la table à colonnes fixes de 32 px, déjà au bord à 1280 (revue du bureau du 29/09/2026,
   arbitrage 6). */
.panneau > .pied-de-liste {
  padding: 0 var(--espace-7) var(--espace-5);
}

.pagination {
  display: flex;
  gap: var(--espace-2);
}

/* ---------------------------------------------------------------------------
 * ÉCRANS DU TERRAIN — convention `C-34` (contrat Contrôles du 27/08/2026)
 *
 * Quatre écrans sont ouverts dehors, au téléphone, une main libre : planning de
 * livraison et de reprise, stock, Contrôles, fiche de contrôle. Sur eux, l'action
 * de ligne EST le but de la visite : elle ne part jamais dans un défilement
 * horizontal. La classe `.liste-terrain` les marque ; les autres files restent
 * des écrans de bureau et gardent le défilement de carte.
 * ------------------------------------------------------------------------ */

/* Cellule d'actions : les gestes d'une ligne, alignés à droite, jamais empilés
   au bureau. Les formulaires perdent leur marge de bloc — ce sont des boutons. */
.cellule-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--espace-2);
  white-space: nowrap;
}

/* Dans un tableau, la cellule d'actions RESTE une cellule (22/09/2026, recette de Yanis) : en
   `display: flex`, elle cessait d'être une cellule de tableau, ne s'étirait plus sur la hauteur
   de la ligne et laissait son bouton collé en haut dès qu'une autre cellule passait sur deux
   lignes. Le texte est aligné à droite, les gestes restent des boîtes en ligne. */
td.cellule-actions {
  display: table-cell;
  text-align: right;
  vertical-align: middle;
}

td.cellule-actions > * + * {
  margin-left: var(--espace-1);
}

.cellule-actions form {
  display: inline-flex;
  align-items: center;
  gap: var(--espace-2);
  margin: 0;
}

/* ---------------------------------------------------------------------------
 * FICHE DU PASSAGE AU TÉLÉPHONE — bloc T1, décisions de Yanis du 28/09/2026
 * (docs/specs/2026-09-28-fiche-passage-telephone.md). Ce qui sert à arriver, la tâche, puis
 * l'accès ; l'action principale est collée au bas de l'écran. Au doigt, aucune cible sous
 * 44px et aucun texte utile sous 14px : la page est lue dehors, au téléphone.
 * ------------------------------------------------------------------------ */

/* Retour à la tournée : un lien discret au-dessus du titre, pas un bouton sur sa rangée. */
.entete-page > .retour-tournee {
  display: inline-flex;
  flex-basis: 100%;
  align-items: center;
  gap: var(--espace-2);
  min-height: var(--hauteur-cible-tactile);
  color: var(--texte-secondaire);
  font-size: var(--taille-cellule);
}

/* Pastilles et mentions de la fiche : 14px, la taille des cellules, et non celle des tableaux
   denses du bureau. `:has` borne la règle à cette page sans classe sur `main`. */
main:has(.barre-passage) .badge {
  font-size: var(--taille-cellule);
}

.resume-passage p {
  margin: 0 0 var(--espace-3);
}

.resume-passage-etat {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--espace-3);
}

.resume-passage-gestes {
  display: flex;
  flex-wrap: wrap;
  gap: var(--espace-3);
  margin-top: var(--espace-5);
}

.resume-passage-gestes .bouton {
  flex: 1 1 auto;
  justify-content: center;
  height: var(--hauteur-cible-tactile);
}

/* Unités : la case est DANS le libellé, toute la ligne coche (arbitrage 12). */
.unites-passage {
  margin: 0;
  padding: 0;
  list-style: none;
}

.unites-passage > li {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--espace-1) var(--espace-4);
  min-height: var(--hauteur-cible-tactile);
  padding: var(--espace-2) 0;
  border-bottom: 1px solid var(--bordure-surface);
}

.unites-passage > li:last-child {
  border-bottom: 0;
}

.unites-passage > li > label {
  display: flex;
  flex: 1;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--espace-1) var(--espace-4);
  min-height: var(--hauteur-cible-tactile);
  cursor: pointer;
}

.unites-passage input[type='checkbox'] {
  width: 22px;
  height: 22px;
  margin: 0;
}

.unite-passage-code {
  font-family: var(--police-chiffres);
  font-weight: var(--graisse-forte);
}

/* La catégorie passe TOUJOURS sous le code (finition du 28/09/2026) : selon sa longueur, elle
   tenait sur la ligne du code ou tombait dessous, et deux unités voisines n'avaient pas le même
   dessin. Sous une case, elle s'aligne sur le code et non sur la case. */
.unite-passage-categorie {
  flex-basis: 100%;
  color: var(--texte-secondaire);
}

.unites-passage > li > label .unite-passage-categorie {
  padding-left: calc(22px + var(--espace-4));
}

.unite-passage-confirmee {
  flex-basis: 100%;
  color: var(--succes-texte-fort);
  font-size: var(--taille-cellule);
}

/* Photos de l'unité, dans sa ligne (lot 2 du 28/09/2026, spec 2026-09-28-photo-par-unite-telephone,
   arbitrages 1 et 3) : la liste sous l'unité, puis « Ajouter une photo ». Le champ fichier couvre
   le bouton et reste invisible — le motif du bouton d'appareil de la fiche de contrôle. */
.unites-passage > li > section {
  flex-basis: 100%;
  padding-bottom: var(--espace-2);
}

.unites-passage > li > section > ul {
  margin: 0 0 var(--espace-3);
  padding: 0;
  list-style: none;
}

.unites-passage > li > section > ul > li {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--espace-1) var(--espace-3);
  padding: var(--espace-2) 0;
  border-bottom: 1px solid var(--separateur-interne);
  color: var(--texte-secondaire);
  font-size: var(--taille-cellule);
}

.unites-passage > li > section > ul > li > a {
  flex-basis: 100%;
}

.unites-passage > li > section > ul > li > form {
  margin: 0 0 0 auto;
}

/* Le bouton garde sa largeur de libellé : deux boutons pleine largeur par unité rivalisaient
   avec l'action principale de la barre collée. */
.depot-photo {
  display: flex;
  flex-flow: row wrap;
  align-items: center;
  gap: var(--espace-3);
  margin: 0;
}

.depot-photo-bouton {
  position: relative;
  flex: none;
  gap: var(--espace-2);
  width: auto;
  height: var(--hauteur-cible-tactile);
  margin: 0;
  cursor: pointer;
}

.depot-photo-bouton > input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  margin: 0;
  padding: 0;
  opacity: 0;
  cursor: pointer;
}

.depot-photo-bouton:has(> input:focus-visible) {
  outline: 2px solid var(--anneau-focus);
  outline-offset: 2px;
}

/* Lu par les lecteurs d'écran, jamais affiché : le code de l'unité dans le nom du bouton. */
.visuellement-masque {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Date et heure effectives repliées (RG-TER-07) : toujours envoyées, dépliées pour changer. */
.heure-effective {
  margin-top: var(--espace-5);
}

.heure-effective summary {
  display: flex;
  align-items: center;
  gap: var(--espace-2);
  min-height: var(--hauteur-cible-tactile);
  color: var(--texte-secondaire);
  font-size: var(--taille-cellule);
  cursor: pointer;
}

.heure-effective label {
  display: block;
  margin-top: var(--espace-3);
}

/* La valeur, puis le geste facultatif en couleur de lien (lot 2, arbitrage 12). */
.heure-effective-texte {
  display: flex;
  flex-direction: column;
  padding: var(--espace-1) 0;
}

.heure-effective-changer {
  color: var(--texte-accent);
  text-decoration: underline;
}

.aide-passage {
  margin: var(--espace-3) 0 0;
  color: var(--texte-secondaire);
  font-size: var(--taille-cellule);
}

.terminer-partiel {
  margin-top: var(--espace-6);
}

/* Bloc Accès resserré (finitions du 29/09/2026, arbitrage 4) : chaque rubrique colle à sa valeur
   — le paragraphe perd sa marge de 1em —, et une note porte son auteur et son bouton-icône
   « Modifier » sur une même rangée, sous son texte. Même contenu, mêmes rubriques. */
.acces-passage h3 {
  margin: var(--espace-5) 0 var(--espace-1);
  font-size: var(--taille-base);
}

.acces-passage h2 + h3 {
  margin-top: 0;
}

.acces-passage h3 + p {
  margin: 0;
}

.note-acces-texte {
  flex-basis: 100%;
}

/* Les listes de la fiche du passage (notes d'accès, documents, signalements, remises) prennent
   le dessin des unités : des lignes séparées, sans puce (finition du 28/09/2026). Une remise
   garde son bouton sur la ligne de sa désignation quand la place le permet. */
.liste-passage {
  margin: 0;
  padding: 0;
  list-style: none;
}

.liste-passage > li {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--espace-2) var(--espace-4);
  padding: var(--espace-3) 0;
  border-bottom: 1px solid var(--bordure-surface);
}

.liste-passage > li:last-child {
  border-bottom: 0;
}

/* La désignation passe sur deux lignes plutôt que de pousser son bouton dessous : toutes les
   remises gardent le même dessin, quelle que soit la longueur du libellé. */
.liste-passage--remises > li {
  flex-wrap: nowrap;
  justify-content: space-between;
}

.liste-passage details {
  flex-basis: 100%;
}

/* « Suggestions : » sur sa ligne, les choix dessous en rangée (dialog d'échec, TER-06). */
.suggestions-echec {
  display: flex;
  flex-wrap: wrap;
  gap: var(--espace-3);
}

.suggestions-echec > span {
  flex-basis: 100%;
  color: var(--texte-secondaire);
}

/* Barre collée au bas de l'écran (arbitrage 5) : « Autres actions » à gauche, l'action
   principale à droite. La zone sûre du bas garde les boutons hors de la barre du téléphone. */
.barre-passage {
  position: sticky;
  bottom: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--espace-5);
  padding: var(--espace-4) var(--espace-5) calc(var(--espace-4) + env(safe-area-inset-bottom));
}

.barre-passage > form {
  display: flex;
  justify-content: flex-end;
  margin: 0;
}

.barre-passage button {
  height: var(--hauteur-cible-tactile);
}

/* À 375px, « Autres actions » et « Confirmer les unités cochées » ne tiennent pas côte à côte sur
   une ligne chacun : l'action principale prend la place qui reste et passe sur deux lignes plutôt
   que de sortir de la carte (le `nowrap` général des boutons l'y pousserait). */
.barre-passage > .dialogue-hote > button {
  flex: none;
  padding: 0 var(--espace-4);
}

.barre-passage .primaire {
  flex: 1 1 auto;
  justify-content: center;
  min-width: 0;
  padding: 0 var(--espace-4);
  line-height: 1.2;
  text-align: center;
  white-space: normal;
}

.barre-passage > form {
  flex: 1 1 auto;
  min-width: 0;
}

/* « Autres actions » : une colonne de gestes pleine largeur, groupés sous un intertitre. */
.autres-actions {
  display: flex;
  flex-direction: column;
  gap: var(--espace-3);
}

.autres-actions h3 {
  margin: var(--espace-5) 0 0;
  color: var(--texte-secondaire);
  font-size: var(--taille-cellule);
}

.autres-actions h3:first-child {
  margin-top: 0;
}

.autres-actions button[aria-haspopup='dialog'],
.autres-actions > a.bouton {
  justify-content: flex-start;
  width: 100%;
  height: var(--hauteur-cible-tactile);
}

/* ---------------------------------------------------------------------------
 * CARTES DE PASSAGE — tournée et passages passés au téléphone, bloc T3, décisions de Yanis du
 * 28/09/2026 (docs/specs/2026-09-28-tournee-telephone.md). Une carte par passage, touchable en
 * entier ; la pastille dit l'état en couleur ; aucun texte utile sous 14px.
 * ------------------------------------------------------------------------ */

/* Jours voisins sur une rangée (arbitrage 7) : deux boutons-icônes carrés au doigt. */
.jours-tournee {
  display: flex;
  flex-basis: 100%;
  flex-wrap: wrap;
  gap: var(--espace-3);
}

.bouton-icone {
  justify-content: center;
  width: var(--hauteur-cible-tactile);
  height: var(--hauteur-cible-tactile);
  padding: 0;
}

/* Lien étendu (arbitrage 2, même mécanisme que les lignes de tableau, C-42) : le lien du titre
   couvre la carte, il reste le seul élément interactif. Le chevron réserve sa colonne à droite. */
.carte-passage {
  position: relative;
  padding-right: calc(var(--espace-5) + 28px);
  font-size: var(--taille-cellule);
}

.carte-passage-titre a::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
}

.carte-passage:has(a:hover) {
  background: var(--surface-survol-bouton);
}

.carte-passage-titre a:focus-visible {
  outline: none;
}

.carte-passage-titre a:focus-visible::after {
  outline: 2px solid var(--texte-accent);
  outline-offset: -2px;
}

.carte-passage-chevron {
  position: absolute;
  top: 50%;
  right: var(--espace-4);
  color: var(--texte-secondaire);
  transform: translateY(-50%);
}

.carte-passage p {
  margin: 0 0 var(--espace-2);
}

.carte-passage-tete {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--espace-2) var(--espace-4);
  color: var(--texte-secondaire);
}

.carte-passage-titre {
  margin: var(--espace-2) 0;
}

.carte-passage .badge {
  font-size: var(--taille-cellule);
}

.carte-passage-date {
  color: var(--texte-principal);
  font-weight: var(--graisse-forte);
}

/* Unités en lignes qui se replient (arbitrage 1) : plus de pastille qui sort de la carte. */
.carte-passage-unites {
  margin: var(--espace-3) 0 0;
  padding: 0;
  list-style: none;
}

.carte-passage-unites li {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--espace-3);
  padding: var(--espace-2) 0;
  border-top: 1px solid var(--bordure-surface);
}

.unite-passage-a-faire {
  flex-basis: 100%;
  color: var(--texte-secondaire);
}

/* Couleur d'état (arbitrage 4, référence §1.3) : planifiée et en file restent neutres. Classes
   propres au terrain : `.etat-confirmee` globale colorerait aussi le panneau de la Logistique. */
.passage-etat--en_cours {
  background: var(--attention-fond);
  color: var(--attention-texte-fort);
}

.passage-etat--confirmee {
  background: var(--succes-fond);
  color: var(--succes-texte-fort);
}

.passage-etat--echec {
  background: var(--danger-fond);
  color: var(--danger-texte);
}

/* ===========================================================================
 * BADGES DE COMPTEUR DU MENU — bloc 3, spec 2026-08-26-badges-compteur-menu
 *
 * Chaque entrée du menu s'enveloppe d'un `.nav-entree` : le lien prend le
 * ressort, le badge se pose à droite. Les `<a>` gardent leur padding et leur
 * rayon — une entrée sans badge se rend exactement comme avant.
 * ======================================================================== */

.barre-laterale nav .nav-entree {
  display: flex;
  align-items: center;
}

.nav-entree > a {
  flex: 1;
  min-width: 0;
}

/* Badge neutre (volume) : le fond du prototype — blanc à 10 % d'opacité —
   EST le jeton `--menu-bordure`, réutilisé tel quel. */
.nav-compteur {
  font-family: var(--police-chiffres);
  font-size: var(--taille-micro);
  font-weight: var(--graisse-forte);
  padding: 0 var(--espace-2);
  border-radius: var(--rayon-pastille);
  background: var(--menu-bordure);
  color: var(--menu-texte);
  margin-right: var(--espace-5);
}

/* Badge « attente » : l'accent nu ne fait que 4,02:1 avec le blanc, sous
   l'AA 4,5:1 — assombri en `--texte-accent-survol` (5,54:1), arbitrage 2
   de la spec. */
.nav-compteur--attente {
  background: var(--texte-accent-survol);
  color: var(--texte-inverse);
}

/* ===========================================================================
 * FAMILLES REPLIÉES — spec 2026-09-14-menu-familles-repliees
 *
 * Chaque famille du menu est un `<details class="nav-famille">` natif dont le
 * `<summary class="nav-groupe-titre">` garde la typographie du titre de groupe
 * (motif 1). Le badge cumulé se cale à droite du titre et disparaît quand la
 * famille est ouverte : chaque entrée montre alors le sien. Aucun JavaScript.
 * Marqueur natif et curseur : règle globale `summary`, section motif 5.
 * ======================================================================== */

/* L'écart au-dessus d'une famille vit sur le `details` : le motif 1 le portait dans
   le rembourrage haut du titre, conçu pour un titre inerte. */
.barre-laterale nav .nav-famille {
  display: flex;
  flex-direction: column;
  margin-top: var(--espace-3);
}

/* Les entrées dépliées vivent dans le conteneur natif du contenu, pas dans le
   `details` : l'écart entre elles s'y pose, avec la valeur de `.barre-laterale nav`. */
.barre-laterale nav .nav-famille::details-content {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

/* Rembourrage symétrique : le titre est un contrôle, sa zone de clic encadre le texte. */
.nav-famille > .nav-groupe-titre {
  display: flex;
  align-items: center;
  gap: var(--espace-2);
  padding: var(--espace-2) var(--espace-4);
}

.barre-laterale nav .nav-famille > .nav-groupe-titre:hover {
  background: var(--menu-fond-actif);
  color: var(--menu-texte-actif);
  border-radius: var(--rayon-controle);
}

.nav-famille > .nav-groupe-titre .chevron {
  display: inline-flex;
  flex: none;
}

/* Le badge d'une entrée finit à `--espace-5` du bord du nav ; celui du titre part déjà
   du rembourrage droit `--espace-4` : sa marge est la différence. */
.nav-famille > .nav-groupe-titre .nav-compteur {
  margin-left: auto;
  margin-right: calc(var(--espace-5) - var(--espace-4));
}

.nav-famille[open] > .nav-groupe-titre .nav-compteur {
  display: none;
}

/* ===========================================================================
 * FILE DE TRAVAIL (archétype 3) — bloc 5, spec 2026-08-26-file-de-travail
 *
 * Trois motifs partagés par les quatre files (atelier, relances, factures,
 * vidanges). Correction 1 de l'archétype : la COULEUR au badge d'échéance,
 * la valeur reste neutre. Valeurs 100 % en jetons.
 * ======================================================================== */

/* Badge d'échéance — neutre par défaut, la variante porte la sémantique. */
.badge-echeance {
  display: inline-block;
  padding: 2px 9px;
  border-radius: var(--rayon-pastille);
  background: var(--neutre-fond);
  color: var(--neutre-texte);
  font-family: var(--police-chiffres);
  font-size: var(--taille-pastille);
  font-weight: var(--graisse-forte);
  white-space: nowrap;
}

.badge-echeance--attention {
  background: var(--attention-fond);
  color: var(--attention-texte);
}

.badge-echeance--depassee {
  background: var(--danger-fond);
  color: var(--danger-texte);
}

/* Bandeau sous-ligne et rail latéral : prévus par l'archétype, PURGÉS avant
   commit — aucun des quatre écrans réels ne rend de bandeau d'alerte sous une
   ligne ni de contenu latéral (spec bloc 5, arbitrage 2 : une règle sans
   usage ne reste pas). Ils se reposeront avec le premier écran qui en a. */

/* ===========================================================================
 * FICHE ENTITÉ (archétype 4) — bloc 6, spec 2026-08-26-fiches-entite
 * ======================================================================== */

/* ---------------------------------------------------------------------------
 * ONGLETS, INDICATEURS ET TIROIR — contrat Fiche client du 27/08/2026
 *
 * Les onglets sont des LIENS (l'état vit dans l'URL, `C-38`), les indicateurs ne
 * portent que des valeurs déjà lues ailleurs (`C-39`), et le journal quitte
 * l'empilement pour un tiroir droit.
 *
 * 28/08/2026 — un SECOND jeu de règles `.onglets` / `.onglet` vivait plus haut dans cette
 * feuille depuis le 24/08, pour des onglets à `aria-selected`. Aucun des 112 gabarits ne
 * pose cet attribut : ces règles n'habillaient rien, et celles d'ici les écrasaient déjà
 * par simple ordre de déclaration. Elles sont supprimées. `aria-selected` appartient
 * d'ailleurs à un `tablist` ; un onglet qui est un LIEN porte `aria-current="page"`.
 * ------------------------------------------------------------------------ */

.onglets {
  display: flex;
  gap: var(--espace-2);
  margin-bottom: var(--espace-6);
  border-bottom: 1px solid var(--separateur-interne);
  /* Hérité du bloc du 24/08 supprimé le 28/08 : sous 390 px, cinq onglets ne tiennent pas
     dans la largeur. Ils défilent, ils ne se replient pas. */
  overflow-x: auto;
}

.onglet {
  display: inline-flex;
  align-items: center;
  gap: var(--espace-2);
  height: var(--hauteur-controle);
  padding: 0 var(--espace-5);
  border-radius: var(--rayon-controle) var(--rayon-controle) 0 0;
  color: var(--texte-secondaire);
  font-size: var(--taille-controle);
  font-weight: var(--graisse-forte);
  text-decoration: none;
  white-space: nowrap;
}

.onglet:hover {
  background: var(--surface-accentuee);
}

/* L'onglet actif : fond sombre, comme la chip active — une seule sémantique de
   sélection dans tout le produit. */
.onglet--actif,
.onglet--actif:hover {
  background: var(--fond-inverse);
  color: var(--texte-inverse);
}

.onglet-compteur {
  font-family: var(--police-chiffres);
  font-weight: var(--graisse-normale);
}

/* Compteur d'un onglet qui porte du retard (coque CRM, 28/08/2026) : le nombre passe en
   rouge. Sur l'onglet ACTIF, le fond est déjà sombre — le rouge y serait illisible, et
   l'onglet ouvert n'a de toute façon plus besoin de s'annoncer : il rend la couleur. */
.onglet-compteur--retard {
  color: var(--danger-texte-fort);
}

.onglet--actif .onglet-compteur--retard {
  color: inherit;
}

/* Bandeau d'indicateurs : quatre colonnes au socle, trois ici — la grille se
   remplit de ce qu'on lui donne. */
.indicateurs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--espace-4);
  margin-bottom: var(--espace-6);
}

.indicateur {
  display: flex;
  flex-direction: column;
  gap: var(--espace-1);
  padding: var(--espace-5) var(--espace-7);
  background: var(--surface);
  border: 1px solid var(--bordure-surface);
  border-radius: var(--rayon-controle);
}

.indicateur-libelle {
  font-size: var(--taille-entete-colonne);
  font-weight: var(--graisse-forte);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--texte-attenue);
}

.indicateur-valeur {
  font-family: var(--police-chiffres);
  font-size: var(--taille-titre-section);
  font-weight: var(--graisse-forte);
}

/* Blocs de chiffres d'une rangée (`partials/blocs-chiffres`) : un libellé sur deux ou trois
   lignes faisait tomber le chiffre à trois hauteurs dans la même rangée (accueil, 1440 px :
   186 / 203 / 220 px). Le libellé prend la hauteur libre : les blocs d'une rangée ont la même
   hauteur, leurs chiffres s'alignent en bas (revue du bureau du 29/09/2026, arbitrage 2). */
a.indicateur .indicateur-libelle {
  flex-grow: 1;
}

/* Bloc de chiffres cliquable (`partials/blocs-chiffres.eta`, spec 2026-09-15-refonte-ecrans,
   E1, E3, arbitrage 18). Le bloc est un LIEN vers la liste filtrée : il garde la couleur de
   texte de la carte, et le survol ne change que le fond (§3.5). La gravité se lit sur le
   chiffre, en texte fort de la famille sémantique (§1.3) — jamais par un aplat (§3.4), ni par
   une bordure d'accent que la carte cliquable du prototype n'a pas (§2.5). À 0, le bloc est
   grisé mais reste cliquable. */
a.indicateur {
  color: var(--texte-principal);
  text-decoration: none;
}

a.indicateur:hover {
  color: var(--texte-principal);
  text-decoration: none;
  background: var(--surface-survol-bouton);
}

.indicateur-montant {
  font-family: var(--police-chiffres);
  font-size: var(--taille-note);
  color: var(--texte-secondaire);
}

.indicateur--danger .indicateur-valeur {
  color: var(--danger-texte-fort);
}

.indicateur--avertissement .indicateur-valeur {
  color: var(--attention-texte-fort);
}

.indicateur--neutre .indicateur-valeur,
.indicateur--neutre .indicateur-montant {
  color: var(--neutre-texte);
}

/* Tiroir droit — consultation d'appoint : on ne quitte pas la fiche pour lire
   qui a changé quoi. Le déclencheur vit dans la ligne de titre ; le tiroir se
   pose au-dessus de tout. Alpine ne porte que l'ouverture, un état éphémère. */
.tiroir-voile {
  position: fixed;
  inset: 0;
  z-index: 20;
  background: var(--voile-modale);
}

.tiroir {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 21;
  width: 400px;
  max-width: 100%;
  padding: var(--espace-7);
  overflow-y: auto;
  background: var(--surface);
  border-left: 1px solid var(--bordure-surface);
}

.tiroir-tete {
  display: flex;
  align-items: center;
  gap: var(--espace-3);
  margin-bottom: var(--espace-5);
}

/* Le ressort n'a AUCUNE règle globale : il ne pousse que là où une règle le dit
   (`.entete-page`, `.barre-superieure`, le pied paginé, et ici). Sans celle-ci, la
   croix de fermeture se collait à gauche. */
.tiroir-tete .ressort {
  flex: 1;
}

.tiroir-tete h2 {
  margin: 0;
}

/* ===========================================================================
 * ÉDITEUR DE LIGNES (archétype 5) — bloc 7, spec 2026-08-26-documents-a-lignes
 * ======================================================================== */

/* Cellule calculée : verrouillage VISUEL d'une valeur que RG-GEN-05 calcule
   côté serveur — la valeur se lit comme un fait, pas comme une saisie. */
.cellule-calculee {
  background: var(--surface-secondaire);
  color: var(--texte-secondaire);
  font-family: var(--police-chiffres);
}

/* Carte de totaux collante dans l'aside : elle suit le défilement de la
   table de lignes (archétype 5 — HT / TVA / TTC toujours sous les yeux). */
.carte--collante {
  position: sticky;
  top: var(--espace-6);
}

/* ===========================================================================
 * VUE PÉRIODE (archétype 6) — bloc 8, spec 2026-08-26-periode-et-parametrage
 *
 * Le « disponible certain » en gros chiffre coloré : la seule frontière que
 * la donnée porte d'elle-même — positif = succès, négatif = `.alerte`
 * existante. Aucun seuil « faible » n'est calculé (rien d'inventé).
 * ======================================================================== */
.chiffre-synthese--succes {
  color: var(--succes-texte-fort);
}

/* ---------------------------------------------------------------------------
 * Écran de connexion — la seule page sans coque d'application
 * (contrat `design-reference/ecrans/connexion.md`, tranché le 02/09/2026)
 *
 * Tout est repris des jetons existants : la carte est `.carte`, la marque `.marque`. Ces
 * règles ne font que les POSER au centre d'une page vide, ce que la coque `main` ne savait
 * pas faire — elle collait le formulaire en haut à gauche, pleine largeur.
 * ------------------------------------------------------------------------ */

.page-publique {
  display: flex;
  min-height: 100vh;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: var(--espace-6);
  gap: var(--espace-5);
}

/* La marque au-dessus de la carte : sur cet écran, personne ne sait encore où il est —
   c'était le seul du produit à ne jamais le nommer. `.marque` est réglée pour la barre
   latérale (couleur de menu, retrait horizontal) : ces deux lignes la ramènent au centre
   d'un fond clair, sans toucher à la règle d'origine. */
.marque--publique {
  margin: 0;
  padding: 0;
  color: var(--texte-principal);
}

/* Une largeur BORNÉE : le formulaire porte deux champs, pleine largeur il est illisible. */
.carte--publique {
  width: 100%;
  max-width: 24rem;
}

.carte--publique h1 {
  margin: 0 0 var(--espace-5);
}

.carte--publique form {
  display: flex;
  flex-direction: column;
  gap: var(--espace-2);
}

.carte--publique button[type='submit'] {
  margin-top: var(--espace-4);
}

/* Le libellé colle à SON champ et s'écarte du précédent (finition du 28/09/2026) : la marge
   générique des libellés l'en éloignait de 20px quand 6px le séparaient du champ d'au-dessus. */
.carte--publique form label {
  margin-bottom: 0;
}

.carte--publique form label ~ label {
  margin-top: var(--espace-5);
}

/* Le champ de mot de passe et son bouton d'affichage sur la même ligne. Le bouton ne
   s'étire pas : il fait la hauteur du champ, et le champ prend le reste. */
.champ-mot-de-passe {
  display: flex;
  align-items: stretch;
  gap: var(--espace-2);
}

.champ-mot-de-passe input {
  flex: 1;
  min-width: 0;
}

/* Bouton de service — il agit sur l'affichage, jamais sur les données : il ne prend donc ni
   la couleur d'une action primaire, ni le poids d'une action secondaire. */
.bouton-discret {
  border: 1px solid var(--bordure-bouton);
  border-radius: var(--rayon-controle);
  background: var(--surface);
  color: var(--texte-secondaire);
  cursor: pointer;
  font-family: inherit;
  font-size: var(--taille-cellule);
  padding: 0 var(--espace-3);
  white-space: nowrap;
}

.bouton-discret:hover {
  background: var(--surface-survol-bouton);
}

/* ---------------------------------------------------------------------------
 * Boîte de dialogue — patron commun (`partials/dialogue.eta`, spec
 * 2026-09-15-refonte-ecrans, R2 et §3.8). `<dialog>` natif ouvert par `showModal()` : le
 * navigateur le pose dans la couche supérieure, ferme sur Échap et rend le focus au bouton
 * d'ouverture. Aucune classe nouvelle à l'intérieur : l'en-tête et le corps sont ceux du
 * panneau (§2.5). Surface : bordure et rayon, aucune ombre (§3.1).
 * ------------------------------------------------------------------------ */
dialog {
  width: min(560px, calc(100vw - 2 * var(--espace-7)));
  max-height: 86vh;
  overflow: auto;
  padding: 0;
  border: 1px solid var(--bordure-surface);
  border-radius: var(--rayon-surface);
  background: var(--surface);
  color: var(--texte-principal);
}

dialog::backdrop {
  background: var(--voile-modale);
}

/* Le panneau latéral de la Logistique est un dialog (spec 2026-09-28-logistique-kanban,
   arbitrage 7 ; référence §2.10) : collé à droite, pleine hauteur, 380px. Il défait ici ce que
   la règle `dialog` pose (centrage, cadre, rayon) ; son ombre, seule admise (§1.9), vient de
   `.panneau-lateral` et le voile reste le `::backdrop` ci-dessus. */
dialog.panneau-lateral {
  inset: 0 0 0 auto;
  margin: 0;
  height: 100dvh;
  max-height: none;
  width: min(380px, 100vw);
  border: none;
  border-left: 1px solid var(--bordure-surface);
  border-radius: 0;
}

/* Le détail se sépare du formulaire « Planifier la reprise » qui le suit : sans cet écart, la
   légende du fieldset touchait la dernière ligne (capture du 28/09). */
dialog.panneau-lateral dl {
  margin: 0 0 var(--espace-5);
}

/* La croix de fermeture de l'en-tête (B1, recette du 18/09/2026) : un carré de la hauteur d'un
   contrôle, calé à droite par le `space-between` de `.panneau-entete`. */
.dialogue-fermer {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--hauteur-controle);
  height: var(--hauteur-controle);
  padding: 0;
  flex-shrink: 0;
}

/* Recette du 25/09, spec 2026-09-28-fil-d-ariane (arbitrage 12) : le fieldset qui porte le pied
   d'un dialog ne redessine pas la carte du formulaire (§2.8) dans celle du dialog — le pied
   était rentré de 17 px, 29 px de vide dessous. Sa légende reste en micro-libellé. */
dialog fieldset:has(> .barre-actions) {
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
}

dialog fieldset:has(> .barre-actions) > legend {
  padding: 0;
  margin-bottom: var(--espace-4);
}

/* ---------------------------------------------------------------------------
 * Recette 18/09 — voie contrat (B7). Un dialog ouvert depuis une cellule reste son DESCENDANT
 * dans le DOM : il héritait du `white-space: nowrap` de `.cellule-actions`, et ses formulaires
 * de son `display: inline-flex` — tous les champs sur une rangée, boutons hors de la fenêtre
 * (mesuré sur « Modifier… » d'une ligne de contrat : 977 px de contenu pour 558 visibles).
 * Recette du 23/09 (r06) : il héritait aussi du `text-align: right` de `td.cellule-actions`
 * (22/09) — mention du prix décalée de 176 px, « Supprimer la ligne » collé au bord droit.
 * ------------------------------------------------------------------------ */
.cellule-actions dialog {
  text-align: start;
  white-space: normal;
}

.cellule-actions dialog form {
  display: block;
}

/* « Modifier… » d'une ligne de contrat : deux ou trois champs par rangée tiennent sans défiler. */
dialog:has(.formulaire-ligne-contrat) {
  width: min(720px, calc(100vw - 2 * var(--espace-7)));
}

/* Un geste destructeur dans le pied d'un dialog (« Supprimer la ligne » d'un contrat ou d'un
   devis) : à part, calé à gauche, loin de l'engagement — le pied reste la dernière rangée du
   corps, comme au patron (finitions du 29/09/2026, arbitrage 6). */
.barre-actions > .geste-a-part {
  margin-right: auto;
}

/* L'hôte Alpine du bouton et de son dialog ne dessine rien : dans une cellule ou un en-tête de
   panneau, le bouton se range comme s'il était posé seul. */
.dialogue-hote {
  display: contents;
}

/* ---------------------------------------------------------------------------
   Lot L1a — refonte des écrans (docs/specs/2026-09-15-refonte-ecrans.md)
   --------------------------------------------------------------------------- */

/* Bouton d'ouverture d'un dialog posé dans une ligne cliquable (R3) : il passe AU-DESSUS du
   lien étendu `td > a::after`, comme les formulaires qu'il remplace. Son hôte est en
   `display: contents` : la règle `td > form` ne l'atteint pas, et le recouvrement positionné de
   la ligne se peignait par-dessus — mesuré sur `degats-liste`, où le clic sur « Intégrer au
   contrat… » ouvrait la fiche de l'unité. Même règle pour les lots L2c et L2f (« Suspendre… »,
   résumés de cellule de la fiche relance) : une seule copie. */
.liste-dense tbody td > .dialogue-hote > button {
  position: relative;
  z-index: 1;
}

/* ---------------------------------------------------------------------------
   Planning sur deux semaines (docs/specs/2026-09-21-planning-tableau-tournees.md, arbitrage 14 ;
   reprise de la recette du 22/09/2026, docs/specs/2026-09-22-planning-logistique.md)
   --------------------------------------------------------------------------- */

/* Deux rangées de cinq jours ouvrés (recette du 22/09/2026). « À planifier » se range à droite quand
   la place le permet (au-delà de ~1 400 px de contenu), dessous sinon : à 1 440 px, la colonne
   latérale ne laissait que 130 px par jour. Sans `@media` : c'est la base du tableau qui décide
   du passage à la ligne, et les colonnes défilent dans leur propre conteneur quand la largeur
   manque. */
.planning-quinzaine {
  display: flex;
  flex-wrap: wrap;
  gap: var(--espace-5);
  align-items: flex-start;
}

.planning-quinzaine-tableau {
  flex: 999 1 1100px;
  min-width: 0;
  overflow: hidden;
}

.planning-quinzaine-a-planifier {
  flex: 1 1 260px;
}

.planning-quinzaine-defilant {
  overflow-x: auto;
}

.planning-quinzaine-grille {
  display: grid;
  grid-template-columns: repeat(5, minmax(140px, 1fr));
}

.colonne-jour {
  min-width: 0;
  min-height: 132px;
  padding: var(--espace-3);
  border-right: 1px solid var(--separateur-interne);
  border-bottom: 1px solid var(--separateur-interne);
}

.colonne-jour:nth-child(5n) {
  border-right: none;
}

.colonne-jour:nth-child(n + 6) {
  border-bottom: none;
}

.colonne-jour--aujourdhui {
  background: var(--surface-accentuee);
}

.colonne-jour-titre {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--espace-2);
  margin: 0 0 var(--espace-3);
  font-size: var(--taille-note);
  font-weight: var(--graisse-forte);
  color: var(--texte-secondaire);
}

.colonne-jour--aujourdhui .colonne-jour-titre {
  color: var(--texte-accent);
}

.colonne-jour-cartes {
  display: flex;
  flex-direction: column;
  gap: var(--espace-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Sous le tableau, « À planifier » a toute la largeur : ses cartes s'y rangent en grille. */
.planning-quinzaine-a-planifier .colonne-jour-cartes {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}

/* Une carte : bouton qui ouvre sa fenêtre, lien vers sa fiche, ou retour attendu. Le bouton
   hérite de la règle `button` (hauteur de contrôle fixe, contenu centré) : sans les remises à
   zéro ci-dessous, la carte était rognée à 32 px et son texte invisible (recette du 22/09).
   Chaque ligne est tronquée plutôt que repliée : la colonne reste lisible à sa largeur minimale. */
.carte-planning {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 3px;
  width: 100%;
  height: auto;
  padding: var(--espace-3);
  border: 1px solid var(--bordure-surface);
  border-radius: var(--rayon-surface);
  background: var(--surface);
  color: var(--texte-principal);
  font-family: inherit;
  font-size: var(--taille-note);
  font-weight: var(--graisse-normale);
  line-height: 1.3;
  text-align: left;
  text-decoration: none;
  white-space: normal;
  cursor: pointer;
}

/* Survol : la bordure seule, comme le reste de l'application (aucune ombre hors des surfaces
   flottantes, aucune transition hors du chevron : gardes de `src/views/`). */
.carte-planning:hover {
  border-color: var(--texte-attenue);
  background: var(--surface);
  color: var(--texte-principal);
  text-decoration: none;
}

/* Confirmée : un fait constaté, plus un geste à faire. */
.carte-planning--confirmee {
  background: var(--surface-secondaire);
}

.carte-planning--retour {
  cursor: default;
}

.carte-planning--retour:hover {
  border-color: var(--bordure-surface);
}

.carte-planning > span {
  display: block;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Tête et pied passent à la ligne plutôt que de rogner : à 140 px, le créneau et l'état restent
   entiers sous le type et le chauffeur. */
.carte-planning > .carte-planning-tete,
.carte-planning > .carte-planning-pied {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 2px var(--espace-2);
}

/* Le client et chaque catégorie gardent deux lignes avant de se tronquer : ce sont les deux
   informations qu'on cherche en premier sur une carte. */
.carte-planning > .carte-planning-client,
.carte-planning > .carte-planning-contenu {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  white-space: normal;
  overflow-wrap: anywhere;
}

.carte-planning-ordre {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 4px;
  border-radius: 9px;
  background: var(--neutre-fond);
  color: var(--texte-principal);
  font-family: var(--police-chiffres);
  font-size: var(--taille-micro);
  font-weight: var(--graisse-chiffre);
}

.carte-planning-type {
  flex: none;
  font-size: var(--taille-micro);
  font-weight: var(--graisse-forte);
  text-transform: uppercase;
  letter-spacing: var(--interlettrage-capitales);
  color: var(--information-texte);
}

.carte-planning-type--reprise,
.carte-planning-type--retour {
  color: var(--attention-texte);
}

.carte-planning-creneau {
  margin-left: auto;
  font-size: var(--taille-micro);
  color: var(--texte-secondaire);
}

.carte-planning-client {
  font-weight: var(--graisse-forte);
}

.carte-planning-detail {
  color: var(--texte-secondaire);
}

.carte-planning-alerte {
  color: var(--danger-texte);
}

/* Ce qui part ou revient : une ligne par catégorie, la quantité en tête. */
.carte-planning-quantite {
  font-family: var(--police-chiffres);
  font-weight: var(--graisse-chiffre);
}

.carte-planning > .carte-planning-contenu-premier {
  margin-top: 2px;
  padding-top: 4px;
  border-top: 1px solid var(--separateur-interne);
}

.carte-planning-pied {
  margin-top: 2px;
  color: var(--texte-secondaire);
}

.carte-planning-chauffeur {
  flex: 0 1 auto;
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.carte-planning-pied > .badge {
  flex: none;
  margin-left: auto;
  padding: 1px 6px;
  font-size: var(--taille-micro);
}

.carte-planning .dialogue-hote > button {
  align-self: flex-start;
  margin-top: var(--espace-2);
}

/* Cartes d'une colonne de Logistique (spec 2026-09-28-logistique-kanban) : la carte du planning,
   en pile, la date poussée à droite de la tête. */
.colonne-logistique-cartes {
  display: flex;
  flex-direction: column;
  gap: var(--espace-3);
  margin: 0;
  padding: var(--espace-4);
  list-style: none;
}

.carte-logistique-date {
  margin-left: auto;
  font-size: var(--taille-micro);
  color: var(--texte-secondaire);
}

.planning-quinzaine-a-planifier .micro-libelle {
  margin: var(--espace-5) 0 var(--espace-3);
}

/* « À livrer » puis « À récupérer » (spec chauffeur-plus-tard du 22/09/2026, arbitrage 5) : le
   premier groupe colle à l'en-tête du panneau. */
.planning-quinzaine-a-planifier .panneau-corps > section:first-child .micro-libelle {
  margin-top: 0;
}

/* Les deux boutons d'ordre d'un arrêt sont un seul formulaire (R3). */
.ordre-tournee {
  display: flex;
  gap: var(--espace-2);
}

/* ===========================================================================
 * FICHE DE CONTRÔLE — recette du 22/09/2026 (spec 2026-09-22-fiche-controle-refonte)
 *
 * Les points ne sont plus un tableau (arbitrage 13) : une liste par section, une ligne par
 * point. Le libellé et le groupe de saisie (OK / KO / NA, commentaire, appareil photo) se
 * partagent la ligne tant qu'ils y tiennent, puis passent l'un sous l'autre, et le commentaire
 * sous les boutons : c'est `flex-wrap` qui le décide, sans `@media` de plus.
 * ======================================================================== */

.points-controle {
  margin: 0;
  padding: 0;
  list-style: none;
}

.point {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--espace-3) var(--espace-5);
  padding: var(--espace-4) 0;
  border-top: 1px solid var(--separateur-interne);
}

.point:first-child {
  border-top: none;
  padding-top: 0;
}

.point:last-child {
  padding-bottom: 0;
}

.point-libelle {
  flex: 1 1 260px;
  min-width: 0;
  color: var(--texte-principal);
  font-size: var(--taille-cellule);
  overflow-wrap: anywhere;
}

.point-libelle .etiquette {
  margin-left: var(--espace-2);
  vertical-align: 1px;
}

.point-consigne {
  display: block;
  margin-top: 2px;
  color: var(--texte-secondaire);
  /* 14 px et non la note de 13 px (décision du 28/09/2026 : aucun texte utile sous 14 px sur la
     fiche au téléphone). Hors du bloc tactile, qui ne règle que des tailles de cible. */
  font-size: var(--taille-cellule);
}

.point-resultat {
  flex: 0 0 auto;
}

input.point-commentaire,
span.point-commentaire {
  flex: 1 1 180px;
  min-width: 0;
  max-width: none;
  margin: 0;
}

span.point-commentaire {
  color: var(--texte-secondaire);
  font-size: var(--taille-cellule);
}

/* Contrôle segmenté en boutons radio (arbitrage 12) : l'`input` recouvre son segment, invisible,
   pour que le clic, le doigt et le clavier touchent le vrai contrôle. Au repos le texte est
   `--texte-action` et non le `--texte-inactif` du §2.13, illisible (2,2:1) sur blanc. */
.segment:has(> input) {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Largeur fixe : la coche du choix n'élargit pas son segment, la rangée ne bouge pas. */
  width: 60px;
  height: var(--hauteur-controle);
  margin: 0;
  padding: 0 0 0 var(--espace-3);
  border-radius: var(--rayon-controle);
  color: var(--texte-action);
  font-size: var(--taille-controle);
  font-weight: var(--graisse-appuyee);
}

.segment > input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.segment:has(> input:focus-visible) {
  outline: 2px solid var(--anneau-focus);
  outline-offset: 2px;
}

.segment:has(> input:checked) {
  font-weight: var(--graisse-forte);
}

/* La coche : le choix se lit sans la couleur. */
.segment:has(> input:checked)::before {
  content: '';
  position: absolute;
  top: calc(50% - 7px);
  left: 10px;
  width: 5px;
  height: 9px;
  border: solid currentColor;
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}

.segment:has(> input:checked).conforme,
.segment--lu.conforme {
  background: var(--succes-fond);
  border-color: var(--succes-bordure);
  color: var(--succes-texte-fort);
}

.segment:has(> input:checked).non-conforme,
.segment--lu.non-conforme {
  background: var(--danger-fond);
  border-color: var(--danger-bordure);
  color: var(--danger-texte-fort);
}

.segment:has(> input:checked).sans-objet,
.segment--lu.sans-objet {
  background: var(--neutre-fond);
  border-color: var(--bordure-saisie);
  color: var(--neutre-texte);
}

.segment--lu {
  display: inline-flex;
  align-items: center;
  cursor: default;
}

/* Photos d'un point : le bouton appareil photo, puis le nombre de photos. */
.point-photos {
  display: flex;
  flex: 0 0 auto;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--espace-2);
  margin-left: auto;
}

.bouton-photo {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--hauteur-controle);
  height: var(--hauteur-controle);
  margin: 0;
  background: var(--surface);
  border: 1px solid var(--bordure-bouton);
  border-radius: var(--rayon-controle);
  color: var(--texte-action);
}

.bouton-photo:hover {
  background: var(--surface-survol-bouton);
  color: var(--texte-principal);
}

.bouton-photo > input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  margin: 0;
  padding: 0;
  opacity: 0;
  cursor: pointer;
}

.bouton-photo:has(> input:focus-visible) {
  outline: 2px solid var(--anneau-focus);
  outline-offset: 2px;
}

/* Envoi en cours : htmx pose `.htmx-request` sur le formulaire le temps de l'envoi. */
.bouton-photo.htmx-request {
  color: var(--texte-accent);
  animation: pulsation 1.4s ease-in-out infinite;
}

.point-photos-refus {
  flex-basis: 100%;
  margin: 0;
  color: var(--danger-texte);
  font-size: var(--taille-note);
  text-align: right;
}

/* Observations, bilan et validation : les quatre cases de bilan en tuiles. */
.carte-bilan .bilan-choix {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--espace-3);
  padding: 0;
  border: none;
}

/* La légende est ici la QUESTION d'un champ, voisine de « Destination » et « Date
   d'inspection » : elle en prend le dessin, et non celui d'un titre de groupe en capitales
   (finition du 28/09/2026). */
.carte-bilan .bilan-choix legend {
  margin-bottom: var(--espace-3);
  padding: 0;
  font-size: var(--taille-cellule);
  font-weight: var(--graisse-normale);
  text-transform: none;
  letter-spacing: normal;
  color: var(--texte-secondaire);
}

/* L'invitation au constat est une phrase à lire, pas une note (décision du 28/09/2026). Les
   phrases des dialogs du constat aussi. */
#zone-degats .mention,
.constat-du-point .mention {
  font-size: var(--taille-cellule);
}

.carte-bilan .bilan-choix label {
  display: flex;
  align-items: center;
  min-height: var(--hauteur-controle);
  margin: 0;
  padding: var(--espace-3) var(--espace-5);
  border: 1px solid var(--bordure-surface);
  border-radius: var(--rayon-controle);
  color: var(--texte-principal);
  cursor: pointer;
}

.carte-bilan .bilan-choix label:hover {
  background: var(--surface-survol-bouton);
}

.carte-bilan .bilan-choix label:has(input:checked) {
  background: var(--surface-accentuee);
  border-color: var(--texte-accent);
  font-weight: var(--graisse-appuyee);
}

.bilan-destination {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--espace-7);
}

.bilan-destination label {
  flex: 1 1 220px;
}

/* ===========================================================================
 * FICHE DE CONTRÔLE — une section par écran (décision de Yanis du 28/09/2026, spec
 * 2026-09-28-fiche-controle-navigation)
 *
 * La frise est la barre d'onglets existante (arbitrage 7) ; en saisie ses étiquettes sont des
 * BOUTONS du formulaire unique, qui perdent donc le cadre d'un bouton pour garder celui d'un
 * onglet. Aucun composant neuf.
 * ======================================================================== */

.frise-controle button.onglet {
  border: none;
  background: transparent;
  color: var(--texte-secondaire);
  font-weight: var(--graisse-forte);
}

.frise-controle button.onglet:hover {
  background: var(--surface-accentuee);
}

.frise-controle .onglet--actif,
.frise-controle button.onglet--actif,
.frise-controle button.onglet--actif:hover {
  background: var(--fond-inverse);
  color: var(--texte-inverse);
}

/* Deuxième passe du 28/09/2026 (spec 2026-09-28-technicien-telephone-2, arbitrage 11). Le
   compteur passe SOUS le libellé : « Contrôle extérieur 1/16 · 1 KO · 1 sans dégât » sur une
   ligne dépassait l'écran. `height: auto` qualifié par le bouton : la hauteur fixe des boutons
   (et celle du bloc tactile) garderait une seule ligne. */
.frise-controle .onglet,
.frise-controle button.onglet {
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 0;
  height: auto;
  min-height: var(--hauteur-cible-tactile);
  padding-block: var(--espace-2);
  line-height: 1.3;
}

/* Le code de l'unité dans le titre de la fiche ne se coupe pas à son tiret (arbitrage 12). */
.titre-code {
  white-space: nowrap;
}

.onglet-compteurs {
  display: flex;
  gap: var(--espace-1);
  white-space: nowrap;
}

/* La frise dit qu'elle défile : un fondu sur le bord qui cache encore des onglets. 12px à
   gauche, soit le rembourrage d'un onglet — l'onglet précédent, posé entier contre ce bord au
   chargement, n'y perd aucune lettre. Défilée au bout, un onglet peut s'arrêter à quelques
   pixels du bord : la page MESURE alors chaque fondu (`--fondu-gauche`, `--fondu-droite`) pour
   qu'il s'arrête au texte du premier et du dernier onglet entiers (finitions du 29/09/2026,
   arbitrage 5). Les valeurs par défaut valent sans script. */
.frise-controle.frise--suite-droite {
  mask-image: linear-gradient(
    to right,
    currentColor calc(100% - var(--fondu-droite, 32px)),
    transparent
  );
}

.frise-controle.frise--suite-gauche {
  mask-image: linear-gradient(
    to right,
    transparent,
    currentColor var(--fondu-gauche, var(--espace-5))
  );
}

.frise-controle.frise--suite-gauche.frise--suite-droite {
  mask-image: linear-gradient(
    to right,
    transparent,
    currentColor var(--fondu-gauche, var(--espace-5)),
    currentColor calc(100% - var(--fondu-droite, 32px)),
    transparent
  );
}

.avancement-controle {
  margin: var(--espace-3) 0 0;
  color: var(--texte-secondaire);
}

.avancement-ko {
  color: var(--danger-texte-fort);
  font-weight: var(--graisse-appuyee);
}

.fiche-identite {
  margin-top: var(--espace-3);
}

.fiche-identite > summary {
  display: inline-flex;
  align-items: center;
  gap: var(--espace-2);
  color: var(--texte-action);
}

.fiche-identite > dl {
  margin-top: var(--espace-3);
}

/* Point replié (arbitrage 8) : le commentaire et la photo passent sous les boutons OK / KO / NA,
   sur une ligne à eux, et n'apparaissent qu'au KO ou à la demande. */
.point-complement {
  display: flex;
  flex: 1 1 100%;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--espace-3) var(--espace-5);
  min-width: 0;
}

.point-deplier {
  gap: var(--espace-1);
  padding: 0 var(--espace-3);
  color: var(--texte-secondaire);
  font-size: var(--taille-cellule);
}

/* Les gestes de la fiche vivent dans la barre collée du chauffeur (`.barre-passage`) depuis le
   28/09/2026 (spec 2026-09-28-technicien-telephone-2). Un bouton est une boîte flexible : l'espace
   entre le chevron et le libellé tomberait sans `gap`. */
.barre-passage > .primaire,
.autres-actions > .bouton {
  gap: var(--espace-2);
}

/* ===========================================================================
 * POINT KO, DÉGÂTS ET LISTE DES CONTRÔLES — décisions de Yanis du 28/09/2026, spec
 * 2026-09-28-controle-ko-degats-liste. Aucun jeton neuf.
 * ======================================================================== */

/* Libellé d'une valeur que l'en-tête de colonne nomme déjà au poste : montré dans le repli
   (bloc `max-width: 900px`), jamais ailleurs. */
.libelle-replie {
  display: none;
}

/* Le chevron d'une carte de la liste des contrôles : montré dans le repli seulement, où la carte
   est le geste (spec 2026-09-28-technicien-telephone-2, arbitrage 6). */
.action-carte-chevron {
  display: none;
}

/* « 3 à traiter » : des mots, pas un chiffre à aligner — la police du texte (§3.2, arbitrage 9). */
.compteur-de-page--mots {
  font-family: var(--police-texte);
}

/* Sous un point KO : l'invitation au constat (arbitrage 5), en teinte d'attention — c'est un
   geste possible, pas une erreur. */
.point-degat {
  display: flex;
  flex: 1 1 100%;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--espace-3) var(--espace-5);
  margin: 0;
  padding: var(--espace-3) var(--espace-4);
  border: 1px solid var(--attention-bordure);
  border-radius: var(--rayon-controle);
  background: var(--attention-fond);
  color: var(--attention-texte-fort);
  font-size: var(--taille-cellule);
}

.point-degat > button,
.point-degat > .dialogue-hote > button {
  margin-left: auto;
}

/* Un point qui a son dégât le dit, sans insister. */
.point-degat--constate {
  border-color: var(--bordure-surface);
  background: var(--surface-secondaire);
  color: var(--texte-secondaire);
}

.avancement-sans-degat,
.onglet-compteur--sans-degat {
  color: var(--attention-texte-fort);
}

.avancement-sans-degat {
  font-weight: var(--graisse-appuyee);
}

.onglet--actif .onglet-compteur--sans-degat {
  color: inherit;
}

.constat-du-point-point {
  margin: 0 0 var(--espace-4);
}

/* Dégâts en cartes (arbitrage 7) : une liste, une carte par dégât, dans la carte « Dégâts ».
   Des séparateurs et non des cartes imbriquées : une carte dans une carte redessine un cadre. */
.degats-controle {
  margin: var(--espace-5) 0 0;
  padding: 0;
  list-style: none;
}

.degat-carte {
  padding: var(--espace-5) 0;
  border-top: 1px solid var(--bordure-surface);
}

/* Une pièce qu'une liste réunie ne supprime pas dit d'où elle vient : la photo du point KO dans
   les photos d'un dégât (spec 2026-09-28-technicien-telephone-2, arbitrage 10). */
.piece-mention {
  color: var(--texte-secondaire);
}

/* Vignette d'une photo (finitions du 29/09/2026, arbitrage 1) : l'image d'origine, servie par la
   route protégée des pièces jointes et réduite ICI — aucune miniature n'est calculée. Posée au-
   dessus du nom, dans le lien qui ouvre la photo entière. */
.piece-vignette {
  display: block;
  width: 96px;
  height: 96px;
  margin-bottom: var(--espace-1);
  object-fit: cover;
  border: 1px solid var(--bordure-surface);
  border-radius: var(--rayon-controle);
  background: var(--surface-accentuee);
}

.degat-carte:last-child {
  padding-bottom: 0;
}

.degat-carte-tete {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--espace-3);
}

.degat-carte-description {
  min-width: 0;
  overflow-wrap: anywhere;
}

.degat-carte .badge {
  flex: none;
  font-size: var(--taille-cellule);
}

.degat-carte-details,
.degat-carte-point {
  margin: var(--espace-2) 0 0;
  color: var(--texte-secondaire);
  font-size: var(--taille-cellule);
}

/* « Photos du point : 1 photo », « Photos du dégât : Aucune photo » — un libellé, un résumé. */
.degat-carte-photos {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--espace-3);
  margin-top: var(--espace-3);
  color: var(--texte-secondaire);
  font-size: var(--taille-cellule);
}

/* ---------------------------------------------------------------------------
 * Aide utilisateur — spec docs/specs/2026-09-29-aide-utilisateur.md.
 *
 * Le lien « Aide » de la coque, sous les familles du menu, et les guides : sommaire, une carte
 * par tâche, étapes numérotées, captures légendées, encadrés « À savoir ». Lus au téléphone
 * comme au poste : une capture ne dépasse jamais sa colonne, une capture de téléphone garde sa
 * largeur. La feuille d'impression, en fin de section, ne vaut que pour une page d'aide.
 * ------------------------------------------------------------------------ */

/* Le lien vit hors de la balise du menu (arbitrage 1) : il reprend le dessin d'une entrée, séparé
   des familles par un filet. */
.barre-laterale .menu-aide {
  display: block;
  margin: var(--espace-6) var(--espace-5) 0;
  padding: var(--espace-4) var(--espace-5) var(--espace-2);
  border-top: 1px solid var(--menu-bordure);
  font-size: var(--taille-controle);
  color: var(--menu-texte);
}

.barre-laterale .menu-aide:hover,
.barre-laterale .menu-aide[aria-current='page'] {
  color: var(--menu-texte-actif);
  text-decoration: none;
}

.aide-carte-guide h2 {
  font-size: var(--taille-base);
}

.aide-sommaire ol {
  margin: 0;
  padding-left: 1.5em;
  columns: 2 280px;
  column-gap: var(--espace-9);
}

.aide-sommaire li {
  margin-bottom: var(--espace-2);
  break-inside: avoid;
}

.aide-guide {
  display: flex;
  flex-direction: column;
  gap: var(--espace-6);
}

.aide-etapes {
  margin: 0;
  padding-left: 1.5em;
}

.aide-etapes > li {
  margin-bottom: var(--espace-7);
}

.aide-etapes > li::marker {
  font-weight: var(--graisse-forte);
  color: var(--texte-accent);
}

.aide-etapes p,
.aide-a-savoir li {
  max-width: 75ch;
}

.aide-capture {
  margin: var(--espace-4) 0 0;
}

.aide-capture img {
  display: block;
  max-width: 100%;
  height: auto;
  border: 1px solid var(--bordure-surface);
  border-radius: var(--rayon-surface);
}

.aide-capture figcaption {
  margin-top: var(--espace-2);
  font-size: var(--taille-note);
  color: var(--texte-attenue);
}

.aide-a-savoir {
  margin-top: var(--espace-6);
  padding: var(--espace-4) var(--espace-6);
  background: var(--information-fond);
  border: 1px solid var(--information-bordure);
  border-radius: var(--rayon-surface);
}

.aide-a-savoir h3 {
  margin: 0 0 var(--espace-2);
  color: var(--information-texte);
}

.aide-a-savoir ul {
  margin: 0;
  padding-left: 1.2em;
}

/* Fiche d'une page du terrain (demande de Yanis du 29/09/2026) : les gestes de la journée,
   numérotés, puis le rappel du réseau faible. Elle tient sur une A4 à l'impression. */
.aide-fiche {
  max-width: 75ch;
}

.aide-fiche-chapo {
  margin-top: 0;
  color: var(--texte-secondaire);
}

.aide-fiche-gestes {
  margin: 0;
  padding-left: 1.5em;
}

.aide-fiche-gestes > li {
  margin-bottom: var(--espace-4);
}

.aide-fiche-gestes > li::marker {
  font-weight: var(--graisse-forte);
  color: var(--texte-accent);
}

.aide-fiche-reseau h2 {
  margin: 0 0 var(--espace-2);
  font-size: var(--taille-base);
  color: var(--information-texte);
}

.aide-fiche-reseau p {
  margin: 0;
}

.aide-fiche-suite {
  margin-bottom: 0;
}

/* Impression et PDF d'un guide : ni coque, ni menu, ni fil, ni bouton ; une page par tâche, et
   une capture ou un encadré jamais coupés. `:has()` borne la règle aux pages d'aide — les autres
   écrans s'impriment comme avant. Une fiche d'une page s'imprime sans coque, elle aussi. */
@media print {
  body:has(.aide-sommaire),
  body:has(.aide-fiche) {
    background: var(--surface);
  }

  body:has(.aide-sommaire) .barre-laterale,
  body:has(.aide-sommaire) .barre-superieure,
  body:has(.aide-sommaire) .fil-ariane,
  body:has(.aide-fiche) .barre-laterale,
  body:has(.aide-fiche) .barre-superieure,
  body:has(.aide-fiche) .fil-ariane,
  .aide-imprimer,
  .aide-lien-fiche {
    display: none;
  }

  body:has(.aide-sommaire) .appli,
  body:has(.aide-fiche) .appli {
    display: block;
    min-height: 0;
  }

  body:has(.aide-sommaire) .contenu,
  body:has(.aide-fiche) .contenu {
    padding: 0;
  }

  .aide-fiche {
    max-width: none;
    border: 0;
    padding: 0;
  }

  .aide-fiche-reseau {
    break-inside: avoid;
  }

  .aide-sommaire,
  .aide-tache {
    border: 0;
    padding: 0;
  }

  .aide-tache {
    break-before: page;
  }

  .aide-capture,
  .aide-a-savoir {
    break-inside: avoid;
  }

  .aide-capture img {
    max-height: 100mm;
    width: auto;
  }

  .aide-sommaire a {
    color: inherit;
  }
}

/* ===========================================================================
 * JOURNAL DES COMPTES — passe de design du 30/09/2026 (spec 2026-09-30-journal-des-comptes).
 * Aucun jeton neuf.
 * ======================================================================== */

/* Le filtre vit dans la tête du bloc : une liste déroulante à la largeur de ses noms, pas aux
   460 px d'un champ de formulaire, et à la hauteur compacte des filtres. */
.journal-filtre {
  justify-content: flex-start;
  font-weight: var(--graisse-normale);
}

.journal-filtre select {
  width: auto;
  min-width: 0;
  height: var(--hauteur-controle-compact);
  margin-top: 0;
}

/* Une ligne de journal ne se replie jamais : la date se lit d'un bloc, et au téléphone c'est le
   tableau qui défile (écran de bureau, convention C-34) au lieu de doubler chaque ligne. */
.liste-journal td {
  white-space: nowrap;
}

/* Hiérarchie de lecture : le compte et l'état obtenu portent la ligne ; la date, l'ancienne
   valeur et l'auteur l'accompagnent en texte secondaire (`--texte-attenue` passerait sous
   4,5:1). */
.liste-journal time,
.liste-journal .journal-auteur,
.liste-journal .journal-avant,
.liste-journal .journal-fleche {
  color: var(--texte-secondaire);
}

.liste-journal .journal-compte,
.liste-journal .journal-apres {
  font-weight: var(--graisse-appuyee);
}

.liste-journal .journal-fleche {
  padding: 0 var(--espace-2);
}
