/* ==========================================================================
   KAIROS — Thème « Instrument Pro » (direction 3a)
   Signature géométrique : chanfrein coin HAUT-DROIT + coin BAS-GAUCHE.
   ========================================================================== */

/* --- 1. TOKENS ---------------------------------------------------------- */
/* ⛔ POINT 6/7 (11.09.2026) — IDENTITE CHAUDE, CHASSIS SOMBRE, DEUX ACCENTS.
   Decision de Brice : graphite BRUN (chaud), pas bleute (froid) -- « ca fait
   plus pro et haute qualite » -- et le rail reste SOMBRE dans les DEUX modes
   clair/sombre du contenu (signature du produit, jamais bascule).
   Detail complet, ratios de contraste mesures et jetons de reprise :
   docs/2609_11_KAIROS_ARCHITECTURE_IDENTITE_VISUELLE_V2.md -- ce fichier
   applique les valeurs, le document explique le POURQUOI. Ne pas modifier
   l'un sans l'autre. */
:root {
  /* --- Chassis (rail), TOUJOURS sombre, ne change jamais avec le theme --- */
  --ai-rail:#16130f; --ai-rail-line:#241f18; --ai-rail-label:#a8a094; --ai-rail-fg:#6e675c;
  --ai-rail-fg-active:#b08843; --ai-rail-fg-text-active:#f0ebe2;
  --ai-thead-bg:#16130f; --ai-thead-fg:#a8a094;

  /* --- Contenu CLAIR (defaut) --- */
  --ai-ink:#2c3038; --ai-ink-2:#454b55; --ai-muted:#6b6b68; --ai-faint:#9a958c; --ai-disabled:#b7b2a8;
  --ai-bg:#efece7; --ai-surface:#f9f7f4; --ai-surface-2:#f1ede6; --ai-row-selected:#e9eff8;
  --ai-line:#e2ded6; --ai-line-2:#cfc9bf; --ai-line-soft:#e9e5dd;
  /* ENCRE POSEE SUR --ai-ink (15.09.2026). Un bloc peint en `var(--ai-ink)`
     (badge du bandeau) est un fond qui S'INVERSE avec le theme ; son encre
     doit donc s'inverser avec lui. Elle etait en dur (#fff, libelle #8b97a8) :
     en sombre, blanc sur creme #f0ebe2 = 1.19:1, libelle 2.49:1 -- la date
     du tableau de bord ne se lisait plus. En clair ces deux jetons valent
     EXACTEMENT les valeurs 3a d'avant : rien n'est repeint.
     Mesures et choix : docs/2609_11_KAIROS_ARCHITECTURE_IDENTITE_VISUELLE_V2.md,
     §4 ; sonde scripts/verifier_contraste_badge.js. */
  --ai-on-ink:#fff; --ai-on-ink-muted:#8b97a8;
  /* ACCENT POSE SUR --ai-ink (15.09.2026, item 74) : le pied et le chevron de
     la carte d'alerte (`.ai-card--alert`) lisaient `--ai-accent-hi`, qui ne
     s'inverse pas avec le fond : en sombre, #6ba3ea sur creme = 2.20:1. En
     clair, valeur 3a exacte (#3d82e0, 3.45:1 -- sous l'AA texte, heritee,
     notee au document d'identite §4 point 6, non touchee). */
  --ai-on-ink-accent:#3d82e0;
  /* Accent BLEU = ce sur quoi on agit (actions, liens, selection, travail
     planifie). Meme valeur qu'avant : elle passait deja tous les contrastes. */
  --ai-accent:#1c56a0; --ai-accent-2:#14406f; --ai-accent-hi:#3d82e0; --ai-accent-soft:#eaf0f8;
  /* Accent LAITON = ce qu'on LIT sans y toucher (absences, vacances,
     teletravail, feries, notes de semaine, gardes, liseres, alertes non
     bloquantes). --ai-lecture pour les traits/icones/pastilles (mesure
     3.05:1 sur --ai-surface, sous le seuil 3:1 sur --ai-bg -- reserver aux
     surfaces claires, jamais en filet direct sur le fond) ; --ai-lecture-fg
     pour du TEXTE (6.17:1 sur --ai-bg, largement AA). */
  --ai-lecture:#b08843; --ai-lecture-fg:#6b5323; --ai-lecture-soft:#f5eee0;
  /* LOT 6a - le rouge d'action destructive. AUCUN des trois n'est un choix
     neuf : les trois existaient deja dans le produit, disperses.
       #a32d2d  ai-ui.js:41 (.aiui-btn--danger) · base.html:83 (.ti-trash)
                absences.py:55 (MALADIE) · tokens.css:45 (--status-danger)
       #7a1d1d  ai-ui.js:42 - le survol du SEUL bouton destructif existant
       #fcebeb  tokens.css:46 (--status-danger-bg) · main.py:992 (DELETE)
     Nommes ici pour que le gabarit 3a puisse les EMPLOYER, pas pour les
     inventer. Les trois roles calquent exactement ceux de l'accent :
     couleur / survol / fond de survol du fantome. */
  --ai-danger:#a32d2d; --ai-danger-2:#7a1d1d; --ai-danger-soft:#fcebeb;
  --ai-ok:#1f6f4a;   --ai-ok-bg:#e8f1eb;
  --ai-warn:#a8620b; --ai-warn-bg:#fbf0df; --ai-warn-fg:#8a5008;
  --ai-info:#1c56a0; --ai-info-bg:#e8eef6; --ai-info-fg:#14406f;
  --ai-off:#7b8494;  --ai-off-bg:#eef0f3;  --ai-off-fg:#4b5563;
  /* ⛔⛔ POINT 1 (11.09.2026), revue de Brice — EXCEPTION NOMMEE A LA REGLE
     DES DEUX ACCENTS, assumee par Brice au nom de la lisibilite : le
     niveau d'urgence des echanges (/echanges) veut des couleurs FRANCHES
     -- un vrai vert, un vrai jaune, un vrai rouge -- jamais du bleu ni du
     laiton. Ce ne sont PAS --ai-danger/--ai-warn/--ai-ok : ces trois-la
     restent reserves a leur usage actuel (action destructrice, alerte,
     succes) partout ailleurs dans le produit -- les reutiliser ici
     ferait deriver LEUR sens a chaque futur ajustement de CETTE echelle.
     Trois jetons dedies, reserves a ce seul usage.
     ⚠️ « Jaune » n'est jamais un jaune pur (#FFEB3B et proches echouent le
     contraste texte des qu'on les pose sur un fond clair OU sombre) --
     un amber/or soutenu se lit encore comme "jaune" et passe l'AA.
     Contraste MESURE (formule WCAG), texte ET pastille, sur --ai-surface
     ET sur --ai-bg des DEUX modes :
       clair : rouge #C62828 (5.26:1 / 4.77:1) · jaune #8A5A00 (5.54:1 /
               5.03:1) · vert #1E6823 (6.42:1 / 5.83:1)
       sombre: rouge #EF5350 (4.96:1 / 5.31:1) · jaune #FFC107 (10.61:1 /
               11.36:1) · vert #4CAF50 (6.22:1 / 6.66:1)
     Toutes >= 4.5:1 (seuil texte), les deux couleurs de chaque niveau
     servent A LA FOIS a la pastille (forme) et au libelle texte a cote --
     jamais la couleur seule, l'etiquette texte reste posee (deja le cas,
     inchange). Nuancier (docs/2609_11_KAIROS_REFERENCE_Palette_V2.html)
     et document d'identite mis a jour avec ces memes six valeurs. */
  --ai-urgence-rouge:#C62828; --ai-urgence-jaune:#8A5A00; --ai-urgence-vert:#1E6823;
  --ai-font:'Space Grotesk',system-ui,-apple-system,sans-serif;
  --ai-mono:'IBM Plex Mono',ui-monospace,monospace;
  /* LOT I point 2 : echelle typographique UNIQUE de l'app (6 niveaux nommes).
     Page titre=h1, titre de carte/section=h3, corps=body, libelle/table dense
     =table, texte d'aide=meta, en-tete de tableau=eyebrow. h2 reste dispo pour
     un rang intermediaire (bandeau semaine, etc.) entre h1 et h3. Ne JAMAIS
     ajouter une taille en dur dans un template : tout passe par ces 6 noms. */
  --ai-fs-h1:27px; --ai-fs-h2:19px; --ai-fs-h3:14px; --ai-fs-body:13.5px; --ai-fs-table:12.5px;
  --ai-fs-meta:11.5px; --ai-fs-eyebrow:10px;
  /* LOT K point 5 : audit complet, 527 tailles en dur trouvees dans toute
     l'app. Les 6 tokens ci-dessus couvrent le TEXTE (titres, corps,
     libelles, aide) ; deux familles restaient hors echelle par nature -
     glyphes d'icone (Tabler, dimensionnes par font-size comme du texte,
     mais ce n'est pas du texte a lire) et grands nombres d'affichage
     (compteurs de carte KPI, badge semaine). Plutot que de les forcer sur
     l'echelle texte (une icone de 17px ecrasee a 14 ou 19px se voit), ce
     sont deux echelles COMPAGNES, memes principes (nommees, une seule
     source), perimetre different. */
  --ai-fs-icon-sm:13px; --ai-fs-icon-md:17px; --ai-fs-icon-lg:21px;
  --ai-fs-display:32px;
  --ai-track-tight:-.035em; --ai-track-eyebrow:.16em;
  --ai-s1:4px; --ai-s2:8px; --ai-s3:12px; --ai-s4:16px; --ai-s5:20px; --ai-s6:28px;
  --ai-chamfer:12px; --ai-chamfer-sm:6px; --ai-chamfer-lg:16px; --ai-radius:0;
  --ai-shadow-1:0 1px 2px rgba(13,20,32,.05);
  --ai-shadow-2:0 1px 2px rgba(13,20,32,.2);
}

/* --- Contenu SOMBRE — bascule explicite (reglages utilisateur, defaut
   CLAIR). Le RAIL n'est PAS redefini ici : il reste sombre dans les deux
   modes, ses jetons ne dependent jamais de [data-theme]. `--ai-accent` est
   AJUSTE dans ce bloc (3d82e0, dejà --ai-accent-hi) : le bleu #1c56a0 ne
   passe qu'a 2.55:1 sur le fond sombre #16130f -- sous l'AA -- alors que
   3d82e0 (deja dans la palette, aucune couleur neuve) tient 4.82:1. Meme
   principe applique a --ai-info, qui partage la valeur de --ai-accent. */
:root[data-theme="dark"] {
  --ai-ink:#f0ebe2; --ai-ink-2:#d8d2c6; --ai-muted:#a8a094; --ai-faint:#6e675c; --ai-disabled:#4f4a40;
  --ai-bg:#16130f; --ai-surface:#1e1a15; --ai-surface-2:#241f18; --ai-row-selected:#1c2733;
  --ai-line:#3a342b; --ai-line-2:#4a4335; --ai-line-soft:#2a251e;
  /* Encre sur --ai-ink (creme #f0ebe2 ici) -- deux valeurs DEJA dans la
     palette, aucune couleur neuve : #16130f (--ai-bg / --ai-rail) = 15.59:1 ;
     #6e675c (--ai-faint / --ai-rail-fg) = 4.71:1, AA texte. L'ecart
     libelle/texte (3.31:1) garde la hierarchie du clair (2.96:1). */
  --ai-on-ink:#16130f; --ai-on-ink-muted:#6e675c;
  /* #1c56a0 (le bleu d'action du clair, --ai-accent-2 ici) sur creme #f0ebe2
     = 6.12:1 -- AA texte, aucune couleur neuve, et il reste BLEU : le pied
     d'une carte d'alerte dit ou l'on va agir. */
  --ai-on-ink-accent:#1c56a0;
  --ai-accent:#3d82e0; --ai-accent-2:#1c56a0; --ai-accent-hi:#6ba3ea; --ai-accent-soft:#1c2733;
  --ai-lecture:#b08843; --ai-lecture-fg:#d8c9a8; --ai-lecture-soft:#2a2318;
  --ai-info:#3d82e0; --ai-info-bg:#1c2733; --ai-info-fg:#6ba3ea;
  --ai-ok-bg:#173224; --ai-warn-bg:#332615; --ai-warn-fg:#e0a860; --ai-danger-soft:#3a1f1f;
  --ai-off-bg:#2a2822; --ai-off-fg:#a8a094;
  /* ⛔ TROUVE EN CONSTRUISANT LE NUANCIER (point 4, 11.09.2026) : --ai-danger
     et --ai-ok n'avaient jamais ete ajustes pour le mode sombre -- seuls
     leurs fonds (-soft/-bg) l'avaient ete. Mesure : #a32d2d (danger) sur
     le fond sombre #16130f ne fait que 2.62:1, #1f6f4a (ok) 3.03:1--
     tous deux SOUS le seuil de texte 4.5:1. Corrige ici, meme principe
     que l'ajustement deja fait sur --ai-accent (garder la teinte,
     eclaircir jusqu'au seuil, jamais inventer une couleur neuve) :
     #d95d58 (danger) = 4.99:1, #2fa66d (ok) = 5.99:1. */
  --ai-danger:#d95d58; --ai-danger-2:#a32d2d;
  --ai-ok:#2fa66d;
  /* POINT 1 (11.09.2026) : memes trois jetons qu'en clair, teinte gardee,
     eclaircis jusqu'au seuil de texte contre le fond sombre -- voir la
     mesure complete au bloc :root ci-dessus. */
  --ai-urgence-rouge:#EF5350; --ai-urgence-jaune:#FFC107; --ai-urgence-vert:#4CAF50;
}
/* ⛔ PAS de @media (prefers-color-scheme) ICI, deliberement : Brice a
   ecrit « stockee PAR COMPTE, defaut CLAIR » -- un systeme d'exploitation
   regle en sombre ne doit RIEN changer tant que le compte n'a pas choisi.
   Le seul declencheur est l'attribut [data-theme="dark"], pose par le
   reglage utilisateur (voir base.html). */

/* ⛔ ITEM 75 (15.09.2026) — EN SOMBRE, PICO PEIGNAIT LES TABLEAUX EN BLEU FROID.
   Pico (premiere feuille de base.html) pose sous [data-theme=dark]
       --pico-background-color: rgb(19,22.5,30.5) · --pico-color: #c2c7d0
       --pico-table-border-color: #202632
   et les LIT dans `td, th { background-color; color; border-bottom }`, dans
   `p, ul, ol, dl, table, pre, address, blockquote`, `label, legend`, `dialog`
   et sur la racine. Aucun jeton Kairos ne les couvrait : toute cellule sans
   fond propre etait une dalle bleu-gris sur la page graphite chaud, tout
   paragraphe sans encre propre un gris-bleu.
   RECENSE PAR SENTINELLES, pas suppose (scripts/verifier_item75_pico_sombre.js,
   41 ecrans, 1280/390) : cellules peintes par Pico sur 11 ecrans — /rh-bascule,
   /rh-decompte-heures-sup, /absences, /personnel/niveaux, /statistiques,
   /sites-salles, /activites-competences (deux onglets), /regles-couverture,
   /comptes, /indispos-salles, /pilotage ; encre lue par la grille du planning
   et par les p/ul/label de tout le produit ; filet lu par AUCUNE cellule (les
   tables Kairos posent le leur) — realigne quand meme, pour la table suivante.
   CORRESPONDANCE, en miroir du clair (ou Pico peint les cellules en BLANC, un
   cran au-dessus de la page #efece7 et presque confondu avec la carte #f9f7f4) :
       racine          -> --ai-bg        le canevas est la page
       td, th          -> --ai-surface   un cran au-dessus de la page, confondu
                                         avec la carte qui porte le tableau
       encre           -> --ai-ink-2     celle des cellules de table.ai-table
       filet de table  -> --ai-line-soft celui de table.ai-table
   Mesure (WCAG, sombre) : --ai-ink-2 sur --ai-surface 11.50:1 ; lien
   --ai-accent sur --ai-surface 4.50:1 (4.505, contre 4.67 sur le bleu de Pico) ;
   T14 repos -> survol (--ai-line-soft) 1.14, seuil 1.10.
   ⚠️ Le fond des cellules reste OPAQUE : les colonnes collees de 71-c et 71-g
      (rh-bascule, rh-decompte) ne posent aucun fond et comptent sur celui-ci
      pour couvrir ce qui defile dessous.
   ⚠️ Le CLAIR n'est pas touche : tout est sous [data-theme="dark"], captures
      claires comparees au pixel par la sonde. */
:root[data-theme="dark"] {
  --pico-background-color:var(--ai-bg);
  --pico-color:var(--ai-ink-2);
  --pico-table-border-color:var(--ai-line-soft);
}
:root[data-theme="dark"] :where(td, th) { --pico-background-color:var(--ai-surface); }

/* ⛔ ITEM 79, PUCE « CHAMPS » (15.09.2026) — EN SOMBRE, PICO PEIGNAIT LES
   CHAMPS EN BLEU-GRIS ET LES CASES COCHEES DANS UN BLEU QUI N'EST PAS LE NOTRE.
   Pico pose `input, select, textarea { --pico-background-color:
   var(--pico-form-element-background-color); --pico-border-color: …;
   --pico-color: … }` et `[type=checkbox]:checked { … var(--pico-primary-
   background) }` ; sous [data-theme=dark] ces variables valent rgb(28,33,44),
   #e0e3e7, #2a3140, #0172ad. `.ai-champs` pose ses champs texte lui-meme :
   tout le RESTE lisait Pico.
   RECENSE PAR SENTINELLES, pas suppose (scripts/verifier_item79_champs_sombre.js,
   36 ecrans, 1280/390) : champs lecteurs sur 17 ecrans — /versions (54 cases,
   2 listes, recherche), /comptes et /activites-competences (radios), /echanges,
   /echanges/1, /mail/parametres, /absences, /personnel, /personnel/1/edit,
   /sites-salles, /audit-log, /regles-couverture, /indispos-salles, /reserve,
   /planning (notes de semaine) ; et le texte d'exemple (::placeholder) des
   champs de `.ai-champs` eux-memes, qu'aucune regle Kairos ne peignait.
   CORRESPONDANCE — un formulaire, un aspect : celui que `.ai-champs` pose
   deja (§ LOT F, decision de Brice du 02.09), dans ses deux modes.
       fond, fond au focus     -> --ai-surface
       encre                   -> --ai-ink
       filet                   -> --ai-line-2 ; au focus --ai-accent
       anneau de focus         -> --ai-accent-soft (champ texte)
       texte d'exemple         -> --ai-muted
       invalide / valide       -> --ai-danger / --ai-ok (aucun porteur
                                  aujourd'hui : pose pour le champ suivant)
       option choisie (liste multiple) -> --ai-row-selected
   Et deux ecarts VOULUS a `.ai-champs`, parce que la mesure les impose :
     - case/radio AU REPOS : filet --ai-muted et non --ai-line-2. Le filet
       EST le composant (WCAG 1.4.11, 3:1) : --ai-line-2 sur --ai-surface =
       1.77:1. Pico sombre d'avant : 1.33:1.
       ⚠️ --ai-faint (3.10:1 sur --ai-surface) a ete ESSAYE ET MESURE FAUX :
       les cases de la fiche personnel sont posees sur --ai-surface-2
       (2.78:1) et « Pictogrammes » de /absences 2.93:1. --ai-muted tient
       6.69:1 sur --ai-surface, 5.6:1 et plus sur --ai-surface-2.
     - case/radio AU FOCUS : anneau --ai-accent-hi. Une case cochee a deja son
       filet en --ai-accent, l'anneau est le SEUL signe du focus ;
       --ai-accent-soft (#1c2733) y serait invisible. 6.63:1 sur --ai-surface.
   Mesure (WCAG, sombre, sur --ai-surface) : encre 14.57:1 ; texte d'exemple
   6.69:1 ; filet au focus 4.50:1 ; invalide 4.66:1 ; valide 5.60:1 ; coche
   blanche sur --ai-accent 3.84:1.
   ⚠️ Les ICONES de champ (chevron, date, heure, recherche) sont des SVG en
      data-URI : une variable CSS n'entre pas dans un data-URI. Leur trace est
      donc ecrit en dur, rgb(168,160,148) = la valeur SOMBRE de --ai-muted —
      a changer ici si ce jeton change. Memes dessins que Pico, seul le
      `stroke` differe. Le clair garde le gris de Pico (non touche).
   ⚠️ Autres puces de 79, blocs suivants : `small` / `summary`, `code`, puis
      boutons (`button.secondary`, bouton bleu de Pico) et filet attenue.
   ⚠️ Le CLAIR n'est pas touche : tout est sous [data-theme="dark"]. */
:root[data-theme="dark"] {
  --pico-form-element-background-color:var(--ai-surface);
  --pico-form-element-active-background-color:var(--ai-surface);
  --pico-form-element-selected-background-color:var(--ai-row-selected);
  --pico-form-element-color:var(--ai-ink);
  --pico-form-element-placeholder-color:var(--ai-muted);
  --pico-form-element-border-color:var(--ai-line-2);
  --pico-form-element-active-border-color:var(--ai-accent);
  --pico-form-element-focus-color:var(--ai-accent-soft);
  --pico-form-element-invalid-border-color:var(--ai-danger);
  --pico-form-element-invalid-active-border-color:var(--ai-danger);
  --pico-form-element-valid-border-color:var(--ai-ok);
  --pico-form-element-valid-active-border-color:var(--ai-ok);
  --pico-icon-chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(168, 160, 148)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
  --pico-icon-date:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(168, 160, 148)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E");
  --pico-icon-time:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(168, 160, 148)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E");
  --pico-icon-search:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(168, 160, 148)' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E");
}
/* Specificite (0,3,1) : Pico pose l'anneau des cases par
   `[data-theme=dark] input:is([type=checkbox], …)` (0,2,1). Le bleu d'action
   est redefini ICI pour les cases et radios, et ce bloc reste necessaire : la
   puce « boutons » (plus bas) pose --pico-primary-background sur --ai-accent-2
   pour les boutons ; les cases gardent --ai-accent (coche blanche 3.84:1,
   seuil 3:1 d'un composant, point 12 du document d'identite). */
:root[data-theme="dark"] input:is([type="checkbox"], [type="radio"]) {
  --pico-primary-background:var(--ai-accent);
  --pico-primary-border:var(--ai-accent);
  --pico-form-element-border-color:var(--ai-muted);
  --pico-form-element-focus-color:var(--ai-accent-hi);
}

/* ⛔ ITEM 79, PUCE « SMALL / SUMMARY » (15.09.2026) — EN SOMBRE, PICO PEIGNAIT
   LES AIDES ET LES TITRES DE SECTION OUVERTS DE SON GRIS BLEUTE.
   `--pico-muted-color` vaut #7b8495 = rgb(123,132,149) sous [data-theme=dark].
   Pico la lit, directement ou par une variable qui en derive, dans
       :where(input,select,textarea,fieldset,.grid) + small      (aide d'un champ)
         … [aria-invalid=false|true] + small  -> --pico-ins-color / --pico-del-color
       details[open] > summary:not([role]):not(:focus)
                               -> --pico-accordion-open-summary-color
       details summary:focus:not([role])
                               -> --pico-accordion-active-summary-color (#79c0ff)
       figure figcaption, hgroup > :last-child, [type=file], separateur du fil
       d'Ariane (li::after), blockquote footer, ins, del.
   ⚠️ Un titre OUVERT battait la couleur que son gabarit lui donne : la regle de
      Pico pese (0,3,2), `.ec-mois__summary { color:var(--ai-ink) }` (0,1,0).
   RECENSE PAR SENTINELLES, pas suppose (scripts/verifier_item79_aides_sombre.js,
   39 ecrans, 1280/390, page servie PUIS tous ses <details> ouverts) :
     aides `small`   — /absences (small.ab-hint ×2), /sites-salles (×3),
                       /salles/new, /type-activites/new ;
     titres ouverts  — /echanges (mois, « Ouvrir un sujet »), /planning
                       (« À regarder », « Note de la semaine »), /rh-bascule,
                       /comptes (« modifier » ×6, 4.04:1 AVANT — sous l'AA),
                       /versions (56 libelles, une fois deplies).
   CORRESPONDANCE, en miroir du clair (ou Pico peint ces textes d'un gris un
   cran sous l'encre) :
       --pico-muted-color, titre ouvert, pied de citation -> --ai-muted
       aide valide / invalide, ins / del     -> --ai-ok / --ai-danger
                                                (memes jetons que les filets
                                                valide/invalide des champs, 79)
       titre au focus (souris)               -> --ai-accent-hi
   Mesure (WCAG, sombre) : --ai-muted 7.16 sur --ai-bg, 6.69 sur --ai-surface,
   6.32 sur --ai-surface-2 (Pico : 4.92 / 4.59 / 4.34) ; ecart --ai-muted /
   --ai-ink-2 1.72:1, le clair tient 1.64 — la hierarchie aide/texte est gardee.
   --ai-ok 5.60 / --ai-danger 4.66 sur --ai-surface (4.41 sur --ai-surface-2 :
   aucun porteur aujourd'hui, pose pour le champ suivant).
   ⚠️ Titre au focus : --ai-accent-hi et non --ai-accent. Le bleu d'action tient
      4.50:1 sur --ai-surface et 4.26:1 sur --ai-surface-2 — sous l'AA ;
      --ai-accent-hi 6.63 / 6.27. C'est le bleu que la case prend deja au focus.
   ⚠️ Hors perimetre : au focus CLAVIER, Pico peint le titre de --pico-primary
      (#01aaff) — puce « bouton bleu de Pico ». Le titre FERME lit
      --pico-color, deja realigne par 75 (--ai-ink-2).
   ⚠️ Le CLAIR n'est pas touche : tout est sous [data-theme="dark"]. */
:root[data-theme="dark"] {
  --pico-muted-color:var(--ai-muted);
  --pico-accordion-open-summary-color:var(--ai-muted);
  --pico-blockquote-footer-color:var(--ai-muted);
  --pico-accordion-active-summary-color:var(--ai-accent-hi);
  --pico-ins-color:var(--ai-ok);
  --pico-del-color:var(--ai-danger);
}

