
:root{
  /* LA GOUTTIÈRE - le rembourrage horizontal du corps sous 640 px, et la SEULE source de cette
     valeur. Les blocs qui sortent volontairement du rembourrage (le cartouche et la feuille en
     plein écran) la compensent par une marge négative : ils écrivaient `-14px` en dur pour un
     rembourrage de 12, d'où 4 px de page en trop et un défilement latéral que DESIGN.md
     interdit. Deux règles qui disent la même chose divergent toujours ; celles-ci LISENT. */
  --gouttiere:12px;
  /* HAUTEUR DU BANDEAU « DÉMONSTRATION » - LA source de cette valeur : le bandeau la POSE et la
     barre du haut la LIT pour caler son ancrage collant. Deux nombres pour une même hauteur
     divergent (règle 9), et ici la divergence se voit tout de suite : la barre recouvre le
     bandeau ou laisse un trou. 0 en production, où le bandeau n'existe pas. */
  --demo-h:30px;
  /* COULEURS. Ces valeurs sont celles qui s'appliquent - plus aucune couche ne les corrige
     plus bas. Elles ont porté un indigo #4b45d1 jusqu'au 28.07, écrasé 578 lignes plus loin par
     un bloc « OVERRIDE ARDOISE » : le haut du fichier annonçait une couleur que l'application
     n'a jamais affichée, et c'est en la citant qu'une question a été posée à Rémy pour rien. */
  /* ── 01.08 (soir) - LA PALETTE CHANGE, ET AVEC ELLE LE MÉTIER DE LA COULEUR ────────────────
     Rémy a fourni la palette et tranché après réserves écrites : space-indigo #2d3047 · teal
     #087e8b · celadon #abd1b5 · mint-cream #edf4ed · rosewood #ba324f. L'ardoise #5C6E99, épinglée
     le 28.07, est REMPLACÉE - ce n'est pas une dérive, c'est une décision.
     LA RÈGLE QUI GOUVERNE MAINTENANT : **si c'est coloré, ça se clique**. Mesuré au navigateur sur
     l'accueil avant correctif : 58 éléments cliquables, UN SEUL portait la moindre chroma, et 11
     objets colorés étaient inertes. La couleur était exactement à l'envers - elle peignait les
     états, qu'on ne peut que lire, et désertait les commandes. Tenu par l'invariant K.
     QUATRE des cinq couleurs tombaient sur la légende métier (rosewood ↔ alarme Δteinte 11,5° ;
     mint-cream ↔ fond « garde tenue » ΔL* 0,49 - la MÊME couleur ; celadon ↔ fatigue calme
     ΔL* 3,4). On ne les fait donc pas COHABITER avec la légende, on la lui fait ABSORBER : le
     rosewood DEVIENT l'alarme, le mint-cream DEVIENT le fond acquis, le celadon DEVIENT la bande
     calme. Deux couleurs voisines qui ne servent pas au même endroit finissent toujours par se
     confondre ; une couleur qui EST le jeton ne le peut pas.
     Le teal brut ne sait pas ÉCRIRE : #087e8b mesure 4,32:1 sur la feuille, sous le plancher.
     D'où `--accent-ink` #006f7b (5,29:1 sur la feuille, 5,90 sur la surface) - la règle de l'aplat
     et de l'encre, appliquée à l'accent lui-même. */
  --accent:#87a0b2; --accent-2:#2f546a; --accent-ink:#43687e; --violet:#c0c1c4; --on-accent:#02111b;
  /* L'EXERGUE (01.08). La palette n'a laissé AUCUNE couleur libre pour la mise en exergue : ses
     cinq membres sont pris par la charpente, l'action et trois signaux métier. L'indigo fait donc
     double emploi - charpente ET filet d'exergue - et c'est assumé, pas subi : il n'apparaît en
     exergue que sur la feuille, où la charpente n'est jamais. Un seul exergue par écran. */
  --ex:#3f4045; --ex-ink:#58595e; --ex-pale:#e3ecf4;
  /* 29.07 - L'ENCRE POSÉE SUR UN APLAT TEINTÉ, POUR LE VERT ET LE ROUGE AUSSI.
     Ces deux tokens existaient dans `portail_med.html` DEPUIS LE 27.07 et n'ont jamais atteint
     la feuille partagée. Résultat : le portail était juste, la CONSOLE peignait `#fff` en dur -
     23 règles, dont 6 à 2,42:1 en thème sombre (logo, bascule de rôle, onglet actif). C'est la
     MÊME divergence qu'au 28.07, à six heures d'intervalle, et exactement ce que le déménagement
     du monde visuel devait empêcher. Tant que le portail garde sa copie des 292 règles, ça
     recommencera : la vraie correction est de lui faire CONSOMMER cette feuille.
     Valeurs reprises TELLES QUELLES du portail - surtout pas recalculées : deux définitions du
     même token divergent d'un demi-ton, puis d'une règle entière (règle 9).
     MESURÉ le 29.07, jamais supposé - sombre : accent 2,42 → 7,75 · vert 2,36 → 7,59 ·
     rouge 3,15 → 5,71. En clair les trois valent #ffffff : la substitution n'y change rien. */
  --on-green:#02111b; --on-red:#ffffff;
  /* 09.08 - L'ENCRE DU SURVOL DU BOUTON PRINCIPAL. Les quatre boutons pleins (console .ui-btn.primary
     et .go.primary/.go.vert, portail .lg2-btn et .btn) foncent au survol vers --accent-2 SANS changer
     d'encre : --on-accent (#02111b) y mesurait 2,36:1 en clair - sous le plancher, précisément sur
     l'action qu'on s'apprête à cliquer. Même construction que --on-red : blanc en clair (8,09:1 sur
     #2f546a), encre-noire en sombre (--accent-2 y est un bleu CLAIR #aecade, 11,2:1). Mesuré, jamais supposé. */
  --on-accent-2:#ffffff;
  /* 29.07 - LES ENCRES DES FRISES (Tournus). Trois familles de fond y cohabitaient sans qu'aucune
     n'ait d'encre à elle, et l'encre de l'ACCENT était posée sur les trois : en thème sombre elle
     bascule à #111118 et l'on obtenait du quasi-noir sur du bleu nuit - 13 mesures entre 1,81:1
     et 3,75:1. Chaque famille a désormais la sienne, mesurée :
       · `--on-frise`  encre des 13 teintes FIXES de la frise CDC (fonds sombres, identiques dans
                       les deux thèmes) : blanc, de 5,02:1 à 10,36:1 selon la teinte.
       · `--sur-pale`  encre des fonds PÂLES figés (#cfe9da « présent », #94a3b8 « Off ») :
                       14,61:1 et 7,33:1. Le blanc y donnait 1,29:1 - la pastille de légende
                       « présent / dispo » était illisible dans LES DEUX thèmes.
       · `--on-ink-soft` encre sur un aplat gris `--ink-soft` : 5,01:1 en clair, 6,93:1 en sombre
                       (le blanc y tombait à 2,71:1 - c'est la pastille « Vacances »).
     Ces trois-là ne portent pas de sens métier : ce sont des encres, et elles n'existent que
     pour être MESURÉES contre leur fond. */
  --on-frise:#ffffff; --sur-pale:#02111b; --on-ink-soft:#ffffff;
  /* ══ LE SECOND BRIN D'UNE TRAME (05.08) ══════════════════════════════════════════════════
     UNE TRAME N'EST PAS UN TROU. Les cellules d'absence du Tournus étaient hachurées
     `repeating-linear-gradient(45deg, var(--ink-soft) 0 2px, var(--card) 2px 4px)` : la moitié
     des pixels laissait voir la CARTE, c'est-à-dire le plan sur lequel l'encre de l'aplat n'a
     aucune raison de tenir. Mesuré au navigateur, sur 99 cellules et sur la légende `.frzleg` :
     **1,00:1 en thème clair** (blanc sur carte blanche) et **1,14:1 en sombre** (encre-noire sur
     carte sombre). À l'écran, « Vacances », « Formation » et « Indispo » s'affichaient comme
     trois pastilles hachurées SANS AUCUN TEXTE - et c'est l'écran dont Rémy avait déjà dit
     « on lit vraiment mal les tournus ».
     `--on-ink-soft` avait bien été mesuré (5,01:1 / 6,93:1) - contre l'APLAT PLEIN. La trame est
     arrivée après, et personne n'a re-mesuré le second brin. C'est la leçon de l'invariant J,
     reprise : le fond n'est pas toujours ce qu'un `background:` déclare.
     LA RÈGLE QUI EN SORT : le second brin est un TON DU PREMIER, déplacé À L'OPPOSÉ de l'encre
     qui s'y pose - jamais un autre plan. Ainsi la trame ne peut que RENFORCER le contraste :
     le pire des deux brins reste le brin plein, celui qu'on a déjà mesuré.
       · sur `--ink-soft` (encre blanche en clair, noire en nuit) : clair 5,19 et 6,79 → pire 5,19 ;
         sombre 7,34 et 9,07 → pire 7,34. Écart de trame ΔL* 7,3 et 6,8 : la hachure se VOIT.
       · sur le pâle figé #94a3b8 (« Off », encre `--sur-pale`) : 7,46 et 9,20, ΔL* 6,8.
         Celui-là était juste en clair par accident (la carte y est blanche) et faux en nuit.
     Teintes conservées à 205° et 215° - les brins restent dans leur famille. */
  --trame-ink-soft:#48555e;   /* sous l'ink-soft : l'encre y est BLANCHE, le brin descend.
                                 RE-DÉRIVÉ le 05.08 avec le recalibrage de `--ink-soft` : le
                                 brin suit son aplat, sinon la trame se referme (l'écart serait
                                 tombé de ΔL* 7,3 à 1,3 - une hachure invisible). 7,67:1. */
  --trame-pale:#a9b5c6;       /* sous le pâle figé : l'encre y est NOIRE, le brin monte */
  /* `--ink-soft` valait #6d6f78 : 5,01:1 sur la surface blanche, mais seulement 4,36 à 4,48 sur
     les QUATRE aplats pâles de signal (rouge, bleu, vert, ambre) - sous le plancher, sur les
     libellés et les marginalia qui vivent précisément dans ces encarts. Assombri de quatre unités,
     calculé sur les six fonds où il se pose : le pire cas passe de 4,36 à 4,63. À l'œil, rien ne
     bouge ; c'est le but - une encre atténuée doit rester atténuée, et rester lisible. */
  /* ── LA FEUILLE PREND LE GRAIN DE L'ARDOISE (01.08) ────────────────────────────────────────
     `--bg` valait #F4F5F7, un gris quasi neutre. Le monde s'appelle « la feuille et son
     cartouche » : une feuille topographique n'est pas blanche, et deux blancs superposés ne font
     pas un champ, ils font un vide. Mesuré sur l'écran d'accueil de la console, la SEULE mesure
     qui décrivait vraiment le grief « ça manque d'atmosphère » : 0,08 % de la surface portait la
     moindre chroma, et l'accent de marque n'y peignait AUCUN pixel.
     La feuille reçoit donc la teinte de l'ardoise, très diluée. Ce n'est pas de la couleur, c'est
     de la MATIÈRE : à l'œil rien n'est « bleu », mais le blanc des blocs redevient du papier
     POSÉ sur quelque chose au lieu de se fondre dans le fond.
     Effet second, et il sert le premier grief : l'écart feuille→surface passe de ΔL* 3,49 à 4,50
     - le pas que le thème SOMBRE tenait déjà (4,05). Les deux thèmes séparent enfin leurs plans
     du même geste, au lieu que le clair soit le plus plat des deux.
     L'encre atténuée y est REMESURÉE, pas supposée : 4,74:1 (contre 4,87 sur l'ancien fond),
     au-dessus du plancher de 4,5. */
  /* ⚠ CE QUI PRÉCÈDE DÉCRIT LE GRAIN D'ARDOISE DU 01.08 (matin) : le principe TIENT, la teinte
     a changé le soir même. La feuille porte désormais la teinte de l'INDIGO de la charpente, même
     dilution - « un peu de saturation dans les gris » (Rémy). Chroma 4 à 7 en Lab, plus au milieu
     de l'échelle où l'œil la voit, moins aux extrêmes : rien ne paraît bleu, mais le blanc des
     blocs redevient du papier POSÉ sur quelque chose. Encre atténuée remesurée sur la nouvelle
     feuille : 4,73:1, au-dessus du plancher. */
  /* ⚠⚠ L'ENCRE ATTÉNUÉE NE TENAIT PAS SUR LES TROIS LAVIS DE SIGNAL (05.08). Sa note ci-dessus
     dit l'avoir « calculée sur les six fonds où elle se pose », et le pire cas y passe « de 4,36
     à 4,63 ». Cette mesure portait sur la FEUILLE et la SURFACE - pas sur les lavis. Mesurée
     contre les cinq plans qui la portent réellement :
        clair  : --bg 4,63 · --card 5,19 · --blue-bg 4,35 · --green-bg 4,35 · --orange-bg 4,34
        sombre : --bg 6,42 · --card 5,77 · --blue-bg 4,43 · --green-bg 4,46 · --orange-bg 5,04
     Cinq combinaisons sous le plancher, sur les trois encarts que le produit emploie pour DIRE
     quelque chose - l'information, l'acquis, la vigilance. C'est là que vivaient les 41 textes
     à 4,43:1 relevés à l'audit, et c'est un défaut de CALIBRAGE du jeton, pas une occurrence :
     l'encre atténuée de la feuille doit tenir sur les fonds de la feuille.
     Assombrie (clair) et éclaircie (sombre) de trois centièmes, teinte conservée à 205° :
     pire cas **4,88:1** en clair, **4,87:1** en sombre. L'écart avec l'encre pleine reste de
     38 points de clarté en clair et 21 en sombre - la hiérarchie ne bouge pas à l'œil.
     ⚠ CE JETON EST AUSSI UN APLAT (la trame d'absence du Tournus le prend pour fond). Le
     déplacer déplace la trame : `--on-ink-soft` remesuré (5,84:1 en clair, 8,06:1 en sombre)
     et `--trame-ink-soft` re-dérivé pour garder son ΔL* 7. On ne bouge pas une encre qui sert
     d'aplat sans refaire les deux. */
  --ink:#02111b; --ink-soft:#586772; --line:#d7e1e8; --bg:#ecf3f9; --card:#ffffff; --silver:#8b98a1;
  /* ── ÉCHELLE DE FORCE D'UNE RÈGLE (`--rang-*`, 07.08) ────────────────────────────────────
     Cinq paliers, du plus négociable au plus intouchable : très souple · souple ·
     moyenne · dur · extrême (vocabulaire de Rémy, 07.08 - le mien était inventé).
     Employés par l'écran des réglages du solveur : ÉCHANTILLON de la légende et `accent-color`
     de la glissière.
     ⚠ C'EST UNE ÉCHELLE, DONC ELLE SE LIT EN NIVEAUX DE GRIS (DESIGN.md, invariant I). Mon
     premier choix - ink-soft / ink / orange-ink / red-ink - a été MESURÉ avant d'être posé et
     REJETÉ : ΔL* de 0,8 entre « souple » (42,7) et « dur » (42,0) en clair, 2,6 en sombre. Deux
     teintes différentes à clarté identique, c'est précisément l'échelle codée par la seule teinte
     que la charte interdit. Et `check_charte` ne l'aurait PAS vue : son invariant I découvre les
     familles par PRÉFIXE de jeton, or ces quatre-là n'en partageaient aucun - la leçon de
     l'invariant J, prise une fois de plus. D'où ce préfixe `--rang-`, qui les fait entrer dans le
     balayage tout seuls.
     ⚠ REFONDUES LE MÊME JOUR, SUR SIGNALEMENT DE RÉMY : « en light, couleur pas lisible, aucune
     couleur de vraiment lisible. » Il avait raison, et la cause n'était pas le choix des teintes
     - c'était le RÔLE qu'on leur avait donné. Version rejetée : #6b7377 · #505e64 · #364a52 ·
     #492c10 · #3e0f0c. Elles teintaient un MOT, donc elles devaient tenir 4,5:1 sur la feuille
     claire, donc AUCUNE ne pouvait dépasser L* 45 - et sous L* 45 il ne reste ni cyan, ni vert,
     ni or : cinq gris-ardoise et deux presque-noirs. Les cinq paliers étaient mesurés bons
     (ΔL* 8,8) et illisibles à l'œil, parce que la mesure disait « distincts en clarté » là où
     l'œil demandait « distincts en couleur ». C'est la limite de l'invariant I dite à l'envers :
     un écart de clarté suffisant ne fabrique pas une couleur reconnaissable.
     LE CORRECTIF EST UN CHANGEMENT DE RÔLE, PAS UNE RETOUCHE. La couleur n'écrit plus le mot :
     elle est un ÉCHANTILLON posé à côté d'un mot en encre pleine (`.rs-leg b i`). Un aplat de
     22 × 10 px n'est pas du texte - son plancher est 3:1 (composant d'interface) et non 4,5:1 -,
     et la bande utile s'ouvre de L* 32 à 57. Le mot, lui, y a GAGNÉ : de 3,3:1 à 19,1:1.
     ⚠ ET LA PREMIÈRE RAMPE POSÉE DANS CETTE BANDE ÉTAIT MOCHE - dit par Rémy, à raison, dans
     l'heure : #009396 · #008734 · #776500 · #9a3700 · #98001e. Cyan, vert, or, orange, rouge, au
     CHROMA MAXIMAL du gamut (33 à 65). J'avais optimisé « le plus coloré possible » comme si
     c'était la même chose que « lisible », alors que TOUTE la palette de ce produit vit entre
     chroma 13 et 45 : accent 13 · green 30 · orange 42 · red 45. Une rampe à 65 n'est pas plus
     visible, elle est ÉTRANGÈRE - cinq couleurs qui n'appartiennent à aucun autre écran.
     LA RAMPE DÉFINITIVE EST CELLE DU PRODUIT, prolongée d'un cran. Elle reprend ses quatre
     teintes exactes - 249° (accent) · 145° (green) · 75° (orange) · 25° (red) - et intercale un
     sienna à 45° pour le cinquième palier. Chroma 15 → 50, dans le registre. Résultat : « souple »
     EST le vert du produit, « moyenne » son ocre, « extrême » son rosewood assombri. Rien de neuf
     à apprendre à l'œil.
     MESURÉ sur les valeurs ci-dessous (clarté imposée, teinte imposée, chroma visé puis rabattu
     au gamut) : ΔL* consécutifs 6,1 · 6,3 · 6,2 · 6,4 en clair - plancher 3,5 - et contrastes de
     3,13:1 à 7,76:1 sur les deux fonds de l'écran. La clarté MONTE avec la force dans les deux
     thèmes (comprendre : le CONTRASTE monte - en clair la couleur s'assombrit, en sombre elle
     s'éclaire).
     ⚠ CETTE RAMPE EMPRUNTE DES COULEURS QUE LE PRODUIT NOMME DÉJÀ (vert/orange/rouge de l'IOT,
     de la fatigue, des légendes). C'est délibéré et c'est le prix à payer : ici elles disent
     l'INTENSITÉ, pas le verdict. Le mot est à côté, en toutes lettres, pour cette raison. */
  --rang-tres-souple:#6f8da1; --rang-souple:#56835b; --rang-moyenne:#8b6123;
  --rang-dur:#924228; --rang-extreme:#8e222d;
  /* LA LÉGENDE ABSORBE LA PALETTE (01.08 soir - voir la note de l'accent). Le rosewood EST
     l'alarme, le mint-cream EST le fond acquis. Mesuré, jamais supposé : blanc sur rosewood
     5,74:1 · blanc sur le vert 5,18:1 · encre acquise sur son fond 4,80:1. Le vert d'aplat a été
     assombri de L* 52 à 46 pour que `--on-green` (blanc) repasse le plancher : à L* 52 il
     mesurait 4,19:1. La vigilance est le seul signal que la palette ne fournit pas - elle ne
     bouge donc pas. */
  /* ══ LA LÉGENDE REPREND SES TROIS COULEURS NOMMÉES (02.08, signalé par Rémy) ═══════════════
     Le portage sur quatre couleurs avait fondu les signaux dans la palette : l'acquis en acier,
     l'alarme en vanille, la vigilance en gunmetal. Mesuré, tout tenait. À l'écran, c'était FAUX -
     et pour une raison qu'aucun contraste ne peut voir : **ce produit NOMME ses couleurs dans son
     propre texte**. Le sélecteur IOT propose « vert (supervise) · orange (autonome) · rouge (ACR
     seul) », les légendes disent « ● atteint · ● proche · ● loin », `fatigue.bande()` renvoie les
     chaînes "vert"/"orange"/"rouge", et l'inbox écrit « 2 membres en zone fatigue (dont 1 en
     rouge) ». Un niveau appelé ROUGE peint en olive fait mentir l'interface à chaque ligne.
     D'où la séparation, qui est la bonne et qu'on aurait dû garder : **les quatre couleurs
     gouvernent l'INTERFACE** (charpente, feuille, action, attention) ; **la LÉGENDE MÉTIER garde
     ses trois couleurs nommées**, désaturées pour vivre avec elles, mais reconnaissables comme
     leur nom. Elles ne peignent QUE des niveaux - jamais un fond de page, jamais un contrôle.
     RAMPE MONOTONE, dans les deux thèmes : le rouge est TOUJOURS le plus loin de la page
     (clair L* 52 → 45 → 38 ; sombre 66 → 70 → 74). Elles se lisent donc aussi en niveaux de gris.
     ⚠ LA LEÇON, pour la prochaine palette : avant de retirer une couleur, chercher si le PRODUIT
     l'écrit en toutes lettres. Une contrainte de vocabulaire ne se voit dans aucune mesure. */
  --green:#59875e; --green-bg:#e0efe1; --orange:#8c6224; --orange-bg:#fae8d5; --red:#993a3b; --red-bg:#ffe4e1;
  --blue-bg:#dfedf7;
  /* ── BANDES DE FATIGUE - UNE ÉCHELLE, DONC UNE RAMPE DE CLARTÉ (01.08) ────────────────────
     Une échelle séquentielle ne se code pas par la TEINTE seule. Les deux palettes qui ont
     existé ici étaient construites sur la teinte, et TOUTES DEUX étaient illisibles en gris -
     mesuré au navigateur, luminance sur 255 :
       · ce que la bande peignait vraiment (--green/--orange/--red) : 160,3 · 160,6 · 131,9
         → calme et vigilance séparés de **0,3 sur 255**. Le même gris, exactement.
       · la famille --fat-* déclarée ici (et jamais utilisée) : écarts de 0,5 et 0,6.
     Autrement dit la palette DOCUMENTÉE était aussi cassée que celle en service ; adopter l'une
     pour l'autre n'aurait rien réglé. Le défaut n'était pas le choix des jetons, c'était le
     principe de construction.
     RAMPE : L* CIE régulier, un cran de 12 entre bandes voisines - écarts en gris désormais de
     31 à 37. Le voyage de teinte vert→ambre→rouge est CONSERVÉ (il porte le sens appris) ; c'est
     la clarté qui fait le travail, donc la bande se lit aussi en niveaux de gris, en daltonisme,
     et sur un écran de nuit mal réglé.
     PRINCIPE, valable dans les deux thèmes : PLUS C'EST CHAUD, PLUS C'EST LOIN DE LA PAGE.
     En clair la page est blanche → le calme est le plus pâle, le risque le plus sombre.
     En sombre la page est noire → l'ordre s'inverse, et ce n'est pas une inversion mécanique :
     le calme est l'état le PLUS FRÉQUENT, il reste donc discret la nuit, et c'est l'alarme qui
     brille. Chroma modéré près du blanc (une teinte pâle très saturée vire au fluo).
     `--fat-extreme` EST RETIRÉ : `fatigue.bande()` ne renvoie que vert/orange/rouge - aucun
     chemin de code ne pouvait produire une quatrième bande. C'était une valeur morte, comme
     l'indigo que DESIGN.md signale déjà. */
  /* 01.08 (soir) - LE CELADON DEVIENT LA BANDE CALME. Il était à ΔL* 3,4 et 1,4° de teinte de
     l'ancienne : deux noms pour une couleur. La rampe est RECONSTRUITE autour de lui pour garder
     le pas de L* 12 : 80,5 · 68,4 · 56,5 (pas de 12,1 et 11,9). Teintes du voyage vert→ambre→rouge
     conservées. Encre #14151a : 10,87 · 7,56 · 5,12 sur les trois. */
  --fat-calme:#b2d5b4; --fat-vigil:#d6af81; --fat-risque:#dc8b86;
  /* Encre des trois aplats. Elle NE BASCULE PAS : les trois aplats sont clairs dans les deux
     thèmes, donc son fond ne bascule pas non plus (même raisonnement que `--sur-pale`).
     Mesurée sur les six aplats : 4,63 au pire, 11,95 au mieux. */
  --fat-ink:#02111b;
  /* FRISE DE ROTATION. L'alternance 4+4 était peinte en --accent (SMUR) contre --violet (bloc) :
     deux violets voisins, illisibles côte à côte sur 53 semaines - on ne VOYAIT pas l'alternance.
     Règle retenue : le SMUR est COLORÉ (il est chez nous), l'ailleurs est SOURD (bloc = ardoise,
     urgences = sarcelle). Le contraste chaud/froid fait apparaître les blocs de 4 semaines.
     ⚠ LE CONTRASTE CHAUD/FROID NE SUFFISAIT PAS (01.08). Ce commentaire affirmait que
     l'alternance 4+4 « se voyait » depuis qu'on opposait l'ardoise à la sarcelle. Mesuré :
     ΔL* 2,4 en clair et 2,9 en sombre - deux teintes différentes, une seule CLARTÉ. En niveaux
     de gris, sur un écran de nuit mal réglé ou pour un daltonien, la frise redevenait ce qu'elle
     était avant le correctif : une bande unie sur 53 semaines. C'est le même défaut que les
     bandes de fatigue et que les postes, sur une troisième famille - d'où l'invariant I, qui les
     BALAIE toutes au lieu d'attendre qu'on y pense. Clartés écartées à ΔL* 9,2, teintes gardées.
     Ces deux teintes ne portent jamais de texte (la légende est à côté du pavé, pas dedans) :
     aucune encre à réapparier. */
  /* ⚠ LE FOND NE DOIT PAS CRIER PLUS FORT QUE LE SUJET (01.08, après retour de Rémy : « la
     couleur n'est pas si facile à lire »). La frise répond à « QUI EST AU SMUR, QUAND ». Or « aux
     urgences » est l'état par DÉFAUT du CDC - le sous-titre le dit : « le reste du temps aux
     urgences » - et il occupait jusqu'à 95 % de la surface en sarcelle PLEINE, mesurée à
     saturation 0,47 contre 0,25 pour l'accent du SMUR. La condition la moins intéressante était
     la plus criante, et ce qu'on vient chercher se noyait dedans : un mur de couleur, pas une frise.
     L'intention était pourtant écrite dans `ROTC` depuis le début - « SMUR = COLORÉ (il est chez
     nous) · ailleurs = SOURD ». Écrite, jamais tenue. Les deux « ailleurs » deviennent donc de
     vrais fonds (saturation 0,21 et 0,16), et le SMUR reste le seul aplat vif - et l'EXTRÊME de la
     rampe de clarté, ce qui le fait lire comme figure. Teintes conservées (sarcelle / ardoise) :
     on baisse la CHROMA et on écarte la CLARTÉ, on ne change pas l'identité des états.
     Clartés L* : hors tournus 95,5 · urgences 84,0 · bloc 75,5 · SMUR 46,6 (écarts 28,9 · 8,4 · 11,5). */
  /* ══ LA FRISE DE ROTATION - LE FOND CRIAIT PLUS FORT QUE LE SUJET (03.08, signalé par Rémy :
     « on lit vraiment mal les tournus ») ══════════════════════════════════════════════════════
     Le code de la frise porte depuis l'origine la règle « SMUR = COLORÉ (il est chez nous) ·
     ailleurs = SOURD ». Mesurée après le portage de palette, elle était FAUSSE : « aux urgences »,
     l'état MAJORITAIRE, était à chroma 13,9 et le SMUR à 13,0 - le fond était plus saturé que le
     sujet, et plus sombre, donc il dominait l'écran. Sur 53 semaines × 60 médecins, ça donne un
     mur d'acier où l'on cherche ses semaines à la loupe.
     C'est le MÊME défaut que le 01.08, revenu par une autre porte : là c'était la saturation, ici
     c'est le portage qui a rendu tout le monde acier. Une règle écrite dans un commentaire ne se
     défend pas toute seule.
     MAINTENANT : le SMUR est le SEUL chromatique (C 34 contre 2,2 pour les autres), et l'ailleurs
     est une rampe de gris. La frise se lit d'un coup d'œil : ce qui est bleu, c'est chez nous.
     ⚠ `--rot-smur` est un jeton NEUF. La frise empruntait `--accent` - un jeton d'ACTION - pour
     peindre un ÉTAT : deux sens dans une valeur, donc l'un des deux cassait à chaque changement
     de l'autre. C'est exactement ce qui vient de se produire. */
  --rot-smur:#1d85b2; --rot-anesth:#9a9b9f; --rot-urgences:#b5b6ba;
  /* LES DEUX NEUTRES DE SÉRIE (29.07). `#9aa0ab` était répété 8 fois dans `console_app.js`,
     `#9a9a9a` 4 fois et `#c08a3c` 3 fois - trois littéraux pour deux rôles : « rien à signaler »
     et « à surveiller, mais sans alarme ». Les deux gris étaient à deux unités l'un de l'autre :
     ils fusionnent. VOLONTAIREMENT identiques dans les deux thèmes, comme la palette de frise :
     une série doit rester reconnaissable d'un thème à l'autre. Ce sont des APLATS - ils peignent
     des barres et des points ; leur ENCRE passe par `encreDe` (console_app.js). */
  /* ⚠ `--serie-neutre` BASCULE DÉSORMAIS (02.08). Le commentaire ci-dessus dit « volontairement
     identiques dans les deux thèmes » - c'était vrai tant qu'il ne peignait que des points et des
     barres de série sur fond clair. Depuis que la jauge « atteint » du tableau hélico le porte, il
     est un APLAT posé sur la carte : mesuré **2,63:1 sur le blanc**, sous le plancher de 3:1. Il
     suit donc son fond, comme les axes.
     ⚠ ET L'AMBRE AUSSI - j'avais écrit ici même « il reste figé : 3,03:1, au-dessus ». C'était
     mesuré contre le BLANC, pas contre la FEUILLE, où il tombe à 2,71:1. L'invariant N m'a pris
     en défaut sur la ligne que je venais d'écrire : mesurer contre le mauvais fond, c'est ne
     rien mesurer (règle 13). */
  --serie-neutre:#8b8b8f; --serie-ambre:#7e784f;
  --fat-hd:#e3ecf4; --fat-hd-ink:#02111b; --fat-cell-line:#dfe6ec;
  --hover:rgba(2,17,27,.05);  /* survol de ligne / cellule (teinté à l'encre, adapté par le dark) */
  /* Employé ~12× (console + portail) mais jamais défini avant le 09.08 : chaque emploi retombait
     sur transparent. Le fallback `var(--chip-bg,var(--bg))` de console.py disait déjà l'intention. */
  --chip-bg:var(--bg);
  /* ── COULEURS DE POSTE - LA TEINTE DIT QUEL POSTE, LA CLARTÉ DIT COMBIEN IL PÈSE (01.08) ───
     SSOT visuelle du calendrier, de la légende et des tags, sur la console ET sur le portail.

     ⚠ ELLES ÉTAIENT QUATRE FOIS LA MÊME COULEUR. Mesurés au navigateur, les écarts de clarté
     entre voisines : 0,7 · 0,9 · 1,6 en clair (L* 93,6 → 95,5) et 0,7 · 1,0 · 1,3 en sombre,
     soit une étendue de 1,4 SUR 255 en niveaux de gris. Le 0,3 sur 255 séparant --sh-h de
     --sh-n en sombre est EXACTEMENT le chiffre que DESIGN.md cite comme preuve du défaut des
     bandes de fatigue - corrigé le 01.08 au matin, quelques lignes plus haut, dans ce fichier.
     Conséquence à l'écran : le calendrier était teinté sans être lisible. Pour savoir quel poste
     on regardait il fallait lire la lettre (« J · », « N · », « H · ») ; la couleur ne portait
     rien. C'est la définition d'une couleur décorative, ce que ce système dit refuser.

     LA RAMPE. Les quatre postes gardent leur TEINTE - le pourpre de l'hélico est une convention
     du service, il ne bouge pas - et reçoivent une seconde dimension : la CLARTÉ, qui ordonne
     jour → soir → nuit → hélico. L'ordre n'est pas décoratif, c'est le poids réel d'une garde :
     le jour est l'ordinaire, la nuit pèse (+15 %, cf. heures_poste), l'hélico engage une
     compétence qu'aucun forçage ne contourne. Plus le poste engage, plus il s'écarte de la page.
     Pas de ΔL* 4,0 en clair - au-dessus du ΔL* 3,5 que le thème clair s'impose déjà entre deux
     plans de travail. Le chroma monte à mesure qu'on descend : un ton pâle simplement assombri
     vire au gris, et l'on aurait remplacé quatre blancs par quatre gris.
     Tenu par l'invariant I de check_charte.py, qui balaie TOUTES les familles de couleur - la
     règle du 01.08 n'avait été appliquée qu'à celle qui l'avait inspirée.

     ENCRES APPARIÉES : inchangées, sauf --sh-ht. Le pourpre #8e44ad tombait à 3,59:1 sur l'aplat
     hélico devenu plus dense ; assombri À TEINTE CONSTANTE (le geste déjà posé le 28.07 pour
     --red-ink / --green-ink / --orange-ink), il mesure 4,61:1 sur son aplat, 6,58:1 sur la
     feuille et 7,18:1 sur la surface. Mesuré, jamais supposé. */
  /* ══ L'HÉLICO GARDE SON POURPRE (02.08, demande de Rémy) ═══════════════════════════════════
     Le portage sur quatre couleurs avait fait des quatre postes une rampe d'une SEULE teinte, et
     le pourpre était tombé avec les autres. Il revient - mais **sans quitter son barreau** : la
     rampe de clarté reste la structure (jour 92 → soir 87,5 → nuit 82 → hélico 76 en clair ;
     20 → 24,5 → 29 → 33,5 en sombre), et le pourpre est posé À la clarté de l'hélico, pas à la
     place de la clarté. La teinte (h 316) est celle du pourpre historique du service ; c'est sa
     CLARTÉ qui dit le poids du poste, sa teinte qui dit lequel c'est.
     Mesuré : encre 5,07:1 sur son aplat en clair, 5,71:1 en sombre ; ΔL* avec la nuit 6,07 et
     4,45 - au-dessus du plancher de 3,5 que l'invariant I impose à toute la famille `--sh-*`. */
  /* ══ LE SOIR PREND L'HEURE DORÉE (02.08, demande de Rémy) ══════════════════════════════════
     Comme le pourpre de l'hélico, cet ambre est posé À la clarté du soir, pas à la place de la
     clarté : la rampe tient (jour 92,1 → soir 86,5 → nuit 82,1 → hélico 76 en clair ; 19,9 →
     25 → 29,1 → 33,4 en sombre, écarts de 4,1 à 5,5). Deux postes portent donc une teinte propre
     - le soir et l'hélico - et deux restent sur l'acier : la teinte identifie, la clarté pèse.
     ⚠ EFFET DE BORD MESURÉ, ET CORRIGÉ ICI : en thème sombre, l'aplat du soir (L* 25) tombait
     sur `--orange-bg` (L* 24,05), même teinte chaude - un fond de vigilance et une garde du soir
     peuvent cohabiter dans une même cellule de calendrier. Le lavis de vigilance est descendu à
     L* 20 (ΔL* 4,9), son encre y gagne d'ailleurs : 6,44:1 contre 5,66.
     Encres mesurées sur leur aplat : 5,50:1 en clair, 6,57:1 en sombre. */
  --sh-j:#deeaf3; --sh-jt:#395e74; --sh-s:#fdd1a3; --sh-st:#6c4c24; --sh-n:#b4d0e4; --sh-nt:#295269; --sh-h:#cdb2da; --sh-ht:#583769;   /* hélico = POURPRE (convention) */
  --bleu:#43687e;
  /* ENCRES DE SIGNAL (28.07). Le vert, le rouge et l'ambre sont définis comme des APLATS -
     ils sont justes en fond. Posés comme ENCRE sur la feuille claire, aucun n'atteint 4,5:1 :
     le rouge plafonnait à 4,09 et servait de texte 140 fois. Ces variantes assombries ne
     servent QU'AU TEXTE ; les aplats gardent leur valeur, donc le monde visuel ne bouge pas.
     C'est le symétrique de `encre-sur-*`, qui règle le cas inverse (encre claire sur aplat).
     Calculées, pas choisies : plancher 4,6:1 sur la feuille, la surface et l'aplat pâle du
     signal lui-même. En thème sombre elles valent le signal, qui y est déjà lisible. */
  --red-ink:#993a3b; --green-ink:#3d6e44; --orange-ink:#845b1d; --violet-ink:#43687e;   /* compat : l'ancien bleu marine de la console suit l'accent - devenu teal le 01.08 */
  /* ENCRES DES SIX AXES D'ÉQUITÉ (29.07). Elles vivaient dans une table JS (`EQFIFA_ENCRE`),
     dont le commentaire disait lui-même qu'elles étaient « calculées, plancher 4,6:1 sur la
     feuille et sur la surface blanche » - donc pour le thème CLAIR uniquement. Posées telles
     quelles sur la carte sombre, les six chiffres du diagnostic d'équité tombaient à 3,5:1.
     C'est l'angle mort déclaré de `check_charte` : une encre sur un fond calculé en JS.
     Ici, ce sont des JETONS : le thème les bascule, et il n'y a qu'un endroit où les lire.
     Les APLATS, eux, restent en JS et ne basculent pas - une série doit rester discernable
     d'elle-même d'un thème à l'autre. Seule l'encre suit son fond. */
  /* ── LES SIX AXES D'ÉQUITÉ ENTRENT DANS LA CHARTE (02.08, signalé par Rémy) ──────────────
     Ils vivaient dans une table JS (`EQFIFA`, console_app.js) : six teintes vives choisies
     hors palette, jamais mesurées entre elles, et invisibles pour l'invariant I - qui balaie
     les JETONS, pas les tables JS. Mesuré sur l'écran Équité après la refonte : six aplats
     saturés, dont CINQ hors palette. C'est l'angle mort que J avait été inventé pour couvrir,
     mais J ne lit que `ROTC` ; la seconde famille peinte en JS lui échappait.
     Ils deviennent donc des JETONS - l'invariant I les balaie par le préfixe `--eq-ax-`, et un
     septième axe ajouté demain entrera dans la famille tout seul. Teintes ancrées sur la
     palette (rosewood 16° · ambre 72° · celadon 148° · sarcelle 175° · teal 213° · indigo
     289°) et surtout ÉCARTÉES EN CLARTÉ : pas de L* 5,0 entre voisines, contre 0,4 à 2,1
     auparavant. La frise se lit désormais en niveaux de gris - ce que six teintes vives à
     clarté égale ne permettaient pas, malgré leur vivacité.
     ⚠ ILS BASCULENT - J'AVAIS ÉCRIT L'INVERSE ICI MÊME, ET C'ÉTAIT FAUX (02.08, signalé par
     Rémy : « je t'ai pointé les mauvaises couleurs sur fond sombre »). J'avais gelé les six aplats
     « pour qu'une série reste reconnaissable d'un thème à l'autre » - un principe hérité, et juste
     en soi. Mais une VALEUR UNIQUE ne peut pas tenir 3:1 contre un fond L* 100 ET contre un fond
     L* 13,4. Mesuré : en SOMBRE `--eq-ax-helico` tombait à **2,65:1** sur la carte (la barre
     disparaissait) ; en CLAIR `--eq-ax-jours` à **2,52:1** et le neutre de série à **2,63:1** -
     donc la jauge « atteint » que je venais de poser sur le tableau hélico était elle-même sous le
     plancher. Le défaut était dans les DEUX thèmes, pas seulement celui qu'on regardait.
     C'est EXACTEMENT l'erreur du rail teal, commise deux heures plus tôt dans la même session sur
     une autre famille : une encre - un aplat ici - se calcule contre SON fond.
     Ce qui porte l'identité d'une série, ce n'est pas la valeur : c'est la TEINTE et l'ORDRE de
     clarté. Les deux sont conservés (helico < feries < heures < nuits < we < jours) ; seule
     l'échelle se déplace - L* 38→58 en clair, 55→75 en sombre. Plancher 3:1 contre la carte ET
     contre la piste de la jauge (WCAG 1.4.11, élément non textuel), tenu par l'invariant N. */
  --eq-ax-helico:#88898c; --eq-ax-feries:#5f5b33; --eq-ax-heures:#26526b;
  --eq-ax-nuits:#4a697e; --eq-ax-we:#6b8291; --eq-ax-jours:#797346;
  --eq-ink-heures:#26526b; --eq-ink-nuits:#4a697e; --eq-ink-we:#576d7d;
  --eq-ink-feries:#5f5b33; --eq-ink-helico:#6a6a6e; --eq-ink-jours:#716c3f;
  /* sidebar - neutre profond, plat (plus de dégradé navy) */
  /* 28.07 - MÊME CORRECTIF QUE LE PORTAIL, ET VOILÀ POURQUOI IL A FALLU L'ÉCRIRE DEUX FOIS.
     `--side-ink-soft` valait #7d7f8a : mesuré 4,27:1 sur la charpente #1b1c21 et 3,62:1 sur la
     pastille de rôle - sous 4,5 dans les DEUX thèmes. « Déconnexion » et l'onglet de rôle inactif
     étaient illisibles. La charpente est sombre quel que soit le thème : son encre se calcule
     contre SON fond, pas contre le thème. #9a9ca6 → 6,30:1 et 5,24:1.
     ⚠ Ce bloc et celui de portail_med.html sont DEUX copies du même monde : corrigé le matin dans
     le portail, le défaut vivait encore ici l'après-midi, dans la console. C'est le doublon qu'on
     résorbe juste en dessous (CARTOUCHE_CSS, servi aux deux pages). */
  /* 01.08 (soir) - LA CHARPENTE PASSE AU SPACE-INDIGO. Elle s'ÉCLAIRCIT : L* 10,4 → 20,5. C'est
     la conséquence la plus visible du choix de palette, et elle a été signalée avant : le cartouche
     n'est plus un quasi-noir, c'est une ardoise profonde. L'ordre des plans tient toujours
     (charpente 20,5 < feuille 95,6 < surface 100), et la règle du cartouche ne bouge pas : ce noir
     bleu porte la charpente et rien d'autre. Encre 10,51:1, atténuée 6,00:1, rail teal 6,34:1 -
     mesurés contre SON fond, jamais contre le thème. */
  --side-1:#02111b; --side-2:#141f26; --side-3:#09161d; --side-line:#2d3b44; --side-ink:#ffffff; --side-ink-soft:#aaabae;
  /* LE RAIL D'UNE JAUGE POSEE SUR LA CHARPENTE (06.08). `--side-line` separe deux zones de la
     charpente : a 1,6:1 sur le cartouche, il est fait pour se faire oublier. Le CONTOUR d'une
     jauge, lui, PORTE DU SENS - il dit ou s'arrete la mesure - et WCAG 1.4.11 lui demande 3:1.
     Mesure : 3,19:1 en theme clair, 3,29:1 en sombre. Meme famille que l'acier, assombrie a
     teinte constante : ce n'est pas une couleur de plus, c'est un barreau de plus. */
  --side-jauge:#516674;
  /* ── DEUX ENCRES DE SIGNAL POUR LA CHARPENTE (09.08) ─────────────────────────────────────
     Le « Point » pose ses données SUR le cartouche, dans les DEUX themes - et les encres de
     signal de la feuille n'y survivent pas : mesurees sur #02111b, --orange-ink tombe a 3,18:1,
     --red-ink a 2,75:1, --green-ink a 3,20:1. Toutes sous le plancher de 4,5:1. Elles sont
     concues pour ecrire sur du clair ; les employer sur la charpente aurait livre du texte
     d'alerte illisible, exactement l'invariant C.
     Ces deux-ci sont les versants NUIT des memes familles, et valent la meme chose dans les deux
     themes - comme --side-jauge, et pour la meme raison : le cartouche est sombre des deux cotes.
     Mesure : attention 9,39:1 (clair) / 9,68:1 (sombre) · alarme 10,14:1 / 10,46:1. */
  --side-att:#dead6f; --side-alarme:#ffa59f; --side-ok:#9ad9a8;
  /* `--side-ok` (10.08) : il manquait un VERT à la charpente, et « Valider » n'avait donc
     aucune encre propre sur le cartouche. Choisi par la MESURE, pas à l'œil : 11,71:1 sur le
     cartouche clair, 12,08:1 sur le sombre, et ΔL* 4,99 du membre le plus proche de sa famille
     (`--side-alarme`) - au-dessus du plancher de 3,5 de l'invariant I. Les trois candidats plus
     saturés que j'ai essayés d'abord tombaient à ΔL* 1,0-1,7 : lus en niveaux de gris, ils
     étaient le MÊME ton que le rouge. */
  /* formes */
  /* LES FORMES SE DURCISSENT (29.07, reset visuel). 8/6/4 → 4/3/2. Un angle très arrondi
     matelasse, et un outil de planification n'a rien à amortir : c'est la signature du tableau de
     bord SaaS que ce système dit refuser depuis le premier jour. Le durcissement se lit surtout
     là où la forme se RÉPÈTE - cellules de calendrier, pastilles, cartes de mesure. Seule la
     pastille d'état reste pleinement ronde : sa forme porte du sens (« la règle du niveau rond »). */
  --radius:4px; --radius-s:3px; --radius-xs:2px;
  /* L'ANNEAU DE FOCUS EST UN JETON (01.08). Il était écrit EN DUR huit fois - cinq ici, deux dans
     le portail, une dans console_app.js - toutes à l'ancienne ardoise rgba(92,110,153,.18). Un
     changement d'accent laissait donc huit anneaux sur l'ancienne couleur, et rien ne pouvait le
     signaler : ce n'est ni un `color:` ni un `background:`, aucun invariant ne le regardait.
     Il se DÉRIVE de l'accent, comme la lueur du cartouche. */
  /* L'ENCRE DE LA NAVIGATION (02.08). Depuis que la navigation sort du coloriage, elle a besoin
     d'une encre à elle : elle n'est ni l'encre du texte (elle doit rester en retrait de l'onglet
     courant) ni l'encre atténuée du thème - en sombre, celle-ci est un GRIS, et Rémy veut du
     BLANC : « en mode sombre cela doit être en blanc les écritures ». Un jeton, deux valeurs,
     mesurées contre le plan qui les porte.
     ⚠ LA SECONDE VALEUR N'AVAIT JAMAIS ÉTÉ ÉCRITE (05.08). La ligne ci-dessus annonce « deux
     valeurs » ; il n'y en avait qu'une, #c0c1c4, recopiée du cartouche - et le commentaire du
     bloc sombre la mesure « 8,82:1 sur la surface », ce qui est vrai LÀ-BAS. Or les TROIS seuls
     consommateurs de ce jeton (`.seg button`, `.seg.sub button`, et `.seg.niv3` qui en hérite)
     vivent tous sur la FEUILLE, jamais sur la charpente : `#secbar` prend `background:var(--bg)`.
     Mesuré au navigateur en thème clair : **1,61:1 sur #ecf3f9**, 1,80:1 sur la carte. Toute la
     barre d'espace de 2e niveau était illisible sur 26 des 32 écrans - seul l'onglet courant,
     qui passe à `--ink`, se lisait. C'est la QUATRIÈME fois qu'une valeur gelée ne tient pas un
     rapport sur deux fonds (`--side-ink-soft` 28.07, la charpente 29.07, `--cartouche-action`
     02.08) - et la première où aucun garde-fou ne pouvait le dire : voir l'invariant O.
     LA VALEUR CLAIRE : #596975, teinte 205,7° - celle de `--ink-soft` et de l'acier, à 0,6° près.
     Mesurée sur les DEUX plans que la navigation touche : 5,07:1 sur la feuille #ecf3f9,
     5,67:1 sur la carte #ffffff. Et elle garde le RANG que le sombre lui donne déjà : la
     navigation est d'un cran PLUS PRÈS de l'encre que la prose atténuée (clair L* 43,5 contre
     46,0 pour `--ink-soft` ; sombre L* 78 contre 65) - un onglet est un contrôle, pas un
     accompagnement. L'onglet courant garde ses 39 points de clarté d'avance. */
  --nav-ink:#596975;
  --anneau:0 0 0 3px rgba(135,160,178,.42);
  /* LE CONTOUR D'UN CONTRÔLE (01.08). « Seuls les cliquables sont colorés » ne se joue pas que
     dans l'encre : un bouton fantôme au filet neutre reste un rectangle inerte. Son contour prend
     donc l'accent, très dilué - assez pour dire « ceci se presse », pas assez pour crier. */
  --accent-bord:rgba(67,104,126,.34);
  --shadow:0 1px 2px rgba(2,17,27,.06);
  --shadow-s:0 1px 1px rgba(2,17,27,.05);
  /* LE VOILE (29.07). Le système déclarait ses ombres et pas ses VOILES : chaque écran modal en
     inventait un - rgba(15,23,42,.12), rgba(10,14,30,.55), rgba(10,10,20,.42) : trois bleus nuit
     différents, aucun dans la palette. C'est le trou qu'un faux positif du détecteur avait mis au
     jour (« hors palette » : la règle n'avait rien à quoi comparer). Comme une ombre, un voile se
     teinte à l'ENCRE, jamais à une couleur d'ambiance. Deux profondeurs : le voile léger d'un
     traitement en cours, le voile franc d'une boîte de dialogue. */
  --voile:rgba(2,17,27,.30); --voile-fort:rgba(2,17,27,.58);
  --font:'Archivo',system-ui,-apple-system,'Segoe UI',Roboto,Arial,sans-serif;
  /* RESET DU 29.07 - « la typographie devient éditoriale » (DESIGN.md). Le display passe de
     Fira Sans à NEWSREADER, une serif de presse : un planning est un document qu'on LIT, pas un
     tableau de bord qu'on surveille. Le contraste serif/monospace remplace le contraste bloc/fond
     qu'on vient de retirer des contrôles. Replis en serif système, jamais en sans. */
  --display:'Newsreader',Georgia,'Times New Roman',serif;
  /* LA CHASSE FIXE EST UN JETON (29.07). Elle était réécrite en toutes lettres 20 fois dans cette
     feuille et dans le portail - c'est-à-dire 20 endroits à corriger le jour où la pile de repli
     change, et 20 occasions de diverger (règle 9). Elle porte trois métiers : la légende, la
     marginalia, et depuis le reset les HORAIRES - `07:30→07:30` s'aligne d'une ligne à l'autre.
     ⚠ LE JETON EXISTAIT DEPUIS LE 29.07 ET N'ÉTAIT PRESQUE PAS EMPLOYÉ (05.08). En venant changer
     de monospace, on a compté **26 piles écrites en toutes lettres** - 12 dans cette feuille,
     12 dans le portail, 2 dans console_app.js - contre une poignée de `var(--mono)`. La règle 9
     avait été écrite ici même, puis appliquée à quatre endroits sur trente. Elles y passent
     toutes : changer de fonte est désormais CETTE ligne, et rien d'autre.
     ── SPACE MONO → IBM PLEX MONO (05.08, choisi par Rémy sur maquette parmi six jeux).
     Ce qui a décidé, et c'est mesuré : à 11 px - la plus petite taille du système, celle des
     étiquettes, des horaires et de l'horodatage - Space Mono avait la plus petite hauteur d'x
     du lot (rapport x/H de **0,709**, contre 0,739 ici). Ce n'est PAS la largeur qui l'a
     départagée : j'avais d'abord reproché à Space Mono d'écrire « 08:00 » 18 % plus large
     qu'Archivo, ce qui revenait à reprocher à une monospace d'être monospace - mesurées, les
     quatre candidates tiennent toutes entre 33 et 34 px. La largeur ne dit rien, la hauteur d'x
     dit tout.
     Trois graisses sont servies (400 · 600 · 700) parce que la feuille en demande trois :
     `.kpi .lab` écrit en 600, `.fcal-mois` et `.fcal-jd b` en 700. Space Mono n'en avait que
     deux et le 600 était SYNTHÉTISÉ par le navigateur - une graisse fausse, que personne ne
     pouvait voir. */
  --mono:'IBM Plex Mono',ui-monospace,Menlo,Consolas,monospace;
  /* ── LES TROIS FAMILLES DU CALENDRIER DE FORMATION (03.08) ────────────────────────────
     Rampe de clarté, comme les postes : F SMUR 92 → F REGA 87 → Formations 82 en clair,
     34 → 28 → 22 en sombre. La teinte identifie, la clarté sépare - et le LIBELLÉ est
     toujours écrit à côté, jamais remplacé par la couleur. Encres mesurées sur leur
     aplat : 5,01 à 6,86:1. */
  --fcal-smur:#d7ebfa; --fcal-smur-t:#26526b; --fcal-rega:#f6d4b3; --fcal-rega-t:#6a4d2b;
  --fcal-autre:#cbccd0; --fcal-autre-t:#4f5055;
}
*{box-sizing:border-box}
body{font-family:var(--font);background:var(--bg);color:var(--ink);font-size:14px;line-height:1.55;
  font-weight:400;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;letter-spacing:-.05px;margin:0}
