/* =========================================================================
   HAZE DISPATCH — le socle de marque
   =========================================================================
   Chantier visuel, passe 2 du 31/08/2026, d'après
   `docs/DIRECTION-ARTISTIQUE_HAZE-DISPATCH.md`.

   > **La marque est un accent, pas un décor.** Marine pour la structure, bleu
   > pour l'état et l'action ; les zones de travail restent neutres. Une
   > couleur saturée en aplat large fatigue à la longue — et cet outil se
   > regarde des heures, pas trente secondes.

   ## Deux thèmes, une seule écriture

   Chaque couleur est déclarée **une fois**, par `light-dark(clair, sombre)`.
   Le thème suit `prefers-color-scheme` ; un choix explicite pose
   `data-theme` sur `<html>`, **côté serveur**, ce qui bascule `color-scheme`
   et donc toutes les couleurs d'un coup — sans JavaScript, donc sans le flash
   blanc qu'un thème appliqué après chargement produit à chaque page. De nuit,
   ce flash se remarque.

   L'alternative — dupliquer toute la palette sous `@media
   (prefers-color-scheme: dark)` **et** sous `[data-theme="sombre"]` — a été
   écartée : elle triple chaque bloc de surface, et deux copies finissent
   toujours par diverger. Un repli minimal pour les navigateurs sans
   `light-dark()` est posé en fin de fichier.

   ## Une surface est un fond qui porte du texte

   > 🔴 **La leçon du 31/08.** `discret-texte` mesurait 5.99 sur le fond de
   > page — conforme — et **2.31 dans l'en-tête**, où le même gris tombait sur
   > un fond marine. Le test de contraste ne pouvait pas le voir : il calculait
   > des paires déclarées, sans savoir sur quel fond un gabarit pose une
   > classe.
   >
   > Le remède est structurel : **tout fond qui porte du texte redéclare ses
   > propres couleurs de texte.** `--texte`, `--texte-discret` et `--lien` sont
   > relatifs à la surface où ils se trouvent, et la cascade s'en charge. La
   > paire testée devient la paire rendue.
   ========================================================================= */