/* ⛔ ITEM 79, PUCE « CODE » (15.09.2026) — EN SOMBRE, PICO POSAIT CHAQUE `code`
   SUR UNE DALLE BLEU MARINE, ENCRE GRIS BLEUTE.
   Sous [data-theme=dark] : --pico-code-background-color rgb(26,30.5,40.25),
   --pico-code-color #8891a4. Pico les lit dans
       code, kbd, pre, samp { background; color }
       pre > code, pre > samp { background: 0 0 }   (le bloc porte la dalle)
   `kbd` lit --pico-code-kbd-background-color = var(--pico-color) et
   --pico-code-kbd-color = var(--pico-background-color) : les deux DERIVENT de
   variables deja realignees par 75 (touche creme sur graphite, 12.30:1) —
   non touchees ici, jugees par la sonde.
   RECENSE PAR SENTINELLES, pas suppose (scripts/verifier_item79_code_sombre.js,
   40 ecrans, 1280/390, page servie PUIS tous ses <details> ouverts) :
     code — /rh-bascule (reglages rh_plafond_report_*), /personnel/1/edit (×2,
            rh_date_reprise / settings.json), /versions (×4 : commit de mesure,
            HEAD, comparaison, une ligne depliee) ;
     kbd  — /personnel/1/patterns, /personnel/27/patterns, dans l'aide des
            trames (app/aide/35-trames.html) — deja chaud par 75.
   Les autres `code` du produit posent leur fond et leur encre eux-memes
   (.aide-corps, .cp-mdp, .vp-*, .rf-liste, .imp-contrat) et ne lisent pas Pico.
   CORRESPONDANCE :
       dalle  -> --ai-line-soft   et PAS --ai-surface-2, qu'ecrivent .aide-corps
                                  et .cp-mdp : mesure des supports, 2 releves
                                  sur 14 (1280 + 390) posent la dalle sur une
                                  section --ai-surface-2 (/personnel/1/edit) —
                                  dalle 1.00:1, invisible.
                                  --ai-line-soft : 1.22 sur --ai-bg, 1.14 sur
                                  --ai-surface, 1.08 sur --ai-surface-2 ; la
                                  dalle de Pico en clair tient 1.03 sur la
                                  carte, 1.08 sur la page : meme discretion.
                                  (--ai-line, 1.33 a 1.50, essaye au calcul :
                                  plus visible qu'une dalle claire, et c'est un
                                  jeton de FILET, pas de surface.)
       encre  -> --ai-ink-2       l'encre du texte courant (75), que les gabarits
                                  de Kairos ecrivent deja quand ils stylent un
                                  code (.vp-version__meta, .rf-liste). Pas
                                  --ai-muted, miroir du gris de Pico clair : un
                                  code est un identifiant qu'on RECOPIE (commit,
                                  nom de reglage) — il ne s'efface pas.
   Mesure (WCAG, sombre) : --ai-ink-2 sur --ai-line-soft 10.10:1 (Pico : 5.22).
   ⚠️ Le CLAIR n'est pas touche : tout est sous [data-theme="dark"]. La dalle
      claire de Pico (rgb(243,244.5,246.75)) est elle aussi froide sur la carte
      creme — constat, pas corrige ici (clair identique, regle de 79). */
:root[data-theme="dark"] {
  --pico-code-background-color:var(--ai-line-soft);
  --pico-code-color:var(--ai-ink-2);
}

/* ⛔ ITEM 79, PUCE « BOUTONS » ET FILET ATTENUE (15.09.2026) — EN SOMBRE, PICO
   PEIGNAIT SES BOUTONS, SES ANNEAUX DE FOCUS ET SES FILETS DANS SA PALETTE FROIDE.
   Sous [data-theme=dark] : --pico-primary-background #0172ad (bouton bleu),
   survol #017fc0, --pico-primary #01aaff, --pico-primary-focus
   rgba(1,170,255,.375), --pico-secondary-background #525f7a,
   --pico-contrast-background #eff1f4, --pico-muted-border-color #202632.
   Pico les lit dans
       button, [role=button], [type=submit|button|reset]   fond, filet, encre
         :hover/:active/:focus -> -hover-* ; :focus -> anneau 0 0 0 2px -focus
       .secondary, [type=reset], ::file-selector-button    -> secondary-*
       .contrast, bulle [data-tooltip]                     -> contrast-*
       .outline                                            -> filet et encre
       a:focus-visible                                     -> anneau -focus
       details summary:focus-visible                       -> encre --pico-primary
       hr, blockquote                                      -> muted-border
   RECENSE PAR SENTINELLES, pas suppose (scripts/verifier_item79_boutons_sombre.js,
   46 ecrans, 1280/390, 31 variables ; page servie, <details>/<dialog> ouverts,
   puis AU SURVOL et AU FOCUS CLAVIER de chaque element interactif) :
     bouton bleu       — /personnel/1/edit et /personnel/new (« Enregistrer »),
                         /indispos-salles (« Enregistrer l'indisponibilite ») ;
     bouton secondaire — « Appliquer 60% » et le lien-bouton « Annuler » de
                         /personnel/1/edit, « Annuler » de /personnel/new ;
     ANNEAU DE FOCUS   — TOUT LE PRODUIT : chaque lien et bouton du rail et du
                         contenu lisait --pico-primary-focus au focus clavier,
                         bleu translucide a 1.99:1 sur le graphite (seuil 3:1) ;
     titre au focus clavier — summary.aide-porte__bouton (--pico-primary) ;
     .contrast, .outline, bulles, hr, citation — aucun porteur : poses pour le
                         suivant, exerces par le banc de la sonde.
   CORRESPONDANCE, en miroir du clair (bouton bleu plein, secondaire neutre
   plein, un cran plus SOMBRE au survol — ce que fait deja .ai-btn) :
       bouton bleu     repos  --ai-accent-2, encre #fff (inchangee)    7.27:1
                       survol melange --ai-accent-2 / --ai-accent-soft 10.79:1
       secondaire      repos  --ai-line-2, encre --ai-ink               8.24:1
                       survol --ai-line                                10.37:1
       contraste, bulle  repos --ai-ink, encre --ai-on-ink             15.59:1
                         survol --ai-ink-2                             12.30:1
       anneaux de focus  --ai-accent-hi OPAQUE : 7.10 sur --ai-bg, 6.63 sur
                         --ai-surface (Pico : 1.99). C'est deja le bleu du
                         focus des cases (79 champs) et des titres (79 aides).
       contour, titre au focus clavier  --ai-accent-hi (6.63 / 6.27) ; survol
                         du contour --ai-ink ; contour secondaire --ai-muted
       filet attenue (hr, citation)     --ai-line, le jeton de filet
   ⚠️ BOUTON BLEU : --ai-accent-2 et PAS --ai-accent, qu'ecrit .ai-btn. Blanc sur
      --ai-accent = 3.84:1, sous l'AA (Pico : 5.23) : aligner les boutons de Pico
      sur .ai-btn les aurait tous degrades. Le defaut de .ai-btn en sombre est
      note (document d'identite §4 point 15), pas corrige ici.
   ⚠️ SURVOL DU BOUTON BLEU : aucun jeton n'est plus sombre que --ai-accent-2 en
      restant bleu (--ai-accent-soft seul : bouton confondu avec la page, 1.22).
      Le melange a parts egales des deux est le seul ecart de ce fichier a
      « aucune couleur neuve » — il ne sort pas de la palette. --ai-accent (plus
      clair, sens de Pico) : 3.84:1, rejete.
   ⚠️ SOULIGNEMENT : color-mix(… 50%, transparent) = la demi-teinte de Pico.
   ⚠️ Le CLAIR n'est pas touche : tout est sous [data-theme="dark"]. */
:root[data-theme="dark"] {
  --pico-primary:var(--ai-accent-hi);
  --pico-primary-background:var(--ai-accent-2);
  --pico-primary-border:var(--ai-accent-2);
  --pico-primary-underline:color-mix(in srgb, var(--ai-accent-hi) 50%, transparent);
  --pico-primary-hover:var(--ai-ink);
  --pico-primary-hover-background:color-mix(in srgb, var(--ai-accent-2), var(--ai-accent-soft));
  --pico-primary-hover-border:color-mix(in srgb, var(--ai-accent-2), var(--ai-accent-soft));
  --pico-primary-hover-underline:var(--ai-ink);
  --pico-primary-focus:var(--ai-accent-hi);
  --pico-primary-inverse:#fff;
  --pico-secondary:var(--ai-muted);
  --pico-secondary-background:var(--ai-line-2);
  --pico-secondary-border:var(--ai-line-2);
  --pico-secondary-underline:color-mix(in srgb, var(--ai-muted) 50%, transparent);
  --pico-secondary-hover:var(--ai-ink-2);
  --pico-secondary-hover-background:var(--ai-line);
  --pico-secondary-hover-border:var(--ai-line);
  --pico-secondary-hover-underline:var(--ai-ink-2);
  --pico-secondary-focus:var(--ai-accent-hi);
  --pico-secondary-inverse:var(--ai-ink);
  --pico-contrast:var(--ai-ink);
  --pico-contrast-background:var(--ai-ink);
  --pico-contrast-border:var(--ai-ink);
  --pico-contrast-underline:color-mix(in srgb, var(--ai-ink) 50%, transparent);
  --pico-contrast-hover:var(--ai-ink-2);
  --pico-contrast-hover-background:var(--ai-ink-2);
  --pico-contrast-hover-border:var(--ai-ink-2);
  --pico-contrast-hover-underline:var(--ai-ink-2);
  --pico-contrast-focus:var(--ai-accent-hi);
  --pico-contrast-inverse:var(--ai-on-ink);
  --pico-muted-border-color:var(--ai-line);
}
/* LIEN-BOUTON AU SURVOL — mesure par la meme sonde : `a:hover { color:
   var(--ai-accent-2) }` (§ liens, poids (0,1,1)) bat l'encre que Pico pose sur
   [role=button] (0,1,0). Au survol, le lien-bouton « Annuler » de
   /personnel/1/edit passait en bleu sur son propre fond : 1.36:1 avec Pico
   sombre, 1.69:1 avec les jetons ci-dessus. Ici l'encre reste celle que Pico
   donne au bouton dans cet etat (--pico-color = -inverse).
   Les boutons Kairos portes par un <a> (.ai-btn, .ai-btn-ghost, .ai-btn-outline)
   posent leur encre eux-memes : exclus.
   ⚠️ Le meme defaut existe en CLAIR — non touche (regle de 79), reporte au
      backlog avec sa mesure. */
:root[data-theme="dark"] a[role="button"]:not([class*="ai-btn"]):hover { color:var(--pico-color); }
/* ANNEAU DE FOCUS POSE SUR UN EN-TETE --ai-ink — mesure par la meme sonde : la
   croix du tiroir de /absences est posee sur `.ab-drawer-header`, peint en
   --ai-ink, donc CREME en sombre. --ai-accent-hi n'y tient que 2.20:1 (seuil
   3:1 ; l'anneau de Pico : 1.38:1). Meme en-tete sur le tiroir du planning
   (`.pl-drawer-header`, 74-c). Le jeton existe pour ce cas precis :
   --ai-on-ink-accent, « accent pose sur --ai-ink » (#1c56a0 sur creme, 6.12:1).
   Pose sur l'en-tete, herite par ce qui s'y focalise.
   ⚠️ `body:not(:has(.k2-pad))` : drapeau de refonte allume
      (refonte_absences_enabled, refonte_planning_enabled — allumes sur DEV),
      kairos-2 repeint l'en-tete en chassis graphite et l'anneau de la racine
      (--ai-accent-hi, 7.59:1) est le bon. La garde porte sur la PAGE et non sur
      l'ancetre : mesure, une garde `:not(.k2-pad *)` dependait de l'endroit ou
      l'en-tete est pose dans le DOM (2.55:1 sur un en-tete hors de .k2-pad,
      drapeau allume). Mesure dans les deux etats de chaque drapeau.
   ⚠️ La sonde pose sa sentinelle AUSSI sur ces deux en-tetes : une variable
      redeclaree localement cacherait sinon ses lecteurs. */
:root[data-theme="dark"] body:not(:has(.k2-pad)) :is(.ab-drawer-header, .pl-drawer-header) { --pico-primary-focus:var(--ai-on-ink-accent); }

/* ⛔ POINT 3 (11.09.2026) — UN SEUL INTERRUPTEUR POUR TOUS LES PICTOGRAMMES.
   Regle deplacee ici depuis planning/view.html : elle vivait dans UN SEUL
   gabarit alors que .pl-picto est desormais pose dans onze fichiers
   (planning, pilotage, personnel, echanges, audit, comptes, sites externes,
   patterns du personnel). Une classe partagee dans la feuille commune, pas
   une copiee-collee par ecran -- sinon la regle divergera au premier oubli.
   La classe body.pl-hide-pictos est posee par app/templates/base.html,
   pour CHAQUE page qui l'etend, depuis la cle localStorage partagee
   `kairosShowPictos` (etat de depart : eteint -- decision de Brice du
   11.09.2026, l'inverse d'avant). */
body.pl-hide-pictos .pl-picto { display:none; }

/* ⛔⛔ ITEM 121 (17.09.2026) — L'AUTRE MOITIE DU POINT 3 : CE QUI NE PASSE
   JAMAIS SOUS L'INTERRUPTEUR CI-DESSUS.

   ⛔ CE QUI A ETE MESURE, PAS DEDUIT. Le 16.09, l'item 120 a ouvert une
      capture et trouve, reglages par defaut :
          .ec-erreur .pl-picto i  ->  checkVisibility() = false
                                      boite 0 x 0, ::before content = ""
      Le recensement de l'item 121 a ensuite trouve 24 triangles d'alerte
      emballes dans `.pl-picto` (21 au grep d'une seule ligne, 3 de plus
      concatenes en JS) : AUCUN ne se peignait chez personne.

   ⭐ LA REGLE QUI TRANCHE, ET ELLE EST DANS LE NOM DES DEUX CLASSES.
      `.pl-picto` = PICTOGRAMME DE CONFORT : il accompagne un libelle, un
         titre ou une propriete affichee en permanence. Il illustre. On peut
         l'eteindre sans rien perdre — c'est tout l'objet de l'interrupteur.
      `.ai-signal` = SIGNAL : il porte le NIVEAU d'un etat anormal, risque
         ou bloquant. Il n'apparait que quand cet etat est vrai. Une erreur
         n'est pas une decoration : son icone ne depend pas d'un reglage de
         confort.

   ⚠️ POURQUOI LA COULEUR NE SUFFIT PAS A REMPLACER LE GLYPHE — c'est deja
      ecrit au § 11ter/11quater plus bas, mesure a l'appui : entre les deux
      niveaux de bandeau la teinte ne rend que 1,05 a 1,53 : 1. « La teinte
      est un RENFORT, elle ne porte rien toute seule. » Ce qui porte, c'est
      le GLYPHE qui change. Un fond ambre n'est donc PAS un porteur de
      remplacement : il ne dispense aucun triangle de se peindre.

   ⚠️ LA TAILLE EST LA LECON DE L'ITEM 120 : sorti de `.pl-picto`, le
      triangle prenait la taille du texte (13,5 px, boite 14 x 13 mesuree au
      navigateur). Il prend donc une taille d'ICONE. Deux seulement, pas une
      par ecran : `--md` (defaut, texte courant) et `--sm` pour les contextes
      denses (meta 11,5 px, eyebrow 10 px : un 17 px y serait demesure,
      exactement la faute que Brice a trouvee le 16.09 sur l'item 113).

   ⚠️ LA COULEUR N'EST PAS POSEE PAR DEFAUT, ET C'EST VOULU : la plupart des
      hotes (.mb-erreur, .pl-indispo-badge, .ai-rapport-*, les lignes rouges
      de personnel/fiche, un bouton --danger) donnent deja la bonne encre par
      heritage. `--warn` n'est ajoute que la ou l'hote est neutre. Une classe
      qui forcerait `--ai-warn-fg` repeindrait en ambre le triangle d'un
      bouton rouge a texte blanc.

   ⭐ LE FILET, ET IL A ETE VU MORDRE (temoin T2 de sonde_pictos_alerte.js).
      Si un tour futur ré-emballe un signal dans `.pl-picto`, il reste
      visible : le cliquet `scripts/verifier_pictos_alerte.js` refuse le
      balisage, et cette regle empeche entre-temps la disparition SILENCIEUSE
      — le defaut que l'item 121 a du aller chercher a la loupe.
      ⚠️ Regle SEPAREE, et non `:not(:has(...))` greffe sur la regle du Point
         3 : sur un navigateur sans `:has()`, une regle greffee serait jetee
         AVEC le masquage et rallumerait TOUS les pictogrammes du produit.
         Separee, elle est simplement ignoree, et on retombe sur le
         comportement d'aujourd'hui dans le seul cas que le cliquet interdit
         deja. */
.ai-signal { font-size:var(--ai-fs-icon-md); vertical-align:-3px; flex:none; line-height:1.35; }
.ai-signal--sm { font-size:var(--ai-fs-icon-sm); vertical-align:-2px; }
.ai-signal--warn { color:var(--ai-warn-fg); }
body.pl-hide-pictos .pl-picto:has(.ai-signal) { display:inline; }

/* ⛔⛔ ITEM 168 (22.09.2026) — LA TROISIEME FAMILLE, ET C'EST UN RETOUR A LA
   DECISION DE L'ITEM 121, PAS UNE EXCEPTION DE PLUS.

   Brice, 22.09.2026 : « so YES : Pictograms hidden by default … UNLESS LIKE
   WE ALREADY SAID IT — they are not a pictogram but essential (like in the
   planning the criss-cross picto to say that the planning is modified
   differently than originally planned for that trame) ».
   Et, sur l'absence de drapeau : « Sans drapeau : c'est le retour a la
   decision, pas une nouvelle fonction. »

   ⭐ LE VOCABULAIRE EST DESORMAIS A TROIS MOTS, ET C'EST LE MEME CRITERE
      QU'EN 121 — « que perd-on en eteignant ? » :

      `.pl-picto`     PICTOGRAMME DE CONFORT. Il accompagne un libelle deja
                      ecrit a cote. Eteint, on perd une illustration, jamais
                      une information. C'est TOUT l'objet de l'interrupteur.
      `.ai-signal`    SIGNAL. Il porte le NIVEAU d'un etat anormal, risque ou
                      bloquant, et n'apparait que quand cet etat est vrai.
      `.ai-essentiel` PORTEUR D'INFORMATION. L'etat qu'il decrit est NORMAL
                      (ce n'est donc pas un signal), mais AUCUN mot de l'ecran
                      ne le dit a sa place. L'eteindre ne retire pas un
                      ornement : il retire une donnee.

   ⚠️ LA QUESTION QUI TRANCHE, ET ELLE SE MESURE : masque-t-on le glyphe et
      relit-on la case sans rien perdre ? Mesure du 22.09 sur le produit
      vivant (`mesures/item_d1/inventaire_pictos_2026-09-22.json`) :
        · `/absences`, interrupteur eteint : une pastille ne porte plus que
          des initiales et une couleur. Le MOTIF — vacances, maladie,
          formation — n'est lisible NULLE PART a l'ecran, legende comprise.
          Seulement au `title`, c'est-a-dire au survol, c'est-a-dire jamais
          sur un telephone. -> essentiel.
        · `/planning` : le trait pointille de `.pl-pill-ponctuel` survit deja,
          mais la LIGNE DE LEGENDE qui le nomme part avec le glyphe. Eteint,
          l'ecran montrait une forme que rien ne nommait. -> essentiel.
        · `/personnel/<id>/patterns` : « trame inactive » et « pas encore en
          vigueur » etaient des glyphes SEULS, sans un mot a cote. -> essentiel,
          ET ils recoivent leur libelle en toutes lettres (demande de Brice du
          22.09) : un glyphe qu'on ne peut plus eteindre reste un glyphe qu'il
          faut deviner.

   ⛔ POURQUOI UNE CLASSE, ALORS QUE RETIRER `.pl-picto` SUFFIRAIT. Parce
      qu'une absence ne se relit pas. `warn-icon--orpheline`, `--expire` et
      `--pastille` etaient deja hors de l'interrupteur, et personne n'a vu
      pendant cinq jours que les deux etats de trame d'a cote, eux, y etaient
      restes : le tri avait ete fait une fois, a la main, et il etait reste
      incomplet SANS QUE RIEN NE LE DISE. Une classe nommee se grep, se
      compte, et se fait verifier — `scripts/verifier_pictos_essentiels.js`.

   ⭐ LE FILET, MEME FORME QU'EN 121 ET POUR LA MEME RAISON. Si un tour futur
      re-emballe un porteur d'information dans `.pl-picto`, il reste visible
      au lieu de disparaitre en silence.
      ⚠️ Regle SEPAREE, jamais `:not(:has(...))` greffe sur la regle du Point
         3 : sur un navigateur sans `:has()`, une regle greffee serait jetee
         AVEC le masquage et rallumerait TOUS les pictogrammes du produit.
   ⚠️ AUCUNE TAILLE NI AUCUNE COULEUR ICI, ET C'EST VOULU — l'inverse de
      `.ai-signal`. Un signal est toujours une icone d'alerte de taille
      comparable ; un porteur d'information vit dans des hotes qui dictent
      deja la sienne (`.ab-pill .ab-icon` en `--sm`, `.pl-ponctuel-icon` en
      `--sm`, la legende en taille de texte). Une taille posee ici gagnerait
      sur la moitie d'entre eux et casserait leur densite — exactement la
      faute que Brice a trouvee le 16.09 sur l'item 113. */
body.pl-hide-pictos .pl-picto:has(.ai-essentiel) { display:inline; }

/* --- 2. BASE ------------------------------------------------------------ */
*,*::before,*::after { box-sizing: border-box; }
body { margin:0; font-family:var(--ai-font); font-size:var(--ai-fs-body); color:var(--ai-ink); background:var(--ai-bg); }
h1,h2,h3 { font-weight:600; letter-spacing:var(--ai-track-tight); color:var(--ai-ink); margin:0; }
h1 { font-size:var(--ai-fs-h1); }
h2 { font-size:var(--ai-fs-h2); }
h3 { font-size:var(--ai-fs-h3); }
a { color:var(--ai-accent); text-decoration:none; }
a:hover { color:var(--ai-accent-2); text-decoration:underline; }
table { border-collapse:collapse; }
.ai-num,.ai-time { font-family:var(--ai-mono); font-variant-numeric:tabular-nums; }
.ai-eyebrow { font-family:var(--ai-mono); font-size:var(--ai-fs-eyebrow); letter-spacing:var(--ai-track-eyebrow); text-transform:uppercase; color:var(--ai-faint); }
/* LOT I point 2 : defauts globaux "libelle" et "texte d'aide" - une case a
   cocher ou un input sans classe locale doit deja tomber juste, sans que
   chaque template ait besoin de re-poser une taille. Une regle plus
   specifique (composant local) continue de gagner via la cascade normale. */
label { font-size:var(--ai-fs-table); }
small { font-size:var(--ai-fs-meta); }

/* --- 3. SIGNATURE GÉOMÉTRIQUE ------------------------------------------- */
.ai-cut {
  clip-path: polygon(0 0, calc(100% - var(--ai-chamfer)) 0, 100% var(--ai-chamfer),
                     100% 100%, var(--ai-chamfer) 100%, 0 calc(100% - var(--ai-chamfer)));
}
.ai-cut-sm { --ai-chamfer:var(--ai-chamfer-sm); }
.ai-cut-lg { --ai-chamfer:var(--ai-chamfer-lg); }

/* --- 4. BOUTONS ---------------------------------------------------------
   clip-path découpe bordures ET outline : un bouton secondaire est donc un
   liseré chanfreiné (parent coloré) contenant un bouton chanfreiné blanc.  */
/* ⛔⛔ RETOUR DE BRICE (18.09 soir) — « LE PROBLÈME EST GÉNÉRAL », ET C'EST
   MESURÉ, PAS SEULEMENT SOUPÇONNÉ. Pico pose `button[type=submit] {
   width:100% }` (élément + attribut = spécificité (0,1,1)). AUCUNE des
   classes de bouton ci-dessous ne porte de `width` propre — `.ai-btn` en
   composait déjà sans, et chaque écran qui en avait besoin posait son
   PROPRE `style="width:auto"` en ligne (personnel/fiche.html : le
   changement de contrat, l'ajout d'une absence — trois fois la même
   rustine avant ce correctif). Une classe seule (0,1,0) NE BAT JAMAIS
   (0,1,1), quel que soit l'ordre des feuilles : c'est pour ça que
   `.ai-btn-ghost--icon` (qui pose pourtant SON PROPRE `width:34px`)
   perdait aussi contre Pico sur un `<button type="submit">` — mesuré sur
   le bouton « Supprimer » d'une ligne d'absence, 125,75 px de large pour
   une icône de 17 px avant ce correctif.
   ⭐ `button.ai-btn` etc. (élément + classe) EST (0,1,1) — À ÉGALITÉ avec
   Pico, et cette feuille se charge APRÈS la sienne (base.html) : l'égalité
   se tranche par l'ordre des feuilles, et celle-ci gagne. Un SEUL correctif
   ici retire le besoin de la rustine en ligne PARTOUT dans le produit —
   les rustines déjà posées (personnel/fiche.html) restent inoffensives
   (une largeur explicite l'emporte toujours sur `width:auto` du même
   poids), rien à leur retirer. */
button.ai-btn, button.ai-btn-ghost, button.ai-btn-ghost--icon, button.ai-btn-outline { width:auto; }
button.ai-btn-ghost--icon { width:34px; }