/* ⚠ UN CONTRÔLE N'HÉRITE PAS DE LA POLICE DU DOCUMENT (29.07, trouvé par la passe d'audit de la
   vue « Générer »). Le navigateur impose sa propre face aux `button`, `input`, `select` et
   `textarea` - sur macOS, Arial 13,33 px. Les classes qui pensaient à écrire `font-family:inherit`
   allaient bien (`.ui-btn`, `.btn`, `.seg button`) ; `button.go` - LA classe de bouton la plus
   employée de la console - ne le faisait pas. Tous ses boutons étaient donc composés en ARIAL,
   dans une application dont la charte déclare trois familles choisies. Le défaut ne se voyait pas
   parce qu'une grotesque ressemble à une autre en 12,75 px ; il s'est vu en le MESURANT.
   Corrigé à la racine plutôt que classe par classe : tout contrôle hérite, désormais et pour tout
   contrôle écrit demain. Les champs qui veulent la chasse fixe la redemandent plus bas. */
button,input,select,textarea{font-family:inherit}
h1,h2,h3,.display{font-family:var(--display);font-weight:600;letter-spacing:-.3px}
/* Un lien ne se teinte plus : il se SOULIGNE. C'est l'affordance la plus ancienne du web, et
   elle ne coûte pas une couleur. Le survol épaissit le filet et le passe à l'accent. */
a{color:var(--ink);text-decoration:none;box-shadow:inset 0 -1px 0 var(--silver)}
a:hover{box-shadow:inset 0 -2px 0 var(--accent)}
a.hb-link,a.sb-link,a.go,a.ui-btn,a.btn,a.porte,a.lg2-lien{box-shadow:none}
a.hb-link:hover,a.sb-link:hover,a.go:hover,a.ui-btn:hover,a.btn:hover,a.porte:hover{box-shadow:none}

/* ---------- layout coquille ---------- */
.shell{display:grid;grid-template-columns:248px 1fr;min-height:100vh}
.sb{background:var(--side-1);color:var(--side-ink);
  display:flex;flex-direction:column;position:sticky;top:0;height:100vh;border-right:1px solid var(--side-line)}
.sb-brand{display:flex;align-items:center;gap:11px;padding:18px 18px 16px}
/* LA MARQUE N'EST PLUS UN CARRÉ COLORÉ (29.07). Un carré arrondi teinté portant une initiale est
   le logotype par défaut de toutes les interfaces générées - et il posait, en prime, le plus gros
   aplat d'accent permanent de l'écran, présent sur CHAQUE page. Il devient une capitale gravée :
   la serif du display, un filet d'un pixel, un angle presque droit. L'écusson du service reste
   l'identité qui gouverne le DISCOURS ; il n'a jamais eu à colorer un carré de 32 px. */
.sb-logo{width:32px;height:32px;border-radius:var(--radius-xs);background:transparent;border:1px solid var(--side-line);
  display:grid;place-items:center;font-family:var(--display);font-weight:600;color:var(--cartouche-ink);font-size:15px;letter-spacing:0}
.sb-brand b{color:var(--cartouche-ink);font-size:15px;font-weight:600}
.sb-brand span{display:block;font-size:11px;color:var(--side-ink-soft)}
.sb-nav{padding:6px 10px;overflow:auto;flex:1}
.sb-grp{font-size:11px;text-transform:uppercase;letter-spacing:.7px;color:var(--cartouche-soft);margin:15px 12px 4px;font-weight:700}
.sb-grp-d{display:block;font-size:11px;text-transform:none;letter-spacing:0;color:var(--cartouche-faint);font-weight:500;margin-top:1px}
.sb-link{display:flex;align-items:center;gap:11px;padding:9px 12px;border-radius:var(--radius);color:var(--cartouche-ink);
  text-decoration:none;font-size:13.5px;font-weight:500;cursor:pointer;margin:2px 0;transition:.15s;border:none;background:none;width:100%;text-align:left}
.sb-link svg{width:17px;height:17px;flex:none;opacity:.85}
.sb-link:hover{background:rgba(255,255,255,.06);color:var(--cartouche-ink)}
.sb-link.on{background:rgba(255,255,255,.11);color:var(--cartouche-ink);box-shadow:inset 2px 0 0 var(--accent)}
.sb-link .pillN{margin-left:auto;background:var(--red);color:var(--on-red);font-size:11px;font-weight:700;padding:1px 7px;border-radius:20px}
.sb-who{border-top:1px solid var(--side-line);padding:12px 16px;display:flex;align-items:center;gap:10px}
.sb-who .av{width:32px;height:32px;border-radius:50%;background:rgba(255,255,255,.08);display:grid;place-items:center;color:#e7e8ee;font-weight:600;font-size:13px}
.sb-who b{color:var(--side-ink);font-size:13px;font-weight:600} .sb-who small{display:block;color:var(--side-ink-soft);font-size:11px}

.main{display:flex;flex-direction:column;min-width:0}
/* 0,82 d'opacité ne masquait pas assez : le contenu défilait DESSOUS et restait
   fantomatiquement lisible à travers la barre. `DESIGN.md` proscrit le verre dépoli
   DÉCORATIF ; ici il ne l'est pas, mais il doit faire son travail - 0,96 laisse la
   sensation de matière sans laisser passer le texte. */
.tb{position:sticky;top:0;z-index:5;background:rgba(255,255,255,.96);backdrop-filter:blur(10px);
  border-bottom:1px solid var(--line);display:flex;align-items:center;gap:14px;padding:13px 26px}
.tb h1{font-size:26px;margin:0;font-weight:700;letter-spacing:-.02em}
.tb .sub{color:var(--ink-soft);font-size:11px;margin-top:1px}
.tb .spacer{flex:1}
/* LA LIGNE DE CARTOUCHE (01.08). L'en-tête de page empilait TROIS lignes - le titre, le
   sous-titre, puis « à jour 21:47 » sur une quatrième voix (11,5 px, sans-serif, sa propre
   couleur). Trois voix pour situer un écran, dans la bande la plus chère de l'application : elle
   se répète sur les 34 écrans, et le contenu commençait 208 px plus bas.
   Un cartouche de carte porte le titre ET sa date d'édition SUR LA MÊME LIGNE, séparés d'un
   filet. C'est ce que fait cette rangée : sous-titre · fraîcheur · marqueur, une seule voix
   (la meta monospace de `DESIGN.md`), un seul filet vertical entre deux mentions. 22 px repris
   sur chaque écran, et une graduation de moins à lire. */
.tb-meta{display:flex;align-items:center;flex-wrap:wrap;gap:0 10px;margin-top:3px;min-width:0}
.tb-meta>*{min-width:0}
/* Le sous-titre et son filet séparateur ont quitté cette rangée le 01.08 (couche de slogans) :
   leurs règles partent avec eux - une classe sans porteur est une ruine qu'on croit vivante. */
@media (max-width:620px){ .tb-meta{row-gap:2px} }
/* La fraîcheur parle désormais la MÊME langue que le sous-titre (meta monospace, 11 px, capitales,
   +0,04em) au lieu de sa quatrième voix à 11,5 px. Elle reste au plancher documenté, jamais en
   dessous. Le point garde sa transition : c'est lui qui pulse quand une donnée vient de changer. */
#tb_fresh{display:inline-flex;align-items:center;gap:6px;font-family:var(--mono);font-size:11px;
  letter-spacing:.04em;text-transform:uppercase;color:var(--ink-soft);transition:color .4s}
#tb_fresh_dot{width:7px;height:7px;border-radius:50%;background:var(--ink-soft);flex:none;
  display:inline-block;transition:background .4s,box-shadow .4s}
.content{padding:22px 26px 44px;max-width:1280px;width:100%}

/* ---------- sous-onglets (segmented) ----------
   UNE SEULE GRAMMAIRE D'ÉTAT, À TOUTES LES LARGEURS (29.07, reset visuel). L'actif se disait de
   DEUX façons selon la largeur : aplat d'ardoise sous 881 px, soulignement au-dessus. Deux
   grammaires pour un même état - et c'est la mauvaise qui s'affichait au téléphone, là où le
   portail est le plus utilisé. Désormais, partout et pour tous les sélecteurs : SOULIGNEMENT de
   2 px, encre pleine, graisse 600. Rien d'autre ne change entre l'état inactif et l'état actif,
   et c'est suffisant - c'est même la seule chose qui se lit du premier coup d'œil dans une rangée.
   La BOÎTE disparaît aussi : une rangée d'onglets n'a pas besoin d'être encadrée, un filet dessous
   suffit et il aligne les onglets sur le corps de la page. */
.seg{display:inline-flex;gap:2px;background:transparent;border:none;border-bottom:1px solid var(--line);border-radius:0;padding:0;flex-wrap:wrap;margin-bottom:16px}
/* ⚠ L'INVERSION DES ONGLETS DU 01.08 EST ANNULÉE (02.08) - voir la note de `.hb-link`.
   J'avais coloré l'onglet inactif « puisqu'il est cliquable ». Sur l'espace Planifier cela
   faisait HUIT sous-onglets teal d'affilée : un ruban de couleur en travers de la page, pour
   dire une chose que le filet de 2 px disait déjà. Retour à la grammaire du 29.07 : l'inactif
   en encre atténuée, le courant en encre pleine, soulignement de 2 px. */
.seg button{border:none;background:none;padding:9px 14px;border-radius:0;font-size:13px;font-weight:500;color:var(--nav-ink);cursor:pointer;font-family:inherit;transition:.12s}
.seg button:hover{color:var(--ink)}
.seg button.on{background:transparent;color:var(--ink);font-weight:600;box-shadow:inset 0 -2px 0 var(--accent)}
.seg.sub{background:transparent;border:none;border-bottom:1px solid var(--line);border-radius:0;padding:0;gap:0}
.seg.sub button{background:none;border:none;font-size:13.5px;font-weight:500;padding:11px 0;border-radius:0;color:var(--nav-ink)}
.seg.sub button:hover{background:transparent;color:var(--ink)}
.seg.sub button.on{background:transparent;color:var(--ink);font-weight:600;box-shadow:inset 0 -2px 0 var(--accent)}
/* BASCULE DE VUE À L'INTÉRIEUR D'UN ÉCRAN (`.bviewbtn`) - RAPATRIÉE ICI LE 05.08.
   Ses trois règles vivaient dans le <style> du gabarit de la BOURSE (non préfixées, donc globales
   le temps que cet écran soit affiché) ET, recopiées, dans celui des ABSENCES (préfixées
   `#p_absences`). Ce n'était pas de la négligence : `dessine()` ne monte QUE le panneau courant
   dans le DOM, donc la déclaration de la Bourse n'existe pas quand on regarde les Absences - la
   copie était NÉCESSAIRE tant que la règle vivait dans un gabarit. Le défaut n'est pas la copie,
   c'est qu'un composant PARTAGÉ n'avait pas de domicile : un troisième écran qui l'aurait employé
   se serait affiché SANS STYLE, sans que rien ne le signale.
   ⚠ Les cibles tactiles de `.bviewbtn` étaient DÉJÀ ici (règles `pointer: coarse`, invariant H) :
     la moitié du composant vivait dans la feuille, l'autre dans deux gabarits. */
.bviewbtn{border:none;background:none;padding:9px 2px;margin-right:22px;font-size:13.5px;font-weight:600;color:var(--ink);cursor:pointer;font-family:inherit;border-bottom:2px solid transparent;margin-bottom:-1px}
.bviewbtn:hover{color:var(--ink)}
.bviewbtn.on{color:var(--ink);border-bottom-color:var(--accent)}

/* ---------- boutons ---------- */
/* ── LA COULEUR CLASSE, ELLE NE SIGNALE PAS (02.08) ─────────────────────────────────────────
   Troisième et dernier recul sur « tous les cliquables sont colorés ». Sur la file « à traiter »
   du Hub, SIX boutons portaient l'accent - encre ET contour, donc la couleur posée deux fois
   par bouton, six fois de suite. Plus rien ne classait : la couleur éclairait tout au lieu de
   désigner. Et sur le quasi-noir du thème sombre, six contours cyan à L* 72 font le « poste de
   pilotage néon sur noir » que ce système nomme explicitement comme contre-référence.
   La charte le disait déjà, depuis le reset du 29.07 : **un seul bouton principal par écran**.
   Le fantôme redevient donc un fantôme - encre du texte, contour au filet - et l'accent reste
   au SEUL bouton principal, qui le porte trois fois (contour à l'encre d'action, filet de 2 px,
   graisse 600). Sur la capture de Rémy, l'œil va enfin à « Voir la fatigue », c'est-à-dire à la
   seule décision urgente - celle que sa ligne signale déjà par son fond.
   Les LIENS gardent l'accent : un lien dans une phrase n'a aucune autre affordance. */
.ui-btn{background:transparent;border:1px solid var(--line);border-radius:var(--radius);padding:6px 12px;font-size:12.75px;font-weight:500;cursor:pointer;color:var(--ink);
  display:inline-flex;align-items:center;gap:6px;line-height:1.35;letter-spacing:-.1px;transition:.12s;font-family:inherit}
.ui-btn:hover{background:var(--hover);border-color:var(--silver)}
/* L'ACTION PRINCIPALE EST UN TAMPON, PAS UNE PASTILLE (29.07, reset visuel). Plus d'aplat : elle
   se distingue par TROIS choses simultanées - un contour à l'ENCRE PLEINE (et non au filet), un
   soulignement interne de 2 px en ardoise, et la graisse à 600. Sur fond identique au fantôme,
   c'est la force du contour qui la désigne, et le filet d'accent qui dit « c'est celle-là ».
   Testé au gris : les trois signaux survivent, ce qu'un aplat coloré ne garantissait pas. */
/* ── LE CLIQUABLE SE DIT PAR UN BOUTON, PAS PAR UNE TYPOGRAPHIE (02.08, décision de Rémy) ───
   « Pas fan de cette typo en bleu comme ça… retire-la partout et fais des boutons de couleur
   si tu veux faire du cliquable. Reviens sur du simple, beau, épuré. »
   102 encres d'action sont redevenues l'encre du texte. Ce qui porte la couleur, désormais,
   c'est UN OBJET - l'action principale, en aplat plein - et non des mots teintés éparpillés.
   ⚠ CECI REVIENT SUR LE RESET DU 29.07 (« l'accent ne remplit plus, il souligne »), et c'est
   la même personne qui l'avait demandé puis l'annule, en connaissance de cause. Ce que le
   reset combattait n'était pas l'aplat en soi : c'étaient CINQ aplats simultanés à 375 px.
   La règle qui reste, et que l'invariant G tient désormais, est le vrai remède : **un seul
   contrôle par écran porte un aplat d'accent - l'action principale, et rien d'autre.**
   Encres mesurées sur l'aplat : 4,81:1 en clair (blanc sur #087e8b), 8,62:1 en sombre. */
.ui-btn.primary{background:var(--accent);color:var(--on-accent);border-color:transparent;font-weight:600;box-shadow:none}
.ui-btn.primary:hover{background:var(--accent-2);color:var(--on-accent-2);border-color:transparent;box-shadow:none}
.ui-btn.ghost{background:transparent;border:1px solid var(--line);color:var(--ink)}
.ui-btn.ghost:hover{background:var(--hover);border-color:var(--silver)}
.ui-btn.sm{padding:5px 11px;font-size:12.5px;border-radius:var(--radius)}
.ui-btn:disabled{opacity:.5;cursor:default;transform:none}
/* DESTRUCTIF (charte UX, principe B « sûr par défaut ») : le ROUGE est RÉSERVÉ à ce qui supprime ou
   remplace des données (Supprimer, De zéro). Il ne s'applique JAMAIS à une action courante - ni aux
   boutons « ↩ Annuler », qui sont des SÉCURITÉS (retour en arrière), pas des dangers. Modificateur
   unique, valable sur .ui-btn / .subtab / .go → un seul style destructif dans toute la console. */
.ui-btn.danger,.subtab.danger,.go.danger{color:var(--red-ink);border:1px solid var(--red);background:transparent}
.ui-btn.danger:hover,.subtab.danger:hover,.go.danger:hover{background:var(--red-bg);border-color:var(--red-ink)}

/* ---------- L'ÉTAPE D'UNE CHAÎNE (29.07) ----------
   Pour un écran qui déroule un PROCESSUS - « ① Désidérata », puis « ② Générer & publier ». Deux
   défauts se répondaient sur la vue « Générer » : l'étape ① vivait dans une carte encadrée quand
   l'étape ② n'était qu'une ligne nue, si bien que deux moments de MÊME rang ne se ressemblaient
   pas ; et la carte ajoutait un troisième contour sous deux rangs de navigation déjà encadrés.
   Ici : aucune boîte. Un numéro atténué, un titre en serif, un filet plein sur toute la largeur -
   le filet SÉPARE et le blanc GROUPE, ce qu'un cadre fait moins bien parce qu'il fait les deux à
   la fois. Plus d'espace au-dessus du titre qu'en dessous (34 / 12), comme partout.
   ⚠ Le numéro reste « ① » / « ② » et NON « 01 » / « 02 » : plusieurs messages de l'application
   renvoient à « ② Générer » en toutes lettres (toasts, infobulles). Renuméroter l'écran aurait
   cassé la référence sans que rien ne le signale. */
.etape{display:flex;align-items:baseline;gap:11px;flex-wrap:wrap;margin:34px 0 12px;padding-bottom:9px;
  border-bottom:1px solid var(--line)}
.etape:first-child{margin-top:4px}
.etape .n{font-size:15px;font-weight:400;color:var(--ink-soft);flex:none;line-height:1}
.etape h3{font-family:var(--display);font-size:19px;font-weight:600;letter-spacing:-.01em;margin:0;line-height:1.1}
.etape .sep{flex:1;min-width:8px}
/* Les actions posées sur la ligne d'étape se recentrent sur elle : alignées sur la LIGNE DE BASE
   du titre, un bouton paraît tomber. */
.etape .go,.etape .ui-btn,.etape .act-link,.etape a{align-self:center;margin-top:0;margin-bottom:0}
/* Le corps d'une étape s'aligne sur son titre, sans se refermer dans un cadre. */
.etape-bd{margin:0 0 6px}

/* ---------- LE DÉCLENCHEUR DISCRET (29.07, reset visuel) ----------
   Pour une action secondaire NOMMÉE qui doit rester trouvable sans peser : « Je ne peux plus → ».
   Le bouton fantôme ne convenait pas - sa boîte à filet, posée seule sous un titre, pèse autant
   qu'un bouton principal tout en étant illisible de nuit : encre atténuée sur filet atténué, les
   deux à faible contraste sur la surface. Mesuré sur le portail en thème sombre avant d'y toucher.
   Ici : pas de boîte du tout. L'encre PLEINE (donc le contraste du texte porteur, pas celui d'un
   libellé), un filet d'un pixel sous le texte seul, un chevron fin. Au survol le filet passe à
   l'ardoise et double d'épaisseur - le filet change, jamais la position ni la taille.
   Sa cible tactile passe par une zone de frappe, pas par la police (règle du doigt). */