/* =========================================================================
   La police — Inter, **auto-hébergée**
   =========================================================================
   🔴 **Aucun CDN, ici comme ailleurs.** Les deux fichiers sont versés dans
   `static/polices/` et servis par l'application : un poste sans internet rend
   la même page qu'un poste connecté, et aucune requête ne part vers un tiers
   à chaque ouverture d'écran. C'est la même décision que les tuiles IGN et
   que Leaflet, prise pour la même raison.

   Un **seul fichier variable** par sous-ensemble couvre les quatre graisses
   employées (400, 500, 600, 700) : `font-weight: 100 900` dit au navigateur
   d'y puiser. Quatre fichiers statiques auraient pesé quatre fois plus pour
   le même rendu — ils étaient d'ailleurs identiques, servis par Google.

   Inter est sous licence SIL Open Font 1.1 ; le texte est versé à côté des
   fichiers, comme la licence l'exige.

   `font-display: swap` : le texte s'affiche immédiatement dans la police
   système, puis bascule. Un écran vide pendant le chargement d'une police
   serait le retour du flash blanc qu'on a chassé du thème.
   ========================================================================= */

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('/static/polices/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
    U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
    U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('/static/polices/inter-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
    U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
    U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
    U+A720-A7FF;
}

:root {
  color-scheme: light dark;

  /* --- Identité de marque — charte plaquette, décision du 31/08 -----------
     Les valeurs du logo (#16b3e8) et du site (#0F172A) en diffèrent de moins
     de 1.2 — indiscernable à l'œil, mais trois sources finissent par produire
     quatre valeurs. La plaquette fait foi ; les fichiers de marque gardent
     les leurs, on n'y touche pas. */
  --haze-marine: #0E2841;
  --haze-bleu: #00B0F0;
  --haze-gris: #B0B0B0;
  /* 🟡 Créé par nécessité : le bleu HAZE donne 2.48 sur blanc. Celui-ci en
     donne 4.98 sur le fond de page et 5.44 sur une carte. À verser à la
     charte de marque — sans quoi la divergence recommence. */
  --haze-bleu-clair: #00719F;

  /* --- Les trois niveaux de surface ---------------------------------------
     🔴 **Le sombre n'est plus un aplat marine uniforme.** La refonte du 02/09
     en donne trois : le fond de page, la carte qui s'en détache, et une
     troisième surface d'appui — en-têtes de tableau, survol, champ désactivé.
     Sans ce troisième niveau, un en-tête de tableau se confondait avec la
     carte, et une ligne survolée ne se distinguait pas.

     `--surface` reste le nom du fond **courant** : c'est lui que chaque bloc
     redéclare, et c'est ce qui fait tenir la mesure de contraste. Les deux
     autres sont des valeurs, pas des surfaces. */
  --surface: light-dark(#F4F6F8, #0B1727);
  --surface-carte: light-dark(#FFFFFF, #132238);
  --surface-appui: light-dark(#EEF3F7, #1B2F4A);

  /* 🟠 **Valeurs réconciliées avec la maquette validée.** Le lien de la
     maquette — `#0284C7` — donne 4.10 sur blanc et 3.78 sur le fond de page :
     sous le seuil AA que ce projet mesure surface par surface depuis le
     31/08. On garde **sa teinte** (200°, ce que l'œil reconnaît) et on
     descend la luminosité jusqu'à 4.7. Même méthode pour les états. */
  --texte: light-dark(#16222E, #E6EEF5);
  --texte-discret: light-dark(#5B6B7A, #9FB2C3);
  --lien: light-dark(#026EA6, #38B7F8);
  --filet: light-dark(#DCE3EA, #264062);
  /* 🔴 **Le filet d'une commande n'est pas celui d'un tableau.**
     `--filet` sépare des lignes : il a le droit d'être discret, et personne
     ne clique dessus. Le contour d'un champ, lui, **est** ce qui dit où
     cliquer — WCAG 1.4.11 lui demande 3:1 contre ce qui l'entoure, comme à
     un texte on en demande 4,5.

     Mesuré avant : **1,71:1** pour un champ en thème sombre, **1,13:1** pour
     une case à cocher. On voyait un rectangle à peine plus clair que la
     page, et le rapport du 20/09 le dit autrement : « bordure à peine
     visible », « des carrés gris pleins, sans bordure nette ». */
  /* Mesuré : 3,43:1 sur blanc et 3,17:1 sur le fond de page en clair,
     5,07:1 sur la surface de carte en sombre. Le premier gris essayé,
     #8A99A8, donnait 2,92 — sous le seuil de huit centièmes, et c'est
     précisément le genre d'écart qu'on ne voit pas à l'œil. */
  --filet-controle: light-dark(#7F8C99, #7E93AC);
  --barre: light-dark(#0E2841, #0E2841);
  /* Une ombre en clair, une bordure en sombre : une ombre sur fond sombre ne
     se voit pas, elle salit. */
  --ombre: light-dark(rgba(14, 40, 65, .08), rgba(0, 0, 0, 0));
  /* Le voile de la palette : il assombrit sans masquer la page. */
  --voile: light-dark(rgba(14, 40, 65, .35), rgba(0, 0, 0, .60));

  /* --- L'accent, et la règle qui l'accompagne -----------------------------
     🔴 **Le bleu HAZE ne porte jamais de texte blanc** : 2.48 de contraste.
     Il est un **fond de bouton à texte marine** (6.06), un élément actif, une
     icône. Pour du texte cliquable, c'est `--lien`. La règle est écrite ici
     parce que c'est ici qu'on la lit avant de s'en servir. */
  --accent: var(--haze-bleu);
  --sur-accent: var(--haze-marine);

  /* Le titre d'écran. 🔴 En sombre il vaut `--texte` **à plein contraste** :
     un marine atténué sur fond marine est le défaut que la refonte corrige. */
  --titre: light-dark(#0E2841, #E6EEF5);

  /* --- Formes et rythme ---------------------------------------------------
     Échelle 4 / 8 / 12 / 16 / 24 / 32. Trois rayons, pas quatre.

     🔴 **Huit jetons d'espacement, posés le 18/09.** L'audit a compté 47
     valeurs de `padding` écrites à la main, de 2 à 48 px, sans échelle : deux
     blocs voisins tombaient à 11 px et 12 px sans que ce soit une décision.

     🟠 **Les 47 anciennes ne sont pas réécrites dans ce chantier.** Un diff
     de 47 paddings mêlé à un changement de charpente serait illisible, et ce
     qu'on ne relit pas, on ne le vérifie pas. La règle vaut pour le neuf ;
     l'ancien se convertit au fil des chantiers suivants. */
  --e0: 2px;  --e1: 4px;  --e2: 8px;  --e3: 12px;
  --e4: 16px; --e5: 24px; --e6: 32px; --e7: 48px;

  /* 🟠 Le voile d'un tiroir ouvert par-dessus le contenu. Écrit en dur, il
     échappait à la mesure — et la garde des couleurs l'a dit. */
  --voile: rgba(0, 0, 0, .45);


  /* --- Les six statuts de vacation ---------------------------------------
     🔴 **Six paires, et elles ne sont pas des `--etat-*`.** Les quatre états
     du projet — impossible, infraction, conforme, déduit — disent la gravité
     d'un constat. Ceux-ci disent où en est une vacation : ce n'est pas la
     même chose, et les confondre ferait lire « conforme » là où il faut lire
     « confirmée ». La table des quatre reste à quatre.

     🔴 **Des aplats, plus de hachures.** `--hachure-pourvoir` faisait tomber
     trois paires sous le seuil — 3,87, 3,92 et 4,02:1 — sans qu'aucun test
     puisse le voir : un test lit `background-color`, pas les arrêts d'un
     dégradé. C'est le fond **rendu** qui compte, et un dégradé n'en a pas un.

     🟠 **Le seuil est 7:1, pas 4,5.** Ces textes vivent sur des aplats
     colorés, là où les paires calées au ras du seuil tombent dès qu'un fond
     n'est pas uni. La marge est voulue.

     Chaque paire est mesurée dans les deux thèmes ; le tableau des valeurs
     est dans la spécification du chantier. */
  --st-pourvoir: light-dark(#4C1D95, #D9C6FF);
  --fond-st-pourvoir: light-dark(#F1EAFE, #2A1F4D);
  --st-non-pourvue: light-dark(#8C1D11, #FFB3A8);
  --fond-st-non-pourvue: light-dark(#FBEAEA, #421A1A);
  --st-proposee: light-dark(#0C4A6E, #9BD9FB);
  --fond-st-proposee: light-dark(#EAF6FD, #0F2E45);
  --st-confirmee: light-dark(#125034, #6FE0AC);
  --fond-st-confirmee: light-dark(#E6F6EE, #123A2C);
  --st-en-cours: light-dark(#6B3F00, #FBC26A);
  --fond-st-en-cours: light-dark(#FDF3E1, #3D2E0E);
  --st-realisee: light-dark(#374956, #C2D2E0);
  --fond-st-realisee: light-dark(#EEF3F7, #1B2F4A);

  --rayon-champ: 6px;
  --rayon-carte: 10px;
  --rayon-pastille: 999px;
  /* 🔴 **44 px, et non 40.** WCAG 2.5.5 pose 44 × 44 pour une cible de
     pointage, et c'est aussi ce que demande un doigt sur tablette. Quarante
     tenait le seuil de 2.5.8 — 24 px — et manquait celui-ci de quatre
     pixels, sur **tous** les champs et tous les boutons de l'application. */
  --hauteur-controle: 44px;
  /* 🔴 **La densité est un réglage, pas un défaut.**
     Une table de données ne doit pas avoir *une* hauteur de ligne : elle doit
     en avoir deux, au choix de celui qui regarde. Le pire choix est celui du
     milieu — « des lignes de 38 px, tolérables dans les deux cas, optimisées
     pour aucun », dit la note du 20/09, et c'est ce qu'on ressentait.

     **Compact pour balayer** : on cherche une anomalie parmi trente lignes, et
     faire défiler est le contraire d'aérer. **Confortable pour lire et
     décider** : le contenu respire, et c'est aussi une cible atteignable au
     doigt. Le choix se garde d'une session à l'autre, par cookie lu côté
     serveur — comme le thème, et sans saut de mise en page. */
  --hauteur-ligne: 44px;
  --creux-de-cellule: 8px 12px;

  /* --- Les quatre états ---------------------------------------------------
     🔴 impossible · 🟠 infraction · 🟢 contractuel · 🔵 déduit. Quatre sens
     déjà établis dans le vocabulaire du projet ; ils ne changent pas ici.

     Les deux déclinaisons partagent **exactement la même teinte** — 0, 33,
     140, 199 — et ne diffèrent que par la luminosité. Un rouge qui virerait à
     l'orange en changeant de thème obligerait l'opérateur à réapprendre le
     code couleur, ce qui est le contraire du but. */
  --etat-impossible: light-dark(#C0392B, #F87E71);
  --etat-infraction: light-dark(#965B00, #FBA524);
  --etat-conforme: light-dark(#17774B, #34D38A);
  --etat-deduit: light-dark(#0369A1, #7DCFFC);

  /* Les fonds teintés des bandeaux et des étiquettes d'alerte. */
  --fond-impossible: light-dark(#FBEAEA, #421A1A);
  --fond-infraction: light-dark(#FDF3E1, #3D2E0E);
  --fond-conforme: light-dark(#E6F6EE, #123A2C);
  --fond-deduit: light-dark(#EAF6FD, #0F2E45);

  /* --- « À pourvoir » — un statut d'affichage, pas un cinquième état -------
     🟠 Les quatre états disent la **conformité** d'une situation : impossible,
     en infraction, conforme, déduit. « À pourvoir » ne dit rien de tout cela —
     un poste vacant n'est ni conforme ni fautif, il est **à faire**. Lui
     donner sa propre couleur, hors de la famille des quatre, évite qu'un
     dispatcheur le lise comme une alerte.

     Le violet est repris de la maquette validée ; les hachures redoublent la
     couleur, parce qu'un statut ne se porte jamais par la seule teinte. */
  --texte-pourvoir: light-dark(#7C3AED, #D0B5FD);
  --fond-pourvoir: light-dark(#F1EAFE, #2A1F4D);

  /* --- La carte -----------------------------------------------------------
     🟠 Trois natures, trois couleurs. La règle « la couleur dit un état,
     jamais une décoration » vise les **zones de données** — un tableau où tout
     est coloré n'a plus de hiérarchie. Sur une carte, la nature d'un point
     *est* l'information : sans distinction, un site et une agence se
     confondent, et c'est la carte entière qui ne dit plus rien.

     Ces couleurs ne portent aucun texte : elles ne sont donc pas mesurées
     contre un fond par le test de contraste, qui n'examine que les jetons de
     texte. */
  --carte-site: light-dark(#00719F, #00B0F0);
  --carte-agence: light-dark(#0E2841, #A8CCFF);
  --carte-vehicule: light-dark(#6B3FA0, #C9A6F0);
}

/* Le choix explicite de l'agent, posé par le serveur d'après son cookie. Il
   ne fait que fixer `color-scheme` : toutes les couleurs suivent. */
:root[data-theme="clair"] { color-scheme: light; }
:root[data-theme="sombre"] { color-scheme: dark; }

/* 🟠 Les deux autres crans ne se déclarent qu'ici : le compact **est** le
   défaut, et redire sur `:root` ce que `:root` porte déjà ferait deux
   sources. */
:root[data-densite="confortable"] {
  --hauteur-ligne: 56px;
  --creux-de-cellule: 12px 14px;
}

/* 🔴 **Le cran de balayage — 34 px.** La recherche donne 32 à 36 pour
   chercher une anomalie parmi trente lignes ; les deux crans du 20/09
   tombaient tous deux dans la bande d'inspection. Sur un planning, celui-ci
   montre deux fois plus de lignes.

   🟠 **Il ne s'applique qu'aux écrans de balayage.** Une fiche en 34 px de
   ligne serait illisible — on y lit et on y décide. La feuille le borne donc
   aux tableaux, et jamais aux champs. */
:root[data-densite="dense"] {
  --hauteur-ligne: 34px;
  --creux-de-cellule: 4px 12px;
}
/* Les commandes gardent leur cible de 44 px même en dense : un doigt ne
   rétrécit pas parce qu'on veut voir plus de lignes. */
:root[data-densite="dense"] .champ input,
:root[data-densite="dense"] .champ select,
:root[data-densite="dense"] .champ textarea { min-height: var(--hauteur-controle); }

/* =========================================================================
   Les surfaces
   ========================================================================= */

/* La carte : tableaux, blocs, panneaux, formulaires. Fond neutre — c'est la
   zone de travail, et la marque n'y entre pas. */
.surface-carte,
table,
.panneau,
.compteur,
.candidat,
.mouvement,
details,
form.bloc,
form.filtres {
  --surface: var(--surface-carte);
  background: var(--surface);
  color: var(--texte);
  border: 1px solid var(--filet);
  border-radius: var(--rayon-carte);
  box-shadow: var(--ombre);
}

/* Les surfaces de marque — marine dans les **deux** thèmes : l'en-tête, et la
   pastille de score du dispatch.

   > 🔴 C'est l'en-tête qui a piégé la passe 1. Le gris et le bleu de la charte
   > y sont à leurs valeurs officielles, et les quatre états y prennent leur
   > déclinaison **claire** : un fond marine ne porte pas un rouge sombre.
   >
   > Chaque surface déclare son jeu **complet**, dans un seul bloc. Répartir
   > les couleurs d'une surface sur deux règles marcherait dans le navigateur,
   > mais le test lit bloc par bloc : il signalerait un défaut qui n'existe
   > pas. On préfère cette redite-là au risque inverse. */
/* 🔴 **La barre latérale est la surface de marque** depuis le 02/09 ; c'était
   l'en-tête. Le raisonnement ne change pas : un fond marine ne porte pas un
   rouge sombre, et chaque surface déclare son jeu **complet** dans un seul
   bloc — le test lit bloc par bloc, et une couleur laissée dehors serait
   mesurée contre la mauvaise.

   🟠 **Les couleurs de la barre du 29/09 sont ici, et nulle part ailleurs**
   (brief de la barre latérale, § 2.4) : aucun gabarit n'en écrit une.
   `test_barre_laterale` en recalcule les contrastes — les liens à 7,2 pour 1,
   l'écran courant à plus de 10. L'ancien écran courant, blanc sur `#00B0F0`,
   tombait à 2,5. */
.barre-laterale {
  --surface: var(--barre);
  --texte: #FFFFFF;
  --texte-discret: #9FB2C3;
  --lien: var(--haze-bleu);
  --etat-impossible: #F87E71;
  --etat-infraction: #FBA524;
  --etat-conforme: #34D38A;
  --etat-deduit: #7DCFFC;
  --texte-pourvoir: #D0B5FD;
  --barre-fond: #0E2841;
  --barre-lien: #C6D3DE;
  --barre-titre: #E6EEF5;
  --barre-discret: #9FB2C3;
  --barre-courant-fond: #1D3B5E;
  --barre-courant-texte: #FFFFFF;
  --barre-repere: #00B0F0;
  --barre-pastille: #F87E71;
  /* Le survol et le filet de la barre : un blanc très dilué sur le marine.
     Déclarés en jetons **de cette surface** — une couleur écrite dans une
     règle échapperait à la mesure. */
  --survol-barre: rgba(255, 255, 255, .08);
  --filet-barre: rgba(255, 255, 255, .12);
  --volet-fond: #132C48;
  --volet-filet: rgba(255, 255, 255, .14);
  --ombre-volet: 0 12px 32px rgba(14, 40, 65, .35);
  /* Les deux largeurs du brief, et le retrait d'un écran sous le libellé de
     son groupe : 12 + 20 (l'icône) + 12. */
  --largeur-barre: 264px;
  --largeur-barre-reduite: 72px;
  --retrait-de-barre: 44px;
}

/* 🔴 **L'écran courant est une surface** : texte blanc sur `#1D3B5E`, et un
   trait de 3 px à gauche. Il déclare son jeu complet, comme toute surface —
   le test mesure la paire rendue. */
.barre-laterale [aria-current="page"] {
  --surface: #1D3B5E;
  --texte: #FFFFFF;
  --texte-discret: #FFFFFF;
  --lien: #FFFFFF;
  --etat-impossible: #FFFFFF;
  --etat-infraction: #FFFFFF;
  --etat-conforme: #FFFFFF;
  --etat-deduit: #FFFFFF;
  --texte-pourvoir: #FFFFFF;
  background: var(--surface);
  color: var(--texte);
}

/* La pastille du compteur d'alertes : rouge clair, texte marine. */
.pastille-compteur {
  --surface: #F87E71;
  --texte: #0E2841;
  --texte-discret: #0E2841;
  --lien: #0E2841;
  --etat-impossible: #0E2841;
  --etat-infraction: #0E2841;
  --etat-conforme: #0E2841;
  --etat-deduit: #0E2841;
  --texte-pourvoir: #0E2841;
  background: var(--surface);
  color: var(--texte);
}

/* L'en-tête fin n'est plus marine : il porte la recherche et le compte sur la
   surface de carte, comme la maquette. Il reste déclaré comme surface — c'est
   sur lui que le défaut du 31/08 était passé. */
header {
  --surface: var(--surface-carte);
  background: var(--surface);
  color: var(--texte);
}

.candidat .score {
  --surface: var(--haze-marine);
  --texte: #FFFFFF;
  --texte-discret: var(--haze-gris);
  --lien: var(--haze-bleu);
  --etat-impossible: #F87E71;
  --etat-infraction: #FBA524;
  --etat-conforme: #34D38A;
  --etat-deduit: #7DCFFC;
  --texte-pourvoir: #D0B5FD;
  background: var(--surface);
  color: var(--texte);
}

/* Le troisième niveau : en-tête de tableau, survol, champ désactivé. */
th {
  --surface: var(--surface-appui);
  background: var(--surface);
}

/* Le bouton **secondaire** — le cas ordinaire. Le primaire est plus bas :
   fond accent, texte marine, et un seul par écran. */
button {
  --surface: var(--surface-carte);
  background: var(--surface);
  color: var(--texte);
}

/* 🟠 **Un survol qui ne se voit pas n'est pas un survol.** La passe 2 posait
   `filter: brightness(0.95)` : cela assombrit dans les deux thèmes, et sur un
   bouton déjà sombre l'effet n'existe pas. On **éclaircit en sombre, on
   assombrit en clair**, et le sens de la variation se déclare comme le reste
   de la palette.

   C'est une surface à part entière — elle porte le même texte, elle entre
   donc dans la mesure de contraste avec les autres. */
button:hover {
  --surface: var(--surface-appui);
  background: var(--surface);
  /* 🟡 Le déplacement de luminosité est **borné par le contraste** : au-delà,
     le texte du bouton passe sous le seuil dans un thème ou dans l'autre. La
     bordure prend le relais — elle rend le survol franc sans coûter un
     dixième de rapport de contraste. */
  border-color: var(--lien);
}

.masque {
  --surface: var(--surface-appui);
  background: var(--surface);
}

/* Zébrage **léger** : il aide l'œil à suivre une ligne, il ne dessine pas un
   damier. Le survol, lui, prend la surface d'appui — franc, et une seule
   ligne à la fois. */
tbody tr:nth-child(even) {
  --surface: light-dark(#F7F9FB, #17293F);
  background: var(--surface);
}

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

/* 🔴 « À pourvoir » ne se porte pas par la seule couleur : le gabarit y met
   le mot, en toutes lettres. La **trame a disparu le 19/09**, ici comme sur
   la vignette : elle échappait à la mesure de contraste — un test lit
   `background-color`, pas les arrêts d'un dégradé — et elle faisait tomber
   trois paires sous le seuil sans que rien ne le dise. L'aplat est mesurable,
   et il est mesuré. */
.a-pourvoir td {
  --surface: var(--fond-st-pourvoir);
  --texte: var(--st-pourvoir);
  background: var(--surface);
  color: var(--texte);
}

.ligne-retenue td {
  --surface: var(--fond-conforme);
  background: var(--surface);
}

.bandeau-ok { --surface: var(--fond-conforme); }
.bandeau-avertissement { --surface: var(--fond-infraction); }
.bandeau-erreur { --surface: var(--fond-impossible); }
.alerte-depassee { --surface: var(--fond-impossible); }
.alerte-proche { --surface: var(--fond-infraction); }

/* =========================================================================
   Le socle
   ========================================================================= */

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: Inter, system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-size: 14px;
  line-height: 1.5;
  color: var(--texte);
  background: var(--surface);
  -webkit-font-smoothing: antialiased;
}

/* --- L'en-tête ------------------------------------------------------------
   Le logo officiel sur une pastille marine, puis « DISPATCH » en texte
   discret. Le logo porte déjà HAZE SECURITY ; le mot nomme l'outil.

   🔴 La pastille vaut dans les **deux** thèmes : le SVG contient du blanc et
   disparaîtrait sur un fond clair. C'est ce qui permet d'utiliser le fichier
   de marque **sans le modifier**, et de se passer du PNG de 78 Ko. */
/* =========================================================================
   La charpente — barre latérale, en-tête fin, colonne principale
   =========================================================================
   > 🔴 **Vingt-cinq entrées à plat ne se lisent plus.** La liste était juste —
   > chaque profil ne voit que ses écrans — mais elle obligeait à parcourir
   > pour trouver. Sept groupes, et l'œil va droit au bon.

   La barre est en `position: sticky` plutôt que `fixed` : elle défile avec la
   page sur un écran court, et un menu qu'on ne peut pas faire défiler est un
   menu tronqué. */
body {
  display: grid;
  /* 🔴 **Des colonnes nommées, et un placement explicite.** Le placement
     automatique rangeait les enfants dans l'ordre du document : il a suffi
     d'un sprite et d'une case cachée en tête pour envoyer la barre à droite
     et le contenu dessous. Nommer les zones rend la charpente indifférente à
     ce qu'on ajoutera demain dans le corps. */
  /* 🟠 **Deux zones depuis le 29/09** : le rail est parti avec le brief de
     la barre latérale. La barre donne sa largeur — 264 px, ou 72 réduite —
     et la colonne la suit : une seule valeur à tenir. */
  grid-template-columns: [barre] max-content [contenu] minmax(0, 1fr);
  grid-template-rows: [charpente] 1fr;
  min-height: 100vh;
}
body.portail { display: block; }

.colonne-principale {
  grid-column: contenu;
  grid-row: charpente;
  display: flex;
  flex-direction: column;
  min-width: 0;                       /* sans quoi un tableau large pousse tout */
}

/* Sans navigation — l'écran de connexion, le portail — la colonne prend
   toute la largeur plutôt que de laisser une colonne vide à gauche. (Le rail
   en avait ajouté une seconde du 18 au 29/09 ; il est parti.) */
body:not(:has(.barre-laterale)) .colonne-principale { grid-column: barre / -1; }

/* --- La barre latérale — brief du 29/09 ----------------------------------
   🔴 **Une seule navigation.** Le rail des groupes et le panneau listaient
   les mêmes sept groupes, côte à côte ; il ne reste que cette barre, de
   264 px, qui se réduit à 72 px. Ses couleurs sont des jetons de sa surface,
   déclarés plus haut. */
.barre-laterale {
  grid-column: barre;
  grid-row: charpente;
  width: var(--largeur-barre);
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  align-self: start;
  /* 🟠 **Les deux bornes, et non une.** Sans plancher, la colonne s'arrête à
     son contenu et laisse sous elle une bande claire ; le plafond garde la
     marque et le pied fixes pendant que la liste défile. */
  min-height: 100vh;
  max-height: 100vh;
  background: var(--barre-fond);
  color: var(--barre-lien);
}

/* 🟠 Un sélecteur descendant, et non une seconde classe : une garde du
   17/09 cherche `class="marque"` littéralement. Et **72 px** : c'est aussi la
   cible de pointage que la garde du socle de marque veut au-delà de 24. */
.barre-laterale .marque {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--e2);
  min-height: 72px;
  padding: 0 var(--e4);
  border-bottom: 1px solid var(--filet-barre);
  text-decoration: none;
}
.barre-laterale .outil {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .16em;
  color: var(--barre-courant-texte);
}

/* La liste défile **à l'intérieur** de la barre ; la marque et le pied
   restent en place. */
.liste-de-navigation {
  flex: 1 1 auto;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--e0);
  padding: var(--e3);
}

.lien-de-premier-niveau,
.titre-de-groupe {
  display: flex;
  align-items: center;
  gap: var(--e3);
  min-height: 40px;
  padding: 0 var(--e3);
  border-radius: var(--rayon-champ);
  font-size: 14px;
  color: var(--barre-lien);
  text-decoration: none;
}
.lien-de-premier-niveau svg,
.titre-de-groupe svg { flex: none; width: 20px; height: 20px; }
.libelle-de-barre { flex: 1 1 auto; min-width: 0; }

/* 🔴 **Un titre de groupe dit qu'il se déplie** : casse normale, gras, et
   un chevron à droite. En capitales espacées et sans flèche, il se lisait
   comme une étiquette. */
.titre-de-groupe {
  font-weight: 600;
  color: var(--barre-titre);
  cursor: pointer;
  list-style: none;
}
.titre-de-groupe::-webkit-details-marker { display: none; }
.chevron-de-groupe { display: inline-flex; color: var(--barre-discret); }
.chevron-de-groupe svg { width: 16px; height: 16px; }
.groupe-de-menu[open] > .titre-de-groupe .chevron-de-groupe svg {
  transform: rotate(90deg);
}
/* Le groupe **fermé** qui contient l'écran courant : un point bleu. */
.point-courant {
  display: none;
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--barre-repere);
}
.groupe-de-menu:not([open]) .point-courant { display: inline-block; }

.lien-de-premier-niveau:hover,
.titre-de-groupe:hover,
.entree-de-menu:hover,
.bascule-de-volet:hover {
  background: var(--survol-barre);
  color: var(--barre-courant-texte);
}

/* 🔴 **Un groupe de menu n'est pas une carte.** En passant de `<div>` à
   `<details>` le 18/09, il a hérité du bloc générique `details` — fond blanc,
   bordure, ombre — et le libellé est tombé à 2,17:1 sur le marine. */
.groupe-de-menu {
  margin: 0;
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  padding: 0;
}
/* 🔴 **`display` sous `[open]`, jamais sur le sélecteur nu** : une règle
   d'auteur qui déclare `display` sur le contenu d'un `<details>` écrase le
   masquage du navigateur, et un groupe fermé occupait sa hauteur. */
.groupe-de-menu[open] > .volet-de-groupe {
  display: flex;
  flex-direction: column;
  gap: var(--e0);
}
.titre-du-volet { display: none; }

/* Un écran dans un groupe : texte seul, aligné sur le libellé du titre. */
.entree-de-menu {
  display: flex;
  align-items: center;
  min-height: 36px;
  padding: 0 var(--e3) 0 var(--retrait-de-barre);
  border-radius: var(--rayon-champ);
  font-size: 14px;
  color: var(--barre-lien);
  text-decoration: none;
}

/* 🔴 **L'écran courant** : un fond, un trait de 3 px et la graisse — pas la
   seule teinte, qu'on ne distingue pas toujours. `aria-current` le dit aux
   lecteurs d'écran. */
.barre-laterale [aria-current="page"] {
  font-weight: 600;
  box-shadow: inset 3px 0 0 var(--barre-repere);
}

.pastille-compteur {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  min-width: 22px;
  height: 22px;
  padding: 0 var(--e2);
  border-radius: 999px;
  font-size: 12px;
  font-weight: 700;
  box-sizing: border-box;
}

.separateur-de-barre {
  flex: none;
  height: 1px;
  margin: var(--e2) var(--e1);
  background: var(--filet-barre);
}

/* --- Le pied : réduire, agrandir ------------------------------------------
   🔴 **Hors de la liste.** « Replier » avait la forme d'un écran, en tête du
   rail, parmi les destinations. C'est un réglage d'affichage : il vit en
   pied, en texte discret. */
.pied-de-barre {
  flex: none;
  padding: var(--e3);
  border-top: 1px solid var(--filet-barre);
}
.bascule-de-volet {
  display: flex;
  align-items: center;
  min-height: 40px;
  padding: 0 var(--e3);
  border-radius: var(--rayon-champ);
  font-size: 13px;
  color: var(--barre-discret);
  cursor: pointer;
}
.bascule-de-volet svg { width: 18px; height: 18px; }
.bascule-de-volet .quand-deploye,
.bascule-de-volet .quand-replie {
  display: inline-flex;
  align-items: center;
  gap: var(--e2);
}
/* Un seul des deux à la fois — les deux restent dans le document. */
.bascule-de-volet .quand-replie { display: none; }
body:has(#barre-repliee:checked) .barre-laterale .bascule-de-volet .quand-deploye {
  display: none;
}
body:has(#barre-repliee:checked) .barre-laterale .bascule-de-volet .quand-replie {
  display: inline-flex;
}

/* 🔴 **Le focus se voit** — un anneau bleu, 6,6 pour 1 sur le marine. La
   case du repli est cachée : c'est son étiquette qui porte l'anneau. */
.barre-laterale a:focus-visible,
.titre-de-groupe:focus-visible,
#barre-repliee:focus-visible + .bascule-de-volet {
  outline: 2px solid var(--barre-repere);
  outline-offset: -2px;
}

/* --- Le mode réduit : 72 px, des icônes, un volet par groupe -------------
   Le même balisage, restylé : les libellés passent **hors écran** et restent
   le nom accessible de chaque icône ; le groupe s'ouvre en volet collé à la
   barre, par-dessus la page. `app.js` n'en laisse qu'un ouvert, et le
   referme sur Échap ou sur un clic à l'extérieur. */
body:has(#barre-repliee:checked) .barre-laterale { width: var(--largeur-barre-reduite); }
body:has(#barre-repliee:checked) .barre-laterale .marque {
  flex-direction: column;
  justify-content: center;
  gap: var(--e0);
  padding: 0;
}
body:has(#barre-repliee:checked) .barre-laterale .outil {
  /* Sans l'espacement du mode déplié : « DISPATCH » tient dans 72 px. */
  font-size: 12px;
  letter-spacing: 0;
}
body:has(#barre-repliee:checked) .barre-laterale .liste-de-navigation {
  overflow: visible;
  align-items: center;
  padding: var(--e3) 0;
}
body:has(#barre-repliee:checked) .barre-laterale .libelle-de-barre {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}
body:has(#barre-repliee:checked) .barre-laterale .lien-de-premier-niveau,
body:has(#barre-repliee:checked) .barre-laterale .titre-de-groupe,
body:has(#barre-repliee:checked) .barre-laterale .bascule-de-volet {
  position: relative;
  justify-content: center;
  width: 48px;
  min-height: 44px;
  padding: 0;
}
body:has(#barre-repliee:checked) .barre-laterale .chevron-de-groupe,
body:has(#barre-repliee:checked) .barre-laterale .point-courant { display: none; }
body:has(#barre-repliee:checked) .barre-laterale .pastille-compteur {
  position: absolute;
  top: 2px;
  right: 2px;
  min-width: 18px;
  height: 18px;
}
/* Le groupe courant : l'icône porte le fond et le trait de l'écran courant. */
body:has(#barre-repliee:checked) .barre-laterale [data-groupe-courant] {
  background: var(--barre-courant-fond);
  color: var(--barre-courant-texte);
  box-shadow: inset 3px 0 0 var(--barre-repere);
}
body:has(#barre-repliee:checked) .barre-laterale .groupe-de-menu {
  position: relative;
  width: 100%;
  display: flex;
  justify-content: center;
}
body:has(#barre-repliee:checked) .barre-laterale .groupe-de-menu[open] > .volet-de-groupe {
  position: absolute;
  top: 0;
  left: calc(100% + 4px);
  z-index: 40;
  width: 232px;
  padding: var(--e2);
  background: var(--volet-fond);
  border: 1px solid var(--volet-filet);
  border-radius: var(--rayon-carte);
  box-shadow: var(--ombre-volet);
}
body:has(#barre-repliee:checked) .barre-laterale .titre-du-volet {
  display: block;
  padding: var(--e2) var(--e3) var(--e1);
  font-size: 12px;
  font-weight: 600;
  color: var(--barre-discret);
}
body:has(#barre-repliee:checked) .barre-laterale .entree-de-menu {
  padding-left: var(--e3);
}
body:has(#barre-repliee:checked) .barre-laterale .separateur-de-barre {
  width: 40px;
  margin: var(--e2) auto;
}
body:has(#barre-repliee:checked) .barre-laterale .pied-de-barre {
  display: flex;
  justify-content: center;
  padding: var(--e3) 0;
}

input[type="radio"] { border-radius: 50%; }

/* --- L'en-tête fin --------------------------------------------------------
   56 px, une ligne. Il portait le menu ; il ne porte plus que la recherche et
   le compte. */
header.entete-fin {
  min-height: 56px;
  padding: 8px 16px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  border-bottom: 1px solid var(--filet);
}

.recherche-globale {
  --surface: var(--surface-appui);
  background: var(--surface);
  color: var(--texte-discret);
  border: 1px solid var(--filet);
  justify-content: flex-start;
  gap: 8px;
  min-width: 260px;
  max-width: 420px;
  flex: 1 1 260px;
  font-weight: 400;
}
.recherche-globale kbd {
  margin-left: auto;
  font-family: inherit;
  font-size: 12px;
  border: 1px solid var(--filet);
  border-radius: 4px;
  padding: 1px 6px;
}

/* La navigation du portail : dans l'en-tete, faute de barre laterale. Elle
   deviendra la barre d'onglets basse de la maquette au lot C. */
.navigation-portail { display: flex; gap: 6px; flex-wrap: wrap; }
.navigation-portail a {
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  padding: 6px 10px;
  border-radius: var(--rayon-champ);
  text-decoration: none;
  color: var(--lien);
}
.navigation-portail a.entree-active {
  --surface: var(--surface-appui);
  background: var(--surface);
  color: var(--texte);
  font-weight: 600;
}

/* --- Le fil d'Ariane ------------------------------------------------------
   Où l'on est, et par où l'on est arrivé. Déduit du chemin, comme le menu. */
.fil-d-ariane {
  display: flex;
  align-items: center;
  gap: var(--e1);
  flex-wrap: wrap;
  /* Aligné sur le contenu de `main`, qui porte déjà sa marge. */
  padding: 0 0 var(--e2);
  font-size: 13px;
  color: var(--texte-discret);
}
.fil-d-ariane a { color: var(--lien); }
.fil-d-ariane strong { color: var(--texte); font-weight: 600; }
/* Le séparateur est décoratif : les lecteurs d'écran ne le lisent pas. */
.separateur-de-fil { color: var(--texte-discret); }

/* =========================================================================
   Les trois points de rupture — déclarés ici, et nulle part ailleurs
   =========================================================================
   🔴 **Quatre seuils épars (700, 800, 900, 1100) devenus trois.** Ils
   n'étaient pas un système : ils avaient été posés un par un, chacun pour un
   écran, et deux d'entre eux se croisaient sans que personne l'ait voulu.

   | Seuil | Ce qui change |
   |---|---|
   | **1 180 px** | la colonne Bilan du planning se replie |
   | **760 px**   | la barre passe en tête de page ; les tableaux deviennent des cartes ; la grille cède la place à la lecture par jour |

   🟠 **À chaque largeur, une navigation entière.** C'est l'invariant du
   27/08 : on ne retire jamais une navigation avant d'avoir posé la
   remplaçante. Depuis le 29/09 il n'y a plus qu'une barre : à 1 024 px elle
   garde ses 264 px (la recette le vérifie), et qui veut la place la réduit
   à 72 ; sous 760 px, elle passe au-dessus du contenu, groupes fermés. */

@media (max-width: 760px) {
  /* ---------------------------------------------------------------------
     🔴 **La grille cède la place à une lecture en blocs.**

     Mesuré à 390 px : la colonne Site (220) et la colonne Bilan (132) en
     prennent 352 sur 362 disponibles. Il reste **dix pixels pour sept
     jours** — le `tbody` s'élargit à 608 px et fait défiler la page entière
     à l'horizontale. Aucun `overflow` de conteneur n'y peut rien : le
     débordement vient de la table elle-même.

     🟠 **Un bloc par entité, et non par jour.** La spécification demandait
     « un bloc par jour, listant ses vacations avec le site » **et** « le même
     balisage pour les deux ». Les deux ne tiennent pas ensemble : une table
     range ses données par ligne — un site, ses sept jours — et transposer en
     colonnes n'est pas à la portée d'une feuille de style. Entre les deux
     exigences, celle du balisage unique l'emporte : deux gabarits qui disent
     la même chose divergent au premier ajout. On lit donc « Entrepôt Bravo,
     lundi 6 : 06 h », au lieu de « lundi 6, Entrepôt Bravo : 06 h ».

     Le bilan disparaît — arbitrage du directeur du 19/09 — et les colonnes
     collantes cessent de l'être : sur un bloc, il n'y a rien à ancrer.
     --------------------------------------------------------------------- */
  .grille-planning { overflow: visible; max-height: none; }
  .grille-des-vacations,
  .grille-des-vacations tbody,
  .grille-des-vacations tr,
  .grille-des-vacations td,
  .grille-des-vacations th { display: block; width: auto; }
  .grille-des-vacations { table-layout: auto; }
  .grille-des-vacations thead,
  .grille-des-vacations tfoot,
  .grille-des-vacations .colonne-bilan { display: none; }

  .grille-des-vacations tr {
    margin-bottom: var(--e3);
    border: 1px solid var(--filet);
    border-radius: var(--rayon-carte);
    overflow: hidden;
  }
  .grille-des-vacations .colonne-entite {
    position: static;
    width: auto;
    max-width: none;
    font-weight: 600;
    background: var(--surface-appui);
  }
  /* 🟠 Une case vide ne devient pas une ligne vide : sept jours dont deux
     portent une vacation donneraient cinq lignes de rien, et le bloc se
     lirait moins bien que la grille qu'il remplace. */
  .grille-des-vacations td.case:not(:has(.vignette)) { display: none; }
  .grille-des-vacations td.case {
    display: grid;
    grid-template-columns: 10ch minmax(0, 1fr);
    gap: var(--e2);
    align-items: start;
    border: 0;
    border-top: 1px solid var(--filet);
    padding: var(--e2);
  }
  /* Le jour, à gauche de ses vacations. Sélecteur long à dessein : la
     règle qui masque est déclarée plus bas dans la feuille, et à
     spécificité égale c'est la dernière lue qui l'emporte. */
  .grille-des-vacations td.case .jour-de-case {
    display: block;
    color: var(--texte-discret);
    font-size: 12px;
  }

  /* La barre passe **au-dessus** du contenu : une colonne, même de 72 px,
     sur un écran de 390 ne laisse rien à lire. Ses groupes restent fermés ;
     le réglage de largeur n'a plus d'objet.

     🔴 **Les deux bornes de hauteur tombent ici — le défaut du 21/09.** Posées
     pour que la colonne du bureau descende jusqu'en bas, `min-height: 100vh`
     faisait prendre à la navigation toute la hauteur d'un téléphone. */
  body { display: block; }
  .barre-laterale,
  body:has(#barre-repliee:checked) .barre-laterale {
    position: static;
    width: auto;
    min-height: 0;
    max-height: none;
  }
  .liste-de-navigation { overflow: visible; }
  .pied-de-barre { display: none; }
}

.marque {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-height: 32px;
  color: var(--texte);
  text-decoration: none;
}

.marque .pastille {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--haze-marine);
  border-radius: 4px;
  padding: 4px 6px;
  min-width: 32px;
  min-height: 32px;
}

.marque img { display: block; height: 24px; width: auto; }

.marque .outil {
  font-weight: 600;
  letter-spacing: 1.5px;
  font-size: 13px;
  color: var(--texte-discret);
}

header nav { display: flex; gap: 12px; flex-wrap: wrap; }
header nav a { color: var(--texte); text-decoration: none; padding: 2px 4px; }
header nav a:hover { text-decoration: underline; }

.compte-connecte { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.compte-connecte a { color: var(--lien); }

/* La bascule de thème, à côté du compte. Un bouton, pas une case : l'état
   courant se lit dans son libellé. */
/* 🟠 **44 px, comme tout contrôle de la maison** — audit du 23/09, § 5 : le
   bouton « Thème » faisait 36 px, le seul sous la hauteur de contrôle. La
   densité et la déconnexion, voisines, s'alignent sur lui. */
.bascule-theme button,
.compte-connecte button {
  min-height: var(--hauteur-controle);
  border-color: var(--filet);
}
/* Le repli des réglages sur le portail : sans cadre, il reste une ligne. */
.reglages-du-compte {
  border: 0;
  padding: 0;
  margin: 0;
}
.reglages-du-compte summary {
  display: inline-flex;
  align-items: center;
  gap: var(--e2);
  min-height: var(--hauteur-controle);
  font-weight: 400;
}
.reglages-du-compte[open] {
  display: flex;
  flex-wrap: wrap;
  gap: var(--e2);
  align-items: center;
}
.reglages-du-compte[open] summary { margin-bottom: 0; }

/* 🔴 **Pas de plafond de page.** Une largeur maximale est juste pour du
   texte — on ne lit pas une ligne de deux cents caractères — et fausse pour
   un tableau : celui-ci se serrait pendant que la moitié de l'écran restait
   vide. Les 1 500 px s'appliquaient aux deux.

   La prose est bornée là où elle est prose, à 70 `ch` (voir C.9 plus bas) ;
   les formulaires gardent la leur. Le reste prend ce qu'on lui donne. */
main {
  padding: var(--e4);
  /* 🟠 **Le lieu avant l'outil.** `base.html` écrit la bande de filtres
     avant `{% block contenu %}`, donc avant le titre de l'écran : on lisait
     « Filtres — Planning ». Le gabarit ne peut pas s'inverser sans que
     chaque écran reprenne la bande à son compte ; la feuille, elle, le fait
     en une ligne. `order` n'agit que sur le rendu, jamais sur l'ordre de
     lecture d'un lecteur d'écran — c'est sa limite, et ici c'est sans
     conséquence : le titre et un bouton de filtre se lisent dans les deux
     sens. */
  display: flex;
  flex-direction: column;
}
main > * { order: 2; }
main > .entete-d-ecran { order: 0; }
/* 🟠 Le fil d'Ariane **au-dessus** du titre de l'écran (brief du 29/09,
   § 2.7) : il passe avant lui, comme le titre passe avant les filtres. */
main > .fil-d-ariane { order: -1; }
main > .controle { order: 1; }

/* 🟠 **Le pied de page porte des liens, donc des cibles.** Ils mesuraient
   15 px de haut : la police reste petite — c'est un pied de page — mais la
   zone cliquable prend les 24 px du seuil, par du creux et non par des
   lettres plus grandes. */
footer a {
  display: inline-block;
  min-height: 24px;
  padding: var(--e1) 0;
}

footer {
  padding: var(--e3) var(--e4);
  color: var(--texte-discret);
  font-size: 12px;
  border-top: 1px solid var(--filet);
}

/* --- Typographie ----------------------------------------------------------
   🔴 **Plus de serif.** La passe 2 mettait les `h1` en Georgia, faute de
   pouvoir télécharger une police. Le rendu réel était le Times par défaut sur
   les postes sans Georgia — la signature exacte d'une page **non stylée**.
   Inter étant maintenant servie par l'application, les titres la prennent
   comme le reste : une seule famille, quatre graisses.

   L'échelle vient de la maquette validée : 24 / 18 / 15 / 14. */
h1 {
  font-size: 24px;
  font-weight: 600;
  line-height: 1.25;
  color: var(--titre);
  margin: 0 0 4px;
}

/* Le sous-titre d'écran : une phrase, sous le titre, jamais deux. */
h1 + .sous-titre { margin: 0 0 16px; color: var(--texte-discret); }

h2 {
  font-size: 18px;
  font-weight: 600;
  margin: 24px 0 8px;
  border-bottom: 1px solid var(--filet);
  padding-bottom: 4px;
}
/* 🟠 **Plus d'air au-dessus qu'en dessous.** « Localisation »,
   « Exigences » et « Consignes » se suivaient de trop près pour des groupes
   qui n'ont rien à voir : un titre doit appartenir visiblement à ce qui le
   suit, pas flotter entre deux blocs. L'écart au-dessus vaut donc six fois
   celui d'en dessous. */
h3 { font-size: 15px; font-weight: 600; margin: var(--e5) 0 var(--e1); }
h3:first-child { margin-top: 0; }

a { color: var(--lien); }

/* 🔴 **Les chiffres s'alignent.** Cette application affiche des heures, des
   euros, des coefficients et des scores en colonnes. Sans chiffres
   tabulaires, les colonnes ne s'alignent pas et un écart ne se voit pas —
   c'est la déclaration au meilleur rapport effort/résultat de tout ce
   chantier, et elle ne coûte rien. */
table,
.nombre,
.compteur strong,
.candidat .score,
.jauge + span,
.compte-resultats,
.kpi-chiffre,
input[type="number"],
input[type="date"],
input[type="time"],
input[type="month"],
time,
code {
  font-variant-numeric: tabular-nums;
}

/* --- Bandeaux -------------------------------------------------------------
   Quatre canaux, quatre sens, et aucun ne change ici : succès, réserve,
   refus, mode recette. Ils changent d'habit, pas de signification. */
/* Quatre variantes, **bord gauche de 4 px** et rien d'autre : un encadré
   entièrement cerné se dispute la page avec le tableau qu'il commente. */
.bandeau {
  padding: var(--e3) var(--e4);
  margin: 0 0 16px;
  border: 1px solid var(--filet);
  border-left-width: 4px;
  border-radius: var(--rayon-champ);
  background: var(--surface);
  color: var(--texte);
  display: flex;
  gap: 10px;
  align-items: flex-start;
}
.bandeau .icone-svg { flex: 0 0 auto; margin-top: 2px; }
.bandeau-ok { border-left-color: var(--etat-conforme); }
.bandeau-erreur { border-left-color: var(--etat-impossible); }
/* Réussi, mais avec une réserve : ni vert ni rouge — l'ambre se distingue des
   deux d'un coup d'œil, ce qui est tout l'objet du troisième canal. */
.bandeau-avertissement { border-left-color: var(--etat-infraction); }
.bandeau-info { --surface: var(--fond-deduit); border-left-color: var(--etat-deduit); }

/* --- Tableaux -------------------------------------------------------------
   🟡 Filets allégés. Le quadrillage `1px solid #ccc` sur chaque cellule
   dominait la donnée : la hiérarchie vient de la typographie et de l'air, pas
   des boîtes. Les écrans de gestion restent denses — on allège ce qu'on voit,
   pas ce qu'ils montrent. */
table {
  border-collapse: collapse;
  width: 100%;
  margin-bottom: 16px;
  border: 1px solid var(--filet);
  border-radius: var(--rayon-carte);
  overflow: hidden;
}

/* 🟠 **Un tableau dense déborde ; il ne doit pas pousser la page.** Le
   conteneur défile pour lui — le corps de page, lui, ne défile jamais
   horizontalement. C'est le correctif acté par la maquette, et il vaut aussi
   pour les écrans de gestion à quinze colonnes. */
.tableau-defilant {
  overflow-x: auto;
  margin-bottom: 16px;
  border-radius: var(--rayon-carte);
  /* 🔴 **Sans lui, le défilement ne retient pas tout** — `/astreinte`
     débordait de 430 px sur téléphone (audit du 23/09 : + 432 px). Les
     libellés `visuellement-cache` des cellules sont en `position: absolute` ;
     un conteneur non positionné ne les rogne pas, et ils élargissaient la
     page entière, bien au-delà du tableau qui, lui, défilait. */
  position: relative;
}
.tableau-defilant > table { margin-bottom: 0; }

th, td {
  border-bottom: 1px solid var(--filet);
  padding: var(--creux-de-cellule);
  text-align: left;
  /* 🟢 **Centré verticalement**, et c'était déjà le cas. La note le
     recommande quand les hauteurs varient peu : cela répartit le blanc et
     facilite le balayage. Rien à faire, et il faut le dire — sans quoi
     quelqu'un le « corrigera » un jour en pensant bien faire. */
  vertical-align: middle;
}
tbody td { height: var(--hauteur-ligne); }

/* 🔴 **L'en-tête reste visible quand on descend.** Une colonne dont on ne lit
   plus le titre au bout de trente lignes n'est plus une colonne : c'est une
   suite de nombres qu'on interprète de mémoire. */
th {
  position: sticky;
  top: 0;
  z-index: 1;
  font-weight: 600;
  font-size: 13px;
  border-bottom: 2px solid var(--filet);
  color: var(--texte);
}
td.nombre, th.nombre { text-align: right; }

.inactif td { color: var(--texte-discret); font-style: italic; }
.onglet-actif { text-decoration: underline; }

/* --- Formulaires ----------------------------------------------------------
   La zone de saisie se distingue nettement du fond : un champ qui se confond
   avec sa carte ne se voit pas comme un champ. */
form.bloc,
.legende-carte {
  border: 1px solid var(--filet);
  padding: var(--e3);
  margin-bottom: var(--e3);
}
/* --- Le tiroir de filtres -------------------------------------------------
   🔴 **`display` va sous `:popover-open`, jamais sur le sélecteur nu.** Une
   règle d'auteur qui déclare `display` sur un popover écrase le `display:
   none` que le navigateur applique quand il est fermé — le tiroir resterait
   ouvert en permanence. C'est le même piège que sur `<details>`, rencontré
   deux heures plus tôt dans le panneau de navigation : la règle
   `.entree-de-menu { display: flex }` y laissait sept groupes repliés occuper
   889 px.

   🟠 Le tiroir n'est pas centré comme un popover par défaut : il s'ancre en
   haut à gauche du contenu, là où était le panneau. On le rouvre au même
   endroit qu'on l'a fermé. */
form.filtres:popover-open {
  display: flex;
  gap: var(--e3);
  flex-wrap: wrap;
  align-items: flex-end;
  position: fixed;
  inset: auto;
  top: 96px;
  left: 50%;
  transform: translateX(-50%);
  width: min(920px, calc(100vw - var(--e6)));
  max-height: 70vh;
  overflow-y: auto;
  padding: var(--e4);
  z-index: 40;
}
form.filtres::backdrop { background: var(--voile); }

.bouton-filtres {
  display: inline-flex;
  align-items: center;
  gap: var(--e2);
  min-height: var(--hauteur-controle);
  padding: 0 var(--e3);
  border: 1px solid var(--filet);
  border-radius: var(--rayon-champ);
  background: var(--surface-carte);
  color: var(--texte);
  font: inherit;
  cursor: pointer;
}
.bouton-filtres:hover { border-color: var(--accent); }

/* 🔴 **Pas de bouton sans tiroir.** Le bouton est posé par `base.html` sur
   toutes les pages ; les écrans qui ne filtrent pas n'ont pas de
   `.filtres`, et un bouton qui n'ouvre rien est pire qu'un bouton absent —
   on clique, rien ne se passe, et on recommence. */
body:not(:has(form.filtres)) .bouton-filtres { display: none; }

/* Et la bande avec lui, quand elle n'a plus rien à porter : une bande vide
   coûte sa marge sur chaque écran qui ne filtre pas. */
body:not(:has(form.filtres)) .controle:not(:has(p)) { display: none; }

/* Le nombre de filtres posés. 🟠 Il ne paraît que s'il y en a : une pastille
   « 0 » se lit comme un filtre de plus. */
.compte-de-filtres {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 var(--e1);
  border-radius: var(--rayon-pastille);
  background: var(--accent);
  color: var(--sur-accent);
  font-size: 12px;
  font-weight: 700;
}

/* 🔴 **Quatre champs par rangée, jamais six.** « Exigences » en portait
   six sur une ligne, sur l'écran où l'on décide. La note du 20/09 range les
   écrans en deux familles : ceux où l'on balaye doivent rester denses, ceux
   où l'on lit doivent respirer — et la fiche de site est le plus dense du
   projet alors que c'est un écran de décision.

   🟠 **La borne est une largeur, pas un compte.** `minmax(max(240px, …))`
   donne à chaque colonne au moins le quart de la rangée : il ne peut donc pas
   y en avoir cinq. Un compte figé aurait tenu jusqu'au premier écran étroit. */
.grille { display: flex; flex-wrap: wrap; gap: var(--e3); align-items: start; }

/* 🔴 **`grille-formulaire` n'avait aucune règle — audit du 23/09.** Les
   écrans du devis étaient les seuls à l'employer, et les seuls à ne pas
   suivre la charte : sur un écran de 2 560 px, sept champs empilés, **chacun
   large de 2 178 px**. Un code postal de deux mètres, et le bouton
   d'enregistrement à deux mille pixels de son libellé.

   🟠 Le même comportement que `.grille`, dont elle n'était qu'un nom
   différent. Elle ne se résorbe pas en la renommant partout : deux gabarits
   l'emploient, et un alias coûte moins qu'un renommage. */
.grille-formulaire {
  display: flex;
  flex-wrap: wrap;
  gap: var(--e3);
  align-items: start;
}

.champ {
  display: flex;
  flex-direction: column;
  min-width: 240px;
  /* 🔴 **Un quart de la rangée, donc quatre par ligne au plus.** La base de
     `200px` en laissait entrer six sur un écran large — et « Exigences » en
     portait six, sur l'écran où l'on décide. Un quart de la largeur moins
     l'écart : un cinquième champ ne tient plus et passe à la ligne.

     🟠 C'est une largeur, pas un compte. Un compte figé aurait tenu jusqu'au
     premier écran étroit ; `min-width` s'en charge en descendant. */
  flex: 1 1 calc(25% - var(--e3));
}
.champ span { font-weight: 600; font-size: 12px; margin-bottom: var(--e0); }
/* 🟠 **Les textes d'aide sont petits et serrés, et ce sont les plus utiles du
   projet.** On ne les retire pas — la note est formelle — on les rend
   lisibles : un interligne plus généreux que celui du corps. */
.champ small { line-height: 1.5; }
.champ em { color: var(--etat-impossible); font-style: normal; }
/* 🟠 **Deux lignes réservées à l'aide, là où il y en a une.** Les textes
   d'explication mesurent 18, 36 ou 54 px selon leur longueur, et la rangée
   prend la hauteur du plus grand : une aide d'une ligne laissait un décalage
   de 18 px avec sa voisine de deux. Réserver deux lignes régularise le cas
   courant — et ne coûte rien aux champs qui n'ont pas d'aide, puisque la
   règle ne s'applique qu'à un `small` qui existe. */
.champ small { display: block; min-height: 2.6em; }

/* La ligne de service d'une fiche : le retour à gauche, ce qui retire la
   fiche de la circulation à droite, et rien entre les deux. */
.barre-de-fiche {
  display: flex;
  align-items: center;
  gap: var(--e2);
  margin: var(--e2) 0 var(--e3);
}
.barre-de-fiche .action-de-retrait { margin-left: auto; }
.champ small { color: var(--texte-discret); font-size: 12px; margin-top: 2px; }
.champ-large { flex: 1 1 100%; }
/* 🟠 **Une zone de notes ne dépasse pas une ligne lisible** — audit du 23/09,
   § 5 : sur un écran de 2 560 px, plus de 300 caractères par ligne. Le champ
   garde toute la place en largeur de colonne ; c'est la zone de texte qui
   s'arrête à une mesure de lecture. */
.champ-large textarea { max-inline-size: 80ch; }
/* 🟠 `flex: 0 0 auto` : la case et son intitulé ne se partagent pas la
   rangée comme un champ ordinaire — ils font un bloc, et ce bloc prend la
   largeur de son texte. */
.champ-case { flex-direction: row; align-items: center; gap: var(--e2);
              flex: 0 0 auto; }
.champ-case span { margin: 0; }
/* 🔴 **Sur un téléphone, ce bloc débordait de 372 px.** « Accord préalable
   exigé pour dépassement » et son aide sur une seule ligne mesuraient 733 px
   dans un écran de 388 — et ce sont ces deux cases qui décident si un
   dépassement est facturable.

   Sous 760 px, la case garde sa ligne avec l'intitulé ; l'aide passe
   dessous. `flex-wrap` plutôt qu'une colonne : une case à cocher séparée de
   son libellé cesse d'être cliquable au libellé. */
@media (max-width: 760px) {
  .champ-case {
    flex-wrap: wrap;
    flex: 1 1 100%;
    min-width: 0;
  }
  .champ-case span { flex: 1 1 auto; min-width: 0; }
  /* Aligné sous l'intitulé : 24 px de case plus l'écart. Par le
     jeton, comme tout ce qui s'ajoute depuis la garde d'échelle. */
  .champ-case small { flex: 1 1 100%; margin-left: var(--e6); }
}

/* 🔴 Une zone de saisie est une surface : elle porte du texte sur un fond qui
   n'est pas celui de la carte. Elle déclare donc `--surface`, comme les
   autres — c'est ce qui la fait entrer dans la mesure de contraste. */
input, select, textarea {
  --surface: var(--surface-carte);
  font-family: inherit;
  font-size: 14px;
  min-height: var(--hauteur-controle);
  padding: 8px 10px;
  border: 1px solid var(--filet-controle);
  border-radius: var(--rayon-champ);
  background: var(--surface);
  color: var(--texte);
}
/* 🔴 **24 px, partout.** WCAG 2.2 pose 24 × 24 pour toute cible, sans
   exception pour les cases à cocher : 18 px, c'est les trois quarts. La
   règle vivait déjà sur le portail (plus bas) ; l'écran de gestion en avait
   autant besoin — c'est là qu'on coche « purger », « retenir », « clore ».

   `accent-color` teinte la coche ; la taille, elle, porte sur la boîte. */
input[type="checkbox"], input[type="radio"] {
  min-height: 0;
  width: 24px;
  height: 24px;
  /* 🟠 **Un contour, et non une bordure.** Ces deux-là gardent leur rendu
     natif — `appearance: auto` — parce qu'il porte la coche, le point, et
     l'état indéterminé que personne ne redessine bien. Or `border` ne
     s'applique pas à un contrôle natif : seul `outline` se peint. Le
     bouton radio reçoit le sien arrondi, sinon un cercle dans un carré.

     Au focus, l'anneau de focus le remplace — c'est voulu : il est plus
     visible, et deux anneaux concentriques ne disent rien de plus. */
  outline: 1px solid var(--filet-controle);
  outline-offset: 1px;
  accent-color: var(--accent);
}
/* 🔴 **Le gris du navigateur ne connaît pas le thème.** Sans règle,
   `::placeholder` vaut `#757575` dans les deux thèmes : 3,47:1 en sombre,
   sous le seuil. Le rattacher au jeton le fait suivre le fond.

   🟠 `opacity: 1` est nécessaire : Firefox applique une transparence par
   défaut au placeholder, qui rabattrait la couleur qu'on vient de poser. */
input::placeholder, textarea::placeholder {
  color: var(--texte-discret);
  opacity: 1;
}

/* Un champ désactivé prend la surface d'appui : il se lit encore, il ne se
   saisit plus. Le griser jusqu'à l'illisible cacherait ce qu'il contient. */
input:disabled, select:disabled, textarea:disabled {
  --surface: var(--surface-appui);
  color: var(--texte-discret);
  cursor: not-allowed;
}
/* Un champ en erreur porte sa bordure **et** son message : la couleur seule
   ne dit pas ce qui ne va pas. */
.champ-en-erreur input, .champ-en-erreur select, .champ-en-erreur textarea {
  border-color: var(--etat-impossible);
  border-width: 2px;
}
.champ-en-erreur .message-d-erreur {
  color: var(--etat-impossible);
  font-size: 12px;
  margin-top: 4px;
}
/* 🔴 **Le focus clavier se voit dans les deux thèmes**, et il se voit sur
   tout ce qui se focalise — pas seulement sur les champs. `--lien` est la
   couleur d'action de la surface courante : elle change avec le thème, donc
   le contour aussi. Le décalage l'empêche de se confondre avec la bordure du
   champ, qui est de la même famille de gris. */
input:focus, select:focus, textarea:focus,
a:focus-visible, button:focus-visible, summary:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--lien);
  outline-offset: 2px;
}

/* --- Les cinq rôles de bouton ---------------------------------------------
   Secondaire par défaut — c'est le cas le plus fréquent : filtrer, exporter,
   annuler. Les quatre autres se demandent par une classe. */
button {
  font-family: inherit;
  font-size: 14px;
  font-weight: 500;
  padding: var(--e2) var(--e4);
  border: 1px solid var(--filet);
  border-radius: var(--rayon-champ);
  cursor: pointer;
  min-height: var(--hauteur-controle);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

/* 🔴 **Le primaire : fond accent, texte marine.** Jamais de texte blanc sur
   ce bleu — 2,48 de contraste, illisible. Le marine y donne 6,06. Une seule
   action primaire par écran : deux boutons bleus ne hiérarchisent rien. */
button.primaire, .bouton-primaire {
  --surface: var(--accent);
  /* 🔴 **Une surface redéclare tout son texte, sans exception.** Sur ce bleu,
     le gris discret tombe à 2,21 et le vert « conforme » à 2,24 : rien de la
     palette ordinaire n'y est lisible. Un bouton primaire ne porte de toute
     façon qu'un libellé et parfois une icône — tout y est marine, et la
     mesure de contraste le vérifie au lieu de le supposer. */
  --texte: var(--sur-accent);
  --texte-discret: var(--sur-accent);
  --lien: var(--sur-accent);
  --etat-impossible: var(--sur-accent);
  --etat-infraction: var(--sur-accent);
  --etat-conforme: var(--sur-accent);
  --etat-deduit: var(--sur-accent);
  --texte-pourvoir: var(--sur-accent);
  background: var(--surface);
  border-color: var(--accent);
  color: var(--texte);
  font-weight: 600;
}
button.primaire:hover, .bouton-primaire:hover {
  filter: brightness(1.06);
  border-color: var(--accent);
}
/* 🔴 Audit du 26/09, T14 : l'appui doit se voir. Sans état actif, rien ne
   bougeait sous le doigt, et l'agent appuyait une seconde fois. */
button.primaire:active, .bouton-primaire:active {
  filter: brightness(0.9);
  transform: translateY(1px);
}
.bouton-primaire[aria-busy="true"], button[aria-busy="true"] {
  cursor: progress;
  opacity: 0.8;
}

/* Tertiaire : un lien qui se comporte en bouton. Actions de ligne, « tout
   voir ». */
button.discret {
  --surface: transparent;
  background: none;
  border-color: transparent;
  color: var(--lien);
  padding: 8px 6px;
}
button.discret:hover { --surface: var(--surface-appui); }

button.attention { border-color: var(--etat-impossible); color: var(--etat-impossible); }

/* Bouton d'icône : carré, et **une info-bulle obligatoire** — une icône seule
   sans nom est une devinette. */
button.icone {
  width: 36px;
  min-height: 36px;
  padding: 0;
}

button:disabled {
  --surface: var(--surface-appui);
  color: var(--texte-discret);
  cursor: not-allowed;
}

.en-ligne { display: inline; }

.onglets {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
  margin: 12px 0 0;
  border-bottom: 1px solid var(--filet);
}
.onglets button { border-bottom: none; }
.onglets button[aria-selected="true"] { font-weight: 700; }
.panneau { border: 1px solid var(--filet); border-top: none; padding: 12px; }
.panneau[hidden] { display: none; }

/* --- Étiquettes d'état ----------------------------------------------------
   🔴 **La couleur dit un état, jamais une décoration.** Hors des quatre sens
   ci-dessous, aucune couleur n'entre dans une zone de données. */
.alerte {
  display: inline-block;
  padding: 1px 6px;
  margin: 1px 2px 1px 0;
  border: 1px solid;
  font-size: 12px;
  background: var(--surface);
  color: var(--texte);
}
.alerte-depassee { border-color: var(--etat-impossible); }
.alerte-proche { border-color: var(--etat-infraction); }

/* --- Compteur de couverture, en tête de planning ------------------------- */
.compteur-couverture .compteurs { margin: 0 0 6px; font-size: 15px; }
.compteur-couverture .compteurs a { text-decoration: none; }
.compteur-couverture .compteurs a:hover { text-decoration: underline; }

/* --- Classement de dispatch ----------------------------------------------
   Le détail du calcul est affiché sous chaque candidat : un opérateur qui voit
   « 82/100 » sans savoir pourquoi finit par suivre l'ordre sans le lire. */
.candidats { padding-left: 24px; }
.candidat { border: 1px solid var(--filet); padding: 8px 12px; margin-bottom: 8px; }
.candidat-tete { margin: 0 0 4px; }
.candidat-detail { margin: 0 0 3px; }
.candidat .score {
  display: inline-block;
  font-weight: 700;
  font-size: 15px;
  padding: 1px 8px;
  margin-left: 6px;
}

/* --- Mouvements du registre ----------------------------------------------
   Une ligne de correction doit se lire sans effort : c'est ce document qu'on
   produit à côté du registre lors d'un contrôle. */
.mouvements { list-style: none; margin: 0; padding: 0; }
.mouvement {
  border-left: 3px solid var(--lien);
  padding: 8px 12px;
  margin-bottom: 8px;
}
.mouvement-tete { margin: 0 0 4px; }
.mouvement-detail { margin: 0 0 2px 16px; }

/* --- Panneau latéral, jamais modale --------------------------------------
   Règle d'interface opposable à toute la suite du projet : le formulaire
   s'ouvre sur le côté, SANS voile ni blocage. Le tableau reste lisible
   derrière — c'est souvent la ligne qu'on est en train de modifier. Aucun
   `position: fixed` couvrant, aucun fond assombri, aucun empilement. */
.panneau-lateral {
  position: fixed;
  top: 0;
  right: 0;
  width: min(440px, 92vw);
  height: 100%;
  overflow-y: auto;
  --surface: light-dark(#FFFFFF, #16324E);
  background: var(--surface);
  color: var(--texte);
  border-left: 2px solid var(--lien);
  box-shadow: -2px 0 6px var(--ombre);
  padding: 16px;
  transform: translateX(100%);
  transition: transform .15s ease-out;
  z-index: 20;
}
.panneau-lateral.ouvert { transform: translateX(0); }
.panneau-lateral .mention-panneau { margin-top: 0; }
/* Le corps se décale au lieu d'être recouvert : rien n'est masqué. */
body.panneau-ouvert main { padding-right: min(440px, 92vw); }
@media (max-width: 760px) { body.panneau-ouvert main { padding-right: 0; } }

.jauge {
  display: inline-block;
  width: 90px;
  height: 10px;
  border: 1px solid var(--filet);
  vertical-align: middle;
}
.jauge span { display: block; height: 100%; background: var(--etat-conforme); }
.jauge-faible span { background: var(--etat-impossible); }
.jauge-moyenne span { background: var(--etat-infraction); }

/* Audit du 26/09, S1 : la largeur d'une jauge et la part d'un score sont des
   classes (filtres Jinja largeur et part), plus un style en ligne que la
   politique de contenu refuse. Une règle par centième, de 0 à 100. */
.largeur-0 { width: 0%; }
.largeur-1 { width: 1%; }
.largeur-2 { width: 2%; }
.largeur-3 { width: 3%; }
.largeur-4 { width: 4%; }
.largeur-5 { width: 5%; }
.largeur-6 { width: 6%; }
.largeur-7 { width: 7%; }
.largeur-8 { width: 8%; }
.largeur-9 { width: 9%; }
.largeur-10 { width: 10%; }
.largeur-11 { width: 11%; }
.largeur-12 { width: 12%; }
.largeur-13 { width: 13%; }
.largeur-14 { width: 14%; }
.largeur-15 { width: 15%; }
.largeur-16 { width: 16%; }
.largeur-17 { width: 17%; }
.largeur-18 { width: 18%; }
.largeur-19 { width: 19%; }
.largeur-20 { width: 20%; }
.largeur-21 { width: 21%; }
.largeur-22 { width: 22%; }
.largeur-23 { width: 23%; }
.largeur-24 { width: 24%; }
.largeur-25 { width: 25%; }
.largeur-26 { width: 26%; }
.largeur-27 { width: 27%; }
.largeur-28 { width: 28%; }
.largeur-29 { width: 29%; }
.largeur-30 { width: 30%; }
.largeur-31 { width: 31%; }
.largeur-32 { width: 32%; }
.largeur-33 { width: 33%; }
.largeur-34 { width: 34%; }
.largeur-35 { width: 35%; }
.largeur-36 { width: 36%; }
.largeur-37 { width: 37%; }
.largeur-38 { width: 38%; }
.largeur-39 { width: 39%; }
.largeur-40 { width: 40%; }
.largeur-41 { width: 41%; }
.largeur-42 { width: 42%; }
.largeur-43 { width: 43%; }
.largeur-44 { width: 44%; }
.largeur-45 { width: 45%; }
.largeur-46 { width: 46%; }
.largeur-47 { width: 47%; }
.largeur-48 { width: 48%; }
.largeur-49 { width: 49%; }
.largeur-50 { width: 50%; }
.largeur-51 { width: 51%; }
.largeur-52 { width: 52%; }
.largeur-53 { width: 53%; }
.largeur-54 { width: 54%; }
.largeur-55 { width: 55%; }
.largeur-56 { width: 56%; }
.largeur-57 { width: 57%; }
.largeur-58 { width: 58%; }
.largeur-59 { width: 59%; }
.largeur-60 { width: 60%; }
.largeur-61 { width: 61%; }
.largeur-62 { width: 62%; }
.largeur-63 { width: 63%; }
.largeur-64 { width: 64%; }
.largeur-65 { width: 65%; }
.largeur-66 { width: 66%; }
.largeur-67 { width: 67%; }
.largeur-68 { width: 68%; }
.largeur-69 { width: 69%; }
.largeur-70 { width: 70%; }
.largeur-71 { width: 71%; }
.largeur-72 { width: 72%; }
.largeur-73 { width: 73%; }
.largeur-74 { width: 74%; }
.largeur-75 { width: 75%; }
.largeur-76 { width: 76%; }
.largeur-77 { width: 77%; }
.largeur-78 { width: 78%; }
.largeur-79 { width: 79%; }
.largeur-80 { width: 80%; }
.largeur-81 { width: 81%; }
.largeur-82 { width: 82%; }
.largeur-83 { width: 83%; }
.largeur-84 { width: 84%; }
.largeur-85 { width: 85%; }
.largeur-86 { width: 86%; }
.largeur-87 { width: 87%; }
.largeur-88 { width: 88%; }
.largeur-89 { width: 89%; }
.largeur-90 { width: 90%; }
.largeur-91 { width: 91%; }
.largeur-92 { width: 92%; }
.largeur-93 { width: 93%; }
.largeur-94 { width: 94%; }
.largeur-95 { width: 95%; }
.largeur-96 { width: 96%; }
.largeur-97 { width: 97%; }
.largeur-98 { width: 98%; }
.largeur-99 { width: 99%; }
.largeur-100 { width: 100%; }
.part-0 { --part: 0; }
.part-1 { --part: 1; }
.part-2 { --part: 2; }
.part-3 { --part: 3; }
.part-4 { --part: 4; }
.part-5 { --part: 5; }
.part-6 { --part: 6; }
.part-7 { --part: 7; }
.part-8 { --part: 8; }
.part-9 { --part: 9; }
.part-10 { --part: 10; }
.part-11 { --part: 11; }
.part-12 { --part: 12; }
.part-13 { --part: 13; }
.part-14 { --part: 14; }
.part-15 { --part: 15; }
.part-16 { --part: 16; }
.part-17 { --part: 17; }
.part-18 { --part: 18; }
.part-19 { --part: 19; }
.part-20 { --part: 20; }
.part-21 { --part: 21; }
.part-22 { --part: 22; }
.part-23 { --part: 23; }
.part-24 { --part: 24; }
.part-25 { --part: 25; }
.part-26 { --part: 26; }
.part-27 { --part: 27; }
.part-28 { --part: 28; }
.part-29 { --part: 29; }
.part-30 { --part: 30; }
.part-31 { --part: 31; }
.part-32 { --part: 32; }
.part-33 { --part: 33; }
.part-34 { --part: 34; }
.part-35 { --part: 35; }
.part-36 { --part: 36; }
.part-37 { --part: 37; }
.part-38 { --part: 38; }
.part-39 { --part: 39; }
.part-40 { --part: 40; }
.part-41 { --part: 41; }
.part-42 { --part: 42; }
.part-43 { --part: 43; }
.part-44 { --part: 44; }
.part-45 { --part: 45; }
.part-46 { --part: 46; }
.part-47 { --part: 47; }
.part-48 { --part: 48; }
.part-49 { --part: 49; }
.part-50 { --part: 50; }
.part-51 { --part: 51; }
.part-52 { --part: 52; }
.part-53 { --part: 53; }
.part-54 { --part: 54; }
.part-55 { --part: 55; }
.part-56 { --part: 56; }
.part-57 { --part: 57; }
.part-58 { --part: 58; }
.part-59 { --part: 59; }
.part-60 { --part: 60; }
.part-61 { --part: 61; }
.part-62 { --part: 62; }
.part-63 { --part: 63; }
.part-64 { --part: 64; }
.part-65 { --part: 65; }
.part-66 { --part: 66; }
.part-67 { --part: 67; }
.part-68 { --part: 68; }
.part-69 { --part: 69; }
.part-70 { --part: 70; }
.part-71 { --part: 71; }
.part-72 { --part: 72; }
.part-73 { --part: 73; }
.part-74 { --part: 74; }
.part-75 { --part: 75; }
.part-76 { --part: 76; }
.part-77 { --part: 77; }
.part-78 { --part: 78; }
.part-79 { --part: 79; }
.part-80 { --part: 80; }
.part-81 { --part: 81; }
.part-82 { --part: 82; }
.part-83 { --part: 83; }
.part-84 { --part: 84; }
.part-85 { --part: 85; }
.part-86 { --part: 86; }
.part-87 { --part: 87; }
.part-88 { --part: 88; }
.part-89 { --part: 89; }
.part-90 { --part: 90; }
.part-91 { --part: 91; }
.part-92 { --part: 92; }
.part-93 { --part: 93; }
.part-94 { --part: 94; }
.part-95 { --part: 95; }
.part-96 { --part: 96; }
.part-97 { --part: 97; }
.part-98 { --part: 98; }
.part-99 { --part: 99; }
.part-100 { --part: 100; }

.compteurs { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }
.compteur { border: 1px solid var(--filet); padding: 8px 12px; min-width: 130px; }
.compteur strong { display: block; font-size: 22px; }
.compteur span { font-size: 12px; color: var(--texte-discret); }
/* L'etape courante du pipeline de prospection. Un filet, pas un fond : changer
   la surface obligerait a y redeclarer le texte, et une etape en cours n'est
   ni une alerte ni une infraction. */
.compteur-actif { border-color: var(--lien); border-width: 2px; padding: 7px 11px; }

/* --- Les puces de filtre et le compteur -----------------------------------
   Elles repondent a « qu'est-ce que je regarde » sans rouvrir le panneau de
   filtre. Un filtre invisible piege : trois lignes sous un menu referme se
   lisent « il n'y a que trois vacations ». */
.puces-de-filtre {
  display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  margin: 0 0 12px;
}
.compte-resultats { font-weight: 700; color: var(--texte); }
.puce {
  display: inline-flex; align-items: center; gap: 6px;
  border: 1px solid var(--filet); padding: 3px 4px 3px 10px;
  font-size: 14px; color: var(--texte);
}
.puce strong { font-weight: 600; }
.puce-retirer {
  /* 24 px de cible : la meme regle que le chantier visuel du portail. */
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 24px; min-height: 24px;
  text-decoration: none; color: var(--lien); font-size: 16px; line-height: 1;
}
.puce-retirer:hover, .puce-retirer:focus-visible { background: var(--fond-deduit); }
.puce-tout-effacer { font-size: 14px; }

/* Les vues enregistrees : des signets personnels, en tete de l'ecran. */
.vues-enregistrees {
  display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  margin: 0 0 12px;
}
.vue {
  display: inline-flex; align-items: center; gap: 4px;
  border: 1px solid var(--filet); padding: 3px 4px 3px 10px; font-size: 14px;
}
.vue-retirer {
  min-width: 24px; min-height: 24px; padding: 0;
  background: none; border: none; color: var(--lien);
  font-size: 16px; line-height: 1; cursor: pointer;
}
.vue-retirer:hover, .vue-retirer:focus-visible { background: var(--fond-deduit); }

/* --- La palette de commandes (Ctrl+K) ------------------------------------
   Une surimpression, et non une modale de formulaire : la regle du projet
   interdit les modales pour SAISIR, parce qu'il faut pouvoir lire la ligne
   qu'on modifie. Ici on ne saisit rien qu'on enregistre — on choisit une
   destination, et la page dessous n'a plus a etre lue. */
/* 🔴 **Hors du flux, pas seulement invisible.** Un élément de 1 px reste un
   enfant de grille : la case du repli occupait une cellule, et la barre
   latérale se retrouvait en colonne 2. Un contenu caché ne doit rien
   occuper. */
.visuellement-cache {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

/* Le sprite d'icônes : il n'affiche rien, il ne doit rien mesurer. */
.jeu-d-icones {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}
.palette {
  position: fixed; inset: 0; z-index: 60;
  background: var(--voile);
  display: flex; justify-content: center; align-items: flex-start;
  padding: 10vh 16px 16px;
}
.palette[hidden] { display: none; }
.palette-boite {
  --surface: light-dark(#FFFFFF, #16324E);
  --texte: light-dark(#1A1A1A, #F2F5F7);
  --texte-discret: light-dark(#5A5A5A, #B0B0B0);
  --lien: light-dark(#00719F, #00B0F0);
  background: var(--surface); color: var(--texte);
  border: 1px solid var(--filet); box-shadow: 0 12px 40px var(--ombre);
  width: min(640px, 100%); max-height: 70vh; display: flex;
  flex-direction: column;
}
.palette-champ { display: block; padding: 12px; border-bottom: 1px solid var(--filet); }
.palette-champ input {
  width: 100%; font-size: 17px; padding: 10px 12px;
  background: var(--surface); color: var(--texte);
  border: 1px solid var(--filet);
}
.palette-liste { list-style: none; margin: 0; padding: 0; overflow-y: auto; }
.palette-liste li {
  padding: var(--e3) var(--e4); cursor: pointer; display: flex; gap: 10px;
  align-items: baseline; border-bottom: 1px solid var(--filet);
}
.palette-liste li:last-child { border-bottom: none; }
.palette-liste li[aria-selected="true"] { background: var(--fond-deduit); }
.palette-liste .palette-libelle { color: var(--texte); }
.palette-liste .palette-categorie { font-size: 12px; color: var(--texte-discret); }
.palette-liste .palette-vide { color: var(--texte-discret); cursor: default; }
.palette-aide { padding: var(--e2) var(--e4); margin: 0; border-top: 1px solid var(--filet); font-size: 12px; }

.avertissement {
  --surface: var(--fond-infraction);
  background: var(--surface);
  border: 1px solid var(--etat-infraction);
  padding: 8px 10px;
  margin-bottom: 12px;
}

.masque { border: 1px dashed var(--filet); padding: 8px 10px; }
.masque code { background: var(--surface); padding: 1px 4px; }

.manquants { color: var(--etat-impossible); font-size: 12px; }

/* 🔴 **La valeur conservée parce qu'elle n'est plus dans la liste.** Ambre
   et non rouge : ce n'est pas une infraction, c'est une donnée que personne
   n'a décidé de perdre. L'écran la garde, la nomme, et laisse le choix.
   🟠 Aucune valeur littérale ici — le creux vient de l'échelle, faute de
   quoi la garde du 21/09 refuserait la règle. */
/* 🔴 **Le statut déduit des contrats, posé au-dessus du volet.** Il dit un
   fait ; le volet, en dessous, dit une déclaration. Les deux se lisent, et
   c'est leur écart qui est l'information.
   🟠 Les creux viennent de l'échelle : la garde du 21/09 refuse un littéral. */
.statut-deduit {
  display: flex;
  flex-direction: column;
  gap: var(--e0);
  padding: var(--e3);
  margin-bottom: var(--e3);
  border-left: 3px solid var(--etat-conforme);
  background: var(--surface-creux);
}

/* L'indéterminé est ambre et non rouge : ce n'est pas une infraction, c'est
   une contradiction entre deux sources — et elle se résout par une saisie. */
.statut-indetermine { border-left-color: var(--etat-infraction); }

/* 🔴 **Les champs d'une société inconnue, révélés par le choix lui-même.**

   Correctif du 22/09 : l'écran « Ouvrir un devis » réclamait un numéro de
   client indevinable. Il propose désormais une liste, dont la dernière
   entrée ouvre trois champs.

   🟠 **Sans une ligne de script.** `:has()` lit la valeur du `<select>` du
   même formulaire ; un script ferait un second endroit où la règle vit, et
   l'écran resterait muet si le script échouait à charger. */
.societe-nouvelle { display: none; }

form:has(select[name="destinataire"] option[value="nouvelle"]:checked)
  .societe-nouvelle {
  display: block;
  padding: var(--e3);
  margin-top: var(--e3);
  border-left: 3px solid var(--accent);
  background: var(--surface-creux);
}

/* 🔴 **La proposition de contrat ouverte, sous la ligne du poste.** Elle dit
   ce qu'elle propose — nature, dates, heures — et porte les deux réponses.
   Avant le 23/09, les routes existaient et aucun bouton n'y menait. */
.ligne-proposition > td {
  background: var(--surface-creux);
  border-left: 3px solid var(--etat-infraction);
}

.ligne-proposition form { margin-top: var(--e2); }

/* 🟠 **Un numéro de série se lit en chiffres alignés.** `C-0007` et `C-0012`
   doivent se comparer d'un coup d'œil dans une colonne ; une police
   proportionnelle décale les chiffres et oblige à lire. */
.numero-de-serie {
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  color: var(--texte-discret);
}

.mention-hors-liste {
  color: var(--etat-infraction);
  font-size: 12px;
  display: flex;
  align-items: center;
  gap: var(--e0);
}
.discret-texte { color: var(--texte-discret); font-size: 12px; }

/* 🟡 `<div class="bloc">` n'avait aucun style — soixante-six emplois sur
   vingt-sept gabarits. Lui en donner un serait réorganiser des écrans, pas
   changer leur habit : hors périmètre de cette passe. */
details {
  border: 1px solid var(--filet);
  padding: 8px 10px;
  margin-bottom: 10px;
}
details summary { cursor: pointer; font-weight: 600; }
details[open] summary { margin-bottom: 8px; }

/* =========================================================================
   Le kit — badges, pastilles, cartes KPI, icônes, ruban
   =========================================================================
   Repris de la maquette validée du 02/09. Ces classes n'inventent aucun
   écran : elles donnent leur forme à ce que les gabarits posent déjà. */

/* --- Icônes ---------------------------------------------------------------
   Un `<use>` vers le sprite de `_icones.html`. `currentColor` fait suivre la
   couleur du texte : une icône ne porte donc jamais de couleur écrite en dur,
   et elle change de thème avec la surface où elle se trouve. */
.icone-svg {
  width: 16px;
  height: 16px;
  vertical-align: -2px;
  flex: 0 0 auto;
}
.icone-svg-20 { width: 20px; height: 20px; }
.icone-svg-24 { width: 24px; height: 24px; }

/* --- Badges ---------------------------------------------------------------
   🔴 **Deux à quatre mots.** Un badge qui s'étale sur trois lignes casse la
   hauteur de ligne du tableau et cesse de se lire d'un coup d'œil : le texte
   long va dans l'info-bulle, ce que `title` fait déjà. */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-height: 22px;
  padding: 2px 10px;
  border-radius: var(--rayon-pastille);
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
  background: var(--surface);
  color: var(--texte);
}
.badge-conforme { --surface: var(--fond-conforme); --texte: var(--etat-conforme); }
.badge-infraction { --surface: var(--fond-infraction); --texte: var(--etat-infraction); }
.badge-impossible { --surface: var(--fond-impossible); --texte: var(--etat-impossible); }
.badge-deduit { --surface: var(--fond-deduit); --texte: var(--etat-deduit); }
.badge-pourvoir { --surface: var(--fond-pourvoir); --texte: var(--texte-pourvoir); }
.badge-neutre { --surface: var(--surface-appui); --texte: var(--texte-discret); }

/* --- C.10 : un constat est une phrase, pas un état -------------------------
   Le tableau de bord porte le message d'alerte entier dans une pastille, et
   `white-space: nowrap` — juste pour « conforme » ou « à pourvoir » — en
   faisait une ligne insécable. Le tableau s'étendait à 1 709 px dans un
   conteneur de 1 426, et la colonne d'action, la dernière, se retrouvait
   **hors champ à 100 %** : le lien « Ouvrir la fiche » de chaque alerte
   n'était atteignable qu'en faisant défiler le tableau à l'horizontale.

   La charpente refaite n'y a rien changé — la largeur ne manquait pas, une
   seule cellule la prenait. 62 `ch` bornent le constat ; la pastille y
   revient à la ligne, et les pastilles d'état gardent leur `nowrap`. */
td.constat, th.constat { max-width: 62ch; }
td.constat .badge { white-space: normal; text-align: left; }

/* La pastille : un point devant un texte, quand le badge serait trop lourd.
   🔴 Elle ne remplace jamais le mot — un statut porté par la seule couleur
   n'existe pas pour qui ne la distingue pas. */
.pastille-etat {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: var(--rayon-pastille);
  margin-right: 6px;
  background: currentColor;
}

/* --- Cartes KPI -----------------------------------------------------------
   Le chiffre d'abord, son libellé ensuite, la précision en dessous. Toute la
   carte est cliquable : un chiffre qui interpelle doit mener quelque part. */
.kpi {
  --surface: var(--surface-carte);
  background: var(--surface);
  border: 1px solid var(--filet);
  border-radius: var(--rayon-carte);
  box-shadow: var(--ombre);
  padding: 16px;
  display: flex;
  gap: 12px;
  align-items: flex-start;
  min-width: 200px;
  flex: 1 1 200px;
  color: var(--texte);
  text-decoration: none;
}
.kpi:hover { border-color: var(--lien); }
.kpi-disque {
  --surface: var(--fond-deduit);
  background: var(--surface);
  color: var(--etat-deduit);
  border-radius: var(--rayon-pastille);
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
}
.kpi-chiffre { display: block; font-size: 28px; font-weight: 700; line-height: 1.1; }
.kpi-libelle { display: block; font-size: 13px; }
.kpi-precision { display: block; font-size: 12px; color: var(--texte-discret); }

/* =========================================================================
   Les trois gabarits — liste, fiche, assistant
   =========================================================================
   Leur forme vient des maquettes validées ; leurs macros vivent dans
   `_gabarits.html`. Rien ici ne décide de ce qui s'affiche. */

/* --- L'en-tête d'écran --------------------------------------------------- */
/* --- Les deux bandes : commande, puis contrôle ----------------------------
   🔴 **Sept bandes avant la donnée, mesurées le 18/09 sur le planning** :
   en-tête fin 57, ruban de recette 52, puces 32, vues 40, en-tête d'écran 71,
   onglets 36, période 40, compteurs 40, filtres 154 — la première vacation à
   624 px. Deux bandes suffisent à tout dire. */
.commande {
  display: flex;
  align-items: center;
  gap: var(--e3);
  flex-wrap: wrap;
  margin-bottom: var(--e3);
}
.commande h1 { font-size: 19px; line-height: 1.25; margin: 0; }
/* Le sous-titre suit le titre sur la même ligne, et repasse dessous quand la
   ligne est trop étroite. Il n'est pas supprimé : il porte ce que l'écran
   montre, ou ce qu'il ne montre pas. */
.commande .sous-titre { margin: 0; color: var(--texte-discret); }
/* 🟠 `margin-left: auto` pousse les actions à droite sans colonne ni grille :
   une seule déclaration, et elle survit à un compteur ajouté au milieu. */
.commande .actions-d-ecran { margin-left: auto; }

.controle {
  display: flex;
  align-items: center;
  gap: var(--e2);
  flex-wrap: wrap;
  margin-bottom: var(--e3);
}
.controle > p { margin: 0; }

.entete-d-ecran {
  display: flex;
  align-items: center;
  gap: var(--e4);
  flex-wrap: wrap;
  margin-bottom: var(--e3);
}
.actions-d-ecran { display: flex; gap: var(--e2); align-items: center; flex-wrap: wrap; }

/* Un lien qui se comporte en bouton : même hauteur, même cible de 40 px. */
.bouton-primaire, .bouton-secondaire {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: var(--hauteur-controle);
  padding: var(--e2) var(--e4);
  border: 1px solid var(--filet);
  border-radius: var(--rayon-champ);
  font-weight: 500;
  text-decoration: none;
}
.bouton-secondaire {
  --surface: var(--surface-carte);
  background: var(--surface);
  color: var(--texte);
}
.bouton-secondaire:hover { --surface: var(--surface-appui); text-decoration: none; }

/* --- La barre d'outils d'une liste --------------------------------------- */
.barre-d-outils { align-items: flex-end; }
.champ-recherche { flex: 1 1 240px; min-width: 200px; }

/* --- L'état vide ---------------------------------------------------------
   🔴 Il dit **pourquoi**, et ce qu'on peut faire. « Aucun résultat » seul
   laisse croire à un défaut ; c'est le plus souvent un filtre trop étroit. */
.etat-vide {
  --surface: var(--surface-carte);
  background: var(--surface);
  border: 1px dashed var(--filet);
  border-radius: var(--rayon-carte);
  padding: 32px 16px;
  text-align: center;
  color: var(--texte-discret);
}
.etat-vide .icone-svg { color: var(--texte-discret); }
.etat-vide-titre { font-weight: 600; color: var(--texte); margin: 8px 0 4px; }

/* --- La colonne d'actions ------------------------------------------------ */
.colonne-actions { width: 44px; text-align: right; }
.icone-de-ligne {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 32px;
  min-height: 32px;
  color: var(--texte-discret);
  text-decoration: none;
}
.icone-de-ligne:hover { color: var(--lien); }

/* --- La fiche ------------------------------------------------------------ */
.entete-de-fiche {
  --surface: var(--surface-carte);
  background: var(--surface);
  color: var(--texte);
  border: 1px solid var(--filet);
  border-radius: var(--rayon-carte);
  padding: 12px 16px;
  margin-bottom: 16px;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  position: sticky;
  top: 0;
  z-index: 5;
}
.identite-de-fiche { display: flex; gap: 12px; align-items: flex-start; }
.disque-de-fiche {
  --surface: var(--fond-deduit);
  background: var(--surface);
  color: var(--etat-deduit);
  border-radius: var(--rayon-pastille);
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
}
.entete-de-fiche h1 { margin: 0; }
.entete-de-fiche .sous-titre {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
  margin: 4px 0 0;
}
.reference { font-variant-numeric: tabular-nums; }

.a-completer {
  --surface: var(--fond-infraction);
  background: var(--surface);
  border: 1px solid var(--etat-infraction);
  border-left-width: 4px;
  border-radius: var(--rayon-champ);
  padding: var(--e3) var(--e4);
  margin-bottom: 16px;
  color: var(--texte);
}
.a-completer summary { cursor: pointer; }
.jetons-de-manque { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0 0; }
.jeton-de-manque {
  --surface: var(--surface-carte);
  background: var(--surface);
  color: var(--texte);
  border: 1px solid var(--filet);
  border-radius: var(--rayon-pastille);
  padding: 2px 10px;
  font-size: 13px;
}

/* La barre d'enregistrement, en bas et collante : sur une fiche à cinquante
   champs, on remontait vérifier puis on redescendait valider. */
.barre-d-enregistrement {
  --surface: var(--surface-carte);
  background: var(--surface);
  color: var(--texte);
  border-top: 1px solid var(--filet);
  padding: 10px 16px;
  margin-top: 16px;
  position: sticky;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 12px;
}

/* La jauge dans un badge : elle dit d'un coup d'oeil ce que le chiffre dit
   en toutes lettres. Les deux ensemble — jamais la couleur seule. */
.jauge-de-badge {
  display: inline-block;
  width: 28px;
  height: 6px;
  border-radius: var(--rayon-pastille);
  background: var(--surface-appui);
  overflow: hidden;
  vertical-align: middle;
}
.jauge-de-badge > span { display: block; height: 100%; background: currentColor; }

/* La geolocalisation se replie quand elle n'a rien a arbitrer : un bloc de
   six lignes pour dire « tout va bien » prend la place de ce qui ne va pas. */
.bloc-de-geolocalisation { margin-bottom: 16px; }
.bloc-de-geolocalisation summary { cursor: pointer; padding: 4px 0; }

/* --- L'assistant --------------------------------------------------------- */
.frise-d-assistant {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  list-style: none;
  margin: 0 0 16px;
  padding: 0;
}
.frise-d-assistant .etape {
  --surface: var(--surface-appui);
  background: var(--surface);
  color: var(--texte-discret);
  border-radius: var(--rayon-pastille);
  padding: var(--e2) var(--e4);
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 32px;
}
.frise-d-assistant .etape-faite {
  --surface: var(--fond-conforme);
  --texte-discret: var(--etat-conforme);
  background: var(--surface);
  color: var(--texte-discret);
}
/* 🔴 L'étape courante se marque par le fond **et** par la graisse : un statut
   porté par la seule couleur n'existe pas pour qui ne la distingue pas. */
.frise-d-assistant .etape-courante {
  --surface: var(--accent);
  --texte: var(--sur-accent);
  --texte-discret: var(--sur-accent);
  --lien: var(--sur-accent);
  --etat-impossible: var(--sur-accent);
  --etat-infraction: var(--sur-accent);
  --etat-conforme: var(--sur-accent);
  --etat-deduit: var(--sur-accent);
  --texte-pourvoir: var(--sur-accent);
  background: var(--surface);
  color: var(--texte);
  font-weight: 600;
}
.numero-d-etape { font-variant-numeric: tabular-nums; font-weight: 700; }

/* --- L'ecran de connexion, en deux zones ----------------------------------
   La moitie gauche est la marque : elle ne porte aucun champ. Sur telephone,
   les deux zones s'empilent et la marque se reduit — un panneau plein ecran
   avant le formulaire ferait defiler pour se connecter. */
.ecran-de-connexion {
  display: grid;
  grid-template-columns: minmax(280px, 5fr) 7fr;
  min-height: calc(100vh - 120px);
  gap: 0;
  margin: -14px;
}

.zone-de-marque {
  --surface: var(--haze-marine);
  --texte: #FFFFFF;
  --texte-discret: var(--haze-gris);
  --lien: var(--haze-bleu);
  --etat-impossible: #F87E71;
  --etat-infraction: #FBA524;
  --etat-conforme: #34D38A;
  --etat-deduit: #7DCFFC;
  --texte-pourvoir: #D0B5FD;
  background: var(--surface);
  color: var(--texte);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: flex-start;
  gap: 8px;
  padding: var(--e7);
}
.zone-de-marque .pastille { background: none; padding: 0; }
.zone-de-marque img { height: 48px; width: auto; display: block; }
.nom-de-l-outil {
  font-size: 28px;
  font-weight: 700;
  letter-spacing: .12em;
  color: var(--lien);
  margin: 4px 0 0;
}
.slogan { font-size: 16px; color: var(--texte-discret); margin: 0; }

.zone-de-saisie {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 16px;
  padding: var(--e7);
}
.carte-de-connexion {
  --surface: var(--surface-carte);
  background: var(--surface);
  color: var(--texte);
  border: 1px solid var(--filet);
  border-radius: var(--rayon-carte);
  box-shadow: var(--ombre);
  padding: 24px;
  width: min(420px, 100%);
}
.grille-verticale { flex-direction: column; }
.grille-verticale .champ { flex: 1 1 100%; }
.pleine-largeur { width: 100%; }

.comptes-de-recette {
  --surface: var(--fond-infraction);
  background: var(--surface);
  color: var(--texte);
  border: 1px solid var(--etat-infraction);
  border-left-width: 4px;
  border-radius: var(--rayon-champ);
  padding: var(--e3) var(--e4);
  width: min(420px, 100%);
}
.comptes-de-recette summary { cursor: pointer; }
.boutons-de-recette { display: flex; flex-wrap: wrap; gap: 6px; }

@media (max-width: 1000px) {
  .ecran-de-connexion { grid-template-columns: 1fr; min-height: 0; }
  .zone-de-marque { padding: 24px; }
  .zone-de-saisie { padding: 24px; }
}

/* --- Les onglets ----------------------------------------------------------
   L'onglet actif se marque par un **soulignement de 2 px** et par la graisse,
   pas par la seule couleur. `aria-current` le dit aux lecteurs d'ecran. */
.onglets-de-dispatch { gap: 0; border-bottom: 1px solid var(--filet); }
.onglets-de-dispatch .onglet {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: var(--e3) var(--e4);
  min-height: 44px;
  color: var(--texte-discret);
  text-decoration: none;
  border-bottom: 2px solid transparent;
}
.onglets-de-dispatch .onglet:hover {
  color: var(--texte);
  text-decoration: none;
}
.onglets-de-dispatch .onglet-actif {
  color: var(--texte);
  font-weight: 600;
  border-bottom-color: var(--accent);
}

/* La carte KPI en attente d'une donnee qui n'existe pas encore. Elle n'est
   pas cliquable, et elle dit ce qui manque — l'omettre laisserait croire a un
   oubli, l'afficher a zero dirait que personne ne travaille. */
/* 🔴 **Une opacité n'est pas une couleur.** `opacity: .72` rabat le texte
   *et* son fond vers ce qu'il y a derrière : la paire nominale à 5,48:1
   tombait à 3,08:1, et aucune garde de jetons ne pouvait le voir — elle lit
   les couleurs déclarées, pas ce que la composition en fait.

   Le texte discret dit la même chose — « cette carte attend une donnée » —
   avec un contraste qui se mesure. Le tireté de la bordure reste. */
.kpi-en-attente {
  color: var(--texte-discret);
  cursor: default;
  border-style: dashed;
}
.bande-kpi { margin-bottom: 16px; }
.ligne-de-referentiel { margin-top: 24px; font-size: 13px; }

/* --- Le calendrier français ------------------------------------------------
   🔴 **Le calendrier natif parle la langue du navigateur, pas celle du
   document.** Sur les postes du banc, `input[type=date]` rend
   « September 2026 », une semaine qui commence au dimanche, et parfois une
   saisie mm/jj/aaaa. `lang="fr"` n'y peut rien : la spécification laisse ce
   rendu à l'implémentation.

   Le champ natif **reste** — sa valeur, sa validation et sa saisie au clavier
   ne changent pas. On lui ajoute un calendrier, ouvert par un bouton. Sans
   JavaScript, le champ fonctionne comme avant : c'est la règle du projet. */
.champ-date { position: relative; display: flex; align-items: stretch; gap: 4px; }
.champ-date > input { flex: 1 1 auto; }

.calendrier-declencheur { align-self: center; }

/* --- C.11 : une seule icône de calendrier ---------------------------------
   🟠 **Pourquoi le bouton existe, vérifié avant de conclure.** Il n'a pas été
   ajouté par goût : le calendrier natif s'affiche dans la langue du
   navigateur, commence la semaine au dimanche, et propose parfois une saisie
   mm/jj/aaaa. `lang="fr"` n'y peut rien — la spécification laisse ce rendu à
   l'implémentation. Le retirer rendrait la saisie de date à l'anglais.

   C'est donc l'icône native qui part. Elle ouvre un calendrier que
   l'application a remplacé : deux icônes côte à côte, deux calendriers
   différents derrière.

   🔴 **Seulement là où le bouton est posé.** Sans JavaScript, `champ-date`
   n'existe pas, le bouton non plus, et l'icône native reste la seule façon
   d'ouvrir un calendrier. Masquer sans condition retirerait le choix à qui
   n'a pas de JavaScript — ce projet sert ses écrans sans en dépendre. */
.champ-date input[type="date"]::-webkit-calendar-picker-indicator {
  display: none;
}

.calendrier-fr {
  --surface: var(--surface-carte);
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  z-index: 40;
  width: 268px;
  padding: 8px;
  background: var(--surface);
  color: var(--texte);
  border: 1px solid var(--filet);
  border-radius: var(--rayon-carte);
  box-shadow: 0 8px 24px var(--voile);
}
.calendrier-fr[hidden] { display: none; }

.calendrier-tete {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 6px;
}
.calendrier-titre { font-weight: 600; }

.calendrier-grille {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 2px;
}
.calendrier-jour-entete {
  font-size: 12px;
  color: var(--texte-discret);
  text-align: center;
  padding: 4px 0;
}
.calendrier-jour {
  min-height: 32px;
  padding: 0;
  border-color: transparent;
  font-variant-numeric: tabular-nums;
}
.calendrier-jour[aria-pressed="true"] {
  --surface: var(--accent);
  --texte: var(--sur-accent);
  --texte-discret: var(--sur-accent);
  --lien: var(--sur-accent);
  --etat-impossible: var(--sur-accent);
  --etat-infraction: var(--sur-accent);
  --etat-conforme: var(--sur-accent);
  --etat-deduit: var(--sur-accent);
  --texte-pourvoir: var(--sur-accent);
  background: var(--surface);
  color: var(--texte);
  border-color: var(--accent);
  font-weight: 600;
}
.calendrier-pied {
  display: flex;
  justify-content: space-between;
  margin-top: 6px;
  border-top: 1px solid var(--filet);
  padding-top: 6px;
}

/* --- Le ruban de recette --------------------------------------------------
   🔴 **Fin, mais jamais masquable.** Le gros bandeau rouge pleine largeur
   disait la même chose en occupant le quart de l'écran ; à force, on ne le
   voyait plus — c'est le sort de toute alarme permanente. Une ligne orange
   sous l'en-tête suffit à ce qu'on ne prenne jamais une session de recette
   pour une session ordinaire, et `role="alert"` le dit aux lecteurs d'écran
   comme avant. */
.ruban-recette {
  --surface: var(--fond-infraction);
  --texte: var(--etat-infraction);
  background: var(--surface);
  color: var(--texte);
  border-bottom: 1px solid var(--etat-infraction);
  padding: 6px 16px;
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 8px;
}


/* ==========================================================================
   Le portail agent
   ==========================================================================
   > 🔴 **C'est le seul écran dont l'utilisateur ne choisit pas les
   > conditions** : de nuit, avec des gants, sous la pluie, sur un téléphone.
   > Les écrans de gestion restent denses — ils se consultent sur un poste
   > fixe, et cette densité est voulue. Rien de ce bloc ne les touche. */

/* 🟠 14 px et non 12 : la conformité demandait seulement un gris plus foncé,
   mais un téléphone se tient à bout de bras. La taille sert la lecture, la
   couleur sert la mesure — on fait les deux. */
body.portail .discret-texte,
body.portail .champ small { font-size: 14px; }

/* 🟡 WCAG 2.5.8 — 24 × 24 px. Relevé avant : la marque à 20 px de haut, le
   lien « mot de passe » à 19. Avec des gants, la marge est plus faible encore
   que le chiffre ne le dit. */
body.portail header a,
body.portail header button,
body.portail main button {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
}

/* -------------------------------------------------------------------------
   🔴 Le pouce, pas le curseur — passe du 14/09.
   -------------------------------------------------------------------------
   Le portail est le seul écran que des agents réels ouvriront, souvent
   debout, souvent d'une main. WCAG 2.5.8 exige 24 px ; 24 px se rate au
   pouce. Les cibles d'action du portail — boutons de formulaire et liens
   qui en tiennent lieu — montent donc à **44 px**, la valeur que ce projet
   s'est donnée depuis la refonte.

   🟠 Les liens de texte dans un paragraphe restent hors de cette règle : les
   agrandir espacerait les lignes d'un texte suivi, et ce n'est pas là qu'on
   appuie.
   ------------------------------------------------------------------------- */
body.portail main button,
body.portail main .bouton-primaire,
body.portail main .bouton-secondaire,
body.portail main .onglet {
  min-height: 44px;
}

/* 🔴 **Rien ne déborde en largeur.** Un mot long — une référence, une adresse
   sans espace — pousse la page et fait glisser tout l'écran latéralement ;
   sur un téléphone, on ne retrouve plus le bord gauche. */
body.portail main {
  overflow-wrap: anywhere;
}

/* Les commandes de formulaire sont des cibles aussi — et la case à cocher est
   la plus petite de toutes : 13 px de côté par défaut, soit la moitié du
   seuil. C'est elle qui porte « Je suis disponible pour cette vacation ». */
body.portail input,
body.portail select,
body.portail textarea { min-height: 24px; }
/* La case du portail suit la règle générale depuis C.4 : elle n'a plus
   besoin de sa propre ligne. */

/* --- Sous 700 px, un tableau du portail devient une carte -----------------
   `width: 100%` ne borne pas un tableau : padding, bordures et mots
   insécables lui donnent une largeur incompressible — 403 px mesurés pour un
   écran de 375, et un défilement horizontal sur **toute la page**. Une
   vacation y tenait sur 151 px de haut, chaque cellule faisant sept lignes
   dans une colonne de 47 à 85 px.

   > 🔴 **Un seul balisage, deux mises en forme.** La carte n'est pas un
   > second gabarit : c'est le même tableau, replié. Deux gabarits auraient
   > divergé — la recette exige que les deux formes montrent les mêmes
   > informations, et une exigence tenue par la structure ne se relit pas.
   > Le libellé de colonne voyage dans `data-libelle` et revient en `::before`
   > là où la carte en a besoin. */
@media (max-width: 760px) {
  .tableau-cartes,
  .tableau-cartes tbody,
  .tableau-cartes tr,
  .tableau-cartes th,
  .tableau-cartes td { display: block; width: auto; }

  /* L'en-tête n'a plus de colonnes à nommer : chaque cellule porte le sien. */
  .tableau-cartes thead { display: none; }

  .tableau-cartes { border: none; background: none; }
  .tableau-cartes tr,
  .tableau-cartes tbody tr:nth-child(even) {
    --surface: light-dark(#FFFFFF, #16324E);
    background: var(--surface);
    border: 1px solid var(--filet);
    border-radius: 4px;
    padding: 6px 10px;
    margin-bottom: 10px;
  }
  .tableau-cartes th,
  .tableau-cartes td {
    border: none;
    padding: 0;
    text-align: left;
    line-height: 1.35;
  }
  .tableau-cartes td.nombre { text-align: left; }
  .tableau-cartes td[data-libelle]::before {
    content: attr(data-libelle) " : ";
    font-weight: 600;
  }
  /* 🟡 Une cellule qui porte une **phrase entière** ne prend pas d'étiquette :
     « Prévenance : Ce planning ne vous a pas encore été communiqué. » coûte
     une ligne de plus pour treize caractères qui n'apprennent rien. La phrase
     se suffit — c'est même pour cela qu'elle a été écrite ainsi. */
  .tableau-cartes td.carte-phrase::before { content: none; }
  /* La tête de carte : ce qu'on cherche d'abord — quand, et où. */
  .tableau-cartes td.carte-tete { font-weight: 700; }
  /* Deux valeurs courtes partagent une ligne plutôt que d'en coûter deux. */
  .tableau-cartes td.carte-en-ligne {
    display: inline-block;
    margin-right: var(--e4);
  }
  /* Une cellule sans contenu ne prend pas de place : sur un tableau replié,
     une cellule vide reste une ligne vide, et six d'entre elles font une
     carte trouée. */
  .tableau-cartes td.carte-vide { display: none; }
}

/* --- Les tableaux de gestion défilent dans leur conteneur -----------------
   🟡 Ils restent des tableaux : leur densité est voulue, et ils se consultent
   sur un poste fixe. Mais consultés sur une tablette, ils emportaient la page
   entière avec eux. **La page ne défile jamais horizontalement ; le tableau,
   oui.**

   Obtenu sans toucher aux trente gabarits concernés : un tableau en
   `display: block` engendre une boîte de tableau anonyme qui se dimensionne à
   son contenu, et déborde donc *à l'intérieur* du bloc. Sous la seule
   condition d'une fenêtre étroite — au-delà, la règle n'existe pas. */
@media (max-width: 1180px) {
  main table:not(.tableau-cartes):not(.grille) {
    display: block;
    overflow-x: auto;
  }
}


/* =========================================================================
   La grille du planning
   =========================================================================
   Lignes : sites ou agents. Colonnes : jours. Une case porte les vacations
   du jour, colorées par statut.

   🔴 **Quatre teintes pour six statuts, et pas une de plus.** La couleur
   répond à une seule question — *est-ce que ça me demande quelque chose ?* Le
   reste est dit par une icône. Six teintes voisines ne se retiennent pas, et
   un code couleur qu'on ne retient pas ne sert qu'à décorer.

   🔴 **Aucun fond n'est inventé ici.** Chacun est une surface déjà mesurée au
   contraste dans les deux thèmes : `--fond-conforme`, `--fond-impossible`,
   `--fond-deduit`, `--fond-pourvoir`, `--surface-appui`. En poser un nouveau
   reviendrait à peindre sans mesurer. */

/* --- Les trois vues, puis les deux angles --------------------------------
   Un seul rang de segments : deux barres l'une sous l'autre feraient croire à
   deux navigations, alors que c'est le même écran vu autrement. */
.barre-de-vues { align-items: center; }
.barre-de-vues .onglet,
.navigation-de-periode .onglet {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border-bottom: 2px solid transparent;
  text-decoration: none;
  color: var(--texte);
}
.barre-de-vues .onglet:hover,
.navigation-de-periode .onglet:hover { background: var(--surface-appui); }
.barre-de-vues .onglet-actif,
.navigation-de-periode .onglet-actif {
  border-bottom-color: var(--accent);
  font-weight: 600;
  text-decoration: none;
}
.separateur-de-vues {
  width: 1px;
  align-self: stretch;
  margin: 4px 10px;
  background: var(--filet);
}

.compteurs-de-couverture {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 12px 0;
}
/* 🟠 **Ce qui se clique le montre ; ce qui ne se clique pas s'efface.**
   Les trois premiers compteurs filtrent la vue — ce sont des commandes, et
   le rapport les a pris pour des voyants. Ils gardent donc un curseur de
   lien et se soulignent au survol. Le quatrième, « 4 / 47 pourvus », ne mène
   nulle part : il passe en texte discret, sans aplat, pour qu'on cesse de le
   viser. */
.compteurs-de-couverture a { text-decoration: none; cursor: pointer; }
.compteurs-de-couverture a:hover .badge,
.compteurs-de-couverture a:focus-visible .badge { text-decoration: underline; }
.compteurs-de-couverture > .discret-texte {
  margin-left: 4px;
  padding-left: 12px;
  border-left: 1px solid var(--filet);
}
/* 🟠 Mesuré à 20 x 40 : la hauteur venait de la ligne, la largeur du seul
   glyphe. Le seuil de WCAG 2.2 porte sur les deux côtés. */
.lien-d-aide {
  border: none;
  background: none;
  padding: 2px;
  min-width: 24px;
  cursor: pointer;
  color: var(--lien);
}

/* --- La table ------------------------------------------------------------
   Colonne d'entité et ligne d'en-tête collantes : sur trente-et-une colonnes,
   une grille qu'on fait défiler sans savoir quel site ni quel jour on lit ne
   se lit plus du tout. */
.grille-planning { overflow: auto; max-height: 72vh; }
/* 🔴 **`table-layout: fixed`, et les jours se partagent le reste.**
   `width: auto` + `min-width: 100%` laissait le navigateur répartir au
   contenu : les jours étaient bloqués à 54 px par la règle ci-dessous, et
   tout le surplus tombait dans Site et Bilan — 463 px et 293 px mesurés à
   1 440. Une colonne de jour à 54 px casse « à pourvoir » en « à pourvoi / r »
   sur quatre lignes.

   🟠 En vue mois — trente-et-une colonnes — la largeur fixe donnerait 28 px
   par jour. Le conteneur garde donc son défilement horizontal, et chaque
   colonne un plancher : voir `.grille-planning`. */
.grille-des-vacations {
  border-collapse: separate;
  border-spacing: 0;
  table-layout: fixed;
  width: 100%;
}
.grille-des-vacations th, .grille-des-vacations td {
  border-bottom: 1px solid var(--filet);
  border-right: 1px solid var(--filet);
  padding: 2px;
  vertical-align: top;
}
/* 🟠 La cellule devient un conteneur de requête : c'est **elle** qui sait sa
   largeur, et c'est sur elle que la vignette décide d'afficher les initiales
   ou non. Une media query ne le saurait pas — la même fenêtre porte sept
   colonnes en semaine et trente-et-une en mois. */
.grille-des-vacations td.case { container-type: inline-size; }
/* En grille, l'en-tête de colonne dit déjà le jour : le répéter dans
   chaque case ferait sept fois la même ligne. Il reparaît sous 760 px,
   où il n'y a plus d'en-tête. */
.jour-de-case { display: none; }
.grille-des-vacations thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--surface-carte);
  padding: 4px 2px;
  text-align: center;
  font-size: 12px;
}
.grille-des-vacations .colonne-entite {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--surface-carte);
  /* Largeur **fixée**, et non un intervalle : avec `table-layout: fixed`, un
     `min-width` n'est plus une borne, c'est la colonne entière qui se
     déclare. 200 px tiennent un nom de site et sa ligne de client.

     🟠 **220 → 200 le 20/09.** Le rail des groupes s'est élargi de dix-huit
     pixels pour cesser de couper « Administration » ; les jours en ont perdu
     autant, et la colonne est tombée de 110 à 108 — sous le seuil du § B.5.
     La largeur se reprend ici, là où elle était la moins utile : un nom de
     site tient dans 200 px, sept jours de planning ne tiennent nulle part
     ailleurs. */
  width: 200px;
  padding: 6px 10px;
  text-align: left;
  vertical-align: middle;
}
.grille-des-vacations .colonne-bilan {
  position: sticky;
  right: 0;
  z-index: 1;
  background: var(--surface-carte);
  width: 132px;
  padding: 6px 10px;
  text-align: right;
  vertical-align: middle;
}
/* Les deux angles de la ligne d'en-tête sont collants dans les deux sens :
   sans ce rang supérieur, ils passeraient sous les cases au défilement. */
.grille-des-vacations thead .colonne-entite, .grille-des-vacations thead .colonne-bilan { z-index: 3; }
.grille-des-vacations .colonne-entite .discret-texte,
.grille-des-vacations .colonne-bilan .discret-texte { display: block; font-size: 12px; }
/* 🔴 **La règle fautive, et elle ne disait pas son nom.** Elle figeait le
   jour à 54 px ; tout ce que la fenêtre offrait de plus allait aux deux
   colonnes collantes. `auto` sous `table-layout: fixed` partage le reste à
   parts égales : 111 px par jour à 1 440, 88 à 1 280. */
.grille-des-vacations .colonne-jour { width: auto; }

/* 🟠 **Le plancher de la vue mois.** Trente-et-une colonnes à parts égales
   donneraient 28 px. Le conteneur défile déjà à l'horizontale ; il faut juste
   que chaque jour reste visable au doigt — 44 px, WCAG 2.2, critère 2.5.8. */
/* 🟠 **Un attribut de données, pas une classe.** « vue mois » est un état
   de l'écran, pas un habillage — et une garde poussée lit
   `class="grille-des-vacations"` littéralement : ajouter un mot dans
   l'attribut la faisait échouer sans que rien d'autre ne bouge. */
.grille-des-vacations[data-vue="mois"] {
  table-layout: auto;
  width: auto;
  min-width: 100%;
}
.grille-des-vacations[data-vue="mois"] .colonne-jour {
  width: 44px;
  min-width: 44px;
}
.jour-lettre {
  display: block;
  font-size: 12px;
  text-transform: uppercase;
  color: var(--texte-discret);
}
.jour-nombre { display: block; font-variant-numeric: tabular-nums; }
.bilan-heures {
  display: block;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* Le week-end et le férié teintent la colonne entière, en-tête comprise :
   c'est le jour qui est chômé, pas la case. */
.grille-des-vacations .jour-chome { --surface: var(--surface-appui); background: var(--surface); }
.grille-des-vacations .jour-ferie { --surface: var(--fond-deduit); background: var(--surface); }

/* --- La vignette ---------------------------------------------------------
   🔴 Jamais la couleur seule : l'icône la redouble, l'heure et les initiales
   la nomment, et `title` donne le statut en toutes lettres. */
.vignette {
  display: block;
  margin-bottom: 2px;
  padding: 2px 4px;
  border: 1px solid var(--filet);
  border-radius: 4px;
  background: var(--surface);
  color: var(--texte);
  font-size: 12px;
  line-height: 1.3;
  text-decoration: none;
}
.vignette:last-child { margin-bottom: 0; }
.vignette:hover, .vignette:focus-visible { border-color: var(--accent); }
.vignette-signe { float: right; }
.vignette-heure { font-weight: 600; font-variant-numeric: tabular-nums; }

/* 🔴 **Les initiales restent dans la case** — arbitrage du directeur du
   19/09 : *« oui, les initiales quand la place le permet »*. La requête de
   conteneur décide de la place : sous 92 px de colonne, il n'y en a pas, et
   deux caractères de plus renverraient la vignette à la ligne. */
.vignette-qui { color: var(--texte-discret); }
@container (max-width: 92px) {
  .vignette-qui { display: none; }
}

/* 🔴 **Un aplat par statut, et le texte qui va avec.** Chaque classe est une
   **surface** : elle déclare son fond et sa couleur de texte dans le même
   bloc, sinon le test mesure la paire contre le mauvais fond. C'est la règle
   du 31/08, et elle vaut pour les six.

   Les hachures ont disparu : voir le commentaire des jetons. */
.vignette-a-pourvoir {
  --surface: var(--fond-st-pourvoir);
  --texte: var(--st-pourvoir);
  background: var(--surface);
  color: var(--texte);
  border-color: var(--st-pourvoir);
}
.vignette-non-pourvue {
  --surface: var(--fond-st-non-pourvue);
  --texte: var(--st-non-pourvue);
  background: var(--surface);
  color: var(--texte);
  border-color: var(--st-non-pourvue);
}
.vignette-proposee {
  --surface: var(--fond-st-proposee);
  --texte: var(--st-proposee);
  background: var(--surface);
  color: var(--texte);
  border-color: var(--st-proposee);
}
.vignette-confirmee {
  --surface: var(--fond-st-confirmee);
  --texte: var(--st-confirmee);
  background: var(--surface);
  color: var(--texte);
  border-color: var(--st-confirmee);
}
/* « En cours » est une confirmée en train de se dérouler : sa propre teinte
   ambre, parce qu'une vacation commencée ne se traite pas comme une vacation
   à venir — c'est elle qu'on appelle si personne n'est arrivé. */
.vignette-en-cours {
  --surface: var(--fond-st-en-cours);
  --texte: var(--st-en-cours);
  background: var(--surface);
  color: var(--texte);
  border-color: var(--st-en-cours);
}
/* « Réalisée » est du passé : elle ne demande rien, elle ne crie rien. */
.vignette-realisee {
  --surface: var(--fond-st-realisee);
  --texte: var(--st-realisee);
  background: var(--surface);
  color: var(--texte);
}

/* 🔴 **Le compte à rebours de prévenance dépassé : un liseré, plus un texte.**
   « reste 2 j » tenait une ligne entière dans une case de 54 px. Le signal
   reste — 3 px sur le bord gauche — et le texte se lit dans l'info-bulle et
   dans le tiroir. Il demeure dans le document, en `visuellement-cache`. */
.vignette-prevenance-depassee {
  border-left: 3px solid var(--etat-impossible);
}

/* --- La légende, sous la grille et jamais ailleurs ----------------------- */
.legende-grille {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  margin: 8px 0 16px;
  padding: 0;
  list-style: none;
  font-size: 12px;
  color: var(--texte-discret);
}
.legende-grille li { display: flex; align-items: center; gap: 6px; }
.vignette-legende, .case-legende {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  margin: 0;
  padding: 0;
  border: 1px solid var(--filet);
  border-radius: 4px;
}
.case-legende.jour-chome { --surface: var(--surface-appui); background: var(--surface); }
.case-legende.jour-ferie { --surface: var(--fond-deduit); background: var(--surface); }

/* --- Le tiroir de détail ------------------------------------------------- */
.tiroir-de-vacation .couples {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 4px 12px;
  margin: 12px 0;
}
.tiroir-de-vacation dt { font-size: 12px; color: var(--texte-discret); }
.tiroir-de-vacation dd { margin: 0; }
.actions-de-tiroir { display: flex; flex-wrap: wrap; gap: 8px; }
.actions-de-tiroir [hidden] { display: none; }


/* --- La période : deux flèches, le présent, trois cadrages --------------- */
.navigation-de-periode {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 12px 0;
}
.bouton-de-fleche { padding: 6px 8px; }
.periode-lue { margin-left: 4px; }
/* 🔴 **Trois bascules, une seule apparence.** Choisir une vue, choisir un
   angle, choisir un cadrage : c'est trois fois le même geste, et l'écran le
   disait de trois façons — onglets à icône, texte souligné, boutons en
   pilule. Le rapport du 20/09 : « unifiez-les ».

   Les cadrages perdent donc leur dialecte propre et prennent celui de
   `.barre-de-vues`, déclaré une fois pour les trois. */
.navigation-de-periode .cadrages { margin: 0 0 0 auto; }

/* --- Le pied de colonne : le taux de pourvoi du jour --------------------- */
.grille-des-vacations tfoot th, .grille-des-vacations tfoot td {
  position: sticky;
  bottom: 0;
  z-index: 2;
  background: var(--surface-carte);
  border-top: 1px solid var(--filet);
  border-bottom: none;
  text-align: center;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}
.grille-des-vacations tfoot .colonne-entite, .grille-des-vacations tfoot .colonne-bilan { z-index: 3; }
/* Le chiffre porte déjà le sens : un taux sous cent se lit sans couleur. La
   teinte ne fait que le trouver plus vite. */
.pourvoi-incomplet { color: var(--etat-impossible); font-weight: 600; }


/* =========================================================================
   Le dispatch — les candidats en cartes
   =========================================================================
   Une carte par agent : identité, trajet, score en jauge, cinq axes en
   mini-barres, signaux en badges, actions.

   🔴 **La jauge redouble le nombre, elle ne le remplace pas.** Un arc ne se
   lit pas au chiffre près et ne se lit pas du tout à voix haute ; le nombre
   reste écrit au centre, et l'arc dit seulement « plutôt haut, plutôt bas ». */

.entete-de-classement {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
  align-items: baseline;
  justify-content: space-between;
  margin: 16px 0 8px;
}
.entete-de-classement .discret-texte { display: block; }
.compteurs-de-classement {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 0;
}
.actions-de-classement { margin: 8px 0; }

.barre-de-volets .onglet {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border-bottom: 2px solid transparent;
  color: var(--texte);
  text-decoration: none;
}
.barre-de-volets .onglet-actif {
  border-bottom-color: var(--accent);
  font-weight: 600;
  text-decoration: none;
}

.cartes-de-candidats {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
  gap: 12px;
  margin: 12px 0;
  padding: 0;
  list-style: none;
}
.carte-candidat {
  --surface: var(--surface-carte);
  background: var(--surface);
  border: 1px solid var(--filet);
  border-radius: var(--rayon-carte);
  padding: 12px;
}
.candidat-tete {
  display: flex;
  align-items: center;
  gap: 12px;
}
.candidat-identite { flex: 1; min-width: 0; }
.candidat-identite strong { display: block; }
.candidat-identite .discret-texte { display: block; font-size: 12px; }

/* L'initiale plutôt qu'une photo : l'application n'en détient pas, et en
   inventer une serait une donnée de plus à protéger pour rien. */
.pastille-d-initiales {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--surface-appui);
  font-weight: 600;
  font-size: 14px;
}

.bloc-de-score {
  display: flex;
  flex: none;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}
.bloc-de-score .discret-texte { font-size: 12px; }
.jauge-de-score {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: conic-gradient(var(--accent) calc(var(--part) * 1%),
                             var(--surface-appui) 0);
}
.jauge-de-score-valeur {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  background: var(--surface-carte);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* --- Les cinq axes ------------------------------------------------------- */
.mini-barres { margin: 10px 0; padding: 0; list-style: none; }
.mini-barre {
  display: grid;
  grid-template-columns: 72px 90px 34px 42px 1fr;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  padding: 1px 0;
}
.mini-barre-note { font-variant-numeric: tabular-nums; text-align: right; }
.mini-barre-poids { font-variant-numeric: tabular-nums; text-align: right; }
/* 🟠 `anywhere` coupait les mots lettre à lettre : « non calculable… »
   s'écrasait sur douze lignes dans une colonne de quatre caractères (audit du
   23/09, § 5). Les mots se coupent entre eux, pas au milieu. */
.mini-barre-constat { overflow-wrap: break-word; min-width: 6em; }
/* Un axe retiré n'a pas de barre à zéro : une barre vide se lit « mauvaise
   note », alors qu'elle veut dire « pas mesurable ». La trame le distingue. */
.jauge-retiree {
  background-image: repeating-linear-gradient(
    135deg, transparent 0 4px, var(--filet) 4px 8px);
}
.mini-barre-retiree .mini-barre-nom { color: var(--texte-discret); }

.signaux {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 8px 0;
}
.chiffrage { margin: 8px 0; font-size: 12px; }
.chiffrage summary { cursor: pointer; }
/* Brief du coût salarial, 30/09, § 3.3 : la décomposition du coût employeur
   d'un candidat — brut, majorations, fin de contrat, charges, RGDU, exonéré,
   coût employeur. Une colonne de montants, alignés sur la virgule. */
.decomposition-du-cout { margin: var(--e2) 0; border-collapse: collapse; }
.decomposition-du-cout th { text-align: left; font-weight: normal;
  padding: var(--e0) var(--e3) var(--e0) 0; }
.decomposition-du-cout td { padding: var(--e0) 0; }
.decomposition-du-cout tr:last-child th,
.decomposition-du-cout tr:last-child td { border-top: 1px solid var(--filet); }
.actions-de-candidat {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 10px 0 0;
}

@media (max-width: 760px) {
  .cartes-de-candidats { grid-template-columns: 1fr; }
  .mini-barre { grid-template-columns: 68px 1fr 34px 42px; }
  .mini-barre-constat { grid-column: 1 / -1; }
}


/* =========================================================================
   L'astreinte — un tableau saisi en ligne
   =========================================================================
   La saisie en cellule vaut ici parce qu'on compare avant de corriger : les
   cinq rangs se lisent l'un sous l'autre, et l'on voit du même coup d'oeil
   celui qui manque. Un formulaire par rang obligeait a ouvrir cinq ecrans
   pour repondre a une question qui tient en un regard. */
.tableau-saisi { width: 100%; }
.tableau-saisi th, .tableau-saisi td { vertical-align: middle; padding: 4px 6px; }
.tableau-saisi .colonne-rang { width: 96px; white-space: nowrap; }
.champ-de-cellule { display: block; }
.champ-de-cellule input {
  width: 100%;
  min-width: 110px;
  padding: 6px 8px;
}
/* Une ligne a completer se signale par sa teinte **et** par le badge de sa
   premiere cellule : jamais par la seule couleur. */
.ligne-a-completer td, .ligne-a-completer th {
  --surface: var(--fond-impossible);
  background: var(--surface);
}

.cartes-d-urgence {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: 12px;
  margin: 12px 0;
}
.carte-d-urgence {
  --surface: var(--surface-carte);
  background: var(--surface);
  border: 1px solid var(--filet);
  border-radius: var(--rayon-carte);
  padding: 12px;
}
.carte-d-urgence h3 {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 0;
}


/* =========================================================================
   Un ecran a sections : la sous-navigation a gauche, une section a la fois
   =========================================================================
   Onze mille pixels de rouleau ne se parcourent pas : on y cherche, on y
   renonce. La sous-navigation dit ce qui existe **avant** d'y aller, et
   n'affiche qu'une section — celle qu'on a demandee. */
.ecran-a-sections {
  display: grid;
  grid-template-columns: minmax(200px, 240px) minmax(0, 1fr);
  gap: var(--e5);
  align-items: start;
}
.sous-navigation ul {
  list-style: none;
  margin: 0;
  padding: 0;
  position: sticky;
  top: 12px;
}
.sous-navigation li { margin: 0; }
.lien-de-section {
  display: flex;
  align-items: center;
  gap: 8px;
  /* 44 px de haut : la cible se vise au doigt comme a la souris. */
  min-height: 44px;
  padding: 8px 10px;
  border-left: 3px solid transparent;
  border-radius: 0 var(--rayon-carte) var(--rayon-carte) 0;
  color: var(--texte);
  text-decoration: none;
}
.lien-de-section:hover { background: var(--surface-appui); }
.lien-de-section-actif {
  --surface: var(--surface-appui);
  background: var(--surface);
  border-left-color: var(--accent);
  font-weight: 600;
  text-decoration: none;
}
.libelle-de-section { flex: 1; }
.compte-de-section { font-variant-numeric: tabular-nums; font-size: 12px; }
/* La marque des cles a effet financier se voit **depuis la navigation** : on
   sait avant d'entrer que la section porte de quoi fausser un bulletin. */
.marque-financiere { color: var(--etat-infraction); display: inline-flex; }
.section-affichee { min-width: 0; }

@media (max-width: 1000px) {
  .ecran-a-sections { grid-template-columns: 1fr; }
  .sous-navigation ul {
    position: static;
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
  }
  .lien-de-section {
    border-left: none;
    border-bottom: 3px solid transparent;
    border-radius: var(--rayon-carte);
  }
  .lien-de-section-actif { border-bottom-color: var(--accent); }
}


/* =========================================================================
   Le portail agent — la barre d'onglets basse et l'accueil
   =========================================================================
   Le pouce atteint le bas de l'ecran, pas le haut. La navigation du portail
   y descend donc, et l'en-tete n'en porte plus. */
.onglets-du-portail {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 20;
  display: flex;
  --surface: var(--surface-carte);
  background: var(--surface);
  border-top: 1px solid var(--filet);
}
.onglet-du-portail {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  /* 56 px : au-dela des 44 px exiges, parce qu'on vise en marchant. */
  min-height: 56px;
  padding: 6px 4px;
  color: var(--texte-discret);
  font-size: 12px;
  text-align: center;
  text-decoration: none;
}
/* L'onglet actif ne se distingue pas que par la couleur : il porte un filet
   en tete et son libelle passe en gras. */
.onglet-du-portail-actif {
  color: var(--texte);
  font-weight: 600;
  box-shadow: inset 0 3px 0 0 var(--accent);
  text-decoration: none;
}
/* La barre couvre le bas de la page : le contenu se termine au-dessus d'elle,
   sinon la derniere ligne se lit sous les onglets. */
body.portail main { padding-bottom: 76px; }

/* 🔴 **Le bloc de pointage et les cartes du classement n'avaient aucune
   règle** — audit du 23/09, § 5. Ils prennent l'allure de la carte de la
   prochaine vacation : ce sont des cartes du même portail. */
.carte-prochaine,
.bloc-de-pointage,
.carte-de-classement {
  --surface: var(--surface-carte);
  background: var(--surface);
  border: 1px solid var(--filet);
  border-radius: var(--rayon-carte);
  padding: 16px;
  margin: 12px 0;
}
.etiquette-de-carte {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 4px;
  color: var(--texte-discret);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.creneau-en-grand { margin: 0; font-size: 22px; font-weight: 700; }
.site-en-grand { margin: 2px 0 0; font-size: 16px; }
.adresse-de-carte { display: flex; gap: 6px; margin: 6px 0; }
.action-principale { margin: var(--e4) 0; }
.bouton-large {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  min-height: 56px;
  font-size: 16px;
}
.consignes { margin: 10px 0; }
.consignes summary { cursor: pointer; }

.liste-de-semaine, .liste-a-faire { list-style: none; margin: 8px 0; padding: 0; }
.jour-de-semaine {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px solid var(--filet);
}
.jour-de-semaine-quand { flex: 0 0 128px; font-size: 12px; }
.jour-de-semaine-ou { flex: 1; min-width: 0; }
.jour-de-semaine-duree { font-variant-numeric: tabular-nums; }
.liste-a-faire li {
  padding: 10px 0;
  border-bottom: 1px solid var(--filet);
}


/* --- Les cartes KPI, cliquables --------------------------------------------
   Chaque carte filtre ce qu'elle annonce : un chiffre qui ne mene nulle part
   est une information morte. */
.cartes-de-categorie {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 10px;
  margin: 12px 0;
}
.carte-kpi {
  --surface: var(--surface-carte);
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-height: 72px;
  padding: 10px 12px;
  background: var(--surface);
  border: 1px solid var(--filet);
  border-radius: var(--rayon-carte);
  color: var(--texte);
  text-decoration: none;
}
.carte-kpi:hover { border-color: var(--accent); }
.carte-kpi-active {
  border-color: var(--accent);
  box-shadow: inset 0 -3px 0 0 var(--accent);
}
.kpi-nombre { font-size: 24px; font-weight: 700; font-variant-numeric: tabular-nums; }
.kpi-libelle { font-size: 12px; color: var(--texte-discret); }
.jetons-de-gravite {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 12px 0;
}
.jetons-de-gravite a { text-decoration: none; }
.jetons-de-gravite a:hover .badge { text-decoration: underline; }


/* Les raccourcis de periode : un rang de segments, comme les cadrages du
   planning - meme geste, meme dessin. */
.raccourcis-de-periode {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 12px 0;
}
.raccourcis-de-periode .onglet {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 6px 12px;
  border: 1px solid var(--filet);
  border-radius: var(--rayon-carte);
  color: var(--texte);
  text-decoration: none;
}
.raccourcis-de-periode .onglet-actif {
  border-color: var(--accent);
  font-weight: 600;
  text-decoration: none;
}


/* Une cellule **manquante** n'est pas une cellule vide : la premiere attend
   une mention legale, la seconde n'attendait rien. Le surlignage porte une
   info-bulle, et la ligne porte son bouton « Completer » : jamais la seule
   couleur. */
.cellule-manquante {
  --surface: var(--fond-impossible);
  background: var(--surface);
  font-weight: 600;
}


/* --- Le graphique par creneau ----------------------------------------------
   Une barre par creneau, proportionnelle a la marge deja calculee. Celles a
   perte partent a gauche et changent de teinte - mais le montant reste ecrit
   a cote, et le mot « a perte » est lu par les lecteurs d'ecran. */
.graphique-de-creneaux { list-style: none; margin: 12px 0; padding: 0; }

/* 🟠 Le poste examiné, dans le tableau des postes d'une vacation : la classe
   était posée et rien ne la dessinait (audit du 23/09). */
tr.ligne-active td { background: var(--surface-appui); }
tr.ligne-active td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
.barre-de-creneau {
  display: grid;
  grid-template-columns: 150px 1fr 110px;
  align-items: center;
  gap: 10px;
  padding: 3px 0;
  font-size: 12px;
}
.creneau-piste {
  display: flex;
  justify-content: flex-start;
  height: 18px;
  background: var(--surface-appui);
  border-radius: 3px;
}
.creneau-barre { display: block; height: 100%; border-radius: 3px; background: var(--etat-conforme); }
.creneau-piste:has(.creneau-barre-perte) { justify-content: flex-end; }
.creneau-barre-perte { background: var(--etat-impossible); }
.creneau-montant { text-align: right; font-variant-numeric: tabular-nums; }
.creneau-montant-perte { color: var(--etat-impossible); font-weight: 600; }
.encadre-de-majoration h2 { margin-top: 0; display: flex; align-items: center; gap: 8px; }


/* --- Les decoupes possibles, en cartes de choix ----------------------------
   Un choix se compare, il ne se lit pas en lignes : « 12 h -> 1 agent » contre
   « 6 h + 6 h -> 2 agents » est la seule question posee. */
.cartes-de-decoupe {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 12px;
  margin: 12px 0;
  padding: 0;
  list-style: none;
}
.carte-de-decoupe {
  --surface: var(--surface-carte);
  background: var(--surface);
  border: 1px solid var(--filet);
  border-radius: var(--rayon-carte);
  padding: var(--e4);
}
/* La carte retenue se distingue par son filet **et** par son badge : jamais
   la seule couleur. */
.carte-de-decoupe-retenue {
  border-color: var(--accent);
  box-shadow: inset 0 -3px 0 0 var(--accent);
}
.decoupe-fragments { margin: 0; font-size: 20px; font-weight: 700; }
.decoupe-consequence { display: flex; align-items: center; gap: 6px; margin: 6px 0; }
.actions-de-decoupe { margin: 10px 0 0; }


/* La ventilation posee a cote de son total : jour, nuit, dont dimanche, dont
   ferie. Un total seul ne permet a personne de reperer qu'un jour ordinaire a
   ete majore. */
.ventilation-des-heures, .ventilation-du-total {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin: 6px 0 12px;
}
.ventilation-du-total { font-weight: 400; }


/* La barre qui choisit la periode d'un document a produire. Elle ressemble a
   une barre de filtres, mais elle ne filtre rien : elle produit. */
.barre-de-document {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 10px;
  --surface: var(--surface-carte);
  background: var(--surface);
  border: 1px solid var(--filet);
  border-radius: var(--rayon-carte);
  padding: 12px;
  margin: 12px 0;
}


/* La main courante et les incidents d'une vacation. */
.fil-de-main-courante { list-style: none; margin: 12px 0; padding: 0; }
.entree-de-main-courante, .carte-d-incident {
  --surface: var(--surface-carte);
  background: var(--surface);
  border: 1px solid var(--filet);
  border-radius: var(--rayon-carte);
  padding: 12px;
  margin-bottom: 10px;
}
.entete-d-entree {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 0 0 6px;
}
.details-d-entree { margin: 4px 0; white-space: pre-wrap; }
.corrections-d-entree {
  list-style: none;
  margin: 8px 0 0;
  padding: 8px 0 0 12px;
  border-top: 1px solid var(--filet);
  border-left: 3px solid var(--etat-infraction);
}
.corriger-l-entree summary { cursor: pointer; font-size: 12px; }
.photos-d-incident { list-style: none; margin: 8px 0; padding: 0; }
.photos-d-incident li { display: inline-block; margin-right: 12px; }


/* La notice d'information : du texte long, qui se lit. */
.notice-d-information { max-width: 80ch; }
.carte-de-traitement {
  --surface: var(--surface-carte);
  background: var(--surface);
  border: 1px solid var(--filet);
  border-radius: var(--rayon-carte);
  padding: var(--e3) var(--e4);
  margin: 12px 0;
}
.carte-de-traitement h3 { margin-top: 0; display: flex; align-items: center; gap: 8px; }
.carte-de-traitement th { width: 180px; }
.liste-des-absences { list-style: none; margin: 8px 0; padding: 0; }
.liste-des-absences li { display: flex; align-items: flex-start; gap: 8px; padding: 4px 0; }
.lien-informations { margin-right: 12px; }
.lien-de-notice { margin-top: var(--e4); }

/* --- Repli pour un navigateur sans `light-dark()` -------------------------
   Sorti avant mai 2024. Il ne verrait aucune couleur : une propriété
   personnalisée invalide n'est pas remplacée par sa déclaration précédente,
   elle rend la propriété qui l'utilise invalide au calcul. On lui redonne
   donc le thème clair, en clair. Il n'a pas de bascule ; il a une
   application lisible. */
@supports not (color: light-dark(#000, #fff)) {
  :root {
    --surface: #F4F6F8;
    --surface-carte: #FFFFFF;
    --surface-appui: #EEF3F7;
    --texte: #16222E;
    --texte-discret: #5B6B7A;
    --lien: #026EA6;
    --filet: #DCE3EA;
    --barre: #0E2841;
    --titre: #0E2841;
    --ombre: 0 1px 3px rgba(14, 40, 65, .08);
    --etat-impossible: #C0392B;
    --etat-infraction: #965B00;
    --etat-conforme: #17774B;
    --etat-deduit: #0369A1;
    --texte-pourvoir: #7C3AED;
    --fond-impossible: #FBEAEA;
    --fond-infraction: #FDF3E1;
    --fond-conforme: #E6F6EE;
    --fond-deduit: #EAF6FD;
    --fond-pourvoir: #F1EAFE;
  }
  .surface-carte, table, .panneau, .compteur, .candidat, .mouvement,
  details, form.bloc, form.filtres { --surface: #FFFFFF; }
  .avertissement { --surface: #FDF3E1; }
  th { --surface: #EEF3F7; }
  button { --surface: #FFFFFF; }
  .masque { --surface: #EEF3F7; }
  tbody tr:nth-child(even) { --surface: #F7F9FB; }
  tbody tr:hover { --surface: #EEF3F7; }
  .a-pourvoir td { --surface: #F1EAFE; }
  .ligne-retenue td { --surface: #E6F6EE; }
  header, .candidat .score {
    --surface: #0E2841;
    --texte: #FFFFFF;
    --etat-impossible: #F87E71;
    --etat-infraction: #FBA524;
    --etat-conforme: #34D38A;
    --etat-deduit: #7DCFFC;
    --texte-pourvoir: #D0B5FD;
  }
  input, select, textarea { --surface: #FFFFFF; }
  button.primaire, .bouton-primaire { --surface: #00B0F0; --texte: #0E2841; }
  .ruban-recette { --surface: #FDF3E1; --texte: #965B00; }
}


/* ==========================================================================
   La carte des sites et des agences
   ==========================================================================
   > 🟠 **La carte est un supplément, pas la source.** Tout ce qu'elle montre
   > figure aussi dans les deux tableaux de la page. Ces règles habillent un
   > écran qui reste utilisable sans elles. */

#carte {
  height: 520px;
  margin-bottom: 12px;
  border: 1px solid var(--filet);
  background: var(--surface);
}

/* Sous 700 px, la carte cède la place : sur un téléphone, une carte de
   520 px de haut repousse la liste des non-affichés hors de l'écran — et
   c'est elle qui porte l'information que la carte ne donne pas. */
@media (max-width: 760px) { #carte { height: 320px; } }

.compte-carte { margin: 0 0 6px; font-size: 15px; }

.legende { list-style: none; margin: 0 0 8px; padding: 0;
           display: flex; flex-wrap: wrap; gap: var(--e2) var(--e4); }
.legende li { display: flex; align-items: center; gap: 6px; }

/* La même classe sert au marqueur — un cercle SVG dessiné par Leaflet — et à
   la pastille de la légende, qui est un `span`. D'où les deux jeux de
   propriétés : `fill`/`stroke` pour l'un, `background`/`border` pour l'autre. */
.pastille-carte {
  display: inline-block;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 2px solid var(--surface);
  fill-opacity: 1;
  stroke: var(--surface);
  stroke-width: 2;
}
.pastille-site { background: var(--carte-site); fill: var(--carte-site); }
.pastille-agence { background: var(--carte-agence); fill: var(--carte-agence); }
.pastille-vehicule { background: var(--carte-vehicule); fill: var(--carte-vehicule); }
.pastille-inconnue { background: var(--texte-discret); fill: var(--texte-discret); }

/* 🟠 **Une approximation ne se présente pas comme une certitude.** Un point
   creux dit « je ne sais pas exactement où » — géocodage à la commune ou à la
   voie. Un point plein dit l'adresse. La distinction est dans la forme, pas
   seulement dans l'infobulle : personne n'ouvre l'infobulle de chaque point. */
.pastille-approchee {
  background: none;
  fill: none;
  fill-opacity: 0;
  border-width: 3px;
  stroke-width: 3;
}
.pastille-approchee.pastille-site { border-color: var(--carte-site);
                                    stroke: var(--carte-site); }
.pastille-approchee.pastille-agence { border-color: var(--carte-agence);
                                      stroke: var(--carte-agence); }

/* Une position posée à la main se voit — et le restera quand le boîtier
   existera : c'est alors la distinction qui comptera le plus. */
.pastille-manuelle {
  border-style: dashed;
  border-color: var(--carte-vehicule);
  stroke-dasharray: 3 2;
}

/* Au-delà du seuil, le marqueur change d'aspect **et** le dit dans son
   infobulle. Une position vieille de quatre heures montrée comme actuelle
   envoie quelqu'un au mauvais endroit, avec la confiance en plus. */
.pastille-perimee {
  background: var(--etat-impossible);
  fill: var(--etat-impossible);
  border-color: var(--etat-impossible);
  stroke: var(--etat-impossible);
  opacity: .55;
}

/* --- C.8 : les contrôles Leaflet, et un commentaire qui mentait -----------
   🔴 **Ces règles ne s'appliquaient pas.** Le commentaire précédent affirmait
   qu'elles rattachaient les contrôles aux jetons ; mesuré en thème sombre,
   l'attribution restait une boîte blanche et son lien tombait à 3,21:1.

   Deux causes, et la première n'était pas celle qu'on croyait :

     - **l'ordre.** `leaflet.css` est chargé depuis le corps de `carte.html`,
       donc *après* `style.css`. À spécificité égale — `.leaflet-container a`
       contre `.leaflet-bar a`, 0-1-1 des deux côtés — c'est la dernière lue
       qui gagne, et ce n'était pas la nôtre ;
     - **la spécificité**, pour le fond : Leaflet écrit
       `.leaflet-container .leaflet-control-attribution` (0-2-0) contre notre
       `.leaflet-control-attribution` (0-1-0).

   `#carte` est l'identifiant du conteneur : 1-0-0, au-dessus de tout ce que
   Leaflet écrit, et qui ne dépend pas de l'ordre de chargement. C'est plus
   robuste que de renverser les deux `<link>` — l'un est posé par le gabarit
   de base, l'autre par l'écran, et rien ne garantirait leur ordre demain. */
#carte.leaflet-container { background: var(--surface); color: var(--texte); }
#carte .leaflet-popup-content-wrapper, #carte .leaflet-popup-tip,
#carte .leaflet-control-attribution, #carte .leaflet-bar a {
  background: var(--fond-carte, var(--surface));
  color: var(--texte);
}
#carte .leaflet-bar a,
#carte .leaflet-control-attribution a,
#carte .leaflet-popup-content a { color: var(--lien); }

/* --- C.9 : la prose se lit en colonne, pas en bandeau -------------------
   Jusqu'à 178 caractères mesurés sur une ligne. Au-delà de 90, l'œil perd
   le début de la ligne suivante : le texte se relit deux fois, ou pas du
   tout. La mesure ne relève que deux porteurs — le texte discret et le
   bandeau de recette —, mais la règle vaut pour la prose en général, et
   c'est ainsi qu'elle tiendra pour le paragraphe écrit demain.

   🟠 Elle ne s'applique pas dans un tableau : une cellule tient déjà sa
   largeur de sa colonne, et un `max-width` y créerait un blanc au lieu de
   resserrer. `text-wrap: pretty` évite en prime le mot seul en dernière
   ligne. */
main p, main li, .discret-texte, .ruban-recette, .aide, .sous-titre {
  /* 🔴 **63 ch, et le compte vient d'une mesure.** 70 ch rendaient
     88 caractères par ligne — bloc de 618 px à 14 px — là où WCAG 1.4.8 en
     pose 80 au maximum, et la typographie classique 45 à 75. La règle de
     trois donne 70 × 80 / 88 ≈ 63,6 : on prend 63, et la ligne tombe à
     soixante-dix-neuf.

     🟠 En `ch` et non en pixels : l'unité suit la police. Une largeur figée
     à 560 px redeviendrait fausse le jour où le corps de texte change. */
  max-width: 63ch;
  text-wrap: pretty;
}
td p, th p, td li, th li,
td .discret-texte, th .discret-texte { max-width: none; }

/* --- C.5 : un lien dans une cellule est une cible, pas un mot ----------
   338 liens mesurés à 20 px de haut sur les seuls écrans `/` et `/planning`.
   Le seuil de WCAG 2.2 (2.5.8) est 24. `inline-block` leur donne une boîte —
   un lien en ligne n'en a pas — et deux pixels de creux la portent au seuil
   sans rien changer à l'interligne du tableau. */
table td a, table th a {
  display: inline-block;
  min-height: 24px;
  padding: 2px 0;
}

/* La navigation d'une liste paginée : le compte d'abord, les liens ensuite.
   Le total se lit avant de chercher où l'on est. */
.pagination { display: flex; flex-wrap: wrap; gap: 12px; align-items: baseline;
              margin: 8px 0; }

/* L'écran Paramètres, par rubrique. Le titre porte le compte, et la rubrique
   entière disparaît quand la recherche n'y trouve rien. */
.rubrique-parametres { margin-bottom: var(--e4); }
.rubrique-parametres[hidden] { display: none; }
/* Brief du coût salarial, 30/09, § 3.2, 4 : les cotisations et leur total
   calculé, en tête de « Paie et charges ». */
.bloc-cotisations { margin: 0 0 var(--e3); max-width: 42rem; }
.bloc-cotisations caption { text-align: left; padding-bottom: var(--e2); }

/* 🔴 Audit du 26/09, S6 : le mot de passe initial, montré une seule fois
   dans la réponse du POST. Assez grand pour être recopié sans erreur. */
.secret-initial code {
  font-size: 1.25rem;
  letter-spacing: 0.08em;
  user-select: all;
}
/* Brief de la migration VPS, § 3.1 : l'enrôlement du second facteur, et les
   dix codes de secours montrés une fois. */
.etapes-d-enrolement { padding-left: var(--e4); }
.etapes-d-enrolement li { margin-bottom: var(--e2); }
.qr-d-enrolement { margin: var(--e2) 0; }
.qr-d-enrolement svg { display: block; }
.cle-d-enrolement {
  letter-spacing: 0.08em;
  user-select: all;
  word-break: break-all;
}
.codes-de-secours {
  list-style: none;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--e2);
}
.codes-de-secours code {
  font-size: 1.1rem;
  letter-spacing: 0.08em;
  user-select: all;
}
/* Audit du 26/09, T12 : l'effacement sur demande, replie par defaut. */
.zone-sensible { border-top: 1px solid var(--filet); }
.zone-sensible > summary { cursor: pointer; color: var(--etat-impossible); }