.ai-btn {
  display:inline-flex; align-items:center; gap:var(--ai-s2); border:0;
  padding:11px 21px; font-family:var(--ai-font); font-size:var(--ai-fs-h3); font-weight:600;
  line-height:1; white-space:nowrap; cursor:pointer;
  background:var(--ai-accent); color:#fff; box-shadow:var(--ai-shadow-2);
  clip-path: polygon(0 0, calc(100% - var(--ai-chamfer)) 0, 100% var(--ai-chamfer),
                     100% 100%, var(--ai-chamfer) 100%, 0 calc(100% - var(--ai-chamfer)));
}
.ai-btn:hover { background:var(--ai-accent-2); }
.ai-btn:focus-visible { box-shadow:inset 0 0 0 2px #fff, var(--ai-shadow-2); outline:0; }
.ai-btn[disabled],.ai-btn:disabled {
  background:#e4e8ee; color:var(--ai-disabled); box-shadow:none; cursor:not-allowed; pointer-events:none;
}
.ai-btn .ti { font-size:var(--ai-fs-icon-md); }
.ai-btn--sm { padding:9px 17px; font-size:var(--ai-fs-body); }

/* ⛔ LOT C4 (02.09.2026) — `.ai-btn--ghost` ETAIT UNE CLASSE MORTE.
   components.css:27 la definissait (fond transparent), mais CE fichier est
   charge APRES (base.html:28 puis :33) et son `.ai-btn` a la meme specificite
   (0,1,0) : a egalite, la derniere regle gagne, et le fond plein --ai-accent
   ecrasait le fantome A CHAQUE FOIS. Mesure au navigateur : 38 boutons voulus
   fantomes par leur auteur rendaient PLEINS — 34 « Creer le compte » sur
   /comptes (comptes/index.html:434, un par personne sans compte) et 4
   « Appliquer » sur /activites-competences (activites_competences.html:129,
   un par groupe). Decision de Brice : reparer la cascade, pas les 38 boutons.
   REPARE ICI, dans le fichier qui gagne, et dans le langage 3a : le resultat
   est le fantome neutre du § 5.2 (fond surface, encre, lisere --ai-line-2),
   PAS le fantome sans bordure de components.css — celui-ci aurait ajoute 38
   fantomes nus aux 21 deja comptes (LOT6_PLAN_CONFORMITE § 0.3).
   ⚠️ LE LISERE EST PORTE PAR ::before, pas par un <span> parent : un element
      qui n'a qu'une classe ne peut pas se donner un parent. Meme geometrie
      que .ai-btn-outline--neutral > .ai-btn-ghost : chanfrein 12 px dehors
      (celui de .ai-btn), 1 px de lisere, chanfrein 11 px dedans. Le fond du
      bouton EST le lisere ; ::before peint la surface par-dessus, sous le
      texte (isolation:isolate + z-index:-1). Padding = 9.5+1 / 19.5+1 :
      meme encombrement que lisere + fantome.
   ⚠️ FOCUS : CORRIGE ICI le 10.09.2026 (F3, NOTES_DETTE.md) — le fantome
      canonique `.ai-btn-ghost` recoit desormais un anneau (currentColor,
      inset, meme procede que la variante --danger du § 4bis). C'est le prix
      d'un alias annonce ci-dessus : `currentColor` resout a `--ai-ink`, la
      couleur deja posee sur `.ai-btn--ghost` (ligne ci-dessous), donc la
      meme regle marche pour les deux sans jeton duplique.
   ⚠️ DESACTIVE : `.ai-btn[disabled]` (0,2,0) l'emporte sur `.ai-btn--ghost`
      (0,1,0) : le lisere devient #e4e8ee et l'encre --ai-disabled, ::before
      garde la surface. Aucun porteur desactive aujourd'hui.
   ORDRE DANS LE FICHIER, non negociable : APRES .ai-btn, .ai-btn:hover et
   .ai-btn:focus-visible — a specificite egale c'est la position qui repare. */
.ai-btn--ghost {
  position:relative; isolation:isolate;
  padding:10.5px 20.5px;
  background:var(--ai-line-2); color:var(--ai-ink); box-shadow:none;
}
.ai-btn--ghost::before {
  content:""; position:absolute; inset:1px; z-index:-1;
  background:var(--ai-surface);
  clip-path: polygon(0 0, calc(100% - 11px) 0, 100% 11px, 100% 100%, 11px 100%, 0 calc(100% - 11px));
}
.ai-btn--ghost:hover { background:var(--ai-line-2); }
.ai-btn--ghost:hover::before { background:var(--ai-surface-2); }
.ai-btn--ghost:focus-visible { box-shadow:inset 0 0 0 2px currentColor; outline:0; }

.ai-btn-outline {
  display:inline-block; padding:1.5px; background:var(--ai-accent);
  clip-path: polygon(0 0, calc(100% - var(--ai-chamfer)) 0, 100% var(--ai-chamfer),
                     100% 100%, var(--ai-chamfer) 100%, 0 calc(100% - var(--ai-chamfer)));
}
.ai-btn-outline--neutral { background:var(--ai-line-2); padding:1px; }
.ai-btn-ghost {
  display:inline-flex; align-items:center; gap:var(--ai-s2); border:0;
  padding:9.5px 19.5px; font-family:var(--ai-font); font-size:var(--ai-fs-h3); font-weight:600;
  line-height:1; white-space:nowrap; cursor:pointer;
  background:var(--ai-surface); color:var(--ai-accent);
  clip-path: polygon(0 0, calc(100% - 11px) 0, 100% 11px, 100% 100%, 11px 100%, 0 calc(100% - 11px));
}
.ai-btn-outline--neutral .ai-btn-ghost { color:var(--ai-ink); }
.ai-btn-ghost:hover { background:var(--ai-accent-soft); }
/* ⛔ LOT 6d — UN BOUTON 3a PORTE PAR UN <a> SE SOULIGNAIT AU SURVOL.
   `a:hover { text-decoration:underline }` (ligne 63 de ce fichier) s'applique
   a TOUT lien, y compris a ceux qui portent .ai-btn / .ai-btn-ghost. Le
   referentiel ne prevoit aucun soulignement sur un bouton.
   ⚠️ On le corrige ICI, dans le systeme, plutot qu'avec un style= en ligne sur
      chaque lien : le plan du lot 6 compte 47 cliquables deja pollues par un
      style= en ligne, on n'en ajoute pas. Cette regle repare aussi les liens
      3a DEJA conformes du produit (patterns_personnel/list.html:335 et les
      deux « Trame temporaire »), qui se soulignaient eux aussi. */
a.ai-btn:hover, a.ai-btn-ghost:hover, .ai-btn-outline:hover a.ai-btn-ghost { text-decoration:none; }


.ai-btn-outline--neutral .ai-btn-ghost:hover { background:var(--ai-surface-2); }
/* ⛔ F3 (NOTES_DETTE.md) — LE FANTOME 3a N'AVAIT NI FOCUS NI DESACTIVE.
   Le lot 6a avait ecrit ces deux etats pour la variante --danger seulement
   (§ 4bis plus bas) ; ils touchaient .ai-btn-ghost--danger, jamais le
   fantome canonique dont hérite tout le reste du produit — les 21 fantomes
   3a recenses au § 4bis (LOT I) en dependent tous, neutre ou accent, puisque
   seule la couleur change entre les variantes (ligne 159).
   Meme procede que le fantome danger (§ 4bis, meme raison : le clip-path
   mange le focus natif, un lisere INTERIEUR survit au chanfrein).
   `currentColor` plutot qu'un jeton fixe : ce fantome change de couleur
   selon son parent (accent par defaut, ink sous --neutral, ligne 159) — un
   jeton unique serait faux pour l'une des deux variantes. */
.ai-btn-ghost:focus-visible { box-shadow:inset 0 0 0 2px currentColor; outline:0; }
.ai-btn-ghost[disabled],.ai-btn-ghost:disabled {
  color:var(--ai-disabled); background:var(--ai-surface); cursor:not-allowed; pointer-events:none;
}
.ai-btn-outline:has(.ai-btn-ghost:disabled) { background:var(--ai-line-2); }
.ai-btn-ghost .ti { font-size:var(--ai-fs-icon-md); }
.ai-btn-ghost--sm { padding:8px 15px; font-size:var(--ai-fs-body); font-weight:500;
  clip-path: polygon(0 0, calc(100% - 10px) 0, 100% 10px, 100% 100%, 10px 100%, 0 calc(100% - 10px)); }
.ai-btn-ghost--icon { width:34px; height:32px; padding:0; justify-content:center; color:var(--ai-ink);
  clip-path: polygon(0 0, calc(100% - 9px) 0, 100% 9px, 100% 100%, 9px 100%, 0 calc(100% - 9px)); }
.ai-btn-ghost--icon .ti { font-size:var(--ai-fs-icon-md); }


/* --- 4bis. LARGEURS — LA REGLE, ECRITE UNE FOIS -------------------------
   ⛔ LOT E (02.09.2026) — DETTE C6 OUVERTE. Brice : « on a fait /comptes au
      cas par cas. On arrete ca. La regle vit dans le theme, elle s'applique
      partout, et on ne recommence pas la reflexion a chaque ecran. »

   LE DEFAUT, MESURE SUR 12 ECRANS x 2 MODES LE 02.09 :
       Pico pose `input:not([type=checkbox],[type=radio]), select, textarea,
       button[type=submit] { width:100% }`. Tout champ et tout bouton de
       soumission prend donc TOUTE la place de sa colonne. Sur
       /personnel/{id}/edit : 16 champs sur 19 depassent 300 px — 405 px pour
       saisir « AD », 822 px pour un email, et « Enregistrer » barre l'ecran
       sur 884 px.

   LA REGLE : LA LARGEUR D'UN CHAMP DIT CE QU'ON ATTEND DEDANS.
   Trois largeurs, et pas quatre. Les valeurs ne sont pas rondes par gout :
   elles sortent d'une mesure au canvas, dans la police REELLEMENT calculee
   des champs (Space Grotesk 14 px), plus le cadre mesure (padding 8/11 +
   bordure 1 = 24 px, box-sizing:border-box) :

       contenu vise                              texte   + cadre   retenu
       « MMMM » 4 capitales                       48,4      72      \
       « 09:00 » une heure                        39,0      63       > courte  110
       « 100 » un nombre                          23,8      48      /
       « Silva Dos Santos » le plus long nom     110,6     135      moyenne   220
       « Cardiologie interventionnelle »         194,2     218      \
       « prenom.nom@cardiologie-vaud.ch »        233,4     257       > longue  340
                                                                    /
   POURQUOI TROIS ET PAS CINQ. J'avais commence a distinguer « code » (80 px,
   4 caracteres) de « courte » (110 px, un nombre). L'ecart ne porte aucune
   information : 110 px contient deja « MMMM » avec 38 px d'air, et 110 est
   DEJA la valeur que le produit emploie sur cet ecran meme
   (duree_contractuelle_hebdo, jours_vacances_annuels). Une echelle se juge au
   nombre de decisions qu'elle epargne, pas a sa finesse. Trois valeurs se
   retiennent ; cinq se re-discutent a chaque champ, et c'est exactement ce
   qu'on arrete.

   ⚠️ `max-width` ET PAS `width`, ET C'EST LE POINT QUI EVITE LE PIEGE DES
      21 px. Une largeur imposee casserait les colonnes etroites et les petits
      ecrans : le champ deborderait au lieu de retrecir. Un plafond laisse la
      grille faire son travail et ne fait que L'EMPECHER DE S'ETALER. Aucune
      mise en page existante ne peut donc se casser : un champ deja plus etroit
      que son plafond ne bouge pas d'un pixel.

   ⚠️ ET LA REGLE NE S'APPLIQUE PAS AU PRODUIT ENTIER D'UN COUP. Elle est
      portee par `.ai-champs` / `.ai-barre-actions`, qu'un ecran pose UNE
      FOIS.
      ⛔ CES DEUX NOMS ONT ETE CHOISIS APRES UNE COLLISION MESUREE :
         `.ai-form` et `.ai-actions` EXISTENT DEJA, definies localement
         dans pilotage/new.html:32 et pilotage/detail.html:48. Ma regle
         s'y serait appliquee sans que personne ne l'ait decide — et je ne
         peux pas en mesurer l'effet : le compte de verification n'a pas le
         droit pilotage, ces deux ecrans rendent 403. On ne pose pas une
         regle sur ce qu'on ne peut pas voir. C'est
      la lecon du lot 6e, ou la meme correction ecrite globalement remontait
      52 boutons de 21 px sur 8 ecrans que personne n'avait demandes. Le cout
      par ecran est UNE classe sur le formulaire, pas une decision par champ :
      la reflexion, elle, n'est faite qu'ici.                              */

:root {
  --ai-w-courte:  110px;   /* ~11 caracteres : un nombre, un taux, une heure */
  --ai-w-moyenne: 220px;   /* ~25 caracteres : un nom, un prenom, un lieu    */
  --ai-w-longue:  340px;   /* ~40 caracteres : un email, un libelle          */
}

/* Le defaut, par TYPE de champ : ce que le champ contient se lit deja dans sa
   declaration. Un ecran qui pose `.ai-form` herite de tout ceci sans avoir a
   qualifier ses champs un par un. */
.ai-champs input[type="text"],
.ai-champs input[type="search"],
.ai-champs input[type="tel"],
.ai-champs input[type="password"],
.ai-champs select                 { max-width: var(--ai-w-moyenne); }
/* ⚠️ CORRIGE APRES MESURE : un email et une URL sont `longue`, pas `moyenne`.
   Ma premiere version les rangeait dans `moyenne` (220 px) alors que j'avais
   moi-meme mesure qu'un email plausible coute 257 px cadre compris. Le
   navigateur a rendu le champ a 220 : trop court pour ce qu'il doit contenir.
   On corrige la regle, pas la mesure — c'est le sens meme de « la largeur dit
   ce qu'on attend dedans ». */
.ai-champs input[type="email"],
.ai-champs input[type="url"]      { max-width: var(--ai-w-longue); }
.ai-champs input[type="number"],
.ai-champs input[type="date"],
.ai-champs input[type="time"],
.ai-champs input.hhmm,
.ai-champs input.pf-time          { max-width: var(--ai-w-courte); }
.ai-champs textarea               { max-width: 100%; }
/* Le selecteur de couleur a sa propre taille (une pastille), il n'est pas
   un champ de saisie : on ne le plafonne pas. */
.ai-champs input[type="color"]    { max-width: none; }

/* ⛔ ET LE CHAMP RESTE SUR SA PROPRE LIGNE, SOUS SON ETIQUETTE.
   MESURE DU 02.09, ET C'EST UNE REGRESSION QUE J'AI INTRODUITE PUIS VUE A LA
   CAPTURE : plafonner la largeur ne suffit pas. Un champ a `width:100%` occupe
   toute la ligne et FORCE le retour a la ligne apres le texte de son
   <label> ; des qu'il devient plus etroit que la ligne, il remonte A COTE du
   texte. Resultat mesure : « FONCTION * » colle au selecteur, « INITIALES * »
   et son champ ecartes aux deux bouts de la colonne — une mise en page que
   personne n'avait demandee, produite par un correctif de LARGEUR.

   `display:block` retablit ce que la largeur assurait par accident : le champ
   sous son etiquette, toujours. On rend explicite ce qui tenait a un effet de
   bord — c'est la meme lecon que le commentaire qui affirme contre le
   mecanisme qui impose.
   ⚠️ Portee volontairement etroite : `label > champ`. Un champ pose dans une
      rangee `flex` (le selecteur de specialite et ses deux boutons, par
      exemple) n'est PAS enfant direct d'un <label> et garde sa disposition. */
.ai-champs label > input:not([type="checkbox"]):not([type="radio"]),
.ai-champs label > select,
.ai-champs label > textarea       { display: block; }

/* --- LOT F (02.09.2026) — LA FORME D'UN CHAMP -------------------------
   ⛔ LA MAQUETTE NE PRESCRIT PAS CE BLOC, ET C'EST ECRIT ICI POUR QUE
      PERSONNE NE LE CROIE. `2607_26_KAIROS_REFERENCE_3a_Instrument_Pro_v2.html`
      est un tableau de bord : 112 <td>, 26 <th>, 9 <button> — et ZERO
      <input>, <select>, <textarea> ou <label>. Elle ne dit donc rien de la
      hauteur d'un champ, de son padding, ni de savoir s'il porte un chanfrein.
      Les deux valeurs ci-dessous sont des DECISIONS DE BRICE du 02.09, prises
      apres mesure et apres qu'il ait tranche. On ne devine pas un referentiel :
      quand il se tait, quelqu'un decide, et on ecrit qui.

   (1) HAUTEUR UNIQUE = `--control-h` (37 px).
       Mesure de l'ecran avant ce lot : TROIS hauteurs sur 18 champs —
       62,5 px (13 fois), 38 px (4 fois), 80 px (1 fois).
       Pourquoi 37 et pas 34 (la valeur du bouton DE LA MAQUETTE) : un champ et
       son bouton se regardent sur la meme ligne, et c'est le PRODUIT qui pose
       37 px, sur 60+ boutons. Aligner les champs sur 34 sans toucher aux
       boutons creerait un desaccord de 3 px partout.
       ⭐ Et le jeton du produit le disait deja : `tokens.css:85` definit
          `--control-h: 37px` avec le commentaire « hauteur unique boutons /
          champs / datepicker ». La regle existait, elle n'etait pas appliquee.

   (2) AUCUN CHANFREIN SUR UN CHAMP.
       La maquette chanfreine les ACTIONS (boutons), les ALERTES
       (.ai-card--alert) et les PASTILLES (.ai-status) — jamais les SURFACES :
       ni .ai-card, ni une cellule de table n'en portent. Un champ de saisie est
       une surface, pas une action. Deduction validee par Brice.

   ⚠️ `height` ET PAS `min-height` : les 62,5 px venaient d'une regle qu'on
      n'annule pas en discutant sa provenance. Pour un desaccord de geometrie,
      on impose des valeurs explicites identiques plutot que de re-theoriser la
      cascade a chaque champ.
   ⚠️ Le <textarea> est EXCLU : sa hauteur EST son contenu. Il recoit un
      plancher, pas un plafond.                                              */
.ai-champs input:not([type="checkbox"]):not([type="radio"]):not([type="color"]):not([type="hidden"]),
.ai-champs select {
  /* ⚠️ `min-height` ET `height`, ET C'EST UNE MESURE. Avec `height` seul, le
     champ ressortait a 38 px et pas 37 : Pico pose un `min-height` sur les
     champs, et EN CSS UN `min-height` L'EMPORTE TOUJOURS SUR `height`, quelle
     que soit la specificite. Le plancher gagnait de 1 px, en silence.
     On pose donc les deux a la meme valeur : plus rien ne peut les separer. */
  height: var(--control-h, 37px);
  min-height: var(--control-h, 37px);
  padding: 0 11px;
  border-radius: 0;
  clip-path: none;                    /* explicite : un champ n'est pas chanfreine */
  box-sizing: border-box;
  font-family: var(--ai-font);
  font-size: var(--ai-fs-h3, 14px);
  /* ⚠️ `background-color` ET PAS `background`. Le raccourci remet TOUTES les
     sous-proprietes a leur valeur initiale — dont `background-image`, avec
     laquelle Pico dessine LE CHEVRON DU SELECTEUR. Ma premiere version
     ecrivait `background: var(--ai-surface)` : les 4 <select> de la fiche
     perdaient leur fleche et devenaient indiscernables d'un champ texte.
     Vu a la capture, pas au code : aucune erreur, aucun ecart de comptage. */
  background-color: var(--ai-surface);
  color: var(--ai-ink);
  border: 1px solid var(--ai-line-2);
}
.ai-champs input:focus,
.ai-champs select:focus,
.ai-champs textarea:focus {
  outline: none;
  border-color: var(--ai-accent);
  box-shadow: 0 0 0 2px var(--ai-accent-soft);
}

/* ⛔ CORRIGE LE 08.09 — LE TEXTE RECOUVRAIT LE CHEVRON.
   Pico reserve `padding-right: calc(... + 1.5rem)` sur un <select> pour que le
   chevron (une image de fond) ait sa place. Le `padding: 0 11px` ci-dessus
   ecrase cette reserve pour tous les champs d'un coup — sur un <select>
   assez rempli (« bureau 13 (LAUSANNE) — type incompatible »), le texte va
   jusqu'au bord et passe SOUS le chevron : capture du 08.09,
   `garde_type_formulaire.png`, aucune sonde de geometrie ne peut le voir
   (un <select> ne rend pas son scrollWidth). Seul le padding-right du
   <select> est corrige : le texte reste a 11px partout ailleurs. */
.ai-champs select {
  padding-right: calc(11px + 1.5rem);
}

/* ⛔ LE RAYON ZERO EST *SCOPE*, ET LA MESURE DIT POURQUOI.
   La maquette n'a AUCUN element arrondi — la regle est donc claire. Mais la
   portee ne l'est pas : mesure du 02.09 sur 16 ecrans du produit,
   **341 elements arrondis**, dont 134 boutons (Pico pose 5 px sur tout
   <button>), 110 champs, 26 selecteurs, 20 pastilles rondes a 50 %.
   Une regle globale `border-radius:0` toucherait ces 341 elements d'un coup —
   c'est le piege des 21 px du lot 6e a dix fois l'echelle, et personne ne l'a
   demande. On la pose donc sur CE QUE LE LOT F COUVRE, et le reste est inscrit
   en dette (R1) avec son declencheur : un lot dedie, captures avant/apres sur
   tous les ecrans. */
.ai-champs input,
.ai-champs select,
.ai-champs textarea,
.ai-champs button,
.ai-barre-actions button,
.ai-barre-actions a[role="button"] { border-radius: 0; }

/* ⛔ LOT G (02.09.2026) — L'ECHAPPATOIRE QUE MA PROPRE MISE EN GARDE EXIGEAIT.
   Au lot F j'ai ecrit, dans la dette R1 : « 20 des 341 sont des pastilles a
   50 % — un rond VOLONTAIRE n'est pas un coin arrondi involontaire, et une
   regle aveugle transformerait 20 ronds en carres. » C'est arrive le jour
   meme, sur cet ecran : les 5 pastilles de couleur et l'apercu sont devenus
   carres. ⚠️ Et pas a cause de la regle scopee ci-dessus — A CAUSE DE MOI :
   mon script du lot F remplacait `border-radius:50%` par `0` EN AVEUGLE dans
   le balisage, sans regarder ce que chaque rond voulait dire.

   `.ai-rond` est la reponse durable : une forme ronde qui se DECLARE survit a
   n'importe quelle regle de mise a plat, presente ou future. Le prochain lot
   qui traitera les 341 arrondis n'aura pas a deviner lesquels sont voulus — il
   lira la classe.
   ⚠️ Le `!important` est ici a sa place : cette regle doit gagner contre une
      remise a plat, c'est toute sa raison d'etre. */
.ai-rond, .ai-champs .ai-rond, .ai-barre-actions .ai-rond { border-radius: 50% !important; }

/* ⛔ LOT I (02.09.2026) — UN BOUTON RADIO EST ROND, ET MA REGLE LE CARRAIT.
   TROISIEME FOIS QUE LA MEME LECON SE PRESENTE, et cette fois c'est ma regle
   du lot F qui est en cause : `.ai-champs input { border-radius: 0 }` inclut
   `input[type=radio]`. Des que le lot H a pose `.ai-champs` sur le formulaire
   des trames, les boutons radio de « DISPONIBILITE » sont devenus CARRES.
   Mesure : `border-radius: 0px` sur des radios de 18x18.

   ⭐ ET `.ai-rond` NE SUFFIT PAS ICI. Elle demande qu'on DECLARE la forme sur
      chaque element ; on ne peut pas exiger qu'un gabarit pose une classe sur
      chaque radio du produit pour obtenir la forme que le navigateur donne
      gratuitement. UNE FORME QUI EST DANS LE TYPE SE PROTEGE PAR LE TYPE :
      un radio est rond parce que c'est un radio, pas parce qu'on l'a dit.
   La regle de mise a plat s'arrete donc AVANT les controles de choix. */
.ai-champs input[type="radio"] { border-radius: 50%; }

/* ⛔ ET LEUR ETAT COCHE PORTAIT LE BLEU DE PICO, PAS CELUI DU PRODUIT.
   Mesure : une case cochee ressort en `rgb(1,114,173)` — le primaire de Pico —
   la ou l'accent du produit est `rgb(28,86,160)` (--ai-accent). Deux bleus
   voisins qui ne sont pas le meme bleu, c'est ce qui fait dire « carres bleus
   pleins d'un cote, cases classiques de l'autre ».
   ⚠️ `background-color` ET PAS `background` : Pico dessine la COCHE avec une
      `background-image`. Le raccourci l'effacerait — la case cochee deviendrait
      un carre plein sans coche. C'est le piege du lot F, evite ici par regle. */
.ai-champs input[type="checkbox"]:checked,
.ai-champs input[type="radio"]:checked {
  background-color: var(--ai-accent);
  border-color: var(--ai-accent);
}

/* ⛔ LOT I — LA MARGE FANTOME DE PICO REMONTE D'UN CRAN, ET LA MESURE LE PERMET.
   Au lot C5 la correction etait posee sur `.cp-t button` — l'ecran /comptes
   seul — parce que la dette C5 parlait de « 52 boutons sur 8 ecrans » et qu'une
   regle globale aurait tout deplace.
   MESURE DU 02.09, SUR LES 16 ECRANS DE LA RONDE, avant d'ecrire cette regle :
       21 fantomes 3a (.ai-btn-outline > .ai-btn-ghost) au total
        4 debordent aujourd'hui : /absences 1, /planning 1, /audit-log 2
       17 sont deja corrects (marge deja a zero, par une regle locale)
       83 boutons NUS trainent encore une marge basse — hors de cette regle
   La regle ci-dessous ne touche donc QUE les 21 fantomes 3a, et n'en CHANGE
   que 4. Les 83 boutons nus restent la dette C5, intacte : c'est eux qui
   deplaceraient la mise en page de 8 ecrans, pas ceux-ci.
   ⭐ Pourquoi ici et pas ecran par ecran : la marge est un defaut DU GABARIT 3a
      (un <button> de Pico dans un <span> colore), pas d'un ecran. Elle se
      corrige la ou le gabarit est defini. */
.ai-btn-outline > .ai-btn-ghost { margin-bottom: 0; }
.ai-champs textarea {
  min-height: calc(var(--control-h, 37px) * 2);
  padding: 8px 11px;
  border-radius: 0;
  clip-path: none;
  box-sizing: border-box;
  font-family: var(--ai-font);
  font-size: var(--ai-fs-h3, 14px);
  background-color: var(--ai-surface);
  color: var(--ai-ink);
  border: 1px solid var(--ai-line-2);
  resize: vertical;
}

/* Et l'exception nommee, quand le type ne suffit pas a dire le contenu. */
.ai-w-courte  { max-width: var(--ai-w-courte)  !important; }
.ai-w-moyenne { max-width: var(--ai-w-moyenne) !important; }
.ai-w-longue  { max-width: var(--ai-w-longue)  !important; }
.ai-w-pleine  { max-width: 100% !important; }

/* --- 4ter. UN BOUTON A SA TAILLE NATURELLE -----------------------------
   Meme cause (`button[type=submit]{width:100%}` de Pico), meme remede, meme
   portee : `.ai-actions` sur la rangee de boutons. On ecrit `width:auto` et
   non un plafond, parce qu'un bouton n'a pas de contenu variable a proteger :
   sa largeur EST celle de son libelle.                                    */
.ai-barre-actions button,
.ai-barre-actions input[type="submit"],
.ai-barre-actions a[role="button"],
.ai-barre-actions .ai-btn,
.ai-barre-actions .ai-btn-ghost       { width: auto; }

/* --- 4quater. UNE CASE A COCHER RESSEMBLE A UNE CASE A COCHER -----------
   ⛔ LOT E / D2 — LES FAUSSES CASES. Sur la fiche personnel, « HEURES
      SUPPLEMENTAIRES AUTORISEES », « NON APPLICABLE », « DIRECTEUR »,
      « RESPONSABLE D'EQUIPE », « PERSONNEL ACTIF » sont de VRAIES cases a
      cocher (`input[type=checkbox]`, mesurees visibles, opacite 1) — mais
      leur etiquette porte une bordure, un fond et un padding qui en font des
      PASTILLES. Un rectangle encadre qui change de couleur quand on clique,
      c'est un bouton a bascule ; ce n'en est pas un.
      Consequence mesuree : 9 cases sur cet ecran, dont l'etat coche se lit a
      la couleur du cadre et pas au signe universel qui dit « coche ».

   CE QU'ON FAIT : on retire le costume, on garde la case. Pas de bordure,
   pas de fond, pas de pastille — la case a cocher du navigateur, agrandie
   pour etre visible et cliquable, et son texte a cote. L'etat se lit dans la
   case elle-meme, comme partout ailleurs.
   ⚠️ La zone cliquable reste TOUTE l'etiquette (c'est un <label>) : on ne
      perd pas la cible large en perdant le cadre.                         */
.ai-check {
  display:inline-flex; align-items:center; gap:9px;
  padding:6px 0; margin:0 18px 6px 0;
  border:0; background:none; border-radius:0;
  font-family:var(--ai-font); font-size:var(--ai-fs-body); font-weight:400;
  color:var(--ai-ink); cursor:pointer; user-select:none;
  text-transform:none; letter-spacing:0;
}
.ai-check input[type="checkbox"],
.ai-check input[type="radio"] {
  width:17px; height:17px; min-height:0; margin:0; flex:none;
  accent-color:var(--ai-accent); cursor:pointer;
  border:0; box-shadow:none;
}
/* Coche = le texte s'affirme, mais le cadre ne revient pas : c'est la case
   qui porte l'etat, pas une pastille coloree. */
.ai-check:has(input:checked) { color:var(--ai-ink); font-weight:600; }
.ai-check:hover { color:var(--ai-accent); }
.ai-check input:disabled { cursor:not-allowed; }
.ai-check:has(input:disabled) { color:var(--ai-disabled); cursor:not-allowed; }

/* --- 4bis. ACTION DESTRUCTIVE (LOT 6a) ---------------------------------
   La reference 3a ne definissait AUCUN bouton de destruction. Consequence
   mesuree au lot 6 : chaque "Supprimer" de l'app avait ete invente sur place
   (ab-btn-danger, confirm-danger, btn btn-danger, kx-del, aiui-btn--danger,
   un class="secondary" repeint). Ces deux variantes remplissent la case.
   ELLES NE SONT ENCORE PORTEES PAR AUCUN BOUTON : le lot 6a ecrit le
   referentiel, la mise en conformite est 6c/6d/6e.

   REGLE (decision de Brice) : le FANTOME est la forme NORMALE. Un ecran a
   une seule action principale, et "Supprimer" n'est jamais l'action
   principale d'un ecran. Le rouge PLEIN est reserve a la modale de
   confirmation - la, et la seulement, supprimer EST l'action principale.

   ORDRE DANS LE FICHIER, non negociable : ce bloc vient APRES .ai-btn:hover
   et .ai-btn-ghost:hover. .ai-btn:hover (classe + pseudo-classe) l'emporte
   sur .ai-btn--danger (classe seule) ; sans les regles :hover reecrites
   ci-dessous, un bouton destructif redeviendrait BLEU au survol.         */

/* PLEIN - modale de confirmation UNIQUEMENT. */
.ai-btn--danger { background:var(--ai-danger); }
.ai-btn--danger:hover { background:var(--ai-danger-2); }
/* base.html:83 force .ti-trash en #A32D2D avec !important, explicitement
   "partout, sans exception". Dans un bouton rouge plein, l'icone poubelle
   devenait donc INVISIBLE - rouge sur rouge. Constate au navigateur, pas
   deduit. Regle : dans un bouton plein, l'icone prend la couleur de TEXTE du
   bouton. Le !important est ici le seul moyen de repondre a un !important. */
.ai-btn--danger .ti { color:#fff !important; }
/* Desactive : rien a ecrire. .ai-btn[disabled] (gris, sans ombre) l'emporte
   deja sur .ai-btn--danger, et c'est le bon rendu - un bouton inerte n'a pas
   a afficher son intention. Verifie au navigateur, pas suppose.           */

/* FANTOME - la forme NORMALE d'un "Supprimer". */
/* Le lisere est porte par le <span> PARENT, comme pour tout fantome 3a :
   clip-path decoupe les bordures ET l'outline, un lisere pose sur le bouton
   lui-meme manquerait le long des deux chanfreins. Un .ai-btn-ghost--danger
   sans son .ai-btn-outline--danger est NON CONFORME, exactement comme un
   .ai-btn-ghost sans son cercle (reference § 5.2).                        */
.ai-btn-outline--danger { background:var(--ai-danger); padding:1px; }
.ai-btn-ghost--danger { color:var(--ai-danger); }
.ai-btn-ghost--danger:hover { background:var(--ai-danger-soft); }
/* Focus : la reference ne donne AUCUN etat de focus au fantome, et le focus
   natif du navigateur est mange par le clip-path. SEUL POINT OU J'EXTRAPOLE.
   Je reprends le procede du bouton plein - un lisere INTERIEUR, qui survit
   au chanfrein - dans la couleur du bouton plutot qu'en blanc, un lisere
   blanc etant invisible sur une surface blanche.                          */
.ai-btn-ghost--danger:focus-visible { box-shadow:inset 0 0 0 2px var(--ai-danger); outline:0; }
/* Desactive : le fantome GENERIQUE n'en a aucun dans la reference (trou
   signale au rapport, il appartient a 6c/6d). Ecrit ici parce qu'un gabarit
   qu'on ajoute doit etre complet. Le lisere du parent grise avec lui, sinon
   un cercle rouge vif encadrerait un libelle eteint.                      */
.ai-btn-ghost--danger[disabled],.ai-btn-ghost--danger:disabled {
  color:var(--ai-disabled); background:var(--ai-surface); cursor:not-allowed; pointer-events:none;
}
.ai-btn-outline--danger:has(.ai-btn-ghost--danger:disabled) { background:var(--ai-line-2); }
.ai-toolbar { display:flex; align-items:center; gap:9px; }

/* --- 4ter. SAISIE D'HEURE INACHEVEE (LOT 8) ------------------------------
   Le champ heure AIDE A TAPER, il ne FINIT PAS la phrase : « 17: » reste
   « 17: » quand on quitte le champ, au lieu de devenir « 17:00 » en silence
   (cf. time-input.js). Il faut donc que l'inachevement SE VOIE, et le dise.

   ⚠️ POSE PAR UNE CLASSE, PAS PAR `:invalid`. `:invalid` est vrai DES LA
      PREMIERE TOUCHE — « 1 », « 17 », « 17: » sont tous invalides pendant
      qu'on tape. Le champ clignoterait en rouge a chaque saisie normale, et
      un signal qui crie tout le temps ne dit plus rien. La classe n'est
      posee qu'a la SORTIE du champ, ou quand la validation native refuse.

   ⚠️ `outline` ET NON `border-color` : `edit.html:110` pose deja un
      `input[type="text"].hhmm:invalid { border-color:#E24B4A }` de
      specificite superieure. Se battre avec lui demanderait un
      `!important` ; l'outline n'est dispute par personne et se voit sur les
      DEUX ecrans concernes, ce que la regle locale ne fait pas.        */
input.pf-time--incomplet,
input.hhmm.pf-time--incomplet {
  outline: 2px solid var(--ai-warn-fg);
  outline-offset: 1px;
}
/* `flex:1 0 100%` : le message passe a la ligne dans une rangee flex
   (.pf-hpaire du formulaire de trame) et reste un bloc normal ailleurs
   (.pf-row de la fiche personnel). Une seule regle pour les deux ecrans. */
.pf-time-msg {
  display: block; flex: 1 0 100%;
  margin-top: var(--ai-s1);
  font-size: var(--ai-fs-meta); line-height: 1.35;
  color: var(--ai-warn-fg);
}

/* --- 5. CARTES ---------------------------------------------------------- */
.ai-cards { display:grid; grid-template-columns:repeat(4,1fr); gap:var(--ai-s3); margin-bottom:14px; }
/* ⛔⛔ QUATRE COLONNES NE TIENNENT PAS DANS UN TÉLÉPHONE — mesuré le 16.09.2026
   par le balayage de TOUS les écrans (item 105 a, clôture ; sonde
   `scripts/balayer_debordement_390.js`).
   `repeat(4,1fr)` a l'air élastique et ne l'est pas : un `1fr` ne descend
   jamais sous le `min-content` de sa carte, et `min-width:auto` (la valeur
   par défaut d'un enfant de grille) interdit de l'écraser. Sur /statistiques
   à 390 px rail replié, les quatre colonnes réclamaient 114 + 114 + 107 +
   144 = 479 px dans une zone de 328 : **202 px hors de l'écran**, la
   quatrième tuile (« Taux d'occupation », le chiffre le plus regardé de
   l'écran) entièrement dans le vide. Capture d'avant :
   `docs/2609_16_item105_balayage/statistiques_390_AVANT.png`.
   ⭐ `auto-fit` + `minmax` PLUTÔT QU'UN NOMBRE DE COLONNES PAR PALIER : la
      rangée se remplit d'elle-même, sans qu'un seuil soit à re-choisir le
      jour où une cinquième tuile arrive. Mesuré sur /statistiques, rail
      replié — zone utile, puis tuiles par ligne :
          390 px → 300 px de zone → 2 tuiles de 144 px (2 lignes)
          430 px → 340 px         → 2 tuiles de 164 px
          640 px → 550 px         → 3 tuiles de 175 px
          641 px et au-delà       → 4 tuiles (la règle d'ordinateur reprend)
   ⚠️ LE PLANCHER EST À 140 px ET IL A ÉTÉ MESURÉ, PAS DEVINÉ. Écrit d'abord
      à 150 px : à 390 px la rangée tombait à UNE seule colonne (300 px de
      zone, il en aurait fallu 312) et les quatre tuiles poussaient le
      premier tableau sous la ligne de flottaison — on lit un chiffre, et on
      cherche les trois autres. À 140 px, deux tuiles de 144 px tiennent
      exactement, et le contenu minimal d'une tuile en fait 117 : rien n'est
      écrasé. Dix pixels de différence sur une valeur, et c'est toute la
      lecture de l'écran qui change — d'où la mesure plutôt que le chiffre
      rond.
   ⚠️ CE QUI NE CHANGE PAS : au-dessus de 640 px, rien. La règle est sous
      media query et `repeat(4,1fr)` continue de commander l'ordinateur —
      vérifié écran par écran à 1280 px (0 px de débordement avant comme
      après, captures regardées). */
@media (max-width: 640px) {
  .ai-cards { grid-template-columns:repeat(auto-fit, minmax(140px, 1fr)); }
}
.ai-card { background:var(--ai-surface); border:1px solid var(--ai-line); padding:14px var(--ai-s4); }
.ai-card__label {
  display:flex; align-items:center; gap:7px; font-family:var(--ai-mono);
  font-size:var(--ai-fs-eyebrow); letter-spacing:.14em; text-transform:uppercase; color:var(--ai-faint);
}
.ai-card__label .ti { font-size:var(--ai-fs-icon-sm); }
.ai-card__value {
  font-family:var(--ai-mono); font-variant-numeric:tabular-nums; font-size:var(--ai-fs-display);
  font-weight:500; letter-spacing:-.04em; color:var(--ai-ink); margin-top:6px;
}
.ai-card__value small { font-size:var(--ai-fs-icon-md); color:var(--ai-muted); }
/* ⛔ ITEM 118 (16.09.2026) — QUAND UN INDICATEUR N'EST PAS CALCULABLE, IL LE
   DIT EN TOUTES LETTRES. Le tiret muet de la carte « Statistiques » ne
   distinguait pas « zero » de « jamais calcule » : c'est la definition meme du
   faux zero que cet item chasse. Le libelle remplace le numeral, donc il ne
   peut pas garder la taille d'un numeral — sinon il deborde de la carte a
   390 px. */
.ai-card__value--muet {
  font-size:var(--ai-fs-body); font-weight:400; letter-spacing:0;
  color:var(--ai-muted);
  /* La carte garde la hauteur qu'elle avait avec un numeral : une tuile qui
     retrecit quand sa valeur manque deplace ses voisines. */
  display:flex; align-items:center; min-height:calc(var(--ai-fs-display) * 1.2);
}
.ai-card__foot { font-size:var(--ai-fs-table); color:var(--ai-muted); margin-top:2px; }
.ai-card--alert {
  background:var(--ai-ink); border:0;
  clip-path: polygon(0 0, calc(100% - var(--ai-chamfer-lg)) 0, 100% var(--ai-chamfer-lg),
                     100% 100%, var(--ai-chamfer-lg) 100%, 0 calc(100% - var(--ai-chamfer-lg)));
}
/* ⛔ ENCRE PAR JETONS, JAMAIS EN DUR (item 74, 15.09.2026) : le fond
   `--ai-ink` s'inverse en sombre (creme) ; `#fff` et `#8b97a8` en dur y
   tombaient a 1.19 et 2.49:1, le pied a 2.20:1. En clair, les trois jetons
   valent les valeurs 3a : rien n'est repeint. Sonde :
   scripts/verifier_carte_alerte.js (vraie carte, sur banc jetable). */
.ai-card--alert .ai-card__label { color:var(--ai-on-ink-muted); }
.ai-card--alert .ai-card__value { color:var(--ai-on-ink); }
.ai-card--alert .ai-card__foot { color:var(--ai-on-ink-accent); font-weight:500; }
.ai-cards a.ai-card {
  position:relative; display:block; color:inherit; text-decoration:none;
  cursor:pointer; transition:border-color .15s ease;
}
.ai-cards a.ai-card:hover { border-color:var(--ai-line-2); color:inherit; text-decoration:none; }
.ai-card__go {
  position:absolute; top:14px; right:14px; font-size:var(--ai-fs-icon-md); color:var(--ai-faint);
  opacity:0; transform:translateX(-3px); transition:opacity .15s ease, transform .15s ease;
}
.ai-cards a.ai-card:hover .ai-card__go { opacity:1; transform:translateX(0); }
.ai-card--alert .ai-card__go { color:var(--ai-on-ink-accent); }

/* === COMPTEURS A ZERO — UNE SEULE FORME NEUTRE, POUR TOUT LE PRODUIT =======

   ⛔ POURQUOI CETTE CLASSE EXISTE. Le 03.09.2026 au matin, Brice a ouvert le
      tableau de bord et N'A PAS VU une vraie alerte de fin de contrat. Elle
      etait juste et elle menait a la bonne fiche — mais elle etait noyee dans
      une rangee ou la carte « Demandes a valider », A ZERO, portait deja le
      fond graphite de `.ai-card--alert`. UNE ALERTE SUR RIEN APPREND A IGNORER
      LES ALERTES : c'est un defaut de fiabilite, pas de cosmetique.

   LA REGLE, POSEE UNE FOIS : un compteur a zero s'affiche en etat NEUTRE.
   L'habit d'alerte — fond graphite, badge rouge, pastille de statut coloree —
   est reserve a une valeur QUI DEMANDE UNE ACTION.

   ⚠️ AUCUNE COULEUR NEUVE : `--ai-surface-2` et `--ai-disabled` sont deja au
      catalogue, et c'est deja l'habit de `.ai-tab-count` et de `.pe-count`,
      les deux compteurs qui se comportaient correctement. On generalise ce qui
      marchait ; on n'invente rien.

   ⚠️ DEUX MECANISMES, UNE SEULE FORME A L'ECRAN — et la raison est mesuree.
      1. La ou l'habit d'alerte est une CLASSE ENTIERE (`.ai-card--alert`, le
         badge « ! » `.fi-essential-flag`), le gabarit ne la pose tout
         simplement PAS a zero. Repeindre par-dessus aurait duplique
         `.ai-card` et laisse deux verites concurrentes dans la feuille.
      2. La ou la couleur vient d'un STYLE EN LIGNE (les pastilles-filtres de
         /pilotage, qui composent leur couleur de statut dans le gabarit),
         AUCUNE feuille de style ne peut la battre sans `!important`. Le
         gabarit n'emet donc plus le style en ligne a zero, et l'element
         retombe sur `.is-zero`.
      Dans les deux cas, ce que l'oeil voit a zero est LA MEME CHOSE.

   ⚠️ LE ::before EST ETEINT LUI AUSSI. `.ai-status` porte une marque de 8 px
      qui dit le statut. Laisser la marque coloree sur un compteur eteint
      aurait garde un point d'alerte sur un zero — soit exactement le defaut,
      en plus petit.

   ⛔ POURQUOI `.is-zero.is-zero` ET PAS `.is-zero`. Mesure de l'ordre de
      chargement : `base.html` charge components.css AVANT kairos-theme.css,
      mais plusieurs ecrans redefinissent leur compteur dans un `<style>` DE
      PAGE (`.pe-count` dans personnel/list.html, entre autres) — et un
      `<style>` de page vient APRES toutes les feuilles. A specificite egale
      (0,1,0), c'est lui qui gagne, et `.is-zero` n'aurait rien fait sur ces
      ecrans-la, EN SILENCE. Le nom double porte la classe a (0,2,0) : elle
      passe devant toute regle a une seule classe, quel que soit l'ordre, sans
      `!important` — un `!important` aurait ensuite oblige tout le reste du
      produit a surencherir.
      ⚠️ CE QU'ELLE NE BAT PAS, ET C'EST VOULU : `.ai-tab.is-active
         .ai-tab-count` (0,3,0). Sur l'onglet ACTIF, le compteur reste blanc
         sur le fond d'accent, comme le reste de l'onglet. « Actif » dit ou on
         se trouve, pas qu'il y a quelque chose a faire : ce n'est pas un habit
         d'alerte, il n'y a donc rien a eteindre. */
.is-zero.is-zero { background:var(--ai-surface-2); color:var(--ai-disabled); border-color:var(--ai-line); }
.is-zero.is-zero::before { background:var(--ai-disabled); border-color:var(--ai-disabled); }

.ai-gauge { height:5px; background:#e6e9ee; margin-top:9px; }
.ai-gauge > i { display:block; height:5px; background:var(--ai-accent); }
.ai-gauge--warn > i { background:var(--ai-warn); }
.ai-gauge--ok > i { background:var(--ai-ok); }

/* --- 6. BANDEAU SEMAINE ------------------------------------------------- */
.ai-weekbar {
  display:flex; align-items:center; justify-content:space-between; gap:var(--ai-s5);
  background:var(--ai-surface); border-top:1px solid var(--ai-line-soft);
  border-bottom:1px solid var(--ai-line); padding:14px 24px;
}
.ai-weekbar__left { display:flex; align-items:center; gap:var(--ai-s4); }
.ai-weekbadge {
  flex-shrink:0; background:var(--ai-ink); color:var(--ai-on-ink); padding:9px var(--ai-s4) 10px; text-align:center;
  clip-path: polygon(0 0, calc(100% - var(--ai-chamfer)) 0, 100% var(--ai-chamfer),
                     100% 100%, var(--ai-chamfer) 100%, 0 calc(100% - var(--ai-chamfer)));
}
.ai-weekbadge__label { font-family:var(--ai-mono); font-size:var(--ai-fs-eyebrow); letter-spacing:.18em; color:var(--ai-on-ink-muted); }
.ai-weekbadge__num { font-family:var(--ai-mono); font-size:var(--ai-fs-display); font-weight:600; letter-spacing:-.04em; line-height:1; margin-top:2px; }
.ai-weekbar__title { font-size:var(--ai-fs-h2); font-weight:600; letter-spacing:-.02em; color:var(--ai-ink); }
.ai-weekbar__meta {
  display:flex; align-items:center; gap:14px; margin-top:5px;
  font-family:var(--ai-mono); font-size:var(--ai-fs-meta); color:var(--ai-faint);
}
.ai-weekbar__today { color:var(--ai-accent); font-weight:500; display:inline-flex; align-items:center; gap:6px; }
.ai-weekbar__today::before { content:""; width:7px; height:7px; background:var(--ai-accent); }
.ai-weeknav { display:flex; align-items:center; gap:var(--ai-s2); flex-shrink:0; }

/* --- 7. TABLEAUX DENSES ------------------------------------------------- */
.ai-table-wrap { background:var(--ai-surface); border:1px solid var(--ai-line); }
.ai-table-wrap__head {
  display:flex; align-items:center; justify-content:space-between;
  padding:11px 14px; border-bottom:1px solid var(--ai-line);
}
.ai-table-wrap__title { font-size:var(--ai-fs-h3); font-weight:600; letter-spacing:-.01em; color:var(--ai-ink); }
table.ai-table { width:100%; font-size:var(--ai-fs-table); }
table.ai-table thead tr { background:var(--ai-thead-bg); }
/* T14 — LE FOND SOMBRE EST POSE SUR LE th, PAS SEULEMENT SUR LE tr.
   Le `thead tr` ci-dessus le portait deja, mais `components.css` posait
   `background:var(--surface-card)` (#FFFFFF) sur `.ai-table th` : la cellule
   recouvrait la ligne, l'en-tete s'affichait BLANC avec son texte
   `--ai-thead-fg` (#98a3b3) prevu pour du sombre. Mesure du 28.08 :
   contraste du texte d'en-tete = 2.553, quand le minimum lisible est 4.5.
   Le fond descend donc sur la cellule, la ou la couleur est peinte, et la
   declaration fautive a ete retiree de components.css : une seule source.
   Mesure apres : 7.227 sur les neuf tableaux. */
table.ai-table th {
  text-align:left; padding:9px 10px; font-weight:500; font-family:var(--ai-mono);
  font-size:var(--ai-fs-eyebrow); letter-spacing:.14em; text-transform:uppercase; color:var(--ai-thead-fg);
  background:var(--ai-thead-bg);
}
table.ai-table th:first-child, table.ai-table td:first-child { padding-left:14px; }
table.ai-table th:last-child, table.ai-table td:last-child { padding-right:14px; text-align:right; }
table.ai-table td { padding:9px 10px; color:var(--ai-ink-2); border-bottom:1px solid var(--ai-line-soft); }
table.ai-table tbody tr:last-child td { border-bottom:0; }
table.ai-table td:first-child { font-weight:600; color:var(--ai-ink); }
table.ai-table td.ai-num { text-align:right; color:var(--ai-ink); }
/* ⛔ `table.ai-table td.ai-num--zero` A ETE RETIREE ICI, ET C'EST LE POINT DE
   DEPART DE TOUT CE LOT. Elle disait la bonne intention — « un zero s'eteint » —
   mais son selecteur exigeait un `td` dans un `table.ai-table`, alors que ses
   TROIS SEULES POSES du produit etaient des `<span>` dans les tiroirs du
   tableau de bord. Mesure du 03.09 : le 0 et le 19 sortaient tous deux en
   `rgb(13, 20, 32)` — la classe etait posee, lue par personne, et laissait
   croire que la regle existait deja. La forme neutre unique `.is-zero`
   (§ COMPTEURS A ZERO, plus bas) la remplace partout. */
/* LOT K point 3 (cause racine, 2e occurrence) : Sites & salles (crayon +
   poubelle groupes, sans cadre) est LA reference - mais son resserrement
   ne tenait qu'a un margin-left:6px pose a la main sur la poubelle. Un
   autre ecran qui oubliait ce style local (Activites & competences)
   retombait sur l'espace blanc NATUREL entre elements inline separes par
   un retour a la ligne dans le template - variable et invisible a l'oeil
   nu dans le code, differente d'un fichier a l'autre selon l'indentation.
   Fix a la racine : le composant .ai-act lui-meme espace ses icones
   (flex + gap fixe), plus aucun template n'a besoin d'un margin-left
   local pour obtenir le meme rendu - et ne PEUT plus s'en ecarter par
   inadvertance. justify-content:flex-end reprend l'alignement a droite
   que text-align:right assurait avant (inerte sur les enfants d'un flex). */
table.ai-table td.ai-act { color:var(--ai-faint); display:flex; align-items:center; justify-content:flex-end; gap:8px; }
table.ai-table td.ai-act .ti { font-size:var(--ai-fs-icon-sm); }
/* ============================================================
   T14 — SURVOL ET SELECTION : SUR LE td, ET AVEC DES TEINTES QUI SE VOIENT
   ============================================================
   DEUX DEFAUTS, UNE SEULE CAUSE. Ces deux regles etaient posees sur le `tr`
   et peintes en `--ai-surface-2` / `--ai-row-selected`.

   1. POSEES SUR LE tr, ELLES NE SE VOYAIENT PAS. Pico (`pico.min.css`, la
      premiere feuille chargee par base.html) pose
      `td, th { background-color: var(--pico-background-color) }` — du BLANC
      OPAQUE sur chaque cellule. La cellule recouvre la ligne. Mesure du
      28.08 : la ligne selectionnee rendait `rgb(255,255,255)`, inchangee.
      **La selection ne peignait rien du tout** ; seul le liseret se voyait.
      Le survol, lui, etait repeint par une regle de `components.css`
      (`.ai-table tr:hover td { background:var(--surface-raised) }`) en
      #FAF8F1 — un beige venant de `tokens.css`, une palette etrangere
      appliquee par accident. Cette regle a ete retiree.

   2. LES TEINTES ETAIENT INDISTINGUABLES. Contre le blanc reel des lignes :
      --ai-surface-2 = 1.083, --ai-row-selected = 1.085, et 1.002 l'un de
      l'autre. Le seuil de perception pose le 27.08 dans NOTES_DETTE.md est
      1.10 : trois paires, trois echecs.

   LES TEINTES RETENUES SORTENT DES JETONS EXISTANTS, ET ELLES SONT MESUREES
      Douze jetons clairs confrontes au blanc, 66 combinaisons, 15 paires
      passant les trois contraintes, 5 respectant l'ordre « le survol pese
      moins que la selection » (un etat passager ne doit pas peser plus
      qu'un etat qui dure). Retenue :
          fond (blanc) -> survol (--ai-line-soft)  : 1.176
          fond (blanc) -> selection (--ai-line)    : 1.34
          survol       -> selection                : 1.14
      Les trois au-dessus de 1.10, mesurees sur les neuf tableaux.
      --ai-surface-2 et --ai-row-selected quittent ce role : ils ne peuvent
      pas distinguer deux etats, c'est ecrit dans NOTES_DETTE.md.

   POURQUOI LA COULEUR DU TEXTE CHANGE AUSSI SUR LA LIGNE SELECTIONNEE
      --ai-line est la teinte la plus claire qui soit a 1.10 du survol ET du
      blanc — les teintes plus douces du theme sont toutes tassees entre
      1.127 et 1.176, donc jamais a 1.10 l'une de l'autre. Sur ce fond, le
      texte `--ai-muted` des cellules secondaires tombe a 4.338, sous le
      minimum de 4.5. On le remonte a `--ai-ink-2` : 7.104. La premiere
      colonne garde son `--ai-ink` (13.77), pour ne pas perdre l'emphase
      qu'elle porte partout ailleurs.
   ============================================================ */
table.ai-table tbody tr:hover td { background:var(--ai-line-soft); }
table.ai-table tbody tr.is-selected td { background:var(--ai-line); color:var(--ai-ink-2); }
table.ai-table tbody tr.is-selected td:first-child { color:var(--ai-ink); box-shadow:inset 3px 0 0 var(--ai-accent); }
table.ai-table tbody tr.is-selected td.ai-act { color:var(--ai-accent); }

/* ============================================================
   T14 — LES QUATRE ROLES DE CELLULE, EN CLASSES ET PLUS EN `style=`
   ============================================================
   Treize cellules portaient leur couleur dans un `style=` en ligne. Un style
   en ligne l'emporte sur toute feuille : la regle de selection ci-dessus ne
   pouvait pas les remonter au-dessus de 4.5, et il aurait fallu un
   `!important` pour la lui arracher. C'est la meme famille que l'enveloppe
   `overflow-x:auto` de T31 — une valeur ecrite en ligne qu'aucune feuille
   ne rattrape. On deplace la couleur, on ne la force pas.

   `--ai-faint` disparait de ces cellules au passage : il valait 3.77 CONTRE
   LE BLANC, deja sous 4.5 avant toute modification de ce lot, et le survol
   l'aurait descendu a 3.21. Remplace par `--ai-muted` : 5.81 sur blanc,
   4.94 en survol, 7.10 en selection. Ce n'etait pas le sujet du ticket ;
   le laisser aurait ete preferer la proprete du perimetre a la lisibilite
   de l'ecran.
   ============================================================ */
.ai-table td.ai-td-2 { color:var(--ai-muted); }
.ai-table td.ai-td-fort { color:var(--ai-ink); font-weight:400; }
.ai-table td.ai-td-code { font-family:var(--ai-mono); font-weight:600; color:var(--ai-muted); text-transform:uppercase; }
.ai-table td.ai-td-horodatage { font-family:var(--ai-mono); font-size:var(--ai-fs-eyebrow); color:var(--ai-muted); }

/* --- Sous-plannings : second rang typographique ------------------------- */
.ai-subplans-title { display:flex; align-items:center; gap:var(--ai-s3); margin-bottom:var(--ai-s3); }
.ai-subplans-title > span:first-child {
  font-family:var(--ai-mono); font-size:var(--ai-fs-eyebrow);
  letter-spacing:var(--ai-track-eyebrow); text-transform:uppercase; color:var(--ai-muted);
}
.ai-subplans-title > hr { flex:1; height:1px; border:0; background:var(--ai-line); margin:0; }
.ai-subplans-title > span:last-child { font-family:var(--ai-mono); font-size:var(--ai-fs-eyebrow); color:var(--ai-faint); }
.ai-subplans { display:flex; flex-direction:column; gap:var(--ai-s3); }
.ai-subplan { background:var(--ai-surface); border:1px solid var(--ai-line); border-left:3px solid var(--ai-faint); }
.ai-subplan--active { border-left-color:var(--ai-accent); }
.ai-subplan--neutral { border-left-color:var(--ai-ink); }
.ai-subplan__head { display:flex; align-items:center; gap:14px; padding:11px 14px; border-bottom:1px solid var(--ai-line-soft); }
.ai-subplan__head > .ti { font-size:var(--ai-fs-icon-md); color:var(--ai-accent); }
.ai-subplan--neutral .ai-subplan__head > .ti { color:var(--ai-ink); }
.ai-subplan.is-collapsed .ai-subplan__head > .ti { color:var(--ai-muted); }
.ai-subplan__title { font-size:var(--ai-fs-h3); font-weight:600; letter-spacing:-.01em; color:var(--ai-ink); }
.ai-subplan__meta { font-family:var(--ai-mono); font-size:var(--ai-fs-eyebrow); color:var(--ai-faint); }
.ai-subplan__right { margin-left:auto; display:flex; align-items:center; gap:var(--ai-s3); }
.ai-subplan__rate { font-family:var(--ai-mono); font-size:var(--ai-fs-meta); font-weight:500; color:var(--ai-muted); }
.ai-subplan__rate--ok { color:var(--ai-ok); }
.ai-subplan__rate--warn { color:var(--ai-warn); }
.ai-subplan__bar { width:70px; height:5px; background:#e6e9ee; display:inline-block; }
.ai-subplan__bar > i { display:block; height:5px; background:var(--ai-accent); }
.ai-subplan__toggle { background:none; border:0; padding:0; cursor:pointer; color:var(--ai-faint); display:flex; }
.ai-subplan__toggle .ti { font-size:var(--ai-fs-icon-md); }
.ai-subplan.is-collapsed .ai-subplan__head { border-bottom:0; }
.ai-subplan.is-collapsed table { display:none; }
.ai-subplan table.ai-table thead tr { background:var(--ai-surface-2); }
.ai-subplan table.ai-table th { color:var(--ai-faint); border-bottom:1px solid var(--ai-line-soft); }
.ai-subplan table.ai-table td { padding:8px 10px; }

/* --- 8. BADGES DE STATUT ------------------------------------------------
   Couleur + FORME du marqueur + libellé : lisible en deutéranopie et en N/B. */
.ai-status {
  display:inline-flex; align-items:center; gap:6px; padding:2px 9px;
  font-size:var(--ai-fs-meta); font-weight:600; line-height:1.5;
  clip-path: polygon(0 0, calc(100% - var(--ai-chamfer-sm)) 0, 100% var(--ai-chamfer-sm),
                     100% 100%, var(--ai-chamfer-sm) 100%, 0 calc(100% - var(--ai-chamfer-sm)));
}
.ai-status::before { content:""; width:8px; height:8px; flex-shrink:0; }
.ai-status--present  { background:var(--ai-ok-bg); color:var(--ai-ok); }
.ai-status--present::before { background:var(--ai-ok); }                                   /* carré plein */
.ai-status--absent   { background:var(--ai-off-bg); color:var(--ai-off-fg); }
.ai-status--absent::before {                                                                /* hachuré */
  background:repeating-linear-gradient(45deg,var(--ai-off) 0 2px,transparent 2px 4px);
  border:1px solid var(--ai-off);
}
.ai-status--optimize { background:var(--ai-warn-bg); color:var(--ai-warn-fg); }
.ai-status--optimize::before { background:var(--ai-warn); transform:rotate(45deg); }        /* losange */
.ai-status--request  { background:var(--ai-info-bg); color:var(--ai-info-fg); }
.ai-status--request::before { border:1.5px dashed var(--ai-info); }                         /* pointillé */
/* LOT I point 5 : entite desactivee (site/salle/site externe/activite/
   competence) - PAS un statut de presence, mais meme composant, meme regle
   de forme distincte (carre CREUX, bordure epaisse, aucun remplissage) pour
   ne jamais se confondre avec absent (hachure) ou les 3 autres formes deja
   prises. */
.ai-status--inactive { background:var(--ai-off-bg); color:var(--ai-off-fg); }
.ai-status--inactive::before { background:transparent; border:2px solid var(--ai-off); }    /* carré creux */

.ai-legend { display:flex; gap:13px; align-items:center; font-family:var(--ai-mono); font-size:var(--ai-fs-eyebrow); color:var(--ai-muted); }
.ai-legend > span { display:inline-flex; align-items:center; gap:6px; }
.ai-legend i { width:9px; height:9px; display:inline-block; }
.ai-legend .lg-present  { background:var(--ai-ok); }
.ai-legend .lg-absent   { background:repeating-linear-gradient(45deg,var(--ai-off) 0 2px,transparent 2px 4px); border:1px solid var(--ai-off); }
.ai-legend .lg-optimize { background:var(--ai-warn); transform:rotate(45deg); }
.ai-legend .lg-request  { border:1.5px dashed var(--ai-info); }

/* --- 9. RAIL DE NAVIGATION ---------------------------------------------- */
.ai-shell { display:flex; border:1px solid var(--ai-line); background:var(--ai-bg); max-width:1440px; margin:0 auto; }
/* T2 — LE RAIL TIENT LA HAUTEUR DU VIEWPORT, LE CONTENU DEFILE DEDANS.
   Avant : le rail faisait la hauteur de la PAGE. Sur le tableau de bord,
   court, il s'arretait au milieu de l'ecran ; sur /comptes, long, il
   fallait descendre jusqu'en bas du document pour trouver
   « Se deconnecter ». Une deconnexion qu'on doit aller chercher n'est pas
   une deconnexion, et un rail qui flotte au milieu du vide n'est pas un
   rail.
   `sticky` plutot que `fixed` : le rail reste DANS le flux de .ai-shell,
   donc il garde sa largeur, sa bordure et le centrage a 1440px. En
   `fixed` il faudrait recreer tout ca a la main, et le decalage du
   contenu principal serait a maintenir en double. */
/* ⛔ POINT 9 (11.09.2026) : largeur en VARIABLE -- mesure faite avant de
   coder, elle etait en dur (208px), aucune --rail-width n'existait.
   .ai-rail--replie (app/templates/_rail.html) redefinit --ai-rail-width,
   jamais cette regle. */
.ai-rail { --ai-rail-width:208px; width:var(--ai-rail-width); transition:width .15s ease;
  flex-shrink:0; background:var(--ai-rail); padding:var(--ai-s5) 0 18px; display:flex; flex-direction:column; gap:var(--ai-s5);
  position:sticky; top:0; height:100vh; align-self:flex-start; }
.ai-rail--replie { --ai-rail-width:60px; }

/* --- 9 bis. LE RAIL SUR TABLETTE ET SUR TELEPHONE — ITEM 122 (21.09.2026)
   ------------------------------------------------------------------------

   ⛔⛔ LE DEFAUT QUE CE BLOC FERME, MESURE AU NAVIGATEUR AVANT DE CODER.
       Le rail vit DANS le flux de `.ai-shell` et ne retrecit jamais
       (`flex-shrink:0`, 208px en dur ci-dessus). A 390px il prenait donc
       208 des 390, et il restait 180px de contenu. Releve du 21.09 :

           /            531 > 390   colonne de contenu : 180px
           /personnel   431 > 390   colonne de contenu : 180px
           /echanges    415 > 390   colonne de contenu : 180px

       ⭐ LE CHIFFRE QUI COMPTE N'EST PAS LE DEBORDEMENT, C'EST LE 180.
          Un debordement se repare en retrecissant le contenu — c'est ce
          qu'a fait le correctif du 18.09 sur `/personnel/<id>` (674c08f),
          qui passe aujourd'hui a 390px exact AVEC le rail ouvert. Mais le
          defaut qui a OUVERT l'item 122 n'etait pas la barre de defilement :
          c'etait `/echanges` lu « en colonne d'un mot par ligne » dans
          152px. Retrecir le contenu repare la mesure et laisse le defaut.
          On ne retrecit donc rien ici : ON REND LA LARGEUR.

   ⚠️ DEUX BANDES, PAS UNE — LA CONTRAINTE AJOUTEE PAR BRICE AU REVERT DU
      17.09 : « le produit devra fonctionner sur tablette ET telephone ».
      Le tour du 17.09 n'avait qu'un seuil (640px) et ne regardait que le
      telephone. Le balayage du 21.09 (390 → 1280px, largeurs d'appareils
      reelles) montre pourquoi ca ne suffisait pas :

          /planning a 744px : 755 > 744   ⛔ DEBORDE
          /planning a 768px : 774 > 768   ⛔ DEBORDE   (tablette en portrait)
          /planning a 820px : 820          ok

      ⛔ PERSONNE NE L'AVAIT VU, ET LA RAISON EST DANS LA SONDE : le socle
         ne mesure que 1440, 1280 et 390px. Entre 640 (ou le tiroir k2
         prenait le relais) et 820, AUCUNE largeur n'etait regardee — la
         tablette en portrait vivait exactement dans ce trou.

   LES TROIS ETATS, ET CE QUI LES DECIDE :

       >= 1024px   rail entier, 208px, dans le flux.        (inchange)
       640–1024    rail REPLIE d'office (60px, icones).     tablette
       <= 640px    rail en TIROIR, pose SUR le contenu.     telephone

   ⭐ POURQUOI LA TABLETTE A DROIT AU REPLI ET NON AU TIROIR. A 768px, le
      rail replie laisse 708px de contenu — largement au-dessus des 547px
      que `/planning` reclame. La navigation reste donc VISIBLE en
      permanence, ce qu'un tiroir lui retire. Un tiroir a 768px aurait
      « marche » au sens de la mesure ; il aurait coute la navigation sur
      le seul appareil qui a la place de la garder.

   ⛔ ET LE REPLI DE LA TABLETTE NE DUPLIQUE PAS UNE SEULE DECLARATION :
      il REUTILISE `.ai-rail--replie`, posee par le script de `_rail.html`
      sur `matchMedia`. Recopier ici les ~20 selecteurs du repli, c'etait
      fabriquer la seconde verite que ce depot passe son temps a payer
      (meme raison que `_rail.html`, `connexion.js`, `chemin_chrome.js`).
      Une media query ne sait pas poser une classe : c'est la SEULE raison
      pour laquelle cette bande-la passe par du script et non par du CSS.

   ⚠️ L'ETAT DU TIROIR EST `body.k2-rail-ouvert` — CELLE QUI EXISTE DEJA.
      `kairos-2-socle.css` ouvre le meme tiroir sur les trois ecrans k2
      (/planning, /moi, /absences) depuis le 14.09. Une seconde classe
      d'etat aurait donne deux tiroirs a fermer et un seul bouton pour le
      faire. Sur un ecran k2, les regles k2 (specificite superieure, feuille
      chargee apres) gagnent et posent la MEME geometrie ; le bouton et le
      voile de ce fichier s'effacent devant ceux du chassis (`:has()`,
      plus bas dans `_rail.html`). */

/* Le bouton du tiroir et son voile n'existent qu'au telephone : caches
   partout ailleurs, y compris quand aucune media query ne s'applique.

   ⛔ LE VOILE S'APPELLE `.ai-rail__voile` ET PAS `.ai-voile` — TROUVE PAR
      LA SONDE, PAS PAR LA RELECTURE. Premier jet : `.ai-voile`. Or ce nom
      EXISTE DEJA, injecte par `app/static/js/ai-ui.js` (le voile des
      fenetres modales) : `position:fixed; inset:0; z-index:10001;
      display:flex`. Cette regle-la gagnait sur le `display:none` d'ici,
      et le voile du rail couvrait TOUT L'ECRAN A TOUTE LARGEUR malgre son
      attribut `hidden`. Mesure : `sonde_socle.js` est tombee dessus tout
      de suite — « <div hidden class="ai-voile"> intercepts pointer
      events » sur le clic de deconnexion, a 1280px, sur une page qui
      n'avait aucun tiroir. Un nom de classe pris ailleurs ne previent
      jamais : il ecrase en silence, et seul un clic reel le revele. */
/* ⛔ ITEM 212 (24.09.2026) : `.ai-rail__bande` rejoint cette liste, et ce
   `display:none` de base est TOUT ce qui la tient hors de l'impression. Le
   bouton et le voile n'ont jamais eu de regle `@media print` non plus, pour
   la meme raison : leurs declarations vivent TOUTES dans `@media (max-width:
   640px)`, et une feuille imprimee se mesure en largeur de PAPIER (A4 =
   794 px a 96 dpi), jamais en largeur de fenetre. La media query ne mord donc
   pas, et c'est cette ligne-ci qui s'applique.
   ⚠️ CE QUI EST MESURE, ET CE QUI EST RAISONNE — la nuance compte. Mesure
      (`emulateMedia('print')` a 794 px, § 9 quater) : `display` vaut bien
      `none`. Ce que la mesure NE prouve pas : qu'un vrai moteur d'impression
      evalue la media query sur le papier et non sur la fenetre. A 390 px de
      fenetre, `emulateMedia('print')` garde 390 px et la regle mord — c'est
      un artefact de l'emulation, pas l'etat d'une feuille. */
.ai-rail__menu, .ai-rail__voile, .ai-rail__bande { display:none; }

/* ⛔⛔ DRAPEAU `rail_tiroir_mobile_enabled` (22.09.2026, ordre de Brice) —
   TOUTES les regles de cette section sont prefixees `body[data-rail-tiroir=
   "oui"]`, attribut peint SERVEUR sur <body> par les trois racines
   (base.html, index.html, base_mobile.html) quand le drapeau est allume.
   Drapeau eteint : l'attribut est absent, aucune de ces regles ne mord, et
   le rail est CELUI D'AVANT l'item 122 -- dans le flux, sans tiroir.
   ⚠️ Le prefixe porte sur `body`, pas sur un conteneur interne : `.ai-shell`
      et `.ai-rail` sont des enfants de <body>, un prefixe pose plus bas ne
      les atteindrait pas. */
@media (max-width: 640px) {
  /* `.ai-shell` cesse d'etre une rangee : le rail sort du flux, et le
     contenu recupere la totalite de la largeur au lieu de 180px. */
  body[data-rail-tiroir="oui"] .ai-shell { display:block; }
  body[data-rail-tiroir="oui"] .ai-rail {
    --ai-rail-width:272px;
    position:fixed; left:0; top:0; bottom:0; z-index:1100; height:100vh;
    transform:translateX(-100%); transition:transform .2s ease; box-shadow:none;
  }
  body[data-rail-tiroir="oui"].k2-rail-ouvert .ai-rail { transform:none; box-shadow:0 0 40px rgba(0,0,0,.45); }

  /* Le bouton flotte au-dessus du contenu, dans la couleur du rail : il
     est le seul acces a la navigation a cette largeur, il ne peut pas se
     confondre avec un bouton de la page.

     ⛔⛔ ITEM 210 (24.09.2026) — SA GEOMETRIE N'EST PLUS ECRITE EN CHIFFRES
        ICI, PARCE QUE TROIS AUTRES REGLES DU PRODUIT EN DEPENDENT.
        Marge et taille deviennent des jetons, et leur somme est publiee sous
        le nom `--ai-collant-haut` : « la hauteur que ce bouton confisque en
        haut de la fenetre ». Tout ce qui se colle au haut de la FENETRE la
        lit, au lieu de recopier un 48 qui derive au premier reglage.
        Voir le bloc « § 9 ter » juste apres cette media query. */
  body[data-rail-tiroir="oui"] .ai-rail__menu {
    display:inline-grid; place-items:center; position:fixed;
    left:var(--ai-rail-menu-marge); top:var(--ai-rail-menu-marge);
    z-index:1060; width:var(--ai-rail-menu-taille); height:var(--ai-rail-menu-taille);
    padding:0; border:0; cursor:pointer;
    background:var(--ai-rail); color:var(--ai-rail-fg-active);
  }
  body[data-rail-tiroir="oui"] .ai-rail__menu i { font-size:var(--ai-fs-icon-lg); }
  body[data-rail-tiroir="oui"] .ai-rail__voile:not([hidden]) {
    display:block; position:fixed; inset:0; z-index:1050;
    background:#16130f; opacity:.55;
  }
  /* ⛔⛔ ITEM 212 (24.09.2026) — LA BANDE QUI PORTE LE BOUTON.
     Drapeau `rail_tiroir_bande_enabled`, ETEINT par defaut : sans lui, cet
     element n'existe pas dans le HTML servi, et rien de ce qui suit n'est
     emis. Tout le raisonnement, les mesures et le prix sont au § 9 quater,
     juste apres le § 9 ter. Ici, seulement la geometrie :
       - la HAUTEUR est le jeton, jamais un 48 recopie : la bande fait
         exactement ce que le bouton confisque, par construction ;
       - le `z-index` est SOUS la famille des panneaux (900, 901, 1000) et
         non sous le seul bouton — voir § 9 quater, c'est la mesure qui l'a
         impose, pas un gout. */
  body[data-rail-tiroir="oui"] .ai-rail__bande {
    display:block; position:fixed; left:0; right:0; top:0;
    height:var(--ai-collant-haut-fenetre); z-index:890;
    background:var(--ai-bande-fond);
    border-bottom:1px solid var(--ai-bande-filet);
  }
  /* Le bouton du tiroir occupe le coin haut-gauche : le contenu lui fait
     sa place, sinon il se pose PAR-DESSUS le premier titre de la page.
     ⚠️ ITEM 210 : c'est le MEME chiffre que celui que confisque le bouton, et
        il n'est plus ecrit deux fois. Un ecran k2 n'a pas ce bouton (regle
        ci-dessous) : `--ai-collant-haut` y vaut 0, et cette marge tombe a 0
        sans qu'aucune regle n'ait a la remettre. */
  body[data-rail-tiroir="oui"] .ai-shell > .ai-main { padding-top:var(--ai-collant-haut); }
  /* Un ecran k2 porte deja son propre bouton dans son chassis et son
     propre voile : les notres s'effacent plutot que de doubler les siens,
     et la place reservee au notre n'a plus lieu d'etre. */
  /* ⛔ ITEM 212 : la bande s'efface avec le bouton qu'elle porte, et c'est la
     MEME phrase — une bande sans son bouton serait 48 px d'aplat qui ne
     servent a rien, exactement ce que la regle de Brice du 23.09 fait
     RETIRER. ⚠️ Elle ne s'effacerait pas toute seule : sa hauteur est
     `--ai-collant-haut-fenetre`, que la regle `:has(.k2-chassis)` du § 9 ter
     remet bien a 0 — mais un element de 0 px de haut garde son filet de
     1 px (`border-bottom`, `box-sizing:border-box`). Un trait noir en travers
     du haut de tous les ecrans k2 : c'est la mesure qui l'a montre, pas la
     relecture. */
  body:has(.k2-chassis) .ai-rail__menu,
  body:has(.k2-chassis) .ai-rail__bande,
  body:has(.k2-chassis) .ai-rail__voile { display:none !important; }
}

/* ══ § 9 ter — CE QUE LE BOUTON DU TIROIR CONFISQUE EN HAUT DE LA FENETRE ══
   ⛔⛔ ITEM 210 (24.09.2026) — MESURE, PAS SUPPOSE, ET SUR CINQ ECRANS.
   Le bouton du tiroir est `position:fixed` au coin haut-gauche. La marge
   `padding-top` de `.ai-main` lui fait sa place AU REPOS — mais elle ne
   protege RIEN des que la page defile : tout ce qui est `position:sticky;
   top:0` remonte se garer SOUS lui et y reste. Releve au navigateur sur le
   banc `banc_grille_classique.sh` (profil client de la grille, port 8099),
   chaque element collant amene au contact du haut de la fenetre :

     320 / 390 / 430 px         recouvrement par le bouton   elementFromPoint
     /planning  th.pl-rowhead   32,5 x 40 px de 120 de large  i.ti-menu-2
                (« Activite » / « Salle », rang 1 + rang 2)
     /absences  th du jour      32,5 x 25,6 px                i.ti-menu-2
     /indispos-salles th jour   32,5 x 21,2 px                button.ai-rail__menu
     /aide      .aide-retour    33 x 40 px  ⛔ ET IL EST CLIQUABLE
     /reserve   td.rs-nom       32 x 40 px  (voir la nuance plus bas)
     641 px et au-dela          aucun : le bouton n'existe plus

   ⛔ LE CAS DE `/aide` EST LE PIRE, ET CE N'EST PAS UNE QUESTION DE LISIBILITE.
      `.aide-retour` porte le bouton « Retour » et fait TOUTE la largeur
      (rect 15,0,290,69 a 320 px) : `elementFromPoint` au tiers gauche de la
      barre rend l'icone du hamburger. Le geste que Brice avait demande
      (« je crains de perdre la page que je consultais », lot M) devient
      partiellement inatteignable — un element masque se devine, un element
      qui ne recoit pas le clic ne se devine pas.

   ⚠️ DEUX FAMILLES, ET ELLES NE SE VALENT PAS.
      - PERMANENT : l'element colle sur l'axe VERTICAL (`top:0`), il se gare
        sous le bouton et n'en repart jamais. /planning (l'en-tete), /absences,
        /indispos-salles, /aide.
      - DE PASSAGE : l'element ne colle que sur l'axe HORIZONTAL (`top:auto`)
        et ne fait que traverser la bande du bouton pendant le defilement.
        `td.rs-nom` de /reserve et les `th.pl-rowhead` du CORPS de la grille.
        C'est le sort de tout contenu qui passe sous un bouton flottant ; ce
        n'est pas ce que cet item corrige, et ce bloc ne pretend pas le faire.

   ⭐ CE QUI EST CORRIGE : PLUS RIEN NE SE COLLE AU-DESSUS DU BAS DU BOUTON.
      Les trois issues laissees ouvertes par l'item etaient « decaler le
      bouton », « decaler la grille », « ne coller l'en-tete qu'en dessous ».
        - DECALER LE BOUTON ne deplace que le probleme, et la mesure le dit :
          `.aide-retour` fait TOUTE la largeur de la fenetre (290 px sur 320).
          Aucun coin de la bande haute n'est libre — un carre de 40 px pose
          n'importe ou dedans recouvre forcement une barre pleine largeur.
        - DECALER LA GRILLE couterait 48 des 320 px de largeur, en permanence,
          sur tous les ecrans, a la taille ou la place manque le plus — et
          contre la doctrine de l'item 66 : « on ne retrecit rien, ON REND LA
          LARGEUR ». 48 px de largeur pour un bouton touche une fois par
          session est le mauvais echange.
        - RETENU, LE TROISIEME : le collage s'arrete au bas du bouton. Ca ne
          coute aucune largeur, rien n'est masque, et les 48 px verticaux sont
          ceux que `padding-top` reservait DEJA au repos.

   ⛔ ET LA VALEUR N'EST PAS ECRITE A LA MAIN : elle est la SOMME de la marge
      et de la taille du bouton, les deux jetons que la regle du bouton lit
      elle aussi. Meme famille de fragilite que l'offset de T30 et que la
      constante `--pl-thead-h` de l'item 207 : « une constante mesuree s'ecrit
      avec ce qui la maintient vraie ». Regler le bouton a 44 px deplace les
      cinq en-tetes du meme coup, sans qu'on ait a y penser.

   ⛔ LE DEFAUT ET SON REMEDE VIVENT DERRIERE LE MEME DRAPEAU. Le bouton
      n'existe que si `rail_tiroir_mobile_enabled` est allume ; c'est donc lui
      qui cree le recouvrement, et c'est lui qui porte la correction. Drapeau
      ETEINT (l'etat du client au 24.09 : la cle est absente de
      `settings.exemple.json` ET de `profils/cvcl.json`), `--ai-collant-haut`
      vaut 0 et TOUS les `top` du produit sont identiques a l'octet pres a ce
      qu'ils etaient. Aucun drapeau neuf : il aurait fallu allumer deux cles
      pour voir un en-tete correct.
   ⚠️ ET LE DEFAUT MESURE N'EST PAS CELUI DU CLIENT D'AUJOURD'HUI : le banc
      sert la grille classique du client AVEC le tiroir du LABO. C'est le
      profil de DEV (tout allume) et celui du jour ou le tiroir sera adopte,
      pas celui de CVCL au 24.09. */
/* ⭐⭐ ITEM 211 (24.09.2026) — DEUX JETONS, PARCE QU'IL Y A DEUX QUESTIONS
   DIFFERENTES, ET LES CONFONDRE A COUTE UNE REGRESSION MESUREE.

     --ai-collant-haut-fenetre  « quelle bande le bouton confisque-t-il en haut
                                de la FENETRE ? » — un FAIT de la fenetre, vrai
                                partout dans la page, JAMAIS annule.
     --ai-collant-haut          « de combien un element collant DANS CE
                                CONTEXTE-CI doit-il decaler son `top` ? » — vaut
                                0 dans une boite qui capture le collage (elle ne
                                se refere plus a la fenetre, item 210).

   ⛔ POURQUOI IL EN FALLAIT DEUX, ET C'EST MESURE. L'item 211 borne la hauteur
      du volet de /planning, et cette hauteur doit RETIRER la bande du bouton —
      sinon le haut du volet remonte jusqu'a y = 9 px au bas du defilement, son
      en-tete s'y gare, et le bouton le recouvre sur 1276 px² EN PERMANENCE
      (« ACTIVITE » relu « CTIVITE »). C'est mot pour mot le « si un jour une
      boite bornee remontait au point que SON haut passe sous le bouton » que
      l'item 210 avait ecrit comme limite assumee — ce lot-ci EST ce jour-la.
      Or le volet ne pouvait pas lire la bande : `--ai-collant-haut` y vaut 0,
      justement parce qu'il capture. Un jeton ne peut pas repondre a la fois
      « la bande vaut 48 » et « ici, ajoute 0 ».
   ⭐ LA VALEUR RESTE ECRITE UNE SEULE FOIS : le second jeton DERIVE du premier
      (`var()`), il ne le recopie pas. Regler le bouton a 44 px deplace toujours
      les cinq en-tetes ET la hauteur du volet du meme coup.
   ⚠️ ET `--ai-collant-haut` GARDE EXACTEMENT LES MEMES VALEURS QU'AU 24.09 au
      matin : 0 partout, `calc(8px + 40px)` sous 640 px quand le tiroir est
      allume et qu'aucun chassis k2 n'est la. La sonde de l'item 210 les relit
      telles quelles. */
:root {
  --ai-rail-menu-marge:8px; --ai-rail-menu-taille:40px;
  --ai-collant-haut-fenetre:0px;
  --ai-collant-haut:0px;
}
@media (max-width: 640px) {
  body[data-rail-tiroir="oui"] {
    --ai-collant-haut-fenetre:calc(var(--ai-rail-menu-marge) + var(--ai-rail-menu-taille));
    --ai-collant-haut:var(--ai-collant-haut-fenetre);
  }
  /* Un ecran k2 n'affiche PAS ce bouton (`display:none !important`, § 9 bis) :
     il ne confisque donc rien, et son chassis porte sa propre bande. Remettre
     le jeton a 0 ici evite de decaler les en-tetes k2 de 48 px pour un bouton
     qui n'est pas la. */
  body[data-rail-tiroir="oui"]:has(.k2-chassis) {
    --ai-collant-haut-fenetre:0px;
    --ai-collant-haut:0px;
  }
}

/* ══ § 9 quater — LA BANDE QUI PORTE LE BOUTON (ITEM 212, 24.09.2026) ═══════
   ⛔⛔ LE DEFAUT, VU SUR UNE CAPTURE DE L'ITEM 210 LUI-MEME
      (`docs/2609_24_item210_captures/aide_390_apres.png`). Le bouton du
      tiroir est un carre OPAQUE de 40 px qui FLOTTE. Le § 9 ter arrete tout
      ce qui colle au bas de ce bouton — mais il ne peut rien contre ce qui
      DEFILE : la lucarne de 48 px qui reste A DROITE du bouton n'est
      couverte par rien, et le contenu continue d'y glisser au-dessus de la
      barre collee. Sur la capture : « patient. » et « Ce message part en
      brouillon .eml » passent au-dessus du bouton « Aller au tableau de
      bord ».
   ⚠️ CE N'EST PAS UN DEFAUT DE L'ITEM 210 : c'est le prix d'un bouton
      flottant, vrai avant lui. L'item 210 l'a seulement rendu VISIBLE, en
      garant proprement la barre collee juste en dessous.

   ⭐ LE REMEDE : UNE VRAIE BANDE OPAQUE PLEINE LARGEUR, ET ELLE NE COUTE
      AUCUNE PLACE. Les 48 px sont DEJA reserves par le `padding-top` de
      `.ai-main` (§ 9 bis) et DEJA publies par `--ai-collant-haut-fenetre`
      (§ 9 ter). La bande ne prend donc rien a personne : elle REMPLIT une
      place que le produit tenait vide depuis le 22.09.

   ⛔ POURQUOI `z-index:890` ET NON « juste sous le bouton » (1060) — C'EST LA
      MESURE QUI L'A IMPOSE, ET LE PREMIER JET ETAIT FAUX. Une bande pleine
      largeur posee a 1045 (sous le voile du tiroir, 1050) recouvrait le haut
      de TROIS familles de panneaux qui vivent a `top:0` :
          .rg-voile / .rg-panneau  900 / 901  (panneau de reglages, item 172 —
                                   pleine largeur sous 640 px)
          .ab-drawer               1000       (tiroir de /absences)
          .pl-drawer               1000       (tiroir du planning)
          #ta-modal-overlay        1000
      Leur en-tete serait passe SOUS un aplat de 48 px. La bande appartient au
      CHASSIS DE LA PAGE, pas a la pile des surfaces qui s'ouvrent par-dessus
      elle : elle se range donc SOUS toute cette famille, et au-dessus du
      contenu ordinaire (le plus haut `z-index` de contenu du produit est 100 —
      `.pf-actions`, la barre d'action de `personnel/form.html`).
   ⚠️ CE QUE CE CHOIX LAISSE OUVERT, ET C'EST ASSUME : un panneau de la famille
      ci-dessus, ouvert, passe DEVANT la bande — le contenu de la PAGE, lui,
      reste couvert. C'est le bon sens de lecture (ce qu'on vient d'ouvrir est
      au-dessus), et c'est deja ce que le bouton fait aujourd'hui : il se peint
      a 1060 par-dessus le panneau de reglages, qui lui degage la place avec
      `padding-left:56px` (voir `panneau-reglages.css`). Cette ligne-la reste
      exacte, la bande n'y change rien.

   ⛔ ET LA BANDE PREND LES CLICS DE SA SURFACE, DELIBEREMENT. Sous elle, le
      contenu est INVISIBLE : le laisser cliquable fabriquerait le piege
      inverse de celui que l'item 210 a corrige sur `/aide` (« un element
      masque se devine, un element qui ne recoit pas le clic ne se devine
      pas » — ici ce serait un element qu'on ne voit pas et qui recoit le
      clic). Au repos, rien de legitime n'habite cette bande : `.ai-main` lui
      fait sa place, et le § 9 ter y interdit tout collage. La sonde le
      verifie ecran par ecran au lieu de le supposer
      (`scripts/verifier_item212_bande.js`, verdict « au repos »).

   ⭐ LA TEINTE EST UN JETON, ET C'EST EXPRES : C'EST LA SEULE CHOSE QUE BRICE
      A A TRANCHER. `--ai-bande-fond` vaut le graphite du rail — la bande se
      lit alors comme la suite du rail, et le bouton y est chez lui. L'autre
      valeur defendable est `var(--ai-bg)` : la bande disparait dans la page
      et ne laisse voir que le bouton, deja opaque. UNE SEULE ligne separe les
      deux, et le choix ne porte QUE sur le mode clair : en sombre,
      `--ai-bg` (#16130f) EST deja `--ai-rail` (#16130f), les deux variantes
      y sont identiques au pixel.
   ⚠️ LE FILET DU BAS N'EST PAS UNE DECORATION : sans lui, en mode sombre, la
      bande et la page ont exactement la meme couleur et la barre collee n'a
      plus de bord — on ne sait plus ou s'arrete le chassis. */
:root { --ai-bande-fond:var(--ai-rail); --ai-bande-filet:var(--ai-rail-line); }

/* ⛔⛔ ITEM 210 — ET IL S'ANNULE DANS TOUTE BOITE QUI CAPTURE LE COLLAGE.
   ⚠️ CETTE REGLE REPARE UNE REGRESSION QUE LA PRECEDENTE AVAIT INTRODUITE, ET
      C'EST LA MESURE QUI L'A TROUVEE, PAS LA RELECTURE. `position:sticky` se
      cale sur le plus proche ancetre DEFILANT, jamais sur la fenetre des qu'il
      en existe un (regle deja mesuree en T31, voir `components.css`). Dans une
      telle boite, le haut de reference n'est PAS le haut de la fenetre : le
      bouton du tiroir n'y confisque rien, et un `top` de 48 px y creuse un trou
      de 48 px au-dessus des lignes.
      Releve au navigateur, /comptes a 390 px, boite `.cp-tw__scroll` bornee
      (h=429 px, scrollH=8019), boite defilee a fond :
          jeton applique sans cette regle ... en-tete a 48 px du haut de boite
          jeton annule (cet etat) .......... en-tete a  0 px, comme a 1280 px
      48 px pris sur 429 de hauteur utile — 11 % de la liste — pour un bouton
      qui n'est pas au-dessus d'elle.
   ⭐ CE QUE LE JETON VEUT DIRE, EXACTEMENT : « la bande que le bouton confisque
      en haut de la FENETRE ». L'annuler dans une boite captrice n'est donc pas
      une exception, c'est sa definition.
   ⚠️ CE QU'ELLE NE COUVRE PAS, ET C'EST ASSUME : si un jour une boite bornee
      remontait au point que SON haut passe sous le bouton, son en-tete
      redeviendrait recouvert. Aucune des boites du produit ne le fait au 24.09
      (/comptes : la page n'a que 88 px de defilement a 390 px, le haut de la
      boite ne monte jamais au-dessus de 202 px). Le corriger demanderait de
      lire une position au JS a chaque defilement — hors de proportion avec un
      cas qui ne se produit pas.
   ⛔ LES CINQ CLASSES SONT CELLES QUI EXISTENT, PAS UNE LISTE DE PRECAUTION :
      `.cp-tw__scroll` (/comptes), `.kx-liste-plafond` (T14, cinq ecrans),
      `.kx-grille-defile` (T31, /absences, /indispos-salles, /rh-bascule),
      `.pl-grille-scroll` (/planning, /pilotage semaine), `.aide-defile`.
   ⚠️ DEUX BOITES CAPTRICES N'Y SONT PAS, ET C'EST MESURE, PAS OUBLIE :
      `.rb-defile` (/rh-bascule) et `.rd-defile` (/rh-decompte-heures-sup)
      enferment bien un en-tete collant, mais celui-ci a `top:auto` — il ne
      colle que sur l'axe HORIZONTAL. Le jeton n'y atteint aucun `top`, donc
      l'y annuler ne changerait rien. Balayage du 24.09 sur 22 ecrans a 390 px :
      ZERO en-tete collant visible, a `top` vertical, dans une boite non
      couverte. */
/* ⛔⛔ ITEM 211 (24.09.2026) — `:not(.kx-grille-rend-la-main)`, ET CE N'EST PAS
   UNE RETOUCHE COSMETIQUE : SANS ELLE, L'ITEM 211 ROUVRAIT L'ITEM 210.
   L'annulation ci-dessous dit « cette boite CAPTURE le collage, donc le bouton
   du tiroir ne confisque rien au-dessus d'elle ». L'item 211 fait justement
   RENDRE LA MAIN a deux de ces boites (/absences et /indispos-salles,
   `overflow-x:visible` — voir `components.css`) : leur en-tete recolle alors a
   la FENETRE, donc il se gare a nouveau SOUS le bouton, et les sept noms de
   jours y seraient coupes. Une boite qui ne capture plus ne fait plus
   exception — c'est la DEFINITION du jeton (« la bande que le bouton confisque
   en haut de la FENETRE »), pas un cas particulier de plus.
   ⭐ ET LA VALEUR RESTE DECLAREE UNE SEULE FOIS : on ne la recopie pas ici, on
      cesse simplement de l'annuler, et l'heritage depuis `<body>` (§ 9 ter
      ci-dessus) la reporte telle quelle. Un second endroit portant la somme
      `marge + taille` aurait divergé au premier reglage du bouton.
   ⚠️ `.pl-grille-scroll` N'EST PAS CONCERNEE et le reste : /planning garde sa
      capture (667 px de debordement reel a 320 px), son en-tete colle au haut
      du VOLET — a 1555 px dans le document — et 48 px y creuseraient exactement
      le trou que l'item 210 avait mesure sur /comptes. */
.cp-tw__scroll, .kx-liste-plafond,
.kx-grille-defile:not(.kx-grille-rend-la-main),
.pl-grille-scroll, .aide-defile { --ai-collant-haut:0px; }

/* ⚠️ LA BANDE TABLETTE N'A PAS SA REGLE ICI, ET C'EST DELIBERE.
   Le bouton « Replier/Deplier » doit disparaitre sous 1024px (voir
   `_rail.html`) — mais cette regle-la ne peut PAS vivre dans ce fichier.
   `_rail.html` porte son propre `<style>`, rendu APRES cette feuille dans
   le document, et il y declare `.ai-rail__theme, .ai-rail__collapse
   { display:flex; ... }`. A specificite egale (une classe), le dernier
   arrive gagne : un `display:none` ecrit ici n'aurait JAMAIS pris.
   ⛔ MESURE, PAS DEDUCTION : la premiere version de cet item avait mis la
      regle ici. La capture `item122_planning_768_clair_tablette.png` a
      montre le chevron « toujours la, en haut du rail replie. C'est la
      capture qui l'a trouve, pas la relecture — exactement la raison pour
      laquelle CLAUDE.md exige qu'on les REGARDE.
   Elle vit donc avec le reste du CSS du rail, dans `_rail.html` (regle T20
   du 27.08 : « le CSS du rail vit avec le balisage qu'il stylise »). */

/* Seules les ENTREES defilent. `min-height:0` est indispensable : sans
   lui, un enfant flex refuse de retrecir sous sa taille de contenu et
   deborde au lieu de defiler. */
.ai-rail__nav { flex:1 1 auto; min-height:0; overflow-y:auto; }
/* .ai-rail__user porte deja `margin-top:auto` : identite et deconnexion
   sont poussees en bas du RAIL, donc en bas du viewport. */
.ai-rail__brand {
  display:flex; align-items:center; gap:10px; padding:0 18px 18px;
  border-bottom:1px solid var(--ai-rail-line); font-size:var(--ai-s4);
  font-weight:700; letter-spacing:.08em; color:#fff;
}
/* ⛔ POINT A2 (11.09.2026) : le logo vit dans le rail sombre en permanence
   -- le bleu (« ce sur quoi on agit ») n'y a pas de sens pour une marque,
   et y ressort mal a cote du bleu d'action des liens. Passe au laiton,
   qui EST deja la couleur du chassis/identite (Brice : "ca fait classe"). */
.ai-rail__mark {
  width:18px; height:18px; background:var(--ai-rail-fg-active); flex-shrink:0;
  clip-path: polygon(0 0, calc(100% - 6px) 0, 100% 6px, 100% 100%, 6px 100%, 0 calc(100% - 6px));
}
/* Le co-branding (KAIROS + nom du cabinet) fait de .ai-rail__brand un <a> :
   la regle generique ".ai-rail a" (specificite egale + type) l'emporterait
   sinon sur padding/couleur/taille. On la re-affirme ici avec 2 classes. */
.ai-rail a.ai-rail__brand { color:#fff; font-size:var(--ai-s4); font-weight:700; letter-spacing:.08em; padding:0 18px 18px; }
.ai-rail a.ai-rail__brand:hover { color:#fff; }
.ai-rail__brandtext { display:flex; flex-direction:column; gap:2px; line-height:1.15; }
.ai-rail__brandtext small {
  font-family:var(--ai-mono); font-size:var(--ai-fs-table); font-weight:600; letter-spacing:.02em;
  color:var(--ai-rail-fg);
}
.ai-rail__brandlogo { max-width:150px; max-height:22px; width:auto; height:auto; object-fit:contain; margin-top:2px; }
/* ⛔⛔ LE PAS DU RAIL A ETE RESSERRE LE 04.09.2026, ET C'EST UNE MESURE QUI
   L'A IMPOSE, PAS UN GOUT.

   L'ajout d'une entree (« Echanges ») a fait passer `.ai-rail__nav` de 637 a
   680 px pour une zone visible de 607 : a 1000 px de haut, « Comptes » est
   sorti de la partie visible et `elementFromPoint` renvoyait `.ai-rail__aide`
   a sa place. Le lien existait, il etait correctement style, ET ON NE POUVAIT
   PLUS LE VOIR — mot pour mot le defaut du lot L, sur un autre lien.

   ⚠️ LA ZONE DEFILE (`overflow-y:auto`, plus haut) : rien n'etait PERDU. Mais
      « un lien qu'il faut aller chercher n'est pas un lien » — et deux entrees
      qui ne demandaient aucun defilement hier en demandaient un aujourd'hui.

   CE QUI EST CHANGE : le pas vertical, et lui seul. `gap` 3 -> 1, le titre de
   section 16/6 -> 10/5, l'entree 10 -> 8 de padding vertical. Ni la police, ni
   les couleurs, ni la largeur, ni les icones.

   MESURE APRES, MEME BANC, MEME JOUR, MEME BASE, DEUX HAUTEURS DE FENETRE :
       1000 px : 12 liens, 0 recouvert   (avant l'entree : 11 liens, 0 ;
                                          avec l'entree, sans ce resserrement : 1 recouvert)
        800 px :  1 recouvert            (avant l'entree, SANS resserrement : 3)
   ⭐ C'est donc STRICTEMENT MIEUX qu'avant ce lot, y compris a 800 px ou trois
      liens etaient deja inatteignables SANS que personne l'ait mesure.

   ⚠️ CE QUI RESTE OUVERT : le rail n'a AUCUNE regle de budget vertical. La
      prochaine entree ajoutee reproduira le probleme, et rien ne le dira.
      `scripts/verifier_rail.js` est la pour ca — a lancer apres toute entree
      nouvelle. Voir le ticket ouvert le 04.09. */
.ai-rail__nav { display:flex; flex-direction:column; gap:1px; }
.ai-rail__section {
  font-family:var(--ai-mono); font-size:var(--ai-fs-eyebrow); letter-spacing:var(--ai-track-eyebrow);
  text-transform:uppercase; color:var(--ai-rail-label); padding:10px 18px 5px;
}
.ai-rail__section:first-child { padding-top:0; }
.ai-rail a { display:flex; align-items:center; gap:10px; padding:8px 18px; font-size:var(--ai-fs-body); color:var(--ai-rail-fg); text-decoration:none; }
.ai-rail a .ti { font-size:var(--ai-fs-icon-md); }
.ai-rail a:hover { color:#fff; text-decoration:none; }
/* ⛔ POINT A1 (11.09.2026) — Brice : « le laiton du rail est trop faible,
   l'icone active se distingue mal, aussi franc que le survol ». Mesure :
   AVANT ce correctif, l'etat actif etait rendu en BLEU plein
   (--ai-accent), jamais en laiton -- le jeton --ai-rail-fg-active (laiton,
   #b08843) documente dans l'architecture d'identite existait mais n'etait
   CABLE NULLE PART. Corrige : icone laiton + liseré laiton epais + texte
   quasi blanc, sur un fond legerement souleve du rail (pas de bleu ici --
   « ce sur quoi on agit » ne s'applique pas au marqueur de page courante,
   qui est un ETAT, pas une action).
   Ratios mesures (WCAG, seuil 3:1 non-texte / 4.5:1 texte) :
     icone laiton  #b08843 / fond actif #241f18 : 5.02:1  (>= 3:1 OK)
     texte actif   #f0ebe2 / fond actif #241f18 : 13.77:1 (>= 4.5:1 OK) */
.ai-rail a.is-active {
  background:var(--ai-rail-line); color:var(--ai-rail-fg-text-active); font-weight:600;
  border-left:3px solid var(--ai-rail-fg-active);
  clip-path: polygon(0 0, calc(100% - var(--ai-chamfer)) 0, 100% var(--ai-chamfer),
                     100% 100%, var(--ai-chamfer) 100%, 0 calc(100% - var(--ai-chamfer)));
}
.ai-rail a.is-active .ti { color:var(--ai-rail-fg-active); }
.ai-rail a.is-sub { padding:7px 18px 7px 34px; font-size:var(--ai-fs-table); }
.ai-rail a.is-sub::before { content:""; width:6px; height:6px; background:var(--ai-rail-label); flex-shrink:0; }
.ai-rail a.is-sub.is-current { color:#c8d1de; }
.ai-rail a.is-sub.is-current::before { background:var(--ai-accent-hi); }
.ai-rail__count { margin-left:auto; font-family:var(--ai-mono); font-size:var(--ai-fs-meta); background:var(--ai-accent); color:#fff; padding:1px 6px; }
/* ⛔ POINT G (11.09.2026) : pastille ROUGE demandee explicitement pour
   Echanges, distincte du bleu de --ai-accent utilise par les autres
   compteurs du rail (ex. Demandes). */
.ai-rail__count--rouge { background:var(--ai-danger); }
/* LOT L — l'entree « Aide », EPINGLEE hors de la zone defilante (voir la
   note de _rail.html : posee dedans, elle tombait sous la ligne de
   flottaison). `margin-top:auto` la colle au bas du rail ; c'est ELLE qui
   porte desormais la poussee, et `.ai-rail__user` la suit sans marge. */
.ai-rail__aide { margin-top:auto; }
.ai-rail__aide + .ai-rail__user { margin-top:0; }
.ai-rail__user { margin-top:auto; padding:14px 18px 0; border-top:1px solid var(--ai-rail-line); display:flex; align-items:center; gap:10px; }
.ai-rail__avatar {
  width:28px; height:28px; background:var(--ai-rail-line); color:var(--ai-rail-fg);
  font-family:var(--ai-mono); font-size:var(--ai-fs-meta); display:flex; align-items:center; justify-content:center;
  clip-path: polygon(0 0, calc(100% - 8px) 0, 100% 8px, 100% 100%, 8px 100%, 0 calc(100% - 8px));
}
.ai-rail__user span { font-family:var(--ai-mono); font-size:var(--ai-fs-eyebrow); color:#7b8698; line-height:1.4; }
.ai-rail__user small { color:var(--ai-rail-label); }

/* --- 10. EN-TÊTE DE PAGE + ZONE DE TRAVAIL ------------------------------ */
.ai-main { flex:1; min-width:0; }

/* ⛔⛔ ITEM 161 (22.09.2026) — LE BOUTON RETOUR, EN HAUT À GAUCHE.
   Brice : « ce bouton de retour … ce pourrait être sympa de le mettre sur
   d'autres sections ». Balisage : `app/templates/_fil_retour.html`.
   Destination et libellé : `app/services/fil_retour.py`.

   ⛔ LA RÈGLE VIT ICI, ET PAS DANS UN <style> DE PARTIEL. Leçon de T20,
      écrite en toutes lettres en tête de `_rail.html` : un partiel voyage,
      le style de son hôte non. `_fil_retour.html` est inclus par `base.html`,
      que les 42 gabarits de l'application étendent.

   ⚠️ 24 px À GAUCHE, ET CE N'EST PAS UN CHIFFRE CHOISI À L'ŒIL : c'est le
      padding gauche de `.ai-pagehead` juste en dessous. Le bouton et le
      titre de l'écran doivent partir de la MÊME verticale — un retrait de
      deux pixels se voit immédiatement sur une capture, et c'est le genre
      de défaut que Brice a trouvé à l'œil le 16.09 (items 109/112/113).
      Si le padding de `.ai-pagehead` change, celui-ci change avec lui.

   ⚠️ PAS DE `background` : il se pose sur le fond de la page, au-dessus du
      bandeau clair de `.ai-pagehead`. Lui donner la surface aurait soudé les
      deux en un seul bloc et fait croire que le retour appartient au titre.

   ⚠️ `width:max-content` : sans lui, le `<nav>` prend toute la largeur et
      la zone cliquable du lien aussi (le `<span>` d'enveloppe est un bloc
      de la largeur disponible). Un lien de retour qui répond au clic à
      1200 px de son texte est une surprise, pas une commodité. */
.ai-retour { padding:14px 24px 0; }
.ai-retour .ai-btn-outline { display:inline-block; width:max-content; max-width:100%; }
.ai-retour .ai-btn-ghost { min-height:34px; }
/* ⛔ À L'IMPRESSION, IL DISPARAÎT — même liste que le rail et les barres
   d'outils (§ « LOT M » plus bas dans cette feuille) : un bouton de
   navigation sur du papier est de l'encre perdue. */
@media print { .ai-retour { display:none !important; } }
.ai-pagehead { background:var(--ai-surface); padding:18px 24px 16px; display:flex; align-items:flex-start; justify-content:space-between; gap:var(--ai-s5); }
.ai-pagehead__eyebrow { display:flex; align-items:center; gap:9px; }
.ai-pagehead__eyebrow::before { content:""; width:18px; height:2px; background:var(--ai-accent); }
.ai-pagehead__eyebrow span { font-family:var(--ai-mono); font-size:var(--ai-fs-eyebrow); letter-spacing:var(--ai-track-eyebrow); text-transform:uppercase; color:#6b7688; }
.ai-pagehead h1 { margin-top:6px; }
/* ⛔ ITEM 80 (15.09.2026) — L'EN-TÊTE PASSE À LA LIGNE SOUS 640 PX.
   Mesuré : flex SANS retour à la ligne, le titre garde sa largeur minimale et
   pousse « Aide » hors de la fenêtre — /demandes-absences 357 px et /comptes
   332 px à 320 rail replié, 505 px et 480 px à 390 rail ouvert. Sous 640 px,
   « Aide » et les actions passent SOUS le titre au lieu de sortir. Au-dessus,
   rien ne change (la sonde compare l'en-tête de 46 écrans à 1280).
   ⚠️ ESSAYÉ ET RETIRÉ : laisser le titre descendre sous son mot le plus long
   (`min-width:0` + `overflow-wrap`) coupait « Décomp|te mensue|l » à 390 rail
   ouvert — vu sur la capture. Un titre qui mord de quelques pixels sur sa
   marge, dans la fenêtre, se lit mieux qu'un titre coupé au milieu des mots.
   ⭐ LE TITRE RÉDUIT AVEC SA COLONNE, pas avec la fenêtre : `14cqi` de
   l'en-tête. Mesuré (15.09) : « Demandes » fait 152 px à 27 px de corps ;
   en-tête utile 268 px à 390 rail replié, 198 px à 320 rail replié (27 px
   gardés dans les deux cas : 14cqi ≥ 27 px dès 193 px), 120 px à 390 rail
   OUVERT — là seulement le titre descend (~17 px) au lieu de sortir.
   Sonde : scripts/verifier_item80.js. */
@media (max-width: 640px) {
  .ai-pagehead { flex-wrap:wrap; row-gap:var(--ai-s3); position:relative; container-type:inline-size; }
  .ai-pagehead h1 { font-size:min(var(--ai-fs-h1), 14cqi); }
  /* Le volet d'aide s'accrochait au BORD DROIT du bouton (`right:0`) : passé
     sous le titre, le bouton est à gauche et le volet sortait à gauche
     (-157 px, /demandes-absences 390 rail replié — mesuré par la sonde de
     l'item 80 après la première passe). Sous 640 px, il s'accroche à
     l'EN-TÊTE, sur toute sa largeur, où que le bouton soit tombé. */
  .ai-pagehead .aide-porte { position:static; }
  .ai-pagehead .aide-porte .aide-corps { left:0; right:0; width:auto; top:calc(100% + 6px); }
}
.ai-content { padding:20px 24px 24px; }

/* --- 11. BANDE DE CHARGE PAR JOUR --------------------------------------- */
.ai-load { background:var(--ai-surface); border:1px solid var(--ai-line); padding:12px var(--ai-s4) 14px; margin-bottom:18px; }
.ai-load__head { display:flex; align-items:center; justify-content:space-between; margin-bottom:10px; }
.ai-load__head > span:first-child { font-family:var(--ai-mono); font-size:var(--ai-fs-eyebrow); letter-spacing:.14em; text-transform:uppercase; color:var(--ai-faint); }
.ai-load__head > span:last-child { font-family:var(--ai-mono); font-size:var(--ai-fs-meta); color:var(--ai-muted); }
.ai-load__grid { display:grid; grid-template-columns:repeat(7,1fr); gap:var(--ai-s2); }
.ai-load__day { display:flex; flex-direction:column; gap:5px; }
.ai-load__bar { height:38px; background:var(--ai-bg); display:flex; align-items:flex-end; }
.ai-load__bar > i { display:block; width:100%; background:var(--ai-accent); }
.ai-load__bar--warn > i { background:var(--ai-warn); }
.ai-load__bar--off > i { background:var(--ai-line-2); }
.ai-load__lbl { font-family:var(--ai-mono); font-size:var(--ai-fs-eyebrow); color:var(--ai-ink-2); text-align:center; }
.ai-load__lbl b { font-weight:400; color:var(--ai-faint); }
.ai-load__day--warn .ai-load__lbl, .ai-load__day--warn .ai-load__lbl b { color:var(--ai-warn); }
.ai-load__day--off .ai-load__lbl, .ai-load__day--off .ai-load__lbl b { color:var(--ai-disabled); }

/* --- 11bis. L'AIDE EN LIGNE — LOT L, 02.09.2026 --------------------------
   ⛔ POURQUOI CES REGLES SONT ICI ET NON DANS UN <style> DE GABARIT.
      L'aide a DEUX portes : la page /aide et le repli d'un ecran (macro
      `porte_aide`). Les deux affichent le MEME fragment. Un style pose dans
      un seul des deux hotes donnerait le meme texte rendu de deux facons —
      exactement le defaut du badge d'environnement du rail (T20, sept ecarts
      sur huit proprietes mesurees). Une classe servie aux deux portes se
      style dans une feuille chargee par toutes les pages, ou nulle part.

   ⚠️ ET DANS `kairos-theme.css` PLUTOT QUE `components.css` : c'est CE
      fichier qui est charge en DERNIER (base.html:33), donc celui qui gagne
      a specificite egale. Les regles ci-dessous doivent battre Pico sur
      <details>, <summary>, <table> et <ol> ; on ne va pas le faire depuis la
      feuille qui perd.
   ------------------------------------------------------------------------- */

/* ══ 11bis.1 — LA PORTE D'UN ECRAN (macro `porte_aide`) ═══════════════════
   ⛔ PICO REPEINT <details> ET <summary> ET IL FAUT LE DEFAIRE EXPLICITEMENT.
      Trois de ses regles frappent ici, aucune n'est visible en lisant le
      gabarit — on les FORCE plutot que de re-theoriser la cascade :
        details { margin-bottom:var(--pico-spacing) }        -> 16 px fantomes
        details summary::after { float:right; chevron }      -> un chevron
              APRES notre bouton, flottant a droite de la ligne entiere
        details[open] > summary { margin-bottom:var(--pico-spacing) }
      Le chevron est tue par `content:none` ET `display:none` : la premiere
      suffit, la seconde protege si un jour la regle Pico pose un fond au
      lieu d'un `content`. */
/* ⛔ POINT 3 (11.09.2026), revue de Brice : « le bouton d'aide vit EN HAUT
   A DROITE, sur la ligne du titre de l'ecran ». SANS FAIRE SAUTER NI PERDRE
   UNE LIGNE -- explicite dans sa demande, UNE CONVENTION UNIQUE SUR TOUS
   LES ECRANS.
   ⚠️ CECI REOUVRE UN ARBITRAGE ANTERIEUR (LOT L, activites_competences.html) :
   la porte vivait volontairement SOUS le bandeau, jamais dedans, parce que
   le panneau (`.aide-corps`) est un enfant DU MEME <details> que le bouton
   (`<summary>`) -- le poser dans un bandeau etroit y ouvrirait le panneau
   EN PLACE, etroit, sur le fond du bandeau (le probleme mesure a l'epoque).
   Resolu par la POSITION plutot que par l'emplacement dans le flux :
   `.aide-corps` devient un panneau FLOTTANT (position:absolute), ancre au
   bouton -- ouvrir l'aide ne deplace donc plus rien autour de lui, et le
   panneau peut etre pleine largeur de son choix au lieu d'etre borne par
   la largeur du bouton qui l'ouvre. Toujours AUCUN JAVASCRIPT : le
   <details> natif ne change pas, seul l'endroit ou son enfant se peint.
   ⭐ PLUS DE SCOPING A UN CONTENEUR (`.ai-pagehead`, `.pe-head`, etc.) :
   mesure en convertissant les ecrans un par un (11.09.2026), AUCUN
   bandeau-titre du produit n'utilise une forme fixe -- certains sont
   `.ai-pagehead`, d'autres (personnel) `.pe-head`, d'autres encore
   (pilotage, statistiques, planning) un simple `<div style="display:flex;
   justify-content:space-between">` pose en dur, sans classe partagee.
   Plutot que lister chaque forme, le comportement flottant devient celui
   de `.aide-porte` PARTOUT, sans condition : posee dans un bandeau flex
   deja `justify-content:space-between`, elle se pousse a droite comme
   n'importe quel enfant flex ; posee ailleurs (l'ancien usage plein-
   largeur n'existe plus nulle part dans le depot, verifie par recherche),
   elle reste sur sa propre ligne, ce qui ne regresse rien. */
.aide-porte {
  display:block; margin:0; position:relative; align-self:center; flex:none;
}
.aide-porte > summary::after,
.aide-porte > summary::marker { content:none; display:none; }
.aide-porte > summary::-webkit-details-marker { display:none; }
.aide-porte .aide-corps {
  position:absolute; top:calc(100% + 10px); right:0; z-index:30;
  width:min(640px, calc(100vw - 48px)); max-height:70vh; overflow-y:auto;
  box-shadow:0 10px 30px rgba(13,20,32,.28);
}

/* Le bouton EST le <summary> : candidat 1 mesure a 18,45 : 1 (LOT_L_AIDE
   § 2). Geometrie de `.ai-btn-outline--neutral` — le lisere est le FOND du
   parent, la surface est peinte par l'enfant `.ai-btn-ghost`.
   ⚠️ `display:inline-block` et pas `flex` : un <summary> en flex perd son
      alignement de ligne dans Firefox, et l'enfant est deja `inline-flex`. */
.aide-porte > summary.aide-porte__bouton {
  display:inline-block; padding:1px; margin:0; cursor:pointer;
  background:var(--ai-line-2); color:var(--ai-ink); line-height:0;
  list-style:none; width:auto; text-align:left;
  clip-path: polygon(0 0, calc(100% - var(--ai-chamfer)) 0, 100% var(--ai-chamfer),
                     100% 100%, var(--ai-chamfer) 100%, 0 calc(100% - var(--ai-chamfer)));
}
/* ⚠️ Le focus, LUI, reste visible — contrairement au fantome neutre des
      boutons (dette « le fantome neutre sans focus »). Un <summary> est le
      seul cliquable de son ecran a s'atteindre uniquement au clavier depuis
      le titre : sans anneau, on ne sait plus ou on est. */
.aide-porte > summary.aide-porte__bouton:focus-visible {
  outline:2px solid var(--ai-accent); outline-offset:2px;
}
/* ⛔ DEFAUT MESURE LE 02.09, ET C'EST LE BOUTON QUI CHANGEAIT DE CANDIDAT.
      Premiere mesure au navigateur : contraste 7,27 : 1 — c'est-a-dire
      EXACTEMENT le chiffre du candidat 2 (le plein accent), celui que Brice
      n'a pas choisi. Cause : `.ai-btn-ghost` porte `color:var(--ai-accent)`,
      et c'est `.ai-btn-outline--neutral .ai-btn-ghost` qui le ramene a
      l'encre. Notre <summary> reprend la GEOMETRIE de `--neutral` sans en
      porter la CLASSE : il heritait donc du bleu.
      Le bouton rendait donc le candidat 2 pendant que le rapport annoncait
      le candidat 1. Un ecart de forme qui se lit comme un ecart de RANG :
      un bouton accent dit « action principale », et l'aide n'en est pas une.
      ⚠️ On ne « rajoute pas la classe » : `.ai-btn-outline--neutral` repeindrait
         aussi le fond du <summary>, deja pose ici. On reprend la seule
         declaration qui manquait. */
.aide-porte > summary.aide-porte__bouton .ai-btn-ghost { color:var(--ai-ink); }
.aide-porte > summary.aide-porte__bouton:hover .ai-btn-ghost { background:var(--ai-surface-2); }
/* L'etat dit CE QU'IL FAIT, pas ou il en est : « Aide » quand c'est ferme,
   « Masquer » serait un second libelle a traduire. On tourne l'icone. */
.aide-porte[open] > summary .ti-help-circle { transform:rotate(180deg); }
.aide-porte > summary .ti-help-circle { transition:transform 140ms ease; }

/* ══ 11bis.2 — LE CORPS DU FRAGMENT ══════════════════════════════════════
   Ce qui suit stylise du HTML ECRIT PAR BRICE, pas par un gabarit. Les
   selecteurs visent donc des BALISES NUES sous `.aide-corps` : il ne peut
   pas y avoir de classe a retenir, sinon la promesse « ecrire une aide =
   creer un fichier et ecrire dedans » est fausse. */
.aide-corps { background:var(--ai-surface); border:1px solid var(--ai-line-2);
  padding:18px 22px 20px; font-size:var(--ai-fs-body); color:var(--ai-ink-2);
  line-height:1.62; overflow-wrap:break-word;
  clip-path: polygon(0 0, calc(100% - var(--ai-chamfer-sm)) 0, 100% var(--ai-chamfer-sm),
                     100% 100%, var(--ai-chamfer-sm) 100%, 0 calc(100% - var(--ai-chamfer-sm))); }
.aide-corps > :first-child { margin-top:0; }
.aide-corps > :last-child { margin-bottom:0; }
.aide-corps h2 { font-family:var(--ai-font); font-size:var(--ai-fs-h2); font-weight:600;
  color:var(--ai-ink); margin:22px 0 8px; letter-spacing:-.01em; }
/* ⛔ LE NIVEAU DE SECTION SE DISTINGUE AUTREMENT QUE PAR LA TAILLE, ET
      C'EST LA GRILLE 3a QUI L'IMPOSE. Son echelle saute de 19 px (--ai-fs-h2)
      a 14 (--ai-fs-h3) puis 13,5 (--ai-fs-body) : entre un titre de section
      et un paragraphe, il reste UN DEMI-PIXEL. Vu a l'image le 02.09 sur le
      texte de Brice — six sections nettes a la lecture de son fichier, un
      seul pave gris a l'ecran. Et sur /aide c'est pire : le <h2> du fragment
      y est masque (deja rendu par la page), donc le <h3> est le SEUL niveau
      que le lecteur voit.
      ⚠️ On ne fabrique pas un jeton de taille intermediaire pour un seul
         bloc — ce serait une echelle parallele a maintenir. On prend le
         vocabulaire que le referentiel emploie deja pour marquer un titre :
         la BARRE D'ACCENT de `.ai-pagehead__eyebrow::before`, plus l'espace
         et un filet.
      ⚠️ `::before` EN INLINE-BLOCK, SURTOUT PAS `display:flex` SUR LE TITRE.
         Un titre en flex fait de chaque enfant INLINE un element flex :
         `<h3>Les <b>deux</b> reglages</h3>` se rangerait en trois colonnes.
         C'est le defaut que l'encadre « a retenir » venait de me couter le
         meme jour, et le contenu vient toujours du fichier de Brice. */
.aide-corps h3 { font-family:var(--ai-font); font-size:var(--ai-fs-h3); font-weight:600;
  color:var(--ai-ink); margin:30px 0 10px; padding-top:17px;
  border-top:1px solid var(--ai-line-soft); letter-spacing:-.005em; }
.aide-corps h3::before { content:""; display:inline-block; width:14px; height:2px;
  vertical-align:middle; background:var(--ai-accent); margin-right:9px;
  position:relative; top:-1px; }
/* Le premier titre n'a rien a separer de ce qui precede : ni filet, ni marge.
   Les deux cas se presentent — sur /aide le fragment commence par son <h3>
   (le <h2> est masque), derriere le bouton d'un ecran il suit le <h2>. */
.aide-corps > h3:first-child { border-top:0; padding-top:0; margin-top:2px; }
/* Derriere le bouton d'un ecran, le <h2> du fragment EST visible : le premier
   titre de section n'a pas de filet a poser (il n'y a rien a separer) mais il
   lui faut de l'air, sinon il colle au titre. Mesure a l'image : 2 px, les
   deux lignes se touchaient. */
.aide-corps > h2:first-child + h3 { border-top:0; padding-top:0; margin-top:16px; }
.aide-corps h4 { font-family:var(--ai-font); font-size:var(--ai-fs-body); font-weight:600;
  color:var(--ai-ink); margin:16px 0 6px; }
.aide-corps p { margin:0 0 12px; max-width:78ch; }
.aide-corps ul, .aide-corps ol { margin:0 0 12px; padding-left:22px; max-width:78ch; }
.aide-corps li { margin:0 0 5px; }
.aide-corps b, .aide-corps strong { color:var(--ai-ink); font-weight:600; }
.aide-corps code { font-family:var(--ai-mono); font-size:var(--ai-fs-meta);
  background:var(--ai-surface-2); color:var(--ai-ink); padding:1px 5px; }
.aide-corps a { color:var(--ai-accent); }
/* Un encadre « a retenir », disponible pour Brice sans rien apprendre :
   <p class="retenir">…</p>. Le seul crochet de classe de tout le corps. */
/* ⛔ `display:block` ET SURTOUT PAS `flex` — DEFAUT VU A L'IMAGE LE 02.09.
      En flex, CHAQUE ENFANT INLINE devient un element flex : un <strong> et
      deux fragments de phrase se sont ranges en trois colonnes cote a cote,
      et le paragraphe s'est lu de travers. Or ce bloc contient du texte
      courant ecrit par Brice — on ne peut pas lui demander de l'envelopper
      dans un <div> pour que la mise en page tienne.
      ⚠️ Aucune sonde ne l'a vu : le HTML etait bien forme, la console vide,
         l'empreinte identique aux deux portes. C'est l'ECRAN qui l'a montre. */
.aide-corps .retenir { display:block; margin:14px 0; padding:11px 14px;
  background:var(--ai-warn-bg); border-left:3px solid var(--ai-warn);
  color:var(--ai-warn-fg); max-width:none; }

/* ══ 11bis.3 — LE SCHEMA (proposition A, arbitree par Brice le 02.09) ═════
   Pas une image : une vraie table au gabarit 3a. Elle se lit a la loupe, se
   copie, se traduit, suit le theme sans qu'on la redessine, et reste juste
   quand la charte bouge.
   ⛔ TOUTES LES VALEURS SONT EXPLICITES parce que Pico habille deja <table> :
      `thead th` y recoit `--pico-border-width:0.1875rem` (3 px sous l'en-tete)
      et `td,th` un `border-bottom` a sa propre couleur. On ne les analyse pas,
      on les remplace. */
.aide-schema { width:100%; border-collapse:collapse; border-spacing:0;
  font-size:var(--ai-fs-table); margin:14px 0 6px; background:var(--ai-surface); }
.aide-schema caption { caption-side:top; text-align:left; font-size:var(--ai-fs-meta);
  color:var(--ai-muted); padding:0 0 8px; margin:0; }
.aide-schema th { text-align:left; padding:9px 12px; font-family:var(--ai-font);
  font-size:var(--ai-fs-eyebrow); text-transform:uppercase; letter-spacing:.08em;
  color:var(--ai-thead-fg); background:var(--ai-thead-bg); font-weight:500;
  border:0; border-bottom:0; vertical-align:middle; }
/* La colonne « question » se distingue de la colonne « qui » : c'est elle
   qu'on compare, l'autre n'est que l'etiquette de la ligne. */
.aide-schema th.q { background:var(--ai-surface-2); color:var(--ai-muted); }
/* ⛔ LOT M — UNE CELLULE D'EN-TETE VIDE NE SE PEINT PAS.
   Un schema en matrice a une case de coin sans titre : les colonnes disent
   « les semaines ecoulees / a partir du 1er octobre », les lignes disent
   « la trame / ce qu'affiche le planning », et le coin ne dit rien.
   Sans cette regle, ce coin recoit quand meme --ai-thead-bg et rend UN PAVE
   NOIR de 260 px, que l'oeil lit comme un defaut d'affichage.
   ⚠️ TROUVE A LA CAPTURE, PAS A LA LECTURE : le HTML etait juste, le CSS
      etait juste, chaque regle faisait exactement ce qu'elle disait. Deuxieme
      fois en deux lots qu'un defaut n'est atteignable que par l'image.
   Pose ici et pas dans le fragment : Brice ecrira d'autres schemas, et il ne
   doit pas avoir a se souvenir d'une astuce pour que son coin soit propre. */
.aide-schema th:empty { background:var(--ai-surface); border-bottom:1px solid var(--ai-line-2); }
.aide-schema td { padding:9px 12px; border:0;
  border-bottom:1px solid var(--ai-line-soft); vertical-align:top;
  color:var(--ai-ink-2); background:transparent; }
.aide-schema tbody tr:last-child td { border-bottom:0; }
.aide-schema .qui { font-weight:600; color:var(--ai-ink); white-space:nowrap; }
.aide-schema .comp { color:var(--ai-muted); font-size:var(--ai-fs-meta); }

/* Les deux verdicts, au langage des pastilles de statut du referentiel. */
.aide-oui, .aide-non { display:inline-flex; align-items:center; gap:6px;
  padding:2px 9px; font-size:var(--ai-fs-meta); font-weight:600; line-height:1.5;
  clip-path:polygon(0 0,calc(100% - 6px) 0,100% 6px,100% 100%,6px 100%,0 calc(100% - 6px)); }
.aide-oui { background:var(--ai-ok-bg); color:var(--ai-ok); }
.aide-non { background:var(--ai-off-bg); color:var(--ai-off-fg); }
.aide-1er { display:inline-block; margin-left:6px; font-size:var(--ai-fs-meta);
  color:var(--ai-muted); font-weight:400; }

/* ⚠️ 820 px : la table a trois colonnes ne se REPLIE pas, elle DEFILE, et
      dans son propre conteneur — jamais la page. Une table repliee en cartes
      perd la comparaison cote a cote, qui est la seule raison d'etre de la
      proposition A. Le conteneur est pose par Brice autour de la table :
      <div class="aide-defile"><table class="aide-schema">…</table></div> */
.aide-defile { overflow-x:auto; }

/* ══ 11bis.4 — LA PAGE /aide : DOUZE VOLETS, UN SEUL OUVERT (LOT N) ══════
   Brice : « les douze sections s'empilent : 2 002 mots à la file. »

   ⛔⛔ TOUT CE BLOC REPOSE SUR `:target`, ET IL N'Y A PAS UNE LIGNE DE JS.
      Un volet est ouvert PARCE QUE son ancre est celle de l'URL. Le
      navigateur n'a qu'une cible a la fois : « un seul ouvert » n'est pas
      une regle appliquee, c'est une propriete de la page. Rien a
      synchroniser, donc rien a desynchroniser.
      La justification complete (et les trois pistes ecartees : JavaScript,
      <details>, parametre serveur) vit dans app/templates/aide/index.html,
      une seule fois. */
/* ⛔ LA BOITE `.aide-sommaire` DU LOT L EST RETIREE — MESURE, PAS GOUT.
   Elle repetait les douze memes titres, avec les memes liens, 179 px
   au-dessus des douze bandes qui les portent deja. Ces 179 px etaient
   exactement ce qui poussait le douzieme titre sous la ligne de flottaison
   (dernier volet a 1 039 px pour 1 000 px de fenetre). Le libelle « Sur cette
   page », lui, reste : il coiffe maintenant la vraie liste.
   Le raisonnement complet est dans app/templates/aide/index.html. */
.aide-liste__t { font-family:var(--ai-mono); font-size:var(--ai-fs-eyebrow);
  letter-spacing:var(--ai-track-eyebrow); text-transform:uppercase;
  color:var(--ai-muted); margin:0 0 9px; }

/* L'ancre de fermeture. Elle n'occupe aucune place : c'est une destination,
   pas un element. `scroll-margin-top` la decale de la barre de retour collee
   en haut — sans lui, « Fermer » ramene le haut du contenu SOUS le bouton.
   ⛔⛔ ITEM 210 (24.09.2026) — LA SOMME EST FAITE ICI, PAS DANS `:root`, ET CE
      N'EST PAS UN GOUT. La barre de retour se gare maintenant 48 px plus bas
      au telephone (`--ai-collant-haut`, § 9 ter) : sans ces 48 px de plus, on
      recreerait EXACTEMENT le defaut que ce `scroll-margin-top` existe pour
      eviter, un cran plus bas. Et le calcul ne peut PAS vivre dans la
      declaration de `--ai-aide-sticky` : `--ai-collant-haut` est pose sur
      `<body>`, un `calc()` ecrit dans `:root` y lirait la valeur de `:root`
      (0px) et serait substitue AVANT d'arriver ici — la somme serait 84 px a
      toute largeur, en silence. Une propriete personnalisee se substitue la ou
      elle est DECLAREE, pas la ou elle est lue. */
.aide-ancre { display:block; height:0;
  scroll-margin-top:calc(var(--ai-aide-sticky) + var(--ai-collant-haut, 0px)); }

/* ⚠️ 84px = la hauteur REELLE de la barre de retour collee (12 + 44 + 12 + 1
      de bordure = 69) plus 15 de respiration. Elle est declaree UNE FOIS ici
      et lue par les deux endroits qui en ont besoin. Sans elle, cliquer un
      titre amene le volet EXACTEMENT SOUS le bouton de retour, qui le
      recouvre : le volet s'ouvre et on ne voit pas son titre — le genre de
      defaut qui ne se voit qu'a l'image. */
:root { --ai-aide-sticky:84px; }

.aide-liste { margin:0; }
/* Meme somme que `.aide-ancre`, et pour la meme raison — voir son commentaire. */
.aide-volet { margin:0 0 8px;
  scroll-margin-top:calc(var(--ai-aide-sticky) + var(--ai-collant-haut, 0px)); }

/* LA BANDE-TITRE, AU GABARIT 3a : surface, liseré, chanfrein.
   ⚠️ `min-width:0` ET `overflow-wrap` NE SONT PAS DES PRECAUTIONS DE STYLE :
      ils reparent un debordement REELLEMENT MESURE le 02.09. Le titre d'une
      section vient du fichier de Brice ; ce jour-la il contenait une ligne de
      60 « = » d'affilee — un mot insecable. Un element flex a pour largeur
      minimale celle de son contenu insecable : le titre a pousse la page a
      1148 px dans une fenetre de 820, et `.ai-content` tout entier avec.
      La lecon vaut au-dela du titre : TOUT ce que ce bloc affiche est ecrit
      hors du code, donc rien ne garantit qu'un mot y soit secable. */
.aide-volet__t { margin:0; display:flex; align-items:stretch;
  justify-content:space-between; gap:0;
  /* `position:relative` porte le lien de repli (voir plus bas). */
  position:relative;
  background:var(--ai-surface); border:1px solid var(--ai-line-2);
  clip-path: polygon(0 0, calc(100% - var(--ai-chamfer-sm)) 0, 100% var(--ai-chamfer-sm),
                     100% 100%, var(--ai-chamfer-sm) 100%, 0 calc(100% - var(--ai-chamfer-sm))); }
/* ⛔ LA CIBLE CLIQUABLE EST TOUTE LA BANDE, PAS LE MOT.
   `flex:1 1 auto` + le padding portes par le LIEN et non par la bande : un
   titre de trois lettres doit se cliquer aussi facilement qu'un titre long. */
.aide-volet__lien { flex:1 1 auto; min-width:0; display:flex; align-items:center;
  gap:10px; padding:13px 18px; text-decoration:none;
  font-family:var(--ai-font); font-size:var(--ai-fs-h3); font-weight:600;
  color:var(--ai-ink); letter-spacing:-.01em; }
.aide-volet__nom { min-width:0; overflow-wrap:anywhere; }
/* ⛔ `color` ET `text-decoration` SONT REDITS AU SURVOL, ET CE N'EST PAS
   DECORATIF — MEME LECON QU'AU BOUTON DE RETOUR (lot M) :
       a:hover { color:var(--ai-accent-2); text-decoration:underline }  (0,1,1)
       .aide-volet__lien { color:var(--ai-ink) }                        (0,1,0)
   `a:hover` gagne. Sans la regle ci-dessous, survoler un titre le souligne
   et le fait changer de couleur au milieu d'une bande — on ne compte pas
   sur ce qui « devrait » gagner, on ecrit un selecteur qui gagne. */
.aide-volet__lien:hover { color:var(--ai-accent); text-decoration:none;
  background:var(--ai-surface-2); }
.aide-volet__chev { flex:0 0 auto; color:var(--ai-muted);
  font-size:var(--ai-fs-icon-sm); transition:transform 140ms ease; }

/* ══ L'ETAT OUVERT — LA SEULE CHOSE QUI CHANGE EST LA CIBLE DE L'URL ══════ */
.aide-volet:target .aide-volet__t { border-color:var(--ai-accent);
  box-shadow:inset 3px 0 0 var(--ai-accent); }
.aide-volet:target .aide-volet__lien { color:var(--ai-accent); }
.aide-volet:target .aide-volet__chev { transform:rotate(90deg); color:var(--ai-accent); }
.aide-volet__corps { display:none; }
.aide-volet:target .aide-volet__corps { display:block; }
/* Le corps se colle a sa bande : `.aide-corps` porte deja sa propre bordure,
   et deux bordures qui se touchent font un trait de 2 px. */
.aide-volet:target .aide-corps { margin-top:-1px; }

/* ⛔ REFERMER PAR LA BANDE. La raison complete est dans le gabarit : `:target`
   n'est pas une bascule, et le CSS ne sait pas changer un `href`. Ce lien
   recouvre la bande UNIQUEMENT sur un volet ouvert, et mene a la meme sortie
   que « Fermer ». Sur un volet ferme il n'existe pas : la bande reste le lien
   d'OUVERTURE, et les ancres `#aide-xxx` continuent de fonctionner. */
.aide-volet__replier { display:none; }
.aide-volet:target .aide-volet__replier { display:block; position:absolute;
  inset:0; z-index:1; }
/* ⛔ « Fermer » REPASSE AU-DESSUS : sans ce cran, le lien de repli le
   recouvrirait et le bouton deviendrait decoratif — present, style, et mort. */
.aide-volet:target .aide-volet__fermer { position:relative; z-index:2; }
/* Le survol de la bande ouverte reste lisible : c'est le lien de repli qui
   recoit le survol, pas le titre — on tint donc la bande elle-meme. */
.aide-volet:target .aide-volet__t:hover { background:var(--ai-surface-2); }

/* « Fermer » N'EXISTE QUE SUR UN VOLET OUVERT. Sur un volet ferme il ne
   ferait rien : ce serait un bouton qui ment. */
.aide-volet__fermer { display:none; }
.aide-volet:target .aide-volet__fermer { display:inline-flex; align-items:center;
  gap:5px; flex:0 0 auto; padding:0 18px; white-space:nowrap;
  font-size:var(--ai-fs-meta); font-weight:400; color:var(--ai-muted);
  border-left:1px solid var(--ai-line-soft); text-decoration:none; }
.aide-volet:target .aide-volet__fermer:hover { color:var(--ai-accent);
  background:var(--ai-surface-2); text-decoration:none; }

/* ⚠️ LE TITRE DU FRAGMENT EST MASQUE ICI, ET SEULEMENT ICI.
      Le fragment porte son propre <h2> — c'est lui qui DECLARE le titre, et
      c'est ce qui rend le bloc autonome derriere le bouton d'un ecran. Sur
      /aide, le meme titre est deja rendu par la bande (il en vient). On le
      masque a l'affichage plutot que de le retirer du texte : on ne modifie
      jamais ce que Brice a ecrit, on choisit ce qu'on en montre — et le
      fragment reste une source unique, pas deux variantes.
      ⛔ LE SELECTEUR RESTE SCOPE A `.aide-volet` : hors de /aide, dans la
         porte d'un ecran, le titre DOIT s'afficher. Le descoper masquerait
         le titre de toutes les portes du produit, en silence. */
.aide-volet .aide-corps > h1:first-child,
.aide-volet .aide-corps > h2:first-child,
.aide-volet .aide-corps > h3:first-child,
.aide-volet .aide-corps > h4:first-child { display:none; }

/* ⛔ `.aide-section*` ET `.aide-remonter` ONT ETE RETIRES, PAS LAISSES.
   Ils stylaient l'empilement du lot L, que plus aucun gabarit n'emet.
   Une regle CSS orpheline n'est pas neutre : au prochain lot, elle laisse
   croire qu'il existe une deuxieme facon d'afficher une section — et c'est
   comme ca qu'on se retrouve avec deux rails. */

/* ══ 11ter — LE BANDEAU « FIN DE CONTRAT » (LOT 3, 03.09.2026) ═══════════
   Brice : « Reprends EXACTEMENT le bandeau du lot 1-bis — n'en invente pas
   un second. »

   ⛔ IL MONTE DE `fiche.html` VERS LE THEME, ET C'EST LE MOMENT EXACT.
      Au lot 1-bis il etait LOCAL a la fiche, avec cette note : « le lot 3
      doit partir d'ici plutot que d'en inventer un second ». Il y a
      maintenant TROIS surfaces (fiche, planning, calendrier). Un bandeau
      recopie trois fois diverge a la premiere retouche — c'est la lecon des
      deux rails (T20), et elle a deja coute trois defauts en une soiree.
      `.fi-sortie` disparait ; la fiche consomme `.ai-sortie` comme les deux
      autres.

   ⛔ IL SE DISTINGUE PAR SA FORME, PAS PAR SA TEINTE, ET C'EST CHIFFRE.
      Mesure du lot 1-bis :
          blanc sur #7A1D1D (ce bandeau)   10,39 : 1   (AA 4,5 ; AAA 7)
          blanc sur #A32D2D (MALADIE)       7,07 : 1
          LES DEUX FONDS ENTRE EUX          1,47 : 1
      1,47 : 1 — l'oeil ne separe PAS ces deux rouges. Choisir « un rouge un
      peu plus sombre » ne distingue donc rien du tout. Ce qui distingue :
        - PLEINE LARGEUR, hors grille : une absence est toujours dans une case ;
        - du TEXTE EN CLAIR : une absence n'a qu'une pastille et une infobulle ;
        - AUCUN EMOJI : les huit motifs d'absence en portent tous un
          (`MOTIF_ICON`), ne pas en avoir est le signe le plus lisible.

   ⚠️ `--ai-danger-2` N'EST PAS UTILISE ICI, LA VALEUR EST ECRITE EN DUR.
      Le jeton sert au SURVOL d'un bouton destructif ; si quelqu'un
      l'eclaircit un jour pour cette raison-la, le bandeau suivrait sans que
      personne ne le veuille. Deux usages, deux durees de vie. */
.ai-sortie { display:flex; align-items:center; gap:10px; margin:0 0 12px;
  padding:11px 16px; background:#7A1D1D; color:#fff;
  font-size:var(--ai-fs-body); font-weight:600; line-height:1.35;
  box-shadow:inset 5px 0 0 rgba(255,255,255,.35);
  clip-path:polygon(0 0,calc(100% - var(--ai-chamfer-sm)) 0,100% var(--ai-chamfer-sm),
                    100% 100%,var(--ai-chamfer-sm) 100%,0 calc(100% - var(--ai-chamfer-sm))); }
.ai-sortie .ti { font-size:var(--ai-fs-icon-md); flex:none; }
.ai-sortie b { font-weight:700; }
/* Plusieurs departs la meme semaine : on les empile, on ne les fusionne pas.
   « 2 fins de contrat » obligerait a aller chercher QUI. */
.ai-sortie + .ai-sortie { margin-top:-4px; }

/* ── LOT 4-bis — LE BANDEAU CLIQUABLE (tableau de bord) ────────────────────
   Il mene a la fiche de la personne, comme la carte qu'il remplace. Sur le
   planning et le calendrier il reste un <div> : la personne y est deja
   nommee dans la grille, et un bandeau cliquable qui n'irait nulle part de
   nouveau serait une promesse vide.

   ⛔⛔ LES DEUX LIGNES DE COULEUR NE SONT PAS COSMETIQUES — C'EST EXACTEMENT
      LE DEFAUT DU BOUTON DE RETOUR DU LOT M, ET IL SE REPOSE ICI :
          a:hover { color:var(--ai-accent-2); text-decoration:underline }  (0,1,1)
          .ai-sortie { color:#fff }                                       (0,1,0)
      `a:hover` GAGNE — un `:hover` pese comme une classe. Le libelle passerait
      donc a #14406f (bleu fonce) sur fond #7A1D1D (rouge fonce) au survol :
      contraste 1,3 : 1, un texte illisible sur un bandeau qui reste cliquable.
      `.ai-sortie--lien:hover` pese (0,2,0) et passe devant sans `!important`.
      ⚠️ ON NE SE DEMANDE PAS QUELLE REGLE « DEVRAIT » GAGNER, ON COMPTE — c'est
         la lecon des lots F, J, K et M, et c'est la quatrieme fois. */
.ai-sortie--lien { text-decoration:none; cursor:pointer; }
.ai-sortie--lien:hover,
.ai-sortie--lien:focus,
.ai-sortie--lien:visited { color:#fff; text-decoration:none; }
/* Le survol se voit par le FOND, seul signe qu'on est bien sur la cible. */
.ai-sortie--lien:hover { background:#8f2323; }
.ai-sortie--lien:focus-visible { outline:2px solid #fff; outline-offset:-4px; }
/* Le chevron pousse a droite : meme grammaire que `.ai-card__go`, qui dit
   « ceci mene quelque part » partout ailleurs dans le produit. */
.ai-sortie__go { margin-left:auto; opacity:.75; }
.ai-sortie--lien:hover .ai-sortie__go { opacity:1; }
/* A l'impression, il DOIT rester : c'est l'information qui manque le plus sur
   un planning affiche au mur. On le repasse en clair, encre sur blanc, avec
   un liseré : un aplat sombre pleine largeur vide une cartouche. */
@media print {
  .ai-sortie { background:#fff !important; color:#000 !important;
    border:1.5px solid #000; box-shadow:none !important; }
}

/* ══ 11quater — LE BANDEAU DE COMPTE RENDU D'UN GESTE (ITEM 120, 16.09.2026)
   Ce qui vient d'etre fait a votre demande, et s'il reste quelque chose a
   verifier. Gabarit : `app/templates/_bandeau_geste.html`.

   ⛔ IL MONTE DE DEUX GABARITS VERS LE THEME, POUR LA RAISON DU § 11ter.
      `.fi-info-geste` (personnel/fiche.html) et `.pat-info-geste`
      (patterns_personnel/list.html) etaient DEUX COPIES du meme bloc, a la
      declaration pres, et `list.html` l'ecrivait lui-meme : « meme gabarit
      visuel que personnel/fiche.html ». Les deux disparaissent ; les deux
      ecrans consomment `.ai-geste`. Meme geste qu'au LOT 3 pour `.fi-sortie`.

   ⛔⛔ CE QUI SEPARE REELLEMENT LES DEUX NIVEAUX : LA FORME, PAS LA TEINTE —
      ET C'EST MESURE, PAS ESPERE. La tentation etait d'ecrire « le fond passe
      a l'ambre, on voit bien la difference ». On a compte, comme au § 11ter :
          fond info vs fond avertissement   clair 1,05 : 1   sombre 1,18 : 1
          liseré info vs liseré avert.      clair 1,53 : 1   sombre 1,24 : 1
      1,05 et 1,53 — l'oeil ne separe PAS ces deux bandeaux sur la couleur,
      exactement comme les deux rouges du § 11ter (1,47 : 1). La teinte est un
      RENFORT, elle ne porte rien toute seule, et on ne pretend pas l'inverse.

      Ce qui porte, ce sont les deux differences de FORME, lisibles sans
      couleur et sans distinguer les teintes :
        - L'ICONE CHANGE DE GLYPHE : `ti-info-circle` -> `ti-alert-triangle`.
          Un rond et un triangle ne se confondent pas.
        - LE LISERE CHANGE D'EPAISSEUR : 3 px -> 5 px, sur le seul bord
          colore du bandeau.
      ⭐ Et le troisieme signe n'est pas visuel du tout : le gabarit pose
         `role="alert"` au lieu de `role="status"` (voir `_bandeau_geste.html`).
         L'emoji ⚠️ qu'on retire etait LU par un lecteur d'ecran ; le dessin
         seul ne l'est pas.

   ⭐ CE N'EST PAS UN TROISIEME VOCABULAIRE : c'est EXACTEMENT celui de
      `.ec-erreur` (echanges/fil.html et liste.html), qui pose deja
      `border-left:3px solid var(--ai-warn)` sur `var(--ai-warn-bg)` avec
      `ti-alert-triangle`. Un avertissement a la meme tete partout dans le
      produit, ou il n'est pas reconnu comme tel.

   ⚠️ LE TEXTE RESTE `--ai-ink` SUR LES DEUX NIVEAUX, il ne passe PAS en
      `--ai-warn-fg`. Mesure :
          clair   --ai-ink #2c3038 sur --ai-warn-bg #fbf0df   11,74 : 1  (AAA)
                  --ai-warn-fg #8a5008 sur #fbf0df             5,77 : 1  (AA)
          sombre  --ai-ink #f0ebe2 sur --ai-warn-bg #332615   12,38 : 1  (AAA)
                  --ai-warn-fg #e0a860 sur #332615             6,96 : 1  (AA)
      C'est une PHRASE, pas une etiquette : on la lit en entier, elle prend
      l'encre du produit et son meilleur contraste. `--ai-warn-fg` est reserve
      a l'icone — non-texte, seuil 3 : 1, tenu deux fois. */
.ai-geste { display:flex; align-items:flex-start; gap:9px; margin:0 0 12px;
  padding:10px 14px; background:var(--ai-surface); color:var(--ai-ink);
  border:1px solid var(--ai-line-2); border-left:3px solid var(--ai-accent);
  font-size:var(--ai-fs-body); }
.ai-geste .ti { color:var(--ai-accent); font-size:var(--ai-fs-icon-md); flex:none;
  /* L'icone s'aligne sur la PREMIERE ligne du texte, pas sur le haut de la
     boite : un bandeau qui passe a deux lignes a 390 px la laisserait sinon
     flotter au-dessus du mot. */
  line-height:1.35; }
.ai-geste--avertissement { background:var(--ai-warn-bg);
  border-color:var(--ai-warn); border-left:5px solid var(--ai-warn); }
.ai-geste--avertissement .ti { color:var(--ai-warn-fg); }
/* A l'impression, meme regle que `.ai-sortie` : le fond ambre vide une
   cartouche. Le liseré EPAIS reste — c'est lui qui distingue les deux
   bandeaux quand la couleur a disparu. */
@media print {
  .ai-geste--avertissement { background:#fff !important;
    border:1.5px solid #000; border-left:5px solid #000; }
}

/* ══ 11bis.5 — LE RETOUR DE /aide (LOT M) ════════════════════════════════
   Brice : « je crains de perdre la page que je consultais. »

   IL EST COLLE EN HAUT (`sticky`) ET NON POSE EN PIED DE PAGE.
   `top:0` suffit : le seul autre element colle de l'ecran est le rail, qui
   est dans l'AUTRE colonne de `.ai-shell` (flex) — ils ne se recouvrent
   jamais, quelle que soit la hauteur de la fenetre.
   ⚠️ `sticky` ne tient QUE si aucun ancetre ne porte `overflow` autre que
      `visible`. Verifie sur la chaine reelle — body > main.container >
      .ai-shell > .ai-main > .ai-pad : aucun n'en pose. Le jour ou l'un en
      posera un, ce bouton cessera de coller EN SILENCE (il restera visible
      et bien style, simplement il defilera) : c'est la forme de defaut que
      seule l'image trouve, et c'est pour ca qu'il est mesure a chaque lot.

   IL FAIT TOUTE LA LARGEUR ET IL A UN FOND OPAQUE. Sans fond, le contenu
   defile DERRIERE le bouton et on lit un texte a travers l'autre.

   ⛔ `.ai-btn` EST REPRIS TEL QUEL, ON NE REDESSINE PAS UN BOUTON.
      Geometrie, chanfrein, ombre, tailles : c'est le bouton primaire du
      referentiel, blanc sur --ai-accent, contraste mesure 7,27 : 1 (seuil
      AA 4,5 ; AAA 7). Redessiner ici aurait cree un deuxieme bouton
      primaire, qui aurait diverge du vrai a la premiere retouche du theme.

   ⛔⛔ LES DEUX LIGNES QUI SUIVENT NE SONT PAS COSMETIQUES — SANS ELLES LE
      LIBELLE DISPARAIT AU SURVOL, ET C'EST UNE QUESTION DE SPECIFICITE.
          a:hover { color:var(--ai-accent-2) }   (0,1,1) — un ':hover' pese
                                                  comme une classe
          .ai-btn { color:#fff }                 (0,1,0)
      `a:hover` GAGNE. Le libelle passe donc a #14406f pendant que
      `.ai-btn:hover` (0,1,1 lui aussi, mais ecrit apres) passe le fond au
      MEME #14406f : texte fonce sur fond fonce, contraste 1,00 : 1. Le
      bouton ne disparait pas — SON TEXTE disparait, ce qui est pire : il
      reste cliquable et illisible.
      C'est la meme lecon qu'aux lots F, J et K : on ne se demande pas quelle
      regle « devrait » gagner, on compte, et on ecrit un selecteur qui gagne.
      `.aide-retour .ai-btn` pese (0,2,0) et passe devant sans `!important`. */
/* ⛔⛔ ITEM 210 (24.09.2026) — LE COMMENTAIRE CI-DESSUS DISAIT « `top:0` SUFFIT,
   LE SEUL AUTRE ELEMENT COLLE EST LE RAIL, DANS L'AUTRE COLONNE ». C'ETAIT VRAI
   QUAND IL A ETE ECRIT, ET L'ITEM 122 L'A RENDU FAUX : au telephone, le rail
   quitte sa colonne et laisse a sa place un bouton `position:fixed` au coin
   haut-gauche. Mesure sur le banc, /aide a 320 / 390 / 430 px : le bouton
   recouvre 33 x 40 px de cette barre, et `elementFromPoint` y rend
   `i.ti-menu-2` — le bouton « Retour » n'est plus seulement illisible, il ne
   recoit plus le clic sur son tiers gauche. Le jeton `--ai-collant-haut`
   (§ 9 ter) vaut 0 partout ailleurs : hors telephone, `top` rend 0 comme avant. */
.aide-retour { position:sticky; top:var(--ai-collant-haut, 0px); z-index:20;
  padding:12px 24px; background:var(--ai-surface);
  border-bottom:1px solid var(--ai-line-2); }
.aide-retour .ai-btn,
.aide-retour .ai-btn:hover,
.aide-retour .ai-btn:focus { color:#fff; text-decoration:none; }
/* Le fond, lui, DOIT changer au survol : c'est le seul signe qu'on est bien
   sur la cible. On reprend la valeur de `.ai-btn:hover` du referentiel. */
.aide-retour .ai-btn:hover { background:var(--ai-accent-2); }
.aide-retour__btn { min-height:44px; }

/* ══ 11bis.5 — QUAND IL N'Y A RIEN, ET QUAND C'EST CASSE ═════════════════
   Les deux messages disent QUOI FAIRE et OU. « L'aide n'est pas disponible »
   n'apprend rien a qui vient de cliquer sur « Aide » ; le chemin du fichier,
   si. C'est la meme regle que le bandeau « aucun reglage n'a encore ete
   choisi » de /activites-competences. */
.aide-vide { display:flex; align-items:flex-start; gap:10px; padding:12px 16px;
  background:var(--ai-warn-bg); border-left:3px solid var(--ai-warn);
  color:var(--ai-warn-fg); font-size:var(--ai-fs-body); }
.aide-vide i { font-size:var(--ai-fs-icon-sm); margin-top:2px; }
.aide-vide code, .aide-absente code { font-family:var(--ai-mono);
  font-size:var(--ai-fs-meta); background:var(--ai-surface); padding:1px 5px; }
.aide-absente { margin:0; padding:11px 14px; background:var(--ai-warn-bg);
  border-left:3px solid var(--ai-warn); color:var(--ai-warn-fg);
  font-size:var(--ai-fs-body); max-width:none; }

/* ⛔ ITEM 149, ÉTAPE 1 (18.09.2026) — « CONTACTER LE SUPPORT ». Carte posée
   AU-DESSUS du sommaire des sections, jamais mélangée à elles : ce n'est
   pas une aide à lire, c'est une action. `.ai-champs` (§ plus haut, la
   règle des largeurs par type de champ) fait le travail des champs eux-
   mêmes ; cette carte ne pose que son cadre. */
.aide-support { background:var(--ai-surface); border:1px solid var(--ai-line);
  padding:var(--ai-s5); margin-bottom:var(--ai-s5); }
.aide-support__t { display:flex; align-items:center; gap:8px; margin:0 0 var(--ai-s4);
  font-size:var(--ai-fs-h3); font-weight:600; color:var(--ai-ink); }
.aide-support__t .ti { color:var(--ai-accent); }
.aide-support .kx-info { margin-bottom:var(--ai-s4); }
.aide-support form label { display:block; font-size:var(--ai-fs-eyebrow);
  font-family:var(--ai-mono); text-transform:uppercase; letter-spacing:.08em;
  color:var(--ai-faint); margin-bottom:var(--ai-s4); }
.aide-support form input, .aide-support form textarea { margin-top:4px; }
/* ⚠️ `.ai-btn` porte `white-space:nowrap` dans le socle (correct pour ses
   dizaines d'usages à libellé court) — MESURÉ à 390 px, rail ouvert, sur
   ce bouton précis : le même défaut que `.ai-btn-ghost` corrigé le même
   jour sur `personnel/fiche.html` (item 148/149, correctif 390 px).
   Libellé raccourci ET retour à la ligne autorisé — deux remèdes, pas un
   seul, parce que la carte vit dans un châssis qui n'offre que ~180 px de
   large à 390 px (même limite déjà disclosée, pas propre à cet écran). */
.aide-support__envoyer { white-space:normal; text-align:left; height:auto; }

/* ══ 11quater — LE FIL D'ARIANE : UN SEUL SEPARATEUR, DESSINE ICI ════════
   Item 73, 15.09.2026. `scripts/verifier_fil_ariane.js` le mesure.

   ⛔ LE DEFAUT : « Personnel > › > JP > › > Trames ». Pico pose deja un
      separateur sur `nav[aria-label=breadcrumb] ul li:not(:last-child)::after`
      (content ">") ; les sept gabarits en ecrivaient un second a la main, dans
      un `<li>›</li>` — qui recevait LUI AUSSI le `::after` de Pico. Trois
      glyphes par intervalle, et un lecteur d'ecran annoncait « liste,
      7 elements » pour 4 etapes en lisant « › » et « > ».
   ⛔ ET A 390 PX : le `li` de Pico porte 1rem de marge interne verticale et
      un `::after` en `position:absolute` large de 2rem. Le fil passait a la
      ligne au milieu d'une etape (« Sites & / salles ») et une ligne pouvait
      COMMENCER par un separateur.

   ⭐ LA REGLE : le gabarit n'ecrit que les etapes (et `aria-current="page"`
      sur la derniere) ; le separateur est dessine ICI, une seule fois.
      - dans le flux (`position:static`) : il appartient a l'etape qui le
        precede, et une etape intermediaire ne se coupe pas (`nowrap`) — un
        separateur ne peut donc jamais ouvrir une ligne ;
      - `›`, le glyphe des gabarits, de `pilotage/*.html` (`&rsaquo;`) et de
        la fiche personnel : un seul dans le produit ;
      - `content:"›" / ""` : texte alternatif VIDE, le separateur n'est pas
        lu. Un navigateur qui ignore cette syntaxe rejette la declaration et
        garde la precedente — il lit « › », comme avant, rien de pire.
   ⚠️ Poids : `ul li:not(:first-child)` de Pico pese (0,2,3) et pose une marge
      de debut — la remise a zero reprend ce selecteur, sinon elle perd. */
nav[aria-label=breadcrumb] { margin:0 0 .75rem; }
nav[aria-label=breadcrumb] ul { display:flex; flex-wrap:wrap; align-items:center;
  row-gap:4px; column-gap:0; margin:0; padding:0; list-style:none; }
nav[aria-label=breadcrumb] ul li,
nav[aria-label=breadcrumb] ul li:not(:first-child) { margin:0; padding:0; min-width:0; }
nav[aria-label=breadcrumb] ul li:not(:last-child) { white-space:nowrap; }
nav[aria-label=breadcrumb] ul li a { margin:0; padding:0; }
nav[aria-label=breadcrumb] ul li:not(:last-child)::after {
  content:"›"; content:"›" / "";
  position:static; display:inline-block; width:auto; margin:0 .6em;
  color:var(--ai-muted); text-decoration:none; }

/* --- 12. IMPRESSION ----------------------------------------------------- */
@media print {
  /* LOT M : `.aide-retour` rejoint la liste. Un bouton colle en haut de
     l'ecran s'imprime sur CHAQUE page dans certains navigateurs, et une
     aide imprimee n'a pas de « page precedente » ou revenir. */
  .ai-rail,.ai-toolbar,.ai-weeknav,.aide-retour { display:none !important; }
  body { background:#fff; }
  .ai-card,.ai-table-wrap,.ai-subplan,.ai-load { border-color:#000; box-shadow:none; }
  table.ai-table thead tr { background:#fff; }
  /* T14 : le fond sombre est desormais sur le th — il faut le neutraliser ICI
     aussi, sinon l'en-tete part en aplat noir a l'impression. */
  table.ai-table th { color:#000; background:#fff; border-bottom:1.5px solid #000; }
  table.ai-table tbody tr:hover td,
  table.ai-table tbody tr.is-selected td { background:#fff; }
  .ai-status { background:#fff !important; color:#000 !important; border:1px solid #000; }
}