.act-link{display:inline-flex;align-items:center;gap:7px;background:none;border:none;padding:2px 0;
  font-family:var(--font);font-size:13px;font-weight:600;color:var(--ink);letter-spacing:-.1px;
  line-height:1.6;cursor:pointer;position:relative;
  box-shadow:inset 0 -1px 0 var(--silver);transition:box-shadow .12s cubic-bezier(.22,1,.36,1),color .12s cubic-bezier(.22,1,.36,1)}
.act-link:hover{color:var(--ink);box-shadow:inset 0 -2px 0 var(--accent)}
.act-link:focus-visible{outline:none;box-shadow:inset 0 -2px 0 var(--accent),var(--anneau)}

/* ⚠ UN `role=button` DOIT SE VOIR QUAND ON L'ATTEINT (01.08). Les cartes de groupe et les cartes
   de radar d'Équité viennent de recevoir rôle, tabindex et clavier (`gesteClavier`) : sans anneau,
   on les atteint au clavier sans savoir où l'on est - un focus invisible n'est pas de la
   navigation, c'est de la devinette. Même anneau que les champs et les boutons, pour ne pas
   inventer une troisième signalétique de focus. `:focus-visible` : rien ne s'affiche à la souris.
   ⚠ `outline`, pas `box-shadow` : ces cartes portent DÉJÀ un `box-shadow` (le liseré du panneau),
   qu'une ombre de focus écraserait - et l'anneau doit survivre au mode contraste élevé, qui
   supprime les ombres mais garde les contours. */
[role="button"]:focus-visible{outline:2px solid var(--accent);outline-offset:2px;
  box-shadow:var(--anneau)}
.act-link .chev{width:14px;height:14px;flex:none;stroke-width:1.5;fill:none;stroke:currentColor}
/* La zone de frappe vaut pour TOUS les pointeurs : viser un filet d'un pixel à la souris n'est pas
   agréable non plus, et elle ne déforme rien puisqu'elle est invisible.
   ⚠ ELLE NE S'ÉTENDAIT QUE VERTICALEMENT (31.07). `left:0;right:0` lui donnait la largeur du
   texte : un déclencheur court restait une cible courte - « Voir → » mesuré à 42,4 × 44 px.
   La hauteur était traitée, la largeur pas : le même défaut, à la même date, que la règle
   tactile générale. Elle atteint désormais 44 px dans les DEUX sens, par l'idiome que le
   système emploie déjà pour `.frm-choix` - le glyphe garde sa taille, la cible grandit. */
.act-link::after{content:"";position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  width:100%;min-width:44px;height:44px}

/* L'EXPOSANT EST DU TEXTE D'INTERFACE (31.07). `sup` et `sub` prennent `font-size:smaller` du
   navigateur - mesuré à 10,4 px sur « 1er » dans les règles RH, donc sous le plancher, sans
   qu'aucune déclaration du projet ne le dise. C'est le pendant de l'invariant A étendu aux rem :
   une taille peut être fautive sans être écrite. On pose un plancher, la proportion est gardée
   partout où le parent est assez grand pour la permettre. */
sup,sub{font-size:max(11px,.75em)}

/* ---------- L'HORAIRE EST UNE MESURE (29.07) ----------
   `07:30→07:30` était composé dans la face du display, collé au libellé du poste dans le même
   bloc - donc lu comme la SUITE du titre, alors que c'est une grandeur. En chasse fixe il
   s'aligne d'une ligne à l'autre et la colonne des heures se lit verticalement, ce qu'aucune
   proportionnelle ne permet. C'est l'emploi légitime de la monospace : une donnée, pas un costume. */
.hor{font-family:var(--mono);font-size:13px;font-weight:400;letter-spacing:.02em;color:var(--ink-soft);
  font-variant-numeric:tabular-nums}

/* ---------- cartes ---------- */
.panel{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);box-shadow:none}
.panel-hd{padding:14px 18px;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:10px}
.panel-hd h3{margin:0;font-size:14.5px;font-weight:600}
.panel-hd .more{margin-left:auto;color:var(--ink);font-size:12.5px;font-weight:550;cursor:pointer;text-decoration:none}
.panel-bd{padding:16px 18px}
.row{display:grid;gap:16px}
/* UNE PISTE DE GRILLE NE RÉTRÉCIT PAS TOUTE SEULE. Par défaut un élément de grille porte
   `min-width:auto` : il refuse de passer sous la largeur de son contenu, et c'est la PISTE qui
   s'élargit - donc la page entière. Mesuré le 28.07 : dans « Réglages de génération », un
   calendrier poussait un `.panel` à 854 px dans une fenêtre de 760, et toute la console
   glissait latéralement. La rupture à 880 px fonctionnait pourtant : elle passait bien en une
   colonne, mais une colonne trop large. C'est `min-width:0` qui manquait, pas un point d'arrêt. */
.row>*,.cols>*,.grid-kpi>*{min-width:0}
/* Le conteneur à défilement des tableaux larges. Il existait dans le portail depuis le 27.07 et
   PAS dans la feuille partagée : la console n'en avait donc aucun, et sa table « Comptes & accès »
   (7 colonnes, 816 px) poussait la page. La charte : un tableau large défile dans SA boîte, le
   corps de la page jamais. */
.tscroll{overflow-x:auto;-webkit-overflow-scrolling:touch;max-width:100%}
/* ══ UN NOM DE MÉDECIN DANS UN GRAPHIQUE EST DU TEXTE (06.08) ══════════════════════════════════
   Les `<text>` d'un SVG portent leur taille en UNITÉS DE VIEWBOX : le rendu vaut la taille
   déclarée MULTIPLIÉE par l'échelle. Les graphiques d'Équité étaient posés en `width:100%` sur
   des viewBox de 1000 et 760 unités : à 375 px l'échelle tombait à 0,317, et `font-size="11"`
   rendait **3,49 px**. Mesuré : « MIDEZ » occupait une boîte de 11 × 4 px - un frottis. C'était
   le même angle mort que l'invariant F, d'un cran plus loin : F lit la valeur DÉCLARÉE, or ici
   c'est la valeur RENDUE qui fautait. Le plancher de 11 px était tenu partout où il s'écrit en
   CSS, et nulle part où il s'écrit en attribut SVG.
   ⚠ Poser `font-size:11px` en CSS n'aurait rien réglé : dans un SVG, une longueur CSS se résout
   elle aussi dans le système de coordonnées utilisateur. La seule façon de garantir la taille
   RENDUE est d'empêcher la mise à l'échelle - d'où le plancher de largeur, et le défilement
   horizontal du conteneur, qui est déjà la grammaire de ce système pour les tableaux larges.
   Le champ de la page, lui, ne défile toujours pas : c'est le conteneur qui défile. */
.svg-plancher{overflow-x:auto;-webkit-overflow-scrolling:touch;max-width:100%}
.svg-plancher>svg{display:block}
.grid-kpi{display:grid;gap:16px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}
.kpi{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);padding:15px 16px;box-shadow:none}
.kpi .lab{color:var(--ink-soft);font-size:12px;font-weight:550;display:flex;align-items:center;gap:7px}
.kpi .lab .dot{width:9px;height:9px;border-radius:50%}
.kpi .v{font-family:var(--display);font-size:26px;font-weight:600;margin-top:7px;letter-spacing:-.8px}
.kpi .t{font-size:11px;margin-top:3px;font-weight:550}
.up{color:var(--green-ink)} .down{color:var(--red-ink)} .flat{color:var(--ink-soft)}

/* ---------- aide / note ---------- */
.note{font-size:12.5px;color:var(--ink-soft);background:var(--bg);border:1px solid var(--line);border-radius:var(--radius);padding:9px 13px;margin-bottom:16px;line-height:1.5}
.hint{color:var(--ink-soft);font-size:13px;line-height:1.55;margin-bottom:14px;max-width:760px}

/* ---------- tables ---------- */
.ui-table{width:100%;border-collapse:separate;border-spacing:0;font-size:13px}
.ui-table th{text-align:left;font-size:11.5px;text-transform:uppercase;letter-spacing:.4px;color:var(--ink-soft);font-weight:700;padding:10px 14px;border-bottom:1px solid var(--line)}
.ui-table td{padding:11px 14px;border-bottom:1px solid var(--line)}
.ui-table tr:last-child td{border-bottom:none}
/* ---------- LE COMPARATIF (29.07) : `.ui-table.compare` ----------
   Pour choisir ENTRE des options mesurées sur les mêmes critères. Les variantes du solveur étaient
   trois cartes côte à côte, chacune portant six mesures et quatre boutons : dix-huit chiffres et
   douze boutons pour une décision unique - « laquelle je publie ». Et comparer deux cartes oblige
   l'œil à sauter d'une colonne à l'autre en retenant le chiffre, ce qu'un tableau supprime : les
   valeurs d'un même critère s'alignent, et l'écart se VOIT au lieu de se calculer.
   Les nombres passent en chasse fixe et alignés à droite - c'est ce qui les rend comparables ;
   une proportionnelle décale les unités d'une ligne à l'autre. */
.ui-table.compare td.n,.ui-table.compare th.n{text-align:right;font-family:var(--mono);
  font-variant-numeric:tabular-nums;white-space:nowrap}
.ui-table.compare th.n{font-family:var(--mono);font-size:11px;letter-spacing:.05em}
.ui-table.compare td.n{font-weight:700;font-size:13px}
.ui-table.compare .nom{font-family:var(--display);font-size:15.5px;font-weight:600;letter-spacing:-.01em}
/* La ligne recommandée se marque par un FILET en tête de ligne - pas par un fond teinté, qui
   colorerait toute la rangée et rendrait ses chiffres moins lisibles que ceux des autres. */
.ui-table.compare tr.reco td{background:var(--bg)}
.ui-table.compare tr.reco td:first-child{box-shadow:inset 3px 0 0 var(--accent)}
.ui-table.compare td.act{white-space:nowrap;text-align:right}
.ui-table.compare .sous{font-family:var(--mono);font-size:11px;letter-spacing:.04em;color:var(--ink-soft);
  margin-top:3px;display:block;font-weight:400}
/* La colonne des noms se faisait écraser par les colonnes de chiffres - le navigateur répartit la
   largeur au prorata du contenu, et six nombres courts pèsent plus qu'un nom long. « FEASIBLE ·
   optimum non prouvé » retombait alors sur trois lignes et faisait enfler toute la rangée. Elle
   garde un plancher ; les chiffres n'ont besoin que de leur chasse.
   ⚠ MESURÉ, PAS SUPPOSÉ : à 236 px de plancher et 14 px de gouttière, le tableau dépassait la
   colonne à 820 px de fenêtre et « Publier » - l'action principale - partait derrière le
   défilement horizontal. Un tableau peut défiler ; le geste qu'il commande, non. Plancher ramené
   à 200 px et gouttières resserrées : sept colonnes tiennent maintenant sous 820 px. */
.ui-table.compare td:first-child,.ui-table.compare th:first-child{min-width:200px}
.ui-table.compare th,.ui-table.compare td{padding-left:10px;padding-right:10px}
.ui-table.compare th:first-child,.ui-table.compare td:first-child{padding-left:14px}
.ui-table.compare td.act .go{padding:5px 11px;font-size:12.5px}
.ui-table tr:hover td{background:var(--hover)}

/* ---------- chips / pills / badges ---------- */
.chip2{display:inline-flex;align-items:center;gap:6px;background:var(--bg);border:1px solid #e2e5f6;border-radius:20px;padding:3px 11px 3px 9px;font-size:12.5px;font-weight:550;margin:2px}
.pill{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:600;padding:3px 10px;border-radius:20px}
.pill.ok,.pill.prev,.pill.info{background:var(--bg);color:var(--ink-soft);border:1px solid var(--line)} .pill.todo{background:var(--red-bg);color:var(--red-ink)}
.badge2{display:inline-block;padding:5px 12px;border-radius:var(--radius);font-weight:600;font-size:13px}
.badge2.ok{background:var(--green-bg);color:var(--green-ink)} .badge2.ko{background:var(--red-bg);color:var(--red-ink)}
/* LA PASTILLE IOT - TROIS SILHOUETTES, PAS TROIS TEINTES (29.07). Mesuré : le rouge `#cf4046`
   (L 0,1737) et le vert `#1a874f` (L 0,1811) donnent **1,03:1 ENTRE EUX**. En niveaux de gris -
   le test que DESIGN.md prescrit lui-même - c'était rigoureusement le même point, sur le premier
   écran de la console. Un `title` n'y change rien : il n'est pas visible.
   Les trois restent RONDES (règle du niveau rond) et se distinguent par la FORME : disque plein ·
   anneau creux · disque cerclé. La couleur devient un renfort, plus le seul porteur.
   `outline` plutôt que `border` sur le rouge : il ne déplace rien dans la ligne. */
.dotmini{width:9px;height:9px;border-radius:50%;display:inline-block;box-sizing:border-box;flex:none}
.iot-v{background:var(--green)}
.iot-o{background:transparent;border:2.5px solid var(--orange)}
.iot-r{background:var(--red);outline:1.5px solid var(--red);outline-offset:1.5px}
.dotmini:not([class*="iot-"]){background:transparent;border:1.5px dashed var(--ink-soft)}

/* ---------- alertes ---------- */
.alert2{display:flex;gap:11px;align-items:flex-start;padding:11px 13px;border-radius:var(--radius-s);margin-bottom:9px;font-size:13px}
.alert2:last-child{margin-bottom:0}
.alert2 .ic{width:26px;height:26px;border-radius:var(--radius);display:grid;place-items:center;flex:none;font-size:13px;font-weight:700}
.alert2.red{background:var(--red-bg)} .alert2.red .ic{background:var(--red-bg);color:var(--red-ink)}
.alert2.org{background:var(--card);border:1px solid var(--line)} .alert2.org .ic{background:var(--bg);color:var(--ink-soft)}
.alert2.grn{background:var(--card);border:1px solid var(--line)} .alert2.grn .ic{background:var(--bg);color:var(--ink-soft)}
.alert2 .meta{color:var(--ink-soft);font-size:12px}
/* barre de progression (actions longues : génération…) */
.prog-wrap{padding:14px 2px}
.prog-track{height:10px;border-radius:var(--radius-s);background:var(--line);overflow:hidden}
/* La SEULE jauge mise à jour EN PLACE (animerBarre, toutes les 400 ms) : elle anime `transform`,
   pas `width` - un scaleX se compose sans relayout. `--p` porte la fraction 0→1. Les autres
   barres du produit sont reconstruites par innerHTML : une transition n'y jouerait jamais. */
.prog-fill{height:100%;width:100%;border-radius:var(--radius-s);background:var(--accent);transform:scaleX(var(--p,.03));transform-origin:left;transition:transform .35s ease;position:relative}
.prog-fill:not(.done)::after{content:"";position:absolute;inset:0;border-radius:var(--radius-s);background:linear-gradient(90deg,transparent,rgba(255,255,255,.45),transparent);background-size:40% 100%;background-repeat:no-repeat;animation:progShine 1.1s linear infinite}
.prog-fill.done{background:var(--green)}
.prog-lbl{margin-top:8px;font-size:13px;color:var(--ink-soft);font-weight:600}
@keyframes progShine{0%{background-position:-40% 0}100%{background-position:140% 0}}
/* Survol : ni deplacement ni ombre teintee (charte) - le filet dit le survol. */
.alert2.act{transition:border-color .12s,background-color .12s;border:1px solid transparent}
.alert2.act:hover{border-color:var(--accent);background:var(--hover)}
.alert2.act.red:hover{border-color:var(--red)} .alert2.act.org:hover{border-color:var(--orange)}
.alert2 .cta{font-size:11.5px;font-weight:700;opacity:.78;margin-top:2px}

/* ---------- inputs ---------- */
.ui-input,select.ui-input,input.ui-input{border:1px solid var(--line);border-radius:var(--radius);padding:7px 10px;font-size:13px;font-family:inherit;color:var(--ink);background:#fff}
.ui-input:focus{outline:none;border-color:var(--accent);box-shadow:var(--anneau)}
label.lab{font-size:12.5px;color:var(--ink-soft);font-weight:550}

/* ---------- barres de progression ---------- */
.bar{height:8px;border-radius:20px;background:#edeef8;overflow:hidden;display:inline-block;width:160px;vertical-align:middle}
.bar > i{display:block;height:100%;border-radius:20px;background:var(--accent)}

/* ---------- calendrier mensuel ---------- */
/* `1fr` VEUT DIRE `minmax(auto,1fr)`, et ce minimum `auto` est celui du CONTENU. Les pastilles
   de poste portent `white-space:nowrap` : un nom long élargissait donc la colonne, les sept
   colonnes faisaient 800 px, et toute la console glissait latéralement entre 620 et 880 px.
   `minmax(0,1fr)` autorise la piste à descendre sous son contenu - le nom s'abrège alors par
   les points de suspension déjà prévus sur `.shift`, au lieu de pousser la page. */
.calx{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:8px}
.calx .dow{font-size:11px;font-weight:400;color:var(--ink-soft);text-transform:uppercase;letter-spacing:.1em;text-align:center;padding-bottom:2px}
.calx .cell{background:var(--card);border:1px solid var(--line);border-radius:var(--radius-s);min-height:104px;padding:7px 8px;display:flex;flex-direction:column;gap:4px;transition:.12s;content-visibility:auto;contain-intrinsic-size:auto 104px}
.calx .cell:hover{border-color:var(--accent)}
/* JOUR EN TENSION (prevision_tension) : un liseré ambre DISCRET. Pas d'aplat - la couleur de fond
   est déjà prise par le week-end, et deux aplats superposés ne se lisent plus. */
.calx .cell.tendu{border-color:var(--orange-ink);box-shadow:inset 0 0 0 1px var(--orange)}
.calx .cell.we{background:var(--bg)}
.calx .cell.out{background:transparent;border:none}
.calx .cell .d{font-size:11px;font-weight:600;color:var(--ink-soft)}
.calx .shift{font-size:11px;font-weight:550;border-radius:var(--radius-s);padding:2px 7px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.calx .s-j{background:var(--sh-j);color:var(--sh-jt)} .calx .s-n{background:var(--sh-n);color:var(--sh-nt)} .calx .s-h{background:var(--sh-h);color:var(--sh-ht)} .calx .s-s{background:var(--sh-s);color:var(--sh-st)}
.calx .force{background:var(--bg);color:var(--ink-soft);border:1px dashed var(--line)}
/* ══ « À CONFIRMER » - LA FORME DIT L'ÉTAT, LA COULEUR DIT LE POSTE (29.07) ════════════════════
   L'onglet Cadres peignait le nom validé en `--green-bg` : un aplat de STATUT, à 7×13 px, trois
   fois le rembourrage d'une pastille de garde. Une garde de cadre est une garde - elle prend donc
   la teinte de SON poste, comme C1, JC ou H. Ce qui reste à dire, c'est « pas encore confirmé par
   le cadre » : un contour TIRÉ, dans l'encre du poste (`currentColor`), donc sur la paire aplat/
   encre déjà mesurée - aucune couleur nouvelle, aucune paire nouvelle. Doublé par un « ? » dans le
   libellé : le test en niveaux de gris passe, la couleur ne porte jamais l'état seule.
   Le contour transparent sur la pastille de base garde les deux états EXACTEMENT à la même taille
   (`box-sizing:border-box` : le contour ne pousse rien). ══ */
.calx .shift{border:1px solid transparent}
.calx .shift.tbc{border-style:dashed;border-color:currentColor}
/* ══ FIGURE ET FOND, SANS DEGRADER LE FOND (29.07 - portail médecin) ═══════════════════════════
   « Mes gardes en évidence, celles des collègues en retrait » se faisait par `opacity:.62` sur la
   pastille du collègue. Mesuré après composition : 3,11 / 3,21 / 2,70 - tout sous le plancher de
   4,5, et sur un mois sans garde personnelle c'était TOUT l'écran. On ne construit pas une figure
   en abîmant le fond : le fond descend d'un cran de GRAISSE (550 → 450, il reste pleinement
   lisible), la figure monte d'un cran ET prend un liseré. La forme survit au gris. */
.calx .shift.collegue{font-weight:450}
.calx .shift.moi{font-weight:700;box-shadow:inset 0 0 0 1px currentColor;white-space:normal;line-height:1.3}
/* MA GARDE NE SE TRONQUE PAS. Mesuré : « Hélico 07:30→07:30 » demandait 119 px dans une cellule de
   107 (87 px utiles) et sortait en « Hélico 07:3… » - l'horaire, c'est-à-dire la seule chose que le
   médecin vient chercher, coupé. Les noms de collègues, eux, tenaient. Elle passe donc à la ligne
   plutôt qu'à l'ellipse : c'est l'élément prioritaire de la case, il peut coûter une ligne de haut.
   Les collègues gardent l'ellipse (15 sur 134 en usaient) - abréger un nom déjà secondaire est le
   bon compromis, tronquer l'horaire de sa propre garde ne l'est pas. */
/* Le champ de fichier cesse d'être un widget de formulaire brut : son bouton emprunte la grammaire
   du bouton fantôme (contour filet, fond transparent), le nom du fichier reste en encre atténuée. */
.ui-file{font-size:12.5px;color:var(--ink-soft);max-width:100%}
.ui-file::file-selector-button{font-family:inherit;font-size:12.5px;font-weight:600;color:var(--ink);
  background:transparent;border:1px solid var(--line);border-radius:var(--radius-s);padding:7px 13px;
  margin-right:11px;cursor:pointer;transition:.12s cubic-bezier(.22,1,.36,1)}
.ui-file::file-selector-button:hover{background:var(--hover);border-color:var(--ink-soft)}
/* L'ANCIENNE VALEUR d'une garde modifiée, sous sa pastille. Même traitement que dans la grille de
   semaine - chasse fixe, barrée - et au plancher de 11 px : c'est une information, pas une note. */
.calx .was{font-family:var(--mono);font-size:11px;color:var(--ink-soft);text-decoration:line-through;margin:-3px 0 0 7px}

/* ---------- en-tête de carte : icône ---------- */
.panel-hd .hic{width:30px;height:30px;border-radius:var(--radius);background:var(--bg);color:var(--ink-soft);display:grid;place-items:center;flex:none}
.panel-hd .hic svg{width:17px;height:17px}
/* MOBILE : l'en-tête « Cadre & équipe du jour » ne tient pas sur une ligne (titre + hier/date) → on
   WRAPPE : le titre reste en haut, le bloc de navigation passe dessous, pleine largeur. Fini le titre tassé. */
@media(max-width:620px){
 .panel-hd{flex-wrap:wrap;gap:8px 10px}
 .panel-hd h3{flex:1 1 auto;min-width:0}
 .panel-hd > div[style*="margin-left:auto"]{margin-left:0 !important;width:100%;justify-content:space-between}
 #pjLabel,#eqjLabel{min-width:0 !important;flex:1 1 auto}
}

/* ---------- actions rapides ---------- */
.qa{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.qa a{display:flex;align-items:center;gap:11px;padding:12px;border:1px solid var(--line);border-radius:var(--radius);
  text-decoration:none;color:var(--ink);font-weight:550;font-size:13px;background:var(--card);transition:.15s;cursor:pointer}
.qa a:hover{border-color:var(--accent);background:var(--bg)}
.qa a .ic{width:32px;height:32px;border-radius:var(--radius);background:var(--bg);color:var(--ink);display:grid;place-items:center;flex:none}
.qa a .ic svg{width:17px;height:17px}

/* ---------- équipe du jour ---------- */
.team-row{display:flex;align-items:center;gap:10px;padding:9px 0;border-bottom:1px dashed var(--line)}
.team-row:last-child{border:none}
.team-row .role{width:66px;font-size:11.5px;font-weight:700;color:var(--ink-soft);text-transform:uppercase;letter-spacing:.4px;flex:none}
.tag{font-size:11px;font-weight:700;padding:2px 8px;border-radius:var(--radius-s);white-space:nowrap}
.tag.j{background:var(--sh-j);color:var(--sh-jt)} .tag.n{background:var(--sh-n);color:var(--sh-nt)} .tag.h{background:var(--sh-h);color:var(--sh-ht)} .tag.s{background:var(--sh-s);color:var(--sh-st)} .tag.x{background:var(--bg);color:var(--ink-soft)}

/* Le bloc `.toast` qui vivait ici est parti le 09.08 : aucun balisage n'appliquait la classe
   (le toast réel, console_app.js `toast()`, se construit en styles inline sur des jetons). */

/* ===================================================================== */
/* PONT HÉRITAGE - re-style des anciennes classes des vues vers le thème  */
/* (chargé APRÈS l'ancien <style> de la console → ces règles l'emportent) */
main{background:transparent;margin:0;padding:0;border-radius:0;min-height:auto}
h2{font-family:var(--display);color:var(--ink);font-weight:600;letter-spacing:-.3px;margin:.1em 0 .55em;font-size:19px}
h3{font-family:var(--display);color:var(--ink);font-weight:600;margin:.6em 0 .35em;font-size:14.5px}
/* ⚠ `.aide` EST MORT (01.08). Il a existé sous TROIS destins simultanés, et aucun n'était celui
   qu'on croyait : `display:none` global (« HUB EPURE2 »), puis `.aide.clamp` - plus spécifique,
   donc gagnant - qui repliait à UNE ligne les blocs de plus de 160 signes SANS lien. Résultat
   mesuré au navigateur sur les 30 onglets : 24 préambules TRONQUÉS AU MILIEU D'UNE PHRASE
   (« …fatigue de l'équipe du jour,… »), 12 invisibles, et 3 invisibles ALORS QU'ILS PORTAIENT DES
   COMMANDES - le « Lien partagé (propriétaire) » de Tournus, champ et boutons compris, n'existait
   plus à l'écran. Le repli avait pourtant une exception écrite pour ça (« un bloc portant un
   lien/bouton ne se replie JAMAIS ») : elle marchait, et elle livrait le bloc au `display:none`
   posé un an plus tôt. DEUX DISPOSITIFS QUI S'ANNULENT, chacun juste pris seul.
   Et le repli à une ligne contredisait frontalement « la règle de la phrase entière » de
   `DESIGN.md` - c'est la charte elle-même qui interdit de couper un texte d'interface en son
   milieu. Ce qui le remplace est ci-dessous : LE MARQUEUR. */
label{font-weight:550;font-size:13px;color:var(--ink-soft);margin-right:8px}
/* ⚠ `input[type=text]` NE COUVRE PAS `<input>` SANS ATTRIBUT (29.07). Un `<input placeholder="…">`
   se comporte comme un champ texte, mais l'attribut `type` est ABSENT du balisage - donc aucun des
   sélecteurs d'attribut ci-dessous ne l'atteint, et le champ retombe sur le widget natif du
   navigateur : `border:2px inset`, rayon nul, Arial. Deux champs de la vue « Générer » vivaient
   ainsi depuis toujours au milieu d'une interface entièrement dessinée. Corrigé à la CLASSE,
   pas à l'occurrence : `input:not([type])` entre dans la liste, et tout champ écrit demain sans
   `type` sera juste par défaut.
   `background` passe de `#fff` en dur au jeton de surface, qui suit le thème. */
select,input:not([type]),input[type=date],input[type=text],input[type=number],input[type=time],input[type=month],input[type=email]{
  border:1px solid var(--line);border-radius:var(--radius-s);padding:7px 10px;font-size:13px;font-family:inherit;color:var(--ink);background:var(--card)}
/* CE QUI SE MESURE SE COMPOSE EN CHASSE FIXE (29.07). Une date, une heure, un nombre : la chasse
   fixe aligne les chiffres d'une ligne à l'autre et sépare la SAISIE D'UNE GRANDEUR de la saisie
   d'un mot. C'est la même règle que les horaires de garde ; ici elle s'applique au formulaire. */
input[type=date],input[type=time],input[type=month],input[type=number]{
  font-family:var(--mono);font-size:12.5px;letter-spacing:.02em;font-variant-numeric:tabular-nums}
select:focus,input:focus{outline:none;border-color:var(--accent);box-shadow:var(--anneau)}
/* SYSTÈME DE BOUTONS UNIFIÉ - une seule famille, hiérarchie par le REMPLISSAGE (pas la couleur).
   .go = action principale (accent plein) · .vert = générer/confirmer · .gris/.ghost = secondaire (contour
   neutre) · .orange = prudence (teinte douce) · .rouge/.danger = destructif (teinte douce). */
/* Boutons AFFINÉS (fin/classe/UX) : compacts, police 500, coins nets 8px, secondaires aériens (filet).
   Un seul plein par vue = .primary/.vert (accent). .gris/.ghost/.orange = secondaire ; .rouge = destructif. */
button.go{background:transparent;color:var(--ink);border:1px solid var(--line);
  padding:6px 12px;border-radius:var(--radius);font-size:12.75px;font-weight:500;cursor:pointer;margin:5px 6px 0 0;
  display:inline-flex;align-items:center;gap:6px;vertical-align:middle;line-height:1.35;letter-spacing:-.1px;transition:.12s}
button.go:hover{background:var(--hover);border-color:var(--silver)}
/* Le tampon, pour l'autre famille de boutons - même grammaire que `.ui-btn.primary`, il n'y en a
   qu'une (29.07). `.vert` reste groupé avec `.primary` : c'est le MÊME rôle (l'action principale),
   sa nuance verte se pose ailleurs, quand l'action diffuse vraiment aux médecins. */
button.go.primary,button.go.vert{background:var(--accent);color:var(--on-accent);border-color:transparent;font-weight:600;box-shadow:none}
button.go.primary:hover,button.go.vert:hover{background:var(--accent-2);color:var(--on-accent-2);border-color:transparent;box-shadow:none}
button.go.gris,button.go.ghost,button.go.orange{background:transparent;color:var(--ink);border-color:var(--line)}
button.go.gris:hover,button.go.ghost:hover,button.go.orange:hover{background:var(--hover);border-color:var(--silver)}
button.go.rouge,button.go.danger,button.go.rouge2{background:transparent;color:var(--red-ink);border-color:var(--line)}
button.go.rouge:hover,button.go.danger:hover,button.go.rouge2:hover{background:var(--hover);border-color:var(--red-ink)}
button.go:disabled{background:transparent;color:var(--silver);cursor:default;transform:none;opacity:.65;border-color:var(--line)}
button.go.sm{padding:4px 10px;font-size:12px;border-radius:var(--radius-s)}
/* icônes de contenu (remplacent les emojis, injectées par iconify) */
.btnico{width:14px;height:14px;flex:none;stroke-width:1.5;opacity:.9}
h2 .btnico{width:19px;height:19px;vertical-align:-3px;margin-right:6px;display:inline-block}
h3 .btnico{width:16px;height:16px;vertical-align:-2px;margin-right:5px;display:inline-block}
.seg button .btnico{vertical-align:-2px;margin-right:4px;display:inline-block}
/* LE TROISIÈME NIVEAU SE DIT TROISIÈME (29.07). « Le planning / Cadres / Archives » vit SOUS les
   cinq espaces et sous les sept sous-onglets - mais il était dessiné à la main, en styles posés en
   ligne, avec la même graisse et le même aplat d'accent que le deuxième niveau : trois rangs de
   navigation qui se ressemblaient, donc trois rangs qu'on ne hiérarchisait pas.
   Il reprend le composant partagé `.seg` (fond feuille, actif en SURFACE) là où le deuxième niveau
   utilise `.seg.sub` (fond carte, actif en aplat d'accent). Un cran plus discret, et un seul
   auteur du composant. */
.seg.niv3{margin:2px 0 14px;border-radius:var(--radius);padding:2px}
.seg.niv3 button{font-size:12.5px;font-weight:600;padding:6px 13px}
/* ── UN CHOIX DANS L'ÉCRAN N'EST PAS UNE NAVIGATION (08.08) ─────────────────────────────────
   QUATRE barres de l'application portaient `.seg.sub` - LA classe de la barre d'espace - pour
   offrir un choix de CONTENU : « Destinataires · E-mail », « Invitation · Relance J-15 · J-7 ·
   J-2 », « Vue 360° · Fiche SSOT · Bilan », « Rapporté à l'exposition · Chiffres bruts ».
   MESURÉ sur l'écran Désidérata, du haut vers le bas : espaces, puis `.seg.sub` (13,5 px /
   600), puis `.seg.niv3` (12,5 px), puis DEUX `.seg.sub` (13,5 px / 600) - encre identique,
   fond identique, graisse identique à la barre d'espace. La hiérarchie descendait d'un cran
   puis REMONTAIT de deux : les rangs 4 et 5 criaient plus fort que le rang 3 dont ils
   dépendaient. C'est ce que Rémy nomme « ces onglets sont entrecroisés ».
   ⚠ CE N'EST PAS UNE QUESTION DE TAILLE MAIS DE NATURE : ces barres ne mènent nulle part, elles
     changent ce qu'on regarde SANS quitter l'écran. Elles prennent donc le costume que ce
     produit emploie déjà pour la méta (chasse fixe, 11,5 px, interlettrage) - le même que
     `.frm-choisir summary` et `.frm-journal summary`. On ne fabrique pas une cinquième
     grammaire : on reprend la sienne.
   L'actif reste SOULIGNÉ, jamais rempli : c'est l'idiome d'état de la charte, et check_charte
   refuse un aplat d'accent (leçon `.pal-b`, 07.08). La cible tactile est inchangée - `.opt` est
   un modificateur de `.seg`, donc `.seg button` continue de lui donner ses 44 px au doigt. */
.seg.opt{border-bottom:1px solid var(--line);gap:0;margin:2px 0 12px}
.seg.opt button{font-family:var(--mono);font-size:11.5px;font-weight:600;letter-spacing:.03em;
  padding:7px 0;margin-right:20px;color:var(--nav-ink)}
.seg.opt button:hover{color:var(--ink)}
.seg.opt button.on{background:transparent;color:var(--ink);box-shadow:inset 0 -2px 0 var(--accent)}
/* L'ÉTIQUETTE EST UNE LÉGENDE, PAS UNE PHRASE (29.07). « MODE TEST → remy.midez@hug.ch » se
   composait dans la face du corps : elle avait l'aspect d'un fragment de texte posé dans une boîte,
   c'est-à-dire d'un widget par défaut. En chasse fixe et sur un rayon de 2 px, elle redevient ce
   qu'elle est - une mention technique, du même ordre que les légendes de colonne.
   ⚠ PAS de `text-transform:uppercase` ici, contrairement au pas d'étiquette de `DESIGN.md` : ces
   badges portent des ADRESSES e-mail. Crier une adresse la rend plus difficile à relire, et donne
   à croire à une casse qui n'est pas celle de la fiche. La chasse fixe suffit à la marquer. */
.badge{display:inline-block;padding:4px 9px;border-radius:var(--radius-xs);font-family:var(--mono);
  font-weight:400;letter-spacing:.03em;margin-top:12px;font-size:11.5px;background:var(--bg);
  color:var(--ink-soft);border:1px solid var(--line)}
/* NEUTRE + une seule ALERTE (rouge) : ok/warn/info = pastilles neutres (l'info est dans le texte) ;
   seul .ko/.bad porte du rouge. Fini le brun/navy/vert « sales » en dark. */
.badge.ok,.badge.warn,.badge.info,.badge.prev{background:var(--bg);color:var(--ink);border-color:var(--line)}
.badge.ko,.badge.bad{background:var(--red-bg);color:var(--red-ink);border-color:transparent}
.chips{margin:10px 0;display:flex;flex-wrap:wrap;gap:6px}
/* ÉTAT VIDE - « rien à montrer » N'EST PAS UNE PANNE (07.09, Rémy : « ça fait très bug »).
   Un mois pas encore généré s'affichait en `.badge ko` ROUGE (« ✗ Terminé - voir le détail ») avec
   un « détail technique » dépliant un message de machine : la forme de l'échec pour un état
   normal. Le bloc existait déjà, mais dans un `<style>` LOCAL à la Bourse - donc invisible partout
   ailleurs, et un second l'aurait suivi. Il monte ici, seul auteur du monde visuel (check_charte E).
   Neutre par construction : pas de rouge, pas d'aplat d'accent, la couleur reste aux boutons -
   c'est-à-dire à ce qui se clique (check_charte K). */
.bempty{border:1px solid var(--line);border-radius:var(--radius);background:var(--card);text-align:center;padding:30px 22px;margin-top:14px}
.bempty .t{font-family:var(--display);font-weight:700;font-size:16.5px;margin-bottom:5px}
.bempty .s{color:var(--ink-soft);font-size:12.5px;max-width:470px;margin:0 auto 16px;line-height:1.55}
.bempty .a{display:flex;gap:9px;justify-content:center;flex-wrap:wrap}
/* - RIEN QUI DÉBORDE : formulaires en lignes souples, champs/boutons contenus - */
.ligne{display:flex;flex-wrap:wrap;align-items:center;gap:8px 10px;margin:8px 0}
.ligne > label{margin:0}
select,input[type=text],input[type=date],input[type=number],input[type=time],input[type=month],input[type=email]{max-width:100%}
.panneau{min-width:0}
.panneau table{max-width:100%}
/* frises / grands tableaux : conteneur qui scrolle horizontalement, jamais de débordement de page */
.frzwrap{overflow-x:auto;border:1px solid var(--line);border-radius:var(--radius);padding:6px;background:var(--card);box-shadow:none;-webkit-overflow-scrolling:touch}
table.frz{border-collapse:separate;border-spacing:0}
table.frz th{color:var(--ink-soft);font-weight:600;background:var(--bg);border:1px solid var(--line);padding:5px 6px}
table.frz td{border:1px solid var(--line);padding:4px 6px}
table.frz td.nm,table.frz th.nm{background:var(--card);position:sticky;left:0;z-index:1}
table.frz th.nm{z-index:2}
table.frz td{height:24px;text-align:center}
/* ── FRISE DE ROTATION (onglet Rotation) - la colonne des noms colle, comme celle du Tournus ──
   La frise mesure 760 px au minimum : au téléphone on la fait glisser, et la colonne des noms
   partait avec elle - 53 bandes colorées sans savoir DE QUI. `table.frz td.nm` (juste au-dessus)
   règle déjà ce problème pour l'écran Tournus, qui montre le MÊME axe de 53 semaines. La frise ne
   le faisait pas : même geste, deux comportements, à un onglet de distance.
   ⚠ La frise est une GRILLE, pas un tableau - d'où une règle à elle et non un sélecteur partagé.
   La largeur vit dans un jeton pour que l'en-tête des mois et les lignes s'alignent forcément :
   deux `150px` écrits à la main auraient fini par diverger.
   ⚠⚠ ET ELLE S'ÉCRIT TOUJOURS `var(--frise-nom,150px)` DANS LE JS, JAMAIS `var(--frise-nom)` NU.
   Vu en direct, sur cet écran : tant que la feuille servie ne portait pas encore ce jeton (le
   serveur importe `ui_theme` AU DÉMARRAGE - une feuille modifiée après coup n'est pas servie), la
   déclaration entière `grid-template-columns:var(--frise-nom) repeat(53,1fr)` devenait INVALIDE.
   Pas « la première colonne fait 0 » : la propriété TOMBE, la grille repasse à une colonne, et les
   53 bandes s'empilent en pleine largeur. La frise disparaît, remplacée par un mur de lignes.
   Une variable manquante ne dégrade pas une grille, elle la SUPPRIME - et un cache de navigateur,
   un déploiement partiel ou un `▶ 3` qui pousse le JS avant la feuille suffisent à la faire
   manquer. La valeur de repli n'est pas une ceinture de sécurité décorative : c'est la seule
   chose qui garantit que l'écran reste un écran.
   ⚠ ELLE S'APPELLE `--frise-nom`, PAS `--rot-nom`. Écrite d'abord `--rot-nom`, elle a fait ROUGIR
   check_charte : l'invariant I balaie la famille « --rot- » en la supposant faite de COULEURS, et
   une largeur ne se résout pas en hexadécimal - il a refusé de conclure plutôt que de passer au
   vert. C'est exactement le comportement attendu (« un contrôle qui ne peut pas vérifier doit
   échouer »), et la leçon vaut d'être notée : un préfixe de jeton est un ESPACE DE NOMS, pas une
   décoration - s'y glisser change ce que les contrôles croient mesurer. */
.rotwrap{--frise-nom:150px}
.rotnom{font-size:12.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-right:8px;
  position:sticky;left:0;z-index:1;background:var(--card)}
@media (max-width:640px){ .rotwrap{--frise-nom:112px} .rotnom{font-size:12px} }

/* fiche médecin (RH) : champs regroupés en cartes titrées */
.rhgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(360px,1fr));gap:12px;margin-top:6px}
.rhcard{border:1px solid var(--line);border-radius:var(--radius);padding:12px 16px;background:var(--card);box-shadow:none}
.rhttl{font-weight:700;color:var(--ink);font-size:13px;margin-bottom:8px;border-bottom:1px solid var(--line);padding-bottom:5px}
.rhcard .ligne{margin:6px 0}
/* légende des frises / tournus : pastilles uniformes */
.frzleg{display:flex;gap:8px;flex-wrap:wrap;margin:4px 0 8px}
/* PAS de `color` ici : chaque pastille reçoit l'encre de SON fond (encreSur(), console_app.js).
   Un `color:#fff` global donnait 1,29:1 sur le vert pâle « présent / dispo » - illisible dans
   les DEUX thèmes. Une encre unique pour des fonds hétérogènes est toujours fausse quelque part. */
.frzleg span{font-size:11px;padding:2px 9px;border-radius:20px;line-height:1.5;white-space:nowrap;box-shadow:inset 0 0 0 1px rgba(9,10,22,.05)}
/* timeline (Gantt) des tournus - conservé pour compat */
.tl{display:grid;gap:2px;min-width:max-content;font-size:11px}
.tl-h{text-align:center;color:var(--ink-soft);font-size:11px;font-weight:600;line-height:1.25;padding:1px 0}
.tl-name{font-weight:600;color:var(--ink);padding:2px 8px;white-space:nowrap;display:flex;align-items:center;background:var(--card);position:sticky;left:0;z-index:1;font-size:11.5px}
.tl-cell{height:22px;border-radius:2px}
.tl-cell.empty{background:#f2f3fb}
table.hs{border-collapse:separate;border-spacing:0;font-size:13px;margin-top:6px;width:100%}
table.hs td{border-bottom:1px solid var(--line);padding:8px 14px 8px 0}
table.hs tr:last-child td{border-bottom:none}
.cards{display:flex;gap:12px;flex-wrap:wrap;margin:14px 0}
.card{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);padding:13px 16px;min-width:150px;box-shadow:none}
.card .v{font-size:26px;font-weight:600;color:var(--ink);font-family:var(--display);letter-spacing:-.4px}
.card .l{font-size:12.5px;color:var(--ink-soft)}
table.grille{border-collapse:separate;border-spacing:0;font-size:11.5px;margin-top:14px;width:100%}
table.grille th{background:var(--bg);color:var(--ink);border:1px solid var(--line);font-weight:600}
table.grille td{border:1px solid var(--line)}
tr.we td{background:var(--bg)} tr.ferie td{background:var(--orange-bg)}
/* Le résumé d'un dépliant est un CONTRÔLE : 24 px au minimum à tous les pointeurs (2.5.8),
   44 px au doigt (voir la règle tactile). Il mesurait 19,4 px, et `<details>` est le motif
   d'explication que DESIGN.md recommande - il y en a partout. */
details{margin-top:12px} details>summary{color:var(--ink);font-weight:550;cursor:pointer;min-height:24px}
pre{background:var(--side-2);color:var(--side-ink);padding:14px;border-radius:var(--radius);font-size:12px;white-space:pre-wrap;max-height:48vh;overflow:auto;margin-top:8px}
.file button{background:var(--green);border:none;color:var(--on-green);padding:9px 16px;border-radius:var(--radius);font-weight:550;cursor:pointer}

/* ---------- fluidité : apparition douce du contenu ---------- */
@keyframes uiFade{from{opacity:0;transform:translateY(3px)}to{opacity:1;transform:none}}
.panneau{animation:uiFade .16s ease both}
.panel,.kpi{animation:uiFade .18s ease both}
/* Sous mouvement réduit, le reflet défilant de la jauge s'arrête ; la barre, le libellé d'étape
   et le temps écoulé continuent de dire l'avancement - on retire le décor, jamais l'information.
   (Sélecteur à spécificité ÉGALE à la règle du reflet, sinon elle gagne et le reflet défile.) */
@media (prefers-reduced-motion: reduce){.panneau,.panel,.kpi{animation:none}
  .prog-fill:not(.done)::after{animation:none}}

/* ---------- responsive : sidebar = DRAWER coulissant (comme le portail médecin) ---------- */
.tb-burger{display:none;background:none;border:1px solid var(--line);color:var(--ink);font-size:19px;
  cursor:pointer;padding:4px 11px;border-radius:var(--radius);line-height:1}
.tb-burger:hover{background:var(--card)}
.sb-scrim{display:none;position:fixed;inset:0;background:rgba(10,12,22,.5);z-index:55}
@media(max-width:880px){
  .shell{grid-template-columns:1fr}
  .sb{position:fixed;left:0;top:0;height:100vh;width:270px;max-width:82vw;flex-direction:column;overflow:auto;
      transform:translateX(-100%);transition:transform .22s ease;z-index:60;box-shadow:0 0 44px rgba(9,10,22,.32)}
  .sb.open{transform:translateX(0)}
  .sb-nav{display:block} .sb-grp{display:block} .sb-who{display:flex}
  .sb-scrim.show{display:block}
  .tb-burger{display:inline-flex}
  .content{padding:16px}
  /* les mises en page 2 colonnes s'empilent (override des grid-template-columns inline) */
  .row,.cols{grid-template-columns:1fr !important}
  .tb{flex-wrap:wrap;gap:8px} .tb .spacer{flex-basis:100%;order:5}
  .qa{grid-template-columns:1fr}
  table.ui-table,table.hs{font-size:12px}
  .calx{gap:3px} .calx .cell{min-height:74px;padding:4px}
}

/* TÉLÉPHONE : un calendrier 7 colonnes ne tient JAMAIS sur un écran ~360-420px (il débordait à droite,
   colonnes rognées, noms coupés). On EMPILE en 1 colonne : chaque jour = une carte pleine largeur,
   alignée au bord de l'écran, nom complet lisible. Le n° de jour (.d) donne le contexte ; les cases
   vides de début/fin de mois (.out) et les entêtes Lun..Dim (.dow) disparaissent (inutiles empilés). */
@media(max-width:620px){
  .calx{grid-template-columns:1fr;gap:6px}
  .calx .dow{display:none}
  .calx .cell.out{display:none}
  .calx .cell{min-height:0;padding:8px 10px}
  .calx .shift{white-space:normal}
}

/* ========================================================================== */
/* MOBILE - COUCHE FINALE « toute la console au téléphone » (≤ 640 px).        */
/*                                                                            */
/* Le SOCLE existait déjà : la sidebar devient un tiroir (≤880), le calendrier */
/* s'empile (≤620), les matrices larges (rotation, fatigue) et les tables     */
/* gelées (Tournus) vivent dans des conteneurs `overflow-x:auto` - elles      */
/* DÉFILENT, il ne faut surtout pas les casser. Ce qui restait inconfortable, */
/* c'est le CONTENU des vues : des grilles 2-colonnes écrites en style INLINE  */
/* (donc invisibles pour `.row{…1fr}`), quelques tables nues, et la densité.   */
/*                                                                            */
/* RÈGLE DE SÉCURITÉ : tout est enfermé dans cette media-query → AUCUN effet   */
/* sur le desktop (le harnais rend en pleine largeur, rien ne bouge pour lui). */
/* On ne touche PAS aux grilles-matrices (`… repeat(…,1fr)`, `repeat(7,30px)`) */
/* : elles doivent défiler, pas s'empiler - les empiler détruirait la frise.   */
/* ========================================================================== */
@media(max-width:640px){
  /* - densité : on récupère chaque pixel de largeur -
     ⚠ `--gouttiere` EST la largeur de cette marge, et c'est la SEULE source (31.07). Les blocs
     qui sortent volontairement du rembourrage (`.cartouche`, `.feuille` - le « plein écran »
     du cartouche sous 620 px) la compensent par une marge négative. Ils écrivaient `-14px`
     quand ce rembourrage valait 12 : 2 px de trop de chaque côté, donc 4 px de page en trop -
     et « le champ ne défile jamais latéralement ». Deux règles disaient la même chose, elles
     ont divergé ; désormais elles LISENT la même valeur (règle 9, appliquée au CSS). */
  .content{padding:12px var(--gouttiere,12px) 40px}
  .panel-bd{padding:13px 13px}
  .panel-hd{padding:12px 13px}
  .tb{padding:11px 14px}
  .tb h1{font-size:19px}
  h2{font-size:15px} h3{font-size:14px}

  /* - grilles 2-colonnes de CONTENU écrites en INLINE → empilées.
       Ciblage par sous-chaîne du style inline : on ne prend QUE les formes de
       contenu (deux colonnes proportionnelles ou maître-détail), jamais les
       matrices `150px repeat(…)` / `130px repeat(…)` / `repeat(7,30px)` qui,
       elles, défilent dans leur conteneur. `.row/.cols` (≤880) et `.eq2col/
       .ficwrap` (≤860/900) sont déjà couverts ; on les répète ici par sûreté. */
  [style*="grid-template-columns:1fr 1fr"],
  [style*="grid-template-columns:1.5fr 1fr"],
  [style*="grid-template-columns:1.25fr 1fr"],
  [style*="grid-template-columns:minmax(0,300px)"],
  [style*="grid-template-columns:minmax(0,1.5fr)"],
  .eq2col,.ficwrap,.row,.cols{grid-template-columns:1fr !important}

  /* - une table NUE (sans conteneur défilant) élargissait la page entière ;
       on la rend défilante DANS son bloc. `.frz` est EXCLUE : elle a déjà son
       `.frzwrap` + colonne collante, et `display:block` casserait le sticky. */
  .content table:not(.frz){display:block;max-width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}

  /* - un SVG/chart à largeur mini fixe ne doit jamais dépasser l'écran :
       s'il n'est pas déjà dans un conteneur défilant, il se borne. */
  .content svg{max-width:100%;height:auto}

  /* - sous-onglets : plutôt que de retomber sur 3 lignes, une bande qui défile
       horizontalement (le pouce balaie). `.seg` reste flex, on désactive le
       wrap et on autorise le débordement horizontal. */
  .seg,.seg.sub{display:flex;flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch;max-width:100%;
    scrollbar-width:none}
  /* LA BARRE DE DÉFILEMENT SE CACHE (29.07). Sur la console à 375 px, la rangée d'onglets de la
     charpente affichait une glissière claire, épaisse, non stylée, juste sous les libellés - elle
     ressemblait à une jauge de progression et n'en était pas une. `.sb-nav` et `.seg.sub` du grand
     écran la masquaient déjà depuis longtemps ; la bande MOBILE, elle, ne l'avait jamais fait,
     c'est-à-dire précisément là où le pouce balaie et où la glissière est inutile. */
  .seg::-webkit-scrollbar,.seg.sub::-webkit-scrollbar{display:none}
  .seg button,.seg.sub button{flex:0 0 auto;padding:7px 12px;font-size:12.5px}

  /* - cibles tactiles un peu plus généreuses - */
  .ui-btn,button.go{padding:8px 13px}
  .sb-link{padding:11px 12px}          /* le menu du tiroir, plus facile au doigt */

  /* - KPI : deux par ligne (un empilement d'une colonne était interminable) - */
  .grid-kpi{grid-template-columns:1fr 1fr}
  .kpi{padding:13px 13px} .kpi .v{font-size:22px}

  /* - BANDE de KPI (écran « À traiter ») : UNE colonne. 27.07, constaté à 375 px.
       Elle était restée sur `minmax(150px,1fr)`, donc DEUX colonnes de ~180 px - trop
       étroites pour ses sous-titres, qui se coupaient EN PLEIN MILIEU D'UNE PHRASE :
       « médecins · 42 au / tournus », « septembre 2026 · 55 / manquants »,
       « 0 déclaration(s) / validée(s) ». C'est le défaut nommé en premier par Rémy.
       Ce n'est pas un réglage de densité : à pleine largeur, ces titres ne se coupent
       plus du tout. Et en pile, le séparateur doit passer à DROITE → EN BAS, sans quoi
       chaque tuile porte un trait vertical dans le vide.

     ⚠ `!important` OBLIGATOIRE ICI, et c'est un piège du fichier : `.kpi-strip` est déclarée
       PLUS BAS (≈ l.680) que cette couche mobile. À spécificité égale, la DERNIÈRE règle du
       fichier gagne - donc l'override mobile était écrasé et ne faisait RIEN, en silence.
       (`.grid-kpi` juste au-dessus fonctionne, elle, parce qu'elle est déclarée l.124, AVANT.)
       Toute future règle mobile visant une classe définie après ce bloc a le même besoin. */
  .kpi-strip{grid-template-columns:1fr !important}
  .kpi-strip .kpi{border-right:none !important;border-bottom:1px solid var(--line)}
  .kpi-strip .kpi:last-child{border-bottom:none}

  /* - actions rapides déjà en 1 colonne (≤880) ; on garde - */
  .qa{grid-template-columns:1fr}
}
/* très petits écrans : les KPI repassent en pile (2 colonnes deviennent illisibles). */
@media(max-width:400px){
  .grid-kpi{grid-template-columns:1fr}
}

/* ---------- LES PLANNINGS PUBLIÉS (04.08) : la version qui FAIT FOI, par mois ----------
   Ces règles vivaient dans le HTML de l'onglet « Archives », donc elles n'existaient QUE
   pendant que cet onglet était rendu. Le sous-onglet « Planning publié » réemploie le même
   bloc et sortait donc SANS STYLE - des cartes réduites à des lignes de texte. Une règle
   utilisée par deux écrans appartient à la feuille, pas à l'un des deux. */
.arch-derniers{border:1px solid var(--line);border-radius:var(--radius);background:var(--card);padding:15px 17px;margin:6px 0 8px}
.arch-derniers .qh{font-family:var(--display);font-weight:700;font-size:14px;margin:0 0 3px}
.arch-derniers .qsub{font-size:11.5px;color:var(--ink-soft);margin-bottom:12px}
.arch-derniers .qgrid{display:flex;flex-wrap:wrap;gap:9px}
.mchip{display:flex;flex-direction:column;gap:5px;min-width:134px;border:1px solid var(--line);border-radius:var(--radius);padding:10px 12px;background:var(--bg)}
.mchip .mo{font-family:var(--display);font-weight:700;font-size:13.5px}
.mchip .vv{font-size:11px;color:var(--ink-soft);font-family:ui-monospace,monospace;letter-spacing:.03em}
.mchip .lk{display:flex;gap:6px;flex-wrap:wrap;margin-top:1px}
.mchip .al{font-size:11px;color:var(--ink);border:1px solid var(--line);border-radius:var(--radius-s);padding:1px 7px;cursor:pointer}
.mchip .al:hover{border-color:var(--accent)}
.mchip .cur{align-self:flex-start;font-size:11px;font-weight:700;color:var(--green-ink);background:var(--green-bg);border-radius:4px;padding:1px 6px;letter-spacing:.04em}

/* ============ MODE SOMBRE (bouton 🌙, mémorisé) ============ */
[data-theme="dark"]{
  /* ══ LA CHARPENTE PASSE SOUS LE CHAMP (29.07) ═══════════════════════════════════════════════
     CE QUI ÉTAIT FAUX : `--side-1` restait figé à #1b1c21 dans les deux thèmes, au nom de « la
     charpente ne suit pas le thème ». Tenue au pied de la lettre, cette règle PRODUISAIT
     l'inversion : en sombre, la charpente (L* 10,4) devenait le plan le plus CLAIR de l'écran,
     au-dessus de la feuille (L* 4,0). « Champ clair pour le travail, cartouche sombre pour la
     charpente - jamais l'inverse » était donc violé par le token censé l'appliquer.
     L'INTENTION de la règle est que la charpente soit le plan qui ENFERME, donc le plus sombre -
     pas qu'elle soit un hexadécimal gelé. C'est l'intention qui est appliquée ici.

     ⚠ NE PAS MESURER DEUX PLANS SOMBRES AU RAPPORT DE CONTRASTE. Entre deux quasi-noirs il reste
     collé à 1 quel que soit l'écart réel (le +0,05 de la formule domine) : remonter la feuille de
     #0d0e11 à #1e2025 FAISAIT BAISSER le rapport de 1,13 à 1,04, alors que l'écart perçu
     augmentait. La bonne grandeur est l'écart de CLARTÉ CIELAB. Référence prise dans le thème
     clair lui-même : il sépare deux plans de ΔL* +3,5 (surface #fff sur feuille #F4F5F7).
     Ici : charpente → feuille ΔL* +4,2 · feuille → surface ΔL* +4,1. Même grammaire, en sombre.

     Bénéfice mesuré, et c'est ce qui a tranché entre les deux réparations possibles : enfoncer la
     charpente laisse les encres sur un fond PLUS SOMBRE, donc TOUTES gagnent - rouge 5,30 (contre
     4,55 si on avait remonté la feuille), encre de charpente 12,05, cartouche 19,68. Et la nuit
     reste la nuit : la feuille est à L* 7,2, pas 13,7. */
  /* 01.08 (soir) - versant nuit de la charpente space-indigo. Elle reste LE PLAN LE PLUS SOMBRE
     de l'écran, comme le veut la règle de l'ordre des plans : L* 5,9 < feuille 9,0 < surface 13,4.
     La teinte est celle du clair, la POSITION est celle qu'impose l'ordre - jamais un hexadécimal
     gelé, c'est en le gelant qu'on avait inversé l'ordre pendant des mois. */
  /* ⚠ CORRIGÉ DANS LA MÊME SESSION, SUR SIGNALEMENT DE RÉMY (« problème de couleur de fond »).
     La charpente était posée à L* 5,9 et la feuille à 9,0 : ΔL* 3,1, SOUS le plancher de 3,5 que
     ce système s'impose entre deux plans voisins. L'ORDRE était juste, le PAS ne l'était pas -
     et à l'écran le haut de la page faisait un aplat unique, barre du haut et sous-onglets
     confondus. Aucun contrôle ne pouvait le dire : la règle de l'ordre des plans était écrite
     dans DESIGN.md depuis le 29.07 et n'avait jamais eu de test. D'où l'invariant M.
     Nouveaux pas : charpente 4,3 → feuille 9,0 (ΔL* 4,7) → surface 13,4 (ΔL* 4,4). */
  --side-1:#000c17; --side-2:#09171f; --side-3:#00121a; --side-line:#273741;
  --side-jauge:#516674;   /* meme valeur dans les deux themes : le cartouche est sombre dans les deux */
  --side-att:#dead6f; --side-alarme:#ffa59f; --side-ok:#9ad9a8;   /* idem : voir la note du theme clair */
  /* La feuille prend le même grain d'ardoise qu'en clair (voir :root) - la nuit aussi, le champ
     est une matière et non un vide. L'ordre des plans est conservé et légèrement raffermi :
     charpente 3,03 → feuille 7,82 → surface 11,92, soit ΔL* +4,79 et +4,10. */
  --ink:#dfe6ec; --ink-soft:#9faab1; --line:#38393d; --bg:#161f25; --card:#1e282e; --silver:#828389;
  /* échelle de force, versant nuit - voir la note du thème clair. Mêmes cinq teintes (249° · 145°
     · 75° · 45° · 25°), sens de clarté inversé : sur fond noir c'est en s'ÉCLAIRANT qu'une couleur
     se détache. Bande L* 52 → 76, ΔL* consécutifs 6,0 · 5,9 · 5,9 · 6,0, contrastes 3,62:1 à
     7,84:1 sur les deux fonds. Là encore les valeurs tombent SUR la palette de nuit du produit :
     « moyenne » #c19356 contre son orange #d2a365, « extrême » #fea3a0 contre son rouge #ff9a95.
     La version rejetée (#88959b · #90aab5 · #93bfd2 · #fac18c · #ffd9d6) portait le même défaut
     qu'en clair, pris par l'autre bout : contrainte de tenir 4,5:1 comme encre, elle ne pouvait
     que PÂLIR, et chroma 10 à 25 ne fait pas cinq couleurs. En échantillon, le rouge du haut
     garde chroma 37 au lieu de 10. */
  --rang-tres-souple:#658092; --rang-souple:#6b966f; --rang-moyenne:#c19356;
  --rang-dur:#ec9577; --rang-extreme:#fea3a0;
  /* La légende absorbée, versant nuit : le celadon et le rosewood éclaircis pour leur fond noir.
     Mesurés sur la surface #212228 : acquis 6,90:1 · alarme 5,36:1. */
  /* ── LES FONDS DE SIGNAL, VERSANT NUIT - MESURÉS CONTRE LEUR CARTE (02.08) ────────────────
     Ils avaient été composés pour la feuille CLAIRE, où ils sont un souffle : chroma 4,4 à 8,5,
     à ΔL* −4,5/−6,9 régulier sous la carte. Reportés en sombre, ils portaient **chroma 15 à
     20,2** - deux à quatre fois plus - avec des écarts de clarté erratiques (+2,6 à +10,7).
     À l'écran : deux dalles olive dans la bande de mesures et une barre rouge en travers de la
     ligne urgente, qui pesaient plus que le bouton d'action censé la traiter.
     Un fond de signal n'est pas là pour être vu : il est là pour teinter. Il reprend donc en
     nuit la discrétion qu'il a le jour - ΔL* +5,0 régulier, chroma 8 - et la TEINTE fait seule
     le travail. Les encres remesurées dessus : 5,70 à 6,37:1 ; l'encre du texte, 11,5 au pire. */
  --green:#7fab83; --green-bg:#2f3c30; --orange:#d2a365; --orange-bg:#3c2e1d; --red:#ff9a95; --red-bg:#4b3331;
  --blue-bg:#2d3b44; --accent:#87a0b2; --accent-2:#aecade; --accent-ink:#8fafc6; --violet:#c0c1c4; --on-accent:#02111b;
  --on-accent-2:#02111b;   /* --accent-2 est CLAIR en sombre : l'encre-noire y mesure 11,2:1 */
  --ex:#afb0b6; --ex-ink:#c0c1c6; --ex-pale:#2f3b43;
  --on-green:#02111b; --on-red:#02111b;   /* 7,59:1 et 5,71:1 - cf. la note du thème clair */
  /* `--on-frise` et `--sur-pale` NE BASCULENT PAS : leurs fonds non plus. Les 13 teintes de la
     frise et les deux pâles sont figées dans les deux thèmes - une encre qui bascule sur un fond
     qui ne bascule pas, c'est exactement le bug qu'on répare ici. Seul `--on-ink-soft` suit le
     thème, parce que `--ink-soft` le suit. */
  --on-ink-soft:#02111b;
  /* Le second brin de la trame suit la même logique que son encre : il s'éloigne d'elle. Ici
     l'encre est NOIRE, donc le brin MONTE (l'inverse du thème clair). #abb4ba : 9,07:1 contre
     l'encre, ΔL* 6,8 sous le brin plein - la hachure se voit sans que le mot s'efface.
     `--trame-pale` ne bascule pas : son fond #94a3b8 ne bascule pas non plus. */
  --trame-ink-soft:#b3bbc1;   /* re-dérivé avec l'encre atténuée - 9,83:1, ΔL* 6,5 */
  --bleu:#8fafc6;
  /* ⚠ UNE ENCRE MESURÉE SUR UN PLAN, EMPLOYÉE SUR UN AUTRE (31.07).
     `--red-ink` reprenait `--red` (#e9686e), et c'était juste - SUR LA SURFACE (#1c1e23) : 5,30.
     Mais `.chip2` porte son propre fond en sombre (#2b2d34), plus clair, et le même rouge y tombe
     à **4,37**, sous le plancher. Mesuré au navigateur, en thème sombre, sur le mot « rouge » du
     niveau IOT - c'est-à-dire l'écran d'équipe lu de nuit, en garde.
     `--orange` et `--green` tiennent sur les DEUX plans (5,65 et 5,82) : eux ne bougent pas.
     Le rouge s'éclaircit juste assez : #ef7d82 → 6,29 sur la surface, 5,19 sur la puce.
     LA LEÇON : une encre n'est pas mesurée « en sombre », elle est mesurée SUR UN FOND. Quand un
     composant se donne son propre fond, il change la question. */
  --red-ink:#ffa59f; --green-ink:#8ab68d; --orange-ink:#dead6f; --violet-ink:#8fafc6;
  /* Sur la surface sombre, cinq des six couleurs VIVES d'axe font déjà de bonnes encres (4,87 à
     7,41:1) : on les reprend telles quelles, comme `--red-ink` reprend `--red`. Seule « nuits »
     tombait à 4,05 - elle est éclaircie jusqu'à 6,13. Mesuré sur #1c1e23, pas supposé. */
  /* Les six encres d'axe, versant nuit - mesurées sur la carte #212228 : 6,91 à 6,94:1.
     Et les six APLATS, qui basculent aussi depuis le 02.08 (voir la note du thème clair) :
     mêmes teintes, même ordre de clarté, échelle remontée pour tenir 3:1 sur le quasi-noir. */
  --eq-ax-helico:#cbccd0; --eq-ax-feries:#938d62; --eq-ax-heures:#57819b;
  --eq-ax-nuits:#809fb5; --eq-ax-we:#a7bfd0; --eq-ax-jours:#b3ac7d;
  --serie-neutre:#959699; --serie-ambre:#b3ac80;
  --eq-ink-heures:#7ca6c1; --eq-ink-nuits:#8fafc6; --eq-ink-we:#a7bfd0;
  --eq-ink-feries:#a8a276; --eq-ink-helico:#c0c1c4; --eq-ink-jours:#b9b282;
  /* Rampe de fatigue, versant nuit - voir la note complète du thème clair. L'ordre s'inverse
     (le calme est le plus sombre, le risque le plus clair) parce que la page a changé de côté :
     ce qui est chaud reste ce qui est LOIN de la page. Composée, pas inversée mécaniquement.
     L* 53,7 · 66,0 · 77,8 → écarts en gris de 30,8 et 31,0 (contre 0,3 avant).
     `--fat-ink` ne figure pas ici : elle est identique dans les deux thèmes, par construction. */
  --fat-calme:#638b67; --fat-vigil:#c19a69; --fat-risque:#ffaba6;
  /* Frise de rotation, en sombre - MÊME INVERSION QU'EN CLAIR (voir le commentaire du thème clair) :
     les deux « ailleurs » deviennent des fonds sourds, le SMUR reste le seul aplat vif et l'extrême
     de la rampe. Clartés L* : hors tournus 7,4 · urgences 26,9 · bloc 38,0 · SMUR 68,3
     (écarts 19,5 · 11,1 · 30,3 - très au-dessus du plancher de 3,5).
     ⚠ VACANCES (68,2) ET FORMATION (69,1) TOMBENT SUR SMUR (68,3) : ΔL* 0,09 et 0,81. C'est pour
     cela - et pour le clair, où Vacances (54,15) tombe sur Aux urgences (54,85) à ΔL* 0,70 - que
     ces deux ÉTATS D'ABSENCE sont désormais HACHURÉS et non teintés (voir `ROT_ABSENCE`,
     console_app.js). On ne les a PAS repeints : `--orange` et `--green` sont des jetons globaux,
     et une absence n'appartient pas à l'échelle des lieux de travail. C'est le CANAL qui change.
     ⚠⚠ ET `--rot-anesth` N'A PAS BOUGÉ - J'AI FAILLI LE REPEINDRE SUR UNE MESURE FAUSSE. J'avais
     calculé « anesthésie ↔ SMUR = ΔL* 2,9, sous le plancher » en comparant `--rot-anesth` SOMBRE
     au hexadécimal de `--accent` CLAIR (#5C6E99). Or le thème sombre redéfinit `--accent` en clair
     (L* 68,3) : l'écart réel est de 24,6, il n'y a jamais eu de collision. Mesurer un jeton d'un
     thème contre la valeur écrite d'un autre, c'est ne mesurer aucun des deux - règle 13, prise
     sur le fait. LIRE LA VALEUR RÉSOLUE PAR LE NAVIGATEUR, jamais la première déclaration trouvée
     dans le fichier (c'est déjà la leçon de l'indigo mort, PRODUCT.md). */
  --rot-smur:#4e9eca; --rot-anesth:#626367; --rot-urgences:#46474a;
  /* remontés avec la surface : ils la surplombent, ils ne doivent pas s'y noyer */
  --fat-hd:#2b363e; --fat-hd-ink:#d5dee5; --fat-cell-line:#313a41;
  --hover:rgba(255,255,255,.06);
  /* LA RAMPE DES POSTES, EN SOMBRE. Même principe qu'en clair et même ordre de poids
     (jour → soir → nuit → hélico), mais la page est noire : on s'en écarte en MONTANT.
     Pas de ΔL* 4,5. Les aplats restent tous sombres, donc les quatre encres claires gardent leur
     métier - mesurées sur leur nouvel aplat : 8,40 · 7,45 · 5,66 · 4,61:1.
     ⚠ On mesure ici en ΔL*, jamais en rapport de contraste : entre deux quasi-noirs le rapport
     WCAG reste collé à 1 et DÉCROÎT quand l'écart perçu grandit (DESIGN.md, « la règle de
     l'ordre des plans »). C'est en le mesurant à la mauvaise grandeur qu'on a pu croire ces
     quatre teintes distinctes pendant des mois. */
  --sh-j:#23323c; --sh-jt:#a3bfd3; --sh-s:#4d371f; --sh-st:#eabf92; --sh-n:#2a485a; --sh-nt:#b0d0e7; --sh-h:#5e4669; --sh-ht:#e5d0ef;   /* hélico = POURPRE (dark) */
  /* INVARIANT L (01.08) - aucun noir pur, pas même dans une ombre : elles se teintent au plan
     le plus sombre de la charpente (#090a16), pas à #000. */
  /* En sombre l'écriture de navigation est BLANCHE, pas grise : L* 78 contre L* 64 pour l'encre
     atténuée. 8,82:1 sur la surface ; l'onglet courant garde 13,31:1, son cran de graisse et son
     filet de 2 px - la hiérarchie tient sans que rien ne devienne gris.
     ⚠ CETTE VALEUR-CI EST JUSTE, ET C'EST BIEN LE PROBLÈME (05.08) : elle était la SEULE, donc
     elle servait aussi en clair, où elle tombait à 1,61:1. Re-mesurée ici : 9,28:1 sur la feuille
     sombre #161f25, 8,34:1 sur la carte #1e282e. Elle ne bouge pas ; c'est le clair qui prend
     enfin la sienne. */
  --nav-ink:#c0c1c4;
  --anneau:0 0 0 3px rgba(135,160,178,.34);
  --accent-bord:rgba(135,160,178,.36);
  --shadow:0 1px 2px rgba(2,17,27,.55);
  --shadow-s:0 1px 1px rgba(2,17,27,.6);
  --voile:rgba(2,17,27,.52); --voile-fort:rgba(2,17,27,.76);
  --fcal-smur:#365366; --fcal-smur-t:#b4d0e4; --fcal-rega:#533e28; --fcal-rega-t:#e4c09c;
  --fcal-autre:#343538; --fcal-autre-t:#babbc1;
}
/* dark : neutraliser quelques surfaces (le reste suit désormais les tokens) */
/* Ces quatre surfaces se posent SUR la surface : elles suivent donc son relèvement, sinon elles
   s'y noient (le champ de saisie #1c1d22 valait exactement la nouvelle surface #1c1e23). */
[data-theme="dark"] .ui-input,[data-theme="dark"] input.ui-input,[data-theme="dark"] select.ui-input{background:#24262c;color:var(--ink);border-color:var(--line)}
[data-theme="dark"] .chip2{background:#2b2d34;border-color:var(--line);color:var(--ink)}
[data-theme="dark"] .prog-track,[data-theme="dark"] .bar{background:#2e3037}
[data-theme="dark"] input,[data-theme="dark"] select,[data-theme="dark"] textarea{background:#24262c;color:var(--ink);border-color:var(--line)}
[data-theme="dark"] .cmd-hints,[data-theme="dark"] details>div{color:var(--ink-soft)}
[data-theme="dark"] hr{border-color:var(--line)}
[data-theme="dark"] img{opacity:.92}
[data-theme="dark"] .tb{background:rgba(20,22,26,.96) !important}   /* la feuille, en translucide : suit --bg */
/* BANDEAUX D'INFO à fond clair codé « inline » : fond + texte + bordure traités ENSEMBLE pour garder
   un bon contraste en mode sombre (sinon fond clair éblouissant ou texte sombre devenu illisible).
   ⚠ CINQ DE CES RUSTINES SONT PARTIES LE 05.08, PARCE QU'ELLES N'AVAIENT PLUS RIEN À RATTRAPER.
   Elles pêchaient une couleur claire écrite en dur dans un `style=` - `#eef3fb`, `#fff3cd`,
   `#e3f2e8`, `#f7f9fc`, `#f4f5fb` - pour la repeindre en sombre. C'était le pansement ; la plaie
   était l'écriture en dur. Celle-ci soignée (les bandeaux passent par `--blue-bg`, `--orange-bg`,
   `--green-bg`, qui BASCULENT), les cinq sélecteurs ne pouvaient plus matcher quoi que ce soit :
   vérifié sur tout le dépôt, ces cinq valeurs n'existaient plus que dans ces règles-là.
   Une rustine qu'on garde après la réparation devient un piège : le jour où quelqu'un réécrit
   `#fff3cd` quelque part, elle le repeint EN SILENCE et masque le vrai défaut.
   ⚠ LA DERNIÈRE RESTE, et c'est délibéré : `color:#789` vit encore dans `bilan_annuel.py`,
   `mon_planning.py` et `rapport_mensuel.py`. Ce sont des documents IMPRIMABLES autonomes (l'un
   porte `@page{size:A4 landscape}`) : ils n'ont pas de jetons, et l'hexadécimal y est correct -
   comme dans un corps de mail. Tant que cette valeur peut atteindre un écran thémé, sa rustine
   se justifie. La retirer serait supposer, et ce fichier a déjà payé pour ça. */
[data-theme="dark"] [style*="color:#556"],[data-theme="dark"] [style*="color:#667"],[data-theme="dark"] [style*="color:#789"]{color:var(--ink) !important}

/* ── INTERRUPTEUR CLAIR/SOMBRE - CURSEUR (Lot 1 refonte UX 09.07) : composant PARTAGÉ, repris du portail
   médecin (.themesw) pour un style d'interrupteur IDENTIQUE partout. Le knob glisse via [data-theme=dark],
   pas de JS d'icône. Variables adaptées à ui_theme (--line/--bg/--card). */
.themesw{position:relative;width:50px;height:26px;flex:0 0 auto;border:1px solid var(--line);background:var(--bg);border-radius:20px;cursor:pointer;padding:0;transition:border-color .15s,background .15s}
.themesw:hover{border-color:var(--ink-soft)}
.themesw svg{position:absolute;top:6px;width:13px;height:13px;stroke-width:2;pointer-events:none;fill:none;stroke:currentColor}
.themesw .ic-sun{left:6px;color:var(--orange-ink);opacity:.9}
.themesw .ic-moon{right:6px;color:var(--ink-soft);opacity:.5}
.themesw .knob{position:absolute;top:2px;left:2px;width:20px;height:20px;border-radius:50%;background:var(--card);box-shadow:0 1px 3px rgba(9,10,22,.22);transition:transform .2s cubic-bezier(.22,1,.36,1)}
[data-theme="dark"] .themesw .knob{transform:translateX(24px)}
[data-theme="dark"] .themesw .ic-moon{opacity:.9;color:var(--ink)}
[data-theme="dark"] .themesw .ic-sun{opacity:.4;color:var(--ink-soft)}

/* ── ÉQUITÉ : 3 graphiques (grille de scouting · hexagones · courbe de dette) ─────────────── */
.eqgrp{margin:16px 0 6px}
.eqgt{font-weight:700;font-size:14px;margin-bottom:8px;color:var(--ink)}
.eq2col{display:grid;grid-template-columns:1fr 1fr;gap:12px}
@media(max-width:860px){.eq2col{grid-template-columns:1fr}}
.card-eq{border:1px solid var(--line);border-radius:var(--radius);padding:12px 14px;background:var(--card);margin-bottom:12px}
.card-eq .eqh{font-size:13px;font-weight:700;margin-bottom:2px}
.card-eq .eqs{font-size:11.5px;color:var(--ink-soft);margin-bottom:8px;line-height:1.45}
.card-eq .eql{font-size:11px;color:var(--ink-soft);margin-top:6px}
.rgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(168px,1fr));gap:10px}
.rc{text-align:center;border:1px solid var(--line);border-radius:var(--radius);padding:6px 4px 4px}
.rc .rcn{font-size:12px;font-weight:700;margin-top:2px}
.rc .rcv{font-size:11px}
.eq-ok{color:var(--green-ink);font-weight:600}
/* ── FICHE MÉDECIN UNIQUE : maître-détail (liste figée à gauche, fiche à droite) ─────────────── */
.ficwrap{display:grid;grid-template-columns:250px 1fr;gap:14px;align-items:start}
@media(max-width:900px){.ficwrap{grid-template-columns:1fr}}
.ficlist{border:1px solid var(--line);border-radius:var(--radius);padding:8px;background:var(--card);
  max-height:560px;overflow:auto;position:sticky;top:70px}
.ficrow{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:var(--radius);cursor:pointer}
.ficrow:hover{background:var(--hover)}
.ficrow.on{background:var(--blue-bg);box-shadow:inset 2px 0 0 var(--accent)}
.ficdet{border:1px solid var(--line);border-radius:var(--radius);padding:16px 18px;background:var(--card);min-height:360px}
.ficsec{font-size:12px;font-weight:800;color:var(--ink);margin:16px 0 8px;padding-bottom:4px;border-bottom:1px solid var(--line)}
.ficsec:first-of-type{margin-top:4px}
.ficsec span{font-weight:400;color:var(--ink-soft)}
/* ── LA VUE 360°, EN SECTIONS (10.08) ───────────────────────────────────────────────────
   Rémy : « c'est fouilli ». Mesuré sur l'écran réel, à 894 px de fenêtre : DEUX cartes posées
   DANS le panneau - ce que DESIGN.md interdit en toutes lettres (« Don't empiler une carte dans
   une carte ») - et qui ne se mettaient JAMAIS côte à côte : la grille `minmax(240px,1fr)`
   n'avait que 475 px de colonne. C'étaient donc deux boîtes empilées à l'intérieur d'une boîte,
   trois bordures pour séparer ce qu'un filet sépare.
   Les blocs deviennent des SECTIONS, et elles empruntent `.ficsec` - la grammaire de l'onglet
   VOISIN (« Fiche du médecin »). Les trois vues d'un même écran cessent d'avoir trois grammaires. */
.fic360{container-type:inline-size}
/* ⚠ QUATRE MESURES NE SE RANGENT PAS EN 3 + 1. `auto-fit` sur `minmax(140px,1fr)` donnait TROIS
   colonnes dans 475 px : la quatrième tombait seule sur une deuxième rangée, à côté d'un vide de
   deux tiers de largeur. Une grille qui s'auto-ajuste orpheline dès que le compte n'est pas
   multiple du nombre de colonnes qu'elle choisit. On fixe donc 2, et 4 quand la place y est -
   jamais 3. La bascule lit la largeur DU CONTENEUR, pas celle de la fenêtre : ce panneau vit
   dans une grille à deux colonnes, sa largeur n'a rien à voir avec celle de l'écran. */
.fic360-kpi{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:11px}
@container (min-width:720px){.fic360-kpi{grid-template-columns:repeat(4,minmax(0,1fr))}}
/* ⚠ RETIRER LA CARTE A LIBÉRÉ LA LARGEUR, ET C'ÉTAIT UN DÉFAUT DE PLUS. Les barres d'équité,
   jusque-là contenues dans 475 px, se sont étirées sur 1 080 px : le libellé à un bout, le
   chiffre à l'autre, et l'œil qui traverse l'écran pour apparier deux informations voisines.
   Une barre n'a pas besoin de toute la place disponible - elle a besoin qu'on lise ses deux
   extrémités d'un seul regard. Les mesures gardent donc la pleine largeur (ce sont des tuiles),
   le corps chiffré se borne. */
.fic360-corps{max-width:680px}
/* ── OÙ EN EST CETTE PERSONNE (11.08, demande de Rémy) ──────────────────────────────────
   « Dernier tournus, prochain tournus, vacances posées, RJF en attente avec un bouton pour les
   poser » - trois faits qu'il fallait aller chercher sur trois écrans.
   ⚠ ET SANS TEXTE ADJACENT (11.08, seconde passe, Rémy : « plus clair sans le texte adjacent :
     on ne veut pas le lire »). Ma première version expliquait pourquoi une colonne était vide -
     trois lignes de prose au milieu d'un tableau de bord. L'intention était bonne (« un vide se
     dit »), la forme non : on n'ouvre pas une fiche pour LIRE, on l'ouvre pour VOIR. Une raison
     qui mérite d'exister mais pas d'être lue va au SURVOL, pas à l'écran.
   Le motif est celui du poste « Le point » (`.pt-l`) : étiquette en chasse fixe à gauche, fait
   à droite, une ligne par fait. Rien à parcourir. */
.fic-situ{display:grid;gap:0;margin-bottom:14px;max-width:560px}
.fic-l{display:grid;grid-template-columns:152px minmax(0,1fr);gap:12px;align-items:baseline;
  padding:8px 0;border-bottom:1px solid var(--line);font-size:13px}
.fic-l:last-child{border-bottom:0}
.fic-l .k{font-family:var(--mono);font-size:11px;font-weight:600;letter-spacing:.05em;
  text-transform:uppercase;color:var(--ink-soft)}
.fic-l .n{color:var(--ink);min-width:0}
.fic-l .n.vide{color:var(--ink-soft)}
.fic-l .n b{font-weight:600}
.fic-l .u{font-size:12px;color:var(--ink-soft)}
/* La pose d'un RJF, sur place : le champ et le bouton sur UNE ligne - c'est un seul geste.
   `.rjfrow` et `.rjf-datepose` sont ceux de l'onglet RJF : même geste, même code. */
.fic-l .rjfrow{display:flex;gap:6px;align-items:center;flex-wrap:wrap;margin-top:6px}
.fic-l .rjf-datepose{max-width:148px}
@media (max-width:560px){.fic-l{grid-template-columns:minmax(0,1fr);gap:3px}}
/* ── LE CALENDRIER DES VACANCES, SOUS SON TOTAL (18.08) ─────────────────────────────────────
   La ligne « Vacances » disait un nombre ; on ouvre une fiche pour savoir QUAND. Une ligne par
   période : la date à gauche (c'est ce qu'on cherche), le compte au milieu, la provenance à
   droite en encre atténuée - elle qualifie, elle n'appelle aucun geste, donc elle n'est pas
   teintée (règle K, réciproque : l'accent ne peint que ce qui se clique).
   Le pas de 11 px est le PLANCHER de la charte, pas une valeur choisie au hasard : en dessous,
   l'invariant A passe au rouge - et il a raison, ces dates se lisent. */
.vacper{margin-top:8px;display:grid;gap:0;max-width:420px}
.vacper-t{font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;
  color:var(--ink-soft);margin:8px 0 2px}
.vacper-t:first-child{margin-top:2px}
.vacper-l{display:grid;grid-template-columns:minmax(0,1fr) 52px minmax(0,84px);gap:8px;
  align-items:baseline;padding:3px 0;font-size:12px;border-bottom:1px solid var(--line)}
.vacper-l:last-child{border-bottom:0}
.vacper-l .d{color:var(--ink);white-space:nowrap}
.vacper-l .j{color:var(--ink);font-weight:600;text-align:right;white-space:nowrap}
.vacper-l .s{color:var(--ink-soft);font-size:11px;text-align:right;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.vacper-n{font-size:11px;color:var(--ink-soft);margin-top:7px;line-height:1.45}
/* Sous 560 px la ligne de fiche est déjà repliée : la période garde ses trois colonnes mais
   lâche la provenance, qui vit au survol - jamais tronquée à deux lettres. */
@media (max-width:560px){.vacper-l{grid-template-columns:minmax(0,1fr) 52px}.vacper-l .s{display:none}}
/* ── UN NOM QUI MÈNE À SA FICHE ──────────────────────────────────────────────────────────────
   Le bilan des vacances listait trente noms en encre de texte : rien ne disait qu'on pouvait
   les ouvrir, et rien ne le pouvait. C'est un vrai <button> (tabulable, annoncé, Entrée).
   ⚠ ET IL EST SOULIGNÉ, PAS TEINTÉ - mon premier jet le peignait à `--accent-ink`, et
     l'invariant K l'a refusé à raison : la règle est celle que Rémy a tranchée le 02.08 (« pas
     fan de cette typo en bleu, fais des boutons de couleur si tu veux du cliquable »), et 102
     encres d'action étaient redevenues l'encre du texte ce jour-là. Un lien se souligne. Le
     trait est atténué au repos et plein au survol : trente noms lourdement soulignés feraient
     une grille de traits là où on vient lire des barres. */
.nomlien{appearance:none;-webkit-appearance:none;background:none;border:0;padding:0;margin:0;
  font:inherit;color:var(--ink);cursor:pointer;text-align:left;border-radius:var(--radius-s);
  text-decoration:underline;text-decoration-color:var(--silver);text-underline-offset:3px}
.nomlien:hover{text-decoration-color:var(--ink)}
.nomlien:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
/* AU DOIGT, UNE CIBLE EST UNE SURFACE (invariant H) : les DEUX côtés, jamais la seule hauteur. */
@media (pointer:coarse){.nomlien{min-height:44px;min-width:44px;display:inline-flex;align-items:center}}
/* Le dépassement de droit, partout où la phrase des vacances est rendue (bilan ET fiche) : la
   phrase n'ayant plus qu'un auteur, sa couleur ne peut plus vivre dans le style d'un seul écran. */
.vacdep{color:var(--red-ink);font-weight:700}
/* ⚠ LA GRILLE DE L'ÉCRAN NE SE REPLIAIT JAMAIS (10.08, trouvé en éprouvant le repli à 375 px).
   `minmax(0,300px) minmax(0,1fr)` était écrit EN LIGNE dans le gabarit, sans aucune bascule :
   à 375 px la liste gardait ses 300 px et la fiche tombait à 59 px de large - la page débordait
   de 25 px et le médecin qu'on venait consulter n'était plus lisible. PRODUCT.md dit que la
   console EST consultée au téléphone, vérifié à 375 px : ce n'était donc pas un cas de bord.
   Sous 860 px, une colonne : la liste d'abord, la fiche dessous. */
/* ── LA FRISE DU TOURNUS, EN SEGMENTS (11.08) ───────────────────────────────────────────
   Rémy : « peut-on revoir cette frise ». Ce qu'elle avait : 53 colonnes de TEXTE, un `<td>` par
   semaine - donc des noms coupés à six lettres (« Chappu… », « Stierl… ») et treize semaines
   visibles sur cinquante-trois sans défiler. Le texte était ce qui forçait la colonne à être
   large ; on le sort de la cellule et une garde de plusieurs semaines écrit son nom en entier.
   ⚠ UN TIERS DES SÉJOURS DURE UNE SEULE SEMAINE (12 sur 33, mesuré) - ce ne sont pas des
     scories, et les fusionner par personne ne les fait pas disparaître (vérifié : on passe de 11
     à 12). Leur nom descend donc SOUS la barre, avec un trait, sur autant de niveaux qu'il en
     faut pour ne pas se chevaucher - la maquette du 11.08 en a mesuré trois. Rien au survol
     seul : ce qu'on doit lire se lit.
   La grille remplace le tableau : plus de défilement horizontal, la frise tient dans sa largeur. */
.frz2{display:grid;gap:4px;font-size:12px;margin-top:4px}
.frz2-r{display:grid;grid-template-columns:var(--frz2-g,86px) minmax(0,1fr);gap:10px;align-items:center}
.frz2-g{font-family:var(--mono);font-size:11px;color:var(--ink-soft);white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis}
/* ⚠ EN RETIRANT LE TABLEAU, J'AI RETIRÉ SES TRAITS (11.08, Rémy : « on voit mal la séparation
   des semaines »). Une cellule de tableau portait une bordure ; un segment de grille flotte sur
   du vide. Les barres disaient donc COMBIEN de temps, plus QUAND - or c'est la première question
   qu'on pose à une frise.
   La grille revient comme un FOND, pas comme des éléments : un trait d'un pixel tous les
   `100 % / n`, la division étant celle du gabarit (`--frz2-n`) et non un 53 écrit ici - l'axe
   change avec l'année. Le mois est plus appuyé que la semaine : on repère le mois d'abord, la
   semaine ensuite. Aucun de ces traits ne porte de texte : ce sont des filets, pas des encres
   (invariant O ne s'y applique pas, et c'est bien un filet qu'on veut voir se taire). */
.frz2-b{display:grid;grid-template-columns:repeat(var(--frz2-n,53),minmax(0,1fr));gap:2px;
  background-image:linear-gradient(to right,var(--line) 0 1px,transparent 1px);
  background-size:calc(100% / var(--frz2-n,53)) 100%;background-repeat:repeat-x}
/* Le repère du MOIS : plus visible que la semaine, posé par le gabarit là où le mois commence. */
.frz2-mois{grid-row:1;height:100%;border-left:1px solid var(--ink-soft);opacity:.45;
  pointer-events:none;align-self:stretch}
.frz2-m span{font-family:var(--mono);font-size:11px;color:var(--ink-soft);letter-spacing:.04em}
/* ⚠ LE REPÈRE DE MOIS ET LA BARRE VIVENT SUR LA MÊME RANGÉE (13.08, Rémy : « not aligned »).
   J'avais posé `grid-row:1` sur le repère SEUL : les segments, placés automatiquement, se sont
   donc rangés sur la rangée SUIVANTE - chaque ligne de frise faisait deux rangées de haut, et
   le nom de la marge ne pointait plus la bonne barre. Une grille place ce qu'on ne place pas
   explicitement : dès qu'un enfant est fixé, tous doivent l'être. */
.frz2-seg{grid-row:1;border-radius:var(--radius-s);height:22px;display:flex;align-items:center;padding:0 6px;
  overflow:hidden;font-size:11.5px;font-weight:600;min-width:0}
.frz2-seg b{font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.frz2-seg.vide{background:var(--blue-bg)}
/* ── UNE LIGNE PAR PERSONNE (11.08) ──────────────────────────────────────────────────────
   Rémy, deux fois : « c'est toujours le foutoir », « trop le bordel - trouve un moyen ».
   Trois mises en forme ont été essayées puis retirées - nom DANS la barre (coupé), nom SOUS la
   barre en quinconce (vingt-sept étiquettes flottantes), nom en PHRASE sous la frise (douze
   d'affilée). C'était trois fois le même défaut : le nom se disputait la place avec la barre.
   Il n'y a pas de bonne façon de faire tenir un nom dans une case d'une semaine - il faut cesser
   de le lui demander. Le nom prend donc sa propre ligne, dans la marge, entier, définitivement.
   La barre redevient ce qu'elle est : une durée. */
.frz2-tete{display:flex;align-items:baseline;gap:9px;margin:11px 0 3px;padding-top:9px;
  border-top:1px solid var(--line)}
.frz2-tete:first-of-type{margin-top:4px}
.frz2-tete b{font-family:var(--mono);font-size:11px;font-weight:600;letter-spacing:.06em;
  color:var(--ink)}
.frz2-tete span{font-size:11.5px;color:var(--ink-soft)}
.frz2-p1{--frz2-g:132px}
.frz2-p1 .frz2-g{color:var(--ink);font-family:inherit;font-size:12.5px}
.frz2-seg.mince{height:13px;padding:0}
/* Une ligne de frise qui AGIT le dit. Porté par la frise CDC urgences : cliquer un médecin
   ouvre la pose d'une reprise de tournus. L'affordance est le SOULIGNÉ pointillé + le curseur,
   PAS une encre d'accent : la charte (K) interdit la typographie d'accent - « la couleur se
   porte par un objet, un lien se souligne » - et c'est elle qui a repris ma première version. */
.frz2-g.frz2-clic{cursor:pointer;text-decoration:underline dotted;text-underline-offset:2px}
.frz2-g.frz2-clic:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:var(--radius-s)}
/* Le pied « Présents » garde ses deux états - il n'a jamais été décoratif depuis le 01.08. */
.frz2-p{height:20px;border-radius:var(--radius-s);display:flex;align-items:center;
  justify-content:center;font-family:var(--mono);font-size:11px;font-weight:600}
@media (max-width:720px){.frz2-r{--frz2-g:62px}.frz2-seg{height:19px;font-size:11px}}
.eq360-lay{display:grid;grid-template-columns:minmax(0,1fr);gap:16px;align-items:start}
@media (min-width:860px){.eq360-lay{grid-template-columns:minmax(0,300px) minmax(0,1fr)}}
.ficgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:10px 14px}
.ficgrid select,.ficgrid input{width:100%}
/* tableau détaillé d'équité : lignes zébrées, en-tête collant, colonne écart en évidence */
.eqtab{border-collapse:collapse;margin-top:6px;width:100%;max-width:820px;font-size:12.5px}
.eqtab th{background:var(--bg);border-bottom:2px solid var(--line);padding:6px 8px;font-size:11px;
  font-weight:700;color:var(--ink-soft);text-align:center;white-space:nowrap}
.eqtab td{padding:6px 8px;border-bottom:1px solid var(--line)}
.eqtab td.c{text-align:center}
.eqtab td.mut{color:var(--ink-soft)}
.eqtab tr:hover td{background:var(--hover)}

/* ── PIED DE SIDEBAR (maquette) : bascule de rôle + fiche + clair/sombre ── */
.pip{display:inline-block;width:9px;height:9px;border-radius:50%;vertical-align:-1px;margin-right:5px;flex:none}
.sb-role{display:flex;gap:3px;margin:10px;padding:4px;background:rgba(255,255,255,.06);border-radius:var(--radius)}
.sb-role a,.sb-role span{flex:1;text-align:center;padding:8px 6px;border-radius:var(--radius);font-size:13px;font-weight:550;
  color:var(--side-ink-soft);text-decoration:none;cursor:pointer}
.sb-role .on{background:transparent;color:var(--cartouche-ink);font-weight:600;box-shadow:inset 0 -2px 0 var(--accent)}
.sb-role a:hover:not(.on){background:rgba(255,255,255,.06);color:#fff}
.sb-who{position:relative}
.sb-sw{margin-left:auto;width:44px;height:24px;border-radius:var(--radius);border:0;background:rgba(255,255,255,.12);
  position:relative;cursor:pointer;flex:none;padding:0}
.sb-sw i{position:absolute;top:3px;left:3px;width:18px;height:18px;border-radius:50%;background:#fff;transition:.18s;
  display:grid;place-items:center}
.sb-sw i svg{width:11px;height:11px;stroke:var(--side-1);fill:none;stroke-width:2}
[data-theme="dark"] .sb-sw i{left:23px}


/* ── Compteur de la sidebar (maquette : pastille rouge sur « À traiter ») ── */
.sb-link{ position:relative; }
.sb-cnt{ margin-left:auto; min-width:20px; height:20px; padding:0 6px; border-radius:var(--radius);
  background:var(--red); color:var(--on-red); font-size:11px; font-weight:600;
  display:inline-flex; align-items:center; justify-content:center; }


/* ============================================================================
   REFONTE UX - PASSE D'ÉPURE FINALE (override additif, purement visuel)
   ----------------------------------------------------------------------------
   À COLLER tel quel À LA FIN de THEME_CSS dans ui_theme.py (juste avant les
   triples guillemets fermants), APRÈS toutes les autres règles : la cascade
   fait gagner ce bloc sans qu'on modifie une seule classe HTML ni un endpoint.

   Ne touche NI aux données, NI au câblage /run, NI au JS : uniquement l'aspect.
   Réversible : supprime ce bloc pour revenir à l'état précédent.

   Ce que ça applique (état final validé de la console maquette) :
     · cartes/panneaux à PLAT (plus de barre d'accent à gauche, plus d'ombre)
     · surfaces neutres (fini les fonds teintés bleus des cartes/KPI)
     · sidebar plus calme (descriptions de groupe estompées)
     · tables et chips allégés
   ============================================================================ */

/* ---------- cartes & KPI : à plat, filet fin, aucune ombre ---------- */
.card{
     /* supprime la barre d'accent épaisse */
  
  background:var(--card);
}

/* ---------- surfaces neutres : plus de fonds bleutés « boîte » ---------- */
.card,.kpi,.panel{ background:var(--card); }
.note{  }              /* note : neutre au lieu du bleu */
.panel-hd .hic{  }     /* icône d'en-tête : neutre */
.qa a .ic{  }          /* action rapide : icône neutre */

/* ---------- sidebar : plus posée ---------- */
.sb-grp-d{ opacity:.72; }                    /* descriptions de groupe estompées */

/* ---------- tables : filets plus discrets, en-têtes calmes ---------- */

/* ---------- chips : neutres, sans fond bleu saturé ---------- */
.chip2{  border-color:var(--line); }
.chip{ background:var(--bg); color:var(--ink); }

/* ---------- segmented control : contour au lieu de plein ---------- */

/* ---------- retrait des ombres résiduelles sur petits éléments ---------- */
.card-eq{ box-shadow:none; }


/* ---------- onglet actif de la charpente : l'accent en aplat très dilué ----------
   Ce qui reste de l'ancien bloc « OVERRIDE ARDOISE ». Ses redéfinitions de tokens ont été
   remontées dans :root et dans le thème sombre - elles n'avaient rien à faire en fin de
   feuille. Ne subsiste ici que ce que le bloc AJOUTAIT vraiment. */
/* 29.07 - L'APLAT DILUÉ ÉTAIT ENCORE UN APLAT. Ces deux règles teintaient l'onglet courant de la
   charpente à 14 % et 16 % d'ardoise : dilué, donc discret, donc jamais discuté - et pourtant
   c'est le contrôle le plus présent de l'application, sur toutes les pages, dans les deux thèmes.
   Le voile redevient NEUTRE (il l'était déjà pour le survol, à 6 % et 11 %) ; c'est le
   soulignement qui désigne l'onglet, comme partout ailleurs depuis le reset. */
html:not([data-theme="dark"]) .sb-link.on{background:rgba(255,255,255,.11);}
[data-theme="dark"] .sb-link.on{background:rgba(255,255,255,.09);}


/* ==== HUB TOPBAR v2 (additif, desktop >=881px) : barre haute + nav integree, sans debordement. Mobile <=880 intact. Reversible. ==== */
@media(min-width:881px){
 .shell{display:block;min-height:100vh}
 .sb{flex-direction:row;align-items:center;height:56px;width:100%;position:sticky;top:0;z-index:40;border-right:none;border-bottom:1px solid var(--side-line);padding:0 12px;gap:2px;overflow:hidden}
 .sb-brand{order:0;flex:none;padding:0 10px 0 2px}
 .sb-brand span{display:none}
 .sb-nav{order:1;flex:0 0 auto !important;min-width:0;flex-direction:row;padding:0;margin:0;gap:2px;overflow-x:auto;scrollbar-width:none}
 .sb-nav::-webkit-scrollbar{display:none}
 .sb-link{width:auto;margin:0;padding:9px 13px;white-space:nowrap;border-radius:0;color:var(--side-ink)}
 .sb-link svg{opacity:.65}
 .sb-link:hover{background:rgba(255,255,255,.06);color:var(--cartouche-ink)}
 .sb-link.on{background:transparent;box-shadow:inset 0 -2px 0 var(--accent);color:#fff}
 .sb-link.on svg{opacity:1}
 .sb-cnt{margin-left:6px}
 .sb>button[onclick^='palOuvrir']{order:5;flex:none !important;width:168px !important;margin:0 2px 0 auto !important;padding:7px 10px !important;border-radius:var(--radius)}
 .sb-role{order:6;flex:none;margin:0 2px}
 .sb-role a,.sb-role span{padding:6px 10px}
 .sb-who{order:7;flex:none;border-top:none;padding:0 4px;margin:0;gap:6px !important}
 .sb-who b{font-size:12.5px} .sb-who small{display:none}
 .sb>div:last-child{order:8;flex:none;padding:0 6px !important}
 .tb{top:56px}
 .content{max-width:1560px;margin:0 auto}   /* corps CENTRE */
 /* CENTRAGE ALIGNE (tous onglets) : titre, sous-onglets, bandeaux et corps centres sur la MEME colonne 1560.
    Les barres restent pleine largeur (fond), mais leur CONTENU est repousse de l'inset de centrage + le padding habituel. */
 .tb{padding-left:calc(max(0px,(100% - 1560px)/2) + 26px);padding-right:calc(max(0px,(100% - 1560px)/2) + 26px)}
 #secbar .seg.sub{display:flex}
 #reassur{margin-left:calc(max(0px,(100% - 1560px)/2) + 18px);margin-right:calc(max(0px,(100% - 1560px)/2) + 18px)}
 #cmd_out{padding-left:calc(max(0px,(100% - 1560px)/2) + 18px);padding-right:calc(max(0px,(100% - 1560px)/2) + 18px)}
 /* sous-onglets = barre secondaire integree (souligne, defile, plus de pave blanc) */
 .seg.sub{background:transparent;border:none;border-bottom:1px solid var(--line);border-radius:0;padding:0;gap:0;flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none}
 .seg.sub::-webkit-scrollbar{display:none}
 .seg.sub button{border-radius:0;padding:10px 15px;white-space:nowrap;color:var(--nav-ink)}
 .seg.sub button:hover{background:var(--hover);color:var(--ink)}
 .seg.sub button.on{background:transparent;color:var(--ink);box-shadow:inset 0 -2px 0 var(--accent)}
}
@media(min-width:881px) and (max-width:1199px){
 .sb-who>div{display:none}
 .sb>button[onclick^='palOuvrir']{width:150px}
 .sb>div:last-child a{font-size:0}
 .sb>div:last-child a::after{content:'AA';font-size:15px}
}


/* ==== TYPO (29.07, mesure remplacée le 05.08) : Newsreader (titres) / Archivo (corps) /
   IBM Plex Mono (labels & meta).
   NEWSREADER remplace Fira Sans (reset du 29.07, DESIGN.md) : un planning est un document
   qu'on LIT. Fira Sans avait elle-meme remplace Space Grotesk le 28.07, sur maquette. Deux raisons,
   dont une qui se voit a l'oeil : le chiffre 1 de Space Grotesk n'a pas d'empattement de pied
   -- un simple baton vertical -- sur une application ou l'on lit des dates et des compteurs
   toute la journee ; et c'est l'une des faces les plus vues des interfaces generees, signalee
   comme telle par le detecteur. Fira Sans est humaniste, dessinee pour l'ecran. ==== */
.tb h1{font-family:var(--display);text-transform:uppercase;line-height:1.05}
.tb .sub{font-family:var(--mono);letter-spacing:.04em;text-transform:uppercase}
.kpi .v,.card .v{font-family:var(--display);letter-spacing:-.03em;font-weight:700}
/* LE BANDEAU DE KPI EST LE PREMIER ÉCRAN DE LA CONSOLE, et il était écrit en 10 px et 9,5 px -
   sous le pas d'étiquette documenté dans DESIGN.md (11 px / 600 / +0,06em / capitales). Sur une
   équipe à large tranche d'âge, c'est une obligation, pas un confort. Et les graisses étaient
   INVERSÉES : le détail (« 55 manquants ») portait 550 quand son titre (« DÉSIDÉRATA REÇUS »)
   portait 400 - la ligne accessoire criait plus fort que celle qu'elle accompagne. */
.kpi .lab,.kpi .l,.card .l{font-family:var(--mono);font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-soft)}
.kpi .t{font-family:var(--mono);font-size:11px;font-weight:400;letter-spacing:.04em;text-transform:uppercase;line-height:1.45}


/* ==== HUB NAVFIX : les 5 espaces TOUJOURS visibles ; bloc droit compacte (nom masque, logout en icone) ==== */
@media(min-width:881px){
 .sb-nav{display:flex !important;overflow:visible !important}   /* display:flex OBLIGATOIRE : sans lui le <nav> reste bloc -> espaces empiles en colonne, un seul visible (bug 21.07) */
 .sb-who>div{display:none !important}
 .sb>div:last-child a{font-size:0}
 .sb>div:last-child a::after{content:'AA';font-size:15px}
}


/* ==== HUB CALX : calendriers harmonises (en-tetes mono, rayon aligne au reste) ==== */
.calx .dow{font-family:var(--mono);}
.calx .cell .d{font-family:var(--mono);letter-spacing:.02em}


/* ==== HUB EPURE : moins de texte inutile, KPI en bandeau joint, cartes a plat ==== */
/* ══ LE POINT - LE POSTE DE DECISION (09.08) ═══════════════════════════════════════════════
   Cet ecran est la SEULE exception au partage « la feuille et son cartouche » : la charpente y
   prend toute la page. Le choix est de Remy, apres cinq maquettes comparees ; la clause est
   ecrite dans DESIGN.md plutot que contournee en silence.
   Ce que ca implique, et qui n'est pas negociable ici : aucune carte (tout se separe par des
   filets), les mesures en chasse fixe, et la couleur reservee au GESTE - l'accent ne peint que
   les boutons, les deux encres de signal ne peignent que des etats.
   ⚠ AUCUNE REGLE DE CE BLOC N'EST DUPLIQUEE DANS LE JS : le gabarit n'emet que des classes
     (invariant E - un seul auteur du monde visuel). */
.pt{background:var(--side-1);border:1px solid var(--side-line);border-radius:var(--radius);overflow:hidden}
.pt-b{display:grid;grid-template-columns:repeat(auto-fit,minmax(132px,1fr));border-bottom:1px solid var(--side-line)}
.pt-b > div{padding:11px 14px;border-right:1px solid var(--side-line)}
.pt-b > div:last-child{border-right:0}
.pt-b .lab{font-family:var(--mono);font-size:11px;font-weight:600;letter-spacing:.06em;
  text-transform:uppercase;color:var(--side-ink-soft)}
.pt-b .v{font-family:var(--mono);font-size:19px;color:var(--side-ink);margin-top:3px;letter-spacing:-.01em}
.pt-b .v.att{color:var(--side-att)}
.pt-b .v.al{color:var(--side-alarme)}
/* ── LA FRISE DU CYCLE - LE TEMPS (09.08) ────────────────────────────────────────────────
   Elle repond a la premiere critique de Remy sur le poste : « pas de temps ». Les jalons sont
   poses sur un rail ; celui ou l'on EST porte l'encre d'attention, ceux qui sont FAITS l'acier.
   Aucun aplat : un jalon est un point sur une ligne, pas une pastille de statut. */
.pt-f{border-bottom:1px solid var(--side-line);padding:12px 15px 13px}
/* ⚠ LE COMPTE A REBOURS A SA PROPRE LIGNE. Pose en absolu a droite de la frise, il CHEVAUCHAIT
   le quatrieme jalon - « Octobre 2026 commence dans 56 j » s'imprimait par-dessus
   « PUBLICATION ». Vu a l'ecran, pas dans le code : un positionnement absolu ne previent jamais
   qu'il recouvre quelque chose. Il devient l'en-tete de la frise, qui n'en avait pas. */
.pt-f .jr{display:block;font-family:var(--mono);font-size:11px;font-weight:600;letter-spacing:.06em;
  text-transform:uppercase;color:var(--side-ink-soft);margin-bottom:11px}
.pt-fj{display:grid;grid-auto-flow:column;grid-auto-columns:1fr;gap:10px}
.pt-fj > div{position:relative;padding-left:20px;min-width:0}
.pt-fj .pt-pt{position:absolute;left:0;top:2px;width:9px;height:9px;border-radius:50%;
  background:var(--side-1);border:2px solid var(--side-line);box-sizing:border-box}
/* ⚠ PAS L'ACCENT SUR UN JALON FRANCHI. `check_charte` (invariant K) l'a refuse, et c'est la
   regle de Remy elle-meme : l'accent designe ce qu'on peut FAIRE ; pose sur un point inerte, il
   promet un geste qui n'existe pas. Un jalon fait se dit par un point PLEIN dans l'encre de la
   charpente - la forme suffit, la couleur de l'action reste rare. */
.pt-fj .pt-pt.fait{background:var(--side-ink-soft);border-color:var(--side-ink-soft)}
.pt-fj .pt-pt.ici{background:var(--side-att);border-color:var(--side-att)}
.pt-fj .jl{font-family:var(--mono);font-size:11px;font-weight:600;letter-spacing:.06em;
  text-transform:uppercase;color:var(--side-ink-soft);display:block}
.pt-fj .je{font-size:12.5px;color:var(--side-ink);display:block;margin-top:2px}
.pt-fj > div.ici .je{color:var(--side-att)}
/* L'ESPACE ETAIT SOUS-UTILISE (Remy) : le poste tenait dans le tiers haut d'un grand ecran. La
   colonne de decision - la plus etroite - devient la plus large, et le poste respire. */
.pt-g{display:grid;grid-template-columns:1fr 1.15fr}
.pt-c{padding:13px 15px;min-width:0}
.pt-c + .pt-c{border-left:1px solid var(--side-line)}
.pt-t{font-family:var(--mono);font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;
  color:var(--side-ink-soft);margin:0 0 6px}
.pt-t + .pt-t,.pt-l + .pt-t,.pt-a + .pt-t{margin-top:16px}
.pt-l{display:grid;grid-template-columns:54px 1fr auto;gap:12px;align-items:center;padding:8px 0;
  border-bottom:1px solid var(--side-line);font-size:12.5px;color:var(--side-ink-soft)}
.pt-l:last-child{border-bottom:0}
.pt-l .k{font-family:var(--mono);font-size:11px;color:var(--side-ink-soft);letter-spacing:.05em}
/* ⚠ LE ROND IOT PARAISSAIT BRISE SUR SA GAUCHE (Remy). Ce n'est pas la pastille : mesuree, elle
   fait bien 9x9 et n'est pas rognee. C'est son ANNEAU - `.iot-r` porte un `outline` pose a
   1,5 px A L'EXTERIEUR de la boite. Or un outline se peint HORS de la boite, et il se fait donc
   couper par le `overflow:hidden` que j'avais mis ici pour l'ellipse des noms longs.
   Mon premier controle a conclu « pas clippe » parce qu'il mesurait le rectangle de la PASTILLE,
   pas celui de son anneau : l'instrument regardait a cote de l'endroit ou la propriete vit.
   Le rembourrage compensé rend la place a l'anneau sans deplacer le texte. */
.pt-l .n{color:var(--side-ink);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  padding-left:4px;margin-left:-4px}
/* La pastille IOT MORDAIT sur le nom (Remy) : `.dotmini` est un inline-block sans marge, et
   partout ailleurs c'est le rembourrage de la puce qui l'ecartait du texte. Ici il n'y a pas de
   puce - l'ecart se declare, et seulement dans ce composant. */
.pt-l .n .dotmini{margin-right:7px;vertical-align:baseline}
/* LE NIVEAU SE VOIT (09.08). En refondant, j'avais garde le rang du tri SERVEUR mais perdu son
   signal : les trois items s'affichaient identiques, et « a surveiller » ne se distinguait plus
   de « a voir ». Un filet de 2 px a gauche de la ligne - jamais un aplat, jamais plus de 2 px. */
.pt-a.n1{box-shadow:inset 2px 0 0 var(--side-alarme);padding-left:11px}
.pt-a.n2{box-shadow:inset 2px 0 0 var(--side-att);padding-left:11px}
.pt-l .u{font-family:var(--mono);font-size:12px;color:var(--side-ink-soft);white-space:nowrap}
.pt-l .u.att{color:var(--side-att)}
.pt-a{display:flex;gap:8px;align-items:flex-start;padding:10px 0;border-bottom:1px solid var(--side-line);
  font-size:12.5px;color:var(--side-ink-soft);flex-wrap:wrap}
.pt-a:last-child{border-bottom:0}
.pt-a > .txt{flex:1;min-width:150px}
.pt-a b{color:var(--side-ink);font-weight:600}
.pt-a .u{font-family:var(--mono);font-size:11px;color:var(--side-ink-soft)}
.pt-btn{font-family:inherit;font-size:12px;font-weight:600;padding:6px 11px;border-radius:var(--radius);
  border:1px solid var(--side-line);background:transparent;color:var(--side-ink);cursor:pointer;white-space:nowrap}
.pt-btn:hover{border-color:var(--side-ink-soft)}
/* ⚠⚠ « SON ENCRE EST L'ENCRE-NOIRE, PAS DU BLANC » - CETTE PHRASE ÉTAIT ÉCRITE ICI, ET ELLE
   ÉTAIT FAUSSE (10.08, Rémy : « les boutons sont dégueulasses »). Elle justifiait d'emettre des
   `ui-btn` a l'interieur du poste. Mesure au navigateur, thème CLAIR : l'encre d'un `ui-btn`
   vaut `rgb(2,17,27)` - c'est-a-dire EXACTEMENT `--side-1`, le fond du cartouche. Contraste
   **1,00:1**. Le texte etait la, peint dans la couleur du fond : « Vu » et « Diffuser aux
   medecins » apparaissaient comme des BOITES VIDES, a la largeur exacte de leur libelle.
   `.ui-btn.danger` tombait a 2,75:1. Seul `.pt-btn`, qui prend son encre dans la charpente,
   etait correct (19,12:1).
   LA REGLE, generale : UN COMPOSANT QUI PEINT SON PROPRE FOND POSSEDE L'ENCRE DE TOUT CE QU'IL
   CONTIENT. Le cartouche est sombre dans les DEUX themes ; un composant de page y entre avec les
   encres de la PAGE, et en theme clair ces encres sont faites pour du papier. Ce n'est pas un
   oubli de plus : c'est que la raison ecrite ici n'avait jamais ete mesuree.
   Les variantes ci-dessous sont des ENCRES, pas des aplats - l'invariant G n'a rien a leur
   reprocher, et le bouton principal du systeme reste unique. La cible tactile de 44 px est deja
   portee par `.pt-btn` (regle `pointer: coarse` juste en dessous), donc rien ne se perd. */
.pt-btn.vert{color:var(--side-ok);border-color:color-mix(in srgb,var(--side-ok) 45%,transparent)}
.pt-btn.vert:hover{border-color:var(--side-ok)}
.pt-btn.danger{color:var(--side-alarme);border-color:color-mix(in srgb,var(--side-alarme) 45%,transparent)}
.pt-btn.danger:hover{border-color:var(--side-alarme)}
.pt-btn.ghost{color:var(--side-ink-soft)}
.pt-btn.ghost:hover{color:var(--side-ink);border-color:var(--side-ink-soft)}
/* LE FILET DE SÉCURITÉ. Le gabarit n'emet plus de `ui-btn` ici - mais le jour ou un rendu
   partage en depose un, il doit rester LISIBLE plutot que de disparaitre en silence. Un defaut
   qui se voit se corrige ; celui-ci ne se voyait pas. */
.pt .ui-btn{color:var(--side-ink);border-color:var(--side-line);background:transparent}
.pt .ui-btn:hover{border-color:var(--side-ink-soft)}
.pt .ui-btn.danger{color:var(--side-alarme);border-color:color-mix(in srgb,var(--side-alarme) 45%,transparent)}
.pt .ui-btn.vert{color:var(--side-ok);border-color:color-mix(in srgb,var(--side-ok) 45%,transparent)}
.pt-vide{color:var(--side-ink-soft);font-size:12.5px;padding:6px 0}
/* ── LA METEO DES TROIS PROCHAINS MOIS (10.08) ───────────────────────────────────────────
   La seule TENDANCE de cet ecran : combien de jours tendus s'annoncent, et quand. La donnee
   etait deja calculee et envoyee a chaque affichage du Point - personne ne la lisait.
   Une barre par mois, longue comme le mois : la part coloree EST la part tendue. Quand tout est
   calme la barre reste un rail neutre, ce qui se lit « degage » sans qu'on ait a compter.
   ⚠ AUCUN APLAT D'ACCENT : la barre porte les encres de SIGNAL de la charpente (etat), jamais
     la couleur de l'action (invariant K). */
.pt-m{border-top:1px solid var(--side-line);padding:13px 15px}
.pt-mr{display:grid;grid-template-columns:130px 1fr auto;gap:12px;align-items:center;padding:7px 0}
.pt-mr .mo{font-size:12.5px;color:var(--side-ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pt-mr .ba{height:6px;border-radius:2px;background:var(--side-line);overflow:hidden;display:flex}
.pt-mr .ba i{display:block;height:100%}
.pt-mr .ba i.cr{background:var(--side-alarme)}
.pt-mr .ba i.fr{background:var(--side-att)}
.pt-mr .qt{font-family:var(--mono);font-size:11px;color:var(--side-ink-soft);white-space:nowrap}
.pt-mr .qt.att{color:var(--side-att)}
.pt-mr .qt.al{color:var(--side-alarme)}
@media (max-width:640px){.pt-mr{grid-template-columns:1fr auto;gap:6px}.pt-mr .ba{grid-column:1/-1}}
/* LE POSTE SE PLIE AVANT DE DEBORDER. Les deux colonnes s'empilent, et l'ordre de lecture
   est conserve : ce qui se passe d'abord reste en haut. */
@media (max-width:820px){.pt-g{grid-template-columns:1fr}.pt-c + .pt-c{border-left:0;border-top:1px solid var(--side-line)}}
@media (pointer: coarse){.pt-btn{min-height:44px;min-width:44px;display:inline-flex;align-items:center;justify-content:center}}
.kpi-strip{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));border:1px solid var(--line);border-radius:var(--radius);overflow:hidden;margin-bottom:16px}
.kpi-strip .kpi{border:none;border-right:1px solid var(--line);border-radius:0;box-shadow:none;background:transparent}
.kpi-strip .kpi:last-child{border-right:none}
#reassur{display:none !important}   /* avertissement jugé inutile (arbitré 21.07) */
#reassur>span:first-child{display:none}


/* ==== HUB EPURE2 : sous-onglets soignes. (`.aide{display:none}` retire le 01.08 - voir la note
   « .aide EST MORT » plus haut : le texte ne se masque plus, il n'est plus ecrit.) ==== */
.seg.sub{margin:2px 0 20px}
.seg.sub button{margin-right:22px}


/* ==== HUB SECBAR : sous-onglets en barre secondaire collee sous la nav (Option C, 21.07). Reversible. ==== */
#secbar:empty{display:none}
#secbar{background:var(--bg);border-bottom:1px solid var(--line)}
#secbar .seg.sub{max-width:1560px;margin:0 auto;padding:0 26px;border-bottom:none;background:transparent}
#secbar .seg.sub button{padding:12px 0;margin-right:26px}
#secbar .seg.sub button.on{box-shadow:inset 0 -2px 0 var(--accent)}
/* LE MOIS DE L'ESPACE PLANIFIER, DEUXIEME LIGNE DE LA MEME BARRE (29.08, ronde 1). `.secbar-mois`
   n'avait AUCUNE regle : le span retombait sans le padding ni le centrage de `.seg.sub`, sur la
   marge nue de #secbar. Meme colonne (1560px, memes 26px de cote) pour que les deux lignes
   s'alignent, le picker aligne a droite - `.mpick` porte deja sa propre feuille, cette regle ne
   fait que le POSITIONNER. */
#secbar .secbar-mois{display:block;max-width:1560px;margin:0 auto;padding:8px 26px;text-align:right}


/* Le cartouche du mois (`.cmois`) a vécu une journée (29.07) : il faisait une QUATRIÈME grammaire
   pour « situer un mois », alors que `.cartouche` - la feuille partagée, servie au portail par
   /theme_cartouche.css - portait déjà exactement ce rôle, avec son modificateur `.seul` pour le cas
   sans feuille en dessous. Le bloc du mois est donc un `.cartouche.seul` sur les DEUX surfaces, et
   ses commandes vivent sur sa rangée `.fl`. `.planbar`, `.pl-sorties` et `.cmois*` partent avec.
   La bande « à diffuser » (`.pubstrip`) reste : elle porte une case qui CHANGE ce que fait le
   bouton, ce n'est pas une commande de plus sur une rangée. */
.pubstrip{display:flex;align-items:center;gap:18px;flex-wrap:wrap;border:1px solid var(--line);
  border-radius:var(--radius);background:var(--card);padding:12px 16px;margin:0 0 24px}
.pubstrip .ps-h{font-size:11px;font-family:var(--mono);text-transform:uppercase;
  letter-spacing:.06em;color:var(--ink-soft);width:100%;margin:0 0 2px}
#planmois .legend,#cadmois .legend{display:flex;flex-wrap:wrap;align-items:center;column-gap:18px;row-gap:6px;margin:0 0 8px}
.leg-indice{display:none}
@media (hover: hover){.leg-indice{display:inline;margin-left:auto;color:var(--ink-soft);padding-left:14px}}

/* Les interrupteurs de vue (Cadres, Modifs, À confirmer) : même contrôle sur la feuille et
   dans le cartouche, seules les encres suivent le support (cf. CARTOUCHE_CSS). */
.pl-toggles{display:inline-flex;align-items:center;gap:16px}
.pl-sw{display:inline-flex;align-items:center;gap:8px;cursor:pointer;font-size:12.5px;color:var(--ink);user-select:none}
/* LA CASE NE DISPARAÎT PAS, ELLE SE CACHE (29.07). `display:none` la retirait de l'ordre de
   tabulation ET de l'arbre d'accessibilité : « Cadres » et « Modifs », les deux seuls réglages de
   l'écran « Le planning », n'existaient que pour la souris - au clavier on ne pouvait pas les
   atteindre, et un lecteur d'écran ne les annonçait pas du tout. Masquage visuel classique : la
   case reste dans le flux, focalisable, et c'est le rail qui porte l'anneau de focus.
   ⚠ SÉLECTEUR QUALIFIÉ, ET C'EST NÉCESSAIRE : `input[type=checkbox]` impose 16×16 plus bas dans la
   feuille, à spécificité ÉGALE - un simple `.pl-sw input` perdait donc par l'ordre de source, et la
   case restait un carré de 16 px posé sur le rail. Mesuré, pas supposé. */
.pl-sw input[type=checkbox]{position:absolute;width:1px;height:1px;min-height:0;margin:0;padding:0;border:0;overflow:hidden;white-space:nowrap;clip-path:inset(50%)}
.pl-sw input:focus-visible + .pl-sw-t{outline:2px solid var(--accent);outline-offset:2px}
.pl-sw-t{width:34px;height:19px;border-radius:var(--radius);background:var(--line);position:relative;transition:.15s;flex:none}
/* ⚠⚠ LE BOUTON DE L'INTERRUPTEUR SUIVAIT SON RAIL (29.07). Il était peint `#fff` EN DUR pendant
   que le rail, lui, bascule entre deux tokens : `--line` éteint, `--ink` allumé. Un seul des deux
   états était donc lisible par thème - et c'était l'état OPPOSÉ d'un thème à l'autre. MESURÉ,
   bouton contre rail :
        clair  ALLUMÉ 17,40  ·  clair  ÉTEINT  1,24
        sombre ALLUMÉ  1,16  ·  sombre ÉTEINT 13,75
   En sombre, « allumé » était un bouton blanc sur un rail blanc : les deux interrupteurs de
   « Le planning » se lisaient comme deux pastilles vides, et on ne pouvait pas savoir si « Cadres »
   ou « Modifs » était actif. C'est la règle de `DESIGN.md` mot pour mot - « une couleur en dur, et
   le thème sombre ment » - et l'état ne tenait plus qu'à la POSITION du bouton.
   Le bouton bascule donc AVEC le rail : encre atténuée sur rail pâle, surface sur rail d'encre.
   Les quatre combinaisons passent 4:1 (seuil WCAG 1.4.11 pour un composant : 3:1).
   L'ombre portée du bouton part avec : elle compensait un bouton qu'on ne voyait pas, et un
   système plat ne pose pas d'ombre sur un objet de 15 px. */
.pl-sw-t::after{content:"";position:absolute;top:2px;left:2px;width:15px;height:15px;border-radius:50%;background:var(--ink-soft);transition:.15s}
/* L'interrupteur allumé passe à l'ENCRE, pas à l'accent (29.07) : « allumé » est un état binaire,
   pas une désignation. L'ardoise est réservée à ce qui DÉSIGNE - l'onglet courant, l'action
   principale, le focus. Et l'encre pleine se distingue mieux du rail éteint que l'ardoise ne le
   faisait, dans les deux thèmes. */
.pl-sw input:checked + .pl-sw-t{background:var(--ink)}
.pl-sw input:checked + .pl-sw-t::after{left:17px;background:var(--card)}
.pl-sw-l{font-weight:500}
.grille-sem{width:100%;table-layout:fixed;border-collapse:separate;border-spacing:0;background:var(--card);border:1px solid var(--line);border-radius:var(--radius);overflow:hidden}
.grille-sem col.poste{width:120px}
.grille-sem th,.grille-sem td{border-right:1px solid var(--line);border-bottom:1px solid var(--line)}
.grille-sem th:last-child,.grille-sem td:last-child{border-right:none}
.grille-sem tr:last-child td{border-bottom:none}
.grille-sem thead th{padding:10px 8px;text-align:center;vertical-align:middle;height:50px}
.grille-sem thead th .dw{font-family:var(--display);font-weight:700;font-size:13px;letter-spacing:-.01em}
/* PLANCHER À 11 PX DANS LA GRILLE DE SEMAINE (29.07). Ces trois-là n'étaient pas des marginalia :
   `.mo` est le MOIS de chaque colonne, `.h` sont les HEURES du poste (07–19, 19–07, 22–07), et
   `.was` est l'ANCIENNE VALEUR d'une garde modifiée - l'information la plus conséquente de la
   vue, écrite plus petit que tout le reste. `DESIGN.md` porte la règle depuis toujours ; elle
   était fausse au cœur de l'écran quotidien. La densité se rattrape par la casse et la chasse
   fixe de la monospace, pas par la taille. */
.grille-sem thead th .mo{font-family:var(--mono);font-size:11px;color:var(--ink-soft);text-transform:uppercase;letter-spacing:.06em;margin-top:1px}
.grille-sem thead th.wknd .dw{color:var(--ink-soft)}
.grille-sem thead th.today{box-shadow:inset 0 -2px 0 var(--accent)}
.grille-sem thead th .crit{color:var(--red-ink);font-weight:700}
.grille-sem td.poste{padding:0 12px;vertical-align:middle;text-align:left;height:64px;background:var(--card)}
.grille-sem td.poste .p{font-family:var(--display);font-weight:700;font-size:12px;text-transform:uppercase}
.grille-sem td.poste .h{font-family:var(--mono);font-size:11px;color:var(--ink-soft);margin-top:1px}
.grille-sem td.cell{height:64px;padding:6px;vertical-align:middle;text-align:center;position:relative}
.grille-sem td.cell.wknd{background:var(--hover)}
.grille-sem td.cell.out{background:var(--bg)}
.grille-sem tr.cadrerow td.cell{height:auto;padding:7px 6px;background:var(--bg)}
.grille-sem .cadretag{font-size:11px;line-height:1.3;color:var(--ink-soft)}
.grille-sem .pill{display:inline-flex;align-items:center;justify-content:center;font-size:12.5px;font-weight:500;color:var(--ink);background:var(--chip-bg);border:1px solid var(--line);border-radius:var(--radius-s);padding:5px 10px;line-height:1.2;max-width:100%}
.grille-sem .pill+.pill{margin-top:4px}
.grille-sem .pill .nm{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.grille-sem .pill.heli{background:var(--card);border-color:var(--accent);color:var(--ink);font-weight:600}
.grille-sem .pill.mod{border-color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent)}
.grille-sem td.cell.mod{cursor:help}
.grille-sem td.cell.mod::after{content:"";position:absolute;top:5px;right:5px;width:0;height:0;border-top:9px solid var(--accent);border-left:9px solid transparent}
.grille-sem .was{display:block;font-family:var(--mono);font-size:11px;color:var(--ink-soft);text-decoration:line-through;margin-top:2px}
.grille-sem .empty{color:var(--ink-soft);font-size:12px;font-family:var(--mono)}
.grille-sem .pill.piq{background:transparent;border-style:dashed;color:var(--ink-soft);font-weight:500}
.grille-sem tr.piqrow td.poste .p{color:var(--ink-soft)}
@media(max-width:880px){.grille-sem{font-size:11px}.grille-sem col.poste{width:76px}.grille-sem td.cell,.grille-sem td.poste{height:52px}}
/* sélecteur de mois (pilule + flèches) */
.mpick{display:inline-flex;align-items:center;gap:4px;background:var(--card);border:1px solid var(--line);border-radius:var(--radius);padding:3px 5px;height:36px;vertical-align:middle}
.mpick .arrow{border:none;background:none;font-size:15px;line-height:1;color:var(--ink);width:28px;height:28px;border-radius:var(--radius-s);cursor:pointer;flex:none;display:inline-flex;align-items:center;justify-content:center;padding:0}
.mpick .arrow:hover{background:var(--hover);color:var(--ink)}
.mpick select{font-family:var(--display);font-weight:700;font-size:13px;color:var(--ink);text-align:center;min-width:116px;letter-spacing:-.01em}
/* Même pilule, sans `<select>` : la navigation par semaine de l'éditeur. Elle portait des boutons
   `.go.gris.sm` - une TROISIÈME grammaire pour « choisir une période » sur deux onglets voisins.
   ⚠ Ici les flèches sont TABULABLES, contrairement à celles du sélecteur de mois : là-bas elles
   doublent les touches fléchées natives du `<select>`, ici elles sont le seul moyen de bouger. */
.mpick .lbl{font-family:var(--display);font-weight:700;font-size:13px;color:var(--ink);text-align:center;min-width:148px;letter-spacing:-.01em;padding:0 4px}
/* ══ BARRE DE « Éditer le planning » (29.07) : UNE barre, deux moitiés qui répondent à deux
   questions. À GAUCHE « qu'est-ce que je regarde » (mois, semaine, construction à la main), à
   DROITE « qu'est-ce que je fais » (piquet, recharger, qui prévenir, état, Enregistrer).
   AVANT : deux rangées de contrôles empilées, sans principe de partage - le mois dans la première,
   la semaine dans la seconde -, et l'action DESTRUCTIVE (« Annuler la dernière publication »)
   posée en rouge au bout de la première, au-dessus de la grille, seule tache de couleur de l'écran
   et donc première chose que l'œil accrochait. Elle est descendue au pied, après la grille. ══ */
.edbar{display:flex;align-items:center;gap:16px;flex-wrap:wrap;margin:6px 0 16px;padding-bottom:14px;border-bottom:1px solid var(--line)}
.edbar-l,.edbar-r{display:inline-flex;align-items:center;gap:10px;flex-wrap:wrap;min-width:0}
.edbar-r{margin-left:auto}
/* L'ÉTAT ET LE BOUTON SONT UN SEUL OBJET, jamais deux : « Aucune modification. » est la réponse à
   la question que pose Enregistrer. Séparés par le retour à la ligne, à 375 px, ils tombaient sur
   deux rangs différents et le bouton perdait sa légende. Ils passent à la ligne ENSEMBLE. */
.edbar .edsave{display:inline-flex;align-items:center;gap:10px;flex-wrap:nowrap;min-width:0}
.edbar .edstate{font-size:12px;color:var(--ink-soft);text-align:right}
.mpick select{-webkit-appearance:none!important;appearance:none!important;border:0!important;background:transparent!important;box-shadow:none!important;padding:0 4px!important;margin:0;cursor:pointer;text-align-last:center;height:28px;line-height:28px}
/* L'ANNEAU DE FOCUS VA SUR L'ENVELOPPE, PAS SUR LE `<select>` (29.07).
   CE QUI ÉTAIT FAUX : le `<select>` du sélecteur de mois était focalisable au clavier et
   STRICTEMENT INVISIBLE quand il l'était - mesuré par sonde comparative, repos et focus rendaient
   des styles IDENTIQUES. La règle générique `select:focus` pose bien bordure + anneau, mais la
   ligne au-dessus la désarme avec `box-shadow:none!important` (nécessaire : le select doit
   disparaître DANS la pilule). Au clavier, le curseur s'évanouissait donc sur le contrôle de
   navigation principal de Générer, Le planning et Éditer le planning. WCAG 2.4.7.
   ⚠ L'anneau ne peut PAS revenir sur le select - le `!important` le lui interdit - d'où
   `:focus-within` sur la pilule, qui est de toute façon la forme que l'œil voit.
   LES FLÈCHES GARDENT `tabindex="-1"`, et c'est DÉLIBÉRÉ : elles ne font que ce que les touches
   fléchées font déjà nativement sur un `<select>` focalisé. Les rendre tabulables ajouterait deux
   arrêts redondants par sélecteur, plusieurs fois par écran, sans rien rendre d'accessible qui ne
   le soit déjà. */
.mpick select:focus{outline:none}
.mpick:focus-within{border-color:var(--accent);box-shadow:var(--anneau)}

/* ===== BANDEAU DU HAUT PARTAGÉ (console + portail médecin) - voir barre_haut() ============
   Barre SOMBRE unique (fond --side-1) en clair ET sombre : logo, nav, recherche, interrupteur
   Gestionnaire⇄Médecin, thème, déconnexion, avatar. UN seul composant → switch invisible. */
/* ══ LE LIEN D'ÉVITEMENT (05.08) ═══════════════════════════════════════════════════════════════
   WCAG 2.4.1 « Contourner des blocs », niveau A - et ce n'est pas une case à cocher ici : avant
   d'atteindre le contenu, un clavier traverse la marque, SIX espaces, la recherche, la bascule de
   rôle, le thème, la déconnexion, l'avatar, PUIS la barre de famille - jusqu'à une vingtaine
   d'arrêts, à CHAQUE changement d'écran. C'est le motif exact que cette règle vise.
   Il ne se voit qu'au clavier : `top:-60px` le sort de l'écran, `:focus` le ramène. Jamais
   `display:none` ni `visibility:hidden` - l'un et l'autre le retireraient de l'ordre de
   tabulation, c'est-à-dire du seul public à qui il sert.
   Il se pose sur la charpente, donc il prend ses encres (fond sombre, écriture blanche) et son
   anneau de focus. */
.evite{position:fixed;top:-60px;left:12px;z-index:100;background:var(--side-1);color:var(--cartouche-ink);
  border:1px solid var(--accent);border-radius:var(--radius);padding:11px 16px;font-size:13px;
  font-weight:600;text-decoration:none;transition:top .12s}
.evite:focus{top:10px;box-shadow:var(--anneau)}
@media (pointer: coarse){.evite{min-height:44px;min-width:44px;display:inline-flex;align-items:center}}
.hb{position:sticky;top:0;z-index:45;display:flex;align-items:center;gap:6px;height:52px;padding:0 14px;
  background:var(--side-1);border-bottom:1px solid var(--side-line);color:var(--side-ink)}
/* LE BANDEAU « DÉMONSTRATION » (02.08). Posé au-dessus de la barre quand Reglages/DEMO.txt
   existe - donc JAMAIS en production, où le fichier n'est pas là. Il porte l'EXERGUE pâle
   (`--ex-pale` / `--ex-ink`), les deux seuls jetons de la palette qui ne peignent ni une
   commande ni un signal métier : la règle K dit que ce qui est coloré se clique, et un
   bandeau ne se clique pas. Il ne colle pas au défilement (la barre, elle, oui) : un rappel
   permanent qu'on ne peut plus lire cesse d'être lu - et c'est EXACTEMENT ce qui s'est passé
   au premier essai : posé en flux simple, il disparaissait dès la connexion, parce que l'écran
   d'accès fait une hauteur de fenêtre pleine et que les 30 px du bandeau la faisaient défiler
   d'autant. Sur LE seul écran que tout évaluateur traverse, le rappel ne tenait pas trois
   secondes. Il est donc COLLANT, et la barre du haut décale son propre ancrage de la même
   hauteur - lue, jamais recopiée. */
.hb-demo{position:sticky;top:0;z-index:60;display:flex;align-items:center;justify-content:center;
  gap:8px;min-height:var(--demo-h);padding:6px 14px;background:var(--ex-pale);color:var(--ex-ink);
  font-size:12px;font-weight:600;letter-spacing:.2px;text-align:center}
.hb-demo b{font-weight:700}
.hb-demo ~ .hb{top:var(--demo-h)}
.hb-brand{display:flex;align-items:center;gap:9px;padding-right:6px;flex:none}
.hb-logo{width:28px;height:28px;border-radius:var(--radius-xs);background:transparent;border:1px solid var(--side-line);display:grid;place-items:center;color:var(--cartouche-ink);font-family:var(--display);font-weight:600;font-size:14px}
/* LE NOM DU PRODUIT PREND LA VOIX DU DISPLAY (17.08). Sans `font-family`, il héritait de la
   sans-serif du corps : le spécimen validé montrait la serif, les écrans rendaient de l'Archivo -
   une maquette qui ne dit pas la même chose que le produit ne prouve rien. */
.hb-brand b{font-family:var(--display);color:var(--cartouche-ink);font-size:14px;font-weight:600;letter-spacing:-.2px;white-space:nowrap}
/* « LE MOT ET SON SERVICE » (17.08, verrouillage retenu par Rémy sur la maquette du même jour).
   Le nom du produit prend la voix du display ; le SERVICE reste, juste à côté, dans la voix meta -
   il ne disparaît pas sous la marque, c'est lui que le médecin cherche à 3 h du matin.
   Le filet vertical n'est pas un ornement : c'est CELUI DE `.tb-meta` (01.08), qui sépare déjà
   deux mentions de même rang sur la ligne de cartouche. Aucune graduation nouvelle à apprendre.
   ⚠ IL DISPARAÎT SOUS 620 px, et le nom RESTE - c'est la règle du 29.07 (« sous 860 px, le
   produit garde son nom »), appliquée ici au même rang de priorité : ce qu'on sacrifie en
   premier est le contexte, jamais l'identité.
   ⚠ ENCRE : `--cartouche-soft`, l'atténuée de la charpente. PAS l'accent - la marque ne se
   clique pas, et l'accent ne peint que ce qui se clique (invariant K et sa réciproque, 02.08).
   ⚠ DEUX PORTEURS, UNE SEULE RÈGLE, ET J'AI FAILLI N'EN SERVIR QU'UN. Écrite d'abord pour le
   seul `.hb-brand`, elle laissait le `.svc` de l'ÉCRAN D'ACCÈS (`.lg2-top`) sans aucun style :
   mesuré au navigateur, « SMUR · BSC » s'y rendait en Archivo 14 px, blanc plein, COLLÉ au nom
   et sans filet - nom et service soudés en une seule voix, sur le seul écran que tout le monde
   traverse. Le verrouillage n'existait que dans la moitié des endroits où il est censé vivre.
   C'est la leçon de K, prise encore une fois : l'instrument (ici le sélecteur) était juste et
   regardait au mauvais endroit. La règle nomme donc SES DEUX PORTEURS.
   ⚠ Et cette prose n'ÉPELLE pas le nom du produit : `test_identite_produit` (I1) lit les chaînes
   littérales de tous les .py, et `THEME_CSS` en est une. Il a rougi ici, sur un commentaire -
   il avait raison de ne pas savoir distinguer un commentaire CSS d'une valeur : lui apprendre
   l'exception aurait ouvert la porte au vrai nom écrit en dur dans une feuille de style. */
.hb-brand .svc,.lg2-top .svc{font-family:var(--mono);font-size:11px;font-weight:600;
  letter-spacing:.06em;text-transform:uppercase;color:var(--cartouche-soft);white-space:nowrap;
  padding-left:9px;border-left:1px solid var(--side-line)}
.hb-brand .svc{margin-left:2px}
.lg2-top .svc{margin-left:-3px}   /* le gap de 12 px de `.lg2-top` s'ajoute au retrait du filet */
@media (max-width:620px){.hb-brand .svc,.lg2-top .svc{display:none}}
.hb-nav{display:flex;align-items:center;gap:2px;flex:1 1 auto;min-width:0;overflow-x:auto;scrollbar-width:none}
.hb-nav::-webkit-scrollbar{display:none}
/* ── LA NAVIGATION N'EST PAS UNE ACTION (02.08) ─────────────────────────────────────────────
   Signalé TROIS FOIS par Rémy, au même endroit : rails de la charpente, puis sous-onglets.
   J'avais appliqué « si c'est coloré, ça se clique » à la lettre - et une barre de navigation
   EST cliquable, donc je l'avais colorée. Compté à l'écran : **12 textes teal, dont 9 de
   navigation**. La carte était devenue la chose la plus bruyante de la page, et en thème
   sombre le cyan sur quasi-noir amplifie tout.
   Ce que la règle ne disait pas et qu'il faut écrire : une navigation répond à OÙ SUIS-JE,
   pas à QUE PUIS-JE FAIRE. Son état se dit déjà par le filet de 2 px - `DESIGN.md`, section
   Navigation, « UNE SEULE GRAMMAIRE, partout », posée au reset du 29.07 - et la demande de
   Rémy disait dans la même phrase « utilise les couleurs avec PARCIMONIE ». Colorer neuf
   repères de carte, c'était tenir la moitié du brief contre l'autre.
   L'accent reste donc à ce qui AGIT : boutons, liens, créneaux tapables, dans la feuille.
   Trois ou quatre par écran au lieu de douze. */
.hb-link{display:inline-flex;align-items:center;gap:7px;padding:7px 12px;border-radius:var(--radius);color:var(--side-ink);
  font-size:13.5px;font-weight:500;text-decoration:none;white-space:nowrap;cursor:pointer;border:none;background:none;font-family:inherit;transition:.12s}
.hb-link svg{width:16px;height:16px;opacity:.85;flex:none}
.hb-link:hover{background:rgba(255,255,255,.06);color:var(--cartouche-ink)}
.hb-link.on{background:rgba(255,255,255,.11);color:var(--cartouche-ink)}
.hb-right{display:flex;align-items:center;gap:8px;flex:none;margin-left:auto}
.hb-search{display:inline-flex;align-items:center;gap:8px;background:rgba(255,255,255,.06);border:1px solid var(--side-line);
  border-radius:var(--radius);color:var(--side-ink-soft);font-size:13px;padding:7px 11px;cursor:pointer;white-space:nowrap;font-family:inherit}
.hb-search svg{width:14px;height:14px}
/* `opacity:.7` écrasait une encre déjà calculée pour ce fond : le ⌘K tombait à 3,80:1 alors que
   `--side-ink-soft` vaut 6,30:1 sur la charpente. Une encre mesurée ne se re-voile pas. */
.hb-search kbd{font-size:11px;font-family:inherit}

/* TAILLE DE BASE DES CASES ET DES RADIOS (29.07). Elle n'existait QUE dans le bloc tactile ; sous
   pointeur fin chaque case retombait sur le défaut du navigateur (~13 px), et les endroits qui
   voulaient mieux le posaient EN LIGNE - un style en ligne qui battait ensuite la règle tactile,
   laissant la case à 16 px au doigt. La taille se déclare ici, la règle tactile la relève, et
   plus personne n'a besoin d'écrire un style en ligne. */
input[type=checkbox],input[type=radio]{width:16px;height:16px}
.hb-role{display:inline-flex;gap:3px;padding:3px;background:rgba(255,255,255,.06);border-radius:var(--radius);flex:none}
.hb-role a{display:inline-flex;align-items:center;gap:6px;padding:6px 11px;border-radius:var(--radius);font-size:13px;font-weight:550;
  color:var(--side-ink-soft);text-decoration:none;white-space:nowrap}
.hb-role a svg{width:14px;height:14px}
.hb-role a.on{background:transparent;color:var(--cartouche-ink);font-weight:600;box-shadow:inset 0 -2px 0 var(--accent)}
.hb-role a:hover:not(.on){background:rgba(255,255,255,.07);color:var(--cartouche-ink)}
.hb-sw{width:44px;height:24px;border-radius:var(--radius);border:0;background:rgba(255,255,255,.12);position:relative;cursor:pointer;flex:none}
.hb-sw i{position:absolute;top:3px;left:3px;width:18px;height:18px;border-radius:50%;background:#fff;transition:.18s;display:grid;place-items:center}
.hb-sw i svg{width:11px;height:11px;stroke:var(--side-1);fill:none;stroke-width:2}
[data-theme="dark"] .hb-sw i{left:23px}
/* 24 px MÊME SOUS POINTEUR FIN (WCAG 2.2 AA 2.5.8) - le plancher que ce projet s'applique déjà
   à `.dtag button` et `.dqp button`. La déconnexion mesurait 19,4 px de haut : c'est la SEULE
   sortie du portail au téléphone, et elle passait sous le minimum à tous les pointeurs. La
   taille tactile (44 px), elle, reste dans la requête du pointeur - c'est une autre question. */
.hb-logout{font-size:12.5px;color:var(--side-ink-soft);text-decoration:none;white-space:nowrap;
  display:inline-flex;align-items:center;min-height:24px}
.hb-logout:hover{color:var(--cartouche-ink)}
.hb-av{width:30px;height:30px;border-radius:50%;background:rgba(255,255,255,.10);display:grid;place-items:center;color:#e7e8ee;font-weight:600;font-size:12px;flex:none;
  /* Devenu un vrai bouton le 19.08 : un `button` n'hérite ni de la police ni de la taille du
     bandeau, et il arrive avec une bordure et un fond du système. On les reprend. */
  border:0;padding:0;font-family:inherit;cursor:pointer;-webkit-appearance:none;appearance:none}
.hb-av:hover{background:rgba(255,255,255,.18)}
/* LE MENU DE COMPTE (19.08). Il n'existait que dans `portail_med.html` : la console n'avait aucun
   menu, et l'avatar y menait DIRECTEMENT à la déconnexion - un clic de trop et la session tombait,
   sans confirmation ni retour. Un avatar ouvre un menu, il n'exécute pas une action irréversible.
   Les valeurs reprennent celles du portail, qui passent déjà la charte. */
.hb-mw{position:relative;display:flex}
.hb-menu{position:absolute;top:calc(100% + 8px);right:0;min-width:232px;background:var(--card);
  border:1px solid var(--line);border-radius:var(--radius);box-shadow:0 12px 34px rgba(9,10,22,.28);
  padding:6px;z-index:60;display:none;flex-direction:column}
.hb-menu.open{display:flex}
.hb-menu .hb-qui{padding:9px 12px 10px;border-bottom:1px solid var(--line);margin-bottom:5px}
.hb-menu .hb-qui b{display:block;font-size:13.5px;color:var(--ink)}
.hb-menu .hb-qui span{font-size:11.5px;color:var(--ink-soft)}
.hb-menu a,.hb-menu button{width:100%;text-align:left;background:none;border:none;color:var(--ink);
  font:inherit;font-size:13.5px;padding:9px 12px;border-radius:var(--radius);cursor:pointer;
  text-decoration:none;display:block}
.hb-menu a:hover,.hb-menu button:hover{background:var(--hover)}
.hb-menu .rouge{color:var(--red-ink)}
@media (pointer: coarse){.hb-menu a,.hb-menu button{min-height:44px;min-width:44px}}
.hb-burger{display:none;background:none;border:1px solid var(--side-line);color:var(--side-ink);border-radius:var(--radius);font-size:19px;padding:2px 9px;cursor:pointer}
/* L'ÉTIQUETTE DU LIEN PRENAIT L'ENCRE DE LA PAGE (28.07). `.lbl` est stylé ailleurs par
   `.lbl,label{color:var(--ink-soft)}` - le jeton du CORPS du document. Or ce lien vit sur la
   charpente SOMBRE, qui ne suit pas le thème : en thème clair `--ink-soft` est un gris foncé, et
   les libellés de navigation tombaient à 3,4:1 sur du noir (2,45:1 sur l'onglet actif). Le
   libellé doit suivre la couleur de SON lien - il hérite, et suit donc l'état actif et le survol. */
.hb-link .lbl{color:inherit}
@media(max-width:900px){.hb-search,.hb-logout{display:none}}
/* ⚠ IL MANQUAIT LA MOITIÉ DE L'INTERVALLE - 680 À 1420 px (05.08, mesuré au navigateur).
   Le repli de 680 px ci-dessous est juste, et celui de 900 px au-dessus aussi ; entre les deux
   et jusqu'à **1420 px**, RIEN. Or la barre a besoin de 1420 px pour tenir : marque 122 +
   navigation 730 + partie droite 548, plus les gouttières. Et de ces trois boîtes, `.hb-nav`
   est la SEULE à porter `flex:1 1 auto` - elle absorbe donc 100 % du déficit. Mesuré à
   1280 × 800, le format d'ordinateur portable le plus courant : `.hb-nav` reçoit 570 px pour
   603 px de contenu, et « Système » s'affiche « Systè », coupé net sous la zone de recherche.
   `scrollbar-width:none` ne laisse aucun indice qu'il y a une suite - exactement le défaut
   diagnostiqué à 375 px le 29.07, à un autre endroit de la même barre.
   CE QUI CÈDE, ET DANS QUEL ORDRE. La navigation répond à OÙ SUIS-JE : c'est la dernière chose
   qui rétrécit, pas la première. On rend donc la partie DROITE compressible, du moins utile au
   plus utile - le libellé de la recherche (elle garde son icône, son raccourci ⌘K et son
   `title`), puis les deux libellés de la bascule de rôle (elles gardent leur icône, leur
   `aria-label` et le filet de 2 px qui dit laquelle est active - c'est le repli déjà employé
   sous 680 px, et les `aria-label` avaient été posés POUR lui).
   ⚠ LA DÉCONNEXION NE CÈDE PAS, ET C'EST DÉLIBÉRÉ. Elle est masquée dès 900 px, ce que la note
   d'en dessous décrit comme ayant laissé le portail « sans aucune sortie » - l'avatar est un
   `div`, pas un lien. Reprendre ce geste entre 900 et 1240 px rejouerait exactement ce défaut ;
   les libellés de rôle rendent la même place sans rien retirer.
   ⚠ MASQUÉ VISUELLEMENT, JAMAIS `display:none` : c'est la leçon écrite quinze lignes plus bas.
   Le bouton garde son nom accessible « Rechercher… ⌘K » ; seul l'œil perd le texte. */
@media(min-width:901px) and (max-width:1419px){
  .hb-search span,.hb-search kbd{position:absolute;width:1px;height:1px;padding:0;margin:-1px;
    overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}
  .hb-search{gap:0;padding-left:9px;padding-right:9px}
}
@media(min-width:901px) and (max-width:1279px){
  .hb-role a span{position:absolute;width:1px;height:1px;padding:0;margin:-1px;
    overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}
  .hb-role a{gap:0}
}
/* SOUS 680 px, LA BARRE PASSE EN DEUX RANGS (29.07). Mesuré avant d'écrire : à 375 px la boîte
   `.hb-nav` faisait 130 px pour 208 px de liens - « Comprendre » et « Système » étaient COUPÉS,
   et `scrollbar-width:none` ne laissait aucun indice qu'il y avait une suite. Deux espaces sur
   cinq, dont l'équité qui justifie une décision d'échange, étaient inatteignables au téléphone.
   Élargir la barre ne suffisait pas : le rang porte aussi la marque, la bascule de rôle, le
   thème et l'avatar. On donne donc au champ de navigation UN RANG À LUI - 296 px utiles même à
   320 px, pour 188 px de liens : plus rien à faire défiler, donc plus rien à cacher.
   ⚠ `display:none` sur le libellé le retirait AUSSI de l'arbre d'accessibilité : les cinq
   boutons n'avaient plus aucun nom. Le libellé est désormais masqué VISUELLEMENT et reste LU. */
@media(max-width:680px){
  .hb{flex-wrap:wrap;height:auto;padding:0 12px 8px}
  .hb-brand,.hb-right{height:52px}
  .hb-nav{order:3;flex:1 0 100%;justify-content:space-between;gap:2px}
  .hb-link{padding:7px 10px}
  .hb-brand b{display:none}
  /* La déconnexion réapparaît : le rang 1 est libéré de la navigation, elle y tient. Elle était
     masquée dès 900 px et le portail n'avait alors PLUS AUCUNE sortie sur téléphone - l'avatar
     est un `div`, pas un lien. */
  .hb-logout{display:inline}
  .hb-role a span,.hb-link .lbl{position:absolute;width:1px;height:1px;padding:0;margin:-1px;
    overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}
}

/* ══ LA TRANCHE 681–1150 PX N'AVAIT JAMAIS ÉTÉ TRAITÉE (06.08) ═════════════════════════════════
   Le repli ci-dessus règle le téléphone, et un autre correctif avait traité 901–1419 px. Entre
   les deux, la barre restait sur UN rang partagé avec la marque, la bascule de rôle, le thème et
   l'avatar. Mesuré : à 1024 px (iPad paysage) « Système » sortait de l'écran ; à 880 px,
   `.hb-nav` affichait 635 px de liens dans 399 px utiles - **236 px cachés**, sans le moindre
   indice puisque `scrollbar-width:none`. Trois espaces sur six, dont « Comprendre » qui porte
   l'Équité.
   ⚠ TROISIÈME OCCURRENCE DU MÊME DÉFAUT, sur la même barre, à une largeur qu'on n'avait pas
   mesurée. Corriger une tranche ne corrige pas la classe : c'est pourquoi un invariant balaie
   désormais SIX largeurs (voir `check_charte`, invariant O).
   Ici on ne masque aucun libellé - il y a la place pour deux rangs entiers. */
@media(min-width:681px) and (max-width:1150px){
  .hb{flex-wrap:wrap;height:auto;padding-bottom:6px}
  .hb-brand,.hb-right{height:52px}
  .hb-nav{order:3;flex:1 0 100%;gap:2px;overflow-x:visible}
}

/* ══ ET QUAND UNE BARRE DÉBORDE MALGRÉ TOUT, ELLE LE DIT ═══════════════════════════════════════
   Une bande qui défile sans glissière est indiscernable d'une bande complète. La classe `.ovf`
   est posée par `navDebordement()` (console_app.js) sur toute barre dont le contenu dépasse, et
   RETIRÉE dès qu'il tient : le voile ne ment donc jamais dans un sens ni dans l'autre.
   Un dégradé, pas un bouton : il ne prétend pas être cliquable, il montre que ça continue. */
/* ⚠ La couleur d'un masque ne PEINT rien : seule son alpha compte. J'avais écrit `#000`, et
   l'invariant L l'a signalé - à raison sur la lettre. Un opaque quelconque fait le même masque,
   et la règle « jamais de noir pur » n'a pas à souffrir d'exception explicative. */
.hb-nav.ovf,.seg.sub.ovf{
  -webkit-mask-image:linear-gradient(to right,#fff calc(100% - 26px),transparent);
  mask-image:linear-gradient(to right,#fff calc(100% - 26px),transparent)}
.seg.sub.ovf{position:relative}
.seg.sub.ovf::after{content:"›";position:absolute;right:2px;top:50%;transform:translateY(-50%);
  font-size:15px;line-height:1;color:var(--ink-soft);pointer-events:none}

/* ════════════════════════════════════════════════════════════════════════════════════════════
   LE DOIGT - adaptation à l'appareil de pointage, pas à la largeur d'écran (28.07).

   CE QUI ÉTAIT FAUX : ce projet n'avait AUCUN `pointer:coarse` ni `hover:none`, nulle part.
   Toutes les adaptations passaient par `max-width`. Or la largeur ne dit pas comment on touche :
   un portable tactile est large ET se manipule au doigt, et l'audit a mesuré des cibles à 14,
   16 et 19 px - sous le minimum WCAG 2.2 AA (24 px) et très loin des 34 px de la charte.

   LA RÈGLE RETENUE : la DENSITÉ reste pour la souris - c'est un choix assumé du produit, un
   planning est un champ de données - et la TAILLE grandit pour le doigt. Rien ne change donc
   sous un pointeur fin ; tout atteint 44 px sous un pointeur grossier.

   ⚠ On agrandit par le PADDING et par des zones de frappe invisibles, jamais par la police :
   grossir le texte reflowerait des tableaux entiers et casserait la mesure des colonnes. ════ */
@media (pointer: coarse){
  /* contrôles ordinaires : la hauteur passe le seuil sans que la maquette bouge.
     31.07 - quatre contrôles rejoignent la liste, tous mesurés en échec : la BASCULE DE RÔLE et
     la DÉCONNEXION (elles vivent sur la charpente, donc sur les 34 écrans), le RÉSUMÉ d'un
     dépliant (`<details>` est le motif d'explication recommandé par DESIGN.md, et son résumé
     mesurait 19,4 px), et le SÉLECTEUR DE VUE de la bourse (`.bviewbtn`, 35 px - sa règle vit
     dans une interpolation JS, l'angle mort documenté de check_charte). */
  .go,.ui-btn,.subtab,.seg button,.seg.sub button,.hb-link,.mpick .arrow,.bviewbtn,.hb-search,
  input:not([type=checkbox]):not([type=radio]),select,textarea{min-height:44px}

  /* ⚠⚠ UNE CIBLE EST UNE SURFACE, PAS UNE HAUTEUR (31.07).
     Cette règle existait depuis le 28.07 et n'a JAMAIS porté que `min-height`. Un contrôle de
     36 × 44 px n'est pas une cible de 44 px : WCAG 2.5.8 demande un carré, et un pouce ne vise
     pas une lame. Mesuré sur les 34 écrans de la console, à 375 px, pointeur grossier :
       · `.hb-link` ....... 36 × 44 - les SIX espaces, sur TOUS les écrans
       · `.hb-role a` ..... 36 × 26 - la bascule Gestionnaire ⇄ Médecin, idem
       · `.hb-logout` ..... 73 × 19,4 - la seule sortie au téléphone
       · `.mpick .arrow` .. 28 × 44 - mois précédent / suivant, 5 écrans
       · `.seg.sub button`  41 × 44 - les sous-onglets
       · `.ui-btn` (✓) .... 34,9 × 44 · `.go` (↻) 31,8 × 44
     La hauteur avait été traitée parce qu'elle se voit ; la largeur ne se voyait pas, et aucun
     contrôle ne pouvait la contredire. C'est la même leçon que les invariants A à D de
     check_charte, à une dimension près - d'où l'invariant H, écrit en même temps que ce
     correctif : il REFUSE désormais qu'une règle tactile ne porte qu'une seule dimension.
     MESURÉ AVANT D'ÉCRIRE, à 375 px : `.hb-nav` offre 351 px utiles pour six liens à 44 px
     (274 px avec les gouttières), et le rang 1 garde 65 px libres pour les 16 px que réclame
     la bascule de rôle. Rien ne déborde ; c'est vérifié plus bas, pas supposé. */
  /* ⚠ `.hb-search` REJOINT CETTE LISTE (05.08). Il était dans le `min-height` juste au-dessus et
     PAS ici - sans conséquence tant que son libellé le tenait large. Le repli de 901–1419 px
     posé plus haut le réduit à son icône : mesuré à 34 px, il serait devenu une cible de
     34 × 44 sur un portable tactile, c'est-à-dire exactement la faute que l'invariant H a été
     écrit pour refuser. La liste d'à-côté était la moitié de la règle ; on ne prend pas le
     risque de refaire le même pas de côté. */
  .go,.ui-btn,.subtab,.seg button,.seg.sub button,.hb-link,.mpick .arrow,.bviewbtn,.hb-search,
  .hb-role a{min-width:44px}
  /* La bascule de rôle et la déconnexion sont des liens INLINE : `min-height` seul ne les touche
     pas (une boîte en ligne n'a pas de hauteur propre). Elles deviennent des boîtes. */
  .hb-role a{min-height:44px}
  .hb-logout{display:inline-flex;align-items:center;min-height:44px}
  /* Le résumé d'un dépliant garde sa PUCE - c'est elle qui dit qu'on peut l'ouvrir. On l'épaissit
     donc par le rembourrage, jamais en le passant en `flex` (ce qui retire le marqueur).
     ⚠ `details>summary` (0,0,2) BAT `summary` (0,0,1), et une requête média n'ajoute aucune
     spécificité : écrite seule, cette règle était annulée par le plancher de 24 px posé plus
     haut, et les résumés plafonnaient à 41,4 px. Mesuré, pas relu. Les deux sélecteurs sont
     donc nommés ensemble. */
  summary,details>summary{min-height:44px;box-sizing:border-box;padding-top:11px;padding-bottom:11px}
  /* ⚠ ET `min-height` NE PEUT RIEN SUR UNE BOÎTE EN LIGNE - troisième fois (01.08). C'était déjà
     la cause de `.hb-role a` (36×26) et de `.hb-logout` (73×19,4) le 31.07 ; c'est ici un
     `<summary style="display:inline">` (« légende des colonnes », onglet Équité), mesuré à 13 px
     au repos et à 35 px SOUS CETTE RÈGLE : le rembourrage gonfle la boîte peinte, il ne donne pas
     de hauteur à une ligne. La règle ci-dessus a donc l'air de s'appliquer, et un contrôle « à
     35 px » est plus trompeur qu'un contrôle à 13 : on le croit traité.
     Le résumé fautif est passé en `inline-block` à la source ; ce repli attrape les suivants.
     ⚠ `!important` est ICI une nécessité, pas une facilité : la déclaration visée est un style EN
     LIGNE, et une feuille ne bat un style en ligne d'aucune autre façon - sans lui, la règle
     serait écrite, lisible, et sans le moindre effet. Elle est bornée au pointeur grossier et à
     un `<summary>` qui se déclare en ligne : elle ne peut rien casser d'autre. */
  summary[style*="display:inline"]:not([style*="inline-block"]){display:inline-block !important}
  /* Contrôles nommés une seule fois dans leur écran, donc jamais entrés dans la liste ci-dessus :
     le segmenté des types d'absence, le déclencheur `.alink`, la puce-lien des archives et la
     pastille de filtre de la fiche 360. Mesurés à 26, 35, 28,6 et 27,4 px de haut. */
  #ab_typeseg button,.alink,a.chip,.chip2[onclick]{min-height:44px;min-width:44px}
  #ab_typeseg button,.alink{padding-top:10px;padding-bottom:10px}
  /* Les interrupteurs « Cadres » et « Modifs » de l'écran « Le planning » : la cible tactile est
     le LABEL entier (rail + mot), mesuré à 19 px. Le rail, lui, garde ses 34×19 - on grandit la
     surface de frappe, pas le dessin. */
  .pl-sw{min-height:44px}
  /* Le bouton du champ de fichier est un PSEUDO-ÉLÉMENT : donner 44 px au champ ne l'atteint pas.
     C'est pourtant lui qu'on vise au doigt - le champ, lui, n'est qu'une étiquette de nom. */
  .ui-file::file-selector-button{min-height:44px;padding:0 15px}
  /* `.act-link` porte déjà sa zone de frappe de 44 px à tous les pointeurs (voir sa définition) :
     lui imposer `min-height` ici décollerait son filet du texte, qui est précisément ce qui le
     rend lisible. On l'espace de ses voisins, on ne le grossit pas. */
  .act-link+.act-link{margin-left:14px}
  .go,.ui-btn,.subtab{padding-top:10px;padding-bottom:10px}
  /* les cases à cocher et les radios grossissent VRAIMENT : leur glyphe est la cible.
     22 → 24 px (31.07) : ce projet cite WCAG 2.5.8 comme son plancher ailleurs (« 24 px MÊME
     SOUS POINTEUR FIN », `.dtag button`, `.dqp button`), et 22 px passait dessous. Deux pixels
     qui ne se voient pas, mais une norme qu'on s'applique ou qu'on ne s'applique pas. */
  input[type=checkbox],input[type=radio]{width:24px;height:24px}
  /* espacer les actions voisines : deux cibles de 44 px collées se touchent quand même mal */
  .ligne .go+.go,.ui-btn+.ui-btn{margin-left:8px}
}

/* Zone de frappe invisible pour les glyphes trop petits pour être des cibles. Le signe garde sa
   taille - l'agrandir ferait une croix énorme au milieu d'un tableau - mais la surface tapable
   atteint 44 px. Vaut pour TOUS les pointeurs : viser un ✕ de 14 px à la souris n'est pas
   agréable non plus. */
.zone-frappe{position:relative}
.zone-frappe::after{content:"";position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  width:44px;height:44px}

/* Le sélecteur de thème est une bascule de 44×24 : la grandir en hauteur la déformerait. On lui
   donne sa zone de frappe au lieu de sa taille. */
@media (pointer: coarse){
  .hb-sw{position:relative}
  .hb-sw::before{content:"";position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
    width:48px;height:44px}
}

/* CE QUI SE RÉVÈLE AU SURVOL DOIT EXISTER SANS SURVOL. Un doigt ne survole pas : une action
   cachée derrière `:hover` n'est pas discrète sur un téléphone, elle est INTROUVABLE. */
@media (hover: none){
  [data-survol-seul]{opacity:1 !important}
}

/* ══════════════════════════════════════════════════════════════════════════════════════════
   REGISTRE DE FORMATION (onglet Formation, 30.07) - la grille marginale du reset du 29.07
   ------------------------------------------------------------------------------------------
   UN REGISTRE, PAS UN TABLEAU DE BORD. La question qu'on se pose en ouvrant cet écran est
   « qui arrive, et que doit-il encore faire ? » : la FILE mène, le registre suit, l'éditeur
   se convoque. Chaque ligne est une obligation datée - la date dans la MARGE en monospace,
   le nom en TITRE en serif. C'est la grammaire déjà retenue le 29.07 pour la carte de garde
   du portail, appliquée telle quelle : le contraste des GENRES tient la hiérarchie, aucun
   aplat n'est nécessaire.

   ⚠ CES RÈGLES VIVENT ICI, PAS DANS LE JS. L'ancien onglet composait son CSS dans des gabarits
   (`background:${TC[t]}`, `border:1px solid ${c}44`) : c'est l'angle mort DOCUMENTÉ de
   check_charte, donc du style non gardé - et `var(--accent)44` est du CSS INVALIDE, si bien que
   ces bordures ne s'affichaient pas du tout. Un seul auteur du monde visuel (invariant E).

   L'ACCENT NE PEINT RIEN ICI. Il souligne (`inset 0 -2px 0`) et il remplit la jauge - une
   quantité, pas un contrôle. Aucun `color:var(--ink)` : `--accent` est un APLAT, son encre
   posée est `--on-accent` (invariant B). Les états portent donc un MOT en plus de leur encre
   (règle du signal doublé), et le plancher de 11 px n'est franchi nulle part.
   ══════════════════════════════════════════════════════════════════════════════════════════ */

/* ---------- l'échéance : un fait, en monospace, sur filet ---------- */
.frm-echeance{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px 18px;
  font-family:var(--mono);font-size:12px;letter-spacing:.03em;color:var(--ink-soft);
  border:1px solid var(--line);border-radius:var(--radius);padding:11px 14px;margin-bottom:18px;
  background:var(--card)}
.frm-echeance b{color:var(--ink);font-weight:700}
.frm-echeance .frm-urgent{color:var(--red-ink);font-weight:700}

/* ---------- la file : une ligne = une obligation datée ---------- */
.frm-titre{font-family:var(--display);font-size:19px;font-weight:600;letter-spacing:-.01em;
  color:var(--ink);margin:26px 0 4px}
.frm-titre:first-child{margin-top:0}
.frm-sous{font-size:12px;color:var(--ink-soft);line-height:1.5;margin:0 0 12px;max-width:70ch}
.frm-file{border-top:1px solid var(--line)}
.frm-ligne{display:grid;grid-template-columns:minmax(0,124px) minmax(0,1fr);gap:0 18px;
  padding:13px 2px;border-bottom:1px solid var(--line);align-items:start}
.frm-ligne>*{min-width:0}
.frm-ligne:hover{background:var(--hover)}

/* la marge : la date et la fenêtre, en monospace - ce qui se MESURE */
.frm-marge{font-family:var(--mono);font-size:11px;line-height:1.45;letter-spacing:.03em;
  color:var(--ink-soft);padding-top:2px}
.frm-marge .frm-quand{display:block;color:var(--ink);font-weight:700}
.frm-marge .frm-bientot{color:var(--red-ink);font-weight:700}

/* le corps : le nom en serif, le rôle en accompagnement */
.frm-nom{font-family:var(--display);font-size:15px;font-weight:600;letter-spacing:-.01em;
  color:var(--ink);line-height:1.25;overflow-wrap:anywhere}
/* UN CRÉNEAU N'EST PAS UNE PERSONNE. Il n'a pas de nom parce qu'on ne lui en invente pas
   (« on ne devine jamais une identité ») : il se dit en italique et atténué, et le mot
   « créneau » l'accompagne toujours dans le balisage. */
.frm-nom.frm-creneau{font-style:italic;color:var(--ink-soft);font-weight:400}
.frm-role{font-size:12px;color:var(--ink-soft);line-height:1.45;margin-top:2px}
.frm-presume{font-family:var(--mono);font-size:11px;letter-spacing:.03em;color:var(--orange-ink)}

/* ---------- les phases : des jetons monospace, jamais des pastilles pleines ---------- */
.frm-phases{display:flex;flex-wrap:wrap;gap:5px;margin-top:7px}
.frm-phase{display:inline-flex;align-items:baseline;gap:5px;font-family:var(--mono);font-size:11px;
  font-weight:600;letter-spacing:.03em;border:1px solid var(--line);border-radius:var(--radius-xs);
  padding:3px 8px;color:var(--ink-soft);background:transparent}
.frm-phase .frm-duree{font-weight:400;color:var(--ink-soft)}
/* À FAIRE : encre pleine, graisse, et le filet de 2 px - la grammaire d'état du 29.07. */
.frm-phase.frm-due{color:var(--ink);border-color:var(--ink-soft);
  box-shadow:inset 0 -2px 0 var(--accent)}
.frm-phase.frm-prevue{color:var(--ink)}
.frm-phase.frm-faite{color:var(--green-ink);border-color:var(--line)}
.frm-phase.frm-ouverte{border-style:dashed}
.frm-rien{font-family:var(--mono);font-size:11px;letter-spacing:.03em;color:var(--green-ink)}

/* ---------- LA FILE ET LE REGISTRE SONT DES LISTES ----------
   Elles étaient des `div` empilées : un lecteur d'écran recevait quatorze lignes en flux
   indifférencié, sans jamais savoir qu'une nouvelle entrée commençait, ni combien il y en avait. */
.frm-file{list-style:none;margin:0;padding:0}
.frm-item{display:block}

/* ---------- L'ÉDITEUR CONVOQUÉ ----------
   Il naît SOUS la ligne qui l'a appelé, décalé dans la colonne du corps pour se lire comme
   appartenant à cette ligne - pas comme un panneau flottant. Un filet, pas une carte : on
   n'empile pas une carte dans une carte (DESIGN.md). */
/* ⚠ Le filet de gauche marque la ligne qui a convoqué l'éditeur - il reste à 1 px : « Don't poser
   une barre latérale colorée de plus de 1 px sur un bloc, une ligne ou une alerte » (DESIGN.md).
   Il était à 2 px, et aucun garde-fou ne l'aurait dit : check_charte surveille l'accent employé
   en APLAT, pas son épaisseur en filet. C'est le décalage à l'œil qui l'a montré. */
.frm-editeur{border:1px solid var(--line);border-left:1px solid var(--accent);
  border-radius:var(--radius);background:var(--card);padding:12px 14px;margin:0 0 14px 142px}
@media (max-width:620px){.frm-editeur{margin-left:0}}
/* Le bouton qui a convoqué l'éditeur porte l'état actif : soulignement, jamais aplat. */
.frm-ouvrir[aria-expanded="true"]{box-shadow:inset 0 -2px 0 var(--accent);font-weight:600}
.frm-session-hd{display:flex;flex-wrap:wrap;align-items:center;gap:8px 10px;margin-bottom:9px}
.frm-session-hd select{font-weight:600}

/* ---------- LE SÉLECTEUR DE NOMS ----------
   Il remplace un `datalist` branché sur un champ MULTI-VALEURS, où il ne pouvait pas fonctionner :
   le navigateur compare la valeur entière du champ, donc l'autocomplétion mourait après le
   premier nom. Replié par défaut - 63 options ouvertes seraient un mur. */
.frm-choisir{margin-top:6px}
.frm-choisir summary{cursor:pointer;font-family:var(--mono);font-size:11px;font-weight:600;
  letter-spacing:.06em;text-transform:uppercase;color:var(--ink);padding:4px 0}
.frm-choisir summary:hover{color:var(--ink)}
/* Le choix du premier jour et le rappel de la durée tiennent sur une ligne. */
.frm-jours{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px}
.frm-jours input[type=date]{min-width:0}

/* FILTRER PLUTÔT QUE FAIRE DÉFILER : 63 noms ouverts d'un bloc sont un mur. */
.frm-filtre{display:flex;align-items:center;gap:8px;margin:6px 0}
.frm-filtre input{flex:1;min-width:0;max-width:280px}
.frm-reste{margin-top:6px}
.frm-reste summary{cursor:pointer;font-family:var(--mono);font-size:11px;letter-spacing:.03em;
  color:var(--ink);padding:3px 0}
.frm-reste summary:hover{color:var(--ink)}
/* Les soldés se rangent derrière un repli : la file ne garde que ce qui appelle une action. */
.frm-ajour{margin-top:8px}
.frm-ajour summary{cursor:pointer;font-family:var(--mono);font-size:11px;font-weight:600;
  letter-spacing:.06em;text-transform:uppercase;color:var(--ink);padding:6px 0}
.frm-ajour summary:hover{color:var(--ink)}
/* UN ACQUIS N'EST PAS UNE SESSION TENUE. Même effet sur la dette, autre nature de preuve : il
   porte le mot « acquis » en plus de son ✓, et son encre reste celle du texte accompagnant. */
.frm-phase.frm-acquis{color:var(--green-ink);border-style:dashed;border-color:var(--line)}
/* Action secondaire posée dans une ligne de la file : elle ne doit peser ni plus que le nom
   qu'elle accompagne, ni moins qu'un bouton atteignable. */
.frm-mini{font-size:11px;padding:2px 8px;margin-left:6px}
.frm-choix-grp{display:flex;flex-wrap:wrap;gap:5px;margin:6px 0}
.frm-choix{font-family:var(--mono);font-size:11px;font-weight:600;letter-spacing:.03em;
  border:1px solid var(--line);border-radius:var(--radius-xs);padding:3px 8px;
  background:transparent;color:var(--ink);cursor:pointer}
.frm-choix:hover{background:var(--hover);color:var(--ink)}
/* CHOISI se dit par l'encre, la graisse et le filet de 2 px - plus le ✓ que pose le balisage
   (règle du signal doublé : en niveaux de gris, la coche reste). Jamais un aplat. */
.frm-choix.frm-choisi{color:var(--ink);border-color:var(--ink-soft);
  box-shadow:inset 0 -2px 0 var(--accent)}
/* ABSENT : l'encre d'alarme et le ✕ que pose le balisage. Pas d'aplat - et surtout pas la seule
   couleur : en niveaux de gris, c'est le ✕ contre le ✓ qui sépare les deux états. Le troisième
   état - PAS ENCORE TRANCHÉ - est l'absence des deux, et il ne se peint pas. */
.frm-choix.frm-absent{color:var(--red-ink);border-color:var(--red-ink)}
.frm-choix .frm-duree{margin-left:5px;font-weight:400}
.frm-champ{display:grid;grid-template-columns:minmax(0,124px) minmax(0,1fr);gap:6px 18px;
  align-items:center;margin:7px 0}
.frm-champ>*{min-width:0}
.frm-champ label{font-family:var(--mono);font-size:11px;font-weight:600;letter-spacing:.06em;
  text-transform:uppercase;color:var(--ink-soft);margin:0}
.frm-champ input{width:100%}
.frm-vide{font-size:12.5px;color:var(--ink-soft);line-height:1.55;border:1px dashed var(--line);
  border-radius:var(--radius);padding:14px 16px;max-width:70ch}

/* ---------- F SMUR 3 : LA SESSION AGILE, ET PLUS DE JAUGE ----------
   La barre mesurait « inscrits / éligibles » : une promesse de 100 % posée sur une formation
   OBLIGATOIRE POUR PERSONNE, sur le seul écran dont l'objet est de distinguer ce qui est DÛ de ce
   qui est OUVERT. Elle fabriquait une dette qui n'existe pas. Rémy a tranché le 30.07 : il n'y a
   AUCUNE capacité fixée - donc aucun plafond à mesurer, donc pas de barre. Le compte écrit dit le
   même fait sans rien promettre.
   ⚠ Conséquence à connaître : il ne reste PLUS AUCUN aplat d'accent sur cet onglet. L'accent n'y
   souligne que l'état actif et l'action principale, ce qui est exactement le reset du 29.07. */
.frm-capacite{font-family:var(--mono);font-size:11px;letter-spacing:.03em;color:var(--ink-soft)}

/* ---------- CE QUI SE PASSE MAL SE DIT À CÔTÉ DE CE QUI L'A CAUSÉ ----------
   L'écran n'avait AUCUN message d'erreur : une date mal saisie ne produisait rien de visible.
   L'erreur vit donc sous son champ, elle nomme la valeur fautive, et elle ne peint pas un aplat -
   l'encre d'alarme et le mot suffisent (la couleur seule ne porte jamais un état). */
.frm-erreur{font-family:var(--mono);font-size:11px;line-height:1.45;letter-spacing:.03em;
  color:var(--red-ink);margin-top:4px;overflow-wrap:anywhere}
.frm-champ input[aria-invalid="true"]{border-color:var(--red-ink)}
/* LE FORMAT ATTENDU EST UNE AIDE PERMANENTE, PAS UN TEXTE INDICATIF. Dans un placeholder il
   disparaissait à la première frappe - et il y mesurait 3,28:1 en thème sombre, sous le plancher,
   faute de règle `::placeholder` (le gris par défaut du navigateur ne suit pas le thème). */
.frm-aide-champ{font-family:var(--mono);font-size:11px;line-height:1.45;letter-spacing:.03em;
  color:var(--ink-soft);margin-top:4px}
/* filet de sûreté : si un texte indicatif subsiste quelque part, il reste lisible dans les deux thèmes */
#frm_box input::placeholder{color:var(--ink-soft);opacity:1}
.frm-alerte{border:1px solid var(--red-ink);border-radius:var(--radius);padding:10px 13px;
  margin:12px 0;font-size:12.5px;line-height:1.55;color:var(--ink);background:transparent}
.frm-alerte b{color:var(--red-ink)}
.frm-sansdate{font-family:var(--mono);font-size:11px;letter-spacing:.03em;color:var(--orange-ink)}

/* ══════════════════════════════════════════════════════════════════════════════════════════
   « DEPUIS MON PASSAGE » (onglet Accueil, 31.07) - l'écran où tout était tronqué
   ------------------------------------------------------------------------------------------
   Une ligne = un changement fait par quelqu'un d'autre. Sa DESCRIPTION portait
   `white-space:nowrap` + `text-overflow:ellipsis` : à 375 px, 185 px affichés pour 358 px de
   texte, sur les 120 lignes de l'écran. On ne lisait donc jamais CE QUI avait changé - la
   seule information que cet écran existe pour donner.
   La ligne reste une ligne sur grand écran ; sous 620 px elle devient une pile, et la phrase
   se lit en entier. « La règle de la phrase entière » (DESIGN.md) n'est pas une préférence :
   c'est la raison d'être de l'écran. */
.qn-item{display:flex;align-items:center;gap:10px;background:var(--card);
  border:1px solid var(--line);border-radius:var(--radius);padding:9px 13px;margin-bottom:6px}
.qn-corps{flex:1;min-width:0}          /* la règle du minimum zéro */
.qn-qui{font-size:12.5px}
/* ══════════════════════════════════════════════════════════════════════════════════════════
   LA COURBE DES ETP DISPONIBLES (13.08) - « un graph prévisible des ETP disponibles par semaine »
   ══════════════════════════════════════════════════════════════════════════════════════════
   Ce qu'elle remplace : deux rangées de bâtonnets de 15 px, sans axe ni échelle. On ne pouvait ni
   lire une valeur, ni voir venir un creux - donc elle ne répondait à aucun des deux mots demandés.
   ⚠ L'ÉCHELLE NE SE CODE PAS PAR LA TEINTE (invariant I). Passé et avenir se distinguent par
     l'OPACITÉ du même aplat, jamais par deux couleurs qu'il faudrait apprendre.
   ⚠ AUCUN NOIR PUR (invariant L) : la ligne du jour est une encre du thème, pas un rgba(0,0,0). */
.etpg{margin:0 0 20px}
.etpg-hd h3{margin:0 0 2px;font-size:15px;color:var(--ink)}
.etpg-chiffres{font-size:12.5px;color:var(--ink-soft);margin-bottom:10px}
.etpg-svg{display:block;width:100%;height:auto;max-height:230px;overflow:visible}
.etpg-gril{stroke:var(--line);stroke-width:1}
/* ⚠ PAS L'ACCENT - vu rouge par l'invariant K le 13.08, et il avait raison : la couleur de
     l'action promet un geste, et un graphique ne se clique pas. L'encre juste était sous les
     yeux : `--rot-smur`, celle dont la frise du dessous peint déjà « au SMUR ». Même chose,
     même couleur - la courbe se lit comme le total de ce que la frise montre.
   Le CONSTATÉ est sourd, le PRÉVISIONNEL est plein : c'est devant qu'on regarde. */
.etpg-passe{fill:var(--rot-smur);opacity:.28}
.etpg-avenir{fill:var(--rot-smur);opacity:.62}
.etpg-ligne{fill:none;stroke:var(--rot-smur);stroke-width:1.5}
.etpg-creux{fill:var(--orange-ink)}
.etpg-auj{stroke:var(--ink-soft);stroke-width:1;stroke-dasharray:3 3}
.etpg-note{font-size:12px;color:var(--ink-soft);line-height:1.55;margin-top:8px;max-width:78ch}
@media(max-width:620px){.etpg-svg{max-height:180px}}

/* L'INTERTITRE DE JOUR - il porte l'horodatage que chaque ligne ne répète plus (13.08). Les
   lignes ne montrent que l'heure : la date vivait douze fois par écran, elle vit une fois par jour. */
.qn-jour{font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-soft);
  margin:14px 0 6px;padding-bottom:4px;border-bottom:1px solid var(--line)}
.qn-jour:first-child{margin-top:2px}
/* LE MULTIPLICATEUR - combien de fois le MÊME fait a été enregistré. C'est un détail technique :
   il se lit si on le cherche, il ne se voit pas si on ne le cherche pas. Donc pas d'aplat coloré
   (« si c'est coloré, ça se clique » - invariant K), et le plancher de 11 px est tenu. */
.qn-n{font-family:var(--mono);font-size:11px;color:var(--ink-soft);border:1px solid var(--line);
  border-radius:var(--radius);padding:0 5px;margin-left:7px;white-space:nowrap}
.qn-quoi{font-size:11.5px;color:var(--ink-soft);margin-top:1px;overflow-wrap:anywhere}
.qn-ts{font-family:var(--mono);font-size:11px;color:var(--ink-soft);white-space:nowrap;flex:none}
.qn-btn{padding:4px 10px;font-size:11.5px;margin:0;flex:none}
.qn-voir{flex:none}
@media(max-width:620px){
  /* La pile : l'horodatage et les actions passent sous le texte plutôt que de le comprimer. */
  .qn-item{flex-wrap:wrap;align-items:flex-start;gap:6px 10px}
  .qn-corps{flex:1 0 100%;order:1}
  .qn-ts{order:2}
  .qn-btn,.qn-voir{order:3;margin-left:auto}
}

/* ══════════════════════════════════════════════════════════════════════════════════════════
   GARDE MINIMALE HÉLICO - une ligne par médecin (onglet Hélico, 31.07)
   ------------------------------------------------------------------------------------------
   Six enfants de flex en `nowrap`, tous dimensionnés en dur (30 + 150 + jauge + 60 + deux
   pastilles) : 453 px de contenu pour 317 px de place à 375 px. Trente-cinq pastilles
   sortaient de l'écran par la droite et faisaient défiler la PAGE de 197 px - dont « treuil à
   faire », c'est-à-dire précisément l'information qu'on vient chercher ici.
   Sous 620 px la ligne devient une pile : identité, puis jauge pleine largeur, puis les
   pastilles. Rien n'est retiré - une carte ne rétrécit pas, elle choisit son rangement. */
.hli{display:flex;align-items:center;gap:12px;padding:9px 0;border-bottom:1px solid var(--line)}
.hli-ini{width:30px;height:30px;border-radius:50%;flex:none;background:var(--bg);
  border:1px solid var(--line);display:flex;align-items:center;justify-content:center;
  font-size:11px;font-weight:600;color:var(--ink-soft)}
.hli-qui{width:150px;flex:none;min-width:0}
.hli-nom{font-size:13px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.hli-team{font-size:11.5px;color:var(--ink-soft)}
.hli-jauge{flex:1;min-width:60px;height:8px;border-radius:var(--radius-s);background:var(--bg);overflow:hidden}
.hli-part{height:100%;border-radius:var(--radius-s)}
.hli-cpt{width:60px;text-align:right;font-family:var(--mono);font-size:12px;font-weight:600;flex:none}
/* Les quatre états portent une ENCRE, jamais un aplat - et le MOT est toujours là (règle du
   signal doublé) : « atteint », « reste 7 », « REGA à jour », « treuil à faire ». */
.chip2.hli-ok{color:var(--green-ink)}
.chip2.hli-att{color:var(--orange-ink)}
.chip2.hli-ko{color:var(--red-ink)}
.chip2.hli-nc{color:var(--ink-soft)}
@media(max-width:620px){
  .hli{flex-wrap:wrap;gap:8px 10px;padding:11px 0}
  .hli-qui{width:auto;flex:1 1 auto}
  .hli-nom{white-space:normal;overflow:visible}
  .hli-jauge{flex:1 0 100%;order:3}
  .hli-cpt{order:2;width:auto}
  .hli .chip2{order:4}
}

/* ══════════════════════════════════════════════════════════════════════════════════════════
   CHECK ANNUEL REGA (onglet Hélico, 31.07) - le tableau qui écrivait son style dans le JS
   ------------------------------------------------------------------------------------------
   Ses règles vivaient dans `helicoCheckHTML()`, en styles posés ligne à ligne. Conséquences
   MESURÉES à 375 px, thème sombre, pointeur grossier : 560 px de large donc 197 px de
   défilement de la PAGE, deux boutons sans aucune classe (« Effacer », une action destructive,
   à 2,74:1 et sous la cible tactile), et le statut « à faire » peint avec l'APLAT `--red`.
   La leçon est celle du registre de formation, le 30.07 : ce qui n'est pas dans la feuille
   n'est ni thémé, ni agrandi au doigt, ni gardé. Le tableau défile dans SON conteneur
   (`.tscroll`), jamais la page ; les boutons sont des `.go` ; les statuts portent une encre. */
.regatab{border-collapse:collapse;min-width:100%}
.regatab th,.regatab td{border:1px solid var(--line);padding:6px 9px;font-size:12px;
  text-align:center;vertical-align:middle}
.regatab th{font-family:var(--mono);font-size:11px;font-weight:600;letter-spacing:.05em;
  text-transform:uppercase;color:var(--ink-soft);white-space:nowrap}
.regatab .rt-nom{text-align:left;font-weight:600;color:var(--ink);white-space:nowrap}
.regatab .rt-date{color:var(--ink-soft);font-family:var(--mono);white-space:nowrap}
/* LE SIGNAL EST DOUBLÉ : le mot dit l'état, l'encre l'appuie - jamais l'inverse (règle du
   signal doublé). Et c'est une ENCRE, pas un aplat : `--red` mesure 4,09:1 en `color:`. */
.regatab .rt-statut{font-weight:700;white-space:nowrap}
.regatab .rt-ok{color:var(--ink-soft)}
.regatab .rt-att{color:var(--orange-ink)}
.regatab .rt-ko{color:var(--red-ink)}
/* La colonne d'action garde ses trois contrôles sur une ligne : c'est le conteneur qui défile,
   pas la page. Au doigt ils grandissent tout seuls - ils sont enfin des `.go`. */
.regatab .rt-actions{white-space:nowrap}
.regatab .rt-actions>*+*{margin-left:6px}
.regatab .rt-actions .ui-input{font-size:12px;padding:4px 7px}
.regatab .rt-actions .go{font-size:12px;padding:4px 10px}

/* ---------- UNE LIGNE RANGÉE (« vu ») ----------
   Rangée n'est pas soldée : elle garde l'encre du texte courant et sa dette reste écrite en
   toutes lettres. Pas d'aplat, pas de barré - un barré dirait « réglé », et il ne l'est pas. */
.frm-rangee .frm-nom{color:var(--ink-soft)}

/* ---------- LE CALENDRIER ----------
   Une navigation, pas une pagination : deux flèches et le mois écrit en toutes lettres. */
.fcal-nav{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;margin-bottom:4px}
.fcal-mois{font-family:var(--mono);font-size:11px;font-weight:700;letter-spacing:.06em;
  text-transform:uppercase;color:var(--ink)}
/* La seule famille que le service IMPOSE. L'accent souligne, il ne remplit pas. */
.frm-phase.fcal-impose{box-shadow:inset 0 -2px 0 var(--accent)}

/* espacements nommés - pour que le JS n'ait aucune raison d'écrire une marge en ligne */
.frm-actions{margin-top:9px}
.frm-sous.frm-serre{margin-top:8px;margin-bottom:0}
.frm-phases.frm-serre{margin-top:8px}

/* ---------- le journal : en ajout seul, replié ---------- */
.frm-journal{margin-top:22px}
.frm-journal summary{cursor:pointer;font-family:var(--mono);font-size:11px;font-weight:600;
  letter-spacing:.06em;text-transform:uppercase;color:var(--ink);padding:6px 0}
.frm-journal summary:hover{color:var(--ink)}
.frm-entree{display:grid;grid-template-columns:minmax(0,124px) minmax(0,1fr);gap:0 18px;
  padding:8px 2px;border-bottom:1px solid var(--line);font-size:12px;align-items:baseline}
.frm-entree>*{min-width:0}
.frm-entree .frm-quand{font-family:var(--mono);font-size:11px;letter-spacing:.03em;
  color:var(--ink-soft)}
.frm-entree .frm-qui{font-weight:600;color:var(--ink)}
.frm-entree .frm-quoi{color:var(--ink-soft);line-height:1.5;overflow-wrap:anywhere}

/* ---------- LA MARGE PASSE AU-DESSUS quand la colonne se resserre ----------
   Aucune table, aucun `nowrap` : l'ancien suivi était un <table> en `white-space:nowrap`, donc
   un scroll horizontal garanti à 375 px. Ici tout s'empile et rien ne déborde. */
@media (max-width:620px){
  .frm-ligne,.frm-champ,.frm-entree{grid-template-columns:minmax(0,1fr);gap:3px 0}
  .frm-marge{padding-top:0}
  /* `.frm-pousse` faisait ici de « Retirer » un bouton PLEINE LARGEUR en tête de carte, au-dessus
     des champs qu'il vidait : la plus grosse cible de la carte était la destructive. Le bouton a
     quitté l'en-tête de l'éditeur ; la règle part avec lui. */
}

/* LA DENSITÉ APPARTIENT À LA SOURIS, LA TAILLE APPARTIENT AU DOIGT. On adapte au POINTEUR,
   pas à la largeur : un téléphone en paysage fait 800 px et garde son pouce. */
@media (pointer: coarse){
  .frm-session-hd select,.frm-champ input{min-height:44px}
  .frm-journal summary,.frm-choisir summary{min-height:44px;display:flex;align-items:center}
  /* Les jetons du sélecteur restent de la taille d'un jeton - c'est leur ZONE DE FRAPPE qui
     grandit, pas leur dessin (le glyphe garde sa taille, la cible devient atteignable). */
  .frm-choix{position:relative}
  .frm-choix::after{content:"";position:absolute;left:50%;top:50%;
    transform:translate(-50%,-50%);width:100%;min-width:44px;height:44px}
  .frm-choix-grp{gap:8px}
}

/* ══ LE MARQUEUR (01.08) ═══════════════════════════════════════════════════════════════════════
   UNE EXPLICATION N'EST PLUS UN PARAGRAPHE : C'EST UN REPÈRE POSÉ SUR LE TITRE DE CE QU'ELLE
   EXPLIQUE. Il remplace les 49 blocs `.aide` de la console et les 5 `.aidebox` du portail - voir
   la note « .aide EST MORT » en tête de cette feuille pour ce qu'ils étaient devenus.

   TROIS PROPRIÉTÉS, ET C'EST TOUT CE QU'IL PROMET :
   · il ne coûte RIEN à la surface de lecture - un glyphe de 15 px sur la ligne du titre, pas une
     rangée de prose au-dessus du contenu ;
   · le texte n'est JAMAIS tronqué (« la règle de la phrase entière »), parce qu'il n'est plus
     posé dans le flux : il n'a plus de largeur à voler à personne ;
   · il ne cache aucune COMMANDE. Une explication se replie ; un bouton, un champ, un lien, non.
     C'est la faute exacte qu'on vient de payer, et elle est désormais tenue par un invariant.

   ⚠ LA RÉVÉLATION EST EN CSS PUR, ET C'EST LE POINT. `:hover` seul aurait été un piège au doigt
   (le portail est une PWA de téléphone) et au clavier. `:focus-within` sur un vrai `<button>`
   donne les trois chemins d'un coup : survol à la souris, tabulation au clavier, appui au doigt.
   Le panneau reste dans l'arbre d'accessibilité (opacité 0, jamais `visibility:hidden` ni
   `display:none`) : un lecteur d'écran le lit, `aria-describedby` l'y rattache, et la recherche
   du navigateur le trouve. Un texte qu'on ne voit pas n'est pas un texte qui n'existe pas.
   Le JavaScript n'intervient QUE pour choisir le côté d'ouverture (`FRONT_JS`) : posée à gauche
   par défaut, la feuille de 460 px sortait de l'écran dès que le marqueur passait la moitié de la
   largeur - vu au navigateur sur l'en-tête de Tournus, 178 px hors champ. Un côté ne se devine
   pas à l'écriture, il se MESURE à l'ouverture. */
/* ⚠ ICI VIVAIT `.rs-bar` - la glissière des réglages du solveur, dont la piste native était
   effacée pour laisser voir des bandes peintes dessous. Ces bandes ont été retirées le 07.08
   après quatre tentatives infructueuses (invisibles · pas progressives · zones dégénérées ·
   code-barres) : on n'encode pas la distribution de 36 valeurs dans un bandeau de 10 px. Le
   rang s'écrit en toutes lettres à côté du champ, et la glissière est redevenue ordinaire.
   La leçon reste, elle : `[data-theme="dark"] input` (0,1,1) BAT `.rs-bar` (0,1,0) - vérifier
   ce qui PEINT au bout de la cascade, pas seulement la règle qu'on vient d'écrire. */
/* ── LÉGENDE COLLANTE DE L'ÉCHELLE DE FORCE (07.08, demande de Rémy) ───────────────────────
   « J'aimais bien la légende de couleur au début, qui deviendrait collante pour suivre le
   défilement, et éviter d'écrire "extrême" partout. » C'est le bon partage : la légende porte le
   VOCABULAIRE une fois, les lignes ne portent plus que la COULEUR. Vingt-neuf fois le même mot
   n'apprenait rien et noyait les valeurs - le reproche initial de l'écran.
   Elle colle sous les DEUX barres hautes qui la précèdent - `.hb` (0→96) puis `.tb`
   (56→135) : bande occupée MESURÉE au navigateur à 135 px, jamais devinée. Elle reste
   opaque : une légende translucide laisserait passer les lignes et deviendrait illisible au
   moment précis où l'on s'y réfère.
   ⚠ LA COULEUR N'EST PAS SEULE À PORTER LE SENS, sans quoi on enfreindrait la charte : la
   POSITION du curseur sur sa barre dit la même chose (plus à droite = plus fort), et le survol
   nomme le palier avec le rang exact. La légende est la clé, pas l'unique canal. */
.rs-leg{position:sticky;top:135px;z-index:4;display:flex;flex-wrap:wrap;gap:18px;align-items:center;
  padding:9px 12px;margin:0 0 12px;background:var(--card);border:1px solid var(--line);
  border-radius:var(--radius);font-size:12px;max-width:1120px}
.rs-leg .t{color:var(--ink-soft);font-weight:400}
/* ⚠ LE MOT N'EST PLUS PEINT, IL EST ÉTIQUETÉ (07.08, « en light aucune couleur de vraiment
   lisible »). Peindre le mot forçait la palette sous L* 45 pour tenir 4,5:1 et n'y laissait que
   des ardoises ; l'échantillon est un aplat de 22 × 10 px - plancher 3:1 -, et le mot récupère
   `--ink`. Le filet à 22 % d'encre existe pour que l'échantillon le plus clair garde un bord
   sur la carte blanche : sans lui, `--rang-tres-souple` en thème clair flotte. */
.rs-leg b{display:inline-flex;align-items:center;gap:7px;font-weight:600;color:var(--ink)}
.rs-leg b i{width:22px;height:10px;border-radius:3px;flex:none;
  box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--ink) 22%, transparent)}
/* ── LES PALIERS SE CLIQUENT (07.08, demande de Rémy : « palier par bouton cliquable ») ──────
   Ils se saisissaient en TEXTE LIBRE (« 3, 5, 7 ») : rien ne disait la plage permise, rien
   n'empêchait 0 ni 99, et l'ordre était à la charge de qui tape. Un palier est un nombre CHOISI
   dans un ensemble fini - c'est un interrupteur, pas une phrase.
   ⚠ L'ÉTAT ACTIF SE DIT PAR UN SOULIGNEMENT, PAS PAR UN APLAT - et je l'ai appris en me faisant
   refuser. Premier jet : `background:var(--accent)` avec son encre `--on-accent`, un couple
   parfaitement mesuré (7,01:1). `check_charte` l'a bloqué au commit : depuis le reset du 29.07,
   ce produit dit l'actif par `box-shadow:inset 0 -2px 0 var(--accent)`, l'encre pleine et la
   graisse. Ma mesure était juste ET hors charte : un contraste correct ne rend pas un idiome
   correct. C'est le garde-fou qui connaissait la règle, pas moi.
   ⚠ LA CIBLE TACTILE PORTE SES DEUX CÔTÉS. L'invariant H est né d'un `min-height` posé seul
   sous `pointer: coarse` - 36 × 44 px n'est pas une cible de 44. Ici les deux y sont. */
/* ══ L'ÉCRAN DES RÉGLAGES DU SOLVEUR - SA MISE EN PAGE VIT ICI (08.08) ══════════════════════
   « Terrible mise en page » (Rémy), et c'était mesurable : la ligne de réglage était une grille
   `minmax(0,1fr) 300px` écrite EN LIGNE dans le JS, sans aucune règle de largeur. À 375 px la
   colonne du nom tombait à **37 px** - un mot par ligne - et la ligne montait à **297 px de
   haut**. « Période sur laquelle la ligne rouge se compte » s'affichait en huit lignes d'un mot.
   ⚠ POURQUOI ÇA N'AVAIT PAS ÉTÉ VU : la grille était écrite dans une chaîne JS, donc aucune
   feuille ne la portait, donc aucune requête média ne pouvait l'atteindre. Un style en ligne est
   un style qui ne connaît qu'une seule largeur - celle sous laquelle on l'a écrit.
   La mise en page redescend donc dans la feuille, où elle peut répondre à la largeur. Effet de
   bord voulu : soixante-cinq `font-size` recopiés en ligne deviennent trois déclarations.
   Le seuil est 760 px, la largeur où la colonne de commande (300 px) et un nom lisible (≥ 260 px)
   cessent de tenir côte à côte. */
/* ⚠ UNE MESURE, MÊME POUR UN FORMULAIRE. Sans plafond, à 1440 px le nom du réglage et sa
   commande sont séparés de plus de 700 px de vide : l'œil perd la paire, et il faut suivre le
   filet du bas pour savoir quelle valeur va avec quel intitulé. `DESIGN.md` pose la mesure pour
   le texte (65–75 caractères) ; une ligne d'écran qui APPARIE deux objets obéit à la même
   physique. 1120 px tient les 47 lignes ensemble sans les serrer. */
.rs-lim{max-width:1120px}
.rs-row{display:grid;grid-template-columns:minmax(0,1fr) 300px;gap:14px;align-items:center;
  padding:7px 0;border-bottom:1px solid var(--line);max-width:1120px}
.rs-row:last-child{border-bottom:0}
.rs-nom{min-width:0}
.rs-nom b{font-weight:600;font-size:13px}
.rs-nom .u{font-size:11px;color:var(--ink-soft);margin-left:2px}
.rs-cmd{display:flex;align-items:center;gap:8px}
.rs-cmd input[type=number]{width:80px;flex:none}
.rs-cmd .rs-bar{flex:1;min-width:130px;margin:0}
.rs-cmd .rs-etat{font-size:12px;white-space:nowrap;font-weight:600;color:var(--ink)}
.rs-fam{color:var(--ink);margin:22px 0 2px;font-size:14px}
.rs-sous{font-size:12px;color:var(--ink-soft)}
/* Le multiplicateur OUVRE sa famille : léger retrait de fond, et le filet vertical qui suit dit
   jusqu'où il porte. Le filet reste à 1 px - un bord coloré épais est un aplat déguisé. */
.rs-mult{padding:10px 12px;margin:2px 0 6px;background:var(--bg);border:1px solid var(--line);
  border-radius:var(--radius)}
.rs-portee{border-left:1px solid var(--line);margin-left:10px;padding-left:14px}
.rs-tete{display:flex;flex-wrap:wrap;gap:8px;align-items:center;padding:10px 12px;margin:0 0 12px;
  background:var(--bg);border:1px solid var(--line);border-radius:var(--radius);max-width:1120px}
.rs-sep{width:1px;height:22px;background:var(--line);flex:none}
@media (max-width:760px){
  /* Une ligne devient DEUX lignes : le nom en entier, la commande dessous, pleine largeur. */
  .rs-row{grid-template-columns:1fr;gap:6px;padding:11px 0}
  /* ⚠ ET LE CHAMP NE RÉTRÉCIT PAS. Il passait à 72 px ici, et l'invariant D a rougi : une CIBLE
     dimensionnée sous `max-width` retombe à sa taille de souris sur une tablette tactile - la
     largeur ne dit pas comment on touche. C'est la leçon du 28.07, reprise sur le fait.
     Mesuré : 80 px + 8 de gouttière + 130 de barre = 218 px dans les 351 disponibles. Le
     rétrécissement ne servait à rien qu'à créer la faute. */
  /* La légende cesse de coller : mesurée ici, la bande haute occupe 217 px et non 135, et une
     légende de trois rangs mangerait un quart du téléphone en restant devant le texte. */
  .rs-leg{position:static;gap:10px 14px}
  .rs-tete{flex-direction:column;align-items:stretch}
  .rs-sep{display:none}
  .rs-tete select,.rs-tete input,.rs-tete button{width:100%}
  /* Le marqueur d'aide s'aligne à gauche : en colonne étirée il se centrait tout seul sur sa
     propre ligne, et un ⓘ seul au milieu d'un bloc ne se rattache visiblement à rien. */
  .rs-tete>.mk{align-self:flex-start}
}
.pal-b{min-width:30px;height:30px;padding:0 7px;font-size:12.5px;font-weight:500;
  border-radius:var(--radius);border:1px solid var(--line);background:var(--card);color:var(--ink-soft);
  cursor:pointer;font-family:inherit;transition:color .1s}
.pal-b:hover{border-color:var(--accent)}
.pal-b:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.pal-b.on{color:var(--ink);font-weight:700;box-shadow:inset 0 -2px 0 var(--accent)}
@media (pointer: coarse){.pal-b{min-width:44px;min-height:44px}}
.mk{position:relative;display:inline-flex;vertical-align:baseline}
.mk-b{appearance:none;background:none;border:0;padding:0;margin:0;cursor:help;position:relative;color:var(--ink-soft);
  display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;
  border-radius:50%;transition:color .12s cubic-bezier(.22,1,.36,1)}
.mk-b svg{width:15px;height:15px;display:block}
.mk:hover .mk-b,.mk-b:focus-visible{color:var(--ink)}
.mk-b:focus-visible{outline:1px solid var(--accent);outline-offset:3px;
  box-shadow:var(--anneau)}
/* Le panneau : une FEUILLE posée, pas une bulle. Filet d'un pixel, angle de 4 px, et la seule
   ombre du système qui a le droit de porter - celle-ci flotte RÉELLEMENT au-dessus du flux. */
.mk-pop{position:absolute;top:calc(100% + 9px);left:-11px;z-index:60;
  width:max-content;max-width:min(460px,calc(100vw - 32px));
  background:var(--card);border:1px solid var(--line);border-radius:var(--radius);
  box-shadow:0 6px 18px rgba(25,26,29,.16);
  padding:13px 15px;text-align:left;text-transform:none;letter-spacing:0;
  font-family:var(--font);font-size:13px;font-weight:400;line-height:1.55;color:var(--ink-soft);
  /* ⚠ `display:none` FERMÉE, ET C'EST LA MESURE QUI L'A IMPOSÉ (05.08). Fermée, cette feuille
     restait POSÉE : 358 px de large ancrés à `left:-11px` sur un marqueur qui vit en milieu de
     ligne. À 390 px, la console entière défilait de côté - `scrollWidth` 468 pour 390 - sur TOUS
     les écrans, alors que la feuille n'avait jamais été ouverte. `PRODUCT.md` dit que la console
     est consultée au téléphone ; c'est le rubber-band latéral à chaque défilement.
     J'allais écrire `visibility:hidden`. Mesuré au navigateur avant de l'écrire : **aucun effet,
     468 px avant comme après** - un élément `visibility:hidden` génère toujours sa boîte et
     compte toujours dans la zone de défilement. Testés dans la page : `display:none` → 390 ✓,
     `content-visibility:hidden` → 390 ✓, `width:0` → 390 ✓, `visibility:hidden` → 468 ✗.
     `content-visibility` aurait gardé la transition, mais Safari ne l'a que depuis 2024 et le
     portail est une PWA de téléphone : on prend celui qui marche partout.
     CE QUE ÇA COÛTE : `display:none` ne se transitionne pas, la feuille s'ouvre NET. J'ai tenté
     de récupérer le fondu avec `@starting-style` ; ça a failli livrer une aide invisible - voir
     la note sur la règle d'ouverture, quinze lignes plus bas. Un défilement latéral permanent
     contre 120 ms de fondu : ce n'est pas un arbitrage serré. */
  display:none;
  opacity:0;pointer-events:none;transform:translateY(-3px);
  transition:opacity .12s cubic-bezier(.22,1,.36,1),transform .12s cubic-bezier(.22,1,.36,1)}
.mk-pop b,.mk-pop strong{color:var(--ink);font-weight:600}
.mk-pop p{margin:0}
.mk-pop p+p{margin-top:9px}
.mk:hover .mk-pop,.mk:focus-within .mk-pop{display:block;opacity:1;pointer-events:auto;transform:none}
/* ⚠ J'AI ESSAYÉ DE SAUVER LE FONDU, ET J'AI FAILLI LIVRER UNE AIDE INVISIBLE (05.08).
   La recette moderne - `@starting-style` + `transition-behavior:allow-discrete` - permet
   d'animer depuis `display:none`. Écrite ici, MESURÉE au navigateur : la feuille passait bien
   en `display:block`, et RESTAIT à **opacité 0**, indéfiniment. 700 ms après l'ouverture, pour
   une transition de 120 ms. Vérifié en retirant la règle à chaud dans la page : opacité 1
   immédiatement. C'était donc bien elle, et non une coïncidence.
   Je ne garde pas un mécanisme que je ne sais pas faire marcher sur une aide qui doit
   s'afficher. La feuille s'ouvre NET - c'est le coût annoncé de `display:none`, et il est
   payé. Le fondu n'est pas une information ; l'explication, si. */
/* Le filet de rattachement : 8 px de trait vertical entre le glyphe et sa feuille. C'est le même
   geste que partout ailleurs - on relie par un filet, pas par une flèche de bulle. */
.mk-pop::before{content:"";position:absolute;left:var(--mk-fil,14px);top:-9px;width:1px;height:9px;background:var(--line)}
[data-theme="dark"] .mk-pop{box-shadow:0 6px 20px rgba(9,10,22,.62)}
/* Repli sans JavaScript (le script de feuille n'a pas encore tourné) : contre le bord droit,
   la feuille s'aligne à droite. Dès que `FRONT_JS` mesure, il pose un `left` exact et retire
   cette classe - un décalage calculé bat toujours un choix binaire près d'un bord. */
.mk.fin .mk-pop{left:auto;right:-11px}
@media (prefers-reduced-motion: reduce){ .mk-pop{transition:none} }
/* UNE CIBLE EST UNE SURFACE, PAS UNE HAUTEUR (invariant H). Le glyphe garde ses 15 px ; c'est sa
   zone de frappe qui atteint 44 × 44, invisible, centrée - comme `.zone-frappe` ailleurs. */
@media (pointer: coarse){
  .mk-b::after{content:"";position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
    width:44px;height:44px}
}

/* ══ CALENDRIER DE FORMATION - UNE GRILLE, PAS UNE LISTE (03.08, signalé par Rémy) ═══════════
   L'écran empilait deux listes linéaires : « le mois » puis « à venir », qui répétaient souvent
   les mêmes lignes. On ne voyait ni la place des journées dans le mois, ni les trous - or c'est
   la question qu'on pose à un calendrier de formation : QUAND, et est-ce que ça tombe mal.
   Sept colonnes, la même grammaire que le calendrier mensuel du planning. */
.fcal-grille{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:6px;margin-top:10px}
.fcal-jn{font-family:var(--mono);font-size:11px;letter-spacing:.06em;color:var(--ink-soft);
  text-align:center;padding-bottom:2px}
.fcal-c{min-height:78px;background:var(--card);border:1px solid var(--line);
  border-radius:var(--radius-s);padding:5px 6px;min-width:0}
.fcal-c.vide{background:transparent;border-color:transparent}
.fcal-c.we{background:var(--bg)}
.fcal-c.auj{box-shadow:inset 0 0 0 1px var(--ink)}
.fcal-c.fcal-fs{box-shadow:inset 3px 0 0 var(--accent)}
.fcal-c.fcal-sel{outline:2px solid var(--accent);outline-offset:-2px}
.fcal-c .d{font-family:var(--mono);font-size:11px;color:var(--ink-soft)}
.fcal-c.auj .d{color:var(--ink);font-weight:700}
.fcal-p{display:block;margin-top:3px;border-radius:var(--radius-s);padding:2px 5px;font-size:11px;
  font-weight:600;line-height:1.35;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.fcal-p.k-fsmur{background:var(--fcal-smur);color:var(--fcal-smur-t)}
.fcal-p.k-rega{background:var(--fcal-rega);color:var(--fcal-rega-t)}
.fcal-p.k-autre{background:var(--fcal-autre);color:var(--fcal-autre-t)}
.fcal-lg{display:flex;flex-wrap:wrap;gap:6px 14px;margin-top:14px;padding-top:12px;
  border-top:1px solid var(--line)}
.fcal-lg span{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--ink-soft)}
.fcal-lg i{width:12px;height:12px;border-radius:2px;display:block}
/* ── LA FRISE DE L'ANNÉE : où sont les formations ? (03.08) ─────────────────────────────────
   Une barre par mois connu, sa hauteur proportionnelle au nombre de journées. Le mois courant
   est souligné, comme partout ailleurs - l'état actif se dit par le filet, jamais par un aplat. */
.fcal-annee{display:flex;gap:4px;align-items:flex-end;flex-wrap:wrap;margin:2px 0 14px;
  padding-bottom:10px;border-bottom:1px solid var(--line)}
.fcal-mm{display:flex;flex-direction:column;align-items:center;gap:3px;min-width:52px;
  min-height:44px;padding:4px 6px 5px;border:none;background:none;cursor:pointer;
  border-radius:var(--radius-s);font:inherit}
.fcal-mm:hover{background:var(--hover)}
.fcal-mm.on{box-shadow:inset 0 -2px 0 var(--accent-ink)}
/* La barre dit COMBIEN, elle ne se clique pas : encre neutre. L'accent reste au
   soulignement du mois courant - le seul endroit qui désigne une action. */
.fcal-mm i{display:block;width:26px;background:var(--ink-soft);border-radius:2px;opacity:.45}
.fcal-mm.on i{background:var(--ink);opacity:.9}
.fcal-mm .mm-l{font-family:var(--mono);font-size:11px;letter-spacing:.04em;color:var(--ink-soft)}
.fcal-mm.on .mm-l{color:var(--ink);font-weight:700}
.fcal-mm .mm-n{font-family:var(--mono);font-size:11px;color:var(--ink-soft)}

/* Le NOM sous une pastille de type, dans une cellule dense. */
.fcal-q{display:block;font-size:11px;line-height:1.3;color:var(--ink-soft);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:0 1px}
.fcal-p .fcal-n{margin-left:4px;font-size:11px;opacity:.8}

/* ── LE RAIL « À VENIR » : groupé par DATE puis par TYPE ────────────────────────────────────
   La liste plate répétait la date à chaque ligne, en petit gris : illisible, et elle ne disait
   pas que deux entrées tombaient le même jour. La date devient un titre. */
.fcal-jour{padding:10px 0;border-bottom:1px solid var(--line)}
.fcal-jour:last-child{border-bottom:0}
.fcal-jour.auj{box-shadow:inset 2px 0 0 var(--accent-ink);padding-left:10px}
.fcal-jd{display:flex;align-items:baseline;gap:8px;margin-bottom:5px}
.fcal-jd b{font-family:var(--mono);font-size:13px;font-weight:700;color:var(--ink);
  letter-spacing:.02em}
.fcal-jd span{font-size:11.5px;color:var(--ink-soft)}
.fcal-tl{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;margin:3px 0}
.fcal-tl .fcal-p{display:inline-block;margin-top:0;flex:none;max-width:none}
.fcal-gens{font-size:12.5px;color:var(--ink-soft);min-width:0}
.fcal-gens i{font-style:normal;font-size:11.5px;opacity:.85}
.fcal-gens .fcal-src{font-family:var(--mono);font-size:11px;letter-spacing:.04em;opacity:.75}

/* La colonne « à venir » : un rail, plus une seconde liste pleine largeur. */
.fcal-2col{display:grid;grid-template-columns:minmax(0,1fr) 268px;gap:22px;align-items:start}
.fcal-rail .frm-item{padding:9px 0}
.fev-fiche{border:1px solid var(--line);border-radius:var(--radius);background:var(--card);padding:12px 14px;margin-top:12px}
.fev-titre{font-size:15px;font-weight:600;margin-bottom:8px}
.fev-titre input{font:inherit;width:100%;max-width:420px}
.fev-ligne{display:flex;gap:16px;flex-wrap:wrap;margin-bottom:6px}
.fev-ligne label{display:block;font-size:11px;color:var(--ink-soft)}
.fev-fiche .gens{display:flex;flex-wrap:wrap;gap:6px;margin:4px 0 8px;align-items:center}
.fev-fiche .g{border:1px solid var(--line);border-radius:14px;padding:3px 9px;background:var(--card);font-size:12px}
.fev-fiche .g.f{border-style:dashed;background:var(--hover)}
.fev-fiche .g i{font-style:normal;font-size:10px;color:var(--ink-soft);margin-left:4px}
.fev-x{appearance:none;border:0;background:transparent;color:var(--red-ink);cursor:pointer;min-width:24px;min-height:24px}
.fev-att{border:1px solid var(--orange);background:var(--card);border-radius:var(--radius-s);padding:7px 9px;margin-bottom:6px;font-size:12px}
.fev-att b{display:block}
.fev-past{appearance:none;border:0;cursor:pointer;display:block;width:100%;text-align:left;margin-top:3px}
.fev-lien{appearance:none;border:0;background:transparent;color:var(--ink);cursor:pointer;padding:4px 0;text-align:left;min-height:32px;
  text-decoration:underline;text-decoration-color:var(--silver);text-underline-offset:3px}
.fev-lien:hover{text-decoration-color:var(--ink)}
.fev-tab{border-collapse:collapse;width:100%;font-size:12px;margin-top:10px}
.fev-tab th{text-align:left;font-size:11px;color:var(--ink-soft);font-weight:500;padding:4px 8px;border-bottom:1px solid var(--line)}
.fev-tab td{padding:6px 8px;border-bottom:1px solid var(--line);vertical-align:top}
.fev-tab td small{display:block;color:var(--ink-soft);font-size:11px}
.fev-tab tr.on td{background:var(--hover)}
.fev-tab tr.fev-creneau td{color:var(--ink-soft)}
.fev-tab tr.fev-detail td{padding:0 0 10px}
@media (pointer: coarse){.fev-x,.fev-lien,.fev-past{min-width:44px;min-height:44px}}
@media (max-width:900px){.fcal-2col{grid-template-columns:1fr}
  .fcal-c{min-height:62px}.fcal-p{font-size:11px}}
