/* =====================================================================
   ERP CHAMPS CROISÉS — feuille de style unique de l'application.
   Charte validée par le client : coquelicot #C8102E sur fond clair.

   Socle commun aux 2 développeurs : on N'AJOUTE PAS de CSS dans les vues.
   Toute classe utile à plusieurs écrans vient ici, et seulement ici.

   Sommaire
     1. Jetons (couleurs, rayons, ombres)
     2. Base & typographie
     3. Coquille : topbar / sidebar / main      <- règle body>.app (cf. ⚠)
     4. Écran de connexion
     5. Panneaux, en-têtes de page, grilles
     6. KPI
     7. Boutons .btn
     8. Badges .b, pastilles .chip, onglets .tabs
     9. Formulaires .field / .input
    10. Tableaux .tbl
    11. Kanban .kanban
    12. Calendrier .cal                          <- ⚠ classe .ev.app
    12 bis. Gantt .gantt                         <- vue chronologique en barres
    13. Modales .modal
    14. Toasts .toast
    15. Graphiques .bars / .donut
    16. États : vide, chargement, erreur
    17. Impression
   ===================================================================== */

/* ---------------------------------------------------------------------
   1. JETONS
   --------------------------------------------------------------------- */
:root{
  /* Charte */
  --poppy:#C8102E;          /* coquelicot — couleur d'accent unique */
  --poppy-dark:#8c0d22;
  --poppy-clair:#f3a39b;
  --ink:#2b2b2b;            /* texte principal */
  --muted:#6f6f6f;          /* texte secondaire */
  --soft:#fbeceb;           /* aplat coquelicot très clair */
  /* ⚠⚠ CE COMMENTAIRE A ÉTÉ FAUX DEUX FOIS, DANS LES DEUX SENS (repris le
     30/09). Il disait « aplat de ligne alternée » quand aucun zébrage
     n'existait ; puis « il n'existe AUCUNE règle `nth-child`, fond des
     EN-TÊTES et des PIEDS » après que le zébrage eut été écrit (25/09) et que
     l'en-tête eut perdu son aplat (24/09). Ce que ce jeton peint, au 30/09,
     ENTRE AUTRES — la liste complète est `grep -n 'var(--soft2)' app.css`
     (61 emplois ce jour-là) ; recopiée ici en entier, elle vieillirait comme
     les deux précédentes :
       · les LIGNES PAIRES de tous les tableaux `.tbl` — le zébrage, bloc
         « TABLEAUX : EN-TÊTE VISIBLE ET LIGNES ALTERNÉES » (et ses exceptions
         `nth-child(even)` par écran). C'est son emploi le plus étendu : qui
         change ce jeton change d'abord TOUS les tableaux ;
       · les lignes de section, écartées et dépliées, les tuiles du Suivi et
         son « à vérifier », les pieds de fenêtre, les champs en lecture
         seule, le survol des boutons et du menu ;
       · la frise du Planning (week-ends, groupes, lignes libres), le
         calendrier (en-tête, jours hors du mois), l'explorateur, les codes de
         l'émargement, les pistes des barres de progression, et deux lignes
         des Paramètres (`.pm-verrou`, `.mo-groupe-tete`).
     Il ne peint PLUS l'en-tête des tableaux (`#faf9f9`, section 10, 27/09),
     ni leur pied (`tfoot td` porte `--soft`). */
  --soft2:#fdf6f5;          /* zébrage des tableaux, fonds secondaires */
  /* ⚠⚠ LE SURVOL D'UNE LIGNE A SON PROPRE JETON, ET C'EST TOUT LE POINT.
     Il valait `--soft2` : au survol, une ligne prenait EXACTEMENT la couleur
     de l'en-tête, et sur une table longue le repère « ceci est l'en-tête »
     disparaissait sous le curseur. Un jeton, deux fonctions.
     ⚠ Gris neutre, là où `--soft` (la sélection) est rosé : leurs clartés sont
     proches — 1,08:1 —, c'est la TEINTE qui les sépare. C'est acceptable ici
     et nulle part ailleurs : le survol suit le curseur, on sait toujours quelle
     ligne il désigne, alors que la sélection persiste sans rien pour la
     désigner. Ne pas recopier ce raisonnement pour un état durable. */
  --ligne-survol:#ebe6e4;   /* 1,24:1 sur blanc · 1,16:1 sur --soft2 */
  --line:#e6e1df;           /* filets et bordures — DÉCORATIF */
  /* ⚠⚠ LE CONTOUR D'UNE COMMANDE N'EST PAS UN FILET DÉCORATIF.
     Mesuré le 22/08 : `--line` sur blanc donne **1,30:1**. Le RGAA impose
     3:1 aux ÉLÉMENTS D'INTERFACE et à leurs contours (WCAG 1.4.11) — pas
     seulement au texte, et c'est le seuil qu'on oublie parce qu'on ne pense
     qu'au texte.
     Un filet qui sépare deux cartes n'est pas concerné : rien ne dépend de sa
     visibilité. Le contour d'un CHAMP DE SAISIE ou d'un BOUTON SECONDAIRE,
     si : sans lui, on ne sait pas où taper ni qu'il y a un bouton. D'où deux
     jetons, et non un seuil imposé partout — qui alourdirait toute la mise en
     page pour rien.
     ⚠ Le même défaut, et la même correction, dans `poppy.css`
     (`--line-champ`). Les deux feuilles sont indépendantes à dessein ; c'est
     le prix à payer, et il est écrit des deux côtés. */
  --line-champ:#8a8a8a;     /* contour d'une COMMANDE — 3,45:1 sur blanc */
  --navy:#3a3f5c;           /* couleur froide de contraste */

  /* Fonds */
  /*
   * ⚠⚠ BLANC LE 26/09 AU MATIN, PUIS #f4f3f2 LE MÊME SOIR, sur deux demandes
   *    successives de Julien : « le background blanc sur tout le site, plus de
   *    gris », puis « je veux le fond #f4f3f2 en background partout ». La
   *    seconde fait foi.
   *
   * ⭐ MESURÉ AVANT DE BASCULER, DANS LES DEUX SENS. Sur #f4f3f2 les huit
   *   contrastes de texte redescendent d'environ 10 % — et TOUS tiennent le
   *   seuil AA : --ink 12,78 · --poppy-dark 8,63 · --info 6,20 · --poppy 5,31
   *   · --att 4,64 · --ok 4,61 · --muted 4,53 · --line-champ 3,12 (seuil 3,
   *   c'est un contour de commande, pas du texte).
   * ⚠ LE PLUS SERRÉ EST `--muted`, À 4,53:1 : il tient, mais il n'a plus de
   *   marge. Toute retouche de ce gris, ou tout fond un cran plus sombre, le
   *   ferait passer sous 4,5. C'est le jeton à mesurer en premier la prochaine
   *   fois qu'on touche à cette ligne.
   * ⚠⚠ ET L'IMPACT RÉEL EST PLUS PETIT QUE CES CHIFFRES : le texte de
   *    l'application vit sur `--carte`, qui reste BLANC. Seuls le titre de
   *    page, son sous-titre et quelques notes posent du texte directement sur
   *    le fond.
   *
   * ⭐ CE QUE ÇA REND : une carte `.panel` se détache de nouveau par sa
   *   COULEUR (1,11:1 contre le fond, au lieu de 1,00 en blanc sur blanc).
   *   C'est très exactement la réserve écrite au point de coordination le
   *   matin même — « si tu trouves que les cartes flottent sur un de tes
   *   écrans, dis-le » : elle tombe d'elle-même.
   * ⚠ Les aplats des en-têtes de tableau et de panneau, eux, NE REVIENNENT
   *   PAS : ils sont partis les 24, 25 et 26/09 pour une autre raison — un
   *   en-tête n'est pas une information, et la ligne la plus voyante d'une
   *   liste ne doit pas être celle qui ne porte aucune donnée.
   */
  --fond:#f4f3f2;
  --carte:#ffffff;

  /* Sémantique (statuts) */
  --ok:#1e7d51;      --ok-bg:#e7f5ee;
  /* ⚠ #a86a00 tombait a 4,04:1 sur --att-bg et 4,44:1 sur blanc, pour un
     minimum WCAG AA de 4,5:1 — et c est la couleur de TOUT ce qui alerte :
     pastilles « a faire », bandeaux d avertissement, evenements en attente
     du calendrier. Le seul ton du systeme qui echouait, et le plus utile a
     lire. #9a6100 rend 4,68:1 et 5,14:1 sans changer la teinte percue. */
  --att:#9a6100;     --att-bg:#fdf3e2;
  --ko:#C8102E;      --ko-bg:#fbeceb;
  --info:#2f5d8a;    --info-bg:#f1f6fb;
  --neutre:#5c5c5c;  --neutre-bg:#eeecea;

  /* Formes */
  --r:10px;          /* rayon standard */
  --r-s:6px;
  --ombre:0 1px 2px rgba(43,43,43,.06), 0 4px 14px rgba(43,43,43,.05);
  --ombre-haut:0 10px 40px rgba(43,43,43,.18);

  /* Gabarit */
  --h-topbar:58px;
  --l-sidebar:236px;
  --l-sidebar-reduit:58px;   /* colonne d'icônes seules : voir « menu rétractable » */
}

/* ---------------------------------------------------------------------
   2. BASE & TYPOGRAPHIE
   --------------------------------------------------------------------- */
*,*::before,*::after{ box-sizing:border-box; }

html,body{ height:100%; }
body{
  margin:0;
  background:var(--fond);
  color:var(--ink);
  font-family:"Segoe UI","Helvetica Neue",Arial,sans-serif;
  font-size:14px;
  line-height:1.5;
  -webkit-font-smoothing:antialiased;
}

h1,h2,h3,h4{ line-height:1.25; margin:0; font-weight:700; }
h1{ font-size:20px; }
h2{ font-size:17px; }
h3{ font-size:15px; }
h4{ font-size:13px; }
p{ margin:.4em 0; }

a{ color:var(--poppy-dark); text-decoration:none; }
a:hover{ text-decoration:underline; }

code,kbd{ font-family:Consolas,"Courier New",monospace; font-size:12.5px; }

.sourdine{ color:var(--muted); }
.petit{ font-size:12.5px; }
/* ⚠⚠ `.att` NUE N'EXISTAIT PAS, et un ecran s'en servait deja. La vue
   Parametres emettait <div class="petit att"> pour distinguer un
   avertissement d'une simple note : la feuille ne connaissait que
   `.alerte.att`, `.kpi.att` et `.b-att`, donc l'« avertissement » sortait en
   NOIR ORDINAIRE et la « note » en gris — la hierarchie visuelle EXACTEMENT
   a l'envers de ce que le code annoncait.
   ⚠ LA REGLE PORTE SUR `.petit.att` ET NON SUR `.att` SEULE, et ce n'est pas
   de la prudence gratuite : `.kpi.att` ne declare que sa bordure et laisse ses
   libelles heriter la couleur du corps. Une regle `.att{color:…}` nue les
   repeindrait tous — apropos.js et demandes.js en portent — pour un correctif
   qui ne visait qu'un texte de formulaire. */
.petit.att{ color:var(--att); }
.gras{ font-weight:700; }
.droite{ text-align:right; }
.centre{ text-align:center; }
.nowrap{ white-space:nowrap; }
.pleine{ width:100%; }
.mono{ font-family:Consolas,"Courier New",monospace; }

/* Utilitaires d'espacement (volontairement peu nombreux) */
.mt0{margin-top:0}    .mt1{margin-top:8px}   .mt2{margin-top:16px}  .mt3{margin-top:24px}
.mb0{margin-bottom:0} .mb1{margin-bottom:8px}.mb2{margin-bottom:16px}.mb3{margin-bottom:24px}

/* Barre d'outils horizontale (filtres, boutons d'action) */
.barre{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
.barre .pousse{ margin-left:auto; }

/* ---------------------------------------------------------------------
   3. COQUILLE : TOPBAR / SIDEBAR / MAIN
   --------------------------------------------------------------------- */
.topbar{
  position:fixed; inset:0 0 auto 0; height:var(--h-topbar); z-index:40;
  display:flex; align-items:center; gap:18px; padding:0 16px;
  background:var(--carte); border-bottom:3px solid var(--poppy);
  box-shadow:0 1px 3px rgba(43,43,43,.07);
}

/* Marque : logo optionnel + signature typographique */
.topbar .marque{ display:flex; align-items:center; gap:10px; min-width:0; }
.topbar .marque img{ height:34px; width:auto; display:block; }
.topbar .marque .mark{ position:relative; width:40px; height:26px; flex:0 0 auto; }
.topbar .marque .mark span{ position:absolute; top:0; width:26px; height:26px; border-radius:50%; }
.topbar .marque .mark .r{ left:0; background:var(--poppy); }
.topbar .marque .mark .g{ left:13px; background:#9a9a9a; mix-blend-mode:multiply; }
.topbar .marque .nom{ font-weight:800; letter-spacing:2px; font-size:14px; white-space:nowrap; }
.topbar .marque .nom small{
  display:block; letter-spacing:1.6px; font-weight:600; font-size:9px;
  color:var(--muted); text-transform:uppercase;
}

/*
 * ┌──────────────────────────────────────────────────────────────────────────┐
 * │ LE CHAMP DE RECHERCHE — généralisé le 24/09/2026.                        │
 * │                                                                          │
 * │ La loupe et la pilule ne vivaient que dans `.topbar`. Les listes, elles, │
 * │ posaient un `.input` nu : le même geste — chercher — se présentait de    │
 * │ deux façons selon l'endroit, et celui des listes ne se distinguait pas   │
 * │ d'un champ de saisie ordinaire. `.champ-recherche` porte la forme, et    │
 * │ `.topbar .recherche` la RÉUTILISE au lieu de la redéclarer : une seconde │
 * │ copie aurait divergé à la première correction, dixième fois ici.         │
 * │                                                                          │
 * │ ⚠ AJOUT PUREMENT ADDITIF pour les listes : un champ qui ne pose pas la   │
 * │   classe garde l'apparence d'un `.input`, au pixel près.                 │
 * └──────────────────────────────────────────────────────────────────────────┘
 */
.champ-recherche{ position:relative; display:inline-block; }
.champ-recherche input{
  width:100%; height:34px; padding:0 12px 0 32px;
  border:1px solid var(--line); border-radius:18px; background:var(--soft2);
  font:inherit; font-size:13px; color:var(--ink); outline:none;
}
.champ-recherche input:focus{ border-color:var(--poppy); background:#fff; }
/* ⚠ La loupe est DÉCORATIVE : `pointer-events:none` pour qu'un clic dessus
   atteigne le champ, et aucun texte alternatif — le champ porte déjà son
   `aria-label`, et une loupe annoncée en plus ferait doublon à l'oreille. */
/* ⚠⚠ LA LOUPE EST UN TRACÉ, PLUS UN CARACTÈRE (27/09). `⌕` (U+2315) se dessine
   sur ce serveur, mais en anneau fin sans manche — et il n'est garanti dans
   AUCUNE police : c'est le piège du `⧗` du 26/09, qui sortait un rectangle
   vide depuis sa naissance sans que rien dans le code puisse le dire. Les
   treize maquettes montrent une vraie loupe.
   ⚠ SVG en ligne dans un `data:` — aucune dépendance, aucune requête. La
     couleur y est FIGÉE (un `data:` ne connaît pas `currentColor`) : c'est la
     valeur de `--muted`, et si ce jeton bouge un jour, cette ligne aussi. Le
     dire vaut mieux que le découvrir. */
.champ-recherche::before{
  content:""; position:absolute; left:11px; top:50%; width:14px; height:14px;
  transform:translateY(-50%); pointer-events:none; opacity:.8;
  background:no-repeat center/14px url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='M20 20l-3.5-3.5'/%3E%3C/svg%3E");
}

/* Recherche globale : la même forme, une largeur à elle. */
.topbar .recherche{ flex:1; max-width:460px; }

/* Identité + déconnexion */
.topbar .moi{ margin-left:auto; display:flex; align-items:center; gap:12px; }
.topbar .moi .qui{ text-align:right; line-height:1.2; }
.topbar .moi .qui b{ display:block; font-size:13px; }
.topbar .moi .qui span{ font-size:11px; color:var(--muted); text-transform:uppercase; letter-spacing:.6px; }
.topbar .moi .pastille{
  width:34px; height:34px; border-radius:50%; background:var(--poppy); color:#fff;
  display:flex; align-items:center; justify-content:center; font-weight:800; font-size:13px;
}

/* ⚠⚠ RÈGLE CRITIQUE — NE PAS SIMPLIFIER EN « .app »
   Le calendrier utilise la classe d'événement « .ev.app » (session en attente).
   Un sélecteur nu « .app » attrapait ces événements et leur appliquait le
   gabarit de la coquille (grille plein écran) : calendrier explosé.
   On cible donc UNIQUEMENT l'enfant direct de <body>. */
body>.app{
  display:grid;
  grid-template-columns:var(--l-sidebar) 1fr;
  align-items:start;
  padding-top:var(--h-topbar);
  min-height:100vh;
}

.sidebar{
  position:sticky; top:var(--h-topbar);
  height:calc(100vh - var(--h-topbar));
  overflow-y:auto;
  background:var(--carte); border-right:1px solid var(--line);
  padding:14px 10px 24px;
}
.sidebar .groupe{
  font-size:10px; font-weight:700; letter-spacing:1.2px; text-transform:uppercase;
  color:var(--muted); margin:16px 6px 6px 10px;
  display:flex; align-items:center; justify-content:space-between;
  cursor:pointer; user-select:none; border-radius:var(--r-s);
}
.sidebar .groupe:hover{ color:var(--poppy-dark); }
.sidebar .groupe:focus-visible{ outline:2px solid var(--poppy); outline-offset:2px; }
.sidebar .groupe:first-child{ margin-top:2px; }
/* Même échelle que .fleche des en-têtes de tableau triables (app.css) : un
   petit indicateur qui ne rivalise pas avec le libellé du groupe. */
.sidebar .groupe .chevron{ opacity:.55; font-size:10px; flex:0 0 auto; margin-left:8px; }

/* ⚠ `.groupe-corps` n'existait pas avant le repli par groupe : c'est le seul
   conteneur entre l'en-tête et ses liens, et c'est LUI qu'on masque — jamais
   les `<a>` un par un, ce qui laisserait échapper le dernier si la vue change
   sans repasser par buildSidebar(). */
.sidebar .groupe-corps.replie{ display:none; }
/* ⚠⚠ EN MENU RÉDUIT, UN GROUPE NE RESTE JAMAIS MASQUÉ. Son en-tête n'est plus
   qu'un filet de 1 px (règle plus bas) : sans texte ni cible cliquable, un
   groupe replié y deviendrait un trou dans la colonne d'icônes, invisible et
   sans moyen de le rouvrir. Cette règle l'emporte sur `.replie` ci-dessus —
   voir aussi buildSidebar(), qui ignore l'état replié dans ce mode. */
body>.app.reduit .sidebar .groupe-corps.replie{ display:block; }
.sidebar a{
  display:flex; align-items:center; gap:10px;
  padding:8px 10px; margin-bottom:2px; border-radius:var(--r-s);
  /* ⚠ 500 et non 600 (27/09, maquette) : en demi-gras, les quinze entrées du
       menu avaient le même poids que l'entrée ACTIVE, et rien ne se détachait.
       Le dessin met le menu en graisse normale et ne renforce que l'entrée où
       l'on se trouve. */
  color:var(--ink); font-size:13.5px; font-weight:500; text-decoration:none;
  border-left:3px solid transparent;
}
.sidebar a:hover{ background:var(--soft2); text-decoration:none; }
.sidebar a.actif{
  background:var(--soft); color:var(--poppy-dark);
  border-left-color:var(--poppy); font-weight:700;
}
.sidebar a .ic{ width:20px; text-align:center; font-size:15px; flex:0 0 auto; }
/* ⚠ UNE ICÔNE DESSINÉE (un nom du catalogue de `CC.icone()`, 27/09) n'est pas
   un caractère : laissée EN LIGNE, un <svg> se pose sur la ligne de base du
   texte et remontait de 3,5 px au-dessus du libellé (mesuré dans chromium sur
   « Documents »). En bloc centré, il tient la case du caractère qu'il
   remplace, centré comme lui.
   ⚠ `height:1.5em` : la hauteur de LIGNE d'un caractère de cette case (le corps
   est en `line-height:1.5`). Sans elle, la case tombait à 16 px et l'entrée
   devenait 2,5 px plus basse que ses voisines ; le dessin, lui, garde ses
   16 px de large et se centre dans cette hauteur (`viewBox` carré). */
.sidebar a .ic > svg{ display:block; margin:0 auto; height:1.5em; }
/* ⚠ ET ELLE PREND LA COULEUR DU TEXTE (27/09, maquette) : grise au repos,
   encre au survol, coquelicot sur l'entrée active. Sur les treize captures, le
   menu se lit ainsi — l'icône accompagne le libellé, elle ne le concurrence
   pas. Ajouté au réglage de dev A, qui règle l'ALIGNEMENT (mesuré dans
   chromium) ; les deux ne se recouvrent pas. */
.sidebar a .ic{ color:var(--muted); }
.sidebar a:hover .ic{ color:var(--ink); }
.sidebar a.actif .ic{ color:var(--poppy); }
.sidebar a .compteur{
  margin-left:auto; background:var(--poppy); color:#fff; border-radius:10px;
  font-size:10.5px; font-weight:700; padding:1px 7px;
}

/* ---------------------------------------------------------------------
   MENU RÉTRACTABLE — la barre se réduit à une colonne d'icônes

   ⚠⚠ LE SÉLECTEUR EST `body>.app`, JAMAIS `.app`. La classe `.app` est aussi
   celle des événements du calendrier (`.ev.app`) : un sélecteur `.app.reduit`
   attraperait un jour un créneau du planning. Le piège est déjà documenté dans
   CLAUDE.md — c'est exactement ici qu'on se fait avoir.

   ⚠ Le bouton vit dans la topbar, PAS dans la barre : posé dedans, il se
   déplacerait avec elle à chaque bascule. À gauche de la marque, il surplombe
   la colonne qu'il commande et ne bouge jamais.
   --------------------------------------------------------------------- */
.topbar .btn-menu{
  flex:0 0 auto; width:34px; height:34px; padding:0;
  display:flex; align-items:center; justify-content:center;
  background:none; border:1px solid transparent; border-radius:var(--r-s);
  color:var(--muted); font-size:17px; line-height:1; cursor:pointer;
}
.topbar .btn-menu:hover{ background:var(--soft2); color:var(--poppy-dark); }
.topbar .btn-menu:focus-visible{ outline:2px solid var(--poppy); outline-offset:2px; }

/* La transition porte sur la COLONNE de la grille : c'est elle qui fixe la
   largeur réelle. Là où elle n'est pas animable, la bascule est instantanée —
   le menu fonctionne quand même. */
body>.app{ transition:grid-template-columns .18s ease; }
body>.app.reduit{ grid-template-columns:var(--l-sidebar-reduit) 1fr; }

body>.app.reduit .sidebar{ padding-left:6px; padding-right:6px; overflow-x:hidden; }

/* L'icône seule, centrée. `white-space:nowrap` empêche le libellé de replier la
   ligne pendant les 180 ms de la transition. */
body>.app.reduit .sidebar a{
  justify-content:center; gap:0; padding-left:0; padding-right:0;
  white-space:nowrap; overflow:hidden;
}
body>.app.reduit .sidebar a .lib{ display:none; }

/* ⚠ LE TITRE DE GROUPE DEVIENT UN FILET, IL NE DISPARAÎT PAS. « PILOTAGE »,
   « ACTIVITÉ », « CONFORMITÉ & FINANCE » structurent vingt entrées : les
   effacer donnerait une colonne de vingt icônes indifférenciées. Le filet
   garde le regroupement sans le texte. */
body>.app.reduit .sidebar .groupe{
  font-size:0; height:1px; margin:12px 8px; padding:0;
  background:var(--line); overflow:hidden;
}
body>.app.reduit .sidebar .groupe:first-child{ margin-top:4px; }

/* Le compteur ne peut plus se ranger à droite d'un libellé absent : il devient
   une pastille sur l'icône. Sans ça, `margin-left:auto` le pousse hors du
   cadre et il disparaît — un compteur invisible vaut mieux mort que muet. */
body>.app.reduit .sidebar a{ position:relative; }
body>.app.reduit .sidebar a .compteur{
  position:absolute; top:2px; right:4px; margin:0;
  min-width:8px; padding:0 4px; font-size:9.5px; line-height:1.5;
}

main#app{ padding:18px 20px 40px; min-width:0; }

/* En-tête de page (titre + actions) */
.page-tete{
  display:flex; align-items:flex-end; gap:14px; flex-wrap:wrap; margin-bottom:16px;
}
.page-tete h1{ margin:0; }
.page-tete .sstitre{ color:var(--muted); font-size:12.5px; margin-top:2px; }
.page-tete .actions{ margin-left:auto; display:flex; gap:8px; flex-wrap:wrap; }

/* Fil d'Ariane / retour */
.retour{ font-size:12.5px; color:var(--muted); margin-bottom:6px; display:inline-block; }

@media (max-width:900px){
  body>.app{ grid-template-columns:1fr; }
  .sidebar{
    position:static; height:auto; border-right:none; border-bottom:1px solid var(--line);
    display:flex; flex-wrap:wrap; gap:4px; padding:8px;
  }
  .sidebar .groupe{ display:none; }
  .sidebar a{ margin:0; border-left:none; border-bottom:3px solid transparent; }
  .sidebar a.actif{ border-left:none; border-bottom-color:var(--poppy); }
  .topbar .recherche{ display:none; }
  main#app{ padding:14px 12px 32px; }
}

/* ---------------------------------------------------------------------
   4. ÉCRAN DE CONNEXION
   --------------------------------------------------------------------- */
body.connexion{
  display:flex; align-items:center; justify-content:center; padding:20px;
  background:
    radial-gradient(circle at 12% 88%, var(--soft) 0%, rgba(251,236,235,0) 55%),
    radial-gradient(circle at 88% 8%, #f6c9c4 0%, rgba(246,201,196,0) 52%),
    var(--fond);
}
.carte-connexion{
  width:100%; max-width:380px; background:var(--carte);
  border:1px solid var(--line); border-top:5px solid var(--poppy);
  border-radius:var(--r); box-shadow:var(--ombre-haut); padding:26px 24px 22px;
}
.carte-connexion .marque{ display:flex; align-items:center; gap:10px; margin-bottom:4px; }
.carte-connexion .marque img{ height:44px; width:auto; display:block; }
/* Sans display:block, le <small> reste en ligne et le nom de la structure se
   colle à la baseline (« CHAMPS CROISÉSOrganisme de formation »). */
.carte-connexion .marque .nom{ font-weight:800; letter-spacing:2px; font-size:14px; line-height:1.25; }
.carte-connexion .marque .nom small{
  display:block; letter-spacing:1.6px; font-weight:600; font-size:9px;
  color:var(--muted); text-transform:uppercase;
}
.carte-connexion .mark{ position:relative; width:46px; height:30px; flex:0 0 auto; }
.carte-connexion .mark span{ position:absolute; top:0; width:30px; height:30px; border-radius:50%; }
.carte-connexion .mark .r{ left:0; background:var(--poppy); }
.carte-connexion .mark .g{ left:15px; background:#9a9a9a; mix-blend-mode:multiply; }
.carte-connexion h1{ font-size:18px; margin:14px 0 2px; }
.carte-connexion .sstitre{ color:var(--muted); font-size:12.5px; margin-bottom:18px; }
.carte-connexion .btn{ width:100%; justify-content:center; margin-top:6px; }
.carte-connexion .pied{
  margin-top:16px; padding-top:12px; border-top:1px solid var(--line);
  font-size:11.5px; color:var(--muted); text-align:center;
}

/* Bandeau d'erreur de formulaire */
.alerte{
  border:1px solid #f0c8c2; background:var(--soft); border-left:5px solid var(--poppy);
  border-radius:var(--r-s); padding:9px 12px; margin:10px 0; font-size:13px; color:var(--poppy-dark);
}
.alerte.info{ border-color:#cfe0ef; background:var(--info-bg); border-left-color:var(--info); color:var(--info); }
.alerte.ok{ border-color:#bfe3d1; background:var(--ok-bg); border-left-color:var(--ok); color:var(--ok); }
/* ⚠ .att et .ko manquaient alors que SEPT vues les emploient (20 emplois).
   Le style de base .alerte etant celui d'une erreur, un avertissement et une
   information sortaient avec la meme urgence visuelle qu'une panne. */
.alerte.att{ border-color:#f0d9a8; background:var(--att-bg); border-left-color:var(--att); color:var(--att); }
.alerte.ko{ border-color:#f0c8c2; background:var(--ko-bg); border-left-color:var(--ko); color:var(--ko); }
.alerte[hidden]{ display:none; }

/* ---------------------------------------------------------------------
   5. PANNEAUX & GRILLES
   --------------------------------------------------------------------- */
.panel{
  background:var(--carte); border:1px solid var(--line); border-radius:var(--r);
  box-shadow:var(--ombre); margin-bottom:16px; overflow:hidden;
}
/*
 * ┌──────────────────────────────────────────────────────────────────────────┐
 * │ L'EN-TÊTE DE PANNEAU PERD SON APLAT — 24/09/2026, décision de Julien.    │
 * │                                                                          │
 * │ Suite du geste de la veille sur les en-têtes de TABLEAU, et pour la      │
 * │ même raison : 162 panneaux sur 32 vues portaient chacun une bande rosée. │
 * │ Un écran qui en aligne trois — et il y en a — comptait ses bandes avant  │
 * │ de lire son contenu, et l'aplat qui devait distinguer ne distinguait     │
 * │ plus rien à force d'être partout.                                        │
 * │                                                                          │
 * │ ⚠ CE QUI DISTINGUE L'EN-TÊTE RESTE : le titre en rouge sombre et le      │
 * │   filet du bas. Deux marques, et elles survivent à l'impression, où les  │
 * │   aplats ne sortent pas.                                                 │
 * │                                                                          │
 * │ ⚠⚠ ET DEUX APLATS SONT GARDÉS, PARCE QU'ILS DISENT QUELQUE CHOSE : le    │
 * │   `tfoot` d'un tableau (un total n'est pas une ligne) et le `footer` de  │
 * │   panneau, qui porte les commandes d'un formulaire. Les seuls qui        │
 * │   restent sont donc les seuls qui se remarquent.                         │
 * └──────────────────────────────────────────────────────────────────────────┘
 */
.panel>header{
  display:flex; align-items:center; gap:10px; flex-wrap:wrap;
  padding:11px 14px; border-bottom:1px solid var(--line); background:var(--carte);
}
.panel>header h2,
.panel>header h3{ font-size:14px; margin:0; color:var(--poppy-dark); }
.panel>header .actions{ margin-left:auto; display:flex; gap:8px; flex-wrap:wrap; }
.panel>.corps{ padding:14px; }
.panel>.corps.serre{ padding:0; }            /* pour un tableau pleine largeur */
.panel>footer{
  padding:10px 14px; border-top:1px solid var(--line); background:var(--soft2);
  display:flex; align-items:center; gap:10px; font-size:12.5px; color:var(--muted);
}

.grille{ display:grid; gap:16px; }

/* ── LA GRILLE DES INTERVENANTS (13/09, dev B sur l'écran du lot A) ────────
   ⚠⚠ DEUX DÉFAUTS DE GÉOMÉTRIE, ET LES DEUX SE MESURENT SANS NAVIGATEUR.

   1. `.panel` porte `margin-bottom:16px` — utile quand des panneaux s'empilent,
      FAUX dans une grille qui a déjà `gap:16px` : l'écart entre deux rangées
      valait 32 px au lieu de 16, et la dernière rangée traînait 16 px de vide.
   2. `.panel` n'est pas une colonne flex : étiré à la hauteur de sa rangée par
      la grille, son `<footer>` suivait le CONTENU au lieu de descendre au bas
      de la carte. Sur trois cartes de longueurs inégales, le bouton « Ouvrir la
      fiche » se retrouvait à trois hauteurs différentes, avec du blanc en
      dessous. **Un élément qui revient sur chaque carte doit se trouver au même
      endroit sur chacune** — sinon l'œil le cherche à chaque fois.

   ⚠⚠ ET RIEN N'EST TOUCHÉ SUR `.panel`. C'est une classe du socle, employée par
      une vingtaine d'écrans : y retirer la marge réparerait ici et déréglerait
      tous les endroits où des panneaux s'empilent hors grille. Les deux règles
      sont bornées à `.frm-cartes`.

   ⚠ La carte entière est cliquable (l'écouteur existe depuis l'origine) et
     RIEN ne le disait : ni curseur, ni survol. Une zone qui répond au clic sans
     le montrer, c'est une fonction que personne ne trouve. Le bouton reste le
     chemin du clavier — un `<div>` ne se prend pas à la tabulation. */
.frm-cartes > .panel{ margin-bottom:0; display:flex; flex-direction:column; }
.frm-cartes > .panel > .corps{ flex:1 1 auto; }
.frm-cartes > .panel[data-formateur]{ cursor:pointer; }
.frm-cartes > .panel[data-formateur]:hover{ border-color:var(--poppy); }

/* ── L'ANNUAIRE : LA LIGNE D'ÉTAT ET SES SIGNAUX  (13/09) ──────────────────
   ⚠⚠ QUATRE TUILES SONT DEVENUES UNE LIGNE. Deux d'entre elles comptaient ce
      que la grille montre ; les deux autres appelaient une action sans y mener.
      Ce bloc est l'équivalent, en une bande, de ce qui en occupait cinq.
   ⚠ Additif et préfixé `frm-` : aucune classe du socle n'est touchée. */
.frm-etat{
  display:flex; flex-wrap:wrap; align-items:center; gap:6px 10px;
  margin:0 0 12px; padding:8px 12px; font-size:13px;
  background:var(--carte); border:1px solid var(--line); border-radius:var(--r-s);
}
/* ⚠⚠ UN FILTRE ACTIF SE VOIT. Sans marque, on oublie qu'on a restreint, la
   liste paraît vide et l'on cherche une demi-heure — le défaut classique d'un
   écran filtré, payé sur la GED, sur les quiz et sur la frise. */
.frm-etat.filtre{ border-left:3px solid var(--poppy); }
.frm-etat-n{ font-weight:700; }
/* ⚠ Un séparateur qui n'est pas un mot : entre trois natures d'information, un
     « · » se lit comme de la ponctuation de phrase et les recolle. */
.frm-etat-sep{
  width:1px; height:14px; background:var(--line); display:inline-block;
}
/* ⚠ La remise à zéro va au bout de la ligne : seule position dont l'abscisse ne
     dépend pas du nombre de signaux affichés. */
.frm-etat-fin{ margin-left:auto; }

/* ⚠⚠ UN SIGNAL EST UN BOUTON, PARCE QU'IL MÈNE. Il porte le ton « attention »
   du socle — un arbitrage à rendre, jamais une panne — et il dit s'il est
   enfoncé, à l'œil comme au lecteur d'écran (`aria-pressed`). */
.frm-signal{
  display:inline-flex; align-items:center; gap:5px;
  padding:2px 9px; border-radius:999px; cursor:pointer; font-size:12.5px;
  color:var(--att); background:transparent; border:1px solid var(--att);
}
.frm-signal:hover{ background:var(--soft2); }
.frm-signal[aria-pressed="true"]{ background:var(--att); color:#fff; font-weight:600; }

/* ⚠ Le nombre de thématiques écartées est une INFORMATION, pas un filtre : il
     ne se clique pas, et il ne doit pas ressembler à une chip. */
.frm-ecartees{ align-self:center; margin-left:4px; }

/* ⚠⚠ LE CONTACT EST UN LIEN, ET IL DOIT LE MONTRER. La carte entière est
   cliquable : sans souligné ni couleur, rien ne distingue « appeler » de
   « ouvrir la fiche », et l'on découvre la différence en composant un numéro
   qu'on ne voulait pas. */
.frm-contact a{ color:var(--poppy-dark); text-decoration:underline; }
.frm-contact a:hover{ color:var(--poppy); }

/* ⚠⚠ ON N'EFFACE PLUS LA LISTE POUR DIRE QU'ELLE CHARGE. Elle était remplacée
   par un écran de chargement avant même l'appel : sur une recherche de cinq
   lettres, la grille clignotait cinq fois. On l'estompe, on la laisse lisible,
   et l'on empêche le clic pendant l'aller-retour.
   ⚠ `transition` sur l'opacité seule : un chargement de 40 ms ne doit pas se
     voir du tout. */
.frm-attente{ opacity:.55; pointer-events:none; transition:opacity .15s ease; }

.grille-2{ grid-template-columns:repeat(2,minmax(0,1fr)); }
.grille-3{ grid-template-columns:repeat(3,minmax(0,1fr)); }
.grille-4{ grid-template-columns:repeat(4,minmax(0,1fr)); }
.grille-2-1{ grid-template-columns:2fr 1fr; }
@media (max-width:1100px){
  .grille-4{ grid-template-columns:repeat(2,minmax(0,1fr)); }
  .grille-3,.grille-2-1{ grid-template-columns:1fr; }
}
@media (max-width:700px){
  .grille-2,.grille-4{ grid-template-columns:1fr; }
}

/* Un élément qui occupe 2 (ou 3) colonnes de la grille parente */
.col-2{ grid-column:span 2; }
.col-3{ grid-column:span 3; }

/* Liste clé/valeur (fiches) */
.kv{ display:grid; grid-template-columns:auto 1fr; gap:6px 14px; font-size:13px; }
.kv dt{ color:var(--muted); font-weight:600; }
.kv dd{ margin:0; }

/* ---------------------------------------------------------------------
   6. KPI
   --------------------------------------------------------------------- */
.kpis{ display:grid; grid-template-columns:repeat(auto-fit,minmax(170px,1fr)); gap:12px; margin-bottom:16px; }
.kpi{
  background:var(--carte); border:1px solid var(--line);
  border-radius:var(--r); box-shadow:var(--ombre); padding:13px 14px;
  display:block; color:inherit; text-decoration:none;
}
a.kpi:hover{ text-decoration:none; border-color:var(--poppy-clair); box-shadow:var(--ombre-haut); }
/*
 * ┌──────────────────────────────────────────────────────────────────────────┐
 * │ LA TUILE DE MESURE — généralisée le 24/09/2026, décision de Julien.      │
 * │                                                                          │
 * │ Elle a vécu une journée en modificateur (`.kpi.kpi-mesure`, borné aux    │
 * │ Statistiques) le temps qu'on la regarde. Elle est désormais LA tuile des │
 * │ 17 écrans : deux dialectes visuels dans la même application sont pires   │
 * │ que l'un ou l'autre.                                                     │
 * │                                                                          │
 * │ ⚠⚠ `order:-1` SUR LE LIBELLÉ, ET SURTOUT PAS TROIS `order` EN SUITE.     │
 * │   Mesuré avant d'écrire : sur les 55 tuiles du dépôt, DIX rendent déjà   │
 * │   le libellé AVANT la mesure — toutes dans `sessions.js`, au lot A.      │
 * │   Numéroter les trois enfants (l:1, n:2, sous:3) aurait INVERSÉ ces      │
 * │   dix-là, remettant le libellé sous le nombre exactement là où il était  │
 * │   déjà bien placé. Une seule règle sur `.l` donne le même rendu quel que │
 * │   soit l'ordre du marquage, et c'est la seule forme qui se généralise.   │
 * │                                                                          │
 * │ ⚠ L'écart entre l'ordre du document et l'ordre visuel ne concerne que    │
 * │   les tuiles qui écrivent la mesure en premier, et il est sans effet sur │
 * │   le parcours clavier : une tuile ne porte qu'un élément focusable.      │
 * └──────────────────────────────────────────────────────────────────────────┘
 */
.kpi{ display:flex; flex-direction:column; }
.kpi .l{
  order:-1;
  font-size:11px; color:var(--muted); margin:0 0 6px;
  text-transform:uppercase; letter-spacing:.5px; font-weight:600;
}
/*
 * ⚠⚠ LA MESURE EST EN ENCRE, ET LE TON RESTE SUR LE LISERÉ.
 *
 *   Le nombre portait la couleur du ton : vert quand tout va bien, orange
 *   sinon. Un nombre VERT ne signale aucune urgence — c'est de la décoration,
 *   et la règle du 16/08 dit qu'une couleur encode l'urgence, jamais le type.
 *   Résultat : cinq tuiles côte à côte affichaient cinq couleurs, et l'œil
 *   n'avait plus d'endroit où se poser.
 *
 * ⚠ LE SIGNAL N'EST PAS PERDU, IL EST DÉPLACÉ : le liseré de 4 px garde le
 *   ton, sur toutes les variantes. Ce qui alerte alerte encore ; ce qui mesure
 *   se lit enfin comme une mesure.
 */
.kpi .n{ font-size:38px; font-weight:800; color:var(--ink); line-height:1; }
.kpi .sous{ font-size:11.5px; color:var(--muted); margin-top:7px; }

/* ⚠ Sous 700 px les tuiles s'empilent : 38 px sur cinq d'entre elles feraient
   défiler un écran entier avant le premier contenu. La mesure reste lisible. */
@media (max-width:700px){
  .kpi .n{ font-size:30px; }
}

/* ⚠⚠ LE TON A QUITTÉ LE LISERÉ POUR L'ICÔNE (26/09) — voir la section 22.
   Ces quatre règles ne posent plus qu'une COULEUR de bordure haute, sans
   épaisseur : elles peignent donc le filet de 1 px du contour, ce qui est
   l'état voulu (invisible). Elles sont gardées parce que `suivi.js` écrit
   `.suivi-kpis .kpi.info{ border-top-color: … }` sur le même modèle : les
   retirer laisserait une règle orpheline dans un fichier du lot A. */
/* ⚠⚠ 26/09/2026, après-midi : le liseré garde le ton des tuiles SANS icône (15 écrans).
   859d5aa l'avait neutralisé pour toutes les tuiles alors que seules celles de l'accueil
   portent une icône : une tuile « En retard » du Suivi ne signalait plus rien (captures de
   Jérôme). La neutralisation ne vaut plus que pour `.kpi-compacte` (section 22). */
.kpi.navy{ border-top-color:var(--navy); }
.kpi.ok{   border-top-color:var(--ok); }
.kpi.att{  border-top-color:var(--att); }
.kpi.neutre{ border-top-color:var(--muted); }

/*
 * UNE TUILE QUI FILTRE DOIT LE DIRE — ajouté le 09/09 (zone commune, signalé
 * au point de coordination).
 *
 * ⚠⚠ `.cliquable` NE SERVAIT À RIEN ICI. Elle n'existait que pour
 *    `table.tbl tbody tr.cliquable` (l. 609) et `.pm-ligne.cliquable`
 *    (l. 2059) : sur une tuile `.kpi`, la classe était posée dans le HTML et
 *    AUCUNE règle ne la lisait. La carte « Sans intervenant » filtre la liste
 *    depuis le 08/09 sans un curseur ni un survol, et les deux cartes d'écart
 *    du 09/09 en avaient hérité — alors que toute la livraison des alertes
 *    repose sur « amener le signal jusqu'à elle » par ces tuiles-là.
 *
 * ⚠ Le survol reprend `a.kpi:hover` mot pour mot (l. 437) plutôt que d'inventer
 *   un second effet : une tuile-lien et une tuile-bouton qui ne réagiraient pas
 *   pareil se liraient comme deux objets différents.
 * ⚠ `:focus-visible` et non `:focus` : le cadre ne doit apparaître qu'au
 *   clavier, sinon il reste collé après chaque clic à la souris. C'est la règle
 *   déjà tenue par `.btn` (l. 465) et `.bascule` (l. 2002).
 */
.kpi.cliquable{ cursor:pointer; }
.kpi.cliquable:hover{ border-color:var(--poppy-clair); box-shadow:var(--ombre-haut); }
.kpi.cliquable:focus-visible{ outline:2px solid var(--poppy); outline-offset:2px; }

/* ---------------------------------------------------------------------
   7. BOUTONS
   --------------------------------------------------------------------- */
.btn{
  display:inline-flex; align-items:center; gap:7px;
  padding:7px 14px; border-radius:var(--r-s);
  /* ⚠ Un bouton secondaire n'a QUE son contour pour se signaler comme bouton :
     fond blanc, texte sombre, pas d'aplat. À 1,30:1, il ne se distingue pas du
     texte qui l'entoure. */
  border:1px solid var(--line-champ); background:var(--carte); color:var(--ink);
  font:inherit; font-size:13px; font-weight:600; line-height:1.35;
  cursor:pointer; text-decoration:none; white-space:nowrap;
}
.btn:hover{ background:var(--soft2); border-color:var(--poppy-clair); text-decoration:none; }
.btn:active{ transform:translateY(1px); }
.btn:focus-visible{ outline:2px solid var(--poppy); outline-offset:2px; }
.btn[disabled],.btn.desactive{ opacity:.5; cursor:not-allowed; pointer-events:none; }

.btn.primaire{ background:var(--poppy); border-color:var(--poppy); color:#fff; }
.btn.primaire:hover{ background:var(--poppy-dark); border-color:var(--poppy-dark); }
.btn.danger{ background:#fff; border-color:var(--poppy); color:var(--poppy); }
.btn.danger:hover{ background:var(--ko-bg); }
.btn.fantome{ background:transparent; border-color:transparent; color:var(--muted); }
.btn.fantome:hover{ background:var(--soft2); color:var(--ink); }
.btn.petit{ padding:4px 9px; font-size:12px; }
.btn.icone{ padding:5px 8px; font-size:14px; }
/* ⚠ Le « ⋯ » d'une cellule d'actions n'est PAS un bouton de plus : c'est le
   repli de ceux qui ne tiennent pas. Bordé, il pesait autant que l'action
   nommée d'à côté et doublait le poids de la colonne (maquette du 26/09). */
.actions-ligne .btn.icone.discret{
  border-color:transparent; background:transparent; color:var(--muted);
  box-shadow:none;
}
.actions-ligne .btn.icone.discret:hover{ background:var(--soft2); color:var(--ink); }

.actions-ligne{ display:flex; gap:5px; justify-content:flex-end; white-space:nowrap; }

/* ---------------------------------------------------------------------
   8. BADGES .b / PASTILLES .chip / ONGLETS .tabs
   --------------------------------------------------------------------- */
.b{
  display:inline-block; padding:2px 9px; border-radius:11px;
  font-size:11.5px; font-weight:700; line-height:1.6; white-space:nowrap;
  background:var(--neutre-bg); color:var(--neutre);
}
.b-ok{ background:var(--ok-bg); color:var(--ok); }
.b-att{ background:var(--att-bg); color:var(--att); }
.b-ko{ background:var(--ko-bg); color:var(--ko); }
.b-info{ background:var(--info-bg); color:var(--info); }
.b-neutre{ background:var(--neutre-bg); color:var(--neutre); }
.b-poppy{ background:var(--poppy); color:#fff; }
/* ⚠⚠ LE JETON DE VARIABLE — dev B, 21/08. Il employait `.b-poppy`, c'est-à-dire
   du COQUELICOT PLEIN sur blanc. Mesuré : 31 jetons sur le contrat de vacation,
   39 sur la convention. L'écran d'édition devenait un mur rouge, et le texte du
   modèle — ce que le client vient lire — passait au second plan.
   C'est la leçon 4 du 16/08 mot pour mot : « une couleur encode l'urgence,
   jamais le type ». Ici le rouge disait « ceci est une variable », donc le type.
   Le jeton rejoint le traitement de TOUTES ses sœurs de la famille `.b` : fond
   pâle, texte foncé. Il reste parfaitement reconnaissable — un aplat coloré,
   arrondi, dans un texte noir sur blanc — sans écraser le document.
   ⚠ La bordure n'est pas décorative : elle garde au jeton une limite nette
   quand plusieurs se suivent, ce que le fond pâle seul ne fait plus. */
.b-jeton{ background:var(--soft); color:var(--poppy-dark);
          border:1px solid var(--poppy-clair); font-weight:600; }
/* Point de couleur (légende de graphique, état de synchro) */
.b.point{ padding:0; width:10px; height:10px; border-radius:50%; }

.chip{
  display:inline-flex; align-items:center; gap:6px;
  padding:4px 11px; border-radius:14px; cursor:pointer;
  /* Une chip de filtre est une commande : même seuil. */
  border:1px solid var(--line-champ); background:var(--carte);
  font-size:12.5px; font-weight:600; color:var(--muted);
  font-family:inherit;
}
.chip:hover{ border-color:var(--poppy-clair); color:var(--ink); }
.chip.actif{ background:var(--soft); border-color:var(--poppy); color:var(--poppy-dark); }
.chip .n{ font-size:11px; opacity:.8; }

.tabs{ display:flex; gap:2px; border-bottom:1px solid var(--line); margin-bottom:14px; flex-wrap:wrap; }
.tabs button{
  border:none; background:none; font:inherit; font-size:13.5px; font-weight:600;
  color:var(--muted); padding:8px 14px; cursor:pointer;
  border-bottom:3px solid transparent; margin-bottom:-1px;
}
.tabs button:hover{ color:var(--ink); }
.tabs button.actif{ color:var(--poppy-dark); border-bottom-color:var(--poppy); }

/* Pastille d'alerte sur un onglet : elle signale qu'un panneau REPLIE demande
   une action. Sans elle, ranger un ecran en onglets revient a masquer ses
   alertes. La couleur seule ne suffit pas (daltonisme) : le titre de survol
   porte l'information, et l'onglet concerne s'ouvre d'office. */
.tabs button .pastille-alerte{
  color:var(--att); font-size:18px; line-height:0; vertical-align:middle;
  margin-left:5px;
}

/* ---------------------------------------------------------------------
   9. FORMULAIRES
   --------------------------------------------------------------------- */
.field{ margin-bottom:12px; min-width:0; }
.field>label{
  display:block; font-size:11.5px; font-weight:700; color:var(--muted);
  text-transform:uppercase; letter-spacing:.5px; margin-bottom:4px;
}
.field>label .obl{ color:var(--poppy); }
.field .aide{ font-size:11.5px; color:var(--muted); margin-top:3px; }
/* ⚠⚠ UNE AIDE QUI DEVIENT UN REFUS CHANGE DE TON. `.ko` n'existait que sous
   `.alerte.ko` : la poser sur une aide passait l'audit — la classe EXISTE dans
   la feuille — sans rien peindre du tout. Un message d'erreur au gris de
   l'aide se lit comme un conseil, et on continue de taper. C'est le piège de
   la classe inventée, sous sa forme la plus discrète : la classe est bien là,
   mais pas pour cet élément-là. */
.field .aide.ko{ color:var(--ko); font-weight:600; }

.input{
  width:100%; padding:7px 10px; font:inherit; font-size:13.5px; color:var(--ink);
  /* ⚠ `--line-champ` et non `--line` : c'est ce contour qui dit OÙ TAPER. À
     1,30:1 il était sous le seuil RGAA — et invisible sur un téléphone en
     plein jour, ce qui est exactement la situation d'un stagiaire qui remplit
     la page d'émargement. */
  background:var(--carte); border:1px solid var(--line-champ); border-radius:var(--r-s);
  outline:none;
}
.input:focus{ border-color:var(--poppy); box-shadow:0 0 0 3px rgba(200,16,46,.12); }
.input[disabled],.input[readonly]{ background:var(--soft2); color:var(--muted); }
.input.erreur{ border-color:var(--poppy); background:var(--ko-bg); }
select.input{ appearance:auto; height:33px; }
/* ⚠⚠ Une liste à choix MULTIPLE ne tient pas dans la hauteur d'une liste
   ordinaire. Le `height:33px` ci-dessus écrasait l'attribut `size="8"` de
   la fenêtre d'émission : les sessions à cocher défilaient une par une dans
   une fente d'une ligne, et la facture groupée — livrée, annoncée au client
   — était en pratique inatteignable. Livrer une fonction derrière un
   contrôle inutilisable revient à ne pas l'avoir livrée. */
select.input[multiple]{ height:auto; min-height:132px; padding:4px; }
textarea.input{ min-height:80px; resize:vertical; }
.input.auto{ width:auto; min-width:180px; }

.case{ display:flex; align-items:center; gap:8px; font-size:13.5px; margin-bottom:8px; }

/* ---------------------------------------------------------------------
   UNE LIGNE DE RÉPONSE DANS L'ÉDITEUR DE QUIZ  .pq-*

   ⚠⚠ AJOUT PUREMENT ADDITIF, ANNONCÉ À L'AUTRE DÉVELOPPEUR. Préfixé `pq-`,
   aucune règle existante modifiée.

   POURQUOI ELLE EXISTE, ET CE QU'ELLE REMPLACE. Ces lignes employaient
   `.actions-ligne` — la classe des BOUTONS D'UNE CELLULE DE TABLEAU, qui
   aligne à droite. C'est le contresens du 03/09 sur la barre d'outils du
   planning, refait sur un formulaire : une ligne de saisie n'est pas une
   cellule d'actions.

   ⚠⚠ ET `min-width:0` N'EST PAS UN DÉTAIL — c'est LUI qui corrige le défaut
   visible. Un élément de flexbox a `min-width:auto` par défaut : il refuse
   de descendre sous la largeur de son contenu. Un `.input` en `width:100%`
   dans une ligne flex ne se comprime donc PAS, il DÉBORDE — et recouvre son
   voisin de gauche, dont la case à cocher devient inclicable. C'est
   exactement ce que Julien a signalé le 10/09 : « les boutons ne marchent
   pas ». Ils marchaient ; ils étaient sous le champ de texte.
   --------------------------------------------------------------------- */
.pq-reponse{ display:flex; align-items:center; gap:8px; margin-bottom:6px; }
/* ⚠⚠ UNE LARGEUR FIXE, ET LA MÊME QUE L'EN-TÊTE `.pq-col-juste`. Sans elle,
   la colonne de gauche fait la largeur du mot « juste » — qui dépend de la
   police du système —, et l'en-tête « Bonne réponse » se retrouve au-dessus du
   champ de texte : il désigne alors la MAUVAISE colonne. Un en-tête qui montre
   à côté est pire que pas d'en-tête. Les deux valeurs sont liées, et c'est
   écrit des deux côtés. */
.pq-reponse .case{ flex:0 0 auto; min-width:92px; margin-bottom:0; white-space:nowrap; }
/* ⚠⚠ `flex:1 1 0` ET PAS `1 1 auto` — sinon la ligne se casse en TROIS. La
   base `auto` résout sur la largeur déclarée, et `.input` porte `width:100%` :
   dans un conteneur qui ENROULE (`flex-wrap:wrap`, posé le 11/09 pour le volet
   d'une réponse), l'input réclame donc la ligne entière, « juste » se retrouve
   au-dessus et les boutons en dessous. Tant que le conteneur n'enroulait pas,
   le même excès était simplement absorbé — le défaut était là, invisible.
   ⚠ C'est la troisième fois que `.pq-reponse` paie une règle de flexbox :
     `min-width:auto` le 10/09, `flex-wrap` manquant le 11/09 au soir, et
     `flex-basis:auto` ici. **Une largeur déclarée sur un enfant de flex n'est
     pas une préférence, c'est une réservation.** */
.pq-reponse .input{ flex:1 1 0; min-width:0; }

/* ═══════════════════════════════════════════════════════════════════════════
   LE RETOUR D'UNE RÉPONSE (migration 2055) — dev B, 11/09.

   ⚠⚠ IL PREND SA PROPRE LIGNE, ET C'EST LA SEULE FAÇON QUE ÇA TIENNE. La ligne
   d'une réponse porte déjà quatre éléments (« juste », le libellé, 💬, ✕) ;
   un cinquième champ de texte à côté du libellé les aurait réduits tous les
   deux à une trentaine de pixels sur un écran de portable. `flex-basis:100%`
   avec `flex-wrap` le renvoie sous la ligne, à la largeur entière.

   ⚠⚠ ET `flex-wrap:wrap` DOIT ÊTRE POSÉ SUR `.pq-reponse`, sinon le champ ne
   passe pas à la ligne : il RESTE sur la première et l'écrase. C'est le même
   piège que `min-width:auto` — un élément qui ne peut pas aller où il faut ne
   disparaît pas, il recouvre son voisin. C'est ce qui a produit « les boutons
   ne marchent pas » le 10/09.

   ⚠ Le décalage à gauche l'aligne sous le LIBELLÉ, pas sous la case : il
     commente la réponse, il ne la coche pas. */
.pq-reponse{ flex-wrap:wrap; }
/* ═══════════════════════════════════════════════════════════════════════════
   LE VOLET D'UNE RÉPONSE — son retour (2055) et son image (2056).

   ⚠⚠ UN SEUL VOLET, ET IL PREND LA LIGNE ENTIÈRE. La ligne d'une réponse porte
   déjà « juste », le libellé, ⋯ et ✕ ; y loger un champ de retour ET une zone
   de dépôt les aurait réduits à une trentaine de pixels sur un portable.
   `flex-basis:100%` avec `flex-wrap` renvoie le volet sous la ligne.

   ⚠⚠ ET `flex-wrap:wrap` DOIT ÊTRE POSÉ SUR `.pq-reponse`, sinon le volet ne
   passe pas à la ligne : il RESTE sur la première et l'écrase. Même piège que
   `min-width:auto` — un élément qui ne peut pas aller où il faut ne disparaît
   pas, il recouvre son voisin. C'est ce qui a produit « les boutons ne
   marchent pas » le 10/09.

   ⚠ Le décalage l'aligne sous le LIBELLÉ, pas sous la case : il commente la
     réponse, il ne la coche pas. */
.pq-rep-plus{
  flex:1 1 100%; min-width:0; margin:4px 0 8px 100px;
  padding:8px 10px; background:var(--soft2);
  border-left:2px solid var(--line); border-radius:0 var(--r-s) var(--r-s) 0;
}
.pq-rep-plus .pq-retour{ width:100%; font-size:12.5px; margin-bottom:6px; }
/* ⚠ La zone de dépôt d'une réponse est plus basse que celle d'une question :
   c'est un complément, pas le sujet de l'écran. */
.pq-rep-plus .pq-depot{ padding:10px; font-size:12px; }
.pq-rep-plus .pq-vignette{ max-height:90px; }

@media (max-width: 640px) {
  /* ⚠ Sous 640 px, la case « juste » passe elle-même à la ligne : le décalage
     n'aurait plus rien sous quoi s'aligner. */
  .pq-rep-plus{ margin-left:0; }
}
/* Le bouton qui fait paraître le champ.
   ⚠ Largeur fixe : sans elle, la ligne se décale d'un pixel selon que l'emoji
     est rendu par la police du système ou par celle du navigateur. */
.pq-bulle{ flex:0 0 auto; min-width:30px; padding-left:4px; padding-right:4px; }


/* Le rang d'une étape, et la flèche d'une association : ni l'un ni l'autre ne
   se comprime — ce sont des repères, pas du contenu. */
.pq-rang, .pq-fleche{ flex:0 0 auto; color:var(--muted); font-size:12px; }
.pq-rang{ min-width:2.2em; text-align:right; }

/* La barre d'une question dans l'éditeur : son rang à gauche, ses gestes à
   droite. ⚠ Le rang est un `<strong>` et non un `<label>` : il ne désigne
   aucun champ, et un label sans cible est annoncé pour rien. */
.pq-tete{
  display:flex; align-items:center; gap:8px; margin-bottom:10px;
}
/* ⚠ Le rang pousse les gestes à droite : `justify-content:space-between` ne
   suffisait plus dès qu'un troisième élément — le chevron — est entré dans la
   barre, il aurait réparti les trois au lieu d'en coller deux à gauche. */
.pq-tete > [data-rang]{ flex:1 1 auto; }
/* Le chevron, qui déplie ET replie. ⚠ Une largeur fixe : sans elle, la barre
   se décale d'un pixel entre « ▸ » et « ▾ », et l'œil le voit. */
.pq-chevron{ flex:0 0 auto; min-width:28px; }
/* ⚠ Le bouton d'ajout est SOUS la dernière question, pas au-dessus : on écrit
   de haut en bas, et le geste suivant doit être là où l'œil arrive. */
.actions-bas{ display:flex; justify-content:center; margin:14px 0 18px; }

/* L'image déjà déposée sur une question, dans l'éditeur.
   ⚠ Une VIGNETTE, pas l'image entière : l'auteur vérifie que c'est la bonne,
   il ne la relit pas. Une photo de téléphone en pleine taille pousserait les
   champs de réponse hors de l'écran. */
.pq-media{
  display:flex; align-items:center; gap:10px;
  margin:0 0 8px; padding:6px 8px;
  border:1px solid var(--line); border-radius:6px;
}
.pq-media figcaption{
  flex:1 1 auto; min-width:0; font-size:13px; color:var(--muted);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
/* ⚠⚠ `contain` ET PAS `cover` : une vignette ROGNÉE ne dit pas ce qu'on a
   déposé, elle dit qu'il y a quelque chose. Sur une photo de situation — deux
   personnes, un geste — le cadrage carré coupait précisément ce qu'on voulait
   reconnaître. Le fond neutre remplace ce que `contain` laisse vide. */
.pq-vignette{
  flex:0 0 auto; width:96px; height:72px; object-fit:contain;
  background:var(--soft2); border-radius:4px;
}

/* ---------------------------------------------------------------------
   LA ZONE DE DÉPÔT D'UNE IMAGE

   ⚠⚠ L'`<input type="file">` EST DEDANS, ET IL N'EST PAS `display:none`.
   Le masquer ainsi le retire du parcours du clavier : la zone deviendrait
   inatteignable autrement qu'à la souris. Il est rendu transparent et étendu
   sur toute la zone — il reste focusable, et `:focus-within` fait paraître le
   cadre pour qu'on voie où l'on est.

   ⚠ Le glisser-déposer est accepté ici parce qu'il a le CLIC comme alternative
   exacte. Ce que ce projet refuse ailleurs, c'est le glisser-déposer comme
   SEUL moyen d'ordonner — il n'y avait alors aucune autre voie.
   --------------------------------------------------------------------- */
.pq-depot{
  position:relative; display:flex; flex-direction:column; align-items:center;
  gap:2px; padding:16px 12px; text-align:center; cursor:pointer;
  border:1px dashed var(--line-champ); border-radius:8px;
  background:var(--carte); color:var(--muted);
}
.pq-depot:hover,
.pq-depot:focus-within,
.pq-depot.pq-survol{ border-color:var(--poppy); background:var(--soft); }
.pq-depot:focus-within{ box-shadow:0 0 0 3px rgba(200,16,46,.12); }
.pq-depot-t{ font-size:13.5px; color:var(--ink); }
.pq-depot input[type="file"]{
  position:absolute; inset:0; width:100%; height:100%;
  opacity:0; cursor:pointer;
}

/* La ligne « adresse + Voir » : le lien ne se comprime pas. */
.pq-video-ligne{ display:flex; align-items:center; gap:8px; }
.pq-video-ligne .input{ flex:1 1 auto; min-width:0; }
.pq-video-ligne > a{ flex:0 0 auto; }

/* ---------------------------------------------------------------------
   L'ÉDITEUR DE MODÈLE DE DOCUMENT  .mod-*

   ⚠⚠ AJOUT PUREMENT ADDITIF, ANNONCÉ À L'AUTRE DÉVELOPPEUR. Toutes les
   classes sont préfixées `mod-` ; aucune règle existante n'est modifiée.
   --------------------------------------------------------------------- */

/* ⚠⚠ LA BARRE D'ACTIONS COLLE. Les gabarits de ce projet font deux à quatre
   cents lignes : au bout de deux écrans, « Publier » et « Brouillon » étaient
   sortis de la page, et l'indicateur de mode (Rédaction / Source) avec eux. On
   enregistre alors moins souvent, donc on perd davantage.

   ⚠ `z-index:3` : au-dessus du texte, SOUS les fenêtres de l'application et
   sous le menu « / » de l'éditeur, qui montent bien plus haut.
   ⚠ Fond OPAQUE : une barre translucide laisse défiler le gabarit dessous, et
   l'on ne lit plus ni l'un ni l'autre. */
.mod-barre{
  position:sticky; top:0; z-index:3;
  background:var(--carte); margin-bottom:14px;
}
/* Les états du modèle, à côté de son code. ⚠ Ils ne se comprimant pas : ce
   sont deux ou trois mots, et les voir se replier sur deux lignes ferait
   grandir la barre à chaque ouverture d'un modèle modifié. */
.mod-etats{ flex:0 0 auto; display:flex; gap:4px; white-space:nowrap; }

/* La colonne de droite : comparaison, historique, données.
   ⚠ `top:64px` et non 12 : la barre d'actions colle au-dessus, et une colonne
   qui s'arrêterait douze pixels sous le haut de la fenêtre passerait dessous.
   Les deux valeurs se tiennent. */
.mod-cote{ position:sticky; top:64px; align-self:start; }

/* ⚠⚠ LA GRILLE DE L'ÉDITEUR NE S'ÉLARGIT PLUS SUR SON CONTENU (répétition du
   29/09). `.grille-2-1` vaut `2fr 1fr` : deux colonnes qui ne descendent jamais
   sous la largeur de leur contenu insécable. Dans l'éditeur, les libellés des
   données (des boutons, donc sur une ligne) et le tableau de l'historique
   portaient la colonne de droite à 1 211 px : la page mesurait 1 939 px à
   1 366 — même en mode Source — et « Remettre en vigueur » sortait de
   l'écran. Trois règles ici, chacune éprouvée par SON sabotage
   (tools/tests_rab_documents_modeles_c.php) :
   · `minmax(0, …)` borne les deux colonnes, comme `.grille-2`, `.grille-3`
     et `.grille-4` le font déjà : c'est elle qui tient la page quand la
     COMPARAISON s'ouvre dans la même colonne (des lignes de gabarit de
     plusieurs centaines de signes, insécables) ;
   · les données passent à la ligne : sans elle, la liste défile de côté
     dans son panneau ;
   · le tableau resserré (`.mod-histo`, et trois colonnes dans modeles.js) :
     sans lui, « Remettre en vigueur » sort du panneau.
   ⚠ Propre à l'éditeur (`mod-grille`, posée par modeles.js) : `.grille-2-1`
     sert ailleurs et n'y change pas. Et la règle du `@media` garde la colonne
     unique sous 1 100 px — sans elle, la première, plus précise, l'aurait
     emporté sur celle du socle. */
.grille-2-1.mod-grille{ grid-template-columns:minmax(0,2fr) minmax(0,1fr); }
@media (max-width:1100px){
  .grille-2-1.mod-grille{ grid-template-columns:minmax(0,1fr); }
}
/* Les données à insérer passent à la ligne au lieu d'élargir la colonne. */
.mod-cote #mod-familles .btn{ white-space:normal; text-align:left; }
/* Le tableau de l'historique, resserré pour un tiers d'écran : cellules moins
   larges, et les deux boutons d'une version s'empilent plutôt que de pousser
   le tableau hors du panneau. */
.mod-histo table.tbl th,
.mod-histo table.tbl td{ padding-left:6px; padding-right:6px; }
.mod-histo .actions-ligne{ flex-wrap:wrap; }

/* ---------------------------------------------------------------------
   L'ÉCRAN D'ÉDITION D'UN QUIZ

   ⚠⚠ LA BARRE D'ACTIONS COLLE EN HAUT, et c'est le premier défaut d'un
   éditeur long : au-delà de trois ou quatre questions, « Enregistrer » sort
   de l'écran, et il faut remonter tout le quiz pour le retrouver. On finit
   par enregistrer moins souvent — donc par perdre davantage.

   ⚠ `z-index:3` : au-dessus des blocs de question, sous les fenêtres de
   l'application (l'aperçu, les confirmations), qui montent bien plus haut.
   ⚠ Le fond est OPAQUE : une barre translucide laisse défiler le texte des
   questions dessous, et l'on ne sait plus lire ni l'un ni l'autre.
   --------------------------------------------------------------------- */
/* ═══════════════════════════════════════════════════════════════════════════
   LA BARRE D'OUTILS PREND TOUTE LA LARGEUR (12/09).

   ⚠⚠ Elle était bornée avec le reste de l'écran, donc « Enregistrer » se
      retrouvait au milieu de la page et non au bord. Une barre d'outils n'est
      pas du contenu : elle borde le document, elle ne s'aligne pas dessus.
      C'est la borne de LECTURE qui doit être bornée — et elle seule.

   ⚠⚠ ET ELLE PASSAIT SOUS L'EN-TÊTE FIXE. `top:0` colle au HAUT DE LA FENÊTRE,
      or `.topbar` y est déjà, fixée, sur 58 px et en `z-index:40` : la barre
      s'y engouffrait et disparaissait dès qu'on faisait défiler. Le défaut
      était là depuis le 10/09 ; il ne se voyait pas tant que la barre était
      étroite et pâle, et il aurait crevé les yeux une fois élargie.
      ⚠ `var(--h-topbar)` et non « 58px » : le jeton existe, et deux valeurs
        écrites séparément divergent — la leçon du blanc d'hier, à un jour
        d'intervalle.
   ═══════════════════════════════════════════════════════════════════════════ */
.pq-barre{
  position:sticky; top:var(--h-topbar); z-index:3;
  background:var(--carte); border-bottom:1px solid var(--line);
  padding:10px 0; margin-bottom:14px;
}
/* ⚠ Une largeur de lecture, pas la largeur de l'écran : un intitulé de
   question sur 1 900 px se lit deux fois moins vite qu'un intitulé sur 900.
   C'est la même raison qui borne le corps des fiches de POPPY à 68 signes. */
/* ⚠ Plus large qu'avant : l'écran porte maintenant DEUX colonnes. La colonne
   des questions garde sa largeur de lecture (voir `.pq-principal`), c'est la
   colonne de droite qui s'ajoute à côté. */
/* ═══════════════════════════════════════════════════════════════════════════
   ⚠⚠ LA LARGEUR DE L'ÉCRAN DÉRIVE DE LA LARGEUR DE LECTURE (12/09).

   ┌──────────────────────────────────────────────────────────────────────────┐
   │ Mesuré : la colonne des questions valait 900 px pour un contenu borné à  │
   │ 74 ch (≈ 550 px) — **320 px de vide à droite de chaque ligne, 37 % de la │
   │ carte**. Le blanc n'était pas un oubli de mise en page : la carte était  │
   │ simplement plus large que tout ce qu'elle contient.                      │
   └──────────────────────────────────────────────────────────────────────────┘

   ⚠⚠ ON NE CENTRE PAS LES CHAMPS DANS LA CARTE — on rétrécit la CARTE. Centrer
      le contenu ne supprime pas le blanc : il le COUPE EN DEUX, 160 px de
      chaque côté, et casse le bord gauche, qui est l'endroit où l'œil revient
      à chaque ligne (aligné avec « Question 1 » et avec le titre de la page).

   ⚠⚠ ET LES TROIS LARGEURS N'EN FONT QU'UNE. `--pq-lecture` est le POINT
      UNIQUE : la borne des champs, la largeur de la carte et celle de l'écran
      en dérivent toutes. Écrites séparément, elles auraient divergé au premier
      réglage — et c'est exactement comme ça que le blanc était apparu : la
      borne des champs avait été posée le 11/09 sans que la carte suive.
      370 px = 30 (rembourrage + filet de la carte) + 20 (gouttière) + 320
      (colonne de droite).
   ═══════════════════════════════════════════════════════════════════════════ */
/* ⚠⚠ LA BORNE PORTE SUR LE CONTENU, PAS SUR L'ÉCRAN (12/09). Posée sur
   `.pq-editeur`, elle emportait la BARRE D'OUTILS avec le document : «
   Enregistrer » se retrouvait au milieu de la page. `.pq-editeur` reprend donc
   toute la largeur, et c'est `.pq-grille` — les deux colonnes, c'est-à-dire ce
   qui se LIT — qui est bornée et centrée.
   ⚠ Les deux seuls enfants directs sont `.pq-barre` et `.pq-grille` : la règle
     ne peut donc rien laisser derrière elle. */
.pq-editeur{ --pq-lecture:74ch; }
.pq-editeur > .pq-grille{
  max-width:calc(var(--pq-lecture) + 370px);
  /* ⚠ Centré : l'espace libre devient une marge de page symétrique, ce qui se
     lit comme un choix — un bloc collé à gauche avec 400 px de vide à droite
     se lit comme un trou. */
  margin-inline:auto;
}

/* ---------------------------------------------------------------------
   DEUX COLONNES : les questions à gauche, le quiz à droite

   ⚠⚠ CE QUI EST À DROITE N'EST PAS CE QU'ON ÉCRIT. La fiche, la thématique,
   l'introduction et l'état se posent UNE FOIS, souvent plus tard, et parfois
   jamais. En tête de page, ils se traversaient à chaque ouverture de l'écran
   avant d'arriver à la première question.

   ⚠ La colonne colle : l'état du quiz — brouillon ou publié — ne sort jamais
   du champ de vision pendant qu'on écrit. C'est le réglage le plus lourd de
   conséquence de cet écran, et le plus facile à oublier.
   --------------------------------------------------------------------- */
.pq-grille{ display:grid; grid-template-columns:minmax(0,1fr) 320px; gap:20px; }
/* ⚠ `minmax(0,1fr)` et non `1fr` : une colonne de grille a une taille minimale
   AUTOMATIQUE, et un intitulé long y refuserait de se comprimer — la colonne
   de droite serait poussée hors de l'écran. C'est le même piège que
   `min-width:auto` en flexbox, qui a coûté les cases « juste » recouvertes. */
/* ⚠ La carte s'arrête où son contenu s'arrête : + 30 px pour le rembourrage
   (14 de chaque côté) et le filet du `.panel`. */
.pq-principal{ min-width:0; max-width:calc(var(--pq-lecture) + 30px); }
.pq-cote{ min-width:0; }
.pq-cote > .panel{ position:sticky; top:64px; }
.pq-cote h3{ margin:0 0 10px; font-size:14px; }

/* ⚠⚠ SOUS 1000 px, LES DEUX COLONNES S'EMPILENT ET LA DROITE PASSE DESSOUS.
   La faire passer au-dessus ferait descendre les questions sous quatre champs
   de réglage — exactement ce qu'on vient de retirer du haut de la page. */
@media (max-width:1000px){
  /* ═══════════════════════════════════════════════════════════════════════
     ⚠⚠ SUR ÉCRAN ÉTROIT, LES RÉGLAGES REPASSENT EN TÊTE. Les deux colonnes
     s'empilent, et la colonne de droite se retrouvait donc SOUS toutes les
     questions : sur un quiz de vingt, il fallait tout traverser pour changer
     l'état ou la session. C'est moi qui l'ai introduit le 10/09 en déplaçant
     ces champs à droite — avant, ils étaient en haut.

     ⚠ `order` les remonte SANS toucher au document : l'ordre de lecture d'un
       lecteur d'écran et le parcours du clavier suivent le DOM, pas la grille.
       Réordonner visuellement ce qu'on ne peut pas réordonner au clavier créé
       un écart entre ce qu'on voit et ce qu'on tabule — ici il est assumé et
       borné à un seul bloc, dont le contenu est un formulaire court.
     ⚠ Et ils sont REPLIÉS : dépliés en tête, ils repousseraient la première
       question sous quatre champs qu'on ne touche presque jamais — ce qui est
       exactement le défaut qu'on avait corrigé en les déplaçant à droite.
     ═══════════════════════════════════════════════════════════════════════ */
  .pq-grille{ grid-template-columns:1fr; }
  .pq-grille > .pq-cote{ order:-1; }
  .pq-cote > .panel{ margin-bottom:10px; }
  .pq-cote > .panel{ position:static; }
}
/* ⚠ Chaque question est un bloc SÉPARÉ, avec de l'air entre elles : empilées
   sans respiration, dix questions se lisent comme un seul mur. */
.pq-editeur #q-questions > .panel{ margin-bottom:16px; padding:14px 16px; }

/* ---------------------------------------------------------------------
   LE BLOC D'UNE QUESTION — trois parties nommées

   ⚠⚠ CE QUI ÉTAIT CONFUS, et que ces règles corrigent : huit champs se
   suivaient à plat, sans hiérarchie, et le plus important — les réponses —
   arrivait APRÈS l'image et la vidéo, c'est-à-dire après deux champs presque
   toujours vides et deux textes d'aide de trois lignes.
   --------------------------------------------------------------------- */

/* L'intitulé est la phrase que le stagiaire lira. La voir petite, au milieu
   d'une grille de champs, fait écrire des questions télégraphiques. */
.pq-intitule{ font-size:15px; padding:9px 12px; }

/* Le titre d'une partie, et le raccourci qui lui appartient — « Vrai / Faux »
   isolé au-dessus des réponses, aligné à droite, n'avait aucun rattachement
   visible : on ne savait pas sur quoi il agissait. */
.pq-section{
  display:flex; align-items:center; justify-content:space-between;
  gap:12px; margin:16px 0 6px;
  font-size:12px; font-weight:600; letter-spacing:.03em;
  text-transform:uppercase; color:var(--muted);
}

/* La ligne d'en-tête des réponses. ⚠ `aria-hidden` côté marquage : ce sont des
   repères visuels, et un lecteur d'écran les lirait AVANT chaque champ, qui
   porte déjà son propre libellé. */
.pq-entetes{
  font-size:11px; text-transform:uppercase; letter-spacing:.03em;
  color:var(--muted); margin-bottom:2px;
}
/* ⚠ MÊME VALEUR que `.pq-reponse .case` ci-dessus — les deux se tiennent, et
   changer l'une sans l'autre décale l'en-tête d'une colonne. */
.pq-col-juste{ flex:0 0 auto; min-width:92px; }

.pq-ajout{ margin:6px 0 4px; }

/* Le ✕ d'une ligne de réponse.
   ⚠ Il ne se comprime pas et reste à droite : c'est la position stable quel
   que soit le nombre de champs de la ligne (une association en porte deux).
   ⚠ Discret au repos, franc au survol : c'est une action destructrice, mais
   elle est répétée sur chaque ligne — en faire un bouton rouge plein
   transformerait la liste en mur d'alertes. */
.pq-retirer{ flex:0 0 auto; opacity:.45; }
.pq-retirer:hover, .pq-retirer:focus-visible{ opacity:1; }

/* L'option « mélanger », sous le choix de la forme.
   ⚠ Son explication est DANS le libellé et en petit : une aide sur sa propre
   ligne aurait ajouté un élément de plus à un écran qu'on vient d'alléger. */
.pq-option{ margin:2px 0 10px; align-items:flex-start; }
.pq-option .aide{ display:inline; margin:0; }

/* Le facultatif, replié. ⚠ Il s'OUVRE TOUT SEUL quand il porte déjà quelque
   chose (attribut `open` posé au rendu) : un contenu caché qu'on ne sait pas
   caché est un contenu perdu. */
.pq-plus{ margin-top:14px; border-top:1px solid var(--line); padding-top:10px; }
.pq-plus > summary{
  cursor:pointer; font-size:12px; font-weight:600; letter-spacing:.03em;
  text-transform:uppercase; color:var(--muted); list-style:none;
}
/* ⚠ Le triangle natif est remplacé par un signe qui suit l'état : sur Safari,
   `list-style:none` le fait disparaître sans rien mettre à la place, et la
   ligne ne se lit plus comme quelque chose qu'on peut ouvrir. */
.pq-plus > summary::-webkit-details-marker{ display:none; }
.pq-plus > summary::before{ content:'▸ '; }
.pq-plus[open] > summary::before{ content:'▾ '; }
.pq-plus > summary:hover{ color:var(--ink); }

/* ---------------------------------------------------------------------
   UNE QUESTION À LA FOIS — le repli

   ⚠⚠ Mesuré sur le marquage : une question dépliée porte NEUF groupes de
   champs. Un quiz de dix questions en affichait quatre-vingt-dix, tous en même
   temps, alors qu'on n'en écrit qu'une à la fois.

   ⚠⚠ `display:none` MASQUE, il ne retire pas du document : `lireEditeur()`
   parcourt les champs de TOUTES les questions, repliées comprises. Retirer les
   blocs repliés ferait perdre, au premier enregistrement, tout ce qui n'est pas
   ouvert à l'écran — et cela ne se verrait qu'après coup.
   --------------------------------------------------------------------- */
.pq-resume{
  display:none; width:100%; align-items:center; justify-content:space-between;
  gap:12px; padding:8px 10px; margin:0;
  background:none; border:0; border-radius:6px;
  font:inherit; text-align:left; color:var(--ink); cursor:pointer;
}
.pq-resume:hover{ background:var(--soft2); }
/* ⚠ L'intitulé se coupe s'il est long : deux lignes de résumé rendraient au
   repli la hauteur qu'il fait gagner. */
.pq-resume-t{
  flex:1 1 auto; min-width:0; overflow:hidden;
  text-overflow:ellipsis; white-space:nowrap; font-weight:600;
}
/* Replié : le résumé paraît, le corps disparaît. */
.panel.pq-replie > .pq-resume{ display:flex; }
.panel.pq-replie > .pq-corps{ display:none; }
/* ⚠ Une question repliée est plus plate : sans cela, dix cartes de la même
   épaisseur qu'une question ouverte ne feraient pas gagner grand-chose. */
.panel.pq-replie{ padding:8px 10px; }
.panel.pq-replie > .pq-tete{ margin-bottom:0; }

/* ⚠ Les règles de la bande de partie ont été retirées le 11/09 avec la
   fonction elle-même. ⚠⚠ `.espace-partie` RESTE plus bas : elle sert au
   regroupement « les quiz de votre formation », qui n'a rien à voir. */

/* ╔═════════════════════════════════════════════════════════════════════════╗
   ║  LE BLOC D'UNE QUESTION — LA MISE EN PAGE, REPRISE LE 11/09/2026        ║
   ╚═════════════════════════════════════════════════════════════════════════╝

   Julien : « je n'aime pas du tout comment est organisé le bloc question […]
   il faut que cela soit moderne ». Dix points, et le diagnostic tient en une
   phrase : c'était une PILE DE CHAMPS DE MÊME POIDS — neuf éléments qui se
   suivent, tous à 1 080 px de large, sous des intitulés en capitales grises
   qui criaient plus fort que leur contenu. Rien ne disait ce qui est le cœur
   (la question et ses réponses) et ce qui est du réglage.

   ⚠⚠ TOUT CE QUI SUIT EST BORNÉ À `.pq-editeur`. `.field > label`, `.input`
   et `.case` sont des classes du SOCLE, partagées avec dev A : les redéfinir
   globalement changerait dix-neuf écrans qui ne l'ont pas demandé. C'est la
   leçon du 31/08, où j'ai failli faire changer le socle typographique pour une
   raison inexistante.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 1) LES INTITULÉS EN CAPITALES GRISES DISPARAISSENT ─────────────────────
   `CE QUE VOUS DEMANDEZ`, `FORME DE LA RÉPONSE`… quatre bandeaux en majuscules
   espacées, plus voyants que les champs qu'ils désignent. C'est le marqueur
   visuel le plus daté de cet écran.
   ⚠ Ils ne sont pas SUPPRIMÉS — un champ sans nom n'est pas nommé pour un
     lecteur d'écran, et le texte d'invite disparaît dès qu'on tape. Ils
     passent en minuscules, petits, et cessent de crier. */
.pq-editeur .field > label{
  text-transform:none; letter-spacing:0; font-size:12.5px; font-weight:600;
  color:var(--muted); margin-bottom:5px;
}

/* ── 2) LA COLONNE DE SAISIE EST BORNÉE ────────────────────────────────────
   ⚠⚠ Un intitulé de question sur 1 080 px se lit mal ET fait écrire des
   questions télégraphiques : l'œil perd le début de la ligne suivante. La
   fiche POPPY est bornée depuis le 02/09 pour la même raison, et en `ch` —
   donc la borne suit la taille du texte au lieu de la contredire. */
.pq-editeur .pq-corps > .field,
.pq-editeur .pq-corps > .pq-section,
.pq-editeur .pq-corps > [data-choixde],
.pq-editeur .pq-corps > .pq-ajout{ max-width:var(--pq-lecture); }

/* ── 5) LES CHAMPS EN BOÎTE DEVIENNENT DES CHAMPS SOULIGNÉS ────────────────
   Neuf rectangles bordés empilés font un formulaire administratif. Un trait
   sous le champ, qui s'épaissit et se colore au focus, est le geste le plus
   rentable de la liste.
   ⚠⚠ LE CONTOUR DE FOCUS RESTE FRANC : c'est `--line-champ` qui dit OÙ TAPER,
   et il avait été relevé au-dessus du seuil RGAA le 10/09. Un soulignement
   pâle au repos est acceptable ; un focus pâle ne l'est pas. */
.pq-editeur .pq-corps .input,
.pq-editeur .pq-cote .input{
  background:transparent; border:0; border-radius:0;
  border-bottom:1px solid var(--line-champ);
  padding-left:2px; padding-right:2px;
}
.pq-editeur .pq-corps .input:focus,
.pq-editeur .pq-cote .input:focus{
  border-bottom:2px solid var(--poppy);
  /* ⚠ Le champ ne doit pas SAUTER d'un pixel au focus : on compense. */
  padding-bottom:6px;
}
/* ⚠ Une liste déroulante garde son cadre : sans lui, rien ne dit qu'elle
   s'ouvre — elle se lit comme du texte. Un champ qui ne dit pas ce qu'il est
   fait chercher ce qui ne marche pas. */
.pq-editeur select.input{
  border:1px solid var(--line-champ); border-radius:var(--r-s);
  padding-left:10px; padding-right:10px; background:var(--carte);
}
.pq-editeur select.input:focus{
  border:1px solid var(--poppy); padding-bottom:7px;
}
/* L'intitulé de la question est le texte que le stagiaire lira : il est en
   gros, et son trait est plus marqué que celui des autres champs. */
.pq-editeur .pq-intitule{ font-size:16px; font-weight:600; padding-bottom:6px; }

/* ── 4) LA LIGNE COCHÉE SE TEINTE ──────────────────────────────────────────
   ⚠⚠ CE QUI REMPLACE LE MOT « JUSTE » ÉCRIT QUATRE FOIS. Répété sur chaque
   ligne, il faisait plus de bruit que les réponses et n'apprenait rien après
   la première. La teinte, elle, se lit d'un coup d'œil — ce qui était
   impossible avant : rien ne distinguait la bonne réponse des autres.
   ⚠ `:has()` : si un navigateur ne le connaît pas, il n'y a PAS de teinte —
     et rien d'autre ne change. C'est une amélioration, pas une béquille ; la
     case reste cochée, lisible et annoncée. */
.pq-juste{ min-width:0 !important; }
.pq-reponse:has(input[data-juste]:checked){
  /* ⚠⚠ LE JETON DU SOCLE, ET PAS UN JETON INVENTÉ AVEC UN REPLI. Ma première
     rédaction écrivait un nom de variable qui n'existe nulle part, suivi d'une
     couleur de secours : ça marchait — et personne n'aurait jamais vu que la
     variable ne désignait rien. C'est le piège du 03/09, où un jeton venu
     d'une autre page a survécu à un audit vert parce que son repli le
     masquait. **Un repli qui cache une invention est pire que l'invention.**
     ⚠ `--ok-bg` existe depuis le socle et sert déjà aux états favorables : la
       teinte est donc celle du reste de l'application, pas un vert à moi. */
  background:var(--ok-bg);
  border-radius:var(--r-s);
  box-shadow:inset 2px 0 0 var(--ok);
  padding:2px 6px 2px 4px; margin-left:-4px;
}
/* ⚠ Hors de l'écran, PAS hors du document : `display:none` retirerait le mot
   de l'accessibilité, et la case s'annoncerait « case à cocher », point. */
.pq-vh{
  position:absolute; width:1px; height:1px; overflow:hidden;
  clip-path:inset(50%); white-space:nowrap;
}

/* ── 7) LE CHEVRON EST SUR LE TITRE, ET LE TITRE EST LE BOUTON ─────────────
   Ils étaient deux éléments voisins : un chevron gris pâle, puis un titre
   inerte. On cliquait sur « Question 1 » et rien ne se passait. */
.pq-titre{
  display:flex; align-items:center; gap:8px;
  flex:1 1 auto; min-width:0;
  background:none; border:0; padding:2px 0; margin:0;
  font:inherit; font-size:14px; font-weight:700; color:var(--ink);
  text-align:left; cursor:pointer;
}
.pq-titre:hover{ color:var(--poppy-dark); }
/* ⚠ Largeur fixe : sans elle la barre se décale d'un pixel entre « ▸ » et
   « ▾ », et l'œil le voit. */
.pq-chevron{
  flex:0 0 auto; width:14px; color:var(--muted); font-size:11px; line-height:1;
}

/* ── 6) LES GESTES APPARAISSENT AU SURVOL, ET LE ✕ EST DÉTACHÉ ─────────────
   Six glyphes gris pâle collés, sans libellé, et le destructeur au même poids
   que le reste.
   ⚠⚠ LE MASQUAGE NE VAUT QUE LÀ OÙ LE SURVOL EXISTE. Sur un écran tactile il
   n'y a pas de survol : masquer rendrait les gestes INTROUVABLES. D'où
   `@media (hover:hover)` — et jamais l'inverse.
   ⚠ `:focus-within` les rend visibles au clavier : sans lui, on tabulerait sur
     des boutons invisibles. */
.pq-tete .actions-ligne{ gap:2px; }
.pq-tete [data-suppr]{ margin-left:10px; }
@media (hover: hover) {
  .panel[data-q] .pq-tete .actions-ligne{ opacity:0; transition:opacity .12s; }
  .panel[data-q]:hover .pq-tete .actions-ligne,
  .panel[data-q] .pq-tete:focus-within .actions-ligne{ opacity:1; }
}

/* ── 8) LE RYTHME DES TROIS PARTIES ────────────────────────────────────────
   Question / Réponses / Explication étaient à plat, séparés par rien. Ce n'est
   pas un trait qu'il faut, c'est de l'espace — et un retrait qui dise que les
   lignes appartiennent à leur titre. */
.pq-editeur .pq-section{
  margin:26px 0 10px; text-transform:none; letter-spacing:0;
  font-size:13px; color:var(--ink);
}
.pq-section-t{ min-width:0; }
.pq-section-a{ font-weight:400; color:var(--muted); }
.pq-section-r{ display:flex; align-items:center; gap:12px; flex:0 0 auto; }
.pq-melange{ margin-bottom:0; font-size:12.5px; color:var(--muted); white-space:nowrap; }
/* ⚠ Le retrait ET le filet : le filet seul se confond avec une bordure de
   champ, le retrait seul ne se voit pas sur une seule ligne. */
.pq-editeur [data-choixde]{
  padding-left:12px; border-left:2px solid var(--line); margin-left:2px;
}
.pq-editeur .pq-ajout{ padding-left:14px; }

/* ── 9) LA CARTE OUVERTE SE DÉTACHE ────────────────────────────────────────
   Rien ne distinguait la question qu'on écrit de celles qu'on ne touche pas.
   ⚠ Le liseré est à GAUCHE, là où l'œil revient à chaque ligne. */
.pq-editeur #q-questions > .panel:not(.pq-replie){
  border-left:3px solid var(--poppy);
  box-shadow:0 2px 12px rgba(0,0,0,.07);
}
.pq-editeur #q-questions > .panel.pq-replie{ box-shadow:none; }
/* ⚠ Une carte repliée se survole comme une ligne cliquable : elle EST
   cliquable — tout son en-tête déplie. */
.pq-editeur #q-questions > .panel.pq-replie:hover{ background:var(--ligne-survol); }

/* ⚠ Les règles de la bande de partie ont été retirées le 11/09 avec la
   fonction elle-même. ⚠⚠ `.espace-partie` RESTE plus bas : elle sert au
   regroupement « les quiz de votre formation », qui n'a rien à voir. */


/* Le champ de recherche de la liste des quiz.
   ⚠ Il vit dans un `.actions`, qui aligne à droite : sans largeur propre il
     prendrait toute la place restante et pousserait les filtres hors de vue.
   ⚠ `margin-right:auto` le colle à GAUCHE et repousse les chips à droite :
     on cherche avant de filtrer, et l'œil lit dans cet ordre. */
.pq-chercher{ flex:0 1 260px; min-width:0; margin-right:auto; }

/* ── L'ÉTAT « NON ENREGISTRÉ », DANS LA BARRE ──────────────────────────────
   ⚠⚠ `etat.modifie` était employé vingt et une fois dans l'écran et affiché
   NULLE PART : on écrivait vingt minutes sans qu'un repère ne dise que rien
   n'était enregistré.
   ⚠ Vide au repos — un « enregistré » permanent deviendrait du décor, et c'est
     exactement ce qu'on ne veut pas qu'il soit. */
.pq-etat{ display:inline-block; margin-left:10px; font-size:12px; color:var(--att); }
.pq-etat.actif{ font-weight:600; }

/* ── LES OUTILS DE LA BARRE, DERRIÈRE UN « ⋯ » ─────────────────────────────
   ⚠⚠ Six boutons de même poids, et un seul compte. Les quatre du milieu sont
   des outils — on s'en sert une fois par quiz, parfois jamais.
   ⚠ `<details>` et pas un écouteur : il s'ouvre et se ferme au clavier sans
     une ligne de JavaScript.
   ⚠ `.panel` porte `overflow:hidden` ailleurs dans cette feuille ; ici le
     parent est `.page-tete`, qui ne le porte pas — le panneau n'est donc pas
     rogné. C'est le défaut du 03/09, vérifié plutôt que supposé. */
.pq-outils{ position:relative; display:inline-block; }
.pq-outils > summary{ list-style:none; cursor:pointer; user-select:none; }
.pq-outils > summary::-webkit-details-marker{ display:none; }
.pq-outils-liste{
  position:absolute; right:0; top:calc(100% + 4px); z-index:30;
  display:flex; flex-direction:column; align-items:stretch; gap:2px;
  min-width:220px; padding:6px;
  background:var(--carte); border:1px solid var(--line);
  border-radius:var(--r-s); box-shadow:0 6px 20px rgba(0,0,0,.12);
}
/* ⚠ Alignés à GAUCHE : une liste de commandes se lit par son premier mot, et
   `.btn` centre par défaut. */
.pq-outils-liste .btn{ justify-content:flex-start; text-align:left; }

/* ⚠ `.pq-ratees` a été RETIRÉE le 11/09 avec le bloc « ce qui passe mal » de la
   colonne de droite. Une classe sans emploi ne casse rien et ne se voit pas —
   c'est le piège du 16/08 pris à l'envers : la feuille garde du poids et le
   prochain lecteur cherche l'écran qui s'en sert. Le classement vit sur
   l'écran de LISTE, qui emploie un vrai tableau (`.tbl`). */

/* La question que le serveur refuse de publier.
   ⚠ Le liseré est à GAUCHE, comme celui de la carte ouverte : l'œil y revient
     à chaque ligne. Il REMPLACE le liseré rouge vif de l'ouverture plutôt que
     de s'y ajouter — deux liserés côte à côte ne disent pas deux choses, ils
     brouillent la première.
   ⚠ Le ton est celui de l'attente, pas de l'erreur : le quiz est en cours
     d'écriture, rien n'est cassé. C'est la règle du 16/08 — une couleur encode
     l'urgence, jamais le type. */
.pq-editeur #q-questions > .panel.pq-fautive{ border-left-color:var(--att); }
.pq-editeur #q-questions > .panel.pq-fautive [data-probleme]{ margin-bottom:12px; }

/* ═══════════════════════════════════════════════════════════════════════════
   LA LISTE COMBINÉE DES SESSIONS (11/09) — recherche et complétion.

   ⚠⚠ LA LISTE FLOTTE AU-DESSUS DU RESTE, et il faut le vérifier plutôt que le
   supposer : `.panel` porte `overflow:hidden` dans cette feuille — c'est lui
   qui avait DÉCOUPÉ le panneau déroulant du planning le 03/09. Le champ vit ici
   dans `.pq-cote > .panel` : on RÉTABLIT donc `overflow:visible` sur ce panneau
   précis, et sur lui seul. Un retrait général sur `.panel` casserait le rayon
   des coins de toutes les cartes de l'application.

   ⚠ `position:relative` sur le conteneur, pas sur le champ : c'est le
     conteneur qui donne son repère à la liste absolue.
   ═══════════════════════════════════════════════════════════════════════════ */
/* ⚠⚠ UNE CLASSE POSÉE PAR LA VUE, ET PAS `:has()`. Sur un navigateur qui ne
   connaît pas `:has()`, la règle ne s'applique pas : `overflow:hidden` reste,
   et la liste est DÉCOUPÉE — c'est-à-dire exactement le défaut qu'on corrige,
   sans que rien ne le dise. C'est la même faute que j'avais évitée le soir
   même sur l'impression d'une fenêtre, et que j'ai refaite ici dix minutes
   plus tard. **Un sélecteur dont l'absence recrée le bug n'est pas un
   sélecteur, c'est un pari.** */
.panel.pq-panel-combo{ overflow:visible; }
.pq-combo{ position:relative; }
.pq-combo-liste{
  position:absolute; left:0; right:0; top:100%; z-index:40;
  margin:2px 0 0; padding:4px; list-style:none;
  max-height:260px; overflow-y:auto;
  background:var(--carte); border:1px solid var(--line-champ);
  border-radius:var(--r-s); box-shadow:0 6px 20px rgba(0,0,0,.14);
}
.pq-combo-liste [role="option"]{
  padding:6px 8px; border-radius:var(--r-s); cursor:pointer;
  font-size:12.5px; line-height:1.35;
}
/* ⚠ `.actif` ET `:hover` : la souris et le clavier désignent la même chose, et
   `aria-activedescendant` ne peint rien tout seul. */
.pq-combo-liste [role="option"].actif,
.pq-combo-liste [role="option"]:hover{ background:var(--soft2); }
.pq-combo-vide{ padding:6px 8px; font-size:12px; color:var(--muted); }
.pq-combo-bas{ margin-top:6px; }

/* ═══════════════════════════════════════════════════════════════════════════
   L'ESSAI D'UN QUIZ PAR SON AUTEUR — dev B, 11/09.

   ⚠ Une question d'essai est un `<fieldset>` : sa légende PORTE la question,
     ce qui lui donne son nom accessible sans `aria-labelledby`. C'est le
     défaut qu'`audit_rgaa` a trouvé le 31/08 sur l'écran de quiz.
   ⚠ `min-width:0` sur le terme d'une association : sans lui, un terme long ne
     rétrécit pas — il déborde et recouvre sa liste déroulante. C'est le piège
     `min-width:auto`, celui qui a fait « les boutons ne marchent pas ». */
/* L'image d'une réponse dans l'espace stagiaire (migration 2056).
   ⚠ Bornée en hauteur : quatre réponses illustrées en taille réelle feraient
     défiler trois écrans pour une seule question.
   ⚠ `object-fit:contain` : une vignette rognée ne montre pas ce qu'on demande
     de reconnaître — et c'est justement ce qu'on demande de reconnaître.
   ⚠ Elle vit DANS le label, à côté du texte : cliquer l'image coche donc la
     réponse, ce que tout le monde essaie de faire. */
/* Le repère de progression d'un quiz dans l'espace stagiaire (11/09).
   ⚠ Le MÊME que dans POPPY, et pour la même raison : sur un quiz de vingt
     questions on ne savait ni où l'on en était, ni combien il restait.
   ⚠ La barre est décorative — le texte à côté dit la même chose, et c'est LUI
     qui est annoncé. Une barre seule ne dit rien à qui ne voit pas. */
.espace-progres{
  display:flex; align-items:center; gap:10px;
  margin-bottom:10px; font-size:12.5px; color:var(--muted);
}
.espace-progres-b{
  flex:1 1 auto; min-width:0; height:6px;
  background:var(--soft2); border-radius:3px; overflow:hidden;
}
.espace-progres-b > i{
  display:block; height:100%; width:0;
  background:var(--poppy); transition:width .2s;
}

.espace-rep-image{
  display:block; max-width:220px; max-height:140px; object-fit:contain;
  margin:4px 0; border:1px solid var(--line); border-radius:var(--r-s);
}

.pq-essai{ margin-bottom:14px; }
.pq-essai .case{ display:flex; margin-bottom:4px; }
.pq-terme{ flex:0 1 40%; min-width:0; font-weight:600; }
.case input{ width:16px; height:16px; accent-color:var(--poppy); }

fieldset{ border:1px solid var(--line); border-radius:var(--r-s); padding:12px 14px; margin:0 0 14px; }
fieldset>legend{ font-size:11.5px; font-weight:700; text-transform:uppercase; letter-spacing:.5px; color:var(--poppy-dark); padding:0 6px; }

/* ---------------------------------------------------------------------
   10. TABLEAUX
   --------------------------------------------------------------------- */
.tbl-scroll{ width:100%; overflow-x:auto; }
table.tbl{ width:100%; border-collapse:collapse; font-size:13px; }
/*
 * ┌──────────────────────────────────────────────────────────────────────────┐
 * │ L'EN-TÊTE PERD SON APLAT — 24/09/2026, décision de Julien.               │
 * │                                                                          │
 * │ C'est le chantier « design des tableaux » ouvert le 17/08 et resté sans  │
 * │ suite : 186 tableaux sur 35 vues. L'aplat `--soft2` posait une bande     │
 * │ rosée en tête de chaque tableau, y compris dans les panneaux qui en      │
 * │ contiennent trois : l'œil comptait les bandes avant de lire les lignes.  │
 * │                                                                          │
 * │ ⚠ CE QUI DISTINGUE L'EN-TÊTE NE DISPARAÎT PAS AVEC L'APLAT : les         │
 * │   capitales, le rouge sombre, la graisse 700 et le filet de 2 px sont    │
 * │   quatre marques, et elles restaient déjà les seules sur un écran        │
 * │   imprimé, où les aplats ne sortent pas.                                 │
 * │                                                                          │
 * │ ⚠⚠ ET LE PIED GARDE LE SIEN (`tfoot`, plus bas) : un total se distingue  │
 * │   d'une ligne ordinaire, et c'est la seule bande qui reste — donc la     │
 * │   seule qui se remarque. Elle ne se remarquait plus quand il y en avait  │
 * │   deux.                                                                  │
 * └──────────────────────────────────────────────────────────────────────────┘
 */
/*
 * ⚠⚠ L'EN-TÊTE N'EST PAS UNE INFORMATION, C'EST UNE ÉTIQUETTE (26/09).
 *
 *    Il était coquelicot, en capitales, interlettré, sur un filet de 2 px :
 *    dans un tableau de dix lignes, la LIGNE LA PLUS VOYANTE de l'écran était
 *    celle qui ne porte aucune donnée. Sur la maquette validée par Julien
 *    (Demandes, 26/09) l'en-tête est GRIS, en casse normale, à la taille du
 *    corps, sur un filet fin — et ce sont les statuts colorés des lignes qui
 *    prennent le regard, c'est-à-dire ce qu'on est venu lire.
 *    C'est la règle du 16/08 — *une couleur encode l'urgence, jamais le type* —
 *    appliquée à l'en-tête : un nom de colonne n'est jamais urgent.
 *
 * ⚠ L'EFFET EST VOULU PARTOUT : sur tous les tableaux `.tbl`, de toutes les
 *   vues. C'est une charte, et une charte qui ne vaudrait que sur un écran n'en
 *   est pas une. (Ce commentaire disait « 49 tableaux, 19 vues », le compte du
 *   17/08 ; le bloc du dessus en mesure 186 sur 35 le 24/09. Un nombre écrit
 *   dans un commentaire vieillit sans bruit : il n'est plus écrit, 30/09.)
 * ⚠ `<th scope="row">` garde sa dérogation plus bas — elle DÉFAISAIT les
 *   capitales et le coquelicot, qui n'y sont plus : elle est désormais
 *   presque sans effet, et on la garde telle quelle pour le `background` et
 *   le `white-space`.
 */
/*
 * ⚠⚠ L'EN-TÊTE PORTE UN GRIS TRÈS PÂLE, PAS DU BLANC (27/09, mesuré).
 *
 *   Relevé au pixel sur les maquettes : `rgb(250,249,249)` sur l'en-tête,
 *   `rgb(255,255,255)` chez nous. Sur un tableau zébré, un en-tête blanc se
 *   confond avec la première ligne — qui est blanche elle aussi : la ligne de
 *   titres n'a alors plus d'assise, et l'œil ne sait plus où commence la
 *   liste. Le gris ne « colore » rien, il POSE la ligne.
 * ⚠ `--fond` vaut #f4f3f2 et serait trop marqué ; la valeur est donc écrite
 *   telle que la maquette la donne. Elle vaut pour **tous les tableaux `.tbl`**
 *   — c'est le socle qui les rend tous, et c'est bien ce qu'on veut :
 *   deux tableaux voisins qui ne se ressemblent pas font douter du reste.
 * ⚠ Le zébrage, lui, était déjà exact : `rgb(253,246,245)` contre
 *   `rgb(252,246,246)` mesuré. On ne touche pas à ce qui tombe juste.
 */
table.tbl th{
  background:#faf9f9; color:var(--muted);
  text-align:left; font-weight:600; font-size:12.5px;
  text-transform:none; letter-spacing:0;
  padding:10px 12px; border-bottom:1px solid var(--line); white-space:nowrap;
}
/*
 * ⚠⚠ `.droite`, `.num` ET `.centre` ÉTAIENT SANS EFFET SUR UN `<th>`, ET ÇA SE
 *    VOYAIT SUR TOUTE L'APPLICATION.
 *
 *    `table.tbl th{ text-align:left }` pèse (0,1,2) ; `.droite` pèse (0,1,0).
 *    La classe perdait donc TOUJOURS, en silence : l'en-tête restait collé à
 *    gauche au-dessus d'une colonne de nombres alignés à droite. Mesuré le
 *    12/09 : **31 `<th class="droite">` et 38 `<th class="num">`, dans 22
 *    vues** — c'est-à-dire presque toutes les colonnes de chiffres du projet.
 *    ⚠ Et `.num` ne posait pas non plus `tabular-nums` sur l'en-tête, ce qui
 *      n'a aucune importance pour un libellé mais dit bien que la règle
 *      `table.tbl td.num` n'avait jamais été pensée pour l'en-tête.
 *
 * ⚠ AJOUT PUREMENT ADDITIF : aucune règle existante n'est touchée. Une vue qui
 *   ne pose pas ces classes ne bouge pas d'un pixel.
 */
table.tbl th.droite,
table.tbl th.num{ text-align:right; }
table.tbl th.centre{ text-align:center; }

/*
 * ⚠⚠ UN EN-TÊTE DE LIGNE N'EST PAS UN EN-TÊTE DE COLONNE, ET IL NE DOIT PAS EN
 *    AVOIR L'AIR.
 *
 *    `table.tbl th` habille TOUT `<th>` en en-tête de colonne : coquelicot,
 *    majuscules, interlettrage, fond d'en-tête. Or `<th scope="row">` est le
 *    libellé d'une ligne dans un tableau clé/valeur — « Sessions sans lieu »,
 *    « PARIS », « SEINE-ET-MARNE ». Signalé par Julien le 12/09 sur l'écran des
 *    lieux : **vingt-et-une lignes rouges en majuscules pour des noms de
 *    villes**, sur un écran où rien n'est anormal. C'est la règle du 16/08 —
 *    *une couleur encode l'urgence, jamais le type* — et le rouge de cette
 *    charte est celui de l'ACTION.
 *
 *    Le socle distinguait déjà les deux `scope` du point de vue de
 *    l'accessibilité (audit_vues, contrôle 14) ; il ne les distinguait pas du
 *    point de vue de l'œil.
 *
 * ⚠ Il reste un `<th>` : gras, et c'est ce qui doit rester. Un libellé de ligne
 *   est le NOM de la ligne, pas une cellule ordinaire.
 * ⚠ Employé dans 7 fichiers au 12/09 — l'effet est visible partout, et c'est
 *   voulu : la faute était partout.
 */
table.tbl th[scope="row"]{
  background:transparent; color:var(--ink);
  text-transform:none; letter-spacing:0; font-size:13px;
  border-bottom:1px solid var(--line);
  white-space:normal;
}

table.tbl th.triable{ cursor:pointer; user-select:none; }
table.tbl th.triable:hover{ color:var(--poppy); }
table.tbl th .fleche{ opacity:.55; font-size:10px; margin-left:3px; }
/* ⚠ 8 → 11 px, et pas davantage. Une ligne porte souvent DEUX informations
   empilées (un nom puis sa ville, un contact puis son adresse) : à 8 px, les
   deux lignes d'une cellule se touchaient plus qu'elles ne se séparaient de la
   cellule voisine. ⚠ Doubler aurait allongé de 400 px les tableaux de 50
   lignes du journal et du suivi — on mesure ce qu'on ajoute (12/09). */
table.tbl td{ padding:11px 12px; border-bottom:1px solid var(--line); vertical-align:middle; }
/*
 * ⚠⚠ LE SURVOL EST RÉSERVÉ AUX LIGNES QU'ON PEUT OUVRIR.
 *
 * Il était inconditionnel : une table de LECTURE — « À propos », les droits par
 * rôle, le détail d'un dossier — réagissait au curseur exactement comme une
 * liste cliquable. C'est la leçon 4 du 16/08 dans une autre famille : le signal
 * encodait le TYPE d'élément (une ligne de tableau) au lieu de l'ACTION
 * possible (celle-ci s'ouvre, celle-là non).
 *
 * ⚠ `.cliquable` existait déjà et portait le curseur ; elle porte maintenant
 *   les deux. Une ligne cliquable sans cette classe perd donc son survol — et
 *   c'est voulu : le contrôle 12 d'`audit_vues` attrape déjà l'inverse (un
 *   sélecteur qui vise une classe absente), et une ligne qui invite au clic
 *   sans être cliquable est le défaut qu'on corrige ici.
 */
table.tbl tbody tr.cliquable{ cursor:pointer; }
table.tbl tbody tr.cliquable:hover{ background:var(--ligne-survol); }
table.tbl tbody tr.selectionnee{ background:var(--soft); }
table.tbl td.num{ text-align:right; white-space:nowrap; font-variant-numeric:tabular-nums; }
table.tbl tfoot td{ background:var(--soft); font-weight:700; color:var(--poppy-dark); border-top:2px solid var(--line); }

.pagination{
  display:flex; align-items:center; gap:8px; justify-content:flex-end;
  padding:9px 14px; font-size:12.5px; color:var(--muted);
}
/* ⚠ `.pousse` n'était définie QUE sous `.barre` (l. 156) : posée dans un pied
   de liste, elle n'alignait rien — une règle qui ne peint pas, sans erreur ni
   avertissement, exactement comme une classe inventée. C'est la DEUXIÈME fois
   en deux jours que je l'écris hors de son parent ; elle vaut désormais ici
   aussi. ⚠ Additif : un pied qui ne la pose pas garde son alignement à droite. */
.pagination .pousse{ margin-left:auto; }

/* ---------------------------------------------------------------------
   11. KANBAN
   --------------------------------------------------------------------- */
.kanban{ display:flex; gap:12px; overflow-x:auto; padding-bottom:8px; align-items:flex-start; }
.kanban>.col{
  flex:0 0 268px; background:#efedec; border:1px solid var(--line);
  border-radius:var(--r); padding:8px; min-height:120px;
}
.kanban>.col>h4{
  display:flex; align-items:center; gap:8px; margin:2px 4px 8px;
  font-size:11.5px; text-transform:uppercase; letter-spacing:.6px; color:var(--muted);
}
.kanban>.col>h4 .n{
  margin-left:auto; background:var(--carte); border:1px solid var(--line);
  border-radius:10px; padding:0 7px; font-size:11px; color:var(--ink);
}
.kanban>.col.survol{ background:var(--soft); border-color:var(--poppy-clair); }
.kanban .carte{
  background:var(--carte); border:1px solid var(--line); border-left:4px solid var(--poppy);
  border-radius:var(--r-s); padding:9px 10px; margin-bottom:8px; cursor:grab;
  box-shadow:0 1px 2px rgba(43,43,43,.05);
}
.kanban .carte:hover{ border-color:var(--poppy-clair); }
.kanban .carte .t{ font-weight:700; font-size:13px; margin-bottom:3px; }
.kanban .carte .m{ font-size:11.5px; color:var(--muted); }
.kanban .carte .pied{ display:flex; align-items:center; gap:6px; margin-top:7px; flex-wrap:wrap; }

/* ---------------------------------------------------------------------
   12. CALENDRIER
   --------------------------------------------------------------------- */
.cal{ background:var(--carte); border:1px solid var(--line); border-radius:var(--r); overflow:hidden; }
.cal .tete{
  display:flex; align-items:center; gap:10px;
  padding:10px 12px; border-bottom:1px solid var(--line); background:var(--soft2);
}
.cal .tete .mois{ font-weight:800; font-size:15px; color:var(--poppy-dark); text-transform:capitalize; }
.cal .jours,
.cal .grille-jours{ display:grid; grid-template-columns:repeat(7,minmax(0,1fr)); }
.cal .jours div{
  padding:6px 8px; font-size:11px; font-weight:700; text-transform:uppercase;
  letter-spacing:.5px; color:var(--muted); border-bottom:1px solid var(--line); text-align:center;
}
.cal .case-jour{
  min-height:104px; border-right:1px solid var(--line); border-bottom:1px solid var(--line);
  padding:4px 5px; overflow:hidden;
}
.cal .case-jour:nth-child(7n){ border-right:none; }
.cal .case-jour.hors{ background:var(--soft2); color:var(--muted); }
.cal .case-jour.aujourdhui{ background:var(--soft); }
.cal .case-jour .num{ font-size:11.5px; font-weight:700; color:var(--muted); }
.cal .case-jour.aujourdhui .num{ color:var(--poppy); }

/* ⚠ Événements du calendrier : la classe d'état « app » (session en attente)
   produit « .ev.app ». C'est CE nom qui entrait en collision avec un ancien
   sélecteur nu « .app » de la coquille — d'où la règle « body>.app » (§3).
   Ne renommer ni l'un ni l'autre sans corriger les deux endroits. */
.cal .ev{
  display:block; margin-top:3px; padding:2px 6px; border-radius:4px;
  font-size:11px; font-weight:600; line-height:1.45;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  cursor:pointer; border-left:3px solid transparent;
}
.cal .ev.prev{ background:var(--neutre-bg); color:var(--neutre); border-left-color:var(--muted); }   /* prévisionnelle */
.cal .ev.conf{ background:var(--ok-bg);     color:var(--ok);     border-left-color:var(--ok); }      /* confirmée      */
.cal .ev.app{  background:var(--att-bg);    color:var(--att);    border-left-color:var(--att); }     /* en attente     */
.cal .ev.ann{  background:var(--ko-bg);     color:var(--ko);     border-left-color:var(--ko);
               text-decoration:line-through; }                                                        /* annulée        */
.cal .ev.term{ background:var(--info-bg);   color:var(--info);   border-left-color:var(--info); }     /* terminée       */

/* ---------------------------------------------------------------------
   12 bis. GANTT .gantt
   Vue chronologique en barres, demandée par le client en complément du
   calendrier mensuel : elle montre d'un coup d'œil les sessions qui
   s'étalent sur plusieurs jours, la charge d'un formateur et les
   chevauchements.

   Positionnement en POURCENTAGE de la période affichée (et non en pixels
   par jour) : la grille reste juste quelle que soit la largeur de l'écran
   et le nombre de jours. Le nombre de jours est passé par --jours.

   Structure attendue :
     .gantt-enveloppe > .gantt[style="--jours:N"]
        > .gantt-entete  ( .gantt-coin + .gantt-jours > span )
        > .gantt-corps   ( .gantt-groupe? + .gantt-ligne
                             ( .gantt-lib + .gantt-piste > .gantt-barre ) )
   --------------------------------------------------------------------- */
/* ⚠⚠⚠ LA BANDE DE DATES ÉTAIT DÉCLARÉE COLLANTE ET NE COLLAIT À RIEN (23/09).

   `.gantt-entete` porte `position:sticky; top:0` depuis le 03/09. Elle n'a
   jamais tenu, et voici pourquoi — c'est une règle de CSS qu'on ne voit pas
   venir : `overflow-x:auto` fait de cet élément un CONTENEUR DE DÉFILEMENT
   SUR LES DEUX AXES (un `overflow` `visible` en face d'un `auto` calcule
   `auto`). Le `sticky` s'ancre donc sur CETTE enveloppe, et non sur la page.
   Or elle n'avait AUCUNE hauteur bornée : son scrollport épousait son contenu,
   ne défilait jamais verticalement, et le `sticky` n'avait pas de quoi
   s'accrocher. La bande partait avec la page, en silence.

   ⚠⚠ C'est un mécanisme LIVRÉ ET JAMAIS AMORCÉ, comme les deux fonctions de
      retrait de POPPY et `billing_pilote_actif()` — la troisième forme en deux
      jours, et la première en CSS. Rien ne pouvait le dire : la déclaration
      est là, elle est juste, et aucune batterie ne rend une page.

   ⚠ CE N'EST PAS LE MÊME DÉFAUT QUE LA BARRE DES QUIZ (12/09), et le remède
     diffère. Là-bas `top:0` collait au haut de la FENÊTRE, où `.topbar` est
     déjà : il fallait recaler sur `var(--h-topbar)`. Ici l'ancre n'est pas la
     fenêtre mais l'enveloppe ; ce qui manque n'est pas un décalage, c'est une
     HAUTEUR. Recaler sur `--h-topbar` ici n'aurait rien changé du tout.

   ⚠ La hauteur est bornée par UN seul jeton, `--gantt-hors-frise`, déclaré
     juste au-dessus : c'est la place que prennent les bandes AU-DESSUS de la
     frise. Deux valeurs écrites séparément divergent — huitième fois dans ce
     projet. Si l'on ajoute ou retire une bande, c'est cette ligne-là qu'on
     change, et elle seule.
   ⚠ `100dvh` et non `100vh` : sur un téléphone, la barre d'adresse se replie
     et `vh` garde la hauteur d'avant — la frise déborderait sous l'écran. */
.gantt-enveloppe{
  --gantt-hors-frise:210px;
  overflow-x:auto; background:var(--carte);
  max-height:calc(100dvh - var(--h-topbar) - var(--gantt-hors-frise));
  border:1px solid var(--line); border-radius:var(--r);
}
.gantt{ --lib:240px; --jours:31; min-width:940px; font-size:12.5px; }

/* -- en-tête : libellés de jours -- */
.gantt-entete{
  display:grid; grid-template-columns:var(--lib) 1fr;
  position:sticky; top:0; z-index:2;
  background:var(--carte); border-bottom:1px solid var(--line);
}
.gantt-coin{
  padding:8px 12px; font-weight:700; color:var(--muted);
  font-size:11px; text-transform:uppercase; letter-spacing:.4px;
  border-right:1px solid var(--line);
}
/* ⚠⚠ L'EN-TÊTE A DEUX ÉTAGES DEPUIS LE 03/09 : la bande des mois au-dessus
   des jours. Sur deux mois, la frise alignait « 1 2 3 … 30 1 2 3 … 31 » et
   RIEN ne disait où septembre finissait : on lisait « 30 » puis « 1 » et il
   fallait le déduire. */
.gantt-colonnes{ display:flex; flex-direction:column; min-width:0; }
/* ⚠⚠ LA BANDE DIT LA SEMAINE ET LE MOIS (12/09) : « S38 sept. ». Elle ne
   disait que le mois, et « on est en quelle semaine ? » est la question qu'on
   pose au téléphone pour caler une formation. La frontière de mois, elle, est
   portée par les traits qui DESCENDENT DANS LA PISTE depuis le 03/09 — c'est
   eux qui la marquent, pas cette bande. */
.gantt-semaines{
  display:grid; grid-template-columns:repeat(var(--jours), 1fr);
  border-bottom:1px solid var(--line);
}
.gantt-semaines span{
  padding:5px 8px; font-size:11px; font-weight:700; letter-spacing:.04em;
  color:var(--poppy-dark);
  border-left:1px solid var(--line);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
/* ⚠ La première semaine n'a pas de filet à gauche : il tomberait sur la
   bordure de la colonne des libellés et ferait un trait double. */
.gantt-semaines span:first-child{ border-left:0; }

/* ⚠ Le compte vit SOUS le nom du regroupement, pas à côté : la case d'angle
   fait 220 px, deux textes côte à côte y seraient tronqués tous les deux. */
.gantt-coin-n{
  display:block; margin-top:2px; font-weight:600; font-size:10.5px;
  text-transform:none; letter-spacing:0; color:var(--muted);
}

.gantt-jours{ display:grid; grid-template-columns:repeat(var(--jours), 1fr); }
.gantt-jours span{
  text-align:center; padding:6px 0; font-size:10.5px; color:var(--muted);
  border-left:1px solid var(--line); line-height:1.25;
}
.gantt-jours span b{ display:block; font-size:11.5px; color:var(--ink); font-weight:700; }
.gantt-jours span.we{ background:var(--soft2); }
.gantt-jours span.auj{ background:var(--soft); color:var(--poppy-dark); }
.gantt-jours span.auj b{ color:var(--poppy); }

/* -- lignes -- */
.gantt-groupe{
  padding:7px 12px; background:var(--soft2); border-bottom:1px solid var(--line);
  font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:.4px;
  color:var(--poppy-dark); position:sticky; left:0;
}
.gantt-ligne{
  display:grid; grid-template-columns:var(--lib) 1fr;
  border-bottom:1px solid var(--line); min-height:38px;
}
.gantt-ligne:last-child{ border-bottom:0; }
/*
 * ⚠⚠ LE SURVOL D'UNE LIGNE N'EST PLUS ROUGE. Il valait `rgba(200,16,46,.03)`,
 * soit le coquelicot de la charte à 3 %. Deux raisons de le changer :
 *
 *   · dans cette application, LE ROUGE DIT L'ACTION — c'est la règle posée le
 *     16/08 après les sept badges rouges de la GED. Le passage du curseur
 *     n'est pas une action ;
 *   · à 3 % d'opacité, il ne se voyait de toute façon presque pas.
 *
 * ⚠ Il reste TRANSLUCIDE, et ce n'est pas un détail : un aplat opaque
 *   masquerait la grille des jours et les week-ends que la piste dessine en
 *   fond, c'est-à-dire exactement les repères qu'on cherche quand on survole
 *   une ligne pour lire une date.
 */
.gantt-ligne:hover .gantt-piste{ background-color:rgba(43,43,43,.045); }
.gantt-lib{
  padding:8px 12px; border-right:1px solid var(--line);
  display:flex; align-items:center; gap:8px; min-width:0;
  position:sticky; left:0; background:var(--carte); z-index:1;
}
.gantt-lib .n{ font-weight:600; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.gantt-lib .c{ margin-left:auto; color:var(--muted); font-size:11px; white-space:nowrap; }

/* ⚠⚠ UNE LIGNE LIBRE SE DISTINGUE SANS DISPARAÎTRE. Elle est là pour être
   VUE — c'est la réponse à « qui peut prendre cette session ? ». La griser
   jusqu'à l'effacement retirerait l'information qu'on vient chercher ; la
   laisser identique aux autres ferait croire à un chargement inachevé.
   Un fond très léger et un libellé en gris : présente, et manifestement vide. */
.gantt-ligne.libre .gantt-lib{ background:var(--soft2); }
.gantt-ligne.libre .gantt-lib .n{ font-weight:500; color:var(--muted); }
.gantt-ligne.libre .gantt-lib .c{ font-style:italic; }

/* -- piste : grille de jours en fond -- */
/*
 * ⚠⚠ LA GRILLE DES JOURS DESSINAIT 3 600 TRAITS AU LIEU DE 60.
 *
 * L'ancienne règle posait `background-size: calc(100%/var(--jours))`, puis un
 * dégradé dont la période valait, ELLE AUSSI, `calc(100%/var(--jours))`. Or
 * les pourcentages d'un dégradé se calculent sur la TUILE définie par
 * `background-size`, pas sur l'élément : la période valait donc
 * `100% / jours²`. Sur deux mois — 60 jours — cela fait un trait tous les
 * 0,25 px sur une piste de 900 px. Ce n'était plus une grille de jours, c'était
 * un aplat gris uniforme derrière les barres.
 *
 * ⚠ La correction tient en un mot : la période du dégradé est `100%` DE LA
 *   TUILE, et c'est la tuile qui vaut un jour. Un trait par jour.
 *
 * ⚠⚠ ET LES WEEK-ENDS ENTRENT DANS LA PISTE. Ils étaient grisés dans l'en-tête
 *    et nulle part ailleurs : sur deux mois, il fallait compter les colonnes
 *    pour savoir si une session mordait sur un samedi. Le second calque a une
 *    période de SEPT jours et se décale de `--we`, que la vue calcule — le CSS
 *    ne sait pas quel jour de la semaine ouvre la période.
 *
 * ⚠ Il est posé AVANT la grille dans l'ordre de peinture (donc écrit après
 *   dans la liste, les calques se superposant du premier au dernier) pour que
 *   les filets des jours restent visibles PAR-DESSUS l'aplat du week-end.
 */
.gantt-piste{
  position:relative; padding:7px 0;
  background-image:
    /* les filets de jours, au-dessus */
    repeating-linear-gradient(to right, var(--line) 0 1px, transparent 1px 100%),
    /* l'aplat des week-ends, en dessous */
    /* ⚠ Le week-end est au DÉBUT de la tuile, pas à la fin : `--we` est le
       décalage du premier SAMEDI, et la tuile commence donc dessus. Écrit
       d'abord à la fin — cinq jours d'écart, et le vendredi grisé. Deux jours
       sur sept, soit 200 % / 7 de la tuile. */
    repeating-linear-gradient(to right,
      /* ⚠⚠ `--fond` ET NON `--soft2` : ce dernier est un aplat COQUELICOT très
         clair (#fdf6f5), et les week-ends ressortaient roses — un rouge de
         plus dans une grille où cette couleur est réservée à l'action et au
         repère du jour. Un samedi n'est pas une alerte. `--fond` est le gris
         de la page : il dit « ce n'est pas la surface de travail », ce qui est
         exactement ce qu'un week-end est. */
      var(--fond) 0 calc(200% / 7), transparent calc(200% / 7) 100%);
  background-size:
    calc(100% / var(--jours)) 100%,
    calc(700% / var(--jours)) 100%;
  background-position:
    0 0,
    calc(var(--we, 0) * 100% / var(--jours)) 0;
  background-repeat:repeat, repeat;
}

/* -- barres -- */
/* La position verticale (top) est posée par la vue : elle dépend de la
   sous-ligne attribuée à la barre, pour que deux sessions qui se chevauchent
   restent toutes les deux visibles. */
/*
 * ⚠ `height` EST ÉCRASÉE PAR LA VUE, qui la calcule au dixième de pixel selon
 *   le nombre de sous-lignes (deux sessions qui se chevauchent partagent la
 *   hauteur). La valeur ci-dessous n'est donc qu'un repli, pour une barre qui
 *   serait rendue sans style en ligne. La modifier ne change RIEN à l'écran :
 *   c'est `H_LIGNE` / `H_BARRE_MIN` dans `gantt.js` qu'il faut toucher.
 */
/*
 * ⚠⚠ APLAT, SANS AUCUN RELIEF — demandé le 03/09, et c'est cohérent avec le
 *    reste de l'application : aucune autre surface de cet écran n'est ombrée.
 *
 *    Les jalons portaient TROIS reliefs différents, tous en `box-shadow` :
 *      · une ombre portée sur toutes les barres ;
 *      · un anneau INTÉRIEUR sur les barres claires, pour leur donner un bord ;
 *      · un anneau JAUNE de 2 px sur les chevauchements.
 *    Trois usages de la même propriété, dont deux qui n'ombraient rien : ils
 *    dessinaient des bords. Un bord se dessine avec `border` — et le mélange
 *    interdisait de retirer l'ombre sans emporter le reste.
 *
 * ⚠ LE BORD EST INTÉRIEUR (`box-sizing:border-box`) : la géométrie des barres
 *   est calculée au centième de pour-cent, et un bord extérieur décalerait
 *   chaque jalon d'un pixel vers la droite.
 *
 * ⚠ Rayon ramené de 5 px à 2 px : sur un jalon d'un jour, large de vingt
 *   pixels, un rayon de 5 arrondissait la moitié de la forme — ce n'était plus
 *   un repère de date, c'était une pastille.
 */
.gantt-barre{
  position:absolute; box-sizing:border-box;
  height:22px; min-width:6px; border-radius:2px;
  display:flex; align-items:center; gap:5px; padding:0 7px;
  color:#fff; font-size:11px; font-weight:600; white-space:nowrap;
  overflow:hidden; text-overflow:ellipsis; cursor:pointer;
  border:1px solid transparent;
}
/* ⚠ BARRE CLAIRE : elle a besoin d'un bord. Depuis le 02/09 le planning porte
   le code couleur du client, fait de teintes pâles (#FFE699, #A6CAEC…). Sur le
   fond blanc de la frise, une barre pâle sans contour n'a plus de limites : on
   ne voit plus où commence ni où finit l'intervention. Le contour est intérieur
   pour ne pas décaler la géométrie des barres, qui est calculée au centième.
   ⚠ Placée AVANT .conflit : un chevauchement de formateur doit garder son
     liseré jaune, qui dit quelque chose de plus grave qu'un manque de contraste. */
/* ⚠ Un BORD, pas une ombre intérieure : c'est un bord qu'on voulait, et le
   dire ainsi permet de retirer les ombres sans l'emporter. */
.gantt-barre.claire{ border-color:rgba(43,43,43,.32); }
/* ⚠ Le survol éclaircit la teinte plutôt que d'ajouter une ombre : en aplat,
   la seule variable disponible est la couleur elle-même. */
.gantt-barre:hover{ filter:brightness(1.08); }
.gantt-barre:focus-visible{ outline:2px solid var(--ink); outline-offset:1px; }
/* Chevauchement de formateur : le liseré jaune reprend le code du calendrier. */
/* ⚠⚠ LE CHEVAUCHEMENT GARDE SON LISERÉ JAUNE — c'est le code du calendrier,
   et il dit quelque chose de plus grave qu'un manque de contraste. En aplat :
   `outline`, qui se dessine PAR-DESSUS sans entrer dans la géométrie ni
   remplacer le bord de la barre claire. */
.gantt-barre.conflit{ outline:2px solid #ffd23f; outline-offset:-2px; }
/* Session annulée : barrée et pâle, elle reste visible mais ne trompe pas. */
.gantt-barre.annulee{ opacity:.45; text-decoration:line-through; }

/* Barre trop étroite pour porter son texte (session d'une journée sur une
   frise de plusieurs mois) : l'étiquette est sortie et posée juste à côté. */
.gantt-barre.etroite{ padding:0; }
.gantt-etiquette{
  position:absolute; height:22px; display:flex; align-items:center;
  padding-left:6px; font-size:11px; color:var(--ink);
  white-space:nowrap; cursor:pointer; max-width:38%;
  overflow:hidden; text-overflow:ellipsis;
}
.gantt-etiquette:hover{ color:var(--poppy-dark); }

/* -- séparateur de mois --
   ⚠⚠ Il descend dans la PISTE et pas seulement dans l'en-tête : un repère qui
   n'existe qu'en haut oblige à remonter les yeux pour savoir dans quel mois
   tombe une barre — sur douze lignes, on le fait douze fois.
   ⚠ Plus marqué que le filet du jour (`--line`), moins que le repère
   d'aujourd'hui (`--poppy`) : trois niveaux de lecture, trois valeurs. */
.gantt-sep-mois{
  position:absolute; top:0; bottom:0; width:1px;
  background:var(--muted); opacity:.35; pointer-events:none;
}

/* -- repère du jour -- */
/* ⚠ Opacité retirée : en aplat, une couleur se choisit, elle ne se dilue pas.
   `--poppy-clair` est le rose de la charte — assez présent pour se repérer,
   assez discret pour ne pas se disputer avec les jalons. */
.gantt-auj{
  position:absolute; top:0; bottom:0; width:2px;
  background:var(--poppy-clair); pointer-events:none;
}

/* -- barre d'outils --------------------------------------------------------
   ⚠⚠ ELLE EMPLOYAIT `.actions-ligne`, QUI EST ALIGNÉE À DROITE. C'est la
   classe des boutons d'une CELLULE DE TABLEAU : employée pour une barre
   d'outils, elle poussait les huit contrôles contre le bord droit et laissait
   le tiers gauche du bandeau vide. Même famille que `.actions-ligne` posée sur
   un `<td>` le 17/08 — la bonne classe au mauvais endroit.

   ⚠ DEUX GROUPES depuis le 13/09 — « quand » et « sur combien de temps ». Ils
     en ont compté trois tant qu'il y avait de quoi restreindre ; le filet qui
     les sépare reste, parce que deux poignées collées se lisent comme une. */
/* ⚠⚠ LA BARRE EST UNE CARTE (27/09, maquette). Posée à nu sur le fond de la
     page, « Période » et « Durée d'affichage » flottaient entre le titre et les
     tuiles : rien ne disait que ces deux groupes vont ensemble et qu'ils
     commandent la frise. Le dessin les pose dans un cadre. */
.gantt-outils{
  display:flex; flex-wrap:wrap; align-items:flex-end; gap:10px 18px;
  background:var(--carte); border:1px solid var(--line); border-radius:var(--r);
  box-shadow:var(--ombre); padding:12px 16px;
}
/* ⚠⚠ ET LES DEUX FLÈCHES SE VOIENT. À 24 px de large avec un chevron de 11 px,
     elles se lisaient comme de la ponctuation — mesuré sur la capture : la
     maquette leur donne 38 px et un chevron deux fois plus gros. Ce sont les
     commandes qu'on emploie le plus sur cet écran. */
.gantt-outils [data-action="precedent"],
.gantt-outils [data-action="suivant"]{
  min-width:36px; justify-content:center; font-size:17px; line-height:1;
  padding:5px 8px;
}
.gantt-outils-groupe{
  display:flex; flex-direction:column; gap:4px;
  padding:0 0 0 14px; border-left:1px solid var(--line);
}
.gantt-outils-groupe:first-child{ padding-left:0; border-left:0; }
.gantt-outils-titre{
  font-size:10.5px; font-weight:700; letter-spacing:.09em;
  text-transform:uppercase; color:var(--muted);
}
.gantt-outils-champs{ display:flex; flex-wrap:wrap; align-items:center; gap:6px; }
/* ⚠⚠ LE LISERÉ « GROUPE ACTIF » EST PARTI LE 13/09, AVEC CE QU'IL MARQUAIT.
   Il a servi deux choses successivement : « ce groupe de filtres restreint la
   frise », puis « le bouton Réglages cache N réglages actifs ». Les filtres,
   puis le bouton, ont été retirés le même jour ; il ne restait plus rien qui
   puisse être actif, et une règle qui ne peint jamais se recopie un jour
   ailleurs en croyant qu'elle fait quelque chose.
   ⚠ Il a été retiré une PREMIÈRE fois avec les filtres, et remis aussitôt :
     un contrôle avait vu que le bouton le portait encore. C'est ce même
     contrôle qui a autorisé ce second retrait, en devenant vert sur un écran
     où plus aucune poignée n'est activable. */

/* ── LES CINQ AXES, EN ONGLETS AU-DESSUS DE LA FRISE (13/09) ───────────────
   ⚠⚠ `.tabs` EST LA FORME DU SOCLE, et on l'emprunte telle quelle : une
      sixième façon de dessiner un onglet ferait un sixième écran qui ne
      ressemble à aucun autre. Ce qui est ajouté ici tient en deux règles — la
      marge sous la barre, et le fait qu'elle ne colle pas au panneau qui la
      précède.
   ⚠ Rien sur les couleurs, rien sur l'état actif : `.tabs button.actif` du
     socle s'en charge, et c'est ce qui garantit que ces onglets se lisent
     comme ceux des Lieux ou des Paramètres. */
.gantt-axes{ margin:0 0 10px; }
/* ⚠ La période est le RÉSULTAT des flèches, pas une étiquette posée à côté :
   elle ne se sépare pas du groupe qui la fait bouger. */
.gantt-periode{ padding:0 4px; white-space:nowrap; font-variant-numeric:tabular-nums; }
/* ⚠ Les libellés des sélecteurs sont VISUELLEMENT cachés, pas absents : aucun
   des quatre n'avait de nom, et un lecteur d'écran annonçait « liste, 2 mois »
   sans dire de quoi. `display:none` les retirerait aussi de l'accessibilité. */
.gantt-vh{
  position:absolute; width:1px; height:1px; overflow:hidden;
  clip-path:inset(50%); white-space:nowrap;
}

/* -- un panneau qui laisse SORTIR ce qu'il contient ------------------------
   ⚠⚠ `.panel` porte `overflow:hidden` — c'est ce qui découpe ses enfants au
   rayon de ses coins, et il ne se retire pas : toutes les cartes de
   l'application en dépendent.

   Mais un panneau déroulant posé DANS une carte est découpé par elle : le
   panneau des filtres s'ouvrait sous le bloc et n'était pas visible. Rien à
   l'écran ne le disait — le bouton répondait, le panneau existait dans le
   document, il était simplement rogné. ⚠ Aucune batterie ne peut voir ça : il
   n'y a pas de moteur de rendu ici.

   ⚠ Le modificateur est BORNÉ à la carte qui en a besoin, et il ne coûte rien
     là où il s'applique : cette carte-ci n'a pas d'en-tête à découper. */
.panel.deborde{ overflow:visible; }

/* -- légende --
   ⚠⚠ ELLE DOIT MONTRER EXACTEMENT CE QUE LA FRISE DESSINE. Une heure après le
   passage des jalons en aplat, elle mentait encore sur deux repères : un
   anneau en `box-shadow` pour le chevauchement quand la barre porte un
   `outline`, et le coquelicot à 55 % pour « aujourd'hui » quand le vrai repère
   est passé en `--poppy-clair` plein. Une légende qui ne montre pas ce qu'elle
   annonce est pire qu'une légende absente : on cherche à l'écran une forme qui
   n'y est pas. D'où des CLASSES, qui suivent les règles des jalons, et non des
   styles recopiés à la main dans le JavaScript. */
/* ⚠⚠ ELLE VIT DANS L'ENVELOPPE DE LA FRISE. Elle flottait entre deux
   panneaux, sur le fond de la page, sans appartenir à rien — et occupait
   autant de hauteur que trois lignes d'intervenants. Elle explique la frise :
   elle vit avec elle, en tête, comme une légende de carte. */
/*
 * ┌──────────────────────────────────────────────────────────────────────────┐
 * │ ⚠⚠ LES ONGLETS ET LA LÉGENDE SONT SUR LA MÊME LIGNE — 25/09.             │
 * │                                                                          │
 * │ Ils étaient EMPILÉS : deux bandes pleine largeur au-dessus d'une frise   │
 * │ dont la première barre commençait déjà trop bas. Or ils ne se            │
 * │ contrarient pas — les onglets tiennent à gauche, la légende n'a besoin   │
 * │ que de son bout de droite. Réunis, ils rendent une bande entière à la    │
 * │ frise, qui est ce qu'on vient voir.                                      │
 * │                                                                          │
 * │ ⚠ Le filet court sous LES DEUX : c'est la barre qui le porte, pas les    │
 * │   onglets, sinon il s'arrêterait au milieu de la ligne.                  │
 * │ ⚠ `flex-wrap` : sous une certaine largeur la légende repasse dessous,    │
 * │   plutôt que d'écraser les six onglets, qui doivent rester cliquables.   │
 * └──────────────────────────────────────────────────────────────────────────┘
 */
.gantt-axes-barre{
  display:flex; align-items:flex-end; gap:16px; flex-wrap:wrap;
  border-bottom:1px solid var(--line); margin-bottom:14px;
}
/* ⚠⚠ `flex:1 1 auto` POUSSAIT LA LÉGENDE À LA LIGNE. Les onglets prenaient
   toute la largeur disponible, et la légende — poussée par `margin-left:auto`
   dans un conteneur qui ENROULE — passait dessous : deux lignes là où la
   maquette en met une, sur un écran où la frise commence déjà bas. Les onglets
   prennent la place de leurs mots, rien de plus. Même famille que le
   `flex-basis:auto` du 11/09, par l'autre bout. */
.gantt-axes-barre > .tabs{ border-bottom:none; margin-bottom:0; flex:0 1 auto; }
.gantt-axes-barre > .gantt-legende{
  margin-left:auto; border-bottom:none; background:transparent;
  flex-direction:row; align-items:center; gap:6px 16px; flex-wrap:wrap;
  padding:6px 12px 8px;
}
/* ⚠ Dans la barre, l'intitulé de chaque clé disparaît : la ligne se lit déjà
   comme une légende par sa position et ses pastilles, et le titre y prenait la
   place de deux entrées. Il reste quand la légende est seule. */
.gantt-axes-barre > .gantt-legende .gantt-legende-titre{ display:none; }

.gantt-legende{
  display:flex; flex-direction:column; gap:6px;
  padding:10px 12px; font-size:11.5px; color:var(--muted);
  /* ⚠ `--soft2` est le jeton d'aplat d'en-tête du projet. J'avais d'abord
     écrit `var(--surface-2, …)` — un jeton qui n'existe PAS dans cette
     feuille : il venait d'une autre page. Le repli l'aurait masqué, et la
     règle aurait marché par accident. Un jeton inventé ne casse rien et ne se
     voit pas — c'est le piège des classes inventées, appliqué aux variables. */
  border-bottom:1px solid var(--line); background:var(--soft2);
}
/* ⚠⚠ DEUX CLÉS, ET ELLES N'EXPLIQUENT PAS LA MÊME CHOSE : l'une dit ce qu'une
   COULEUR signifie, l'autre ce qu'une FORME signifie. Mêlées sur deux lignes
   pleine largeur, elles se lisaient comme une seule liste de dix items dont on
   ne voyait pas la logique. */
.gantt-legende-cle{ display:flex; flex-wrap:wrap; align-items:center; gap:6px 14px; }
.gantt-legende-titre{
  font-size:10.5px; font-weight:700; letter-spacing:.08em;
  text-transform:uppercase; color:var(--muted);
  min-width:120px;
}
/* ⚠ Le nombre d'écartés est DIT, comme pour le taux de remplissage (§22) :
   « 2 types sur 7 » se lit autrement que « 2 types ». Sans cette mention, on
   croirait l'organisme réduit à deux prestations. */
.gantt-legende-note{ font-style:italic; }
.gantt-legende span{ display:flex; align-items:center; gap:6px; }
/* ⚠ UNE SEULE RÈGLE POUR CE SÉLECTEUR. Il y en a eu deux quelques minutes —
   la mienne au-dessus de celle d'origine — et `border-radius` vivait dans la
   seconde : le contrôle qui vérifie que la pastille a la forme du jalon lisait
   la PREMIÈRE et concluait qu'elle ne l'avait plus. Deux règles de même
   sélecteur, c'est une collision de cascade en attente. */
.gantt-legende i{
  width:12px; height:12px; display:inline-block;
  border-radius:2px; border:1px solid rgba(43,43,43,.28);
}
/* ⚠⚠ LES QUATRE RÈGLES `.l-*` ONT ÉTÉ RETIRÉES LE 03/09 AVEC LA CLÉ QU'ELLES
   PEIGNAIENT (le chevauchement, aujourd'hui, la barre pleine, les repères
   reliés). Les garder aurait fait quatre sélecteurs que plus aucun marquage
   n'emploie — et une règle morte finit par être « corrigée » par quelqu'un qui
   la croit active, ou recopiée ailleurs. C'est la même raison qui fait
   contrôler les classes CSS inventées depuis le 16/08, dans l'autre sens.
   ⚠ Les SIGNES eux-mêmes n'ont pas bougé : `.gantt-barre.conflit`,
   `.gantt-auj` et `.gantt-seance` dessinent toujours. C'est leur EXPLICATION
   qui est partie, pas eux. */

@media (max-width:900px){
  .gantt{ --lib:150px; }
}

/* ---------------------------------------------------------------------
   13. MODALES
   --------------------------------------------------------------------- */
.modal-fond{
  position:fixed; inset:0; z-index:80;
  background:rgba(43,43,43,.45);
  display:flex; align-items:flex-start; justify-content:center;
  padding:40px 16px; overflow-y:auto;
}
/*
 * ⚠⚠ LA TÊTE ET LE PIED RESTENT À L'ÉCRAN, LE CORPS SCROLLE.
 *
 * Avant, c'est `.modal-fond` qui scrollait : sur un formulaire long — la
 * nouvelle demande en compte quinze champs — le titre de la modale ET le
 * bouton « Créer » sortaient de l'écran. On remplissait, et il fallait
 * remonter la page pour trouver le bouton. Sur un portable 768 px, l'action
 * principale n'était jamais visible en même temps que la fin du formulaire.
 *
 * ⚠ Purement additif : une modale plus courte que le plafond ne bouge PAS
 *   d'un pixel. `min-height:0` sur le corps n'est pas décoratif — sans lui,
 *   un enfant flex refuse de rétrécir sous sa hauteur de contenu et
 *   `overflow` ne se déclenche jamais.
 *
 * ⚠ 80 px = les 40 px de marge haute et basse de `.modal-fond`. Les changer
 *   ici sans les changer là-bas donnerait une modale qui déborde de son fond.
 */
.modal{
  width:100%; max-width:560px; background:var(--carte);
  border-radius:var(--r); box-shadow:var(--ombre-haut);
  border-top:5px solid var(--poppy); overflow:hidden;
  animation:cc-monte .14s ease-out;
  display:flex; flex-direction:column; max-height:calc(100vh - 80px);
}
.modal>header, .modal>.pied{ flex:none; }
.modal>.corps{ min-height:0; overflow-y:auto; }
.modal.large{ max-width:860px; }
.modal.etroite{ max-width:400px; }
@keyframes cc-monte{ from{ opacity:0; transform:translateY(-8px); } to{ opacity:1; transform:none; } }

.modal>header{
  display:flex; align-items:center; gap:10px;
  padding:12px 16px; border-bottom:1px solid var(--line);
}
.modal>header h3{ font-size:15px; color:var(--poppy-dark); margin:0; }
.modal>header .fermer{
  margin-left:auto; border:none; background:none; cursor:pointer;
  font-size:20px; line-height:1; color:var(--muted); padding:2px 6px; border-radius:4px;
}
.modal>header .fermer:hover{ background:var(--soft2); color:var(--poppy); }
.modal>.corps{ padding:16px; }
.modal>.pied{
  display:flex; align-items:center; gap:8px; justify-content:flex-end;
  padding:12px 16px; border-top:1px solid var(--line); background:var(--soft2);
}
.modal>.pied .gauche{ margin-right:auto; }

/*
 * ⚠ UN GROUPEMENT NE DOIT PAS COÛTER LA HAUTEUR QU'IL FAIT GAGNER EN LECTURE.
 *
 * Les `<fieldset>` de la page portent 12/14 px de retrait et 14 px de marge :
 * quatre groupes dans une modale ajoutaient ~200 px, c'est-à-dire le défaut
 * qu'on venait corriger. Serrés dans une modale, ils gardent leur fonction —
 * dire où s'arrête une idée — sans rallonger le formulaire.
 *
 * ⚠ Le dernier ne porte pas de marge basse : `.corps` a déjà son retrait, et
 *   les deux s'additionnaient en un blanc de 30 px au-dessus des boutons.
 */
.modal fieldset{ padding:10px 12px; margin:0 0 10px; }
.modal fieldset:last-of-type{ margin-bottom:0; }

/* ---------------------------------------------------------------------
   14. TOASTS
   --------------------------------------------------------------------- */
.toasts{
  position:fixed; right:16px; bottom:16px; z-index:120;
  display:flex; flex-direction:column; gap:8px; align-items:flex-end;
  pointer-events:none;
}
/* ⚠⚠ UN TOAST NE PREND PLUS LE CLIC (recette du 28/09). Il était en
      `pointer-events:auto` : pendant les huit secondes qui suivent la
      connexion, « Votre compte n'a pas de second facteur… » recouvrait
      « Enregistrer » en bas à droite des grandes fenêtres, et le clic tombait
      sur le toast (vérifié par elementFromPoint) — rien ne se passait. Même
      chose avec un toast d'erreur posé sur « Ajouter la ligne ».
   ⚠ Rien ne s'y clique : `CC.toast` n'y écrit que du TEXTE (`textContent`),
     sans bouton ni lien, et ne se ferme qu'avec le temps. Le conteneur
     `.toasts` était déjà transparent au clic ; le toast l'est désormais aussi,
     et le clic atteint ce qu'il recouvre.
   ⚠ Mesuré par un VRAI clic, dans chromium : tools/tests_recette_ecrans_communs_c.php. */
.toast{
  pointer-events:none; min-width:220px; max-width:380px;
  background:var(--ink); color:#fff; border-radius:var(--r-s);
  padding:10px 14px; font-size:13px; font-weight:600;
  box-shadow:var(--ombre-haut); border-left:4px solid var(--muted);
  animation:cc-entre .16s ease-out;
}
.toast.ok{ border-left-color:#4ad295; }
.toast.erreur{ border-left-color:var(--poppy); }
.toast.info{ border-left-color:#7fb2e0; }
.toast.sortie{ opacity:0; transform:translateY(6px); transition:opacity .2s, transform .2s; }
@keyframes cc-entre{ from{ opacity:0; transform:translateY(8px); } to{ opacity:1; transform:none; } }

/* ---------------------------------------------------------------------
   15. GRAPHIQUES (aucune bibliothèque : CSS + SVG inline)
   --------------------------------------------------------------------- */
.bars{ display:flex; align-items:flex-end; gap:10px; height:170px; padding-top:18px; }
/* ⚠⚠ LA BARRE FAIT LES DEUX TIERS DE SA COLONNE, ET ELLE EST EN DÉGRADÉ
     (27/09, maquette `docs/maquette/views/stats.js`). Elle occupait TOUTE la
     largeur de sa colonne et n'était qu'un aplat : quatre pavés rouges collés
     par un écart de 10 px, là où le dessin validé pose des fuseaux séparés.
   ⚠ La couleur, elle, a été VÉRIFIÉE AVANT D'ÊTRE ACCUSÉE : j'ai d'abord pris
     ces barres rouges pour la faute du 16/08 (« une couleur encode l'urgence,
     jamais le type »). La maquette dit `linear-gradient(180deg,var(--poppy),
     #e8607a)` — c'est bien le dessin validé, et un chiffre d'affaires n'est
     pas une alerte parce qu'il est rouge s'il l'est PARTOUT. Mesurer deux fois
     avant d'accuser, y compris son propre socle.
   ⚠ Le dégradé est en CSS, jamais en ligne : `barChart` pose un `background:`
     dans l'attribut `style` quand un appelant donne une couleur, et cet
     attribut l'emporte — une barre explicitement colorée le reste. */
.bars .barre-col{
  flex:1; display:flex; flex-direction:column; justify-content:flex-end;
  align-items:center; height:100%; min-width:0;
}
.bars .barre-col .tige{
  position:relative; width:66%; border-radius:6px 6px 0 0; min-height:2px;
  background:linear-gradient(180deg, var(--poppy), #e8607a);
}
.bars .barre-col .tige:hover{ background:var(--poppy-dark); }
/* ⚠ Le libellé et la valeur se cadrent sur la COLONNE, pas sur le fuseau :
     posés sur une tige de 66 %, « 19 960,00 € » déborderait des deux côtés et
     se chevaucherait d'une barre à l'autre. */
.bars .barre-col .lib{ width:100%; }
.bars .barre-col .val{
  position:absolute; top:-17px; left:50%; transform:translateX(-50%); text-align:center;
  font-size:10.5px; font-weight:700; color:var(--poppy-dark); white-space:nowrap;
}
.bars .barre-col .lib{
  text-align:center; font-size:10.5px; color:var(--muted); margin-top:5px;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}

/* ⚠⚠ LA LÉGENDE EST À CÔTÉ DU BEIGNET, PAS DESSOUS (27/09, maquette). Elle
     passait à la ligne dès que la carte descendait sous ~360 px — et c'est le
     cas des trois graphiques de l'accueil, posés côte à côte. Le beignet se
     retrouvait seul sur une ligne et sa légende sur la suivante : deux fois la
     hauteur, pour la même information.
   ⚠⚠ Et la cause est celle de la journée : **`flex-wrap` enroule AVANT de
     rétrécir**. `min-width:0` sur la légende ne sert à rien tant que
     l'enveloppe a le droit d'enrouler. D'où `nowrap` au-dessus de 700 px, où
     la place existe ; en dessous, l'empilement reste juste. */
.donut{ display:flex; align-items:center; gap:16px; flex-wrap:wrap; }
@media (min-width:700px){ .donut{ flex-wrap:nowrap; } }
.donut svg{ flex:0 0 auto; }
.donut .legende{ flex:1 1 0; min-width:0; }
.donut .centre-val{ font-size:20px; font-weight:800; fill:var(--ink); }
/* ⚠ Le mot du centre n'est plus en capitales : « SESSIONS » sous « 19 » criait
     l'unité plus fort que le nombre. Le dessin l'écrit en minuscules. */
.donut .centre-lib{ font-size:10px; fill:var(--muted); letter-spacing:.2px; }

.legende{ display:flex; flex-direction:column; gap:5px; font-size:12.5px; min-width:0; }
.legende .item{ display:flex; align-items:center; gap:7px; }
.legende .item .pastille{ width:10px; height:10px; border-radius:2px; flex:0 0 auto; }
/* ⚠⚠ UN LIBELLÉ DE LÉGENDE TRONQUÉ NE DIT PLUS CE QU'IL COMPTE. « JP - Journée
     Péd… » et « Conférence, col… » : on lit le code et on perd le nom, c'est-
     à-dire l'inverse de ce qu'une légende sert à faire. Le dessin les écrit en
     entier, sur deux lignes s'il le faut — une légende grandit en hauteur, ce
     qui ne coûte rien, là où une troncature coûte le sens.
   ⚠ Même correction que les thématiques d'un intervenant, le 27/09 au matin :
     on laisse le texte passer à la ligne plutôt que de le couper. */
.legende .item{ align-items:flex-start; }
.legende .item .lib{ min-width:0; line-height:1.35; }
.legende .item .pastille{ margin-top:3px; }
.legende .item .n{ margin-left:auto; font-weight:700; padding-left:10px; }

/* ---------------------------------------------------------------------
   16. ÉTATS
   --------------------------------------------------------------------- */
.vide{
  text-align:center; padding:34px 18px; color:var(--muted);
}
.vide .ic{ font-size:30px; opacity:.35; display:block; margin-bottom:8px; }
.vide .t{ font-weight:700; color:var(--ink); font-size:14px; margin-bottom:3px; }
.vide .m{ font-size:13px; }
.vide .btn{ margin-top:14px; }

.chargement{ display:flex; align-items:center; justify-content:center; gap:10px; padding:38px; color:var(--muted); font-size:13px; }
.spin{
  width:18px; height:18px; border-radius:50%;
  border:2.5px solid var(--line); border-top-color:var(--poppy);
  animation:cc-tourne .7s linear infinite;
}
@keyframes cc-tourne{ to{ transform:rotate(360deg); } }

/* Squelette de chargement (listes) */
.squelette{ background:linear-gradient(90deg,#eeecea 25%,#f7f5f4 37%,#eeecea 63%); background-size:400% 100%;
            animation:cc-brille 1.2s linear infinite; border-radius:4px; height:12px; }
@keyframes cc-brille{ from{ background-position:100% 0; } to{ background-position:0 0; } }

/* ---------------------------------------------------------------------
   16 bis. PROGRESSION (barre & anneau)
   Repris tel quel de la maquette validée par le client (docs/maquette/app.css).
   Utilisés par la complétude Qualiopi (anneau) et les répartitions (barre) :
   ne pas les supprimer, les vues portées depuis la maquette en dépendent.
     <div class="prog"><i style="width:62%"></i></div>
     <div class="ring" style="--p:62"><span>62%</span></div>
   --------------------------------------------------------------------- */
.prog{ height:9px; border-radius:6px; background:#eee; overflow:hidden; }
.prog>i{ display:block; height:100%; background:var(--poppy); }

.ring{
  --p:0;
  width:54px; height:54px; border-radius:50%;
  background:conic-gradient(var(--poppy) calc(var(--p)*1%), #eee 0);
  display:flex; align-items:center; justify-content:center;
  font-size:12px; font-weight:800;
}
.ring span{
  background:#fff; width:40px; height:40px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
}

/* ---------------------------------------------------------------------
   17. IMPRESSION
   --------------------------------------------------------------------- */
/* ═══════════════════════════════════════════════════════════════════════════
   IMPRIMER UNE FENÊTRE MODALE — et elle seule (dev B, 11/09).

   ⚠⚠ La feuille d'impression masque la barre et le menu ; elle ne savait rien
   d'une fenêtre modale, qui n'existait pas quand elle a été écrite. Imprimer
   un aperçu de quiz sortait donc l'écran de SAISIE — ses champs vides, sa
   colonne de réglages — avec l'aperçu quelque part au milieu.

   ⚠ La classe est posée par le script le temps de l'impression, et retirée
     après : un sélecteur `:has()` aurait fait imprimer les deux sur un
     navigateur qui ne le connaît pas, c'est-à-dire le défaut qu'on corrige,
     sans que rien ne le dise.
   ⚠ La fenêtre reprend le flux du document (`position:static`) : en
     `position:fixed`, elle n'imprime que ce qui tenait dans la hauteur de
     l'écran — le reste est simplement absent du papier.
   ═══════════════════════════════════════════════════════════════════════════ */
@media print{
  body.pq-impression main#app{ display:none !important; }
  body.pq-impression .modal-fond{
    position:static; background:none; padding:0; display:block; overflow:visible;
  }
  body.pq-impression .modal{
    position:static; max-width:none; max-height:none; width:auto;
    box-shadow:none; border:0; border-radius:0;
  }
  body.pq-impression .modal > header,
  body.pq-impression .modal > .pied{ display:none !important; }
  body.pq-impression .modal .corps{ max-height:none; overflow:visible; padding:0; }
}

@media print{
  .topbar,.sidebar,.toasts,.page-tete .actions,.btn{ display:none !important; }
  body{ background:#fff; }
  body>.app{ display:block; padding-top:0; }
  main#app{ padding:0; }
  .panel{ box-shadow:none; break-inside:avoid; }
}

/* ---------------------------------------------------------------------
   18. SCHÉMA FONCTIONNEL (écran « À propos »)
   Le SVG a un repère fixe de 900 de large et s'adapte en hauteur. En
   dessous de ~740 px il devient illisible : on le fait défiler dans son
   conteneur plutôt que de le comprimer — un schéma tassé ne se lit pas,
   et surtout la PAGE ne doit jamais défiler horizontalement.
   --------------------------------------------------------------------- */
.schema-scroll{ overflow-x:auto; -webkit-overflow-scrolling:touch; }
.schema{ display:block; width:100%; min-width:740px; height:auto; }
.schema text{ font-family:inherit; }

@media print{
  .schema-scroll{ overflow:visible; }
  .schema{ min-width:0; }
}

/* ---------------------------------------------------------------------
   18 bis. FICHE INTERVENANT — identité étroite, tableau large

   L'identité est une liste courte de largeur à peu près fixe ; le tableau
   des pièces porte quatre colonnes dont deux boutons. À parts égales, les
   boutons sortaient du cadre. La géométrie était posée en style en ligne
   dans la vue : elle ne pouvait donc pas être annulée sur petit écran, où
   340 px de colonne fixe ne laissent plus rien au tableau.
   --------------------------------------------------------------------- */
.grille.grille-fiche{ grid-template-columns:340px minmax(0,1fr); }
@media (max-width:900px){
  .grille.grille-fiche{ grid-template-columns:1fr; }
}

/* ---------------------------------------------------------------------
   19. BARRE DU HAUT SUR PETIT ÉCRAN

   La barre porte : marque, recherche, identité, initiales, « À propos »,
   « Déconnexion ». La recherche disparaît déjà sous 900 px, mais l'ajout
   du lien « À propos » a fait déborder le reste sous ~600 px : le nom de
   l'utilisateur passait SOUS la marque et « Déconnexion » sortait de
   l'écran. On retire donc, par ordre de ce dont on peut le mieux se
   passer — l'identité écrite (l'initiale reste, et le nom complet est
   dans « Mon compte »), puis la signature typographique (le logo reste).
   Les deux BOUTONS ne sont jamais masqués : se déconnecter doit rester
   possible en une fois, sur n'importe quel écran.
   --------------------------------------------------------------------- */
@media (max-width:640px){
  .topbar{ gap:10px; padding:0 10px; }
  .topbar .moi{ gap:8px; }
  .topbar .moi .qui{ display:none; }

  /* Qui cède la place à qui : la marque se tronque, les boutons JAMAIS.
     Sans min-width:0 + overflow, un texte en white-space:nowrap refuse de
     rétrécir sous sa largeur naturelle et pousse les boutons hors écran —
     sans aucune barre de défilement pour les rattraper. */
  .topbar .marque{ min-width:0; overflow:hidden; }
  .topbar .marque .nom{ overflow:hidden; text-overflow:ellipsis; }
  .topbar .moi{ flex:0 0 auto; margin-left:auto; }
}
@media (max-width:520px){
  .topbar .marque .nom{ display:none; }
}

/* ---------------------------------------------------------------------------
   APERÇU D'UN DOCUMENT PRODUIT (vue documents)

   Le fragment inséré vient d'un gabarit du dépôt : ses balises sont les nôtres
   (h1 h2 p table ul strong em hr), pas celles du reste de l'application. On les
   style DANS .apercu-doc uniquement — sans ce cloisonnement, une règle sur
   « table » repeindrait tous les tableaux de l'ERP.

   Le rendu vise le papier : fond blanc, texte noir, marges généreuses. C'est ce
   que l'utilisateur va imprimer ou retrouver dans le fichier ODT ; un aperçu
   aux couleurs de l'application mentirait sur le résultat.
   --------------------------------------------------------------------------- */
.apercu-doc{
  /* ⚠⚠ `--line` ET PAS UN JETON INVENTÉ AVEC UN REPLI. Cette ligne portait
     `var(--bord, #d7dbe3)` : le jeton n'existe NULLE PART dans cette feuille,
     et le repli le masquait depuis le 16/08 — l'aperçu ne prenait donc jamais
     la couleur de bordure de l'application, sans que rien ne le dise. C'est le
     piège du 03/09, trouvé le 11/09 par le contrôle 20 qui vient d'être écrit
     pour ça. **Un repli qui cache une invention est pire que l'invention.** */
  background:#fff; color:#111; border:1px solid var(--line); border-radius:6px;
  padding:28px 32px; max-height:60vh; overflow:auto;
  font-size:14px; line-height:1.55;
}
.apercu-doc h1{ font-size:19px; margin:0 0 4px; color:#111; }
.apercu-doc h2{ font-size:15px; margin:20px 0 6px; color:#111;
                border-bottom:1px solid #e3e6ec; padding-bottom:3px; }
.apercu-doc p{ margin:0 0 8px; }
.apercu-doc hr{ border:0; border-top:1px solid #cfd4dd; margin:16px 0; }
.apercu-doc ul{ margin:0 0 8px 18px; }
.apercu-doc table{ width:100%; border-collapse:collapse; margin:8px 0 14px; }
.apercu-doc th, .apercu-doc td{
  border:1px solid #c9ced8; padding:6px 9px; text-align:left; vertical-align:top;
}
.apercu-doc th{ background:#f2f4f8; font-weight:600; }
/* Une cellule de signature vide doit garder sa hauteur : sans cela le tableau
   d'émargement s'aplatit et devient insignable une fois imprimé. */
.apercu-doc td:empty{ height:26px; }

/* ---------------------------------------------------------------------------
   … ET SON EN-TÊTE, SON PIED, SON LOGO.

   ⚠⚠ AJOUT PUREMENT ADDITIF, ANNONCÉ À L'AUTRE DÉVELOPPEUR. Toutes les règles
   sont cloisonnées dans `.apercu-doc` : aucune ne peut atteindre l'ERP.

   POURQUOI ELLES SONT ICI ET PAS DANS LE GABARIT. Le squelette `_page.html`
   porte sa propre feuille de style, dans un `<style>` — et `style-src 'self'`
   la bloque : l'aperçu la retire avant de l'insérer. Sans ces règles-ci, le
   logo sort à sa taille naturelle (il fait 120 px de côté, il en occuperait le
   double du texte), l'en-tête s'empile au lieu de se ranger en deux colonnes,
   et le pied de page se confond avec la dernière ligne du document.

   ⚠ Elles RECOPIENT volontairement quelques valeurs de `modeles/_page.html`
   (56 px de haut pour le logo, le filet coquelicot). C'est la seule recopie
   admise du projet, et elle est bornée à l'aperçu : la vérité reste le
   gabarit, qui décide de ce qui est imprimé et de ce qui part dans l'ODT.
   Un écart de quelques pixels entre l'aperçu et le papier est sans
   conséquence ; l'absence d'en-tête, elle, en avait une.
   --------------------------------------------------------------------------- */
.apercu-doc .entete{
  display:flex; justify-content:space-between; align-items:flex-start;
  gap:20px; border-bottom:2px solid var(--poppy,#C8102E); padding-bottom:10px;
  margin-bottom:16px;
}
.apercu-doc .logo{ max-height:56px; max-width:260px; display:block; margin-bottom:6px; }
.apercu-doc .emetteur{ font-weight:800; font-size:15px; color:var(--poppy,#C8102E);
                       letter-spacing:.2px; }
.apercu-doc .mentions{ color:#6b7280; font-size:11px; line-height:1.5; margin-top:3px; }
.apercu-doc .reperes{ text-align:right; font-size:11px; color:#6b7280; white-space:nowrap; }
.apercu-doc .reperes b{ display:block; color:#1c1f2b; font-size:13px; margin-bottom:2px; }
.apercu-doc .pied{
  margin-top:22px; padding-top:8px; border-top:1px solid #cfd4dd;
  font-size:10.5px; color:#6b7280; text-align:center;
}
/* ⚠ Le gabarit centre sa page dans une largeur de papier ; dans une fenêtre de
   l'application, cette contrainte ferait une colonne étroite au milieu du
   vide. On la neutralise, et elle seule. */
.apercu-doc .page{ max-width:none; margin:0; padding:0; box-shadow:none; background:#fff; }

/* ---------------------------------------------------------------------
   18. EXPLORATEUR DE DOCUMENTS  .exp-*
   
   ⚠ AJOUT PUREMENT ADDITIF, ANNONCÉ À L'AUTRE DÉVELOPPEUR. Aucune règle
   existante n'est modifiée et toutes les classes sont préfixées `exp-` :
   `app.css` est le fichier partagé, et une retouche d'une règle commune se
   verrait sur des écrans que son auteur ne regarde pas. Voir
   docs/CONVENTIONS.md § 8 et docs/POINT-DE-COORDINATION.md.

   ⚠ POURQUOI ICI ET PAS DANS LA VUE. La CSP de l'application est
   `style-src 'self'` : un `<style>` injecté par un écran est BLOQUÉ par le
   navigateur (une racine fantôme n'y change rien, éprouvé le 16/08). Seuls
   les attributs `style=` passent (`style-src-attr`), et une trentaine de
   règles en attributs deviendrait illisible et impossible à corriger.

   L'écran imite l'explorateur de Windows, à la demande du client : volet de
   navigation à gauche, liste en colonnes triables à droite, barre d'adresse
   en haut, barre d'état en bas.
   --------------------------------------------------------------------- */

.exp{ border:1px solid var(--line); border-radius:var(--r); background:var(--carte);
      overflow:hidden; box-shadow:var(--ombre); }

/* ── Barre d'outils + barre d'adresse ── */
.exp-barre{ display:flex; align-items:center; gap:8px; padding:8px 10px;
            border-bottom:1px solid var(--line); background:var(--soft2); }
.exp-nav{ display:flex; gap:2px; }
/* Les flèches Précédent / Remonter : carrées, comme dans un explorateur. */
.exp-nav button{ width:30px; height:30px; padding:0; display:inline-flex;
                 align-items:center; justify-content:center; font-size:14px;
                 border:1px solid transparent; border-radius:var(--r-s);
                 background:transparent; color:var(--ink); cursor:pointer; }
.exp-nav button:hover:not(:disabled){ background:#fff; border-color:var(--line); }
.exp-nav button:disabled{ opacity:.35; cursor:default; }
/* L'affichage actif (Détails / Cartes) : enfoncé, comme un bouton bascule. */
.exp-nav button.actif{ background:#fff; border-color:var(--line); color:var(--poppy-dark); }

/* La barre d'adresse : le fil d'Ariane, cliquable segment par segment. */
.exp-adresse{ flex:1; min-width:0; display:flex; align-items:center; gap:2px;
              flex-wrap:nowrap; overflow-x:auto; background:#fff;
              border:1px solid var(--line); border-radius:var(--r-s);
              padding:3px 8px; height:30px; }
.exp-adresse button{ border:0; background:transparent; cursor:pointer; padding:2px 5px;
                     border-radius:4px; font:inherit; font-size:12.5px; color:var(--ink);
                     white-space:nowrap; }
.exp-adresse button:hover{ background:var(--soft); }
.exp-adresse .sep{ color:var(--muted); font-size:11px; }
.exp-adresse .ici{ font-weight:600; padding:2px 5px; white-space:nowrap; }

/* ── Le corps : deux volets ── */
.exp-corps{ display:grid; grid-template-columns:210px 1fr; min-height:420px; }
.exp-volet{ border-right:1px solid var(--line); background:var(--soft2);
            padding:8px 6px; overflow:auto; }
.exp-volet .titre{ font-size:10.5px; text-transform:uppercase; letter-spacing:.5px;
                   color:var(--muted); padding:4px 8px 6px; }
.exp-volet a{ display:flex; align-items:center; gap:7px; padding:5px 8px;
              border-radius:var(--r-s); color:var(--ink); text-decoration:none;
              font-size:13px; cursor:pointer; }
.exp-volet a:hover{ background:#fff; }
.exp-volet a.actif{ background:var(--soft); color:var(--poppy-dark); font-weight:600; }
/* Les niveaux : un simple retrait, comme l'arborescence de gauche de Windows. */
.exp-volet .n1{ padding-left:22px; }
.exp-volet .n2{ padding-left:36px; }

.exp-liste{ overflow:auto; }
/* ⚠ LA LISTE PORTE SA PROPRE CLASSE DE TABLEAU, `.exp-tbl`, plutôt que d'être
   stylée par un sélecteur descendant `.exp-liste table`. Deux raisons :
   `tools/audit_vues.php` refuse — à raison — toute `<table>` sans classe, et
   une table stylée « à distance » perd son style dès qu'on la déplace. */
.exp-tbl{ width:100%; border-collapse:collapse; font-size:13px; }
/* ⚠⚠ MÊME EN-TÊTE QUE PARTOUT AILLEURS (27/09). Celui-ci portait une SECONDE
     convention pour la même chose — fond blanc, 11,5 px, 7 px de rembourrage
     contre 12,5 px et 10 px dans le socle : deux tableaux voisins qui ne se
     ressemblent pas font douter du reste. C'est la huitième fois que ce projet
     paie une seconde convention (les six formules de complétude Qualiopi, les
     deux largeurs de carte du 12/09).
   ⚠ Ce qui reste PROPRE à cet écran est gardé : l'en-tête COLLE au défilement
     (une liste de fichiers se parcourt sur plusieurs écrans) et il refuse la
     sélection — deux choses que le socle n'a pas à savoir. */
.exp-tbl thead th{ position:sticky; top:0; z-index:1; background:#faf9f9;
                     border-bottom:1px solid var(--line); text-align:left;
                     font-size:12.5px; font-weight:600; color:var(--muted);
                     padding:10px 12px; white-space:nowrap; user-select:none; }
.exp-tbl thead th.triable{ cursor:pointer; }
.exp-tbl thead th.triable:hover{ background:var(--soft2); color:var(--ink); }
.exp-tbl tbody td{ padding:5px 10px; border-bottom:1px solid #f2efee;
                     vertical-align:middle; }
.exp-tbl tbody tr{ cursor:default; }
.exp-tbl tbody tr:hover{ background:var(--soft2); }
/* La sélection : l'aplat de la charte, pas le bleu de Windows — on imite la
   DISPOSITION, on ne change pas de charte graphique. */
.exp-tbl tbody tr.sel{ background:var(--soft); }
.exp-tbl tbody tr.dossier{ cursor:pointer; }
.exp-ic{ display:inline-block; width:18px; text-align:center; margin-right:6px; }
.exp-nom{ display:flex; align-items:center; min-width:0; }
.exp-nom .t{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

/* ── Barre d'état ── */
.exp-etat{ display:flex; align-items:center; gap:14px; padding:6px 12px;
           border-top:1px solid var(--line); background:var(--soft2);
           font-size:12px; color:var(--muted); }
.exp-etat .pousse{ margin-left:auto; }

/* Sur un écran étroit, le volet de navigation passe au-dessus de la liste :
   210 px de colonne sur 380 px de large ne laissent pas de place aux noms. */
@media (max-width:820px){
  .exp-corps{ grid-template-columns:1fr; }
  .exp-volet{ border-right:0; border-bottom:1px solid var(--line);
              display:flex; flex-wrap:wrap; gap:4px; }
  .exp-volet .titre{ width:100%; }
  .exp-volet .n1, .exp-volet .n2{ padding-left:8px; }
}

/* ── Affichage en cartes (bascule « Détails / Cartes » de l'explorateur) ──
   ⚠ MÊME JEU DE DONNÉES, MÊMES GESTES. La carte n'est pas un autre écran :
   clic pour choisir, double-clic pour ouvrir, sélection multiple — comme dans
   la liste. Deux jeux de gestes pour deux affichages du même dossier
   obligeraient à réapprendre en changeant de vue. */
.exp-cartes{ display:grid; gap:12px; padding:14px;
             grid-template-columns:repeat(auto-fill, minmax(190px, 1fr)); }
.exp-carte{ position:relative; display:flex; flex-direction:column; gap:6px;
            padding:12px 12px 10px; border:1px solid var(--line);
            border-radius:var(--r); background:var(--carte);
            transition:border-color .12s, box-shadow .12s, background .12s;
            min-width:0; }
.exp-carte:hover{ border-color:var(--poppy-clair); box-shadow:var(--ombre); }
.exp-carte.dossier{ cursor:pointer; background:var(--soft2); }
.exp-carte.sel{ border-color:var(--poppy); background:var(--soft); }
/* La vignette : gros pictogramme, comme les grandes icônes de l'explorateur. */
.exp-carte .vign{ font-size:30px; line-height:1; }
.exp-carte .nom{ font-weight:600; font-size:13px; line-height:1.35;
                 /* ⚠ DEUX LIGNES AU PLUS, PUIS DES POINTS DE SUSPENSION.
                    « Analyse de la pratique professionnelle — séance 4 » sur
                    quatre lignes ferait des cartes de hauteurs différentes, et
                    la grille perdrait son alignement. */
                 display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical;
                 overflow:hidden; word-break:break-word; }
.exp-carte .meta{ font-size:11.5px; color:var(--muted); display:flex;
                  flex-wrap:wrap; align-items:center; gap:5px; margin-top:auto; }
.exp-carte .actions-ligne{ margin-top:6px; }

/* --- dev A : repères de séance sur la frise (20/08/2026) ------------------
   Une barre de frise dit la PÉRIODE d'engagement d'une session. Sur les
   données reprises du client, cette période vaut 62,7 jours calendaires en
   moyenne pour 3,2 jours de présence réelle : « du 5 janvier au 17 décembre »
   désigne dix matinées. Ces repères marquent, à l'intérieur de la barre, les
   jours où la session a réellement lieu.

   ⚠ `pointer-events:none` : le repère est posé DANS le bouton de la barre.
     Sans ça, cliquer pile sur un repère ne déclencherait pas l'ouverture de
     la session — un clic mort à l'endroit précis qui attire l'œil.
   ⚠ Couleur en blanc translucide : la barre porte la couleur du TYPE d'action,
     qui varie. Une teinte fixe serait invisible sur la moitié d'entre elles. */
/* -- le trait qui relie les dates éparses --
   ⚠ Il RELIE, il ne se manipule pas : ni clic, ni nom accessible. Les repères
     qu'il joint sont, eux, des boutons.
   ⚠ Ses constantes vivent ici et non dans le JavaScript : `app.css` est la
     seule feuille du projet, et une épaisseur écrite en ligne ne s'y trouve
     pas le jour où on la cherche. La couleur et la géométrie, elles, se
     calculent — elles restent en attribut. */
.gantt-liaison{
  position:absolute; height:2px; opacity:.28; pointer-events:none;
}

.gantt-barre .gantt-seance{
  position:absolute; top:2px; bottom:2px; width:2px;
  margin-left:-1px;
  background:rgba(255,255,255,.85);
  border-radius:1px;
  pointer-events:none;
}
.gantt-barre.annulee .gantt-seance{ background:rgba(255,255,255,.45); }

/* ---------------------------------------------------------------------
   21. COMPARAISON DE VERSIONS D'UN MODÈLE  .diff-*
   Bloc de dev B, ajouté en FIN DE FICHIER — même convention que .exp-*
   (section 18). Trois classes, employées par le seul écran « Modèles ».

   ⚠ POURQUOI DES CLASSES ET PAS DES ATTRIBUTS `style=`. La CSP autorise les
   deux, mais un diff pose une couleur sur CHAQUE ligne : un gabarit de cent
   cinquante lignes porterait cent cinquante attributs identiques, illisibles
   à la relecture et impossibles à corriger d'un seul endroit.

   ⚠ LA COULEUR N'EST PAS LE SEUL SIGNAL. Chaque ligne porte aussi son signe
   « + » ou « − » en tête : un daltonien lit le signe, et le diff reste
   compréhensible imprimé en noir et blanc.
   --------------------------------------------------------------------- */
.diff{ font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
       font-size:12px; line-height:1.55; border:1px solid var(--line);
       border-radius:var(--r-s); overflow-x:auto; background:var(--carte); }
/* ⚠ `.diff-ligne` ET NON `.l` : le socle emploie déjà `.l` pour le LIBELLÉ
   d'une tuile `.kpi`. La portée aurait suffi à éviter la collision, mais un
   même nom pour deux sens finit toujours par se rencontrer — et `.k`/`.v`
   inventés dans factu.js ont déjà coûté une correction le 16/08. */
.diff-ligne{ display:block; white-space:pre; padding:0 10px;
             border-left:3px solid transparent; }
.diff-ligne.plus{ background:var(--ok-bg);  border-left-color:var(--ok); }
.diff-ligne.moins{ background:var(--ko-bg); border-left-color:var(--ko); }
.diff-ligne.rien{ color:var(--muted); }

/* ---------------------------------------------------------------------
   22. ÉMARGEMENT PUBLIC  .emarg-*
   Bloc de dev B, en FIN DE FICHIER — même convention que .exp-* et .diff-*.

   ⚠⚠ CETTE PAGE N'A PAS LE SHELL DE L'APPLICATION : ni menu, ni topbar, ni
   `body>.app`. Elle est servie par public/emargement.php à quelqu'un qui n'a
   pas de compte, sur un téléphone, souvent debout dans une salle. D'où une
   colonne unique, centrée, et des cibles larges.

   ⚠ Les cibles font 44 px de haut au minimum : c'est la taille en dessous de
   laquelle un doigt rate. Ce n'est pas du confort — une signature ratée trois
   fois se termine sur la feuille papier, et le dispositif n'aura servi à rien.
   --------------------------------------------------------------------- */
body.emarg-page{ background:var(--fond); margin:0; padding:16px;
                 display:flex; justify-content:center; align-items:flex-start; }
.emarg-carte{ background:var(--carte); border:1px solid var(--line);
              border-radius:var(--r); box-shadow:var(--ombre);
              padding:20px; width:100%; max-width:480px; }
.emarg-carte h1{ margin:0 0 4px; font-size:20px; color:var(--poppy-dark); }
.emarg-seance{ margin:0 0 18px; }
/* ⭐ (25/09) LE LIEN « Vos données personnelles », SOUS la carte des pages sans
   compte — il ne paraît que lorsque l'organisme a validé son texte.
   ⚠ `flex-wrap` : sans lui, le lien se serait rangé À CÔTÉ de la carte, puisque
   le corps de page est une ligne flexible. ⚠ `align-content:flex-start` : sans
   lui, les deux lignes se partagent la hauteur de l'écran et le lien flotte au
   milieu de la page (vu au rendu le 25/09). Sans le lien, la carte reste en
   haut exactement comme avant : la page ne bouge pas d'un pixel. */
body.emarg-page{ flex-wrap:wrap; align-content:flex-start; }
.emarg-pied{ flex-basis:100%; margin:12px 0 0; text-align:center; }

/* ⚠ `touch-action:none` : sans lui, tracer un trait fait DÉFILER la page sur
   téléphone, et la signature part en zigzag. C'est le défaut qui rend une
   zone de signature inutilisable au doigt. */
.emarg-signature{ display:block; width:100%; height:160px; touch-action:none;
                  background:#fff; border:1px dashed var(--poppy-clair);
                  border-radius:var(--r-s); cursor:crosshair; }
.emarg-valider{ width:100%; min-height:44px; margin-top:6px; }
body.emarg-page .input{ min-height:44px; }

/* ⚠ CÔTÉ ORGANISME, cette fois : le QR que le formateur montre ou projette.
   Il est volontairement GRAND — un code de 3 cm sur un vidéoprojecteur ne se
   scanne pas depuis le fond d'une salle — et centré sur fond blanc, parce
   qu'un lecteur a besoin du contraste. ⚠ Le fond reste blanc même en impression
   d'un thème sombre : un QR clair sur fond sombre ne se lit pas. */
.emarg-qr{ background:#fff; padding:14px; border:1px solid var(--line);
           border-radius:var(--r-s); max-width:340px; margin:0 auto 12px; }
.emarg-qr svg{ display:block; width:100%; height:auto; }
/* L'adresse en clair : elle doit pouvoir être recopiée à l'œil, donc coupée
   proprement et en chasse fixe. */
.emarg-url{ word-break:break-all; font-size:12px; background:var(--soft2);
            padding:8px 10px; border-radius:var(--r-s); margin:0 0 10px; }

/* ⚠⚠ LE CODE SEUL, GROS. C'est le repli qu'on veut voir emprunté : il se lit à
   deux mètres, depuis le fond de la salle, sur un vidéoprojecteur fatigué.
   L'adresse au-dessus est en 12 px parce qu'on la COPIE ; celui-ci est en
   22 px parce qu'on le RECOPIE À L'ŒIL, caractère par caractère.
   `letter-spacing` et chasse fixe pour la même raison — voir `.poppy-saisie`,
   qui reçoit ce que celui-ci affiche. */
/* ⚠⚠ UNE PIÈCE ÉCARTÉE DU DOSSIER DE PREUVE. Rendue à l'identique des autres,
   elle laisserait croire qu'elle est encore due — et le taux d'à côté
   paraîtrait faux. On l'estompe SANS la masquer : elle reste au dossier, avec
   son motif, parce que c'est lui qui la défend devant un auditeur.
   ⚠ L'opacité seule ne suffirait pas à qui distingue mal les contrastes : le
   motif est écrit en toutes lettres sous le libellé. */
table.tbl tbody tr.ecartee > td{ background:var(--soft2); opacity:.72; }
table.tbl tbody tr.ecartee > td:first-child{ border-left:3px solid var(--line-champ); }

/* ⚠ La VIGNETTE d'une signature manuscrite. Bornée en hauteur : les images
   arrivent d'un canvas dont la résolution suit l'écran du téléphone — sans
   plafond, une signature prise sur un appareil à forte densité occupe la
   moitié du tableau. `object-fit: contain` pour ne jamais la déformer : une
   signature étirée n'est plus la même signature. */
.emarg-vignette{ max-height:44px; max-width:160px; object-fit:contain;
                 background:#fff; border:1px solid var(--line);
                 border-radius:var(--r-s); padding:2px; }

/* La ligne dépliée sous une ligne de tableau : elle appartient à celle du
   dessus, et son fond le dit. */
table.tbl tbody tr.deplie > td{ background:var(--soft2); }

.emarg-code{ font-size:22px; letter-spacing:.14em; text-align:center;
             background:var(--soft2); padding:12px 10px;
             border-radius:var(--r-s); margin:0 0 10px; user-select:all; }

/* ⚠ L'échelle de notes de la page publique d'évaluation. Cinq boutons larges
   plutôt qu'un curseur : un curseur se règle mal au doigt et ne dit pas où l'on
   est. Cibles à 44 px — quelqu'un qui remplit debout, en fin de journée, ne
   vise pas. */
.eval-echelle{ display:flex; gap:8px; }
/* ⚠ Une VARIANTE, et non une modification de .eval-echelle : cinq boutons
   numérotés ne débordent jamais, des libellés (« Trop petit », « Adapté »…) si.
   Toucher à .eval-echelle aurait changé le rendu des questionnaires existants
   pour un besoin qui ne les concerne pas. */
.eval-options{ flex-wrap:wrap; }
/* ⚠ Une grille N'EST PAS un tableau HTML : sur un téléphone, un tableau de
   quatre colonnes force le zoom ou le défilement horizontal, et ces
   questionnaires se remplissent debout, en fin de séance. Chaque ligne est donc
   un intitulé suivi de ses boutons, empilés si la place manque. */
.eval-grille{ display:flex; flex-direction:column; gap:14px; }
.eval-grille-ligne{ display:flex; flex-direction:column; gap:6px; }
.eval-grille-intitule{ font-size:.94rem; }
.eval-options .eval-note{ flex:1 1 auto; min-width:110px; padding:0 12px; }
.eval-note{ flex:1; min-height:44px; border:1px solid var(--line);
            background:var(--carte); color:var(--ink); border-radius:var(--r-s);
            font:inherit; font-weight:600; cursor:pointer; }
.eval-note[aria-checked="true"]{ background:var(--poppy); border-color:var(--poppy);
                                 color:#fff; }
/* ⚠ (25/09, dev A — questionnaires de la cliente, migration 1120) Une SECTION
   de ses formulaires (« INDICATEURS FORMATION », « OBJECTIFS DE LA
   FORMATION ») : un titre et son texte, jamais un champ. Un filet au-dessus
   la sépare des questions qui précèdent — sa numérotation repart à chaque
   section. */
.eval-section{ margin:22px 0 10px; padding-top:12px; border-top:1px solid var(--line); }
.eval-section h2{ font-size:1.05rem; margin:0 0 6px; }
/* Le texte sous un intitulé garde ses retours à la ligne (« Notation : 4 = … »
   est sur sa propre ligne chez elle) ; une section de la modale du bureau
   occupe toute la largeur ; une question RETIRÉE s'y lit sans se saisir. */
.eval-texte{ white-space:pre-line; }
.eval-bloc-large{ grid-column:1 / -1; }
.eval-retiree{ grid-column:1 / -1; padding:8px 10px; border-left:3px solid var(--line);
               background:var(--soft2); }
.eval-obligatoire{ color:var(--ko); }
/* L'écran « Questionnaires » : une ligne de section, et l'aperçu. */
tr.ligne-section > td{ background:var(--soft2); }
.apercu-questionnaire{ margin:0; padding-left:1.4rem; }
.apercu-questionnaire > li{ margin:6px 0; }
.apercu-questionnaire > li.apercu-section{ list-style:none; margin-top:.8rem; }
.qe-case-type{ display:inline-block; margin-right:1rem; }

/* =============================================================================
   23. PARCOURS DES MODULES (écran « À propos »)  .parcours-*
   -----------------------------------------------------------------------------
   ⚠⚠ POURQUOI EN HTML ET NON EN SVG, alors que le schéma fonctionnel juste
   au-dessus est en SVG. Le schéma montre une ARCHITECTURE — des blocs figés,
   dont la disposition porte le sens. Le parcours montre un ENCHAÎNEMENT : il
   doit se replier sur deux colonnes sur un téléphone sans qu'on redessine
   quoi que ce soit, et porter du texte qui se sélectionne et se lit à la voix.
   Un `<svg>` fait mal les trois.

   ⚠ TOUTE L'ANIMATION VIT ICI. `style-src 'self'` interdit le moindre
   `<style>` injecté par une vue — seuls les attributs `style=` passent, et
   c'est par eux que la vue pose le `animation-delay` de chaque étape.
   ========================================================================== */

/* ⚠ `<ol>` et non `<div>` : une séquence EST une liste, et le rang doit être
   porté par le balisage pour qu'un lecteur d'écran l'annonce. On retire donc
   la puce et le retrait, pas la sémantique. */
.parcours{ display:flex; flex-wrap:wrap; align-items:stretch; gap:0;
           list-style:none; margin:0; padding:0; }

.parcours-etape{
  flex:1 1 158px; min-width:150px;
  background:var(--carte); border:1px solid var(--line);
  border-radius:var(--r-s); padding:12px 12px 11px; position:relative;
}
/* Le rang et l'état sur la même ligne : l'un situe, l'autre qualifie. */
.parcours-etape .haut{
  display:flex; align-items:center; justify-content:space-between;
  gap:6px; margin-bottom:6px;
}
.parcours-etape .haut .b{ font-size:10px; }
.parcours-etape .num{
  display:inline-flex; align-items:center; justify-content:center;
  width:22px; height:22px; border-radius:50%; flex:0 0 auto;
  background:var(--soft2); color:var(--poppy); font-size:11px; font-weight:700;
}
.parcours-etape .t{ font-weight:700; font-size:13px; line-height:1.25; }
.parcours-etape .d{ font-size:11.5px; color:var(--muted); margin-top:4px; line-height:1.35; }
.parcours-etape .p{
  font-size:11px; margin-top:7px; padding-top:6px; border-top:1px dashed var(--line);
  color:var(--muted);
}
.parcours-etape .p b{ color:var(--ink); font-weight:600; }

/* ⚠ LE DOSSIER CENTRAL SE VOIT. « Une donnée saisie une seule fois, réutilisée
   partout » n'est pas un slogan : la session alimente tout ce qui suit. Une
   étape identique aux autres ferait lire le parcours comme une simple file. */
.parcours-etape.pivot{
  border-color:var(--poppy); border-width:2px;
  box-shadow:0 2px 10px rgba(200,16,46,.13);
}
.parcours-etape.pivot .num{ background:var(--poppy); color:#fff; }

/* ⚠⚠ LA FLÈCHE PORTE CE QU'ELLE TRANSPORTE. Elle était décorative — or c'est
   là que se joue le principe du projet : « une donnée saisie une seule fois,
   réutilisée partout ». Une flèche nue affirme l'enchaînement ; une flèche qui
   nomme sa charge le démontre.
   ⚠ Elle est plus large qu'avant (66 px) pour loger deux ou trois mots : en
   dessous, le texte se coupe au milieu d'un mot et devient illisible. */
.parcours-fleche{
  flex:0 1 66px; min-width:52px;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:2px; padding:0 3px; user-select:none;
}
.parcours-fleche .f{ color:var(--poppy); font-size:15px; line-height:1; }
.parcours-fleche .quoi{
  font-size:9.5px; line-height:1.2; text-align:center; color:var(--muted);
  hyphens:auto;
}

/* ⚠ La bande Qualiopi ne suit PAS le parcours, elle le LONGE : la preuve se
   constitue tout du long, elle n'est pas une étape qu'on franchit. */
.parcours-bande{
  margin-top:10px; border:1px dashed var(--poppy); border-radius:var(--r-s);
  padding:9px 12px; background:var(--soft2); font-size:12px;
}
.parcours-bande b{ color:var(--poppy); }

/* ── L'animation ──────────────────────────────────────────────────────────
   Décalée d'une étape à l'autre : c'est ce décalage qui donne à lire un
   ENCHAÎNEMENT plutôt qu'une apparition simultanée. */
@keyframes cc-parcours-entree{
  from{ opacity:0; transform:translateY(10px); }
  to  { opacity:1; transform:none; }
}
.parcours-anime .parcours-etape,
.parcours-anime .parcours-fleche,
.parcours-anime .parcours-bande{
  opacity:0;
  animation:cc-parcours-entree .42s ease-out forwards;
}

/* ⚠⚠ ET ON RESPECTE LE RÉGLAGE DU SYSTÈME. `prefers-reduced-motion` n'est pas
   une préférence esthétique : il est réglé par des personnes que le mouvement
   rend malades — troubles vestibulaires, migraines. Une animation décorative
   qui l'ignore est une faute d'accessibilité, pas un détail. Tout s'affiche
   alors d'un coup, ce qui ne retire AUCUNE information. */
@media (prefers-reduced-motion: reduce){
  .parcours-anime .parcours-etape,
  .parcours-anime .parcours-fleche,
  .parcours-anime .parcours-bande{
    opacity:1; animation:none;
  }
}

/* =============================================================================
   24. ESPACE STAGIAIRE (page publique)  .espace-*
   -----------------------------------------------------------------------------
   ⚠ Cet écran se lit debout, sur un téléphone, parfois dehors. Les cibles sont
   à 44 px comme dans POPPY, et chaque entrée porte son aide sous son libellé :
   un menu de quatre mots fait hésiter, un menu qui dit ce que chaque entrée
   ouvre ne fait pas hésiter.
   ========================================================================== */

/* -- l'en-tête : la formation, pas le nom de l'écran ------------------------
   ⚠⚠ La personne vient de scanner un code : elle sait où elle est. Ce qu'elle
   ne sait pas, c'est de quelle journée on parle quand elle en suit trois. */
.espace-tete{ margin:4px 0 22px; }
.espace-sur{
  margin:0 0 4px; font-size:11px; font-weight:600; letter-spacing:.08em;
  text-transform:uppercase; color:var(--muted);
}
.espace-tete h1{ margin:0 0 4px; font-size:21px; line-height:1.25; }
.espace-quand{ margin:0; font-size:13px; color:var(--muted); }

/* -- les groupes -----------------------------------------------------------
   ⚠⚠ ONZE ENTRÉES À PLAT SE LISAIENT COMME UN MUR. Trois groupes, parce que
   ces entrées n'ont pas la même NATURE : ce qui se fait aujourd'hui et sera
   clos ce soir, ce qui se consulte des mois après, et ce qui concerne la
   personne. Le groupe est servi par `espace_verifier.php` — deux endroits qui
   classent la même chose finissent par se contredire. */
/* -- la barre de retour ----------------------------------------------------
   ⚠⚠ Les huit sous-écrans s'écrivaient SOUS le menu, qui restait affiché :
   sur un téléphone, taper « Fiches pratiques » semblait ne rien faire — le
   contenu s'ouvrait plus bas, hors de l'écran, derrière onze entrées. Le menu
   se replie, cette barre le remplace.

   ⚠ Elle est COLLANTE : sur une liste de trente fiches, un retour qui n'est
     qu'en haut oblige à remonter toute la page pour sortir. */
.espace-nav{
  position:sticky; top:0; z-index:5;
  display:flex; align-items:center; gap:10px;
  margin:0 0 16px; padding:8px 0;
  /* ⚠ `--carte` et non `--fond` : cette barre vit DANS `.emarg-carte`, qui est
     blanche. Le gris de la page aurait dessiné un bandeau qui ne correspond à
     rien — et, étant collante, il aurait laissé passer le contenu dessous en
     transparence si le fond avait été omis. */
  background:var(--carte); border-bottom:1px solid var(--line);
}
/* ⚠⚠ `hidden` DOIT GAGNER (28/09). `display:flex` ci-dessus est une règle
   d'auteur : elle l'emporte sur le `[hidden]{display:none}` du navigateur. La
   barre « ‹ Menu » s'affichait donc SUR LE MENU, vide de titre, alors
   qu'espace.js la masque (`nav.hidden = true`) — mesuré dans chromium : 61 px
   de barre au-dessus des entrées. */
.espace-nav[hidden]{ display:none; }
.espace-retour{
  display:inline-flex; align-items:center; gap:6px;
  min-height:44px; padding:0 10px 0 4px;
  background:none; border:0; color:var(--poppy-dark);
  font:inherit; font-weight:600; cursor:pointer; border-radius:var(--r-s);
}
/* ⚠ La règle sert un `<button>` dans l'espace et un `<a>` sur les deux pages
   publiques (questionnaire, émargement) — celles-ci CHANGENT DE PAGE, un lien
   y est le bon élément. `text-decoration:none` est donc nécessaire : sans lui,
   le lien sort souligné là où le bouton ne l'est pas, et le même repère n'a
   pas la même tête selon l'écran. */
a.espace-retour{ text-decoration:none; }
.espace-retour:hover{ background:var(--soft); text-decoration:none; }
.espace-retour span{ font-size:20px; line-height:1; }
/* ⚠ Le titre de l'écran ouvert : sans lui, la barre dit comment SORTIR mais
   pas où l'on EST. */
.espace-nav-titre{
  font-weight:600; font-size:15px; min-width:0;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}

.espace-menu{ display:flex; flex-direction:column; gap:8px; margin:0; }
.espace-groupe{ margin:0 0 26px; }
.espace-groupe h2{
  margin:0 0 2px; font-size:11.5px; font-weight:700; letter-spacing:.08em;
  text-transform:uppercase; color:var(--poppy-dark);
}
.espace-groupe-aide{ margin:0 0 10px; font-size:12.5px; color:var(--muted); }

/* -- une entrée ------------------------------------------------------------
   ⚠⚠ UNE CARTE, PLUS UN APLAT ROUGE. Les onze entrées étaient des
   `.btn.primaire` pleine largeur : un mur de coquelicot où « Signer ma
   présence » — ouvert quelques minutes — pesait autant que « Nouveautés ».
   C'est le défaut corrigé le 02/09 sur la liste des fiches de POPPY ; cet
   écran-ci ne l'avait jamais reçu. */
.espace-entree{
  display:flex; align-items:center; gap:12px;
  width:100%; min-height:56px; text-align:left;
  padding:12px 14px; line-height:1.3;
  background:var(--carte); color:var(--ink);
  border:1px solid var(--line); border-radius:var(--r-s);
  font:inherit; cursor:pointer;
}
/* ⚠ L'espacement vit dans le CONTENEUR, jamais entre frères : `.espace-menu`
   sert aussi aux sous-listes (fiches, quiz, défis), et deux règles
   d'espacement se seraient additionnées là où elles se rencontrent. */
.espace-entree:hover{ border-color:var(--line-champ); background:var(--soft2); }
.espace-entree:active{ background:var(--soft); }

/* Le pictogramme : sur un écran tenu à bout de bras, c'est lui qu'on reconnaît
   avant d'avoir lu. ⚠ `aria-hidden` côté marquage — il double le libellé. */
.espace-entree .p{
  flex:0 0 auto; width:28px; text-align:center;
  font-size:17px; line-height:1; color:var(--poppy-dark);
}
.espace-entree .c{ flex:1 1 auto; min-width:0; }
.espace-entree .t{ display:block; font-weight:600; }
/* ⚠ L'aide est SOUS le libellé, pas en infobulle : une infobulle n'existe pas
   sur un écran tactile. */
.espace-entree .a{ display:block; font-size:12px; font-weight:400; color:var(--muted); margin-top:2px; }
.espace-entree .f{ flex:0 0 auto; color:var(--muted); font-size:18px; line-height:1; }

/* ⚠⚠ UN SEUL ROUGE PAR ÉCRAN, et il désigne ce qui est ouvert MAINTENANT.
   Règle du 16/08 : une couleur encode l'urgence, jamais le type. S'il n'y a
   rien d'ouvert aujourd'hui, aucune entrée n'est rouge — et c'est juste, il
   n'y a rien à faire dans l'instant. */
.espace-entree.principale{
  background:var(--poppy); border-color:var(--poppy); color:#fff;
}
.espace-entree.principale .p,
.espace-entree.principale .f{ color:#fff; }
.espace-entree.principale .a{ color:rgba(255,255,255,.88); }
.espace-entree.principale:hover{ background:var(--poppy-dark); border-color:var(--poppy-dark); }

/* ⚠ Une entrée fermée reste LISIBLE. `opacity` seule la rendrait illisible sur
   un téléphone en plein jour — et c'est précisément l'entrée dont il faut
   comprendre pourquoi elle est close. */
.espace-entree[disabled], .espace-entree.close{
  opacity:1; color:var(--muted); cursor:default;
  background:var(--soft2); border-style:dashed;
}
.espace-entree.close .p{ color:var(--muted); }
.espace-entree.close:hover{ background:var(--soft2); border-color:var(--line); }

.espace-bloc{ margin-top:18px; border-top:1px solid var(--line); padding-top:14px; }

/* ═══════════════════════════════════════════════════════════════════════════
   LE TITRE D'UNE PARTIE DE QUIZ (migration 2054) — dev B, 11/09.

   Il sert dans DEUX écrans : l'espace stagiaire (`espace.js`) et l'aperçu de
   l'éditeur (`poppy_quiz.js`), qui doit montrer ce que le stagiaire verra.

   ⚠⚠ IL DOIT SE DISTINGUER DES INTITULÉS DE QUESTION, qui sont déjà en gras.
   Sans quoi on aurait deux gras de suite et le plan disparaîtrait — un
   intertitre qui ne se voit pas ne range rien. D'où la capitale et le filet,
   la même grammaire que les en-têtes de tableau du socle.
   ⚠ Le premier n'a pas de marge haute : il ouvre l'écran. */
.espace-partie{
  font-size:12px; font-weight:700; text-transform:uppercase; letter-spacing:.6px;
  color:var(--poppy-dark); margin:26px 0 0; padding-bottom:6px;
  border-bottom:2px solid var(--poppy);
}
.espace-partie:first-child{ margin-top:0; }
.espace-bloc h2{ font-size:14px; margin:14px 0 8px; }
.espace-docs{ list-style:none; margin:0; padding:0; display:flex;
              flex-direction:column; gap:8px; }
.espace-docs a{ display:block; width:100%; min-height:44px; text-align:left; }

/* Une question « à remettre dans l'ordre ».
   ⚠⚠ AJOUT PUREMENT ADDITIF, ANNONCÉ À L'AUTRE DÉVELOPPEUR — préfixé
   `espace-`, comme toute la section 24.

   ⚠ PAS DE GLISSER-DÉPOSER : cet écran s'ouvre depuis un QR code, donc sur un
   TÉLÉPHONE. Le glisser-déposer y demande un appui long et une visée précise,
   il n'existe pas au clavier, et un lecteur d'écran n'en dit rien. Deux
   boutons de 44 px marchent partout.

   ⚠ Le numéro de rang vient de la liste ordonnée elle-même : sans lui, on
   déplace à l'aveugle. `inside` le garde dans le bloc sur écran étroit. */
.espace-ordre{ list-style:decimal; list-style-position:inside; margin:0; padding:0; }
.espace-ordre li{
  display:flex; align-items:center; gap:10px;
  min-height:44px; padding:6px 8px; margin-bottom:6px;
  border:1px solid var(--line); border-radius:6px;
}
.espace-ordre li > span:first-of-type{ flex:1 1 auto; }
/* ⚠ Un bouton d'extrémité est DÉSACTIVÉ, jamais caché : le faire disparaître
   déplacerait les autres à chaque mouvement, et la main perdrait sa cible. */
.espace-ordre button[disabled]{ opacity:.35; cursor:default; }

/* L'illustration d'une question de quiz.
   ⚠ Bornée en hauteur AUTANT qu'en largeur : une photo prise au téléphone fait
   plusieurs milliers de pixels de haut, et pousserait la question hors de
   l'écran avant qu'on ait pu la lire. `contain` la réduit sans la déformer.
   ⚠⚠ UN DOUBLON DE `.espace-paire` OCCUPAIT CETTE PLACE — deux blocs
   rigoureusement identiques, posés par deux tentatives d'insertion. Ils ne
   cassaient rien, et c'est bien le problème : une règle en double ne se voit
   pas, et corriger une seule des deux laisse l'autre s'appliquer sans qu'on
   comprenne pourquoi. */
.espace-q-image{
  display:block; max-width:100%; max-height:38vh; object-fit:contain;
  border-radius:6px; margin:0 0 10px;
}
/* Une question « à associer ».
   ⚠ Le terme AU-DESSUS de sa liste, jamais à côté : cet écran s'ouvre depuis
   un QR code, donc sur un téléphone, et deux colonnes y donnent deux mots par
   ligne et une définition tronquée au milieu d'un mot. */
.espace-paire{ margin-bottom:10px; }
.espace-paire label{ display:block; font-weight:600; margin-bottom:4px; }
.espace-paire select{ width:100%; min-height:44px; }

/* =============================================================================
   25. ÉDITEUR D'ORDRE DU MENU (Paramètres → « Menu »)  .mo-*
   -----------------------------------------------------------------------------
   ⚠⚠ UN GLISSER-DÉPOSER SEUL EST INACCESSIBLE. WCAG 2.1.1 : toute fonction
   doit être atteignable au clavier — et le glisser-déposer n'en est jamais
   une. Chaque poignée est donc un `<button>` focalisable qui déplace son
   élément aux flèches. Ce n'est pas un supplément : sans lui, l'écran est
   fermé à qui n'a pas de souris, et l'organisme perd le droit de ranger son
   propre menu.

   ⚠ Le contour d'une poignée emploie `--line-champ` et non `--line` : c'est
   une COMMANDE (WCAG 1.4.11, 3:1), pas un filet de séparation. Le même
   raisonnement que les champs de saisie, et la même erreur à ne pas refaire.
   ========================================================================== */

.mo-liste{ list-style:none; margin:0; padding:0; display:flex;
           flex-direction:column; gap:10px; }

.mo-groupe{ border:1px solid var(--line); border-radius:var(--r-s);
            background:var(--carte); }
.mo-groupe-tete{ display:flex; align-items:center; gap:8px;
                 padding:8px 10px; background:var(--soft2);
                 border-bottom:1px solid var(--line);
                 border-radius:var(--r-s) var(--r-s) 0 0; }
.mo-groupe-nom{ flex:1; font-weight:600; }
/* ⚠ Le compte des écrans est SOUS le nom du groupe et non dans une infobulle :
   c'est ce qui permet de voir d'un coup d'œil qu'on vient de vider un groupe. */
.mo-groupe-nb{ font-size:11.5px; color:var(--muted); font-weight:400; }

.mo-entrees{ list-style:none; margin:0; padding:8px; display:flex;
             flex-direction:column; gap:6px; min-height:40px; }
/* ⚠ Un groupe VIDE doit rester une cible de dépôt visible — sinon on ne peut
   plus rien y remettre, et le seul geste possible est de le supprimer. */
.mo-entrees:empty::after{ content:'Déposez un écran ici';
                          display:block; padding:10px; text-align:center;
                          font-size:12px; color:var(--muted);
                          border:1px dashed var(--line-champ);
                          border-radius:var(--r-s); }

.mo-entree{ display:flex; align-items:center; gap:8px;
            padding:6px 8px; border:1px solid var(--line);
            border-radius:var(--r-s); background:var(--carte); }
.mo-entree .ic{ width:18px; text-align:center; }
/* ⚠ Même règle que la barre latérale : une icône dessinée, en bloc centré. */
.mo-entree .ic > svg{ display:block; margin:0 auto; }
.mo-entree .lib{ flex:1; }
/* ⚠ Un écran que l'utilisateur COURANT ne peut pas ouvrir se range quand même :
   il appartient à d'autres rôles. On le dit, sans le retirer — le retirer le
   ferait disparaître du menu de TOUT LE MONDE au premier enregistrement. */
.mo-entree .autre{ font-size:11px; color:var(--muted); }

.mo-poignee{ cursor:grab; background:none; border:1px solid var(--line-champ);
             border-radius:var(--r-s); color:var(--muted);
             width:26px; height:26px; line-height:1; padding:0;
             display:inline-flex; align-items:center; justify-content:center; }
.mo-poignee:active{ cursor:grabbing; }
.mo-poignee:focus-visible{ outline:2px solid var(--poppy); outline-offset:1px; }

/* L'élément qu'on déplace — il reste lisible, il ne disparaît pas. */
.mo-porte{ opacity:.45; }
/* La cible où il tomberait. ⚠ Une bordure et un aplat, PAS seulement une
   couleur : le repère doit se voir aussi en niveaux de gris. */
.mo-cible{ outline:2px dashed var(--poppy); outline-offset:2px; background:var(--soft); }

@media (prefers-reduced-motion: reduce){ .mo-entree, .mo-groupe{ transition:none; } }

/* -----------------------------------------------------------------------------
   25 bis. LA BASCULE  .bascule
   -----------------------------------------------------------------------------
   ⚠⚠ C'EST UNE CASE À COCHER, HABILLÉE. Le `<input type="checkbox">` est bien
   là, focalisable, actionnable à la barre d'espace, annoncé par un lecteur
   d'écran, et il porte l'état. Un `<div>` avec un `onclick` aurait la même
   allure et ne serait ni focalisable, ni annoncé, ni cochable au clavier —
   c'est la forme qu'on trouve partout et qu'il ne faut pas recopier.

   ⚠ `appearance:none` retire l'habillage du navigateur MAIS PAS le
   comportement : `:checked`, le focus et la navigation par tabulation restent
   ceux de la case.

   ⚠ Le contour emploie `--line-champ` (3,45:1) et non `--line` (1,30:1) : une
   bascule est une COMMANDE, et WCAG 1.4.11 lui impose 3:1. Éteinte, elle doit
   se VOIR — sinon on ne sait pas qu'il y a quelque chose à actionner.

   ⚠⚠ ET L'ÉTAT NE SE DIT PAS QUE PAR LA COULEUR. La pastille se déplace : un
   daltonien, ou un écran en plein soleil, lisent la POSITION. Une bascule qui
   ne change que de teinte ne dit rien à personne.
   -------------------------------------------------------------------------- */

.bascule{
  appearance:none; -webkit-appearance:none;
  flex:none; width:38px; height:22px; margin:0; padding:0;
  border:1px solid var(--line-champ); border-radius:11px;
  background:var(--carte); cursor:pointer; position:relative;
  transition:background .12s ease, border-color .12s ease;
}
.bascule::after{
  content:''; position:absolute; top:2px; left:2px;
  width:16px; height:16px; border-radius:50%;
  background:var(--muted);
  transition:transform .12s ease, background .12s ease;
}
.bascule:checked{ background:var(--ok-bg); border-color:var(--ok); }
.bascule:checked::after{ transform:translateX(16px); background:var(--ok); }
.bascule:focus-visible{ outline:2px solid var(--poppy); outline-offset:2px; }
.bascule:disabled{ cursor:not-allowed; opacity:.55; }

/* ⚠ Pas d'animation pour qui n'en veut pas — la pastille SAUTE à sa place,
   elle ne disparaît pas : l'état reste lisible. */
@media (prefers-reduced-motion: reduce){
  .bascule, .bascule::after{ transition:none; }
}

/* -----------------------------------------------------------------------------
   Une ligne masquée reste LISIBLE.
   ⚠ `opacity` seule la rendrait illisible — et c'est précisément la ligne dont
   il faut comprendre pourquoi elle est éteinte, puisque c'est celle qu'on va
   vouloir rétablir. On atténue le texte, on ne l'efface pas.
   -------------------------------------------------------------------------- */
.mo-entree.masquee .lib,
.mo-groupe.masque .mo-groupe-nom{ color:var(--muted); text-decoration:line-through; }
.mo-groupe.masque .mo-groupe-tete{ background:var(--fond); }

/* ⚠ « Aucun autre chemin » n'est pas une décoration : c'est l'avertissement qui
   empêche de supprimer un écran en croyant le ranger. Il se lit AVANT le geste,
   pas après. */
.mo-entree .chemin{ font-size:11px; color:var(--muted); }
.mo-entree .chemin.seul{ color:var(--att); }

/* ---------------------------------------------------------------------------
   Lignes d'interlocuteur répétables — écran Clients, demande 14 du 25/08/2026.

   ⚠ AJOUT du 25/08 (dev A). Aucune règle existante n'est modifiée : c'est le
     premier motif de « lignes que l'on ajoute au fil de l'eau » du projet.
     Si un second apparaît, cette classe est à généraliser plutôt qu'à copier.

   ⚠ `--line` est DÉCORATIF et son contraste est faible (1,30:1) — c'est
     assumé ici : le filet sépare visuellement deux lignes, il ne porte aucune
     information. Le contour des champs, lui, garde `--line-champ`.
   --------------------------------------------------------------------------- */
.contact-ligne{
  padding:12px 0; border-top:1px solid var(--line);
}
.contact-ligne:first-child{ border-top:none; padding-top:4px; }
.contact-ligne .grille{ margin-bottom:8px; }

/* ============================================================================
   25. Ce que POPPY montre — Paramètres → POPPY (dev B, 03/09)

   Ajout purement additif, annoncé à Jérôme. Trois classes, employées par la
   seule carte `htmlCarteMenu()` de `parametres.js`.

   ⚠ Une ligne verrouillée n'est PAS une case grisée : c'est une ligne d'une
     autre nature, qui porte sa raison. Les griser côte à côte laisserait
     croire à un réglage temporairement indisponible.
   ========================================================================= */
.pm-ligne{
  display:flex; align-items:flex-start; gap:10px;
  padding:10px 12px; border:1px solid var(--line); border-radius:8px;
  margin-bottom:6px; background:var(--fond);
}
.pm-ligne.cliquable{ cursor:pointer; }
.pm-ligne.cliquable:hover{ background:var(--ligne-survol); }
.pm-ligne > input[type=checkbox]{ margin-top:3px; flex:0 0 auto; }
/* ⚠ Le fond distingue le verrou sans le griser : ce n'est pas indisponible,
     c'est interdit — et pour une raison qui est écrite à côté. */
.pm-verrou{ background:var(--soft2); border-style:dashed; }
.pm-cadenas{ flex:0 0 auto; opacity:.55; margin-top:1px; }

/* ============================================================================
   25 ter. PARAMÈTRES — LA MISE EN PAGE DES ONGLETS (dev A, 26/09)

   Capture de Jérôme sur l'instance de la cliente, onglet « État de santé » :
   un libellé et son explication écrasés dans une colonne d'environ 140 px, la
   valeur qui prend toute la largeur, la commande de correction rejetée à
   droite et coupée en petits morceaux. Relevé ensuite dans chromium, onze
   onglets × 1 280 / 1 440 / 1 920 px ; ce qui suit corrige ce relevé.

   ⚠⚠ TOUTES CES RÈGLES SONT PORTÉES PAR `#vue-parametres`. Aucune n'est nue :
      le 26/09 au matin, `.kpi{ flex-direction:row }` écrite pour un écran en a
      défait quinze autres. Ici, rien ne sort de l'écran Paramètres.
   ⚠ La garde est RENDUE dans un navigateur, aux trois largeurs :
     tools/tests_parametres_mise_en_page.php. Chercher ces règles dans ce
     fichier ne prouverait pas qu'elles s'appliquent.
   ========================================================================= */

/* ── La barre d'onglets ─────────────────────────────────────────────────────
   Onze onglets ne tiennent pas sur une ligne sous 1 920 px : ils passent à la
   ligne, et c'est voulu — les faire défiler cacherait ceux qu'on ne sait pas
   encore chercher (c'est la raison d'être des onglets, voir `ONGLETS`).
   ⚠⚠ Mais le filet n'existait que sous la DERNIÈRE rangée : l'onglet actif de
   la première (« Organisme », « Intégrations »…) soulignait le vide, entre
   deux rangées. Chaque onglet porte donc le filet sous lui — peint en fond,
   sous sa bordure transparente, que le soulignement de l'onglet actif
   recouvre — et l'écart entre onglets tombe à zéro pour qu'il reste continu
   (les 14 px de marge intérieure espacent déjà les mots).
   ⚠ `border-box` : sans lui, le filet se place au bas de la marge intérieure,
     3 px AU-DESSUS du filet de la barre, et la dernière rangée en porte deux. */
#vue-parametres > .tabs{ column-gap:0; }
#vue-parametres > .tabs > button{
  background:linear-gradient(var(--line), var(--line)) left bottom / 100% 1px no-repeat border-box;
}

/* ── État de santé : une carte par point ────────────────────────────────────
   Le mot d'état dans une colonne étroite et fixe ; TOUT le reste dans la
   seconde, qui prend la largeur : le libellé, l'explication, la mesure, puis
   le geste en dessous. */
#vue-parametres .sante-liste{
  list-style:none; margin:6px 0 14px; padding:0; border-top:1px solid var(--line);
}
#vue-parametres .sante-point{
  display:grid; grid-template-columns:96px minmax(0, 1fr); column-gap:14px;
  padding:10px 2px; border-bottom:1px solid var(--line);
}
#vue-parametres .sante-etat{ white-space:nowrap; }
/* ⚠ `min-width:0` : un élément de grille ne descend pas, sinon, sous la largeur
   de son plus long mot — un chemin d'un seul tenant pousserait la carte malgré
   `minmax(0, 1fr)`. */
#vue-parametres .sante-texte{ min-width:0; }
/* Un point conforme : le libellé, et sa mesure à droite, sur la même ligne
   tant qu'ils y tiennent. */
#vue-parametres .sante-tete{
  display:flex; flex-wrap:wrap; align-items:baseline; column-gap:18px; row-gap:2px;
}
#vue-parametres .sante-titre{ flex:1 1 260px; min-width:0; }
#vue-parametres .sante-tete .sante-mesure{ flex:0 1 auto; margin-left:auto; text-align:right; }
/* ⚠ `overflow-wrap:anywhere` : une mesure porte parfois une liste
   d'identifiants ou un chemin, d'un seul tenant. Elle passe à la ligne au lieu
   de pousser la carte. */
#vue-parametres .sante-mesure{ font-size:13px; overflow-wrap:anywhere; }
/* ⚠ L'explication s'arrête à ~120 signes par ligne : sur un écran de 1 920 px,
   une ligne de 230 signes ne se suit plus d'un bord à l'autre. */
#vue-parametres .sante-quoi{ margin:3px 0 6px; max-width:120ch; }
#vue-parametres .sante-geste{ margin-top:8px; }
#vue-parametres .sante-geste-titre{
  font-size:11.5px; font-weight:700; color:var(--muted);
  text-transform:uppercase; letter-spacing:.5px; margin-bottom:3px;
}
/* ⚠⚠ LA COMMANDE PASSE À LA LIGNE, ELLE NE SE COUPE PAS EN MORCEAUX. `pre-wrap`
   garde ses espaces et ses retours (une commande par ligne), `anywhere` coupe
   un chemin trop long plutôt que de déborder. Un retour à la ligne visuel ne
   s'emporte pas au copier : la commande collée est celle qu'on a lue. */
#vue-parametres .sante-commande{
  margin:0; padding:8px 10px; background:var(--neutre-bg); color:var(--ink);
  border-radius:var(--r-s); white-space:pre-wrap; overflow-wrap:anywhere;
  line-height:1.55;
}
#vue-parametres .sante-consigne{ font-size:13px; }

/* ── Référentiels : les thématiques ─────────────────────────────────────────
   ⚠⚠ Un libellé sans espace — 160 caractères, la longueur que la saisie
   autorise, présent sur le décor — faisait 1 573 px de large : la colonne
   « État » et les boutons partaient hors de la carte, derrière un défilement
   horizontal qu'on ne soupçonne pas. Le libellé passe à la ligne. */
#vue-parametres #carte-thematiques td:first-child{ overflow-wrap:anywhere; }
/* Le champ « Nouvelle thématique » et son bouton sur la même ligne : le champ
   à 100 % renvoyait « Ajouter » dessous. */
#vue-parametres #carte-thematiques .barre > .input{ flex:1 1 320px; width:auto; max-width:640px; }

/* ── Droits par rôle ────────────────────────────────────────────────────────
   ⚠ `table.tbl th` interdit le retour à la ligne, et « Assistante
   administrative » ou « Responsable pédagogique » écrits d'un seul tenant
   réservaient ~205 px à une case à cocher. La colonne qui explique la
   permission tombait à 286 px à 1 280 d'écran — la décision du 25/08 sur les
   rémunérations sur 14 lignes. Les en-têtes de rôle passent à la ligne. */
#vue-parametres #carte-droits thead th.centre{ white-space:normal; }

/* ============================================================================
   26. Liste de pièces jointes — lot B (dev B, 09/09)

   Ajout purement additif, annoncé à Jérôme.

   ⚠⚠ ELLE REMPLACE UNE `.liste` INVENTÉE. `qualiopi.js` posait
   `<ul class="liste">` : la classe n'existait nulle part, donc la liste
   sortait avec les puces et l'indentation du navigateur, au milieu d'un écran
   qui n'en a aucune autre. Une classe inventée ne casse rien et ne se voit
   pas — c'est le piège du 16/08, et `audit_vues` l'attrape depuis.

   ⚠ Nommée pour son USAGE, pas « liste » : un nom générique dans une feuille
     partagée finit par être repris pour autre chose, et le jour où on l'ajuste
     on déplace trois écrans qu'on n'avait pas en tête.
   ========================================================================= */
/* ⚠ AJOUTÉE LE 09/09 AU SOIR (dev A) — `sessions.js` l'employait pour les
   frais de déplacement d'une fiche de session, et elle n'existait pas : la
   liste sortait avec les puces du navigateur, dans une cellule de tableau.
   Signalé par dev B avec `tools/audit_vues.php`.
   ⚠⚠ `.pj-liste` (dev B, posée une heure plus tôt) portait EXACTEMENT les
      mêmes trois déclarations, sur une seconde règle. Fondues le 30/09 en UNE
      règle à deux sélecteurs, SANS renommer aucune classe dans les vues :
      chacune garde son nom et son usage, et `.pj-liste li` garde sa mise en
      ligne juste en dessous. Aucune règle ne s'intercalait entre les deux :
      la cascade ne change pas. */
.sans-puces,
.pj-liste{ list-style:none; margin:0; padding:0; }
.pj-liste li{
  display:flex; align-items:center; gap:8px; flex-wrap:wrap;
  padding:6px 0; border-bottom:1px solid var(--line);
}
.pj-liste li:last-child{ border-bottom:none; }
/* ⚠ Le bouton d'action est poussé à droite : sans cela son abscisse dépend de
     la longueur du nom du fichier, et il saute d'une ligne à l'autre. Même
     raison que `.actions-ligne` dans une cellule de tableau. */
.pj-liste li .btn{ margin-left:auto; }

/* ═══════════════════════════════════════════════════════════════════════════
   L'ÉCRAN DE LISTE DES QUIZ  .pq-l-* / .pq-nat / .pq-rang-* / .pq-ratees
   ───────────────────────────────────────────────────────────────────────────
   dev B, 11/09. ⚠⚠ AJOUT PUREMENT ADDITIF, ANNONCÉ À L'AUTRE DÉVELOPPEUR.
   Préfixé `pq-`, comme tout ce que l'écran des quiz a déjà posé ici : aucune
   règle existante n'est touchée, aucun sélecteur du socle n'est élargi.

   ⚠ Et il n'y a pas d'autre endroit : `style-src 'self'` bloque tout `<style>`
     injecté par une vue. Seuls les attributs `style=` passent.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ⚠⚠ LA LISTE EST BORNÉE EN LARGEUR. Elle s'étalait sur toute la fenêtre pour
   quatre informations : le titre d'un quiz occupait un cinquième de la largeur,
   l'espace mort était au milieu, et l'œil traversait l'écran entier pour relier
   un titre à son état. Une liste de quiz se compte en dizaines — elle n'a pas
   besoin de la place d'un tableau de facturation.
   ⚠ La borne porte sur TOUT l'écran, en-tête compris : la poser sur le seul
     tableau donnerait un titre et un bouton « Nouveau quiz » qui flottent
     au-delà de la liste qu'ils commandent. `.pq-editeur` fait déjà ainsi. */
.pq-liste-vue{ max-width:1100px; }

/* ── La nature du rattachement ─────────────────────────────────────────────
   ⚠ Le pictogramme est DÉCORATIF (`aria-hidden` dans la vue) : c'est le mot
     qui le suit qui porte l'information. Un symbole seul ne s'annonce pas. */
.pq-nat{ color:var(--muted); font-size:12px; margin-right:1px; }
.pq-nat-l{ color:var(--muted); font-size:11.5px; white-space:nowrap; }

/* ⚠⚠ « 0 QUESTION » N'EST PAS UNE MESURE, C'EST UN DÉFAUT. Aligné en gris
   comme un « 12 », il se lisait comme un chiffre parmi d'autres. Le mot, en
   italique et sans chiffre, sort de la colonne des nombres — ce qui est
   exactement ce qu'il faut : il n'y en a pas. */
.pq-zero{ color:var(--muted); font-style:italic; font-size:12px; }

/* La raison pour laquelle un quiz ne peut pas être publié, sous son badge.
   ⚠ Bornée en largeur : un motif long repousserait les colonnes voisines. */
.pq-pourquoi{ margin-top:3px; max-width:26ch; line-height:1.3; }

/* ── Le rang, passé à GAUCHE et porteur de son numéro ──────────────────────
   ⚠⚠ La colonne vivait à l'extrême droite, à plus de mille pixels du titre du
      quiz qu'elle déplace : on ne voyait pas ce qu'on rangeait. Et son en-tête
      annonçait « Rang » sans qu'aucun rang ne soit affiché nulle part. */
.pq-rang-c{ white-space:nowrap; width:1%; }
.pq-rang-f{ display:inline-flex; gap:2px; margin-left:6px; vertical-align:middle; }

/* ── Les gestes d'une ligne, et le chevron ─────────────────────────────────
   ⚠⚠ MASQUER AU SURVOL SANS `@media (hover: hover)` REND LE GESTE INTROUVABLE
      AU DOIGT — la leçon payée le 11/09 sur l'en-tête d'une question. Au repos
      les boutons sont donc VISIBLES ; c'est la souris, et elle seule, qui les
      efface tant qu'elle ne survole pas la ligne.
   ⚠ `:focus-within` les fait reparaître au clavier : sans lui on tabulerait
     sur des boutons invisibles. */
.pq-l-gestes{ white-space:nowrap; width:1%; }
@media (hover: hover) {
  .pq-l-gestes .actions-ligne{ opacity:0; transition:opacity .12s ease; }
  table.tbl tbody tr:hover .pq-l-gestes .actions-ligne,
  .pq-l-gestes:focus-within .actions-ligne{ opacity:1; }
}
@media (prefers-reduced-motion: reduce) {
  .pq-l-gestes .actions-ligne{ transition:none; }
}

/* ⚠ La ligne est cliquable depuis toujours et RIEN ne le disait : `.cliquable`
   ne pose qu'un curseur, invisible au doigt et inexistant au clavier. */
.pq-l-chev{ width:1%; color:var(--muted); font-size:17px; line-height:1; text-align:right; }
table.tbl tbody tr:hover .pq-l-chev{ color:var(--ink); }

/* ── « Ce qui passe mal » : une respiration, et un filet ───────────────────
   ⚠⚠ Le bloc se collait au dernier `<tr>` du tableau, même graisse, aucune
      séparation : on ne voyait pas où la liste s'arrêtait. */
/* ⚠ LE FILET A ÉTÉ RETIRÉ LE 30/09 : il séparait le bloc du dernier `<tr>` du
     tableau, quand les deux vivaient dans la même carte. Ce sont deux cartes
     désormais, et leurs bords font ce travail — un filet de plus doublerait la
     séparation au lieu de la dire. *Une règle qui ne sépare plus rien reste
     visible, et on la lit comme une intention.* */
.pq-ratees{ margin-top:16px; }
/* ⚠ L'état vide ne se distingue plus de l'état garni : c'est la même carte,
     avec la même tête. Seul son contenu change — l'encadré qui dit pourquoi il
     est vide, au lieu du tableau.
   ⚠ (30/09 au soir, relecture contradictoire, M8) `.pq-ratees > .corps > h3`
     est RETIRÉE : depuis 80e6a18 le titre vit dans `.pq-ratees-tete`, elle ne
     peignait plus rien — et une règle morte se lit comme une intention (le
     leurre décrit par 1e80565).
   ⚠ La règle ci-dessous ne peint plus rien non plus (le bloc vide n'a plus
     de paragraphe direct), mais c'est la SEULE qui déclare la classe du bloc
     vide, que la vue pose et qu'une batterie lit comme repère : la retirer
     ferait crier `audit_vues` à la classe inventée. Laissée, et signalée à
     dev B (garder le repère avec une règle utile, ou le retirer de la vue). */
.pq-ratees-vide > .corps > p{ margin:0; }

/* ⚠ La barre de filtres s'aligne sur la liste qu'elle commande. */
.pq-filtres{ flex-wrap:wrap; gap:8px; }

/* ⚠⚠ L'ALIGNEMENT EN HAUT, ET IL N'EST PAS DÉCORATIF. Depuis que la cellule
   d'état porte DEUX lignes (le badge, puis la raison de « à finir »), un
   alignement centré ferait flotter le titre du quiz au milieu de sa ligne,
   décalé de tous ses voisins — sur la seule ligne qui demande à être lue. */
.pq-liste-tbl td{ vertical-align:top; }
.pq-liste-tbl td.pq-l-chev{ vertical-align:middle; }

/* ═══════════════════════════════════════════════════════════════════════════
   LA COLONNE DE DROITE DE L'ÉDITEUR DE QUIZ — LE RYTHME  (dev B, 12/09)
   ───────────────────────────────────────────────────────────────────────────
   ⚠⚠ « TROP DE TEXTE, ÇA FAIT FOUILLIS. » Mesuré : 272 signes de prose
      PERMANENTE dans une colonne de 320 px — une justification sous la fiche,
      quatre lignes sous la session, et « Facultatif » écrit DEUX fois alors
      que tout y est facultatif sauf l'état. La plus grosse part est partie de
      la vue (58 signes restent) ; ce qui suit donne de l'air à ce qui reste.

   ⚠ Purement additif et borné à `.pq-cote` : aucune règle du socle n'est
     élargie, `.field` et `.aide` gardent leurs valeurs partout ailleurs.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ⚠ Un cran d'air de plus entre les champs. Dans une colonne étroite, c'est
   l'ESPACE qui sépare — pas les mots : cinq champs à 12 px de marge se lisent
   comme un seul bloc, et c'est cette impression de bloc qu'on appelle
   « fouillis ». */
.pq-cote .field{ margin-bottom:18px; }
.pq-cote .field:last-child{ margin-bottom:0; }

/* ⚠ Les aides qui restent sont contextuelles : elles paraissent quand elles
   s'appliquent, et disparaissent ensuite. Elles se lisent donc mieux serrées
   et détachées du champ, plutôt qu'accrochées dessous comme une légende. */
.pq-cote .field .aide{ line-height:1.45; margin-top:5px; }

/* ⚠ « ✕ Retirer » est une action de RETRAIT sous le champ qu'elle vide : elle
   se colle à lui, sinon elle flotte entre deux champs et l'on ne sait plus
   lequel des deux elle concerne. */
.pq-cote .pq-combo-bas{ margin-top:4px; }

/* ⚠⚠ « IMAGE ET VIDÉO » S'EMPILE QUAND LA PLACE MANQUE, ET LA PLACE SE MESURE
   ICI — PAS SUR LA FENÊTRE. `.grille-2` du socle ne repasse à une colonne
   qu'en dessous de **700 px de FENÊTRE** : depuis que la carte est bornée à la
   largeur de lecture, elle n'en offre plus que ~546, et la règle du socle ne
   le voit pas. Deux zones de dépôt de 250 px sur un écran de 1 600 px sont
   parfaitement possibles pour elle.
   ⚠ `auto-fit` + `minmax` mesure ce qui est RÉELLEMENT disponible : deux
     colonnes tant qu'elles tiennent, une seule ensuite. Aucune requête de
     média, donc aucun seuil à tenir à jour le jour où la borne bouge — c'est
     ce qui vient de coûter ce blanc. */
.pq-plus .grille-2{
  grid-template-columns:repeat(auto-fit, minmax(250px, 1fr));
}

/* ⚠ La liste se centre aussi : sans cela, passer de la liste à l'éditeur
   déplacerait toute la page d'un bord à l'autre. Deux écrans du même module
   ne se cadrent pas différemment. */
.pq-liste-vue{ margin-inline:auto; }

/* ═══════════════════════════════════════════════════════════════════════════
   LE PLANNING (frise) — LES DIX POINTS DU 12/09  .kpi-cible / .gantt-tri / …
   ───────────────────────────────────────────────────────────────────────────
   dev B, 12/09. ⚠⚠ AJOUT PUREMENT ADDITIF. Préfixé `gantt-` comme le reste de
   cet écran, sauf `.kpi-cible` qui MODIFIE une carte du socle — voir ci-dessous,
   la règle est bornée à la classe et n'élargit aucun sélecteur existant.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ⚠⚠ `.gantt-etiq` EST PARTIE LE 13/09, AVEC LE SEUL CONTRÔLE QU'ELLE NOMMAIT.
   Elle étiquetait « Aller au », retiré sur demande. Les contrôles qui restent
   dans le groupe « Période » sont les flèches, la date affichée et
   « Aujourd'hui » : chacun se nomme par son propre texte ou son `aria-label`,
   et le titre du groupe les coiffe. Une classe sans emploi se recopie un jour
   ailleurs en croyant qu'elle nomme quelque chose. */

/* ⚠⚠ UNE CARTE QUI MÈNE QUELQUE PART EST UN BOUTON. Elle hérite de `.kpi` —
   même fond, même filet, même relief — et n'ajoute que ce qui dit qu'on peut
   cliquer : un curseur, un survol, et un contour de focus franc.
   ⚠ `text-align:left` et `font:inherit` : un `<button>` centre son contenu et
     impose sa police, ce qui ferait une carte différente des trois autres. */
.kpi-cible{
  text-align:left; font:inherit; cursor:pointer; width:100%;
  transition:box-shadow .12s ease, transform .12s ease;
}
.kpi-cible:hover{ box-shadow:0 4px 14px rgba(43,43,43,.13); transform:translateY(-1px); }
.kpi-cible:focus-visible{ outline:3px solid var(--poppy); outline-offset:2px; }
@media (prefers-reduced-motion: reduce){
  .kpi-cible{ transition:none; }
  .kpi-cible:hover{ transform:none; }
}

/* ⚠⚠ LA LIGNE « SANS FORMATEUR AFFECTÉ » SE DISTINGUE. Mesuré le 12/09 :
   5 sessions sur 22 n'ont aucun formateur. Elles paraissaient au même niveau
   que les autres, alors que ce sont justement celles à traiter — un planning
   sert autant à voir les trous qu'à voir le plein.
   ⚠ Un LISERÉ, pas un fond rouge : ce n'est pas une panne, c'est du travail
     qui attend. La règle du 16/08 — une couleur encode l'urgence, jamais le
     type — vaut ici comme ailleurs. */
.gantt-ligne.gantt-orphelins .gantt-lib{
  box-shadow:inset 3px 0 0 var(--att);
}
.gantt-ligne.gantt-orphelins .gantt-lib .n{ color:var(--att); }

/* ═══════════════════════════════════════════════════════════════════════════
   IMPRIMER LA FRISE — elle remplace un planning EXCEL (12/09)

   ⚠⚠ Donc quelque chose qu'on IMPRIMAIT et qu'on affichait au mur. Il n'y
      avait ni impression propre, ni export : la page sortait avec le menu, la
      barre du haut, les filtres, et la frise coupée en deux.

   ⚠ C'est l'impression du NAVIGATEUR qu'on habille, pas un second dessin. Une
     mise en page maison serait un deuxième rendu à maintenir, et il
     divergerait du premier — ce projet a payé neuf fois une composition écrite
     deux fois.
   ⚠⚠ ET LES COULEURS DOIVENT PARTIR. Un planning imprimé en niveaux de gris
      perd la seule chose qui distingue un type d'action d'un autre, et la
      légende devient un mensonge. `print-color-adjust: exact` le demande
      explicitement — sans lui, la plupart des navigateurs vident les aplats.
   ═══════════════════════════════════════════════════════════════════════════ */
@media print{
  /* ⚠ Paysage : une frise est large par nature. Sur deux mois en portrait,
     soixante colonnes tiennent dans 17 cm, soit 2,8 mm par jour. */
  @page{ size:landscape; margin:10mm; }

  /* ⚠ Les réglages ne s'impriment pas — ce sont des CONTRÔLES, ils ne veulent
     rien dire sur papier. La PÉRIODE, si : sans elle, la feuille ne dit pas de
     quand elle parle. C'est le titre de la page qui la porte. */
  .gantt-outils-groupe{ display:none; }
  .gantt-outils-groupe:first-child{ display:block; }
  .gantt-outils-groupe:first-child .btn,
  .gantt-outils-groupe:first-child select,
  .gantt-outils-groupe:first-child input{ display:none; }

  /* ⚠ La barre collante ne colle plus : sur papier, « position: sticky »
     laisse un blanc à l'endroit où elle aurait dû suivre. */
  .gantt-entete{ position:static; }

  /* ⚠⚠ UNE LIGNE NE SE COUPE PAS ENTRE DEUX PAGES : la moitié des barres d'un
     intervenant sur la page 1 et l'autre moitié sur la page 2 est pire qu'une
     page de plus. */
  .gantt-ligne{ break-inside:avoid; page-break-inside:avoid; }
  .gantt-enveloppe{ break-inside:auto; }

  /* ⚠ Les aplats de couleur des barres et de la légende PARTENT. */
  .gantt-barre, .gantt-legende-pastille,
  .gantt, .gantt-piste{
    -webkit-print-color-adjust:exact; print-color-adjust:exact;
  }

  /* ⚠ Les cartes du haut restent : « 5 sessions · 9 650 € » est ce qu'on
     commente devant la feuille. En revanche celles qui sont devenues des
     BOUTONS ne doivent pas en avoir l'air sur papier. */
  .kpi-cible{ box-shadow:none; transform:none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   REPLIER UNE LIGNE DE LA FRISE (12/09)

   ⚠⚠ L'INTITULÉ EST LE BOUTON, comme le titre d'une question dans l'éditeur de
      quiz. Un chevron de 28 px posé à côté d'un texte inerte ne se lit pas
      comme une affordance : on clique sur le nom, rien ne se passe. C'est la
      leçon du 10/09, payée sur le repli des questions.
   ⚠ `flex:1 1 auto` + `min-width:0` : sans le second, un nom long refuse de se
     comprimer et pousse le compteur hors de la colonne — le piège
     `min-width:auto`, payé trois fois sur `.pq-reponse`.
   ═══════════════════════════════════════════════════════════════════════════ */
.gantt-replier{
  flex:1 1 auto; min-width:0;
  display:flex; align-items:center; gap:6px;
  background:none; border:0; padding:0; margin:0;
  font:inherit; color:inherit; text-align:left; cursor:pointer;
}
.gantt-replier:hover .n{ color:var(--poppy-dark); }
.gantt-replier:focus-visible{ outline:2px solid var(--poppy); outline-offset:2px; }
/* ⚠ Le chevron garde sa largeur quoi qu'il arrive : sans `flex:0 0 auto`, il
   se comprime avant le texte et le triangle devient un trait. */
.gantt-chev{ flex:0 0 auto; color:var(--muted); font-size:11px; line-height:1; }

/* ⚠⚠ `display:none` MASQUE, il ne retire pas du document — la ligne repliée
   doit rester atteignable : « aller au chevauchement » y mène, et le repli se
   défait. C'est la même règle que le repli des questions d'un quiz. */
.gantt-ligne.repliee .gantt-piste > .gantt-barre,
.gantt-ligne.repliee .gantt-piste > .gantt-etiquette,
.gantt-ligne.repliee .gantt-piste > .gantt-liaison{ display:none; }
/* ⚠ La grille et le repère du jour RESTENT : une ligne repliée sans rythme
   hebdomadaire se lit comme un trou dans la frise, pas comme une ligne fermée. */
.gantt-ligne.repliee{ min-height:0; }
.gantt-ligne.repliee .gantt-lib{ padding-top:5px; padding-bottom:5px; }
.gantt-ligne.repliee .gantt-lib .n{ color:var(--muted); }

/* ═══════════════════════════════════════════════════════════════════════════
   LES BOUTONS SEGMENTÉS DE LA FRISE (12/09) — durée et regroupement

   ⚠⚠ ILS ONT REMPLACÉ DEUX LISTES DÉROULANTES. Six durées dans un `<select>`,
      c'est cinq choix cachés et DEUX clics pour en changer ; en boutons, on les
      voit toutes et il en faut un. C'est la règle du 16/08 — chips pour les
      listes, onglets pour les formulaires — appliquée à un réglage à positions.

   ⚠ Le groupe se comporte comme UN contrôle : un seul filet autour, des
     séparateurs à l'intérieur. Six boutons bordés côte à côte feraient six
     contrôles, et l'œil chercherait ce qui les distingue.
   ═══════════════════════════════════════════════════════════════════════════ */
.gantt-seg{
  display:inline-flex; align-items:stretch;
  border:1px solid var(--line-champ); border-radius:var(--r-s);
  overflow:hidden; background:var(--carte);
}
.gantt-seg-b{
  background:none; border:0; border-left:1px solid var(--line);
  padding:5px 10px; font:inherit; font-size:12px; color:var(--muted);
  cursor:pointer; white-space:nowrap;
}
.gantt-seg-b:first-child{ border-left:0; }
.gantt-seg-b:hover{ background:var(--soft2); color:var(--ink); }
/* ⚠⚠ L'ACTIF SE VOIT SANS COULEUR SEULE : fond ET graisse. Un simple aplat
   coquelicot à 8 % ne se distingue pas pour qui perçoit mal les contrastes de
   teinte — c'est le critère 3.1 du RGAA, et `aria-pressed` ne le remplace pas
   à l'œil. */
.gantt-seg-b.actif{
  background:var(--soft); color:var(--poppy-dark); font-weight:700;
}
.gantt-seg-b:focus-visible{ outline:2px solid var(--poppy); outline-offset:-2px; }

/* ═══════════════════════════════════════════════════════════════════════════
   LA FRISE RESSERRÉE (12/09, tard) — j'avais ajouté dix fonctions sans jamais
   rien retirer, et la première barre commençait à 79 % de la hauteur de l'écran.
   Ce bloc ne fait que RETIRER et COMPACTER.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 1. Les réglages ne se replient plus ──────────────────────────────────
   ⚠⚠ LE PANNEAU EST PARTI LE 13/09. Il avait été créé le 12/09 pour ranger
      quinze contrôles qui poussaient la première barre de la frise à 79 % de
      la hauteur de l'écran ; il n'en restait plus qu'UN, la durée. Un bouton
      qui cache un seul réglage coûte plus qu'il n'économise. */

/* ── 2. Le tri est un clic sur l'en-tête ──────────────────────────────────
   ⚠⚠ C'est le geste qu'on essaie d'instinct sur un tableau, et il ne coûte
      aucun contrôle dans la barre d'outils. Il hérite de la typographie de la
      case d'angle : un bouton qui se voit comme un bouton ferait, ici, un
      contrôle de plus à l'endroit le plus dense de l'écran. */
.gantt-tri{
  display:inline-flex; align-items:center; gap:4px;
  background:none; border:0; padding:0; margin:0;
  font:inherit; color:inherit; text-transform:inherit; letter-spacing:inherit;
  cursor:pointer;
}
.gantt-tri:hover{ color:var(--poppy-dark); }
.gantt-tri:focus-visible{ outline:2px solid var(--poppy); outline-offset:2px; }
.gantt-tri-f{ color:var(--poppy); font-weight:700; }

/* ⚠ Un lien dans une ligne de texte, pas un bouton : « tout replier » est une
   action secondaire posée après un compteur, et un `.btn` y ferait une tache. */
.gantt-lien{
  background:none; border:0; padding:0; margin:0; font:inherit;
  color:var(--poppy-dark); text-decoration:underline; cursor:pointer;
}
.gantt-lien:hover{ color:var(--poppy); }
/* ⚠⚠ LES DEUX SIGNAUX QUI APPELLENT UNE ACTION (13/09). Ils étaient deux tuiles
   de la bande des chiffres, retirée sur demande ; ils rejoignent la case
   d'angle, où ils ne coûtent aucune bande de plus.
   ⚠ Le ton « attention » et non « erreur » : un chevauchement de formateur est
     un arbitrage à rendre, une session sans formateur un travail qui attend —
     ni l'un ni l'autre n'est une panne. C'est le ton que le calendrier emploie
     déjà pour le même fait ; deux tons pour un même fait, c'est l'un des deux
     qui ment. */
/* ⚠⚠ `--att`, ET PAS `--att-txt` : ce second jeton N'EXISTE PAS. Je l'ai écrit
     d'abord, avec une valeur de repli — c'est-à-dire exactement le piège que ce
     projet a payé quatre fois : `var(--jeton-inventé, secours)` s'applique, la
     couleur n'est pas celle de l'application, et RIEN ne le dit jamais. Le
     contrôle 20 d'`audit_vues` existe pour ça, et il l'aurait attrapé. */
.gantt-lien-att{ color:var(--att); font-weight:700; }
.gantt-lien-att:hover{ color:var(--att); text-decoration:underline; }

/* ── 5. Les cartes du haut tiennent sur une ligne ─────────────────────────
   ⚠⚠ 140 px pour quatre chiffres, dont « 0 chevauchement » — la bonne
      nouvelle, qu'on n'a pas besoin de lire en 36 px. On garde les cartes
      (elles sont cliquables, et c'est le motif de toute l'application) en
      divisant leur encombrement par deux.
   ⚠ Le sous-titre reste : c'est lui qui dit « annulées comprises », et c'est
     exactement ce qui empêche de confondre ce chiffre avec celui du
     calendrier. Le retirer ferait deux écrans qui se contredisent. */
/* ⚠⚠ `#gantt-corps` ET PAS `#vue-gantt` : cette vue n'a PAS de racine nommée —
   elle rend une page-tête, puis `#gantt-corps`. Ma première rédaction visait
   `#vue-gantt`, qui n'existe nulle part : les cinq règles n'auraient rien peint,
   sans une erreur ni un avertissement. C'est le piège de la classe inventée du
   16/08 appliqué à un IDENTIFIANT — et `audit_vues` ne garde que les classes,
   il ne l'aurait pas vu.
   ⚠ `#gantt-corps` enveloppe exactement ce qu'on resserre : les cartes, la
     barre d'outils et la frise. */
/* ⚠⚠ LES QUATRE RÈGLES DES TUILES SONT PARTIES LE 13/09, AVEC LES TUILES.
   Elles resserraient cinq cartes de chiffres en tête de la frise ; ces cartes
   ont été retirées sur demande, et les règles sont restées deux heures à ne
   peindre RIEN — sans une erreur ni un avertissement, exactement comme le
   `#vue-gantt` inventé que cet encadré raconte juste au-dessus. Une règle qui
   ne peint jamais finit par être recopiée ailleurs en croyant qu'elle agit. */

/* ── 6. La légende ne prend plus une ligne pleine ─────────────────────────
   ⚠ Elle explique la frise : elle reste EN TÊTE (la cliente a demandé une
     légende permanente, 03/09), mais elle se serre. */
.gantt-legende{ padding:6px 10px; font-size:11px; }

/* ── 9. Une ligne fait 30 px, pas 38 ──────────────────────────────────────
   ⚠⚠ La barre en fait 13 au minimum ; le reste était de l'air. Sur trente
      intervenants, huit pixels par ligne font 240 px — six lignes de plus à
      l'écran, c'est-à-dire six intervenants qu'on n'a plus à chercher. */
.gantt-ligne{ min-height:30px; }
.gantt-lib{ padding-top:5px; padding-bottom:5px; }

/* ── 10. Les deux zones se distinguent ────────────────────────────────────
   ⚠⚠ Chiffres, réglages et données étaient TROIS panneaux blancs identiques
      empilés : rien ne disait où commence ce qu'on est venu lire. Les cartes
      de chiffres sont parties le 13/09 ; il n'en reste que deux, les réglages
      et la frise.
   ⚠⚠ LE BLOC DES RÉGLAGES REPREND LE FOND DES CARTES (13/09, demandé). Il
      descendait sur le fond de la page pour ne pas se confondre avec la frise
      — la distinction ne coûtait rien tant qu'il portait quinze contrôles sur
      deux bandes ; sur UNE ligne, un bloc transparent se lit comme un trou
      dans la page plutôt que comme un en-tête.
   ⚠ Le filet POINTILLÉ reste, et c'est lui qui porte maintenant toute la
     distinction : sans lui, deux cartes blanches superposées ne diraient plus
     laquelle règle et laquelle montre. L'ombre reste absente pour la même
     raison — un réglage ne se soulève pas au-dessus de ce qu'il règle.
   ⚠ Le fond vient du JETON des cartes, jamais d'un `#ffffff` écrit ici : deux
     blancs écrits séparément divergent, et celui-ci doit être exactement celui
     de la frise juste en dessous. */
#gantt-corps .panel.deborde{
  background:var(--carte); box-shadow:none; border-style:dashed;
}

/* ⚠ La ligne de la barre. Elle n'a plus de vis-à-vis depuis que le panneau
   replié est parti (13/09), et la classe reste : c'est par elle que le
   contrôle et l'impression la désignent, et elle aligne les poignées sur le
   BAS — sans quoi la période et la durée, dont les titres n'ont pas la même
   hauteur, flotteraient à deux niveaux. */
.gantt-outils-tete{ align-items:flex-end; }

/* ═══════════════════════════════════════════════════════════════════════════
   27. LA CARTE DES DÉPARTEMENTS  .carto-*
   ───────────────────────────────────────────────────────────────────────────
   dev B, 12/09. ⚠⚠ AJOUT PUREMENT ADDITIF, ANNONCÉ À L'AUTRE DÉVELOPPEUR.
   Aucune règle existante n'est touchée, aucun sélecteur du socle n'est élargi.

   ⚠ LE PRÉFIXE EST `carto-`, PAS `carte-` : `.carte-connexion` occupe déjà ce
     début de nom depuis l'écran de connexion (ligne 357). Deux familles qui
     commencent pareil dans une feuille partagée finissent par se faire
     modifier l'une pour l'autre.

   ⚠ Et il n'y a pas d'autre endroit : `style-src 'self'` bloque tout `<style>`
     injecté par une vue. Seuls les attributs `style=` passent.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── La mise en place ──────────────────────────────────────────────────────
   ⚠⚠ `min-width:0` SUR LES DEUX COLONNES, et ce n'est pas de la prudence :
      la valeur initiale de `min-width` pour un enfant de flex est `auto`,
      c'est-à-dire « au moins la taille de mon contenu ». Un SVG de 1 143 378
      unités de large revendique alors sa largeur intrinsèque et pousse la
      colonne voisine hors de l'écran. Troisième fois que ce piège est payé
      ici (10/09 sur la frise, 11/09 sur une ligne de réponse). */
/* ⚠⚠ LE CARTOUCHE EST UN ENCART, PAS UN CO-ÉQUIPIER. Les deux colonnes
      poussaient à ÉGALITÉ (`flex:1 1 …`) : sur un écran large, l'encart
      d'Île-de-France finissait à ~450 px contre 660 pour la France entière, et
      l'œil ne savait plus laquelle des deux cartes était la carte. Signalé par
      Julien le 12/09. La colonne de droite ne POUSSE plus (`flex:0 1`) : elle
      prend ce qu'il lui faut, jamais plus.
   ⚠ `min-width:0` sur les deux — la valeur initiale pour un enfant de flex est
     `auto`, c'est-à-dire « au moins la taille de mon contenu ». Piège payé
     trois fois (10/09 la frise, 11/09 une réponse, 12/09 la carte). */
/* ⚠⚠ TROIS QUARTS / UN QUART (Julien, 12/09, sur capture). La carte a été à
      moitié, puis seule, et elle retrouve une colonne — mais au QUART. À
      moitié, la France était serrée dans une demi-largeur et deux communes
      voisines ne se distinguaient plus ; sans colonne, il fallait garder une
      bulle ouverte pour lire une adresse.
   ⚠⚠ `flex:3` ET `flex:1`, PAS `75 %` ET `25 %` : les pourcentages ignorent le
      `gap` et débordent d'exactement seize pixels. Les proportions se disent au
      flex, qui répartit CE QUI RESTE.
   ⚠ `min-width:0` sur les deux — la valeur initiale pour un enfant de flex est
     `auto`, c'est-à-dire « au moins la taille de mon contenu », et une carte
     Leaflet revendique alors sa largeur intrinsèque en poussant la colonne
     hors de l'écran. **Cinquième fois** que ce piège est payé ici (10/09 la
     frise, 11/09 une réponse, 12/09 la carte deux fois).
   ⚠ Sous 900 px la colonne passe SOUS la carte plutôt que de se serrer à
     150 px, où ni une adresse ni une liste d'inscrits ne se lisent. */
.carto-zone{ display:flex; flex-wrap:wrap; gap:16px; align-items:flex-start; }
.carto-zone > .carto-cadre{ flex:3 1 520px; min-width:0; }
/* ⚠ La colonne a la HAUTEUR de la carte et défile chez elle : sans cela, une
     session à vingt inscrits allongeait la page et la carte disparaissait vers
     le haut. C'est la même valeur que la carte, écrite une fois — deux blocs
     côte à côte qui ne finissent pas au même endroit se lisent comme un défaut
     d'alignement. */
.carto-cote{
  flex:1 1 260px; min-width:0;
  max-height:min(84vh, 900px); overflow-y:auto;
}
.carto-cote > .panel{ margin-top:0; }
.carto-cote > *:first-child{ margin-top:0; }
@media (max-width:900px){ .carto-cote{ max-height:none; } }

/* ── La carte : Leaflet, depuis le 12/09 au soir ───────────────────────────
   ⚠⚠ CE QUI A DISPARU D'ICI. Les contours, les points et le cartouche
      d'Île-de-France étaient dessinés par nous : un SVG en Lambert-93, des
      pastilles posées en HTML par-dessus, quatre tailles et neuf teintes.
      Julien a demandé **Leaflet** (registre §159) : la bibliothèque peint
      elle-même, par des options de style en JavaScript — une couche GeoJSON ne
      lit pas une classe CSS. Le cartouche n'a plus d'objet : on zoome.

   ⚠ CE QUI RESTE ICI : la mise en place, la légende et les tableaux, qui sont
     du HTML ordinaire. Et `leaflet.css` est servi à part, depuis
     `assets/vendor/leaflet/` — il n'est pas recopié dans cette feuille, qui
     n'aurait alors plus qu'une seule raison d'être partagée.

   ⚠⚠ UNE HAUTEUR EXPLICITE, SANS QUOI LA CARTE EST INVISIBLE. Leaflet monte
      dans un conteneur dont elle NE FIXE PAS la taille : un `<div>` vide a une
      hauteur nulle, la carte s'initialise sans erreur, et l'écran ne montre
      rien du tout. C'est le piège nº 1 de cette bibliothèque. */
/* ⚠⚠ EN GRAND, ET LA HAUTEUR SUIT LA FENÊTRE. À 440 px sur toute la largeur,
      la France tiendrait dans une bande : on verrait surtout de la mer de part
      et d'autre, et deux points voisins seraient indiscernables. `72vh` donne
      une carte qu'on regarde, bornée à 760 px pour qu'un grand écran ne fasse
      pas descendre la légende sous la ligne de flottaison.
   ⚠ `min-height` garde une carte utilisable sur une fenêtre basse, où 84 %
     de la hauteur ne feraient que 350 px.
   ⚠⚠ 84vh ET NON 72 : demandé par Julien le 12/09 au soir, une heure après le
      passage en pleine largeur. Le plafond monte avec (900 px) — sans quoi
      `min()` l'aurait rendu inopérant sur tout écran de plus de 1 070 px de
      haut, c'est-à-dire là où la demande se posait. */
/* ⚠⚠ `isolation:isolate` — ET CE N'EST PAS UNE PRÉCAUTION, C'EST UN DÉFAUT VU À
      L'ÉCRAN (Julien, 12/09 : « pop up en arrière plan »). Leaflet pose ses
      couches jusqu'à `z-index:700` et ses CONTRÔLES à **1000** ; son conteneur
      n'ouvre aucun contexte d'empilement (il est `position:relative` sans
      `z-index`), donc ces valeurs jouent dans le contexte de la PAGE. Les
      fenêtres de l'application, elles, sont à **80** : les boutons de zoom et
      l'attribution passaient donc PAR-DESSUS la fenêtre « Nouveau lieu », qui
      paraissait en arrière-plan.
   ⚠ Une seule ligne suffit, et c'est la bonne : monter la modale aurait déplacé
     le problème sur les toasts (120) et sur la barre haute (40), et il aurait
     fallu réviser toute l'échelle du socle pour un défaut qui vient d'une
     bibliothèque. `isolation` crée le contexte d'empilement SANS toucher à la
     mise en page — ni `position`, ni `transform`, ni `z-index` imposé.
   ⚠ Et la même chose sur la mini-carte de la fiche d'un lieu : elle porte les
     mêmes contrôles, dans un écran qui ouvre les mêmes fenêtres. */
.carto-carte, .carto-mini{ isolation:isolate; }
.carto-carte{
  height:min(84vh, 900px); min-height:480px;
  border:1px solid var(--line); border-radius:var(--r-s);
  background:var(--neutre-bg);
}
@media (max-width:700px){ .carto-carte{ height:70vh; min-height:380px; } }

/* ⚠ Les teintes du fond sont déclarées ICI **et** dans `carte.js`, et c'est
     assumé : Leaflet les veut en JavaScript, la légende les veut en CSS. Un
     contrôle vérifie qu'elles ne divergent pas — c'est la seule parade quand
     une valeur doit vivre des deux côtés. */
.carto-pastille{
  display:inline-block; width:30px; height:14px; border:1px solid var(--line);
  vertical-align:middle;
}
.carto-0{ background:#eeecea; }
.carto-c1{ background:#e4e7ee; }
.carto-c2{ background:#d3d8e4; }
.carto-c3{ background:#bfc6d8; }
.carto-c4{ background:#a9b2ca; }

/* Les pastilles de la légende qui désignent les POINTS : mêmes tailles et même
   coquelicot que les cercles posés par Leaflet. */
.carto-pin{
  display:inline-block; border-radius:50%; background:var(--poppy);
  border:1.5px solid var(--carte); vertical-align:middle; margin:0 2px;
}
.carto-pin-1{ width:10px; height:10px; }
.carto-pin-2{ width:14px; height:14px; }
.carto-pin-3{ width:18px; height:18px; }
.carto-pin-4{ width:24px; height:24px; }

/* ── La légende ────────────────────────────────────────────────────────────
   ⚠ Elle DIT ses bornes en chiffres : l'échelle est calculée sur le maximum
     observé, donc elle change avec la période. Une légende muette laisserait
     croire que la même teinte vaut toujours la même chose. */
/* ── La légende ────────────────────────────────────────────────────────────
   ⚠⚠ ELLE EST SOUS LA CARTE, PAS DANS LA COLONNE DE DROITE. Reléguée à côté du
      cartouche, elle se retrouvait à 500 px du dessin qu'elle explique, en
      11 px, sous un encart — c'est-à-dire nulle part. Signalé par Julien le
      12/09. Elle occupe désormais toute la largeur SOUS la carte, ses deux
      lectures côte à côte.
   ⚠ Elle DIT ses bornes en chiffres : l'échelle est calculée sur le maximum
     observé, donc elle change avec la période. Une légende muette laisserait
     croire que la même teinte vaut toujours la même chose. */
.carto-legende{
  display:flex; flex-wrap:wrap; gap:6px 28px;
  margin:10px 0 0; padding-top:10px; border-top:1px solid var(--line);
}
.carto-cle{ min-width:0; }
.carto-echelle{ display:flex; align-items:center; gap:3px; flex-wrap:wrap; }

/* ⚠⚠ `min-height` RÉSERVAIT 56 px MÊME VIDE, et c'est ce qui décalait le
      panneau des sessions de la carte d'en face (signalé par Julien, 12/09) :
      la carte commençait en haut, la fiche 56 px plus bas, et les deux
      colonnes ne s'alignaient sur rien. La hauteur existait pour que l'encadré
      ne fasse pas « sauter » la page à la sélection — mais il ne s'affiche plus
      QUE pour un département, et il est vide le reste du temps.
   ⚠ `:empty` et non `:has()` : il est connu partout, et son absence ne
     recréerait pas le défaut — elle laisserait juste le blanc d'avant. */
.carto-detail:not(:empty){ min-height:56px; margin-bottom:12px; }
.carto-detail:empty{ display:none; }

/* Le curseur d'une session à l'autre, dans l'en-tête du panneau. */
.carto-curseur{ display:flex; align-items:center; gap:6px; }
.carto-curseur .btn{ min-width:28px; justify-content:center; }

/* ── Le détail d'un point, sous la carte ───────────────────────────────────
   ⚠ Une session par bloc, séparées par un filet : empilées sans rien entre
     elles, quatre tableaux clé/valeur se lisent comme un seul de seize lignes.
   ⚠ Le dernier n'a pas de filet — un séparateur après le dernier élément
     annonce une suite qui n'existe pas. */
.carto-seance{ padding:10px 0; border-bottom:1px solid var(--line); }
.carto-seance:last-child{ border-bottom:none; padding-bottom:0; }
.carto-seance:first-child{ padding-top:0; }
.carto-seance h4{ margin-bottom:2px; }
/* ⚠ Le tableau d'une séance est une fiche clé/valeur, pas une liste : il ne
     prend pas toute la largeur d'un écran large, où les valeurs se
     retrouveraient à un mètre de leur libellé. */
.carto-seance .tbl{ max-width:74ch; margin-top:6px; }
.carto-fiche h3{ margin-bottom:4px; }

/* ── LA BULLE D'UN POINT ───────────────────────────────────────────────────
   Ce que la colonne de droite portait vit maintenant dans une bulle Leaflet,
   ancrée sur la pastille cliquée.

   ⚠⚠ LES DEUX SÉLECTEURS SONT À NOUS, MAIS LE TROISIÈME EST À LEAFLET.
      `.leaflet-popup-content` porte des marges et une largeur posées par
      `leaflet.css` — que nous ne modifions pas, il est servi tel qu'il a été
      téléchargé. On le surcharge depuis ICI, et la spécificité suffit
      (`.carto-bulle-hote .leaflet-popup-content` l'emporte quel que soit
      l'ordre de chargement des deux feuilles).

   ⚠⚠ UNE BULLE QUI DÉPASSE L'ÉCRAN N'EST PAS UNE BULLE. Une session à vingt
      inscrits ferait deux mètres de haut et sortirait de la carte par le haut,
      hors d'atteinte : le corps défile chez lui, plafonné à un peu plus de la
      moitié de la fenêtre. La tête — nom de la commune et curseur « 2 / 6 » —
      ne défile pas avec, sinon on perd l'endroit en lisant ce qui s'y passe. */
/* ⚠⚠ ON NE POSE PAS `width:auto !important` ICI. Leaflet écrit la largeur en
      style en ligne, bornée par `minWidth`/`maxWidth` : la forcer à `auto`
      annulait ce calcul et la bulle retombait sur la largeur naturelle du
      tableau — 290 px, des noms coupés en deux. Seule la marge est reprise. */
.carto-bulle-hote .leaflet-popup-content{
  margin:0; font-size:inherit; line-height:inherit;
}
/* ⚠ Sur un téléphone, 380 px de large sortiraient de l'écran : là, et là
     seulement, on reprend la main sur la largeur. */
@media (max-width:700px){
  .carto-bulle-hote .leaflet-popup-content{ width:auto !important; max-width:76vw; }
}
.carto-bulle-hote .leaflet-popup-content-wrapper{
  border-radius:var(--r-s); padding:0; overflow:hidden;
  background:var(--carte); color:var(--ink);
}
/* ⚠ Le focus est porté au conteneur à l'ouverture (une pastille Leaflet n'est
     pas focalisable) : sans contour visible, le clavier ne saurait pas où il
     est — et sans `outline-offset`, le contour serait rogné par l'arrondi. */
.carto-bulle-hote .leaflet-popup-content-wrapper:focus-visible,
.carto-bulle-hote:focus-visible{ outline:2px solid var(--poppy); outline-offset:2px; }
.carto-bulle{ min-width:0; }
.carto-bulle-tete{
  display:flex; align-items:center; gap:10px; flex-wrap:wrap;
  padding:10px 34px 10px 14px; border-bottom:1px solid var(--line);
  background:var(--soft2);
}
.carto-bulle-tete h3{ margin:0; font-size:15px; }
/* ⚠⚠ MOINS HAUTE QU'ELLE NE L'ÉTAIT : à 380 px, une bulle ouverte sur un point
      du sud faisait glisser la France entière hors du cadre — Leaflet déplace la
      carte pour loger la bulle, et c'est ce qu'on voyait sur la capture du
      12/09. Le corps défile, la carte reste en place. */
.carto-bulle-corps{
  padding:10px 14px 12px;
  max-height:min(38vh, 300px); overflow-y:auto;
}
/* ⚠ Dans la bulle, la liste des sessions est un SOMMAIRE : une ligne par
     session, son intitulé au-dessus de ses dates. La session ouverte s'y
     marque — sans marque, on ne sait plus laquelle la colonne montre. */
.carto-bulle .carto-items{ display:block; }
.carto-bulle .carto-item-nom{ display:block; }
.carto-bulle .carto-item-nom .petit{ display:block; }
.carto-item-choisi{ background:var(--soft); border-color:var(--poppy); }
/* ── LA FICHE D'UNE SESSION : UNE GRILLE, PLUS UN TABLEAU ──────────────────
   ⚠⚠ « LA POPUP N'AFFICHE PAS BIEN LES DONNÉES » (Julien, 12/09 au soir). La
      fiche était un `table.tbl` : dans une bulle de 400 px, le socle y apporte
      des en-têtes en capitales sur aplat, des filets et 8 à 10 px de rembourrage
      par cellule. « Intervenant » et « Inscrits » tenaient sur deux lignes, et
      les NOMS se retrouvaient dans une colonne trop étroite pour eux — « Hugo
      Chevalier » se coupait en deux, un mot par ligne.
   ⚠ `grid-template-columns:auto 1fr` : la colonne des libellés prend exactement
     ce qu'il lui faut, et TOUT le reste va aux valeurs. C'est ce que `width:1%`
     bricolait dans un tableau. */
.carto-faits{
  display:grid; grid-template-columns:auto 1fr; gap:4px 12px;
  margin:8px 0 0; min-width:0;
}
.carto-faits dt{
  font-size:12px; font-weight:700; color:var(--muted); white-space:nowrap;
}
.carto-faits dd{ margin:0; min-width:0; }

/* ⚠⚠ LES INSCRITS EN LIGNE, PAS EN COLONNE. Empilés, six inscrits faisaient
      douze lignes — nom et état chacun sur la sienne — et l'on ne voyait plus
      la session qui les porte.
   ⚠ Chaque inscrit est INSÉCABLE (`inline-flex`, pas de retour entre le nom et
     son état) : séparés, ils cessent d'être une information. */
.carto-gens-tete{
  display:flex; align-items:center; gap:6px;
  margin-top:10px; font-size:12px; font-weight:700; color:var(--muted);
}
.carto-gens{
  display:flex; flex-wrap:wrap; gap:4px 12px; margin:4px 0 0; padding:0;
  list-style:none;
}
.carto-gens li{ display:inline-flex; align-items:baseline; gap:5px; }
.carto-gens-nom{ white-space:nowrap; }

/* ⚠ Le titre de la session est le premier mot de la bulle, et c'est un LIEN :
     on lit « où », on veut ouvrir « quoi ». */
.carto-bulle-titre{ display:block; font-weight:700; font-size:14.5px; }
.carto-bulle .carto-seance{ border-bottom:none; padding:0; }

.carto-tbl{ margin-top:0; }

/* ── La liste des communes, dans la colonne de droite ──────────────────────
   ⚠⚠ ELLE RÉPOND À TROIS DÉFAUTS À LA FOIS : la moitié droite était BLANCHE à
      l'ouverture, le tableau des communes sous la carte REDISAIT les points, et
      RIEN ne s'ouvrait au clavier — ni une pastille de Leaflet, ni une ligne de
      tableau. Des BOUTONS règlent les trois : ils occupent la place, portent
      les mêmes chiffres, et se parcourent à la tabulation.
   ⚠ Le nom et le nombre sont aux deux bouts : une colonne de chiffres alignée
     se compare d'un coup d'œil, collée au nom elle ne se compare pas. */
/* ⚠⚠ EN GRILLE DEPUIS QUE LA LISTE EST SOUS LA CARTE, PAS À CÔTÉ. Une seule
      colonne de boutons sur 1 100 px donnerait des lignes de dix centimètres
      de blanc entre le nom et son nombre. `auto-fill` + `minmax` mesure la
      place DISPONIBLE, sans requête de média : aucun seuil à tenir à jour le
      jour où la mise en page bouge — c'est la leçon du 12/09 sur la carte du
      quiz, qui s'empilait sur la largeur de la FENÊTRE et pas sur la sienne. */
.carto-items{
  display:grid; grid-template-columns:repeat(auto-fill, minmax(260px, 1fr));
  gap:2px 16px;
}
.carto-items li{ display:flex; align-items:baseline; gap:8px; flex-wrap:wrap; }
.carto-item{
  flex:1 1 auto; display:flex; align-items:baseline; gap:10px;
  background:none; border:1px solid transparent; border-radius:var(--r-s);
  padding:5px 8px; font:inherit; text-align:left; cursor:pointer; color:inherit;
  min-width:0;
}
.carto-item:hover{ background:var(--ligne-survol); }
.carto-item[aria-current="true"]{ background:var(--soft); border-color:var(--poppy); }
/* ⚠⚠ UNE BOÎTE DE LARGEUR FIXE POUR LE PIN, et c'est ce qui tient la colonne.
      Les pastilles font 10 à 24 px selon le nombre de sessions : posées telles
      quelles, chaque nom commencerait à une abscisse différente et la liste
      paraîtrait bancale. La boîte est fixe, la pastille est centrée dedans.
   ⚠ `flex:0 0 auto` : sans lui, la boîte se laisserait comprimer par un nom
     long — la valeur initiale pour un enfant de flex étant « rétrécis si
     besoin », et c'est exactement ce que la carte a déjà payé cinq fois. */
.carto-item-pin{
  flex:0 0 auto; width:24px; display:inline-flex;
  align-items:center; justify-content:center;
}
.carto-item-nom{ flex:1 1 auto; min-width:0; }
/* ⚠ `tabular-nums` : sans lui une colonne de nombres ne s'aligne pas, et c'est
     tout l'intérêt de les mettre au même bout. */
.carto-item-n{ font-variant-numeric:tabular-nums; font-weight:700; }

/* ── LE CADRE DE LA CARTE, ET L'INDICE DE MOLETTE ──────────────────────────
   ⚠⚠ UNE CARTE QUI NE ZOOME PAS PASSE POUR CASSÉE. La molette est neutralisée à
      dessein (une carte qui la capture bloque la lecture de la page), et cela
      n'était écrit NULLE PART : on faisait tourner la molette, la page défilait,
      et l'on en concluait que le zoom ne marchait pas. L'indice paraît une
      seconde et demie, là où l'œil est déjà.
   ⚠ Il est posé À CÔTÉ de la carte, dans un cadre positionné : Leaflet est
     maître de son conteneur, un élément étranger s'y retrouverait sous les
     tuiles ou en travers des clics. */
/* ⚠⚠ LES DEUX CADRAGES PORTENT UN LISERÉ (Julien, 12/09). Fantômes, ils se
      lisaient comme deux mots de plus dans l'en-tête : rien ne disait qu'on
      pouvait les presser. Le contour leur rend leur nature de bouton sans leur
      donner le poids d'une action principale — ce sont deux cadrages, pas deux
      décisions.
   ⚠ Le texte passe au coquelicot avec le bord : un contour rouge autour d'un
     libellé gris se lit comme un bouton désactivé qu'on aurait entouré. */
/* ⚠⚠ TROIS CLASSES DANS LE SÉLECTEUR, ET CE N'EST PAS DU ZÈLE — LA RÈGLE
      N'AVAIT AUCUN EFFET. Écrite `.carto-cadrage{…}`, elle pèse une classe ;
      `.btn.fantome{ border-color:transparent }` en pèse DEUX et l'emportait.
      Le liseré était écrit, appliqué nulle part, et rien ne le signalait —
      c'est le piège de la classe inventée, en pire : la classe existe, la règle
      existe, et la cascade l'annule en silence. Signalé à l'écran, pas par un
      contrôle.
   ⚠ `.btn.fantome.carto-cadrage` pèse trois classes : elle gagne quel que soit
     l'ordre des sections, ce qui vaut mieux que de compter sur le fait d'être
     écrite plus bas. */
.btn.fantome.carto-cadrage{
  border-color:var(--poppy); color:var(--poppy);
}
.btn.fantome.carto-cadrage:hover{ background:var(--soft); color:var(--poppy); }

.carto-cadre{ position:relative; }
.carto-indice{
  position:absolute; left:50%; bottom:16px; transform:translateX(-50%);
  /* ⚠ `2`, pas `500` : il n'a besoin d'être au-dessus que de la carte, sa
     voisine. À 500 il se serait retrouvé au-dessus des fenêtres de
     l'application (80) — le défaut même qu'on vient de corriger à côté. */
  z-index:2; pointer-events:none;
  background:rgba(28,28,28,.86); color:#fff; border-radius:999px;
  padding:7px 14px; font-size:12.5px; white-space:nowrap;
  opacity:0; transition:opacity .18s ease;
}
.carto-indice.vu{ opacity:1; }
@media (prefers-reduced-motion:reduce){ .carto-indice{ transition:none; } }

/* ⚠⚠ UNE HAUTEUR EXPLICITE, comme la grande : Leaflet monte dans un conteneur
      dont elle NE FIXE PAS la taille, et un `<div>` vide a une hauteur nulle —
      la carte s'initialise sans erreur et l'écran ne montre rien. */
.carto-mini{ height:280px; }
@media (max-width:700px){ .carto-mini{ height:220px; } }

.carto-retour{ display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin:0 0 10px; }
/* Les liens « Corriger » d'une écriture à reprendre. */
.carto-vers{ display:flex; align-items:center; gap:6px; flex-wrap:wrap; margin-top:6px; }
.carto-tbl caption{ caption-side:top; text-align:left; padding:0 0 6px; }

/* ⚠⚠ LES RÉSERVES PRÉCÈDENT LES CHIFFRES, et la marge le dit. Ce ne sont pas
      des notes de bas de page : ce sont les conditions de validité de chaque
      ligne. Placées après, on les lit une fois les chiffres recopiés — c'est la
      règle du BPF (02/09), qu'elles ne respectaient pas sur cet écran. */
.carto-reserves{ margin:14px 0 4px; }
/* ⚠⚠ LE MODE SOBRE NE POSE PLUS RIEN SOUS LA CARTE — pas même une ligne
      repliée (Julien, 12/09). Elle avait été le compromis de la demande
      précédente ; elle restait une ligne grise au bas d'un écran qu'on a
      demandé net. Ce qu'elle portait vit dans le document, `hidden`, et
      reparaît si le fond de carte ne se charge pas : un écran qu'on allège ne
      doit pas devenir un écran qui ne sait plus rien dire.
   ⚠ Plus aucune règle `.carto-plus` : la classe n'existe plus, et une règle
     orpheline finirait par être recopiée ailleurs « puisqu'elle existe ». */

/* ⚠ Le repli porte le nombre : rien n'est caché tant qu'il est écrit. */
.carto-reste summary{ cursor:pointer; font-size:12.5px; }
.carto-reste[open] summary{ margin-bottom:6px; }
.carto-source{ margin-top:8px; }

/* ── À l'impression ────────────────────────────────────────────────────────
   ⚠⚠ SANS `print-color-adjust`, LES APLATS PARTENT EN BLANC et la carte
      imprimée ne dit plus rien : les navigateurs retirent les fonds par
      défaut. Le tableau, lui, reste lisible en noir et blanc — c'est une
      raison de plus pour qu'il existe. */
@media print{
  .carto-pastille, .carto-pin{
    -webkit-print-color-adjust:exact; print-color-adjust:exact;
  }
  /* ⚠⚠ UNE CARTE À TUILES NE S'IMPRIME PAS, ET IL VAUT MIEUX NE PAS ESSAYER.
     Les tuiles sont des images chargées au fil du défilement : à l'impression,
     la moitié n'est pas arrivée, et la page sort avec un damier gris à moitié
     rempli. Le TABLEAU, lui, porte exactement les mêmes chiffres et s'imprime
     en noir et blanc — c'est une raison de plus pour qu'il existe. */
  .carto-carte{ display:none; }
}

/* ============================================================================
   28. SUIVI DES SESSIONS — le suivi opérationnel de A à Z   .suivi-*

   Vue `suivi` (public/views/suivi.js), demandée par la cliente le 16/09 et
   tranchée par elle le 17/09 à 16 h 40 : dix-sept étapes cochées À LA MAIN,
   une vue filtrable par mois, quatre états.

   ⚠ Préfixées `.suivi-` : la CSP refuse tout `<style>` injecté par un écran,
     et un nom générique dans une feuille partagée finit repris pour autre
     chose (voir la section 26).

   ⚠⚠ LES QUATRE ÉTATS REPRENNENT LES TONS DE LA MAISON (`--ok`, `--info`,
      `--neutre`), pas des couleurs à part : « fait » doit se lire comme le
      « présent » du dossier Qualiopi, sans légende à réapprendre.
   ========================================================================= */
.suivi-barre{
  display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin:6px 0 10px;
}
.suivi-barre .input{ width:auto; min-width:180px; }
.suivi-mois{ min-width:130px; text-align:center; text-transform:capitalize; }
.suivi-reste{
  display:inline-flex; align-items:center; gap:6px; font-size:13px;
  color:var(--ink); cursor:pointer;
}
.suivi-legende{
  display:flex; gap:14px; flex-wrap:wrap; align-items:center;
  font-size:12.5px; color:var(--muted); margin:0 0 8px;
}
.suivi-legende > span{ display:inline-flex; align-items:center; gap:5px; }

table.tbl.suivi-tbl{ font-size:12.5px; }
.suivi-tbl th, .suivi-tbl td{ white-space:nowrap; }
.suivi-tbl th.suivi-phase{
  text-align:center; background:var(--soft); color:var(--navy);
  border-left:2px solid var(--line);
}
.suivi-tbl th.suivi-phase-fiche{ text-align:left; border-left:none; }
/* ⚠ Les en-têtes d'étape PASSENT À LA LIGNE : dix-sept colonnes de libellés
     entiers ne tiennent sur aucun écran. Le libellé complet reste l'infobulle. */
.suivi-tbl th.suivi-col{
  white-space:normal; width:78px; min-width:78px; max-width:92px;
  font-size:11.5px; line-height:1.25; vertical-align:bottom;
  text-align:center; cursor:pointer;
}
.suivi-tbl th.suivi-col:hover, .suivi-tbl th.suivi-col:focus-visible{
  background:var(--ligne-survol); outline:none;
}
.suivi-tbl th.suivi-col-filtree{ box-shadow:inset 0 -3px 0 var(--navy); }
.suivi-tbl .suivi-debut-phase{ border-left:2px solid var(--line); }
.suivi-n{ display:block; margin-top:3px; font-size:12px; font-weight:700; color:var(--att); }
.suivi-n.zero{ color:var(--ok); }

/* ⚠ La colonne « Session » reste visible pendant qu'on fait défiler les
     étapes : sans elle, à la douzième colonne on ne sait plus de quelle
     session on coche la case. */
.suivi-tbl .suivi-fixe{
  position:sticky; left:0; z-index:2; background:var(--carte);
  min-width:230px; max-width:300px; white-space:normal;
  box-shadow:1px 0 0 var(--line);
}
.suivi-tbl thead .suivi-fixe{ z-index:3; background:var(--soft); }
.suivi-intitule{ font-size:12.5px; color:var(--ink); margin:2px 0 1px; }

.suivi-tbl td.suivi-c{ text-align:center; padding:2px; }
.suivi-case{
  width:100%; min-width:62px; min-height:32px; box-sizing:border-box;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  border:1px solid transparent; border-radius:var(--r-s); background:transparent;
  font:inherit; font-size:14px; line-height:1.1; color:var(--muted); padding:3px 2px;
}
button.suivi-case{ cursor:pointer; }
/* ⚠ Le survol RENFORCE désormais, il ne RÉVÈLE plus : la case se voit au repos.
   Il pose donc l'encre, et non `--line` — qui ÉCLAIRCISSAIT le bord d'une case
   « à faire » au moment où le curseur la désigne, soit l'inverse de ce qu'on
   attend d'un survol. */
button.suivi-case:hover{ border-color:var(--ink); background:var(--ligne-survol); }
button.suivi-case:focus-visible{ outline:2px solid var(--navy); outline-offset:1px; }
button.suivi-case:disabled{ opacity:.5; cursor:progress; }
.suivi-case small{ font-size:10.5px; font-weight:400; color:inherit; opacity:.85; }
.suivi-e-fait{ background:var(--ok-bg); color:var(--ok); font-weight:700; }
.suivi-e-gere_client{ background:var(--info-bg); color:var(--info); }
.suivi-e-sans_objet{ background:var(--neutre-bg); color:var(--neutre); }
/* ┌──────────────────────────────────────────────────────────────────────────┐
   │ ⚠⚠⚠ « À FAIRE » ÉTAIT RENDU INVISIBLE — corrigé le 23/09, sur une capture.│
   │                                                                          │
   │ La case portait une chaîne VIDE, un fond transparent et une bordure      │
   │ transparente. Sur un mois où rien n'est encore coché, l'écran montrait   │
   │ donc des lignes ENTIÈREMENT vides : on ne distinguait pas « les dix-sept │
   │ étapes restent à faire » de « cette session n'a pas de dossier ». C'est  │
   │ pourtant l'état de DÉPART de toute session.                              │
   │                                                                          │
   │ ⚠⚠ ET LA LÉGENDE MONTRAIT UNE CASE QUE LE TABLEAU NE DESSINAIT PAS :     │
   │    `.suivi-mini` posait `border-color:var(--line)`, donc la pastille     │
   │    avait un contour que la vraie case n'avait pas. Même famille que la   │
   │    légende de la frise corrigée le même jour, à l'envers.                 │
   │                                                                          │
   │ ⚠⚠ ET LA COMMANDE N'APPARAISSAIT QU'AU SURVOL. Chaque case est un        │
   │    `<button>` ; seul `:hover` lui donnait un bord. Or la règle du 12/09  │
   │    est écrite : « masquer au survol sans `@media (hover: hover)` rend le │
   │    geste introuvable au doigt ». Sur une tablette on voyait une grille   │
   │    vide, et rien ne disait que les cases se cliquent.                    │
   │                                                                          │
   │ ⚠ LE CONTOUR EMPLOIE `--line-champ` ET NON `--line`, et ce n'est pas un  │
   │   détail : `--line` vaut 1,30:1 sur blanc et la feuille le dit           │
   │   DÉCORATIF. Une case cliquable est une COMMANDE — WCAG 1.4.11 lui       │
   │   impose 3:1, et le projet a écrit un second jeton exactement pour ça.   │
   │   C'est le raisonnement du 22/08, appliqué là où il n'avait pas été vu.  │
   └──────────────────────────────────────────────────────────────────────────┘ */
.suivi-e-a_faire{ background:transparent; border-color:var(--line-champ); }
.suivi-indice{ color:var(--att); font-weight:700; }
/* La case de la légende : même dessin, en petit, et inerte. */
/* ⚠⚠ LA PASTILLE NE POSE PLUS SON PROPRE CONTOUR, ET C'EST CE QUI LA REND
   EXACTE. Elle écrivait `border-color:var(--line)` : plus spécifique que l'état,
   elle l'emportait, et la légende annonçait un bord que la case du tableau
   n'avait pas. En le retirant, chaque pastille prend le bord de SON état —
   donc exactement celui que le tableau dessine, aujourd'hui et après le
   prochain changement. Une légende se dérive de ce qu'elle annonce ; recopiée,
   elle en diverge. */
.suivi-case.suivi-mini{
  display:inline-flex; width:22px; min-width:22px; min-height:18px;
  font-size:11px;
}
/* La fiche d'une session : une case large, sur une ligne. */
.suivi-case.suivi-large{
  min-width:170px; flex-direction:row; gap:8px; justify-content:flex-start; padding:5px 10px;
}
.suivi-fiche .suivi-tbl td{ white-space:normal; }

/* ── Le menu d'une case ou d'une colonne ──────────────────────────────── */
.suivi-menu{
  position:absolute; z-index:1000; display:flex; flex-direction:column; gap:3px;
  min-width:240px; max-width:300px; padding:8px;
  background:var(--carte); border:1px solid var(--line); border-radius:var(--r);
  box-shadow:var(--ombre-haut);
}
.suivi-menu-titre{
  font-size:12px; color:var(--muted); padding:2px 6px 6px; margin-bottom:3px;
  border-bottom:1px solid var(--line); white-space:normal;
}
.suivi-menu button{
  text-align:left; background:transparent; border:1px solid transparent;
  border-radius:var(--r-s); padding:6px 8px; font:inherit; font-size:13px;
  color:var(--ink); cursor:pointer;
}
.suivi-menu button:hover, .suivi-menu button:focus-visible{
  background:var(--ligne-survol); border-color:var(--line); outline:none;
}
.suivi-menu button.actuel{ font-weight:700; background:var(--soft); }
.suivi-menu-date{
  display:flex; align-items:center; gap:6px; padding:0 8px 4px 28px;
  font-size:12px; color:var(--muted);
}
.suivi-menu-date .input{ width:auto; padding:2px 6px; font-size:12px; }
.suivi-menu-note{ font-size:11.5px; color:var(--muted); padding:6px 8px 2px; white-space:normal; }

@media print{
  /* ⚠ `#vue-suivi` devant : sans lui, cette règle masquerait à l'impression
       les boutons d'en-tête de TOUS les écrans. */
  .suivi-barre, .suivi-menu, #vue-suivi .page-tete .actions{ display:none; }
  .suivi-tbl .suivi-fixe{ position:static; box-shadow:none; }
  .suivi-case{ -webkit-print-color-adjust:exact; print-color-adjust:exact; }
}

/* ── Les étapes reprises de SON classeur (migration 1084, 24/09) ──────────
   « BDC+conv », « QTN », « Convoc » : trois colonnes de son 1-BDD.xlsx que
   ses dix-sept étapes n'avaient pas. La marque « classeur » dit d'où elles
   viennent : elle ne les trouvera pas dans son courriel du 16/09. */
.suivi-classeur-marque{
  display:inline-block; margin-left:4px; padding:0 4px; border-radius:var(--r-s);
  font-size:10px; font-weight:600; font-style:normal; line-height:1.5;
  color:var(--info); background:var(--info-bg); vertical-align:middle;
}
.suivi-tbl th.suivi-col .suivi-classeur-marque{ display:block; width:max-content; margin:3px auto 0; }
.suivi-tbl th.suivi-col-classeur{ font-style:italic; }

/* ── Le bloc de la fiche de session (24/09) ────────────────────────────────
   Promis le 23/09 à 11 h 12 : « dans la fiche de session, un bloc de suivi ».
   ⚠ EN COLONNES, PAS EN TABLEAU : il vit dans la synthèse, au-dessus des
     stagiaires qu'on pointe le jour de la formation. Vingt lignes l'une sous
     l'autre les pousseraient hors de l'écran ; réparties en quatre colonnes,
     elles tiennent en quelques lignes. */
/* ⚠⚠ UNE GRILLE, PLUS UN FLUX MULTI-COLONNES (30/09). Avec `columns:4 220px`,
     les quatre phases devenues insécables tenaient en TROIS colonnes et la
     quatrième restait vide — 440 px perdus sur 1 620, mesurés sur la vue
     rendue. Un flux répartit un TEXTE ; ici on range QUATRE blocs nommés, et
     c'est une grille.
   ⚠ `auto-fit` + `minmax()` mesure la place DISPONIBLE et non la fenêtre : le
     bloc vit aussi dans la fiche de session, dans une colonne plus étroite, et
     aucun seuil n'est à tenir à jour le jour où elle bouge (leçon du 12/09 sur
     la carte du quiz). */
.suivi-bloc{ display:grid; grid-template-columns:repeat(auto-fit, minmax(220px, 1fr));
  gap:0 22px; align-items:start; }
/* ⚠⚠ UNE PHASE NE SE COUPE PAS ENTRE DEUX COLONNES (30/09, demandé sur
     capture). Mesuré sur la vue rendue : **trois colonnes sur quatre
     commençaient par des étapes ORPHELINES de leur titre** — « Support envoyé »
     en tête de la 2e appartient encore à « Préparation pédagogique »,
     « Émargement signé » en tête de la 3e à « Réalisation », « Bilan formateur
     reçu » en tête de la 4e à « Évaluations et bilan ». Un flux multi-colonnes
     coupe où il veut, et `break-after:avoid` ne protégeait que le titre de SA
     PREMIÈRE LIGNE, pas le bloc.
   ⚠⚠ Sur un DOSSIER DE PREUVE, la phase n'est pas un intertitre décoratif :
     c'est elle qui dit si l'étape se fait avant, pendant ou après la formation,
     donc quand elle est en retard. Une étape sans sa phase se lit sans son
     échéance. C'est la même famille que « une colonne qui rend `a || b || c` ne
     dit pas lequel » (11/09).
   ⚠ Le prix, assumé : les colonnes ne sont plus de hauteur égale, et le bloc
     grandit de quelques lignes. Une colonne nommée vaut mieux qu'une colonne
     courte. */
.suivi-bloc-phase{ margin:0 0 8px; break-inside:avoid; }
.suivi-bloc-phase h4{ margin:0 0 4px; font-size:12px; color:var(--navy); break-after:avoid; }
.suivi-bloc-phase ul{ list-style:none; margin:0; padding:0; }
.suivi-bloc-etape{
  display:flex; align-items:center; gap:7px; padding:2px 0;
  font-size:12.5px; break-inside:avoid;
}
/* La case du bloc : une puce, sans la date — elle s'écrit à côté, en entier.
   ⚠⚠ ELLE NE POSE PAS SON PROPRE CONTOUR : plus spécifique que l'état, un
   `border-color` ici effacerait celui de « à faire » (`--line-champ`, le jeton
   des commandes) — la faute corrigée le 23/09 sur la pastille de légende. */
.suivi-case.suivi-puce{
  width:26px; min-width:26px; min-height:22px; flex:0 0 auto;
  padding:0; font-size:12px;
}
.suivi-bloc-lib{ color:var(--ink); }
.suivi-bloc-date{ margin-left:auto; font-size:11.5px; color:var(--muted); white-space:nowrap; }
#s-suivi .suivi-legende{ margin:6px 0 0; }

/* ── La vue pilotage (24/09) ──────────────────────────────────────────────
   Sa demande du 24/09 à 8 h 57 : « savoir en quelques secondes quels
   dossiers nous concernent, ce qui est à faire et ce qui devient urgent ».
   ⚠ LES COMPOSANTS DE LA MAISON, PAS DE NOUVEAUX : les sept compteurs sont
     des `.kpi.cliquable` (section 6), les états des `.b` (section 8), la
     ligne choisie `tr.selectionnee` (section 10). Seul ce qui n'existait
     pas est écrit ici. */
/* ⚠⚠ UN CHOIX ENTRE DEUX VUES EST UN SÉLECTEUR SOUDÉ, PAS DEUX BOUTONS
     (26/09, maquette). Séparés de 4 px, ils se lisaient comme deux commandes
     indépendantes — « Vue détaillée » pouvait s'entendre comme un écran de
     plus, et non comme l'autre moitié d'un interrupteur. Soudés, la bordure
     commune dit qu'il y en a exactement un des deux. */
.suivi-vues{ display:inline-flex; gap:0; border-radius:var(--r-s); }
.suivi-vues > .btn{ border-radius:0; margin:0; }
.suivi-vues > .btn:first-child{ border-top-left-radius:var(--r-s); border-bottom-left-radius:var(--r-s); }
.suivi-vues > .btn:last-child{ border-top-right-radius:var(--r-s); border-bottom-right-radius:var(--r-s); }
.suivi-vues > .btn + .btn{ margin-left:-1px; }
.suivi-vues > .btn.primaire{ position:relative; z-index:1; }
/* ⚠ Une liste de clients s'élargit à son plus long nom (« CENTRE DÉPARTEMENTAL DE
   L'ENFANCE-AUBE ») : bornée, la barre tient sur une ligne de plus, pas sur trois. */
.suivi-barre select.input{ max-width:260px; }
/* Un compteur est un BOUTON (il filtre) : il hérite de la tuile, pas du
   bouton du navigateur. */
.suivi-kpis button.kpi{ font:inherit; text-align:left; width:100%; cursor:pointer; }
.suivi-kpis .kpi[aria-pressed="true"]{
  background:var(--soft2); border-color:var(--poppy);
  box-shadow:inset 0 0 0 1px var(--poppy), var(--ombre);
}
table.tbl.suivi-pilote{ font-size:12.5px; }
.suivi-pilote td{ vertical-align:middle; }
.suivi-par{ display:inline-flex; align-items:center; gap:5px; }
/* La barre d'avancement : étapes réglées sur étapes applicables. La couleur
   suit l'état de la session — rouge en retard, ambre à traiter — et le
   nombre est écrit à côté : la couleur seule ne dit rien à qui ne la voit pas. */
.suivi-avancement{ display:flex; align-items:center; gap:7px; min-width:120px; }
.suivi-avancement-barre{
  flex:1 1 auto; height:8px; min-width:60px; border-radius:4px;
  background:var(--neutre-bg); overflow:hidden;
}
.suivi-avancement-barre > span{ display:block; height:100%; background:var(--ok); }
.suivi-avancement[data-etat="retard"] .suivi-avancement-barre > span{ background:var(--ko); }
.suivi-avancement[data-etat="bientot"] .suivi-avancement-barre > span{ background:var(--att); }
.suivi-avancement-n{ font-size:11.5px; color:var(--muted); white-space:nowrap; font-variant-numeric:tabular-nums; }
/* ┌──────────────────────────────────────────────────────────────────────────┐
   │ ⚠⚠ « EN RETARD » EST UNE NUANCE DE « À FAIRE », PAS UN ÉTAT DE PLUS.     │
   │                                                                          │
   │ La case garde tout ce que `.suivi-e-a_faire` lui donne (son rond, son    │
   │ statut de commande) ; elle se colore en plus. `.suivi-case` devant : la  │
   │ règle doit l'emporter sur celle de l'état, SANS toucher à celle-ci — la  │
   │ batterie garde le contour de « à faire » au caractère près.             │
   │ ⚠ `--ko` sur blanc passe les 3:1 d'une commande (WCAG 1.4.11).           │
   └──────────────────────────────────────────────────────────────────────────┘ */
.suivi-case.suivi-u-retard{ border-color:var(--ko); background:var(--ko-bg); color:var(--ko); }
.suivi-ech-retard{ color:var(--ko); font-weight:700; }
/* ┌──────────────────────────────────────────────────────────────────────────┐
   │ ⚠⚠ « À VÉRIFIER » : L'HISTORIQUE, DÛ AVANT LA MISE EN SERVICE DU SUIVI. │
   │ Ni rouge ni ambre — ce n'est ni un retard ni une urgence. Un contour en │
   │ TIRETS, bleu nuit : la forme se lit sans distinguer les teintes, et le  │
   │ bleu nuit n'est pris par aucun autre état (« géré par le client » est   │
   │ un aplat `--info`). `--navy` sur blanc passe les 3:1 d'une commande.    │
   └──────────────────────────────────────────────────────────────────────────┘ */
.suivi-case.suivi-u-verifier{ border-style:dashed; border-color:var(--navy); background:var(--soft2); color:var(--navy); }
.suivi-ech-verifier{ color:var(--navy); font-style:italic; }
/* Le badge « À vérifier » du tableau porte la MÊME identité que la case et la
   tuile : tirets bleu nuit. En `.b-info`, il avait le bleu de « géré par le
   client » (relevé à la relecture du 24/09). */
.b.suivi-b-verifier{ background:var(--soft2); color:var(--navy); border:1px dashed var(--navy); padding:1px 8px; }
/* Le compteur « Avant la formation » passe au bleu d'information : le bleu
   nuit est désormais celui de l'historique à vérifier. */
.suivi-kpis .kpi.info{ border-top-color:var(--info); }
.suivi-kpis .kpi.info .n{ color:var(--info); }
.suivi-ech-bientot{ color:var(--att); font-weight:600; }
.suivi-detail{ margin-top:14px; }
.suivi-detail-tete{ margin:0 0 10px; font-size:12.5px; line-height:1.7; }
/* Le choix de plusieurs étapes : des cases à cocher dans le menu de la barre. */
.suivi-menu.suivi-menu-etapes{ max-width:340px; max-height:70vh; overflow:auto; }
.suivi-menu fieldset{ border:none; margin:4px 0 0; padding:0; }
.suivi-menu legend{ font-size:11.5px; font-weight:700; color:var(--navy); padding:4px 8px 2px; }
.suivi-menu label{
  display:flex; align-items:center; gap:6px; padding:2px 8px;
  font-size:12.5px; color:var(--ink); cursor:pointer;
}
.suivi-menu-actions{ display:flex; gap:6px; flex-wrap:wrap; padding:8px 8px 2px; }
.suivi-menu .suivi-menu-actions .btn{ text-align:center; }
/* Le réglage des échéances. */
.suivi-ech-tbl td{ white-space:normal; }
.suivi-ech-champ{ display:inline-flex; align-items:center; gap:5px; flex-wrap:wrap; }
.suivi-ech-champ .input{ width:auto; padding:3px 6px; font-size:12.5px; }
.suivi-ech-champ input.input{ width:64px; }
.suivi-ech-lie{ display:flex; align-items:center; gap:6px; margin-top:4px; font-size:12px; color:var(--muted); }
input.input.suivi-ech-fenetre{ width:64px; display:inline-block; padding:3px 6px; }
input.input.suivi-ech-depart{ width:auto; display:inline-block; padding:3px 6px; }
@media print{
  #suivi-detail-zone .actions{ display:none; }
  .suivi-case.suivi-u-retard, .suivi-case.suivi-u-verifier{ -webkit-print-color-adjust:exact; print-color-adjust:exact; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   LES VOLETS D'UN ÉCRAN DE PILOTAGE (24/09/2026, dev B)
   ═══════════════════════════════════════════════════════════════════════════
   ⚠ `.kpi.kpi-mesure` A VÉCU UNE JOURNÉE ET N'EXISTE PLUS : la tuile de mesure
     est passée dans `.kpi` (section 7) le jour même, sur décision de Julien.
     Un modificateur qui devient la règle ne se garde pas « au cas où » — il
     resterait à porter deux formes de la même chose, et l'une des deux
     finirait par dériver.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ─── Une pastille de filtre porte le TON de ce qu'elle filtre ─────────────
   ┌──────────────────────────────────────────────────────────────────────────┐
   │ ⚠⚠ CE N'EST PAS « UNE COULEUR PAR TYPE » — la règle du 16/08 tient.      │
   │                                                                          │
   │ La pastille « Actifs » est verte parce que le badge « Actif », dans la   │
   │ colonne d'à côté, l'est déjà : `CC.TONS.clients` donne prospect→info,    │
   │ actif→ok, a_relancer→att, inactif→neutre. La couleur n'est pas choisie   │
   │ pour distinguer des catégories, elle RAPPELLE celle que la ligne porte   │
   │ déjà — sans elle, on lit un filtre gris au-dessus d'une colonne colorée  │
   │ et rien ne dit que les deux parlent de la même chose.                    │
   │                                                                          │
   │ ⚠ Contrastes mesurés le 24/09, WCAG 1.4.3 (seuil 4,5:1 pour du texte) :  │
   │   au repos, le ton sur son fond pâle → ok 4,55 · att 4,68 · info 6,33 ·  │
   │   neutre 5,67. Actif, le blanc sur le ton plein → ok 5,11 · att 5,14 ·   │
   │   info 6,88 · neutre 6,69 · accent 5,88. Les neuf tiennent.              │
   │                                                                          │
   │ ⚠ AJOUT PUREMENT ADDITIF : une pastille qui ne pose aucune de ces        │
   │   classes garde l'apparence du socle, au pixel près.                     │
   └──────────────────────────────────────────────────────────────────────────┘ */
.chip-ok{     background:var(--ok-bg);     color:var(--ok);     border-color:transparent; }
.chip-att{    background:var(--att-bg);    color:var(--att);    border-color:transparent; }
.chip-info{   background:var(--info-bg);   color:var(--info);   border-color:transparent; }
.chip-neutre{ background:var(--neutre-bg); color:var(--neutre); border-color:transparent; }
.chip-accent{ background:var(--soft);      color:var(--poppy-dark); border-color:transparent; }

/* ⚠ L'état ACTIF est une PILULE PLEINE, et il l'emporte sur `.chip.actif` du
   socle — sans quoi la couleur du ton serait écrasée par le rouge générique et
   l'on ne saurait plus lequel des cinq filtres est posé. */
.chip.chip-ok.actif{     background:var(--ok);     color:#fff; border-color:var(--ok); }
.chip.chip-att.actif{    background:var(--att);    color:#fff; border-color:var(--att); }
.chip.chip-info.actif{   background:var(--info);   color:#fff; border-color:var(--info); }
.chip.chip-neutre.actif{ background:var(--neutre); color:#fff; border-color:var(--neutre); }
.chip.chip-accent.actif{ background:var(--poppy);  color:#fff; border-color:var(--poppy); }

/* ⚠ Le survol doit rester perceptible SUR une pastille déjà colorée : la règle
   du socle change la bordure, qui est ici transparente. On assombrit le texte,
   seule propriété disponible qui ne déplace rien. */
.chip[class*="chip-"]:hover:not(.actif){ color:var(--ink); }

/* ─── Les volets d'un écran de pilotage ───────────────────────────────────
   ⚠ `.chip` reste la pastille de FILTRE du socle. Un volet n'est pas un
   filtre : il ne retire rien de la liste, il change ce qu'on regarde. La
   variante le dit par la forme — pleine quand elle est active, sans contour
   quand elle ne l'est pas — au lieu de se déguiser en filtre posé.
   ⚠ Le contour ne disparaît qu'au REPOS : au survol et au focus il revient,
   sinon rien ne dirait que le mot se clique. */
/* ⚠⚠ `.chip-volet` A ÉTÉ RETIRÉE LE 28/09 : les volets des statistiques sont
     devenus de vrais ONGLETS (maquette). La règle du 16/08 dit « onglets pour
     les FORMULAIRES, chips de filtre pour les LISTES » — et ces six-là ne sont
     ni l'un ni l'autre : ce sont des SECTIONS d'un même écran, dont on ne voit
     qu'une à la fois. Une pastille de filtre promet de RESTREINDRE une liste
     qu'on voit ; ici on CHANGE de contenu.
   ⚠ Les règles sont retirées et non gardées « au cas où » : du CSS qui ne
     peint plus rien ne se voit pas. */
.st-onglets{ margin-bottom:14px; }

/* --- dev A : glisser-déposer un fichier (depot-mail-et-glisser, 25/09) -----
   La cible que vise un fichier en cours de glisser : le bouton « Déposer… »
   d'une ligne (documents.js), « Déposer un justificatif » ou le NOM d'un
   onglet Avant / Pendant / Après de la fiche (sessions.js). Posée et retirée
   par la vue pendant le survol ; un contour et non un fond : c'est une visée,
   pas un état. Sans `.btn` devant : un bouton d'onglet n'en porte pas.
   ⚠ Ajoutée EN FIN DE FICHIER, dans son bloc (CONVENTIONS § « app.css est un
   fichier partagé ») — la première écriture l'avait glissée au milieu. */
.depot-cible{ outline:2px dashed var(--poppy); outline-offset:2px; }

/* ═══════════════════════════════════════════════════════════════════════════
   LE PLANNING — LA MAQUETTE DU 25/09 (dev B)
   ═══════════════════════════════════════════════════════════════════════════
   ⚠ Tout est préfixé `gantt-` et ne peint que cet écran : ajout purement
     additif, aucune règle existante n'est touchée.
   ═══════════════════════════════════════════════════════════════════════════ */

/*
 * ┌──────────────────────────────────────────────────────────────────────────┐
 * │ ⚠⚠ LES TUILES REVIENNENT, ET CE NE SONT PAS LES MÊMES.                   │
 * │                                                                          │
 * │ Julien avait demandé le retrait des cinq `.kpi` le 13/09 : elles         │
 * │ occupaient 110 px en tête d'un écran où la première barre commençait     │
 * │ déjà à 91 % de la hauteur, et la première — « 6 sessions », le fait le   │
 * │ plus neutre de l'écran — portait le liseré ROUGE, donc la couleur        │
 * │ d'alerte (règle du 16/08).                                               │
 * │                                                                          │
 * │ Celles de sa maquette du 25/09 sont d'une autre nature : une rangée      │
 * │ HORIZONTALE de 44 px, icône à gauche, et le ton n'est plus décoratif —   │
 * │ rose pour le volume, bleu pour les gens, ORANGE pour la seule qui        │
 * │ appelle un geste. Elles coûtent un tiers de la hauteur des anciennes.    │
 * └──────────────────────────────────────────────────────────────────────────┘
 */
/* ⚠⚠ RENOMMÉES `.tuiles-ic` LE 26/09 : la maquette du CRM porte exactement les
   mêmes. Les recopier sous un second préfixe aurait donné deux composants pour
   une seule chose, qui divergent à la première correction — dixième fois dans
   ce projet. `gantt-` était un préfixe d'écran ; ce n'en est plus un. */
.tuiles-ic{
  display:grid; grid-template-columns:repeat(auto-fit,minmax(200px,1fr));
  gap:12px; margin-bottom:14px;
}
/* ⚠⚠ L'ICÔNE EST À DROITE ET LE LISERÉ EN HAUT (26/09 au soir, seconde
   maquette du CRM). Ce n'est pas la tuile du tableau de bord (`.kpi`), qui
   porte l'icône à GAUCHE et pas de liseré — et les deux maquettes de Julien
   ne s'accordent pas sur ce point. Elles ont chacune leur raison : le CRM
   pose QUATRE tuiles larges de 355 px, où le libellé en capitales tient sur
   une ligne et où l'icône à droite équilibre le bloc ; le tableau de bord en
   pose NEUF, larges de 250, où l'icône doit identifier la tuile avant qu'on
   lise. La question est posée à Julien au point de coordination : si une
   seule forme doit rester, c'est une ligne à déplacer. */
/*
 * ⚠⚠ L'ICÔNE EST À GAUCHE, ET IL N'Y A PAS DE LISERÉ — 27/09, relu sur les
 *   DEUX maquettes qui emploient ce composant (Demandes et Planning). Je
 *   l'avais posée à droite le 26/09 « d'après la maquette » : j'avais lu de
 *   travers. Les deux dessins montrent la même chose — un carré teinté à
 *   gauche, le texte à sa droite, aucun trait de couleur en haut.
 * ⚠ Le liseré du 26/09 appartient aux tuiles SANS icône (`.kpi`), où il est la
 *   seule marque de ton disponible. Ici l'icône le porte : deux marques de ton
 *   sur la même tuile s'annulent — c'est la leçon du 12/09 sur les deux
 *   couleurs de ligne, appliquée à une carte.
 */
.tuile-ic{
  display:flex; align-items:flex-start; gap:12px;
  padding:12px 14px; border:1px solid var(--line);
  border-radius:var(--r); background:var(--carte);
}
.tuile-ic .ic{
  flex:0 0 auto; width:34px; height:34px; border-radius:9px;
  display:flex; align-items:center; justify-content:center; font-size:17px;
}
/*
 * ⚠⚠ LE LIBELLÉ EST AU-DESSUS, COMME SUR `.kpi` — 26/09.
 *
 *   La maquette lit « DEMANDES EN COURS » puis « 7 » : on sait de quoi on
 *   parle avant de lire le chiffre. L'inverse oblige à remonter. C'est
 *   exactement l'ordre posé sur `.kpi` le 24/09, et ces tuiles-ci l'avaient
 *   gardé à l'envers — deux composants de la même famille qui ne se lisent pas
 *   pareil, ce qui est pire que deux composants différents.
 *
 * ⚠ `order:-1` sur le SEUL libellé, jamais trois `order` en suite : c'est ce
 *   qui rend la règle indifférente à l'ordre du marquage (leçon payée sur les
 *   13 tuiles de `sessions.js`, dont le libellé vient déjà en premier).
 */
.tuile-ic > span:last-child{ display:flex; flex-direction:column; min-width:0; }
.tuile-ic .n{ order:2; font-size:26px; font-weight:800; color:var(--ink); line-height:1.05; }
.tuile-ic .l{
  order:-1; font-size:11px; color:var(--muted); margin:0 0 3px;
  text-transform:uppercase; letter-spacing:.5px; font-weight:600;
}
.tuile-ic .sous{ order:3; font-size:11.5px; color:var(--muted); margin-top:4px; }
.tuile-ic.t-sessions .ic{ background:var(--soft);      color:var(--poppy-dark); }
.tuile-ic.t-gens     .ic{ background:var(--info-bg);   color:var(--info); }
.tuile-ic.t-neutre   .ic{ background:var(--neutre-bg); color:var(--neutre); }
/* ⚠⚠ LE LISERÉ EST RETIRÉ (27/09). Son commentaire disait que « dire deux fois
   la même chose » était voulu — un liseré se voit de loin, une icône de près.
   C'est plausible, et les deux maquettes qui emploient ce composant disent le
   contraire : ni Demandes ni Planning n'en portent. **Un raisonnement qui tient
   debout ne vaut pas un dessin validé** — c'est la leçon du 27/09 au matin,
   payée sur l'icône « € » des modèles. */
/* ⚠ Le vert du CRM : « Validées » n'alerte pas, elle constate — le ton vit sur
   l'icône, jamais sur le bord, qui est réservé à ce qui appelle un geste. */
.tuile-ic.t-ok       .ic{ background:var(--ok-bg);     color:var(--ok); }
/* ⚠ Celle qui APPELLE porte le ton jusqu'au bord : c'est la seule de la rangée
   à demander quelque chose, et une alerte qui ressemble à ses voisines n'en est
   plus une. Elle ne paraît QUE s'il y a à faire — « 0 à affecter » posé en
   permanence est un mot de plus à lire chaque jour pour apprendre qu'il n'y a
   rien (même raison qu'au coin de la frise, 13/09). */
/* ⚠ Celle qui appelle garde son bord teinté sur TOUT le tour — pas un liseré
   en haut : c'est l'aplat qui la distingue, comme sur la maquette du planning. */
.tuile-ic.t-alerte{ border-color:var(--att); background:var(--att-bg); }
.tuile-ic.t-alerte .ic{ background:var(--carte); color:var(--att); }
.tuile-ic.t-alerte .n,
.tuile-ic.t-alerte .l{ color:var(--att); }
/* ⚠ Elle est cliquable : `button` remet le curseur et le focus visible que
   `.tuile-ic` ne porte pas — un bouton qui ne se voit pas au clavier est
   un bouton qu'on ne peut pas atteindre. */
button.tuile-ic{ font:inherit; text-align:left; cursor:pointer; width:100%; }
button.tuile-ic:hover{ border-color:var(--poppy-clair); }
button.tuile-ic:focus-visible{ outline:2px solid var(--poppy); outline-offset:2px; }

/* ─── L'avatar de la colonne de gauche ────────────────────────────────────
   ⚠ Les initiales viennent de `CC.initiales()`, point unique du socle : deux
     écrans qui appliqueraient chacun leur règle se contrediraient sur un nom
     composé — c'est écrit dans `clients.js` depuis le CRM. */
.gantt-ava{
  flex:0 0 auto; width:28px; height:28px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  background:var(--soft); color:var(--poppy-dark);
  font-size:11px; font-weight:700; letter-spacing:.3px;
}
.gantt-lib .gantt-replier{ display:flex; align-items:center; gap:8px; text-align:left; }
.gantt-lib .gantt-ident{ display:flex; flex-direction:column; min-width:0; }
.gantt-lib .gantt-ident .c{ font-size:11.5px; color:var(--muted); }

/* ─── La ligne qui attend une affectation ─────────────────────────────────
   ⚠⚠ ELLE SE VOIT SANS QU'ON LISE SON NOM. C'est la seule ligne de la frise où
      quelque chose est à faire ; la chercher parmi trente revient à faire à la
      main le travail que l'écran est censé faire.
   ⚠ Le fond s'ajoute au badge et au bouton, il ne les remplace pas : une
     couleur seule n'existe ni pour qui ne la distingue pas, ni à l'oreille. */
.gantt-ligne.a-affecter{ background:var(--att-bg); box-shadow:inset 3px 0 0 var(--att); }
.gantt-ligne.a-affecter .gantt-ava{ background:var(--carte); color:var(--att); }
.gantt-affecter{ margin-left:auto; }

/* ─── La bulle d'une barre de la frise (25/09) ─────────────────────────────
   ┌──────────────────────────────────────────────────────────────────────────┐
   │ ⚠⚠ `position:fixed`, ET C'EST LA SEULE QUI TIENNE ICI.                   │
   │                                                                          │
   │ `.gantt-enveloppe` porte `overflow-x:auto` — ce qui en fait un conteneur │
   │ de défilement sur LES DEUX AXES : une bulle en `absolute` y serait       │
   │ rognée, ou ferait défiler la frise en s'ouvrant. C'est le défaut du      │
   │ 03/09 sur le panneau déroulant, et celui que la cellule d'action de      │
   │ `#clients` a évité le 24/09 en renonçant au menu déroulant.              │
   │ `fixed` sort de tout conteneur ; le script la recale si elle déborde.    │
   └──────────────────────────────────────────────────────────────────────────┘ */
.gantt-bulle{
  position:fixed; z-index:60; width:300px; max-width:calc(100vw - 24px);
  background:var(--carte); border:1px solid var(--line);
  border-radius:var(--r); box-shadow:0 8px 28px rgba(43,43,43,.16);
  padding:14px 16px;
}
.gantt-bulle h3{ font-size:14px; margin:0 0 10px; color:var(--ink); }
.gantt-bulle dl{ margin:0; display:flex; flex-direction:column; gap:7px; }
.gantt-bulle .li{ display:flex; align-items:flex-start; gap:9px; font-size:12.5px; }
/* ⚠ L'icône est décorative : la valeur qui suit se suffit, et l'annoncer
   ferait lire « calendrier 15 au 17 septembre » à un lecteur d'écran. */
.gantt-bulle .li .ic{ flex:0 0 16px; color:var(--muted); text-align:center; }
.gantt-bulle .pastille{
  flex:0 0 auto; width:11px; height:11px; border-radius:3px; margin-top:2px;
}
.gantt-bulle .voir{ display:block; margin-top:12px; text-align:right; font-weight:700; }

/* ─── dev A — déplacer une séance sur la frise (25/09) ─────────────────────
   ┌──────────────────────────────────────────────────────────────────────────┐
   │ Glisser-déposer (views/gantt.js) : ces règles ne visent que des classes  │
   │ NEUVES — `deplacable`, `en-glisse`, `gantt-glisse-actif`, le fantôme et  │
   │ la confirmation. Aucune règle existante n'est touchée.                   │
   │ ⚠⚠ `touch-action:none` SUR LES SEULES BARRES DÉPLAÇABLES : au doigt, le │
   │    navigateur ferait défiler la frise au lieu de laisser glisser la      │
   │    séance, et le geste s'annulerait (`pointercancel`). La frise défile   │
   │    toujours depuis ses espaces vides, ses en-têtes et les barres qu'on   │
   │    ne peut pas déplacer.                                                 │
   │ ⚠ Le fantôme ne reçoit AUCUN clic (`pointer-events:none`) : c'est ce qui │
   │   laisse `elementFromPoint` trouver la ligne qu'il survole.              │
   └──────────────────────────────────────────────────────────────────────────┘ */
.gantt-barre.deplacable{ cursor:grab; touch-action:none; }
.gantt-barre.en-glisse{ opacity:.45; }
.gantt-glisse-actif, .gantt-glisse-actif *{ cursor:grabbing; user-select:none; -webkit-user-select:none; }
.gantt-fantome{
  position:fixed; z-index:70; pointer-events:none; box-sizing:border-box;
  border:2px dashed var(--ink); border-radius:2px; background:rgba(43,43,43,.10);
}
.gantt-fantome.refus{ border-color:var(--ko); background:var(--ko-bg); }
.gantt-fantome-texte{
  position:fixed; z-index:71; pointer-events:none; max-width:calc(100vw - 16px);
  padding:5px 9px; border-radius:var(--r); background:var(--ink); color:#fff;
  font-size:12px; font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.gantt-bulle .gantt-deplacer{ display:block; margin:10px 0 0 auto; }
.gantt-deplacer-resume{ margin:0 0 12px; padding-left:18px; display:flex; flex-direction:column; gap:4px; }
.gantt-deplacer-resume li.conflit{ font-weight:700; color:var(--att); }
.gantt-deplacer-avert{ margin:0; padding-left:18px; }
/* L'alerte « l'intervenant change pour toute la session » (26/09) : séparée
   de l'alerte de conflit qui peut la suivre. */
.gantt-deplacer-intervenant{ margin-bottom:10px; }
.gantt-deplacer-nature{
  border:1px solid var(--line); border-radius:var(--r); padding:8px 12px 10px; margin:12px 0;
}
.gantt-deplacer-nature legend{ font-weight:700; padding:0 4px; }
.gantt-deplacer-nature label{ display:flex; align-items:center; gap:8px; padding:4px 0; }

/* ─── dev A — l'Aide : les guides lus dans l'application (25/09) ───────────
   ┌──────────────────────────────────────────────────────────────────────────┐
   │ Écran `#aide` (public/views/aide.js). Le texte vient du serveur, déjà    │
   │ rendu (includes/guides.php) : ces règles ne visent QUE `.guide-*`, et    │
   │ ne touchent à aucune classe existante.                                   │
   │ ⚠ La colonne de texte est BORNÉE (~80 signes) : un guide de trois mille  │
   │   lignes ne se lit pas sur toute la largeur d'un écran de bureau.        │
   │ ⚠ `scroll-margin-top` sur les titres : sans lui, le titre visé par       │
   │   `#aide?chapitre=…` passe SOUS la barre supérieure fixe (--h-topbar).   │
   └──────────────────────────────────────────────────────────────────────────┘ */
.guide-cadre{
  display:grid; grid-template-columns:minmax(190px, 250px) minmax(0, 1fr);
  gap:16px; align-items:start;
}
.guide-sommaire{
  position:sticky; top:calc(var(--h-topbar) + 12px);
  max-height:calc(100vh - var(--h-topbar) - 24px); overflow:auto;
  background:var(--carte); border:1px solid var(--line); border-radius:var(--r);
  padding:8px; font-size:12.5px;
}
.guide-sommaire ul{ list-style:none; margin:0; padding:0; }
.guide-sommaire a{
  display:block; padding:4px 8px; border-radius:var(--r-s); color:var(--ink);
}
.guide-sommaire a:hover{ background:var(--ligne-survol); text-decoration:none; }
.guide-sommaire a[aria-current="true"]{
  background:var(--soft); color:var(--poppy-dark); font-weight:700;
}
a.chip:hover{ text-decoration:none; }
.guide-texte{ max-width:84ch; }
.guide-texte > .corps{ padding:18px 22px; line-height:1.55; }
.guide-texte .guide-titre{ scroll-margin-top:calc(var(--h-topbar) + 12px); }
.guide-texte h2.guide-titre{ font-size:21px; margin:0 0 12px; }
/* ⚠ Pas de filet sur le titre de chapitre : le guide pose déjà un `---`
   devant chacun, et les deux traits se doublaient. */
.guide-texte h3.guide-titre{ font-size:18px; margin:26px 0 10px; color:var(--poppy-dark); }
.guide-texte h4.guide-titre{ font-size:15px; margin:22px 0 8px; }
.guide-texte h5.guide-titre,
.guide-texte h6.guide-titre{ font-size:14px; margin:16px 0 6px; }
.guide-texte p{ margin:0 0 10px; }
.guide-texte ul,
.guide-texte ol{ margin:0 0 10px; padding-left:22px; }
.guide-texte li{ margin:3px 0; }
.guide-texte a{ text-decoration:underline; }
.guide-texte hr{ border:0; border-top:1px solid var(--line); margin:18px 0; }
.guide-texte code{ background:var(--neutre-bg); padding:1px 4px; border-radius:4px; }
.guide-texte .tbl-scroll{ margin:6px 0 14px; }
.guide-encadre{
  margin:12px 0; padding:10px 14px; background:var(--att-bg);
  border-left:4px solid var(--att); border-radius:var(--r-s);
}
.guide-encadre > :last-child{ margin-bottom:0; }
.guide-titre-encadre{ font-size:14px; }
@media (max-width: 900px){
  .guide-cadre{ grid-template-columns:minmax(0, 1fr); }
  .guide-sommaire{ position:static; max-height:260px; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   TABLEAUX : EN-TÊTE VISIBLE ET LIGNES ALTERNÉES (25/09) — dev A.

   Les dettes (a) et (b) de `docs/AUDIT-DESIGN-2026-09-23.md` §2, ouvertes le
   17/08 : « aucune règle nth-child » et « `table.tbl th` n'a pas
   `position:sticky` », sur 173 tableaux et 34 vues.

   ⚠ AJOUT PUREMENT ADDITIF, en fin de fichier : aucune règle au-dessus n'est
     touchée, et ce bloc emploie les jetons de la charte (`--soft2`, `--carte`,
     `--h-topbar`). Il est mesuré dans un vrai navigateur, sur les vraies vues,
     par `tools/lib_banc_tableaux.php` (lancé par `tests_audit_vues`).
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 1. LES LIGNES ALTERNÉES — sur TOUS les tableaux `.tbl` ────────────────
   ⚠⚠ `:where()` N'EST PAS UNE COQUETTERIE : il ramène le poids de la règle à
      (0,1,3), SOUS `tr.selectionnee` (0,2,3) et `tr.cliquable:hover` (0,3,3).
      Écrite `tr:nth-child(even)`, elle pesait (0,2,3) comme la sélection et,
      placée plus bas dans la feuille, elle l'emportait : une ligne choisie sur
      deux perdait sa couleur, sans une erreur. Le banc le vérifie.
   ⚠ `of :not([hidden], .deplie)` : on compte les lignes VISIBLES et
     ordinaires. `supports.js` filtre en masquant des lignes (`tr.hidden`), et
     `emargement.js` insère une ligne `.deplie` sous la ligne ouverte ; comptées,
     elles cassaient l'alternance — deux fonds identiques côte à côte après un
     filtre, et toute la fin du tableau qui s'inversait à chaque dépliage.
   ⚠ Un navigateur antérieur à 2023, qui ne connaît pas `of S`, écarte le
     sélecteur à l'intérieur de `:where()` sans invalider le reste : pas de
     zébrage, c'est-à-dire l'état d'avant. L'absence ne recrée aucun défaut.
   ⚠ `--soft2` est PLUS CLAIR que `--fond` (luminance 0,934 contre 0,899) : un
     texte qui passe le seuil sur le fond de page le passe ici. Le plus serré,
     `--muted`, donne 4,71:1 (5,02:1 sur blanc) ; le contour d'une commande
     (`--line-champ`) 3,23:1, au-dessus des 3:1. Le survol reste distinct :
     1,16:1 sur `--soft2`.
   ⚠⚠ LA SÉLECTION, ELLE, L'EST MOINS : `--soft` fait 1,08:1 contre une ligne
      `--soft2`, là où elle faisait 1,15:1 contre le blanc. C'est sa teinte,
      plus rosée, qui la porte désormais — le raisonnement que l'en-tête de
      cette feuille déconseille pour un état durable. Signalé à dev B, qui
      tient la charte ; depuis le 30/09 la sélection porte EN PLUS un liseré
      coquelicot à gauche (bloc « SOCLE : POINTS DE DEV B », fin de fichier),
      qui ne dépend pas de la teinte.
   ⚠ Un fond posé EN LIGNE sur un `<tr>` (le liseré « à relancer » de
     `clients.js`) l'emporte toujours : l'alerte garde sa couleur.
   ⚠⚠ UN TABLEAU À SECTIONS N'EST PAS ZÉBRÉ (`:not(:has(… tr.ligne-section))`).
      L'éditeur de questionnaires (`questionnaires.js`, 25/09) distingue une
      ligne de section par UN seul signe : le fond `--soft2` de ses cellules,
      le même que le zébrage. Sur « Bilan formateur » chez la cliente (20
      lignes dont 4 sections, lu le 25/09), le zébrage accolait une section à
      deux lignes teintées : une bande de trois. Ne pas compter les sections
      dans l'alternance (`of :not(.ligne-section)`) était PIRE : leurs deux
      voisines se suivent alors dans le décompte, l'une des deux est paire, et
      les QUATRE sections se collaient chacune à une ligne teintée. Dans ces
      tableaux, les sections découpent déjà la liste en groupes courts : elles
      restent les seules lignes teintées. Le banc rend l'éditeur réel.
      ⚠ Dans `:where()` lui aussi : `:has()` ne doit rien peser. Un navigateur
        qui ne connaît pas `:has()` (Firefox avant la 121, fin 2023) écarte le
        `:where()` entier : aucun zébrage, l'état d'avant. */
table.tbl:where(:not(:has(> tbody > tr.ligne-section))) > tbody > tr:where(:nth-child(even of :not([hidden], .deplie))){
  background:var(--soft2);
}

/* ⚠⚠ LA COLONNE FIXE DU SUIVI SUIT LA LIGNE. `.suivi-tbl .suivi-fixe` porte un
   fond opaque (`--carte`) — il le FAUT, c'est lui qui masque les étapes qui
   défilent dessous. Sans cette règle, la première cellule restait blanche sur
   une ligne sur deux : un zébrage troué à gauche. `inherit` reprend le fond
   calculé de la ligne alternée (`--soft2`, opaque lui aussi) ; une ligne
   impaire garde le blanc. ⚠ Ni sélection ni survol ici : les lignes qui
   portent `.suivi-fixe` (vue détaillée) ne sont ni `.cliquable` ni
   `.selectionnee` — un sélecteur pour un état qui n'arrive pas serait une
   règle morte qu'on croirait vivante. */
.suivi-tbl > tbody > tr:where(:nth-child(even of :not([hidden], .deplie))) > .suivi-fixe{
  background-color:inherit;
}

/* ── 2. L'EN-TÊTE QUI RESTE VISIBLE — sur les LISTES longues ───────────────
   ⚠⚠ POURQUOI UNE HAUTEUR BORNÉE, ET PAS UN `sticky` SUR LA PAGE. Un en-tête
      collant s'accroche au plus proche ancêtre qui défile. Ici il y en a deux :
      `.tbl-scroll` (`overflow-x:auto`, qui calcule `auto` sur LES DEUX axes)
      et `.panel` (`overflow:hidden`). Collé à la page, l'en-tête ne collerait à
      rien — c'est le défaut de la frise du planning, réparé le 23/09 à
      `.gantt-enveloppe` : déclaré collant, il partait avec la page. Le remède
      est le même : le conteneur reçoit une HAUTEUR, et l'en-tête colle en
      haut de LUI.
   ⚠⚠ POURQUOI CINQ ÉCRANS NOMMÉS, ET PAS TOUS LES `.tbl-scroll`. Une liste
      bornée défile DANS sa boîte. Trois vues réécrivent leur tableau sur place
      et ne rétablissent que le défilement de la PAGE (`window.scrollTo(0, y)`
      dans `documents.js`, `evaluations.js`, `modeles.js`) : bornées, elles
      perdraient la ligne qu'on vient de traiter à chaque geste — le défaut que
      ce `scrollTo` a été écrit pour corriger. Les cinq ci-dessous repartent
      d'un écran « Chargement » (la position est déjà perdue aujourd'hui) ou
      mettent à jour leurs cases sur place (le suivi) : rien ne s'y perd.
      Au 25/09 sur l'instance de la cliente : 432 sessions (25 par page),
      40 clients (25 par page), 21 020 lignes de journal (50 par page),
      jusqu'à 58 sessions par mois au suivi (juin ; 56 en octobre). La
      facturation n'a que 3 pièces, mais montera à 200 par écran.
   ⚠ `--tbl-hors-liste` : la place qu'il faut sous la liste, une fois la page
     descendue jusqu'à elle — la pagination (44 px), le bord et la marge du
     panneau (17 px). Barre du haut, boîte et pagination tiennent alors
     ensemble dans la fenêtre (le banc le mesure). Un seul jeton, comme
     `--gantt-hors-frise` : deux valeurs écrites séparément divergent.
   ⚠ `max(280px, …)` : sur une fenêtre très basse (téléphone couché), la boîte
     garde au moins quatre lignes de liste sous son en-tête.
   ⚠ `100dvh` après `100vh` : un navigateur qui ne le connaît pas garde la
     première déclaration.
   ⚠⚠ `scroll-padding-top` : une commande qui reçoit le focus au clavier ne doit
      pas finir SOUS l'en-tête collant (WCAG 2.4.11, focus non masqué). Le
      navigateur la fait défiler sous cette marge. 48 px couvrent un en-tête
      d'une ligne (38 px mesurés) ; le suivi détaillé en a DEUX (étapes sur
      trois lignes et leur compte : 121 px), d'où 136 px. Le banc met une
      commande sous l'en-tête, lui donne le focus et mesure où elle arrive. */
:is(#zone-sessions, #zone-clients, #zone-journal, #vue-suivi) > .tbl-scroll,
#zone-factu .tbl-scroll{
  --tbl-hors-liste:72px;
  overflow-y:auto;
  max-height:max(280px, calc(100vh - var(--h-topbar) - var(--tbl-hors-liste)));
  max-height:max(280px, calc(100dvh - var(--h-topbar) - var(--tbl-hors-liste)));
  scroll-padding-top:48px;
}
/* ⚠ Le suivi pose sa liste À MÊME LA PAGE, sans panneau : ses lignes impaires
     prenaient le gris du fond, les paires `--soft2`, et le bas de la boîte
     bornée coupait des lignes grises sur du gris — rien ne disait où la liste
     s'arrêtait. Elle reçoit le cadre de `.gantt-enveloppe`, l'autre boîte
     bornée posée sur la page. Le banc lit le fond sur lequel se détache une
     ligne impaire (le blanc de la boîte, pas le gris de la page) et la
     bordure du bas. */
#vue-suivi > .tbl-scroll{
  scroll-padding-top:136px;
  background:var(--carte); border:1px solid var(--line); border-radius:var(--r);
}

/* ⚠⚠ C'EST LE `<thead>` QUI COLLE, PAS CHAQUE `<th>`. Le suivi détaillé a DEUX
      lignes d'en-tête (les temps, puis les étapes) : des `<th>` collés à
      `top:0` s'empileraient tous au même endroit, la seconde ligne recouvrant
      la première. Le `<thead>` colle d'un bloc.
   ⚠ `z-index:3` : les cellules fixes du suivi (`.suivi-fixe`) défilent en
     `z-index:2` ; l'en-tête doit passer AU-DESSUS d'elles, sinon la colonne
     « Session » glisse par-dessus lui. Le banc descend la liste et lit ce qui
     est peint au centre du coin « Session » : une cellule de l'en-tête, pas
     celle d'une session (en `z-index:1`, il lisait `TD.suivi-fixe`).
   ⚠ Le fond du `<thead>` couvre une éventuelle cellule `<td>` d'en-tête, qui
     n'en a pas : les lignes ne doivent pas se lire au travers. */
:is(#zone-sessions, #zone-clients, #zone-journal, #vue-suivi) > .tbl-scroll > table.tbl > thead,
#zone-factu .tbl-scroll > table.tbl > thead{
  position:sticky; top:0; z-index:3;
  background:var(--carte);
}
/* ⚠ La facturation pose `.tbl-scroll` SUR `.corps`, qui porte 14 px de marge
     intérieure (`.panel>.corps`). Un en-tête collant se cale SOUS la marge
     intérieure de sa boîte : il collait à 14 px du bord, et les lignes qui
     défilaient se lisaient dans la bande au-dessus de lui. `top:-14px` le
     ramène au bord. ⚠ Deux valeurs écrites à deux endroits : si la marge de
     `.corps` change, le banc le voit (écart mesuré entre l'en-tête et le haut
     de la boîte). */
#zone-factu .corps.tbl-scroll > table.tbl > thead{ top:-14px; }

/* ⚠⚠ LE FILET SOUS L'EN-TÊTE EST PEINT PAR LE FOND, PAS PAR LA BORDURE. En
      `border-collapse:collapse`, une bordure appartient à la GRILLE du
      tableau, pas à la cellule : l'en-tête collait, son filet de 2 px restait
      en place et partait avec les lignes. Vu à la capture le 25/09 : sous
      l'en-tête collé, plus un pixel de `--line`, et les lignes venaient
      buter contre lui sans rien qui les sépare. Le même filet, peint en fond
      de la cellule, voyage avec elle ; la bordure passe à 0 pour ne pas le
      doubler au repos. Toutes les lignes d'en-tête le portent, comme avant :
      le suivi garde son filet entre les temps et les étapes.
   ⚠ `background-image` et non `background` : les teintes du suivi
     (`.suivi-phase`, le coin `.suivi-fixe`, le survol d'une étape) restent. */
:is(#zone-sessions, #zone-clients, #zone-journal, #vue-suivi) > .tbl-scroll > table.tbl > thead th,
#zone-factu .tbl-scroll > table.tbl > thead th{
  border-bottom-width:0;
  background-image:linear-gradient(to top, var(--line) 2px, transparent 2px);
}

/* ⚠⚠ SUR PAPIER, LA BOÎTE REPREND SA HAUTEUR. Bornée, une liste imprimée
   s'arrête à la hauteur de l'écran, sans rien qui le dise. Imprimé en PDF
   depuis chromium le 25/09, liste descendue de 300 px : SANS ce bloc, 8
   sessions sur 25 et 3 lignes de suivi sur 40 sortaient ; AVEC, les 25 et les
   40, l'en-tête répété en tête de chaque page. Le `<thead>` repasse dans le
   flux (c'est ce qui le fait répéter) et le filet redevient une bordure.
   ⚠ C'est LA PAGE AFFICHÉE qui sort (25 sessions sur 432), en entier en
     HAUTEUR seulement : `.tbl-scroll{overflow-x:auto}` (plus haut, inchangé)
     coupe toujours à droite les étapes du suivi détaillé qui dépassent la
     feuille — 9 colonnes sur 16 au PDF du 25/09, comme avant ce bloc.
   ⚠ Le banc joue ces règles pour de bon : il rejoue la feuille avec
     `@media print` lu comme `@media all` et mesure la boîte, l'en-tête et le
     filet — pas seulement qu'une règle existe. */
@media print{
  :is(#zone-sessions, #zone-clients, #zone-journal, #vue-suivi) > .tbl-scroll,
  #zone-factu .tbl-scroll{ max-height:none; }
  :is(#zone-sessions, #zone-clients, #zone-journal, #vue-suivi) > .tbl-scroll > table.tbl > thead,
  #zone-factu .tbl-scroll > table.tbl > thead{ position:static; }
  /* ⚠ Un fond ne s'imprime pas : sans la bordure, le papier perdait le filet
       de l'en-tête — la seule de ses quatre marques qui ne soit pas du texte. */
  :is(#zone-sessions, #zone-clients, #zone-journal, #vue-suivi) > .tbl-scroll > table.tbl > thead th,
  #zone-factu .tbl-scroll > table.tbl > thead th{ border-bottom-width:2px; background-image:none; }
}

/* ─── Une ligne de liste qui appelle un geste (26/09) ──────────────────────
   ⚠⚠ MÊME RAISON QUE LA LIGNE « À AFFECTER » DE LA FRISE : sur une liste de
      dix demandes, celles qui dorment doivent se voir sans qu'on lise la
      colonne « Statut » une par une. Trois lignes sur dix, c'est la journée de
      quelqu'un.
   ⚠ La teinte s'AJOUTE au badge et à la date colorée — jamais seule : une
     couleur n'existe ni pour qui ne la distingue pas, ni à l'oreille.
   ⚠ Additif : une ligne qui ne pose pas la classe ne bouge pas d'un pixel. */
/* ⚠⚠ ET LE 26/09 AU SOIR, LA MESURE A DIT QUE C'ÉTAIT TROP : SEPT LIGNES SUR
      DIX portaient la teinte, le liseré et la date colorée. Sept alertes sur
      dix, c'est un écran jaune — c'est-à-dire aucune alerte, et la règle est
      écrite depuis le 16/08 (« sept badges rouges sur un dossier neuf »).
      La maquette n'en teinte que TROIS : celles qui dorment CHEZ LE CLIENT,
      les seules dont le geste ne dépend pas de nous. Les quatre autres
      attendent une action de notre part — elles sont dans le bandeau du haut,
      qui est fait pour ça, et la colonne « Statut » les nomme.
   ⚠ La teinte passe de l'ambre au coquelicot très clair : l'ambre est la
     couleur de l'ATTENTE dans cette charte (« devis en attente »), et il la
     dépensait sur une ligne de liste. */
table.tbl tbody tr.ligne-attente{ background:var(--soft); }
table.tbl tbody tr.ligne-attente:hover{ background:var(--soft); }
/* ⚠ La date dit DEPUIS QUAND ça dort : c'est l'information qui décide du
   geste, et elle se lit à l'autre bout de la ligne. */
table.tbl tbody tr.ligne-attente .date-attente{ color:var(--poppy-dark); font-weight:600; }

/* ─── 21. LE BANDEAU « DEMANDES SANS MOUVEMENT » (26/09) ──────────────────
   ⚠⚠ IL A EU DEUX FORMES DANS LA MÊME JOURNÉE, et la seconde est la bonne.
      Le matin, deux colonnes de listes : « attend le client » et « attend
      nous » sont deux journées différentes, et elles ne se mélangent pas dans
      une liste à puces — le raisonnement tient toujours. Mesuré sur la
      capture du soir : ce bloc occupait **220 px**, plus que les quatre
      tuiles de mesure réunies, et repoussait le tableau sous la ligne de
      flottaison. **Un bloc d'alerte plus grand que le contenu qu'il annonce
      finit par être sauté des yeux** — c'est le bandeau permanent du 11/09,
      par la taille au lieu de la fréquence.
   ⭐ La distinction n'est pas perdue, elle est RÉSUMÉE sur une ligne ; QUELLES
      demandes est à un clic, dans le tableau, par le bouton qui le filtre.
   ⚠ `flex-wrap` : sous ~620 px le bouton passe à la ligne plutôt que d'écraser
     le titre à deux mots par ligne. */
.dem-suivre{
  display:flex; flex-wrap:wrap; align-items:center; gap:12px; padding:12px 14px;
}
.dem-suivre .pastille-ic{
  flex:0 0 auto; width:32px; height:32px; border-radius:50%;
  display:grid; place-items:center; font-size:16px;
  background:var(--att); color:#fff;
}
.dem-suivre .txt{ display:flex; flex-direction:column; min-width:200px; flex:1 1 320px; }
.dem-suivre .t{ font-size:14px; font-weight:700; color:var(--att); }
.dem-suivre .s{ font-size:12.5px; color:var(--att); margin-top:1px; }
/* ⚠ Le bouton est BLANC sur l'aplat ambre, jamais coquelicot : le rouge est
   la couleur de l'action principale de l'écran (« + Nouvelle demande »), et
   deux boutons pleins de couleurs différentes sur la même vue se disputent le
   regard. Ici il suffit qu'il se détache du fond. */
.dem-suivre-cta{
  flex:0 0 auto; margin-left:auto;
  background:var(--carte); border-color:var(--att); color:var(--att); font-weight:700;
}
.dem-suivre-cta:hover{ background:#fff; border-color:var(--att); color:var(--att); }

/* ─── Une rangée de pastilles de filtre (26/09) ───────────────────────────
   ⚠⚠ ELLE ÉTAIT EN `.actions-ligne`, QUI ALIGNE À DROITE — la classe des
      boutons d'une CELLULE de tableau. La phrase d'aide sortait donc par la
      gauche, hors du panneau, tronquée en « ligne sur un statut : ». C'est le
      défaut du 03/09 sur la barre d'outils de la frise, et le troisième
      emploi fautif de cette classe dans le projet.
   ⚠ `flex-wrap` : neuf statuts plus « Toutes » ne tiennent pas sur une ligne
     dans une fenêtre étroite, et une rangée qui déborde emporte les derniers
     filtres hors de l'écran sans rien dire.
   ⚠ L'aide est POUSSÉE à droite, jamais posée en tête : c'est une note de
     service à côté de neuf commandes, pas leur intitulé. */
.dem-chips{ display:flex; flex-wrap:wrap; align-items:center; gap:7px; margin-bottom:12px; }
.dem-chips-aide{ margin-left:auto; font-size:11.5px; color:var(--muted); }

/* ⚠ « Devis en attente » N'EST PAS UNE ALERTE : c'est un compte. Sur la
   maquette du CRM, les quatre tuiles sont blanches et seule l'icône porte la
   couleur — le bord teinté est réservé à ce qui APPELLE un geste (la session
   à affecter, sur le planning). Une rangée dont une tuile crie apprend à lire
   les trois autres moins vite. */
.tuile-ic.t-att .ic{ background:var(--att-bg); color:var(--att); }
/* ⚠ 34 → 44 px et le rembourrage avec : sur la maquette l'icône est la marque
   de la tuile, et à 34 px elle se lisait comme une puce. */
.tuile-ic{ padding:14px 16px; }
.tuile-ic .ic{ width:44px; height:44px; border-radius:12px; font-size:20px; }

/* ─── La barre de filtres du CRM (26/09, maquette de Julien) ──────────────
   ⚠⚠ UNE ÉTIQUETTE AU-DESSUS DE CHAQUE COMMANDE, et des largeurs ÉGALES.
      Sans étiquette, un filtre actif perd son nom : « Haute », seul dans une
      rangée de quatre listes, ne dit pas de quoi il parle. Et « Tous les
      marchés » s'étirait sur 530 px — plus que les trois autres réunies —
      parce qu'il était le seul à pouvoir grandir dans une rangée en `flex`.
   ⚠ `auto-fit`+`minmax` : la zone est bornée par la barre latérale, et un
     seuil de FENÊTRE ne voit pas la place réelle (leçon du 12/09). */
.dem-filtres{
  display:grid !important; gap:10px 14px; align-items:end;
  grid-template-columns:repeat(auto-fit, minmax(180px, 1fr));
}
.dem-filtres .f-q{ grid-column:span 2; }
.dem-filtres .f,
.dem-filtres-2 .f{ display:flex; flex-direction:column; gap:4px; min-width:0; }
.dem-filtres .f > label,
.dem-filtres-2 .f > label{ font-size:12px; color:var(--muted); font-weight:600; }
.dem-filtres .f .champ-recherche{ width:100%; }
.dem-filtres .f > select{ width:100%; }
/* ⚠ Le bouton tient sur la LIGNE DE BASE des commandes, pas sur celle des
   étiquettes : aligné en haut, il flottait au-dessus des listes. */
.dem-plus{ align-self:end; justify-self:end; color:var(--poppy); white-space:nowrap; }
.dem-plus:hover{ color:var(--poppy-dark); }
.dem-filtres-2[hidden]{ display:none; }

/* ⚠ La flèche neutre d'un en-tête triable s'efface, celle de la colonne SUIVIE
   est pleine : l'affordance existe partout, la décision se voit d'un coup. */
table.tbl th .fleche.suivie{ opacity:1; font-size:9px; }
/* ⚠ `.btn.fantome` pèse (0,2,0) et écrasait `.dem-plus` (0,1,0) : le lien
   restait gris. Deuxième fois qu'une spécificité insuffisante rend une règle
   muette (les `.droite` sur un `<th>`, 12/09). */
.btn.dem-plus{ color:var(--poppy); }
.btn.dem-plus:hover{ color:var(--poppy-dark); background:var(--soft2); }

/* ⚠ DANS UNE BARRE DE FILTRES, LE CHAMP DE RECHERCHE EST UNE COMMANDE COMME
   LES AUTRES. `.champ-recherche` porte le fond rosé de la barre du haut, où il
   se détache d'un bandeau blanc ; posé à côté de trois listes blanches, il
   devenait le seul champ teinté de la rangée — donc le seul à avoir l'air
   rempli. Même bordure, même hauteur, même fond que les `select` voisins.
   ⚠ Portée à CETTE barre : la recherche globale garde sa pilule. */
.dem-filtres .champ-recherche input{
  background:var(--carte); border-color:var(--line-champ);
  height:36px; border-radius:var(--r-s);
}
/* ⚠ La pastille ACTIVE est pleine (26/09, maquette). En contour, elle se
   distinguait des neuf autres par une nuance de rouge sur du blanc — et l'on
   ne voyait pas, d'un coup d'œil, sur quel statut la liste était filtrée. Le
   plein est la seule marque qui ne demande pas de comparer.
   ⚠ Portée aux pastilles de CE filtre : `.chip.actif` sert huit écrans et son
     contour y va très bien ; ici elles sont dix côte à côte. */
.dem-chips .chip.actif{ background:var(--poppy); border-color:var(--poppy); color:#fff; }
.dem-chips .chip.actif:hover{ background:var(--poppy-dark); border-color:var(--poppy-dark); }

/* ⚠⚠ LE SECOND RANG DE FILTRES EST UNE RANGÉE, PAS UNE GRILLE (26/09).
   Repris sur la même grille que le premier, ses deux commandes se retrouvaient
   à un écran l'une de l'autre, et la pastille « Masquer les demandes closes »
   s'étirait sur TOUTE sa colonne — une pastille large de 600 px n'est plus une
   pastille, c'est un champ. Vu sur une capture, invisible dans le code.
   ⚠ `.f{ align-items:flex-start }` : dans une colonne flex, un enfant s'étire
     par défaut sur toute la largeur. C'est le défaut du 11/09 (`width:100%`
     sur un enfant de flex) par l'autre bout — quatrième règle de flexbox payée
     sur cet écran. */
.dem-filtres-2{
  display:flex; flex-wrap:wrap; gap:10px 18px; align-items:flex-end;
  padding:0 14px 12px;
}
.dem-filtres-2 .f{ align-items:flex-start; }
.dem-filtres-2 .f[hidden]{ display:none; }

/* ─── 22. LA TUILE DE MESURE PORTE SON ICÔNE (26/09, maquette de Julien) ───
   ⚠⚠ LE TON QUITTE LE LISERÉ POUR L'ICÔNE. Le liseré de 4 px en haut était le
      porteur de la couleur depuis le 24/09 ; sur les DEUX maquettes validées
      (Demandes et Tableau de bord) la tuile n'a aucun liseré et la couleur vit
      dans un carré arrondi, à gauche du nombre. Ce qui alerte alerte encore —
      le signal n'est pas perdu, il change de place —, et l'on gagne ce que le
      liseré ne donnait pas : **on reconnaît la tuile sans lire son libellé**.
      C'est exactement la forme de `.tuile-ic`, posée le 26/09 sur le CRM et le
      planning : les deux familles convergent au lieu de diverger.
   ⚠ Additif : une tuile qui ne passe pas d'icône rend un `.ic` vide, large de
     rien (`:empty`), et ne bouge pas d'un pixel. Les 81 tuiles du dépôt sont
     donc intactes tant qu'on ne leur en donne pas une.
   ⚠ L'ORDRE DE LECTURE NE CHANGE PAS : le libellé reste AU-DESSUS du nombre
     (décision du 24/09, 81 tuiles, 17 écrans). La maquette du tableau de bord
     l'écrit en dessous, celle du CRM au-dessus — les deux ne s'accordent pas
     entre elles, et deux dialectes dans la même application sont pires que
     l'un ou l'autre. Question posée à Julien au point de coordination. */
/*
 * ⚠⚠ RÉGRESSION DU 26/09 AU MATIN, CORRIGÉE LE SOIR — ET ELLE NE SE VOYAIT
 *    QUE SUR UN AUTRE ÉCRAN QUE CELUI QU'ON REPRENAIT.
 *
 *   `.kpi` était passée en `flex-direction:row` pour porter une icône à
 *   gauche, avec le texte dans un `.txt`. Mais **une tuile qui n'a pas été
 *   adaptée ne rend pas de `.txt`** : elle pose `.n`, `.l` et `.sous` en
 *   enfants directs — et les trois se sont retrouvés CÔTE À CÔTE. Mesuré sur
 *   une capture de l'écran Sessions : six tuiles empilées de travers, et
 *   « 48 520,00 € » **superposé à son propre sous-titre**.
 *   ⚠ Treize tuiles de `sessions.js` (lot A) étaient dans ce cas, et aucune
 *     batterie ne pouvait le dire : la règle est valide, le marquage aussi,
 *     c'est leur RENCONTRE qui ne l'est pas.
 *
 * ⭐ La direction en ligne appartient donc à la variante qui la demande —
 *   `.kpi-compacte`, posée par la vue quand elle passe une icône — et jamais
 *   au composant. **Une règle qui suppose un marquage doit être portée par la
 *   classe que ce marquage pose.**
 * ⚠ Et surtout pas `:has(> .txt)` : un sélecteur dont l'absence recrée le
 *   défaut est un pari (11/09). Une classe posée par la vue marche partout.
 *
 * ⚠ `.kpi{ display:flex; flex-direction:column; }` est posée UNE fois, section
 *   6 (« LA TUILE DE MESURE »). La déclaration identique qui suivait ce
 *   commentaire est retirée le 30/09 (doublon signalé par dev B le 27/09).
 *   Vérifié avant : aucune règle de même poids posée entre les deux ne vise un
 *   élément qui porte aussi `.kpi` — la cascade ne change pas, et les
 *   captures des écrans à tuiles non plus.
 */
.kpi.kpi-compacte{ flex-direction:row; align-items:center; gap:12px; }
/* ⚠ 26/09/2026 : le ton de la tuile compacte vit sur son icône, pas sur le liseré. */
.kpi.kpi-compacte{ border-top-color:var(--line); }
.kpi > .txt{ display:flex; flex-direction:column; min-width:0; flex:1 1 auto; }
.kpi > .ic{
  flex:0 0 auto; width:44px; height:44px; border-radius:12px;
  display:flex; align-items:center; justify-content:center;
  background:var(--soft); color:var(--poppy-dark);
}
.kpi > .ic:empty{ display:none; }
/* ⚠ Le chevron ne paraît que sur une tuile qui MÈNE quelque part : posé sur
   toutes, il promettrait un clic aux quatre qui n'en ont pas. */
.kpi > .chev{ flex:0 0 auto; color:var(--muted); font-size:20px; line-height:1; }
a.kpi:hover > .chev{ color:var(--poppy); }
/* ⚠⚠ UN MONTANT NE TIENT PAS À 38 px DANS 170 px, et il débordait SANS que
   rien ne le dise : le texte sortait par la droite et la tuile voisine le
   peignait par-dessus — « 14 650,0( » à l'écran. Vu sur une capture, jamais
   dans le code : aucune règle n'est fausse, c'est la LARGEUR du texte rendu
   qui ne tient pas. La maquette fait pareil — ses tuiles de montant portent un
   nombre plus petit que celles qui comptent. */
/* ⚠⚠ ET SURTOUT PAS `overflow-wrap:anywhere` : essayé d'abord, il a coupé
   « 14 650,00 € » en « 14 650,0 » puis « 0 € » — un montant scindé au milieu
   de ses centimes est FAUX à la lecture, bien pire qu'un montant serré. Un
   nombre ne se coupe jamais ; c'est la PLACE qu'on lui donne. */
.kpi.kpi-long .n{ font-size:26px; }
/* ⚠ 26/09/2026 : insécable SEULEMENT dans la tuile compacte, où `.kpi-long` règle la taille ;
   posé sur toutes les tuiles, il faisait sortir un long montant de sa tuile (Statistiques). */
/* ⚠⚠ RECTIFICATIF DU 26/09, 15 h — MESURÉ DANS UN NAVIGATEUR : CE N'ÉTAIT PAS L'INSÉCABLE.
   `CC.eur()` (Intl, fr-FR) sépare les milliers par une espace fine INSÉCABLE et met une
   insécable devant « € » : un montant ne se coupe jamais, avec ou sans `nowrap`. Rendu sur
   la feuille de 36080ff (insécable retiré de ces tuiles), « 1 007 264,50 € » sortait ENCORE
   de sa tuile des Statistiques, de 100 px à 1440 px (tools/tests_tuiles_kpi.php). Ce qui
   manquait, c'est une TAILLE qui cède : l'ajustement de core.js (section 7 bis), qui
   descend le nombre par paliers (38 → 32 → 26 → 22 → 20 px) tant qu'il dépasse.
   L'insécable revient donc sur TOUTES les tuiles : un nombre écrit avec une espace
   ordinaire (« 213,5 j », « 83 % ») se couperait sinon en deux lignes dans une case
   étroite — et coupé, il ne dépasse plus : l'ajustement ne le verrait pas. */
.kpi .n{ min-width:0; white-space:nowrap; }
/* ⚠ Et la grille laisse la place d'un montant AVEC son icône : 44 px d'icône
   + 12 de gouttière + ~150 de texte + 28 de rembourrage. `minmax(170px)` la
   refusait d'avance, quelle que soit la taille du texte. */
/* ⚠ 26/09/2026 : la grille large seulement pour les tuiles compactes ; les autres gardent 170 px. */
.kpis:has(> .kpi-compacte){ grid-template-columns:repeat(auto-fit,minmax(250px,1fr)); }

/*
 * ⚠⚠ LA TUILE COMPACTE : LE NOMBRE D'ABORD, LE LIBELLÉ EN CASSE NORMALE.
 *
 *   Les deux maquettes de Julien ne s'accordent PAS sur cet ordre — le CRM
 *   écrit le libellé au-dessus, le tableau de bord en dessous —, et elles ont
 *   raison toutes les deux : c'est la PLACE qui diffère, pas le goût. Le CRM
 *   pose QUATRE tuiles, larges de 355 px ; le tableau de bord en pose NEUF,
 *   larges de 250. Une icône prend 56 px à la colonne de texte : il en reste
 *   ~150, où « SESSIONS CONFIRMÉES À VENIR » en capitales interlettrées passe
 *   sur DEUX lignes et pousse le nombre en bas de la tuile — mesuré sur la
 *   capture. En casse normale il tient sur une.
 *
 * ⚠ Ce n'est donc pas un second dialecte : c'est une tuile LARGE et une tuile
 *   COMPACTE, et l'icône, qui identifie la tuile sans qu'on lise, tient le
 *   rôle que le libellé-en-tête tenait sans elle.
 * ⚠ La classe est posée PAR LA VUE, jamais déduite par `:has()` : un
 *   sélecteur dont l'absence changerait l'ordre de lecture est un pari
 *   (règle du 11/09).
 */
.kpi.kpi-compacte .l{
  order:1; margin:2px 0 0;
  text-transform:none; letter-spacing:0; font-size:12.5px; font-weight:500;
}
.kpi.kpi-compacte .n{ order:0; }
.kpi.kpi-compacte .sous{ order:2; margin-top:5px; }
/* Le ton vit désormais sur l'icône. */
.kpi.navy   > .ic{ background:var(--info-bg);   color:var(--info); }
.kpi.ok     > .ic{ background:var(--ok-bg);     color:var(--ok); }
.kpi.att    > .ic{ background:var(--att-bg);    color:var(--att); }
.kpi.ko     > .ic{ background:var(--ko-bg);     color:var(--ko); }
.kpi.neutre > .ic{ background:var(--neutre-bg); color:var(--neutre); }

/* ─── 23. « À TRAITER » — le bloc d'alertes du tableau de bord (26/09) ─────
   ⚠⚠ UNE ALERTE QUI NE MÈNE NULLE PART N'EST PAS UNE ALERTE. Les deux
      bandeaux étaient des pavés de prose pleine largeur, sans un seul bouton :
      on apprenait qu'un bilan est en retard, et il fallait chercher soi-même
      par quel écran on le traite. La maquette leur donne un TITRE, une ligne
      d'explication, et LE GESTE à droite — le raisonnement de la colonne
      « Action » du CRM.
   ⚠ Le ton vit dans la pastille et dans un liseré gauche de 3 px, pas dans un
     aplat pleine largeur : trois alertes en aplat font un écran alarmé.
   ⚠ `flex-wrap` : sous ~560 px le bouton passe à la ligne plutôt que d'écraser
     le titre à deux mots par ligne. */
.dash-at-bloc{ margin:0 0 16px; }
/* ⚠ Le titre hors carte n'existe plus : il a rejoint `.dash-at-liste`. La
     règle est gardée vide plutôt que supprimée — `pour-info` s'y accroche. */
/* ⚠⚠ UN BLOC QUI NE PORTE QUE DE L'INFORMATION NE SE TITRE PAS EN ROUGE
   (26/09, décision de Jérôme). « À traiter » en coquelicot au-dessus de
   « Pennylane n'est pas encore en service : rien ne vous est demandé » disait
   le contraire de sa propre ligne, tous les jours jusqu'à la bascule. La vue
   pose `pour-info` (et le titre « Pour information ») quand aucune ligne
   n'est `ko` ni `att` ; le titre prend alors l'encre du texte. */
.dash-at-bloc.pour-info > h2{ color:var(--ink); }
.dash-at-liste{
  border:1px solid var(--line); border-radius:var(--r);
  background:var(--carte); box-shadow:var(--ombre); overflow:hidden;
}
/* ⚠⚠ CHAQUE LIGNE EST UN ENCART TEINTÉ, PAS UNE RANGÉE DE TABLEAU (27/09,
     maquette). Trois défauts tenaient ensemble : le titre flottait hors de la
     carte, les lignes se touchaient par un filet, et l'urgence ne tenait qu'à
     un liseré de 3 px sur le bord gauche. Une alerte posée sur du blanc, sans
     autre marque qu'un trait, se lit comme une ligne de liste.
   ⚠ L'aplat reste PÂLE : c'est la teinte du ton, pas sa couleur pleine. Ce qui
     alerte doit se voir, pas crier — deux encarts saturés sur l'écran d'accueil
     apprendraient à ne plus les lire (règle du 12/09). */
.dash-at-liste > h2{ font-size:13px; font-weight:700; color:var(--poppy);
                     margin:0; padding:12px 14px 2px; }
.dash-at-bloc.pour-info .dash-at-liste > h2{ color:var(--ink); }
.dash-at{
  display:flex; flex-wrap:wrap; align-items:center; gap:12px; padding:11px 14px;
  border-radius:var(--r-s); margin:8px 12px;
}
.dash-at.att{  background:var(--att-bg); }
.dash-at.ko{   background:var(--ko-bg); }
.dash-at.info{ background:var(--info-bg); }
.dash-at-chev{ flex:0 0 auto; color:var(--muted); font-size:18px; line-height:1; }
/* ⚠⚠ LE ROND DE L'ICÔNE SE DÉTACHE DE L'APLAT QU'IL PORTE. Il valait la teinte
     de fond de la LIGNE : posé dessus, il devenait invisible, et l'icône
     flottait sans support — c'est le défaut du 12/09 sur les deux couleurs de
     ligne qui s'annulent, à l'échelle d'un pictogramme. Repli blanc déclaré
     avant le mélange, pour un navigateur qui ignorerait `color-mix`. */
.dash-at > .ic{
  flex:0 0 auto; width:30px; height:30px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
}
.dash-at > .txt{ display:flex; flex-direction:column; min-width:180px; flex:1 1 320px; }
.dash-at > .txt > .t{ font-size:13.5px; font-weight:700; color:var(--ink); }
.dash-at > .txt > .d{ font-size:12.5px; color:var(--muted); margin-top:2px; }
.dash-at > .btn{ flex:0 0 auto; margin-left:auto; }

.dash-at.att  > .ic{ background:#fff; color:var(--att); }
.dash-at.att  > .ic{ background:color-mix(in srgb, var(--att) 16%, #fff); }

.dash-at.ko   > .ic{ background:#fff; color:var(--ko); }
.dash-at.ko   > .ic{ background:color-mix(in srgb, var(--ko) 13%, #fff); }

.dash-at.info > .ic{ background:#fff; color:var(--info); }
.dash-at.info > .ic{ background:color-mix(in srgb, var(--info) 15%, #fff); }

/* ─── 24. L'ÉCHELLE D'UN GRAPHIQUE EN BARRES (26/09, option `axe`) ────────
   ⚠⚠ SANS ÉCHELLE, UN GRAPHIQUE SE DEVINE. Les barres flottaient sur du
      blanc : on voyait laquelle est la plus haute, et rien de plus. La grille
      donne l'ordre de grandeur sans qu'on lise une seule étiquette.
   ⚠ La graduation et les traits sont produits par la MÊME liste de valeurs
     (core.js) : deux boucles séparées finiraient par ne plus s'aligner.
   ⚠ `justify-content:space-between` sur les deux, et la hauteur de la zone
     ÉGALE à celle des barres : c'est ce qui met le trait du haut sur le
     sommet de l'échelle et celui du bas sur la ligne de base. Un décalage
     d'un pixel ici fait mentir tout le dessin.
   ⚠ La grille est SOUS les barres (`z-index`), jamais au-dessus : un trait
     qui traverse une barre pleine se lit comme une césure de la donnée. */
/* ⚠⚠ LA HAUTEUR VIT SUR `.bars-axe`, ET `.bars` LA SUIT. Sans la seconde
   moitié, `.bars{height:170px}` (posé pour le graphique nu) l'emportait : le
   graphique restait court au fond d'une carte étirée par ses voisines, et
   l'écran montrait 200 px de blanc sous les barres — mesuré sur la capture.
   ⚠ 230 px et non 170 : dans une rangée où les deux autres cartes portent
     sept et neuf lignes de légende, c'est la hauteur qui les égale sans
     étirer personne. */
.bars-axe{ display:flex; gap:10px; align-items:stretch; height:230px; }
.bars-axe .zone,
.bars-axe .bars{ height:100%; }
.bars-axe .ech{
  flex:0 0 auto; display:flex; flex-direction:column; justify-content:space-between;
  font-size:10px; color:var(--muted); text-align:right;
  font-variant-numeric:tabular-nums;
  /* ⚠ Le rembourrage haut de `.bars` (18 px, la place des valeurs) et la
     hauteur des libellés du bas (~21 px) sont REPRIS ici : sans eux, la
     graduation glisse par rapport aux traits qu'elle nomme. */
  padding:18px 0 21px;
}
.bars-axe .zone{ position:relative; flex:1 1 auto; min-width:0; }
.bars-axe .grille-h{
  position:absolute; inset:18px 0 21px 0; z-index:0;
  display:flex; flex-direction:column; justify-content:space-between;
  pointer-events:none;
}
.bars-axe .grille-h i{ display:block; height:1px; background:var(--line); }
.bars-axe .bars{ position:relative; z-index:1; padding-top:18px; }

/* ⚠ La rangée de graphiques du tableau de bord : le CA prend deux parts, les
   deux répartitions une chacune. `minmax(0,…)` et non `2fr 1fr 1fr` nu : sans
   lui, un contenu large (les étiquettes de l'axe) empêche la colonne de
   rétrécir et la grille déborde — le piège classique de `1fr`, qui vaut
   `minmax(auto,1fr)`. */
.dash-graphes{ grid-template-columns:minmax(0,2fr) minmax(0,1fr) minmax(0,1fr); }
@media (max-width:1250px){ .dash-graphes{ grid-template-columns:minmax(0,1fr); } }

/* Les demandes par statut : des barres, pas un anneau à neuf parts. */
.dash-barres{ display:flex; flex-direction:column; gap:7px; }
.dash-barres .l{ display:grid; grid-template-columns:1fr 90px auto; gap:10px; align-items:center; }
.dash-barres .lib{
  font-size:12.5px; color:var(--ink); min-width:0;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.dash-barres .piste{ height:8px; border-radius:4px; background:var(--soft2); overflow:hidden; }
.dash-barres .piste i{ display:block; height:100%; background:var(--poppy); border-radius:4px; }
/* ⚠⚠ CHAQUE BARRE PREND LA COULEUR DE SON STATUT (27/09, maquette). Les neuf
     étaient coquelicot : un même rouge pour « Reçue », « Refusée » et
     « Abandonnée ». Le graphique n'ajoutait alors rien à sa colonne de
     chiffres — c'est sa couleur qui le rend lisible d'un coup d'œil.
   ⚠ Les classes sont celles des BADGES (`b-ok`, `b-att`…) : le ton vient de
     `CC.ton()`, le point unique du socle, et la teinte de sa variable. Deux
     tables de couleurs finiraient par diverger. */
.dash-barres .piste i.b-ok{      background:var(--ok); }
.dash-barres .piste i.b-att{     background:var(--att); }
.dash-barres .piste i.b-ko{      background:var(--ko); }
.dash-barres .piste i.b-info{    background:var(--info); }
.dash-barres .piste i.b-neutre{  background:var(--neutre); }
.dash-barres .piste i.b-poppy{   background:var(--poppy); }
.dash-barres .n{
  font-size:12.5px; font-weight:700; color:var(--ink);
  font-variant-numeric:tabular-nums; min-width:22px; text-align:right;
}
/* ⚠ Une note sous un tableau posé dans un corps `serre` : le corps n'a pas de
   rembourrage (le tableau touche les bords), la note en a besoin. */
.dash-note{ padding:10px 14px 2px; }

/* ⚠ « Prochaine action » : une pastille qui porte un GESTE, pas un état. Elle
   se distingue des pastilles de statut par sa forme — fond pâle sans bordure,
   rayon plus doux — parce que deux pastilles identiques côte à côte se lisent
   comme deux fois la même information (26/09, seconde maquette du CRM). */
/* ⚠ `.dem-action` a été retirée le 27/09 avec la colonne « Prochaine
     action » qu'elle peignait (maquette 14). Vérifié avant : aucune autre vue
     ne l'employait. */

/* ⚠ « Réinitialiser » : un lien d'annulation, pas une commande de la barre —
   il se lit en coquelicot et sans bordure (26/09, seconde maquette du CRM). */
.btn.dem-raz{ color:var(--poppy); white-space:nowrap; align-self:end; }
.btn.dem-raz:hover{ color:var(--poppy-dark); background:var(--soft2); }
/* ⚠ La case à cocher est une COMMANDE, pas un champ étiqueté : son libellé est
   à sa droite, dans le `<label>` qui l'enveloppe, et le bloc s'aligne sur la
   ligne de base des listes voisines et non sur celle de leurs étiquettes. */
.dem-filtres-2 .f-case{ justify-content:flex-end; padding-bottom:7px; }
.dem-filtres-2 .f-case label{
  display:flex; align-items:center; gap:7px;
  font-size:12.5px; color:var(--ink); font-weight:400; cursor:pointer;
}

/* ⚠⚠ UNE CIBLE DE DÉPÔT DOIT SE SIGNALER PENDANT LE GLISSER, et `.depot`
   n'avait AUCUNE règle : on traînait une ligne sur une pastille sans jamais
   savoir si elle était la bonne. Le geste marchait, personne ne pouvait le
   deviner. Trouvé par `audit_vues` (contrôle 3), qui cherchait la classe.
   ⚠ La classe est à elle : elle a réutilisé `.actif` jusqu'au 26/09, et
     depuis que ces pastilles FILTRENT, `.actif` veut dire « filtre en cours ».
     Un survol effaçait donc la sélection. */
.chip.depot{ transition:box-shadow .12s ease, transform .12s ease; }
.chip.depot.depot-survol{
  box-shadow:0 0 0 2px var(--poppy); transform:translateY(-1px);
}

/* ─── 25. LA BARRE DE FILTRES DU VIVIER (26/09, maquette de Julien) ───────
   ⚠ Même forme que celle du CRM : une étiquette VISIBLE au-dessus de chaque
     commande, des largeurs égales mesurées sur la PLACE et non sur la fenêtre.
     Six listes alignées sans nom ne se distinguent que par leur contenu, et un
     filtre actif n'a plus de nom du tout.
   ⚠ `!important` sur le `display` : `.panel > header` est en `flex` dans le
     socle, et cette barre est une grille. Le poids est celui de la règle du
     socle, pas une facilité — on ne peut pas resserrer un sélecteur d'en-tête
     de panneau sans le faire pour les 35 autres. */
/*
 * ⚠⚠ LES COLONNES SONT DÉCLARÉES, PAS DEVINÉES. Avec `auto-fit`, le nombre de
 *    pistes dépend d'une largeur minimale, et le DERNIER élément — le bouton
 *    « Réinitialiser » — tombait seul sur une deuxième ligne dès qu'il en
 *    manquait une. J'ai d'abord baissé le minimum de 170 à 150 px : ça
 *    déplaçait le seuil sans le supprimer, et il serait revenu au premier
 *    filtre ajouté. Huit commandes, huit pistes : la recherche en vaut deux,
 *    le bouton prend ce qui lui faut.
 * ⚠ `minmax(0,…)` et non `1fr` nu : `1fr` vaut `minmax(auto,1fr)`, et une
 *   option longue (« Bientraitance et prévention de la maltraitance ») ferait
 *   alors déborder la grille au lieu de tronquer.
 * ⚠ Sous 1100 px on repasse à `auto-fit` : à huit colonnes fixes, chacune
 *   ferait 90 px et aucune liste ne serait lisible.
 */
.frm-filtres{
  display:grid !important; gap:10px 14px; align-items:end;
  /* ⚠⚠ TOUT SUR UNE RANGÉE DEPUIS LE 30/09 (demandé sur capture) : la
       recherche, SIX listes et « Réinitialiser ». Le motif du 26/09 — « à six
       listes, chacune tombe à 150 px et le texte est tronqué » — reste vrai ;
       ce qui a changé, c'est la contrainte qui le rendait inévitable. **On ne
       revient pas sur une mesure, on retire ce qui la rendait vraie.**
     ⚠⚠ `auto-fit` + `minmax()`, ET AUCUNE REQUÊTE DE MÉDIA. Mon premier essai
       figeait huit colonnes au-dessus de 1 500 px de FENÊTRE : à 1 536 px le
       panneau n'offre que ~1 296 px, et les libellés sortaient tronqués —
       « Toutes les zor », « Vivier actif (1 ». **Une grille qui s'empile sur la
       fenêtre ne voit pas la place réelle** (12/09, la carte du quiz), et mon
       propre commentaire annonçait déjà `auto-fit` pendant que le code posait
       un seuil : *un commentaire qui décrit autre chose que son code fait
       cesser de chercher.*
     ⭐ 150 px est le minimum où « Vivier actif (11) » et « Toutes les zones » se
        lisent en entier — mesuré. Au-dessus de la place nécessaire, tout tient
        sur une ligne ; en dessous, la rangée ENROULE au lieu de tronquer. Le
        navigateur tranche : aucun seuil à tenir à jour le jour où la barre
        latérale se replie ou qu'une commande s'ajoute.
     ⚠ La recherche occupe DEUX colonnes : c'est la commande qu'on emploie le
       plus, et un champ de saisie a besoin de place là où une liste n'en
       demande que pour son libellé le plus long. */
  grid-template-columns:repeat(auto-fit, minmax(150px, 1fr));
}
.frm-filtres .f-q{ grid-column:span 2; }
/* ⚠⚠ LA REQUÊTE DE MÉDIA `max-width:1100px` EST RETIRÉE (30/09) — elle posait
     le MÊME `auto-fit` que la règle principale, donc elle ne peignait plus
     rien ; mais elle n'était pas inoffensive pour autant : mon contrôle du jour
     cherchait `.frm-filtres{ … minmax(NNNpx` et **trouvait LA SIENNE**. Sabotée
     à zéro, la règle principale laissait le contrôle VERT. *Une recherche mal
     bornée trouve l'autre occurrence* — neuvième fois dans ce projet, et la
     première où c'est une règle CSS devenue inutile qui sert de leurre.
   ⭐ Une règle qui ne peint plus rien ne se garde pas « au cas où » : elle
      trompe le prochain contrôle. */
.frm-filtres .f{ display:flex; flex-direction:column; gap:4px; min-width:0; }
.frm-filtres .f > label{ font-size:12px; color:var(--muted); font-weight:600; }
.frm-filtres .f > select,
.frm-filtres .f .champ-recherche{ width:100%; }
/* ⚠ Le champ de recherche prend la forme des commandes voisines : sa pilule
   rosée est celle de la barre du HAUT, où elle se détache d'un bandeau blanc.
   Posée ici, elle serait le seul champ teinté de la rangée. */
/* ⚠⚠ LA HAUTEUR ET LE RAYON SONT PARTIS EN SECTION 29 — ILS ÉTAIENT ÉCRITS
   DEUX FOIS. Posés ici le soir (36 px, rayon 6) puis là-bas la nuit (44 px,
   rayon 10), la cascade tranchait en faveur de la seconde et la première
   n'était plus que du bruit — mais mon propre contrôle lisait la PREMIÈRE et
   rougissait sur un écran juste. *Deux valeurs écrites séparément
   divergent* : c'est la dixième fois dans ce projet, et la première où les
   deux sont de moi, à trois heures d'intervalle. Ce qui reste ici est ce qui
   ne se répète pas : le fond et le contour. */
.frm-filtres .champ-recherche input{
  background:var(--carte); border-color:var(--line-champ);
}
.btn.frm-raz{
  color:var(--poppy); white-space:nowrap; align-self:end; justify-self:end;
  display:inline-flex; align-items:center; gap:6px;
}
.btn.frm-raz:hover{ color:var(--poppy-dark); background:var(--soft2); }
/* ⚠⚠ DÉSACTIVÉ, IL N'EST PLUS COQUELICOT (28/09). Un rouge pâle qu'on ne peut
   pas actionner se lit comme une action dangereuse indisponible, et il faisait
   le second rouge d'un bloc où rien n'est urgent — à côté de la pastille
   « Toutes thématiques », qui portait le premier. La couleur d'action revient
   avec la possibilité d'agir. */
.btn.frm-raz:disabled{ color:var(--muted); }
/* ⚠⚠ ET SA RAISON SE LIT AU SURVOL (30/09 au soir, relecture contradictoire).
     Le bouton désactivé dit pourquoi dans son `title`, mais `.btn[disabled]`
     (section 7) pose `pointer-events:none` : le survol et le clic tombaient
     sur la barre, et l'infobulle ne paraissait JAMAIS — « désactivé avec sa
     raison » n'était tenu que dans le source. On rend la souris au bouton, à
     lui seul : un clic sur un bouton désactivé n'est pas remis par le
     navigateur, le curseur « interdit » reste, et le survol ne le repeint pas
     comme s'il pouvait agir.
   ⚠ …NI L'APPUI NE L'ENFONCE (01/10, relecture contradictoire). Rendu à la
     souris, le bouton désactivé prenait aussi `.btn:active{transform:
     translateY(1px)}` (section 7) : à l'appui, il s'enfonçait d'un pixel —
     le retour visuel d'un bouton qui agit. `transform:none` ici (0,3,0)
     l'emporte sur le socle (0,2,0). */
.btn.frm-raz:disabled{ pointer-events:auto; transform:none; }
.btn.frm-raz:disabled:hover{ color:var(--muted); background:transparent; }

/* ─── 26. LES MESURES DU VIVIER (26/09, maquette de Julien) ───────────────
   ⚠⚠ ELLES REMONTENT AU-DESSUS DES FILTRES. Elles vivaient sous la barre de
      filtres et sous treize pastilles de thématique : les trois nombres qui
      disent l'état du vivier arrivaient après ce qui sert à le trier. **On
      regarde ce qu'un écran contient avant de le régler.**
   ⚠ DEUX DES TROIS SONT DES BOUTONS qui filtrent — c'est ce qui a été gagné le
     13/09 et qui ne se perd pas en changeant de forme : « 3 en alerte
     documentaire » qui se lit et s'arrête là oblige à ouvrir onze fiches pour
     retrouver lesquels. Le curseur et le focus visible le disent.
   ⚠ `auto-fit`+`minmax` : la zone est bornée par la barre latérale, un seuil
     de FENÊTRE ne voit pas la place réelle. */
.frm-mesures{
  display:grid; grid-template-columns:repeat(auto-fit, minmax(240px, 1fr));
  gap:0; margin-bottom:14px;
  background:var(--carte); border:1px solid var(--line); border-radius:var(--r);
  box-shadow:var(--ombre); overflow:hidden;
}
.frm-mesure{
  display:flex; align-items:center; gap:12px; padding:14px 16px;
  background:transparent; border:0; text-align:left; font:inherit; color:inherit;
}
/* ⚠ Le filet sépare les blocs SANS border-left sur le premier : sur une grille
   qui enroule, un filet à gauche du premier bloc se retrouverait au milieu de
   rien dès que les colonnes s'empilent. */
.frm-mesure + .frm-mesure{ border-left:1px solid var(--line); }
.frm-mesure > .ic{
  flex:0 0 auto; width:42px; height:42px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
}
.frm-mesure > .txt{ display:flex; flex-direction:column; min-width:0; }
.frm-mesure > .txt > .t{ font-size:14px; color:var(--ink); }
.frm-mesure > .txt > .t b{ font-size:17px; }
.frm-mesure > .txt > .s{ font-size:12px; color:var(--muted); margin-top:1px; }
.frm-mesure.t-vivier    > .ic{ background:var(--soft);   color:var(--poppy-dark); }
.frm-mesure.t-alerte    > .ic{ background:var(--att-bg); color:var(--att); }
.frm-mesure.t-vigilance > .ic{ background:var(--att-bg); color:var(--att); }
button.frm-mesure{ cursor:pointer; width:100%; }
button.frm-mesure:hover{ background:var(--soft2); }
button.frm-mesure:focus-visible{ outline:2px solid var(--poppy); outline-offset:-2px; }
button.frm-mesure.actif{ background:var(--att-bg); }
button.frm-mesure.actif > .ic{ background:var(--att); color:#fff; }
@media (max-width:760px){ .frm-mesure + .frm-mesure{ border-left:0; border-top:1px solid var(--line); } }
/* ⚠ La thématique retenue, avec de quoi la retirer. Sa croix est un VRAI
   bouton et non un « × » dans le libellé : elle doit s'atteindre au clavier et
   s'annoncer pour ce qu'elle fait. */
.frm-choisie{ display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
.frm-chip-choisie{ display:inline-flex; align-items:center; gap:6px; }
.frm-chip-choisie button{
  border:0; background:transparent; color:inherit; cursor:pointer;
  font-size:15px; line-height:1; padding:0 2px; border-radius:3px;
}
.frm-chip-choisie button:hover{ background:#ffffff40; }
/* ⚠ Les deux moyens de joindre quelqu'un se séparent par de l'ESPACE, pas par
   un point médian : un numéro et une adresse collés se lisent comme une seule
   suite de signes. */
.frm-contact{ display:flex; flex-wrap:wrap; gap:6px 18px; }
.frm-contact a{ display:inline-flex; align-items:center; gap:6px; }
/* ⚠ L'icône de lieu s'aligne sur la ligne de base du texte qu'elle précède. */
.frm-ic{ display:inline-flex; vertical-align:-2px; color:var(--muted); }

/* ─── L'alerte de pièces d'un intervenant (26/09 au soir) ─────────────────
   ⚠⚠ UNE LIGNE, PAS DEUX. Le badge et la date vivaient séparés, et la date en
      gris menu ne se rattachait visiblement à rien. Ce sont deux moitiés d'un
      même fait. ⚠ Le ton les distingue : une pièce EXPIRÉE bloque une
      affectation (rose), une pièce qui va expirer demande un geste (ambre). */
.frm-piece{
  display:flex; align-items:center; gap:7px; flex-wrap:wrap;
  padding:6px 10px; border-radius:var(--r-s); font-size:12px;
}
.frm-piece.ko{  background:var(--ko-bg);  color:var(--ko); }
.frm-piece.att{ background:var(--att-bg); color:var(--att); }
.frm-vigilance{ display:flex; align-items:center; gap:7px; }
/* ⚠ Le bouton de la fiche est DANS la carte, aligné à droite : le pied gris
   qui le portait posait une bande d'un autre ton sur chacune des onze cartes,
   pour une phrase qui était la même sur presque toutes. */
.frm-fiche{ display:flex; justify-content:flex-end; margin-top:12px; }
/* ⚠ L'expérience se lit d'un trait : les étoiles, la note et le compte de
   sessions sur la même ligne de base. */
.frm-exp{ display:flex; align-items:center; gap:5px; flex-wrap:wrap; }
/* ⚠ Une pastille de disponibilité porte son dessin ET son mot : trois teintes
   seules n'existent pas pour qui ne les distingue pas. */
.b.frm-dispo{ display:inline-flex; align-items:center; gap:5px; }
/* ⚠ La dernière ligne d'une carte : l'alerte s'étire, le bouton garde sa
   taille. C'est le texte qui doit pouvoir enrouler, jamais la commande. */
/*
 * ⚠⚠ L'ALERTE ET LE BOUTON S'EMPILENT QUAND LA CARTE EST ÉTROITE (27/09).
 *   Posés sur une ligne le 26/09 — la carte faisait alors la moitié de la
 *   grille —, ils ne tiennent plus à trois colonnes : l'encart tombait à
 *   ~110 px et « Facture via sa société ; RIB non fourni. Délai de réponse
 *   long. » s'écrivait **un mot par ligne, sur sept lignes**.
 * ⚠ `flex-basis:210px` est le seuil : au-dessus l'alerte partage la ligne avec
 *   le bouton, en dessous elle prend la largeur et le bouton passe sous elle.
 *   C'est la place réelle qui décide, pas une requête de média — la grille
 *   change de nombre de colonnes selon la fenêtre ET le réglage.
 */
.frm-bas{ display:flex; align-items:center; gap:10px; margin-top:12px;
          flex-wrap:wrap; }
.frm-bas > .frm-piece,
.frm-bas > .alerte{ flex:1 1 210px; min-width:0; margin-top:0; }
.frm-bas > .btn{ flex:0 0 auto; margin-left:auto; }
/*
 * ⚠⚠ UNE THÉMATIQUE TRONQUÉE NE DIT PLUS RIEN (27/09). La troncature datait de
 *   la grille à DEUX colonnes ; à trois — demande de Julien du 26/09 — la
 *   carte perd un tiers de sa largeur et l'on lisait « Analyse de la pratique
 *   pro… », « Écrits professionnels en tra… », « Accueil et développement… ».
 *   Or c'est précisément ce qu'on cherche dans un vivier : QUI sait faire QUOI.
 * ⚠ Le remède n'est pas d'élargir la carte — c'est le nombre de colonnes qui
 *   est demandé — mais de laisser l'étiquette passer à la ligne. Elle grandit
 *   en hauteur, ce qui ne coûte rien : les cartes d'une rangée s'alignent déjà
 *   sur la plus haute.
 * ⚠ `max-width:100%` reste : une thématique à rallonge ne doit pas élargir la
 *   carte au-delà de sa colonne.
 */
.chip.frm-th{ max-width:100%; white-space:normal; text-align:left;
              line-height:1.35; height:auto; }
/* ⚠ Le nom d'un intervenant est ce qu'on cherche dans une grille de onze
   cartes : il porte la taille qui va avec (maquette du 26/09). */
.frm-cartes > .panel > header h3{ font-size:16px; }

/* ─── 27. LA MISE EN PAGE DE L'ANNUAIRE (26/09, nuit) ────────────────────
   ⚠⚠ DEUX FILETS QUE LA MAQUETTE N'A PAS, ET ILS COUPENT CE QUI SE LIT
      D'UN TRAIT.

   `.panel > header` porte un filet bas — juste, et c'est ce qui sépare un
   TITRE de son contenu sur les trente-cinq autres panneaux du projet. Mais
   ici l'en-tête n'est pas un titre :
     · sur une carte d'intervenant, c'est **son nom** — et « Barreau Julien /
       Vacataire · 580 € / jour » est une seule phrase, coupée en deux par un
       trait ;
     · sur la barre de filtres, ce qui suit le filet est **la suite de la même
       commande** : les thématiques retenues sont l'état du filtre posé juste
       au-dessus. Un trait entre les deux les fait lire comme deux choses.

   ⚠ Bornés aux DEUX endroits concernés : les trente-cinq autres panneaux
     gardent leur filet, qui les sert. */
.frm-cartes > .panel > header{ border-bottom:0; padding-bottom:0; }
.frm-cartes > .panel > .corps{ padding-top:8px; }
.frm-filtres{ border-bottom:0 !important; padding-bottom:0 !important; }
.frm-choisie{ padding-top:10px; }
/* ⚠⚠ ET LA RECHERCHE RESPIRE. Dans la maquette, le champ de recherche est
   SÉPARÉ du groupe de listes : on y tape un nom, on règle des filtres — deux
   gestes différents. Collé, il se lisait comme la première d'une rangée de
   sept commandes identiques. */
.frm-filtres .f-q{ margin-right:18px; }

/* ─── 28. LE DESSIN DE LA CARTE D'UN INTERVENANT (26/09, nuit) ────────────
   ⚠⚠ UNE THÉMATIQUE SUR UNE CARTE N'EST PAS UNE COMMANDE, C'EST UNE
      ÉTIQUETTE — et `.chip` la dessinait comme un bouton de filtre : contour
      `--line-champ` (le gris FONCÉ réservé aux commandes, pour le seuil de
      3:1), texte gris, `cursor:pointer`. Résultat : six pastilles par carte
      qui ont l'air cliquables et ne le sont pas. C'est le défaut du bouton
      qui ne fait rien (03/09), sous une autre forme — on ne le découvre qu'en
      essayant.
   ⚠ La classe existait déjà (`.frm-th`, pour la troncature) : elle porte
     maintenant tout le dessin. Les seize écrans qui emploient `.chip` comme
     commande ne bougent pas d'un pixel.
   ⚠ Filet CLAIR et texte en ENCRE : une étiquette se lit, elle ne s'actionne
     pas. Le contraste du texte y gagne (`--ink` 12,78:1 contre `--muted`
     4,53) ; celui du filet n'a plus de seuil à tenir puisqu'il n'entoure plus
     une commande. */
.chip.frm-th{
  cursor:default; border-color:var(--line); color:var(--ink); font-weight:500;
  padding:7px 13px; border-radius:16px;
}
.chip.frm-th:hover{ border-color:var(--line); color:var(--ink); }

/* ⚠⚠ ET LA CARTE RESPIRE. 14 px de rembourrage pour sept blocs empilés — nom,
   statut, contact, thématiques, zones, alerte, bouton — les collait les uns
   aux autres : *dans une colonne étroite, c'est l'ESPACE qui sépare, pas les
   mots* (12/09). La maquette en met vingt. */
.frm-cartes > .panel{ border-radius:12px; }
.frm-cartes > .panel > header{ padding:18px 20px 0; }
.frm-cartes > .panel > .corps{ padding:10px 20px 18px; }
/* ⚠ La pastille de disponibilité est le seul repère qu'on balaie de carte en
   carte sans lire : elle prend la taille qui va avec, et une forme de pilule
   pleine qui la distingue des étiquettes rectangulaires du dessous. */
.frm-cartes .b.frm-dispo{ padding:5px 12px; border-radius:999px; font-size:12px; }

/* ─── 29. LE DESSIN DU BLOC DE RECHERCHE DU VIVIER (26/09, nuit) ──────────
   ⚠⚠ LES COMMANDES ÉTAIENT DEUX FOIS PLUS BASSES QUE SUR LA MAQUETTE, et
      c'est ce qui faisait toute la différence : `.input` mesure 32 px de haut
      pour un rayon de 6 — des champs de formulaire denses, justes dans une
      fenêtre de saisie où l'on en empile quinze. Ici il y en a SIX, en tête
      d'écran, et ce sont les commandes qu'on touche en arrivant : la maquette
      leur donne 44 px et un rayon de 10.
   ⚠ `.input` n'est PAS touchée — elle sert des dizaines de formulaires où la
     densité est ce qu'on veut. La dérogation est bornée à cette barre.
   ⚠⚠ ET LE CONTOUR RESTE `--line-champ` : c'est lui qui dit OÙ TAPER, et il
      tient le seuil de 3:1 que `--line` ne tient pas (1,17 sur ce fond). Un
      champ qu'on ne voit pas sur un écran en plein jour n'est pas un champ.
   ⚠ 44 px est aussi la cible tactile minimale : sur une tablette, six listes
     de 32 px se manquent une fois sur trois. */
.frm-filtres .f > select,
.frm-filtres .champ-recherche input{
  height:44px; border-radius:10px; font-size:13.5px;
}
.frm-filtres .f > select{ padding:0 10px; }
.frm-filtres .champ-recherche input{ padding-left:34px; }
.frm-filtres .champ-recherche::before{ font-size:18px; }
/* ⚠ Le bloc respire comme les cartes : quatorze pixels pour deux rangées de
   commandes les collaient au bord, et l'écran commençait à l'étroit. */
.frm-filtres{ padding:20px 20px 0 !important; }
.frm-choisie{ padding:14px 20px 18px !important; }
/* ⚠⚠ LA MARGE BASSE REVIENT QUAND LA RANGÉE DES THÉMATIQUES EST MASQUÉE (30/09
     au soir). Le `0` ci-dessus comptait sur elle pour fermer le bloc — ses
     18 px du bas —, et elle ne paraît plus qu'avec une thématique retenue :
     au repos, les listes touchaient le bord de la carte (0 px de marge,
     mesurés à 1 366 et 1 920 px). Les mêmes 18 px, portés par la barre elle-même
     tant que sa voisine est `hidden` ; avec une thématique retenue, rien ne
     bouge. ⚠ `:has()` et non une classe posée par la vue : sans lui, on
     retrouve seulement le défaut d'avant, jamais un pire. */
.frm-filtres:has(+ #frm-chips[hidden]){ padding-bottom:18px !important; }
/* ⚠ Une thématique retenue est une ÉTIQUETTE qu'on peut retirer, pas un
   filtre qu'on actionne : même dessin que celles des cartes, plus la croix.
   ⚠ « Toutes thématiques » garde le ton d'accent : c'est l'état par défaut,
     et le distinguer des thématiques réellement posées évite de compter une
     de trop. */
.frm-chip-choisie{ padding:7px 13px; border-radius:16px; cursor:default; }
.frm-chip-choisie:not(.actif){ border-color:var(--line); color:var(--ink); font-weight:500; }
/* ⚠ `cursor:pointer` est DÉJÀ posé en section 25, sur la même règle : l'écrire
   ici une seconde fois ne changeait rien et rendait un sabotage inopérant —
   on retirait une déclaration, l'autre tenait, et l'on concluait à un
   contrôle creux. Ce qui reste ici est ce qui n'y était pas : la couleur. */
.frm-chip-choisie button{ color:var(--muted); }
.frm-chip-choisie.actif button{ color:inherit; }
/* ⚠ La seconde rangée : les thématiques retenues à gauche, les deux réglages
   qu'on touche le moins à droite. Elles ne sont pas perdues — elles ont
   changé de ligne, pas disparu. */
/* ⚠⚠ REPRIS LE 28/09 — LA RANGÉE PORTAIT 530 px DE VIDE EN SON MILIEU
   (mesuré sur la capture, bloc de 1 286 px). Trois choses sans rapport y
   étaient alignées et rien ne les séparait qu'un blanc. Un blanc ne sépare
   pas : plus étroit les deux groupes se collent, plus large ils se perdent.
   ⭐ Les trois colonnes se composent DE LA MÊME FAÇON — un intertitre de 12 px
      puis son contenu —, et c'est la PREMIÈRE qui s'étire. Le vide devient
      l'étirement d'une colonne, ce qui se lit comme une composition ; poussé
      contre un bord par `margin-left:auto`, il se lisait comme un trou. Leçon
      du 12/09 (la carte du quiz) et du 28/09 au soir (la frise). */
.frm-choisie{ display:flex; align-items:flex-end; gap:18px; flex-wrap:wrap; }
.frm-choisie .f{ display:flex; flex-direction:column; gap:4px; min-width:0; }
.frm-choisie .f-thematiques{ flex:1 1 320px; }
/* ⚠ La liste de pastilles garde sa hauteur de champ, sinon la colonne de
   gauche remonte de 6 px et les trois intertitres cessent d'être alignés. */
.frm-choisie-liste{ display:flex; align-items:center; gap:8px; flex-wrap:wrap;
  min-height:38px; }
.frm-reglages{ display:flex; gap:14px; }
.frm-reglages .f{ display:flex; flex-direction:column; gap:4px; min-width:150px; }
/* ⚠ Les trois intertitres de la rangée ont la MÊME graphie que les cinq de la
   rangée du haut : un `<label for>` quand il y a un champ à désigner, un
   `.f-lib` quand la colonne porte un ÉTAT et non une saisie. Deux graphies
   pour la même fonction, dans le même bloc, c'est ce qu'on lit « fouillis ». */
.frm-choisie .f > label,
.frm-choisie .f > .f-lib{ font-size:12px; color:var(--muted); font-weight:600; }
.frm-reglages select{ height:38px; border-radius:8px; }

/* ─── 30. LE TITRE ET LES MESURES À LA TAILLE DE LA MAQUETTE (26/09) ──────
   ⚠⚠ MESURÉ EN POSANT LA MAQUETTE ET LA CAPTURE DANS LA MÊME IMAGE, à la
      même échelle — c'est la seule façon d'arrêter de deviner. Le titre de
      page y fait 30 px, le mien 20 ; le nombre d'une mesure 20 px, le mien
      17 ; l'icône d'une mesure 52 px, la mienne 42.
   ⚠ `h1` reste à 20 px pour tout le projet : c'est la taille d'un titre DANS
     un document, et trente écrans l'emploient. Seule la ligne de TÊTE d'un
     écran — celle qu'on lit en arrivant, une par page — prend la taille de la
     maquette. La dérogation est bornée à `.page-tete`, qui n'existe que là.
   ⚠ `text-wrap:balance` : un titre de deux mots ne doit pas casser après le
     premier quand la fenêtre se resserre. */
.page-tete h1{ font-size:28px; line-height:1.15; text-wrap:balance; }
.page-tete .sstitre{ font-size:13.5px; }
/* ⚠ Les trois mesures sont ce qu'on lit en second : elles portent la taille
   qui va avec, et l'icône se voit de loin — c'est elle qui distingue les
   trois blocs avant qu'on lise un mot. */
.frm-mesure{ padding:18px 20px; }
.frm-mesure > .ic{ width:52px; height:52px; }
.frm-mesure > .txt > .t{ font-size:15px; }
.frm-mesure > .txt > .t b{ font-size:20px; }
.frm-mesure > .txt > .s{ font-size:12.5px; }
/* ⚠⚠ LE CONTACT TIENT SUR UNE LIGNE. À trois colonnes, le téléphone et
   l'adresse passaient l'un sous l'autre : deux lignes par carte, onze fois.
   L'adresse se TRONQUE — c'est elle qui est longue, et on la copie par le
   lien, pas en la lisant. */
/* ⚠⚠ ET C'EST L'ADRESSE QUI SE TRONQUE, JAMAIS LE NUMÉRO. Posé sur les deux,
   `min-width:0` coupait « 06 17 28 39 40 » en « 06 17 28 39 » — **un numéro
   de téléphone tronqué est un numéro FAUX**, et rien à l'écran ne dit qu'il
   manque un chiffre. L'adresse, elle, se copie par le lien : on ne la lit
   pas. */
.frm-contact{ flex-wrap:nowrap; gap:14px; font-size:12px; }
/* ⚠⚠ LE NUMÉRO SE NOMME, IL NE SE REPÈRE PAS PAR SA PLACE (28/09). Écrite
   `a:first-child`, la règle gelait le PREMIER lien — c'est-à-dire l'ADRESSE
   sur une carte qui a un courriel sans téléphone : elle ne pouvait plus
   céder de place, débordait de la carte, et sans « … ». La leçon du « +N »,
   plus bas, à l'identique. Mesuré par le banc de tests_formateurs_ux. */
.frm-contact a[href^="tel:"]{ flex:0 0 auto; white-space:nowrap; }
/* ⚠⚠ LA TRONCATURE PORTE SUR LE TEXTE, PAS SUR LE LIEN. `text-overflow` ne
     fait rien sur un conteneur `inline-flex` : la règle était là depuis le
     26/09 et **elle n'a jamais tronqué quoi que ce soit** — à trois colonnes,
     l'adresse sortait simplement de la carte. Le `<span class="t">` que la vue
     pose autour de l'adresse est le bloc sur lequel la règle mord. */
.frm-contact a:last-child{ min-width:0; }
.frm-contact a:last-child > .t{
  min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.frm-contact a > svg{ flex:0 0 auto; }
/* ⚠⚠ DEUX THÉMATIQUES PAR LIGNE, COMME SUR LA MAQUETTE. Empilées, elles
   prenaient deux lignes de plus par carte — sur onze cartes à trois colonnes,
   c'est une rangée entière. Chacune est bornée à la moitié de la largeur et
   se tronque : le texte entier reste dans l'infobulle, et ce qu'on cherche
   dans une pastille, c'est son premier mot. */
.frm-themes{ display:flex; flex-wrap:wrap; gap:6px; }
.frm-themes > .chip.frm-th{ max-width:calc(50% - 3px); }
/* ⚠⚠ LE « +N » A SA CLASSE, ET SURTOUT PAS `:last-child`. Écrit ainsi, il
   visait la DERNIÈRE pastille de la rangée — c'est-à-dire la dernière
   THÉMATIQUE quand il n'y a pas de « +N », qui prenait alors toute la
   largeur et repoussait sa voisine à la ligne. Le défaut ne se voyait que sur
   les cartes SANS troisième thématique, et l'écran restait parfaitement
   valide : c'est le piège du sélecteur positionnel, qui décrit une place au
   lieu de nommer une chose. */
.frm-themes > .chip.frm-th-plus{ max-width:none; flex:0 0 auto; }
/* ⚠ Un lien de contact n'est pas souligné ici : dans une carte, six éléments
   se suivent et le soulignement ajoute une ligne de bruit sous chacun. La
   couleur et l'icône suffisent à dire que c'est actionnable — et le survol
   le confirme. */
.frm-contact a{ text-decoration:none; }
.frm-contact a:hover{ text-decoration:underline; }
/* ⚠ Le nom est ce qu'on cherche dans une grille de onze cartes. */
.frm-cartes > .panel > header h3{ font-size:18px; }

/* ─── 31. LES TUILES DU PLANNING (26/09, maquette de Julien) ──────────────
   ⚠⚠ ELLES ONT LA FORME DE LA MAQUETTE DU PLANNING, QUI N'EST NI CELLE DU
      CRM NI CELLE DU TABLEAU DE BORD — et les trois maquettes ont chacune
      leur raison. Ici : **fond teinté**, icône à GAUCHE, nombre PUIS libellé.
      Le fond teinté remplace le liseré : sur une frise, ces quatre tuiles
      sont le seul bloc coloré au-dessus d'une grille grise, et un liseré de
      4 px ne s'y voit pas.
   ⚠ La forme est posée par une classe de la vue (`.gantt-tuile`), pas déduite
     d'un contexte : les tuiles du CRM, qui partagent `.tuile-ic`, ne bougent
     pas d'un pixel. */
.tuile-ic.gantt-tuile{
  border-top-width:1px; border-top-color:var(--line);
  align-items:center; padding:16px 18px;
}
.tuile-ic.gantt-tuile > .ic{ order:0; margin-left:0; width:44px; height:44px; }
.tuile-ic.gantt-tuile > .txt{ display:flex; flex-direction:column; min-width:0; }
.tuile-ic.gantt-tuile .n{ order:0; font-size:24px; }
.tuile-ic.gantt-tuile .l{
  order:1; margin:2px 0 0; text-transform:none; letter-spacing:0;
  font-size:13px; font-weight:400; color:var(--muted);
}
.tuile-ic.gantt-tuile.t-sessions{ background:var(--soft);      border-color:transparent; }
.tuile-ic.gantt-tuile.t-gens{     background:var(--info-bg);   border-color:transparent; }
.tuile-ic.gantt-tuile.t-neutre{   background:var(--neutre-bg); border-color:transparent; }
.tuile-ic.gantt-tuile.t-alerte{   background:var(--att-bg);    border-color:var(--att); }
/* ⚠ Sur un aplat teinté, l'icône ne peut plus se détacher par son fond : elle
   se détache par sa COULEUR, et le rond disparaît. */
.tuile-ic.gantt-tuile > .ic{ background:transparent !important; }
/* ⚠ Les trois commandes du planning portent leur dessin : sur une frise, on
   les balaie sans lire — et « Imprimer » et « Exporter » se confondent à
   l'œil quand ils n'ont que leur mot. */
.page-tete .actions .btn > svg{ flex:0 0 auto; }
/* ⚠ Les intitulés de la barre du planning passent en casse normale (26/09,
   maquette) : « PÉRIODE » et « DURÉE D'AFFICHAGE » en capitales interlettrées
   criaient plus fort que les commandes qu'ils nomment, sur un écran où le
   regard doit aller à la frise. Même geste que sur les en-têtes de tableau le
   26/09 — un nom n'est pas une information. */
.gantt-outils-titre{
  font-size:12.5px; font-weight:600; letter-spacing:0; text-transform:none;
}
/* ⚠ Le numéro de semaine et sa plage, sur deux lignes (26/09, maquette) :
   « S36 » se repère, « 31 août – 6 sept. » se lit. Le `<i>` n'est pas de
   l'italique sémantique — il porte la seconde ligne, et le style l'y ramène. */
.gantt-semaines span{ display:flex; flex-direction:column; align-items:center; gap:1px; }
.gantt-semaines span > b{ font-size:11.5px; font-weight:700; color:var(--ink); }
.gantt-semaines span > i{ font-style:normal; font-size:10px; color:var(--muted); }

/* ─── 32. LE SUIVI DES SESSIONS — DEUX GROUPES, PAS SEPT MESURES (26/09) ──
   ⚠⚠ LA SÉPARATION EST LE SUJET. Les sept compteurs étaient alignés à
      égalité, et on les lisait comme sept mesures comparables. Ils ne le sont
      pas : QUATRE désignent du travail à faire aujourd'hui, TROIS décrivent où
      en sont les dossiers. Les premiers appellent un geste, les seconds
      informent — et côte à côte, « 6 après la formation » se lisait avec le
      même poids que « 5 en retard ».
   ⚠ C'est la PLACE qui porte la distinction, pas une couleur de plus : la
     règle du 16/08 interdit d'encoder un type par une teinte.
   ⚠ Le bloc des actions prend la part qu'il mérite — quatre tuiles contre
     trois — et les deux se replient ensemble sous 1250 px.
   ⚠⚠ LA CLASSE S'APPELLE `.suivi-mesure-groupe` ET SURTOUT PAS `.suivi-bloc` :
      celle-là existe depuis le 24/09 pour le détail des étapes d'une session,
      et elle porte `columns:4 220px` — un découpage MULTI-COLONNES qui a
      éclaté les quatre tuiles en colonnes de 220 px, hors de leur carte.
      C'est le défaut du 11/09 (« une classe de style peut servir à deux
      choses »), à l'envers : cette fois c'est moi qui ai inventé la
      collision. **On cherche si le nom est pris AVANT de l'écrire.** */
/* ⚠⚠ LA PART N'EST PAS « QUATRE CONTRE TROIS » (26/09, mesuré sur la
     maquette posée dans la même image que la capture). Elle y donne 893 px au
     bloc des actions contre 468 au second, soit 1,9 pour 1 — et non 1,33.
     Le second groupe DÉCRIT (avant, après, complets) : ses tuiles peuvent
     être plus étroites que celles qui appellent un geste. À 4fr/3fr, les
     libellés du premier bloc passaient à la ligne (« À traiter
     prochainement » sur deux lignes) pendant que le second respirait. */
.suivi-kpis{ display:grid; grid-template-columns:1.9fr 1fr; gap:14px; margin-bottom:14px; }
@media (max-width:1250px){ .suivi-kpis{ grid-template-columns:1fr; } }
.suivi-mesure-groupe{
  background:var(--carte); border:1px solid var(--line); border-radius:var(--r);
  box-shadow:var(--ombre); padding:14px 16px;
}
.suivi-mesure-groupe > h2{ font-size:13px; font-weight:700; color:var(--ink); margin:0 0 12px; }
.suivi-mesure-groupe > .kpis{ margin-bottom:0; gap:10px; }
/* ⚠ Les tuiles d'un bloc n'ont plus d'ombre ni de filet : elles sont DANS une
   carte, et une carte dans une carte fait deux bords pour une chose. */
.suivi-mesure-groupe .kpi{ box-shadow:none; background:var(--soft2); border-color:transparent; }
.suivi-mesure-groupe .kpi.att{ background:var(--att-bg); }
.suivi-mesure-groupe .kpi.ko{  background:var(--ko-bg); }
.suivi-mesure-groupe .kpi.navy,
.suivi-mesure-groupe .kpi.info{ background:var(--info-bg); }
.suivi-mesure-groupe .kpi.ok{  background:var(--ok-bg); }
/*
 * ⚠⚠ L'ICÔNE DU PREMIER GROUPE GARDE SA PASTILLE, CELLE DU SECOND N'EN A PAS
 *   (26/09, mesuré sur la maquette agrandie). Ce n'est pas un ornement : les
 *   quatre tuiles d'action sont sur aplat, et une icône posée nue sur un aplat
 *   teinté perd son contour — la pastille la redétache. Les trois tuiles du
 *   second groupe sont sur fond BLANC : la pastille y ferait un second bord
 *   pour rien.
 * ⚠ La teinte de la pastille est un `color-mix` avec un repli BLANC déclaré
 *   juste avant : un navigateur qui ignore la fonction garde le blanc, qui se
 *   tient sur l'aplat. Le repli n'y cache aucune invention (11/09) — la
 *   couleur mélangée vient d'un jeton existant.
 */
.suivi-mesure-groupe .kpi{ padding:10px 11px; }
/* ⚠⚠ L'ICÔNE S'ALIGNE SUR LE NOMBRE, PAS SUR LE MILIEU DE LA TUILE.
   Centrée, elle descendait au niveau du sous-titre — à trois lignes de texte,
   elle se retrouvait à hauteur de « en retard, ou échéance », comme si elle
   qualifiait cette phrase-là. La maquette la pose en face du chiffre, qui est
   ce qu'elle nomme. */
.suivi-mesures-actions .kpi{ gap:8px; align-items:flex-start; }
.suivi-mesures-actions .kpi > .ic{
  width:28px; height:28px; border-radius:8px;
  background:#fff;
}
.suivi-mesures-actions .kpi.att > .ic{ background:color-mix(in srgb, var(--att) 18%, #fff); }
.suivi-mesures-actions .kpi.ko  > .ic{ background:color-mix(in srgb, var(--ko) 14%, #fff); }
.suivi-mesures-actions .kpi.navy > .ic,
.suivi-mesures-actions .kpi.info > .ic{ background:color-mix(in srgb, var(--info) 17%, #fff); }
.suivi-mesures-actions .kpi.ok  > .ic{ background:color-mix(in srgb, var(--ok) 16%, #fff); }
.suivi-mesures-etats .kpi > .ic{ background:transparent; }
.suivi-mesure-groupe .kpi.att > .ic{ color:var(--att); }
.suivi-mesure-groupe .kpi.ko  > .ic{ color:var(--ko); }
.suivi-mesure-groupe .kpi.navy > .ic,
.suivi-mesure-groupe .kpi.info > .ic{ color:var(--info); }
.suivi-mesure-groupe .kpi.ok  > .ic{ color:var(--ok); }
/* ⚠ Le nombre passe avant le libellé, et le libellé en casse normale : la
   maquette lit « 2 / À traiter », et dans une tuile de 200 px un libellé en
   capitales interlettrées passe sur deux lignes. */
.suivi-mesure-groupe .kpi .n{ font-size:26px; }
/*
 * ⚠⚠ LE LIBELLÉ TIENT SUR UNE LIGNE, ET ÇA SE CALCULE (26/09).
 *   « À traiter prochainement » passait sur deux lignes, « Avant la
 *   formation » et « Dossiers complets » aussi — et deux tuiles voisines dont
 *   l'une déborde se lisent comme deux mesures de nature différente.
 *   Mesuré sur la maquette, la place disponible dans une tuile d'action vaut
 *   207 px de tuile, moins 24 de marge, moins 32 d'icône et 10 d'écart, soit
 *   141 px ; le plus long des sept libellés y tient à 12 px, pas à 12,5.
 * ⚠ On RÉTRÉCIT ce qui entoure plutôt que de couper le texte : une étiquette
 *   tronquée oblige à survoler pour lire ce qu'on compte.
 */
.suivi-mesure-groupe .kpi .l{
  order:1; margin:1px 0 0; text-transform:none; letter-spacing:0;
  font-size:11px; font-weight:700;
}
.suivi-mesure-groupe .kpi.att .l{ color:var(--att); }
.suivi-mesure-groupe .kpi.ko  .l{ color:var(--ko); }
.suivi-mesure-groupe .kpi.navy .l,
.suivi-mesure-groupe .kpi.info .l{ color:var(--info); }
.suivi-mesure-groupe .kpi.ok  .l{ color:var(--ok); }
.suivi-mesure-groupe .kpi .sous{ order:2; margin-top:4px; }
.suivi-mesure-groupe .kpi[aria-pressed="true"]{ outline:2px solid var(--poppy); outline-offset:-2px; }
/* ⚠⚠ LES COLONNES SONT DÉCLARÉES, PAS DEVINÉES. `.kpis` du socle emploie
   `auto-fit, minmax(250px, 1fr)` : dans un bloc de 740 px cela donne DEUX
   colonnes, et les quatre tuiles se rangeaient en escalier sur deux rangées.
   Le nombre de tuiles par bloc est connu — quatre et trois — il se déclare.
   C'est la même correction que sur la barre de filtres du vivier : *baisser un
   minimum déplace le seuil sans le supprimer.*
   ⚠ `minmax(0,…)` et non `1fr` nu : un sous-titre long ferait sinon déborder
     la grille au lieu d'enrouler. */
.suivi-mesures-actions > .kpis{ grid-template-columns:repeat(4, minmax(0,1fr)); }
.suivi-mesures-etats   > .kpis{ grid-template-columns:repeat(3, minmax(0,1fr)); }
/*
 * ⚠⚠ LE SECOND GROUPE MET SON ICÔNE EN LIGNE AVEC LE NOMBRE, et ce n'est pas
 *   une coquetterie : c'est ce qui rend le LIBELLÉ à la largeur de la carte.
 *   En colonne de gauche, l'icône mange 44 px + l'écart, et « Avant la
 *   formation » — trois mots — passait sur deux lignes dans une tuile de
 *   150 px, puis « Dossiers complets » aussi. La maquette pose le nombre
 *   après l'icône et ramène le libellé au bord gauche de la carte : mesuré,
 *   le libellé y démarre à la marge de la tuile, le nombre non.
 * ⚠ Pas de `.txt` ici, donc pas de `display:contents` : un enveloppement
 *   qu'on annule est un pari (11/09) — sur un navigateur qui l'ignore, le
 *   libellé reviendrait indenté. La vue pose les quatre enfants à plat.
 */
.suivi-mesures-etats .kpi{
  display:grid; grid-template-columns:auto 1fr; column-gap:8px;
  align-items:center; align-content:start;
}
.suivi-mesures-etats .kpi > .ic{ grid-column:1; grid-row:1; width:auto; height:auto; }
.suivi-mesures-etats .kpi > .n{ grid-column:2; grid-row:1; }
.suivi-mesures-etats .kpi > .l{ grid-column:1 / -1; grid-row:2; margin-top:5px; }
.suivi-mesures-etats .kpi > .sous{ grid-column:1 / -1; grid-row:3; }
/*
 * ⚠⚠ LE SECOND GROUPE NE PORTE AUCUN APLAT, ET SON LIBELLÉ EST À L'ENCRE.
 *   Teinter « Après la formation » en vert ou « Avant la formation » en bleu
 *   ferait ce que la règle du 16/08 interdit : **encoder un TYPE par une
 *   couleur**. Ces trois-là ne sont pas urgents — ils décrivent où en sont les
 *   dossiers. Le ton ne vit que sur l'icône, qui aide à retrouver la tuile
 *   sans rien promettre. La maquette fait exactement cela : trois cartes
 *   blanches bordées, nombre et libellé noirs, icône colorée.
 */
.suivi-mesures-etats .kpi{ background:var(--carte) !important; border-color:var(--line) !important; }
.suivi-mesures-etats .kpi .n{ color:var(--ink) !important; }
.suivi-mesures-etats .kpi .l{ color:var(--ink) !important; font-weight:600; }
@media (max-width:900px){
  .suivi-mesures-actions > .kpis{ grid-template-columns:repeat(2, minmax(0,1fr)); }
  .suivi-mesures-etats   > .kpis{ grid-template-columns:repeat(2, minmax(0,1fr)); }
}
/* ⚠ Dans une rangée, les tuiles ont la MÊME hauteur : un sous-titre sur deux
   lignes ne doit pas faire remonter ses voisines — c'est ce qui donnait
   l'escalier. */
.suivi-mesure-groupe > .kpis{ align-items:stretch; }
.suivi-mesure-groupe .kpi{ height:100%; }
/* ⚠ Et le liseré coloré du haut n'a plus lieu d'être : le FOND porte le ton
   dans ces blocs, et deux marques pour un fait en font une de trop. */
.suivi-mesure-groupe .kpi{ border-top-color:transparent !important; }
/* ⚠ La barre du suivi passe à la ligne à un endroit CHOISI : `suivi-barre-saut`
   force la coupure après les quatre commandes qu'on touche à chaque visite.
   Sans elle, l'enroulement dépendait de la largeur de la fenêtre et coupait au
   milieu d'un groupe. */
.suivi-barre-saut{ flex-basis:100%; height:0; }
.suivi-q-champ{ flex:1 1 260px; max-width:320px; }
.suivi-barre .champ-recherche input{
  background:var(--carte); border-color:var(--line-champ);
  height:38px; border-radius:8px;
}
.suivi-barre .input{ height:38px; border-radius:8px; }
.btn.suivi-raz{ color:var(--poppy); }
/* ⚠⚠ DÉSACTIVÉ, IL N'EST PLUS COQUELICOT (30/09) — même remède que `.frm-raz`
     sur l'annuaire des intervenants, deux jours plus tôt. Un rouge pâle qu'on
     ne peut pas actionner se lit comme une action dangereuse indisponible, et
     il posait la seule couleur d'alerte d'une barre où rien n'est urgent. La
     couleur d'action revient avec la possibilité d'agir. */
.suivi-raz:disabled{ color:var(--muted); }
.btn.suivi-raz:hover{ color:var(--poppy-dark); background:var(--soft2); }
/* ⚠⚠ ET SA RAISON SE LIT AU SURVOL (30/09 au soir) — même remède que
     `.frm-raz`, section 25 : sans lui, `pointer-events:none` (section 7)
     envoyait le survol à `.suivi-barre` et l'infobulle « Aucun filtre n'est
     posé… » ne paraissait jamais. Le survol d'un bouton désactivé ne le
     repeint pas en coquelicot : la règle du dessus vaut pour un bouton qui
     agit. Et l'appui ne l'enfonce pas d'un pixel (01/10) : `transform:none`,
     même raison que `.frm-raz`. */
.btn.suivi-raz:disabled{ pointer-events:auto; transform:none; }
.btn.suivi-raz:disabled:hover{ color:var(--muted); background:transparent; }

/* ═══════════════════════════════════════════════════════════════════════════
   33. DOCUMENTS — L'ÉCRAN SUR SA MAQUETTE (26/09/2026)   .exp-*
   ───────────────────────────────────────────────────────────────────────────
   ⚠ ADDITIF, ANNONCÉ. Toutes les classes sont préfixées `exp-`, comme la
   section 18 dont ceci est la suite : aucune règle commune n'est modifiée.

   ⚠⚠ CE QUI CHANGE, ET POURQUOI. L'écran était une fenêtre d'explorateur :
   une barre d'outils, un volet, une liste, un pied. À la RACINE, cette forme
   ne disait rien — trois lignes, quatre colonnes dont deux vides, et les
   chiffres qui comptent (neuf clients, sept intervenants) en petits
   caractères. La maquette la remplace par trois TUILES, un raccourci vers les
   derniers fichiers, et une carte qui dit le classement. Dès qu'on entre dans
   un dossier, la fenêtre d'explorateur revient : c'est elle qui sait trier
   quarante fichiers.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── La barre de tête : recherche, filtres, affichage ──
   ⚠ Elle est AU-DESSUS des trois colonnes et non dans la carte du dossier :
     la recherche va partout, et l'affichage est un réglage de la personne. */
.exp-tete{ display:flex; align-items:center; gap:10px; margin-bottom:14px;
           flex-wrap:wrap; }
.exp-q-champ{ flex:1 1 320px; min-width:0; }
.exp-filtres{ position:relative; }
/* ⚠⚠ `position:absolute` ET NON un panneau dans le flux : posé dans le flux,
     il pousserait les trois colonnes vers le bas à chaque ouverture, et la
     liste qu'on filtre sauterait sous l'œil au moment où on la regarde. */
.exp-filtres-panneau{
  position:absolute; z-index:30; top:calc(100% + 6px); left:0; min-width:250px;
  background:var(--carte); border:1px solid var(--line); border-radius:var(--r);
  box-shadow:var(--ombre-haut); padding:10px 12px;
  display:flex; flex-direction:column; gap:8px;
}
.exp-filtres-panneau label{ display:flex; align-items:center; gap:8px;
                            font-size:13px; cursor:pointer; }
.exp-vues{ display:inline-flex; gap:0; }
.exp-vues > .btn{ border-radius:0; }
.exp-vues > .btn:first-child{ border-top-left-radius:var(--r-s);
                              border-bottom-left-radius:var(--r-s); }
.exp-vues > .btn:last-child{ border-top-right-radius:var(--r-s);
                             border-bottom-right-radius:var(--r-s); }
.exp-vues > .btn + .btn{ margin-left:-1px; }
.exp-vues > .btn.primaire{ position:relative; z-index:1; }

/* ── Trois colonnes ──
   ⚠ La carte d'aide passe SOUS le contenu avant de disparaître : c'est elle
     qui explique le classement, et elle sert surtout à qui arrive. */
.exp-corps{ display:grid; grid-template-columns:250px minmax(0,1fr) 330px;
            gap:14px; min-height:0; align-items:start; }
@media (max-width:1400px){
  .exp-corps{ grid-template-columns:230px minmax(0,1fr); }
  .exp-aide{ grid-column:1 / -1; }
}
@media (max-width:900px){ .exp-corps{ grid-template-columns:1fr; } }

/* ⚠ L'enveloppe `.exp` perd son cadre : ce sont les CARTES qui en portent un
     désormais. Un cadre autour de trois cartes fait deux bords pour une chose. */
.exp{ border:0; border-radius:0; background:transparent; box-shadow:none;
      overflow:visible; }
.exp-volet{ border:1px solid var(--line); border-right:1px solid var(--line);
            border-radius:var(--r); background:var(--carte);
            box-shadow:var(--ombre); padding:12px 10px; }
.exp-centre{ min-width:0; display:flex; flex-direction:column; gap:14px; }

.exp-bloc{ background:var(--carte); border:1px solid var(--line);
           border-radius:var(--r); box-shadow:var(--ombre); padding:14px 16px; }
.exp-bloc > h2{ font-size:14px; font-weight:700; margin:0 0 12px; color:var(--ink); }
.exp-bloc-tete{ display:flex; align-items:center; justify-content:space-between;
                gap:10px; margin-bottom:10px; }
.exp-bloc-tete > h2{ font-size:14px; font-weight:700; margin:0; color:var(--ink); }
.btn.exp-tout{ color:var(--poppy); gap:5px; }
.btn.exp-tout:hover{ color:var(--poppy-dark); background:var(--soft2); }
/* La carte qui porte la liste d'un dossier garde sa barre et son pied collés
   aux bords : ils bordent le contenu, ils ne s'alignent pas dessus. */
.exp-bloc-liste{ padding:0; overflow:hidden; }

/* ── Les trois grandes tuiles ── */
.exp-tuiles{ display:grid; grid-template-columns:repeat(auto-fit, minmax(210px,1fr));
             gap:12px; }
.exp-tuile{
  display:flex; flex-direction:column; align-items:flex-start; gap:6px;
  padding:16px; border:1px solid var(--line); border-radius:var(--r);
  background:var(--carte); cursor:pointer; text-align:left; font:inherit;
  color:var(--ink);
}
.exp-tuile:hover{ border-color:var(--poppy-clair); box-shadow:var(--ombre-haut); }
.exp-tuile:focus-visible{ outline:2px solid var(--poppy); outline-offset:2px; }
.exp-tuile-ic{ display:inline-flex; margin-bottom:6px; }
/* ⚠⚠ TROIS TEINTES, ET ELLES N'ENCODENT PAS UNE URGENCE. La règle du 16/08 dit
     qu'une couleur encode l'urgence, jamais le type — elle vise les ALERTES.
     Ici rien n'alerte : ce sont trois repères de rangement, et c'est
     exactement ce que fait la couleur d'un dossier dans un classeur. Aucune
     n'est le rouge de l'action ni l'orange de l'attente. */
.exp-t-a{ color:#9c2743; }
.exp-t-b{ color:#6b5ca5; }
.exp-t-c{ color:var(--muted); }
/* ⚠ 15,5 px et non 14,5 : le nom d'un dossier principal est ce qu'on lit en
     premier sur cet écran, et la maquette le pose à la taille d'un titre de
     carte. Mesuré côte à côte. */
.exp-tuile-nom{ display:flex; align-items:center; justify-content:space-between;
                width:100%; gap:8px; font-weight:700; font-size:15.5px; }
.exp-tuile-chev{ color:var(--muted); display:inline-flex; }
.exp-tuile:hover .exp-tuile-chev{ color:var(--poppy); }
.exp-tuile-n{ font-size:12.5px; color:var(--muted); }
.exp-tuile-d{ font-size:12px; color:var(--muted); }

/* ── La carte d'aide ── */
.exp-aide{ background:var(--carte); border:1px solid var(--line);
           border-radius:var(--r); box-shadow:var(--ombre); padding:14px 16px; }
.exp-aide > h2{ font-size:14px; font-weight:700; margin:0 0 14px; color:var(--ink); }
.exp-aide-item{ display:flex; gap:11px; margin-bottom:14px; }
.exp-aide-ic{ flex:0 0 auto; width:36px; height:36px; border-radius:50%;
              display:inline-flex; align-items:center; justify-content:center;
              background:var(--soft2); }
.exp-aide-item strong{ display:block; font-size:13.5px; margin-bottom:2px; }
.exp-aide-item p{ margin:0; font-size:12.5px; color:var(--muted); line-height:1.5; }
.btn.exp-aide-lien{ color:var(--poppy); padding-left:0; gap:5px;
                    border-top:1px solid var(--line); border-radius:0;
                    width:100%; justify-content:flex-start; padding-top:12px;
                    text-align:left; white-space:normal; }
.btn.exp-aide-lien:hover{ color:var(--poppy-dark); background:transparent;
                          text-decoration:underline; }

/* ⚠ Les icônes de la liste et du volet sont des `<svg>` depuis le 26/09 :
     elles s'alignent sur le texte plutôt que de flotter comme un caractère. */
.exp-ic{ display:inline-flex; align-items:center; color:var(--muted); }
.exp-volet a .exp-ic{ color:inherit; }
.exp-carte .vign{ display:inline-flex; align-items:center; justify-content:center;
                  color:var(--muted); }

/* ── Le volet, la bascule et les teintes, sur la maquette (26/09) ── */
.exp-volet-titre{ font-size:14px; font-weight:700; color:var(--ink);
                  margin:0 0 10px; padding:0 8px; }
/* ⚠ L'entrée retenue est une PILULE, pas un simple gras : dans une colonne de
     quatre lignes, le gras seul ne se distingue pas du titre juste au-dessus. */
.exp-volet a.actif{ background:var(--soft); color:var(--poppy-dark); font-weight:700; }
.exp-volet a.actif .exp-ic{ color:var(--poppy); }
.exp-volet a{ margin-bottom:2px; }
/* ⚠ Les icônes du volet sont teintées par leur branche, comme les tuiles :
     c'est le même repère de rangement, il doit être le même signe. */
.exp-volet a[data-aller="clients"] .exp-ic{ color:#9c2743; }
.exp-volet a[data-aller="formateurs"] .exp-ic{ color:#6b5ca5; }

.exp-chev{ color:var(--muted); font-size:11px; margin-left:2px; }
/* ⚠⚠ L'ÉTAT RETENU SE MARQUE EN DOUCEUR. Le coquelicot plein est la couleur de
     l'ACTION : posé sur « Vue grille », il fait de l'état courant le bouton le
     plus voyant de l'écran. Aplat rosé, texte coquelicot, bordure assortie. */
.exp-vues > .btn.exp-vue-actif{
  background:var(--soft); color:var(--poppy-dark);
  border-color:var(--poppy-clair); position:relative; z-index:1;
}
/* Les grandes tuiles prennent le fond très pâle de la maquette. */
.exp-tuile{ background:var(--soft2); }
.exp-tuile:hover{ background:var(--carte); }
/* Les ronds de la carte d'aide portent la teinte de leur branche, très diluée. */
.exp-aide-ic.exp-t-a{ background:#fbecef; }
.exp-aide-ic.exp-t-b{ background:#f1eefa; }
/* Le champ de recherche de cet écran est à la hauteur des boutons voisins. */
.exp-q-champ input{ height:44px; }
.exp-tete .btn{ height:44px; }

/* ⚠⚠ LA TEINTE NE DESCEND PAS SUR LE NOM. `.exp-t-a` posée sur la TUILE
     colorait aussi « Clients » : trois titres de trois couleurs pour trois
     dossiers de même nature — c'est encore encoder un type par une couleur, et
     cette fois sur du TEXTE, là où ça se voit le plus. La maquette met les
     trois noms à l'encre et ne teinte que le dossier. */
.exp-tuile.exp-t-a, .exp-tuile.exp-t-b, .exp-tuile.exp-t-c{ color:var(--ink); }
.exp-tuile.exp-t-a .exp-tuile-ic{ color:#9c2743; }
.exp-tuile.exp-t-b .exp-tuile-ic{ color:#6b5ca5; }
.exp-tuile.exp-t-c .exp-tuile-ic{ color:#9a9a9a; }
/* ⚠ Le champ de recherche de cet écran est un champ de FORMULAIRE, pas la
     pilule sourdine de la barre du haut : il est large, blanc et bordé comme
     les commandes qui l'entourent (maquette). Borné à `.exp-q-champ` — la
     pilule du socle ne bouge pas. */
.exp-q-champ input{ border-radius:var(--r); background:var(--carte);
                    font-size:14px; padding-left:44px; }
.exp-q-champ::before{ left:16px; font-size:17px; }
/* La colonne d'aide tient son titre sur une ligne. */
@media (min-width:1401px){
  .exp-corps{ grid-template-columns:250px minmax(0,1fr) 348px; }
}

/* ⚠⚠ LA LOUPE DU SOCLE EST LE CARACTÈRE `⌕`, ET IL EST CHÉTIF. Il se dessine
     — contrairement à `⧗` ou `👤` — mais c'est un trait fin de 17 px qui ne
     ressemble pas à la loupe franche de la maquette, et sa forme change d'une
     police à l'autre. Sur CET écran, où le champ fait 44 px de haut et occupe
     la moitié de la barre, ça se voit.
   ⚠ Le remède est une image de FOND en `data:` — un `content:` ne sait pas
     porter de SVG. Aucune requête réseau, aucune dépendance.
   ⚠ Et c'est BORNÉ à `.exp-q-champ` : `.champ-recherche` sert la barre du haut
     et six écrans, dont ceux de dev A. Le jour où l'on voudra la même loupe
     partout, ce sera une décision à deux, pas un effet de bord. */
.exp-q-champ::before{ content:none; }
.exp-q-champ input{
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236f6f6f' stroke-width='1.8' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='M16.5 16.5L21 21'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:15px center; background-size:19px 19px;
}
/* Le chevron d'une tuile prend la teinte de son dossier : c'est le même geste. */
.exp-tuile.exp-t-a .exp-tuile-chev{ color:#9c2743; }
.exp-tuile.exp-t-b .exp-tuile-chev{ color:#6b5ca5; }
.exp-tuile.exp-t-c .exp-tuile-chev{ color:#9a9a9a; }

/* ⚠ La teinte d'une branche passe APRÈS `.exp-ic` (même spécificité, c'est le
     dernier écrit qui gagne) : sans ces trois lignes, les icônes du tableau de
     l'accueil retombaient au gris sourdine et la vue liste perdait le repère de
     couleur que la vue grille donne. Les deux vues montrent la même chose. */
.exp-ic.exp-t-a{ color:#9c2743; }
.exp-ic.exp-t-b{ color:#6b5ca5; }
.exp-ic.exp-t-c{ color:#9a9a9a; }

/* ═══════════════════════════════════════════════════════════════════════════
   34. BILAN PÉDAGOGIQUE ET FINANCIER — L'ÉCRAN SUR SA MAQUETTE (27/09/2026)
   ───────────────────────────────────────────────────────────────────────────
   ⚠ ADDITIF, ANNONCÉ. Tout est préfixé `bpf-` ; aucune règle commune ne bouge.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Les deux bandeaux de tête ──
   ⚠⚠ Les commandes étaient COLLÉES à la fin de la phrase : sur un écran large
     elles se retrouvaient au milieu du bandeau, là où l'œil ne cherche pas une
     commande. Poussées au bord droit, elles reprennent la place qu'elles ont
     partout ailleurs dans cet ERP. */
.bpf-bandeau{ display:flex; align-items:flex-start; gap:12px; }
.bpf-bandeau-ic{ flex:0 0 auto; display:inline-flex; margin-top:1px; }
.bpf-bandeau-txt{ flex:1 1 auto; min-width:0; }
.bpf-bandeau-sous{ display:block; margin-top:3px; font-weight:400; opacity:.85; }
.bpf-bandeau-actions{ flex:0 0 auto; display:flex; gap:8px; align-items:center; }
@media (max-width:820px){
  .bpf-bandeau{ flex-wrap:wrap; }
  .bpf-bandeau-actions{ width:100%; }
}

/* ── L'en-tête d'une carte : son icône, et d'où viennent ses chiffres ── */
.bpf-tete-ic{ display:inline-flex; vertical-align:-3px; margin-right:7px;
              color:var(--poppy); }
/* ⚠ Le badge de provenance est NEUTRE et non coloré : il n'alerte de rien, il
     situe. La règle du 16/08 vaut ici comme ailleurs. */
.b.bpf-source{ font-weight:500; }

/* ── Les réserves ── */
.bpf-reserve{ display:flex; align-items:flex-start; gap:9px; }
.bpf-reserve-ic{ flex:0 0 auto; display:inline-flex; margin-top:2px; }
.bpf-reserves .corps > .alerte + .alerte{ margin-top:8px; }

/* ── Le financier et le cadre C, côte à côte ──
   ⚠ `align-items:start` : sans lui, la carte la plus courte s'étire à la
     hauteur de l'autre et son tableau flotte au-dessus d'un vide. */
.bpf-duo{ display:grid; grid-template-columns:1fr 1fr; gap:14px;
          align-items:start; margin-bottom:14px; }
@media (max-width:1200px){ .bpf-duo{ grid-template-columns:1fr; } }
.bpf-duo > .panel{ margin-bottom:0; }
/* ⚠ `.bpf-carte` ne porte AUCUNE déclaration propre, et c'est délibéré : elle
     marque les trois panneaux de cet écran pour que la prochaine retouche ait
     un point d'accroche, sans rien changer à `.panel`. Une classe déclarée vide
     est honnête ; une classe posée dans le marquage et absente de la feuille
     est le piège du 16/08. */
.bpf-carte{ /* marqueur — voir ci-dessus */ }
/* ⚠ Le chevron TOURNE plutôt que de changer de caractère : « ▴ » et « ▾ » ne
     font pas la même largeur dans cette police, et le bouton sautait d'un pixel
     à chaque repli. */
.bpf-chev{ display:inline-block; transition:transform .15s; font-size:12px; }
#bpf-plier[aria-expanded="false"] .bpf-chev{ transform:rotate(-90deg); }

/* ── Modèles de document : la barre de comptage (27/09, maquette) ──
   ⚠ ADDITIF, préfixé `mod-`. Deux raccourcis vers les deux sections de la
     page ; ce sont des BOUTONS, pas des liens d'ancre — le `#` est le routeur
     de cette application. */
/* ⚠ LA BARRE EST SUR LE MÊME APLAT BLEU QUE LE BANDEAU DE SESSION qui la
     précède, et son texte est bleu : les deux ensemble forment le CONTEXTE de
     lecture de la page — la session choisie, et ce qu'elle contient. Posée en
     blanc sur fond blanc, elle se lisait comme une quatrième carte de contenu.
     C'est le dessin de la maquette, mesuré sur l'image, et non mon idée. */
.mod-compteurs{ background:var(--info-bg); border-color:transparent; box-shadow:none; }
.mod-compteurs > .corps{ display:flex; align-items:center; gap:18px;
                         flex-wrap:wrap; padding:12px 16px; }
.mod-compteurs button{
  display:inline-flex; align-items:center; gap:9px;
  background:none; border:0; padding:4px 2px; font:inherit; font-size:14px;
  color:var(--info); cursor:pointer; border-radius:var(--r-s);
}
.mod-compteurs button b{ color:#1d4a73; font-size:15px; }
.mod-compteurs button .ic{ display:inline-flex; color:#1d4a73; }
.mod-compteurs button:hover b, .mod-compteurs button:hover{ color:var(--poppy-dark); }
.mod-compteurs button:focus-visible{ outline:2px solid var(--poppy); outline-offset:2px; }
/* ⚠ Le séparateur est un TRAIT, pas un caractère : « | » change de hauteur
     d'une police à l'autre et ne se centre jamais sur la ligne. */
.mod-sep{ width:1px; height:24px; background:var(--line); }
@media (max-width:560px){ .mod-sep{ display:none; } }
/* ⚠ Bornée au catalogue des modèles : `.petit` sert partout, et deux écrans de
     dev A l'emploient dans un en-tête de panneau. */
.mod-aide{ font-size:12px; flex:1 1 auto; min-width:0; }

/* ── Qualiopi : le pied de pagination (27/09, maquette) ──
   ⚠ ADDITIF, préfixé `q-`. Aucune règle commune ne bouge. */
/* ⚠ Le pied est GRIS, pas rosé : `--soft2` est un rose très pâle, et posé
     sous un tableau dont une ligne sur deux est déjà rosée, il prolongeait le
     zébrage — on ne voyait plus où finissait la liste. */
.q-pied{ display:flex; align-items:center; justify-content:space-between; gap:12px;
         flex-wrap:wrap; padding:9px 14px; border-top:1px solid var(--line);
         background:#fafafa; font-size:12.5px; color:var(--muted); }
.q-pages{ display:flex; align-items:center; gap:3px; }
.q-pages .btn.petit{ min-width:30px; justify-content:center; padding:5px 8px; }
/* ⚠ La page courante se marque en DOUCEUR : le coquelicot plein est la couleur
     de l'action, et l'état courant n'est pas une action. */
.q-pages .btn.q-page-ici{ background:var(--soft); color:var(--poppy-dark);
                          border-color:var(--poppy-clair); font-weight:700; }
.q-saut{ padding:0 4px; color:var(--muted); }
/* Le retard, sous le compte de pièces qu'il qualifie. */
.q-retard{ margin-top:4px; }
/* ⚠⚠ LA BARRE D'OUTILS TIENT SUR LA LIGNE DU TITRE (27/09, maquette). Elle
     passait en dessous : le bloc titre est un enfant de flex sans `min-width`,
     donc il réclame la largeur de son SOUS-TITRE — une phrase de 460 px — et
     pousse les sept commandes à la ligne suivante. `min-width:0` lui rend le
     droit de rétrécir ; le sous-titre passe alors sur deux lignes quand il le
     faut, ce qui est le bon compromis : une phrase qui s'enroule coûte moins
     qu'une barre d'outils qu'on cherche. */
/* ⚠⚠ `flex:1 1 auto` A AGGRAVÉ LE DÉFAUT QU'IL DEVAIT CORRIGER. Avec
     `flex-grow:1`, le bloc titre prend TOUTE la place restante et pousse la
     barre d'outils à la ligne suivante — exactement ce qu'on voulait empêcher.
     Ce qu'il lui faut, c'est le droit de RÉTRÉCIR (`1` en flex-shrink,
     `min-width:0`) et surtout pas celui de grandir. Vu sur la capture, pas en
     relisant la règle. */
/* ⚠⚠ ET LA VRAIE CAUSE N'ÉTAIT NI L'UN NI L'AUTRE : `flex-wrap` ENROULE AVANT
     DE RÉTRÉCIR. Le navigateur place d'abord les items sur la ligne d'après
     leur taille de base — ici la largeur du sous-titre, 446 px — et n'applique
     `flex-shrink` qu'ensuite, ligne par ligne. Donner `min-width:0` au titre ne
     sert donc à rien tant que l'enveloppe a le droit d'enrouler : il manquait
     ONZE pixels, et la barre partait à la ligne suivante.
   ⚠ D'où `nowrap` au-dessus de 1 100 px, où la place existe : le titre rétrécit
     alors pour de bon et son sous-titre passe sur deux lignes si besoin — une
     phrase qui s'enroule coûte moins qu'une barre d'outils qu'on cherche.
     En dessous, `wrap` reprend : sur un écran étroit, l'empilement est juste.
   ⚠ Le garde-fou de `capture_vue.php` a dit « image identique à la
     précédente » sur ma première correction : `flex:0 1 auto` est la valeur
     PAR DÉFAUT d'un item flex, donc elle ne changeait rien. Sans lui, j'aurais
     cru avoir corrigé. */
/* ⚠⚠ TOUT LE RÉGLAGE DE LARGEUR DE `.q-tete` A ÉTÉ RETIRÉ LE 28/09, ET IL
     N'AVAIT PLUS D'OBJET DEPUIS LA VEILLE. Il existait pour faire tenir sept
     commandes à côté du titre — `nowrap` au-dessus de 1100 px, chips resserrées
     de 95 à 75 px, boutons à 6 px, champ borné à 210 px, « la barre tient à
     quatre pixels près ». La maquette du 27/09 **descend cette barre dans la
     carte du tableau** : l'en-tête ne porte plus que le titre et son
     sous-titre, et il n'y a plus rien à faire tenir.
   ⚠⚠ Dev A l'a relevé et m'a laissé le geste, parce que ces règles sont les
     miennes : « les y remettre écrase la barre du tableau (mesuré sur
     capture) ». Du CSS qui ne peint plus rien ne se voit pas — c'est le piège
     de la classe inventée, dans l'autre sens : ici la classe existe, mais la
     structure qu'elle réglait n'existe plus. **On retire ce qui n'a plus
     d'objet, on ne le garde pas « au cas où ».**
   ⚠ `.q-aide` reste : l'aide vit toujours dans l'en-tête de la carte, à côté
     du titre « Dossiers de session », et elle doit toujours pouvoir rétrécir. */
.q-aide{ flex:1 1 auto; min-width:0; font-size:12px; }
#vue-qualiopi .kpis .kpi .l{ font-size:10.5px; letter-spacing:.3px; }
/* ⚠ La colonne « Ce qui manque » porte le titre de l'écran : elle a la place,
     les autres se serrent. `table-layout:fixed` fait respecter les largeurs
     déclarées plutôt que de les traiter comme des suggestions. */
#vue-qualiopi .panel .tbl{ table-layout:fixed; }
#vue-qualiopi .panel .tbl td{ vertical-align:top; }
/* ⚠ Les trois gestes de la carte restent sur la ligne du titre, même raison
     que la barre de tête : `flex-wrap` enroule avant de rétrécir, et l'aide
     mesure 430 px. Au-dessus de 1 100 px, elle rétrécit au lieu de pousser. */
/* ⚠⚠ LE `nowrap` NE VISE QUE LA CARTE DES DOSSIERS (27/09). Appliqué à toutes
     les cartes de l'écran, il écrasait l'aide de « Réclamations, incidents et
     améliorations » — deux lignes dans le dessin, trois chez moi, coincées
     entre un titre et deux boutons. Une règle posée pour tenir trois gestes
     sur une ligne ne vaut pas pour une carte qui n'en a que deux. */
@media (min-width:1100px){
  #vue-qualiopi .q-carte-dossiers > header{ flex-wrap:nowrap; }
  #vue-qualiopi .q-carte-dossiers > header .actions{ flex-wrap:nowrap; }
}
/* ⚠ Le titre de la carte ne se coupe pas : avec `nowrap` sur l'enveloppe, le
     `h2` est le premier à rétrécir et « Dossiers de session » passait sur deux
     lignes. C'est l'aide qui doit céder la place, pas le titre. */
#vue-qualiopi .q-carte-dossiers > header > h2{ white-space:nowrap; flex:0 0 auto; }

/* ── CRM : l'encart de suivi à deux colonnes (27/09, maquette) ──
   ⚠ ADDITIF, préfixé `dem-`. Il revient à la forme du 26/09 au matin : les
     deux colonnes disent QUELLES demandes dorment, ce qu'une ligne de résumé
     ne peut pas faire. */
.dem-suivre{ display:block; }
.dem-suivre-tete{ display:flex; align-items:flex-start; gap:12px; }
.dem-suivre-corps{
  display:flex; align-items:flex-start; gap:22px; flex-wrap:wrap;
  margin:10px 0 0 42px; padding-top:10px;
}
.dem-suivre-col{ flex:1 1 280px; min-width:0; }
/* ⚠ Un filet SÉPARE les deux colonnes : ce sont deux journées différentes
     (relancer, travailler), pas deux moitiés d'une même liste. */
.dem-suivre-col + .dem-suivre-col{ border-left:1px solid var(--att);
                                   padding-left:22px; }
.dem-suivre-ct{ display:flex; align-items:center; gap:7px; margin-bottom:5px;
                font-size:12.5px; color:var(--att); }
.dem-suivre-ct .ic{ display:inline-flex; }
.dem-suivre-col ul{ margin:0; padding-left:18px; font-size:12px;
                    color:var(--ink); line-height:1.7; }
.dem-suivre-cta{ flex:0 0 auto; margin-left:auto; align-self:center; }
@media (max-width:900px){
  .dem-suivre-corps{ margin-left:0; }
  .dem-suivre-col + .dem-suivre-col{ border-left:0; padding-left:0; }
}
/* La croix de repli du bandeau de suivi : discrète, au coin. */
.dem-suivre{ position:relative; }
.dem-suivre-x{
  position:absolute; top:8px; right:10px; border:0; background:none;
  font-size:18px; line-height:1; color:var(--muted); cursor:pointer;
  padding:2px 6px; border-radius:var(--r-s);
}
.dem-suivre-x:hover{ color:var(--ink); background:rgba(0,0,0,.05); }
.dem-suivre-x:focus-visible{ outline:2px solid var(--poppy); outline-offset:2px; }

/* ⚠⚠ LE NOMBRE PREND LE TON DE SA MESURE (27/09, maquette). Les trois nombres
     étaient à l'encre : « 11 », « 4 » et « 3 » se lisaient comme trois
     comptages équivalents, alors que deux d'entre eux appellent un geste. Le
     dessin met « 11 » en coquelicot (le vivier), les deux autres en orange.
   ⚠ Ce n'est pas encoder un type par une couleur : ces deux-là sont des
     ALERTES — pièces manquantes, disponibilité à surveiller — et elles portent
     déjà l'orange sur leur icône. Le nombre le reprend parce que c'est LUI
     qu'on lit à deux mètres. */
.frm-mesure.t-vivier    .t b{ color:var(--poppy-dark); }
.frm-mesure.t-alerte    .t b,
.frm-mesure.t-vigilance .t b{ color:var(--att); }
/* ⚠ Et le titre de ces deux-là suit son nombre : « 3 à surveiller » est un
     tout, le couper en deux couleurs le ferait lire en deux temps. */
.frm-mesure.t-vigilance .t{ color:var(--att); }
/* ⚠ La période est dans un CADRE, entre ses deux flèches (maquette) : en texte
     nu, « Septembre — octobre 2026 » se lisait comme un titre posé là, et les
     deux flèches semblaient commander autre chose. Encadrée, elle devient ce
     qu'elle est — la valeur que les flèches font varier. */
.gantt-outils .gantt-periode{
  display:inline-flex; align-items:center; justify-content:center;
  min-width:210px; height:34px; padding:0 14px;
  border:1px solid var(--line-champ); border-radius:var(--r-s);
  background:var(--carte); font-size:13.5px;
}

/* ---------------------------------------------------------------------
   35. ÉVALUATIONS — l'accueil, porté sur la maquette du 27/09 (image 15)
   ---------------------------------------------------------------------
   ⚠⚠ LES TUILES METTENT LE CHIFFRE AU-DESSUS DU LIBELLÉ, ET C'EST L'INVERSE
     DE `.tuile-ic` (Demandes, Planning), qui pose le libellé en capitales
     au-dessus. Les deux sont des dessins de Julien, et ils ne s'accordent
     pas — exactement comme le côté de l'icône, tranché le 27/09 au matin.
     Je ne choisis pas à sa place : la forme de CET écran suit SA maquette,
     la divergence part au point de coordination. Un modificateur BORNÉ à
     `.ev-tuile`, jamais une reprise de `.tuile-ic`, qui casserait deux
     écrans pour un troisième.
   ⚠ Le libellé y est en minuscules, et ce n'est pas un détail : « sans
     retour » en capitales espacées se lit comme une rubrique ; en
     minuscules, comme la légende du nombre — ce qu'il est. */
.ev-tuile .n{ order:1; }
.ev-tuile .l{
  order:2; text-transform:none; letter-spacing:0; font-weight:400;
  font-size:12.5px; margin:2px 0 0;
}
.ev-tuile .ic{ width:40px; height:40px; border-radius:11px; }
.tuile-ic.t-att .ic{ background:var(--att-bg); color:var(--att); }
.tuile-ic.t-ok  .ic{ background:var(--ok-bg);  color:var(--ok); }

/* ⚠ Le bandeau n'est pas une tuile de plus : il porte une PHRASE et une
     action. D'où la grille `auto 1fr auto` — l'icône ne bouge pas, le texte
     prend ce qui reste, le bouton reste collé à droite quelle que soit la
     longueur de la phrase. */
.ev-bandeau{
  display:grid; grid-template-columns:auto 1fr auto; align-items:center; gap:14px;
  /* ⚠ `#f0d9a8` est la bordure que `.alerte.att` emploie déjà depuis le
       socle : la reprendre vaut mieux qu'inventer un jeton — j'avais
       écrit `var(--att-bord, …)`, que le contrôle 20 a attrapé. */
  background:var(--att-bg); border:1px solid #f0d9a8;
  border-radius:var(--r); padding:14px 16px; margin-bottom:14px;
}
.ev-bandeau .ic{ color:var(--att); display:flex; }
.ev-bandeau .txt strong{ color:var(--ink); }
.ev-bandeau .txt .petit{ color:var(--muted); margin-top:2px; }
/* ⚠ Sur téléphone la grille s'empile et le bouton reprend toute la largeur :
     un bouton de 280 px poussé à droite d'une colonne de 320 px déborderait. */
@media (max-width:640px){
  .ev-bandeau{ grid-template-columns:auto 1fr; }
  .ev-bandeau .btn{ grid-column:1 / -1; }
}

.ev-onglets{ margin-bottom:14px; }
/* ⚠⚠ LA BARRE N'ENROULE PAS AU-DESSUS DE 760 px, ET ELLE EST DÉCLARÉE :
     `flex-wrap` enroule AVANT de rétrécir — la recherche réclamerait sa
     largeur de base et les chips tomberaient seules sur une seconde ligne
     alors que la place existe. Leçon payée trois fois ce mois-ci. */
.ev-barre{
  display:flex; flex-wrap:wrap; align-items:center; gap:10px;
  padding:12px 14px; border-bottom:1px solid var(--line);
}
@media (min-width:760px){ .ev-barre{ flex-wrap:nowrap; } }
.ev-rech{ flex:1 1 240px; min-width:0; }
.ev-chips{ display:flex; flex-wrap:wrap; align-items:center; gap:7px; }
.ev-chips .chip.actif{ background:var(--poppy); border-color:var(--poppy); color:#fff; }
/* ⚠ Un filtre à zéro se DÉSACTIVE avec sa raison, il ne se masque pas — mais
     il doit alors se VOIR désactivé, sinon on clique dans le vide. */
.ev-chips .chip[disabled]{ opacity:.45; cursor:not-allowed; }

/* ⚠ La pastille porte son icône : le dessin met « ⚠ Aucun » et « ✓ 2 à
     chaud ». L'icône prend la couleur du texte, donc celle du ton. */
.ev-b{ display:inline-flex; align-items:center; gap:5px; }
.ev-b svg{ flex:0 0 auto; }
.ev-froid{ font-size:11.5px; color:var(--muted); margin-top:3px; }
.ev-pied{ padding:10px 14px; }

/* ⚠⚠ PAS DE ZÉBRAGE SUR CE TABLEAU (27/09, maquette). Deux couleurs de ligne
     sur le même tableau s'annulent : la parité teinte une ligne sur deux et la
     pastille « Aucun » signale une ligne sur quatorze — c'est le défaut du
     26/09 sur les demandes, à l'identique. Le dessin montre des lignes
     blanches et laisse la pastille porter seule le signal.
   ⚠ Borné à CET écran : le zébrage reste la règle partout ailleurs. */
#vue-evaluations table.tbl tbody tr:nth-child(even){ background:transparent; }

/* ⚠⚠ PAS DE ZÉBRAGE SUR L'ANNUAIRE DES CLIENTS NON PLUS (28/09), ET C'EST LA
     MESURE QUI TRANCHE, PAS LE GOÛT. Relevé sur la vue rendue, au pixel :
       · une ligne zébrée vaut **(253, 246, 245)** ;
       · une ligne « à relancer » vaut **(253, 243, 226)**.
     Elles sont **identiques sur le rouge et sur le vert**, et ne diffèrent que
     de 19 points de bleu sur 255 — 7 %. Autrement dit, la seule ligne de
     l'écran qui appelle un geste se distingue à peine d'une ligne sur deux qui
     n'appelle rien. **Deux couleurs de ligne sur le même tableau s'annulent** —
     défaut du 26/09 sur les demandes, du 27/09 sur les évaluations, ici pour la
     troisième fois.
   ⚠ Et ce zébrage n'est pas gris : `--soft2` est un aplat COQUELICOT (leçon du
     03/09, les week-ends roses de la frise). Une ligne sur deux de cet écran
     était donc teintée de la couleur d'action.
   ⭐ La maquette validée ne porte aucun zébrage. La teinte d'alerte reste, et
     redevient le seul signal de l'écran.
   ⚠ Borné à CET écran : le zébrage reste la règle partout ailleurs, et
     `lib_banc_tableaux` le vérifie sur un tableau quelconque. */
/* ⚠⚠ ET LA RÈGLE ÉPARGNE LA LIGNE CHOISIE — trouvé par le banc des tableaux
     À LA SECONDE où le zébrage a été coupé, jamais par une relecture. Écrite
     sans `:not()`, elle bat `tr.selectionnee` en spécificité (0,2,2 contre
     0,1,2) : **cliquer une ligne PAIRE ne la teintait plus du tout**, sur un
     écran où le clic ouvre l'activité. On retire le zébrage, on ne retire pas
     la sélection. C'est une CLASSE, jamais un `:has()` : un sélecteur dont
     l'absence recrée le défaut n'est pas un sélecteur, c'est un pari.
   ⚠ La teinte « à relancer » n'a pas besoin d'y figurer : `clients.js` la pose
     EN LIGNE, et un style en ligne l'emporte sur toute feuille. */
#vue-clients table.tbl tbody tr:nth-child(even):not(.selectionnee){ background:transparent; }

/* ⚠⚠ UNE SEULE LIGNE DE COMMANDES SUR L'ANNUAIRE DES CLIENTS (28/09, demandé
     sur capture : « tout sur une ligne »). Les cinq pastilles de statut, la
     recherche, les deux filtres et le tri partagent la ligne.
   ⚠ Les pastilles forment un GROUPE : sans lui, `flex-wrap` les casserait une à
     une entre la recherche et les listes, et « Actif · 6 » se retrouverait seul
     sur la ligne du dessous. Un groupe passe à la ligne entier ou pas du tout.
   ⚠ Il ne rétrécit PAS (`flex:0 0 auto`) : une pastille tronquée perd son
     compte, qui est tout ce qu'elle apporte. C'est la recherche qui absorbe. */
.barre-clients .cli-chips{ display:flex; align-items:center; gap:8px;
  flex:0 0 auto; flex-wrap:wrap; }
/* ⚠⚠ ET LES TROIS LISTES NE DÉBORDENT PAS : ELLES ENROULENT. Mesuré à 1 536 px
     de fenêtre, la ligne demande 1 340 px pour 1 260 disponibles — les pastilles
     480, la recherche 200 au minimum lisible, les trois listes 620. Sans cette
     règle le tri sortait de la carte, coupé net au bord droit : `min-width:auto`
     empêche un `<select>` de rétrécir sous son texte, donc la ligne DÉBORDAIT
     au lieu d'enrouler. **Septième fois qu'une règle de flexbox se paie ici**,
     et la seconde fois pour `min-width:auto` (10/09).
   ⭐ `min-width:0` leur rend le droit de céder, et `flex-basis` déclare la
      largeur qu'elles VEULENT : tant qu'il y a la place, elles l'ont ; quand il
      n'y en a plus, la barre passe proprement à deux lignes. Le navigateur
      tranche sur la largeur réelle — aucun seuil écrit à la main, donc rien à
      tenir à jour le jour où une commande s'ajoute. */
/* ⚠⚠ ET LA BARRE PREND LA LARGEUR DU HEADER, SANS QUOI ELLE NE PEUT PAS
     ENROULER — c'est la vraie cause, et elle a coûté trois essais.
     `.panel>header` est **lui-même un conteneur flex** (l. 506) : la barre y est
     un ITEM, sa largeur vaut donc son CONTENU (`flex-basis:auto`), et un
     conteneur large comme son contenu n'enroule jamais. Elle débordait, et
     `.panel{overflow:hidden}` la rognait net au bord droit : le tri était coupé
     en deux, sans une erreur ni une barre de défilement.
   ⚠ Tant qu'il y avait DEUX barres dans ce header, le défaut ne se voyait pas —
     chacune tenait. Il est né de la fusion.
   ⭐ `flex:1 1 100%` lui rend la largeur du header, `min-width:0` lui rend le
     droit de céder (`min-width:auto` est le plancher implicite d'un item flex,
     **deuxième fois** après le 10/09). Son propre `flex-wrap` peut alors
     jouer : une ligne quand il y a la place, deux quand il n'y en a plus, et
     c'est le navigateur qui tranche sur la largeur réelle. */
.barre-clients{ flex:1 1 100%; min-width:0; }
/* ⚠ Cette règle-ci n'est PAS ce qui débloquait l'enroulement — c'était
   `flex-wrap:nowrap`, plus bas, et je l'ai crue coupable pendant trois essais.
   Elle reste parce qu'elle est juste par elle-même : `.panel>header` est un
   conteneur flex (l. 506), une barre posée dedans n'aurait sinon que la largeur
   de son CONTENU, et `min-width:auto` est le plancher implicite d'un item flex
   (deuxième fois après le 10/09). Un commentaire qui affirme une cause fausse
   est pire qu'aucun commentaire : celui-ci dit ce qu'il fait, et ce qu'il ne
   fait pas. */

/* ⚠⚠ LA DATE PORTE L'URGENCE SUR L'ÉCRAN DES DEMANDES (27/09, maquette 14).
     Le dessin écrit en rouge la date des demandes qui dorment chez le client,
     et c'est ce qui REMPLACE la colonne « Prochaine action » retirée par la
     même maquette : l'information ne disparaît pas, elle change de porteur.
   ⚠ Une couleur encode l'urgence, jamais le type (16/08) — et ici c'en est
     bien une : la demande attend une réponse depuis soixante-sept jours. */
.dem-date-tard{ color:var(--poppy-dark); font-weight:600; }

/* ⚠⚠ PAS DE ZÉBRAGE SUR L'ÉCRAN DES DEMANDES (27/09, maquette 14). Le dessin
     montre des lignes blanches et TROIS lignes teintées — celles qui dorment
     chez le client. Deux couleurs de ligne sur le même tableau s'annulent : la
     parité en teinte une sur deux, l'attente une sur trois. C'est le constat
     du 26/09, et la règle globale de zébrage posée depuis l'a recouvert.
   ⚠ Borné à cet écran, et le banc de tableaux (`lib_banc_tableaux`) ne le
     couvre pas — il éprouve Sessions, Clients, Journal, Suivi et Facturation.
     Vérifié avant d'écrire cette ligne. */
#vue-demandes table.tbl tbody tr:nth-child(even){ background:transparent; }

/* ⚠ Le pied de la liste des demandes est à GAUCHE (maquette 14) : « 9 demandes
     · Page 1 sur 1 » y est une LÉGENDE du tableau, pas une commande. Le socle
     pousse `.pagination` à droite, ce qui est juste quand elle porte des
     boutons de page — ici elle n'en porte pas. Borné à cet écran. */
#vue-demandes .pagination{ justify-content:flex-start; }
/* ⚠ La note du bandeau de suivi : une phrase de réserve, donc sous le corps et
     en retrait — elle conditionne les chiffres du dessus sans leur disputer
     l'œil. Même raisonnement que les réserves du BPF, rendues AVANT les
     chiffres parce que là-bas elles conditionnent leur validité ; ici elles
     conditionnent un SEUIL, pas un total. */
.dem-suivre-note{ margin-top:10px; padding-top:9px; border-top:1px solid rgba(0,0,0,.07); }

/* ⚠⚠ LE ZÉBRAGE DES CLIENTS N'EST PAS COUPÉ, ET C'EST UNE QUESTION OUVERTE
     (27/09). La maquette 15 montre neuf lignes BLANCHES et une seule teintée —
     « MECS La Clairière », à relancer. Deux couleurs de ligne sur le même
     tableau s'annulent, et la coupe a été écrite… puis RETIRÉE :
     `lib_banc_tableaux.php` (dev A) éprouve l'alternance **sur cette vue
     précisément**, dans un vrai navigateur, et il a rougi.
   ⚠⚠ Deux exigences légitimes se contredisent : un banc de dev A qui garde une
     règle générale, et un dessin validé par le client qui la dénie sur cet
     écran. **Ce n'est pas à moi de trancher seul sur le banc de l'autre.** Le
     point part au registre et au point de coordination, avec la capture.
   ⚠ Demandes et Évaluations, elles, sont coupées : le banc ne les couvre pas —
     vérifié avant d'écrire chaque ligne. */

/* ⚠⚠ LA SECONDE BARRE DES CLIENTS N'ENROULE PAS AU-DESSUS DE 1000 px (27/09,
     maquette 15). Le dessin pose recherche + type + département + tri sur UNE
     ligne ; `.barre` du socle porte `flex-wrap:wrap`, et le tri tombait sur une
     TROISIÈME ligne de commandes avant la première donnée.
   ⚠ La cause est celle déjà payée quatre fois : **`flex-wrap` enroule AVANT de
     rétrécir** — le navigateur place chaque enfant à sa taille de base, puis
     enroule, puis seulement rétrécit ce qui reste sur la ligne. Déclarer
     `flex:1` sur la recherche ne suffisait donc pas.
   ⚠ Borné à cet écran et à cette largeur : en dessous, l'empilement reste
     juste, et `.barre` ne bouge nulle part ailleurs. */
/* ⚠⚠⚠ RETIRÉ LE 28/09 — ET C'ÉTAIT MA PROPRE RÈGLE QUI CASSAIT L'ÉCRAN.
     Les deux barres ont fusionné en une (« tout sur une ligne », demandé sur
     capture). `:last-of-type` désigne donc maintenant la barre UNIQUE, celle
     qui porte AUSSI les cinq pastilles : en `nowrap` elle ne pouvait plus
     enrouler, donc elle DÉBORDAIT, et `.panel{overflow:hidden}` la rognait net
     au bord droit — le tri coupé en deux, sans une erreur ni une barre de
     défilement.
   ⚠⚠ **UNE RÈGLE POSÉE POUR UN CAS QUI N'EXISTE PLUS NE DEVIENT PAS INERTE :
      elle change de cible.** Un sélecteur d'ORDRE (`:last-of-type`) suit la
      structure ; le jour où la structure bouge, il vise autre chose et fait
      exactement le contraire de ce pour quoi il a été écrit.
   ⚠ Et j'ai tenté QUATRE correctifs à l'aveugle avant de mesurer le DOM dans
     Chromium, qui a rendu `flex-wrap: nowrap` en une ligne. *Mesurer, pas
     deviner* — la règle du projet, repayée.
   ⭐ Sans `nowrap`, le navigateur tranche sur la largeur réelle : une ligne à
      1 920 px (celle de la capture), deux lignes propres quand la place manque.
      Aucun seuil écrit à la main, donc rien à tenir à jour. */
@media (min-width:1000px){
  #vue-clients .panel > header > .barre:last-of-type .input.auto{ flex:0 0 auto; }
  /* ⚠ `.pousse` pose `margin-left:auto` : en `nowrap`, cette marge PREND tout
       l'espace restant et le champ de recherche ne grandit plus. Le dessin lui
       donne ~570 px et range le tri juste après les deux filtres. On neutralise
       la poussée ICI — jamais dans le socle, où huit écrans s'en servent. */
  #vue-clients .panel > header > .barre:last-of-type .pousse{ margin-left:0; }
  /* ⚠⚠ UN `<select>` PREND LA LARGEUR DE SON PLUS LONG LIBELLÉ, et celui du
       tri en porte un de trente signes (« sessions (les plus nombreuses) ») :
       il réservait 300 px et la recherche se retrouvait à 220 là où le dessin
       lui en donne 570. La liste reste lisible — le libellé CHOISI est court —
       et ce qui dépasse se tronque à l'ouverture, pas à l'affichage. */
  #vue-clients #cli-tri{ max-width:190px; }
}

/* ⚠⚠ LE FILTRE « SUIVI » DES SESSIONS RÉSERVAIT 346 px POUR AFFICHER
     « Suivi : toutes » (30/09). Mesuré dans Chromium : c'est le plus large des
     onze éléments de la barre, devant la recherche (193) et les trois autres
     listes (180-195). La cause est celle déjà payée sur le tri des clients le
     27/09 : **un `<select>` prend la largeur de son plus long libellé**, et
     celui-ci en porte un de quarante-six signes — « — Sans suivi (ni session,
     ni lot, ni marché) — », dont chaque mot est nécessaire parce qu'il est dans
     la REQUÊTE.
   ⭐ On borne l'AFFICHAGE, pas le libellé : la liste reste entière et lisible à
      l'ouverture, et c'est le choix COURANT — toujours court — qui s'affiche.
      Gain mesuré : 166 px, sans retirer un mot à personne. */
#vue-sessions #ses-suivi{ max-width:180px; }
/* ⚠⚠ …SAUF QUAND « SANS SUIVI » EST LE CHOIX COURANT (30/09 au soir). Ce
     choix-là n'est PAS court : borné à 180 px, il s'affichait « — Sans suivi
     (ni sess » (303 px de texte pour 137 de place, flèche déduite, mesurés
     dans la liste), sans infobulle — le
     choix qui rend le filtre utile était le seul qu'on ne pouvait pas lire.
     La liste reprend alors sa largeur naturelle, celle de son plus long
     libellé DANS SA PROPRE POLICE : c'est vrai sur tout poste, quelle que
     soit la police qu'il a. Les autres choix gardent les 166 px gagnés.
   ⚠ `:has()` : sans lui, on retrouve le libellé coupé d'avant, rien de pire. */
#vue-sessions #ses-suivi:has(option[value="aucune"]:checked){ max-width:none; }
/* ⚠⚠ « du … au … » PASSE À LA LIGNE D'UN BLOC (30/09). Les quatre éléments
     étaient enfants directs du `<header>`, que `flex-wrap` casse où il veut :
     mesuré à 1 536 px, « au » restait sur la première ligne et SA DATE tombait
     sur la suivante. Un libellé séparé de son champ ne nomme plus rien.
   ⚠ `flex:0 0 auto` : le groupe ne rétrécit pas — un champ de date tronqué
     perd son sélecteur natif, qui est à droite. C'est la barre qui enroule.
   ⚠⚠ …ET IL PASSE À LA LIGNE EN DEUX MOITIÉS QUAND LE PANNEAU EST PLUS ÉTROIT
     QUE LUI (30/09 au soir). À 412 px (téléphone), le groupe insécable
     débordait : le champ « au » allait jusqu'à 448 px, le panneau s'arrête à
     399 — rogné par son `overflow:hidden`, sélecteur de date hors de vue. Le
     groupe ne dépasse plus le panneau (`max-width:100%`) et enroule ENTRE
     ses deux moitiés, « du + date » et « au + date », chacune insécable
     (`.ses-periode-b`) : le libellé ne quitte jamais son champ. */
#vue-sessions .ses-periode{ display:inline-flex; align-items:center; gap:10px;
  flex:0 0 auto; flex-wrap:wrap; max-width:100%; }
#vue-sessions .ses-periode-b{ display:inline-flex; align-items:center; gap:10px;
  flex:0 0 auto; }

/* ---------------------------------------------------------------------
   36. QUESTIONNAIRES — la liste, portée sur la maquette du 27/09 (image 16)
   ---------------------------------------------------------------------
   ⚠⚠ L'ÉCRAN COMPOSAIT QUATRE TABLEAUX, UN PAR TYPE, CHACUN AVEC SON EN-TÊTE.
     Chaque `<table>` calcule ses largeurs sur SON contenu : « Servi à » se
     retrouvait à quatre abscisses différentes sur la même page, et l'œil qui
     descend devait rechercher la colonne à chaque intertitre. Un seul tableau,
     des lignes de groupe. */
.q-bandeau{
  display:grid; grid-template-columns:auto 1fr auto; align-items:center; gap:14px;
  background:var(--info-bg); border:1px solid rgba(0,0,0,.06);
  border-radius:var(--r); padding:14px 16px; margin-bottom:14px;
}
.q-bandeau .ic{ color:var(--info); display:flex; }
.q-bandeau .txt .petit{ color:var(--muted); margin-top:2px; }
@media (max-width:640px){
  .q-bandeau{ grid-template-columns:auto 1fr; }
  .q-bandeau .btn{ grid-column:1 / -1; }
}

.q-onglets{ margin-bottom:14px; }
/* ⚠ `flex-wrap` enroule AVANT de rétrécir : sans `nowrap` au-dessus de 860 px,
     les deux listes tombent sous la recherche alors que la place existe.
     Cinquième fois que cette règle se paie sur ce projet. */
.q-barre{
  display:flex; flex-wrap:wrap; align-items:center; gap:10px;
  padding:12px 14px; border-bottom:1px solid var(--line);
}
@media (min-width:860px){ .q-barre{ flex-wrap:nowrap; } }
.q-rech{ flex:1 1 280px; min-width:0; }
.q-listes{ display:flex; gap:8px; flex:0 0 auto; }

/* ⚠⚠ LA LIGNE DE GROUPE N'EST PAS UN EN-TÊTE, c'est un INTERTITRE : pas de
     `<th>`, donc pas de `scope` — en poser un annoncerait une colonne qui
     n'existe pas, ce qui est pire que ne rien poser (contrôle 14). */
/* ⚠⚠ GRIS, PAS `--soft2` : ce jeton est un aplat COQUELICOT très pâle, pas un
     gris — mes lignes de groupe sortaient roses, et le dessin les fait grises.
     C'est le piège du 03/09, à l'identique (« mes week-ends étaient roses »).
     Le même gris que l'en-tête, parce que ces deux lignes ne portent pas de
     donnée : elles situent. */
.q-groupe > td{
  background:#faf9f9; font-weight:700; font-size:12.5px; color:var(--ink);
  padding:9px 12px; border-bottom:1px solid var(--line);
}
/* ⚠⚠ PAS DE ZÉBRAGE ICI, ET LA RAISON EST TECHNIQUE AUTANT QUE VISUELLE :
     `nth-child(even)` compte TOUS les `<tr>`, **intertitres compris**. Chaque
     ligne de groupe décale donc l'alternance d'un cran, et le motif saute à
     chaque section — une alternance qui ne tient pas n'aide plus à suivre une
     ligne, elle fait douter de ce qu'on lit. Le dessin montre des lignes
     blanches et laisse les intertitres porter seuls le rythme.
   ⚠ `lib_banc_tableaux` ne couvre pas cette vue — vérifié avant d'écrire. */
#vue-questionnaires table.tbl tbody tr:nth-child(even){ background:transparent; }
.q-groupe .n{ color:var(--muted); font-weight:600; }

.q-titre{ display:inline-flex; align-items:baseline; gap:7px; font-weight:600; }
.q-titre .ic{ color:var(--muted); align-self:center; flex:0 0 auto; }
.q-origine{ margin-top:2px; padding-left:22px; }

/* ⚠ LE POINT DE LA PASTILLE N'EST PAS DÉCORATIF : c'est la seule marque qui
     survit quand la couleur ne passe pas — impression, daltonisme, contraste
     élevé. Le mot la double de toute façon : la couleur ne porte jamais seule. */
.q-etat{ display:inline-flex; align-items:center; gap:6px; }
.q-etat .pt{ width:7px; height:7px; border-radius:50%; background:currentColor; flex:0 0 auto; }

/* ---------------------------------------------------------------------
   37. FICHES PRATIQUES — la liste, portée sur la maquette du 27/09 (image 16)
   ---------------------------------------------------------------------
   ⚠⚠ LA LIGNE N'AVAIT AUCUNE ACTION NOMMÉE : elle était `cliquable` et rien ne
     le disait. Le seul moyen d'ouvrir une fiche était de deviner qu'on pouvait
     cliquer dessus — ce qui n'existe ni au doigt, ni au clavier, ni pour un
     lecteur d'écran. Le dessin pose « Ouvrir » et un « ⋮ ». */
.pf-barre{ padding:12px 14px 0; }
.pf-barre .actions{ display:flex; flex-wrap:wrap; gap:8px; }
.pf-rech{ padding:12px 14px; }
.pf-rech .champ-recherche{ display:block; width:100%; }
.pf-titre{ display:inline-flex; align-items:center; gap:8px; }
.pf-titre .ic{ color:var(--muted); flex:0 0 auto; }
.pf-pied{ padding:10px 14px; justify-content:flex-start; }

/* ⚠⚠ LES CELLULES SONT SÉPARÉES PAR UN FILET VERTICAL (maquette 16) — c'est
     ce qui remplace le zébrage sur cet écran : trois lignes seulement, où
     l'alternance ne sert à rien, mais cinq colonnes étroites qu'il faut
     distinguer. Le filet ne porte AUCUN sens (ni urgence, ni type) : il borne.
   ⚠ Pas sur la dernière cellule — un filet au bord du tableau dessinerait un
     cadre que le dessin n'a pas. */
#vue-poppy-fiches table.tbl tbody td + td{ border-left:1px solid var(--line); }
#vue-poppy-fiches table.tbl tbody td:last-child{ border-left:0; }
/* ⚠ Pas de zébrage ici : trois lignes, et le filet vertical porte déjà le
     rythme. `lib_banc_tableaux` ne couvre pas cette vue — vérifié. */
#vue-poppy-fiches table.tbl tbody tr:nth-child(even){ background:transparent; }

/* ⚠⚠ LES LIGNES SONT DÉTACHÉES (maquette 16) : coins arrondis, un filet
     autour, et dix pixels d'air entre elles. Trois fiches seulement — une
     alternance n'aurait rien à rythmer — mais cinq colonnes étroites qu'il
     faut borner. `border-collapse:separate` est ce qui rend `border-spacing`
     effectif ; avec `collapse`, la propriété est simplement IGNORÉE, sans
     erreur ni avertissement.
   ⚠ Les arrondis se posent sur la PREMIÈRE et la DERNIÈRE cellule, jamais sur
     le `<tr>` : un `border-radius` sur une ligne de tableau n'a aucun effet. */
#vue-poppy-fiches table.tbl{ border-collapse:separate; border-spacing:0 10px; }
#vue-poppy-fiches table.tbl tbody td{
  border-top:1px solid var(--line); border-bottom:1px solid var(--line);
  background:var(--carte);
}
#vue-poppy-fiches table.tbl tbody td:first-child{
  border-left:1px solid var(--line);
  border-radius:var(--r-s) 0 0 var(--r-s);
}
#vue-poppy-fiches table.tbl tbody td:last-child{
  border-right:1px solid var(--line);
  border-radius:0 var(--r-s) var(--r-s) 0;
}
/* ⚠ « Ouvrir » est BORDÉ de rouge, pas plein : c'est l'action principale de la
     ligne, mais trois boutons pleins feraient trois aplats rouges sur un écran
     où rien n'est anormal — *une couleur encode l'urgence, jamais le type*. */
.pf-ouvrir{ color:var(--poppy); border-color:var(--poppy); display:inline-flex;
            align-items:center; gap:6px; }
.pf-ouvrir:hover{ background:var(--soft); }

/* ---------------------------------------------------------------------
   38. QUALIOPI — les indicateurs, portés sur la maquette du 27/09
   ---------------------------------------------------------------------
   ⚠⚠ L'ÉCRAN POSAIT HUIT TUILES À ÉGALITÉ, dont deux seules sur une deuxième
     ligne. Les quatre qui décident d'un audit s'y noyaient au milieu des
     compteurs du registre, qui comptent une ACTIVITÉ et non un MANQUE.
     Régression du 12/09 : *dix améliorations successives font une régression
     si rien ne sort* — et chacune avait été ajoutée pour une bonne raison.
   ⚠⚠ L'ICÔNE EST À DROITE, DANS UN ROND. C'est une TROISIÈME forme de tuile
     dans l'application : Demandes et Planning la posent à gauche dans un
     carré, Évaluations à gauche avec le chiffre au-dessus. Les trois sont des
     dessins de Julien, et je ne tranche pas à sa place — chaque écran suit SA
     maquette, la divergence part au point de coordination. */
#vue-qualiopi .q-kpis{ grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); }
.q-kpi{ display:flex; align-items:flex-start; justify-content:space-between; gap:12px; }
.q-kpi .txt{ min-width:0; }
.q-kpi .rond{
  flex:0 0 auto; width:46px; height:46px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  background:var(--soft); color:var(--poppy-dark);
}
.q-kpi.ko .rond{ background:var(--ko-bg); color:var(--ko); }
.q-kpi.att .rond{ background:var(--att-bg); color:var(--att); }
.q-kpi.ok .rond{ background:var(--ok-bg); color:var(--ok); }

/* ⚠⚠ LA BANDE DU REGISTRE N'EST PAS UNE RANGÉE DE TUILES, et c'est tout le
     point : ces trois nombres comptent ce qu'on a CONSIGNÉ, pas ce qui manque.
     Leur donner la forme des quatre du haut les faisait lire comme des
     alertes — « 2 réclamations » à côté de « 81 pièces en retard », même
     carte, même taille. Ici elles annoncent la section qu'elles ouvrent. */
.q-bande{
  display:flex; flex-wrap:wrap; align-items:center; gap:0;
  background:var(--carte); border:1px solid var(--line); border-radius:var(--r);
  margin-bottom:14px; overflow:hidden;
}
.q-bande-item{
  flex:1 1 220px; display:flex; align-items:center; gap:10px;
  padding:14px 16px; font-size:13px; color:var(--muted); min-width:0;
}
.q-bande-item + .q-bande-item{ border-left:1px solid var(--line); }
.q-bande-item b{ color:var(--ink); font-size:15px; }
.q-bande-item .ic{ display:flex; flex:0 0 auto; }
.q-bande-item .ic.t-ko{ color:var(--ko); }
.q-bande-item .ic.t-att{ color:var(--att); }
.q-bande-item .ic.t-neutre{ color:var(--muted); }
/* ⚠⚠ LA BARRE DU TABLEAU VIT DANS LA CARTE (27/09, maquette Qualiopi), plus
     dans l'en-tête de page : elle filtre CE tableau et rien d'autre. Posée
     à côté du titre, elle lui disputait la ligne — sept commandes pour
     1 264 px, ce qui avait coûté trois réglages de largeur le 26/09.
   ⚠ `flex-wrap` enroule AVANT de rétrécir : `nowrap` au-dessus de 1100 px,
     sinon les exports tombent seuls sous la recherche. Sixième fois. */
.q-barre{
  display:flex; flex-wrap:wrap; align-items:center; gap:10px;
  padding:12px 14px; border-bottom:1px solid var(--line);
}
@media (min-width:1100px){ .q-barre{ flex-wrap:nowrap; } }
.q-barre .q-rech{ flex:1 1 220px; min-width:0; }
.q-chips{ display:flex; flex-wrap:wrap; gap:7px; flex:0 0 auto; }
.q-exports{ display:flex; gap:8px; flex:0 0 auto; }
/* ⚠ Le sous-titre passe SOUS le titre (maquette) : `.q-titre` le posait à
     côté, où il s'étalait sur trois lignes. */
#vue-qualiopi .q-titre{ display:block; }
/* ⚠⚠ ET LE ROND NE S'ENROULE PAS SOUS LE TEXTE. `.q-kpi` est un conteneur
     flex ; sans `nowrap`, à 240 px de large, le texte réclame sa base et le
     rond tombe à la ligne — il se retrouvait EN BAS À GAUCHE au lieu d'en haut
     à droite. **Septième fois que `flex-wrap` enroule avant de rétrécir.**
   ⚠ Et le texte doit pouvoir rétrécir pour que le rond tienne : `min-width:0`
     sur un enfant de flex, sinon sa taille intrinsèque fait déborder. */
/* ⚠⚠ `flex-direction:row` EXPLICITEMENT : `.kpi` du socle porte
     `flex-direction:column` — et il le porte DEUX FOIS (l. 656 et l. 5490,
     duplication à signaler). Ma tuile était donc une colonne, et le rond
     tombait SOUS le texte, en bas à gauche, au lieu d'être en haut à droite.
     `justify-content:space-between` ne pouvait rien y faire : sur une colonne,
     il répartit en HAUTEUR.
   ⚠ Et le texte doit pouvoir rétrécir pour que le rond tienne : `min-width:0`
     sur un enfant de flex, sinon sa taille intrinsèque fait déborder. */
.q-kpi{ flex-direction:row; flex-wrap:nowrap; }
.q-kpi .txt{ flex:1 1 auto; min-width:0; }

/* ---------------------------------------------------------------------
   39. ÉVALUATIONS À FROID — porté sur la maquette du 27/09
   ---------------------------------------------------------------------
   ⚠ Le bandeau reprend la forme de celui des questionnaires : même nature
     (une phrase qui conditionne tout l'écran), donc même dessin. Deux
     bandeaux voisins qui ne se ressemblent pas font douter du reste. */
.ef-bandeau{ grid-template-columns:auto 1fr; }
.ef-barre{ padding:12px 14px; border-bottom:1px solid var(--line); }
.ef-barre .actions{ display:flex; flex-wrap:wrap; gap:7px; }

/* ⚠⚠ LA BARRE NE REMPLACE PAS LES NOMBRES, ELLE LES DOUBLE. « 2 / 6 » se lit,
     mais l'avancement de dix lignes ne se COMPARE pas sans dessin ; une barre
     seule, elle, ne dit ni combien ont répondu ni sur combien — et à zéro
     inscrit elle serait un trait vide qui ment. */
.ef-rep{ min-width:110px; }
.ef-rep .n{ font-variant-numeric:tabular-nums; font-weight:600; }
.ef-rep .prog{
  margin-top:5px; height:6px; border-radius:3px; background:var(--soft2);
  overflow:hidden;
}
.ef-rep .prog i{ display:block; height:100%; background:var(--ok); }

/* ⚠ L'aide vit EN BAS, après la liste : qui connaît le geste ne la relit pas.
     *Ce qui est toujours là cesse d'être lu* — mais deux lignes en pied de
     page ne disputent rien à la donnée. */
.ef-aide{ margin-top:14px; }
.ef-aide .tete{ display:flex; align-items:center; gap:9px; margin-bottom:8px; }
.ef-aide .tete .ic{ color:var(--info); display:flex; }
.ef-aide ol{ margin:0; padding-left:22px; color:var(--muted); font-size:13px; }
.ef-aide li + li{ margin-top:4px; }

/* ---------------------------------------------------------------------
   40. QUIZ — le bandeau de la porte, porté sur la maquette du 27/09
   ---------------------------------------------------------------------
   ⚠⚠ IL NOMMAIT LE CHEMIN, IL N'Y MENAIT PAS. « Paramètres → POPPY » écrit en
     toutes lettres oblige à quitter l'écran, ouvrir les paramètres et trouver
     l'onglet — trois gestes pour une alerte qui dit précisément qu'on ne peut
     rien faire d'ici. Même leçon que le bandeau des demandes du 26/09.
   ⚠ Rouge, et c'est justifié : tant que l'entrée est masquée, publier un quiz
     ne le rend visible de personne. Ce n'est pas un type, c'est une urgence —
     la distinction du 16/08.
   ⚠⚠ SES RÈGLES SONT RETIRÉES (30/09 au soir, relecture contradictoire, M8).
     Depuis 17f419a, les quatre écrans de POPPY rendent la porte par le
     composeur du socle (`CC.htmlBandeau`, règles `.cc-bandeau-*`) :
     `.pq-porte`, ses `.ic`, `.txt`, `.petit` et `.pq-porte-btn` ne
     peignaient plus rien. Le numéro de section reste, pour les renvois. */

/* ---------------------------------------------------------------------
   41. STATISTIQUES — le bandeau des lieux manquants (28/09, maquette)
   ---------------------------------------------------------------------
   ⚠⚠ SON LIEN ÉTAIT UNE QUATRIÈME LIGNE DE PHRASE. Il tombait sous trois
     lignes de texte, à la même graisse, et rien ne le distinguait d'une
     incise. Le dessin en fait un bouton bordé, à droite — *un bandeau qui
     compte doit offrir d'y aller* (26/09, demandes ; 28/09, quiz).
   ⚠ Ambre et non rouge : quinze adresses manquantes gênent la carte, elles
     n'empêchent rien. La couleur encode l'urgence, jamais le type. */
.st-bandeau{
  display:grid; grid-template-columns:auto 1fr auto; align-items:center; gap:14px;
  background:var(--att-bg); border:1px solid #f0d9a8;
  border-radius:var(--r); padding:14px 16px; margin-bottom:14px;
}
.st-bandeau .ic{ color:var(--att); display:flex; }
.st-bandeau .txt{ color:var(--muted); font-size:13px; }
.st-bandeau .txt strong{ color:var(--ink); }
.st-bandeau-btn{ color:var(--att); border-color:var(--att); white-space:nowrap; }
@media (max-width:760px){
  .st-bandeau{ grid-template-columns:auto 1fr; }
  .st-bandeau .btn{ grid-column:1 / -1; }
}

/* ---------------------------------------------------------------------
   42. MODÉRATION — le bandeau d'incohérence (28/09, maquette)
   ---------------------------------------------------------------------
   ⚠⚠ L'ÉCRAN SE CONTREDISAIT, ET RIEN NE LE DISAIT : « À lire 2 » au-dessus de
     « Rien à lire pour le moment ». Deux sources — un `COUNT(*)` d'un côté,
     une requête JOINTE de l'autre — et deux résultats. La cause est corrigée
     (`LEFT JOIN`), le filet reste : c'est une CLASSE de défaut, pas un
     incident. Ce projet a payé sept fois pour deux calculs de la même chose.
   ⚠ Il ne paraît QUE si les deux divergent : un bandeau permanent cesse d'être
     lu, et apprend à ne pas lire ce qui est à côté (11/09). */
.pm-anomalie{
  display:grid; grid-template-columns:auto 1fr auto; align-items:center; gap:14px;
  background:var(--att-bg); border:1px solid #f0d9a8;
  border-radius:var(--r); padding:14px 16px; margin-bottom:14px;
}
.pm-anomalie .ic{ color:var(--att); display:flex; }
.pm-anomalie .txt .petit{ color:var(--muted); margin-top:2px; }
.pm-anomalie-btn{ color:var(--att); border-color:var(--att); white-space:nowrap; }
@media (max-width:760px){
  .pm-anomalie{ grid-template-columns:auto 1fr; }
  .pm-anomalie .btn{ grid-column:1 / -1; }
}

/* ---------------------------------------------------------------------
   43. MARCHÉS — deux chiffres en tête, cinq compteurs dans une bande (28/09)
   ---------------------------------------------------------------------
   ⚠⚠ L'ÉCRAN POSAIT SEPT TUILES À ÉGALITÉ, DONT CINQ À ZÉRO. Les deux qui
     disent l'activité — combien de marchés tournent, combien ils rapportent —
     s'y noyaient au milieu de compteurs de VEILLE, qui disent « rien à faire »
     neuf jours sur dix. Même geste que Qualiopi la veille, même régression du
     12/09 : *dix améliorations successives font une régression si rien ne
     sort.*
   ⚠⚠ ET LES TROIS COMPTEURS QUI FILTRENT LA LISTE LE FONT TOUJOURS. Rendus
     cliquables à la recette du 26/09 (B6, G22, G23) parce qu'une alerte qu'on
     ne voit qu'en ouvrant le marché ne prévient de rien. On ne supprime pas
     une fonction que le dessin ignore — on la range. */
.mk-tete{
  display:flex; flex-wrap:wrap; align-items:center;
  background:var(--carte); border:1px solid var(--line); border-radius:var(--r);
  box-shadow:var(--ombre); margin-bottom:12px; overflow:hidden;
}
.mk-grand{ flex:1 1 280px; display:flex; align-items:center; gap:16px; padding:20px 22px; min-width:0; }
.mk-grand + .mk-grand{ border-left:1px solid var(--line); }
.mk-grand .rond{
  flex:0 0 auto; width:56px; height:56px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
}
.mk-grand .rond.t-sessions{ background:var(--soft);    color:var(--poppy-dark); }
.mk-grand .rond.t-ok{       background:var(--ok-bg);   color:var(--ok); }
.mk-grand .rond.t-neutre{   background:var(--neutre-bg); color:var(--neutre); }
.mk-grand .n{ font-size:30px; font-weight:800; line-height:1.05; color:var(--ink); }
.mk-grand .l{ font-size:13.5px; color:var(--muted); margin-top:3px; }

/* ⚠ La bande n'est pas une rangée de tuiles : ces cinq nombres disent ce qu'il
     reste à surveiller, pas ce que l'organisme fait. À zéro ils s'effacent —
     et ils ne s'effacent PAS quand leur filtre est actif, sinon on perdrait le
     seul endroit qui le retire. */
.mk-bande{
  display:flex; flex-wrap:wrap; align-items:stretch; gap:0;
  background:var(--carte); border:1px solid var(--line); border-radius:var(--r);
  margin-bottom:14px; overflow:hidden;
}
.mk-compteur{
  flex:1 1 170px; display:flex; align-items:center; justify-content:center; gap:7px;
  padding:14px 12px; font-size:13px; color:var(--muted); min-width:0;
  background:transparent; border:0; font-family:inherit; text-align:center;
}
.mk-compteur + .mk-compteur{ border-left:1px solid var(--line); }
.mk-compteur b{ color:var(--ink); font-size:19px; font-weight:800; }
.mk-compteur.t-ko b{ color:var(--ko); }
.mk-compteur.t-att b{ color:var(--att); }
button.mk-compteur{ cursor:pointer; }
button.mk-compteur:hover{ background:var(--soft2); }
.mk-compteur.actif{ background:var(--soft); }
.mk-compteur.actif b{ color:var(--poppy-dark); }
/* ⚠ La réserve de la marge : un bandeau d'INFORMATION, pas une alerte — elle
     dit ce que les deux chiffres du haut ne disent pas. Bleu, donc, et non
     ambre : la couleur encode l'urgence, jamais le type (16/08). */
.mk-reserve{
  display:grid; grid-template-columns:auto 1fr; align-items:start; gap:12px;
  background:var(--info-bg); border:1px solid rgba(0,0,0,.06);
  border-radius:var(--r); padding:13px 16px; margin-top:14px;
}
.mk-reserve .ic{ color:var(--info); display:flex; margin-top:1px; }
.mk-reserve p{ margin:0; font-size:12.5px; color:var(--muted); line-height:1.5; }
/* ⚠ Le sous-titre d'un grand chiffre : l'assiette de la mesure (§22). Le dessin
     ne la montre pas ; l'information ne se perd pas pour autant. */
.mk-grand .sous{ font-size:11.5px; color:var(--muted); margin-top:3px; }
.mk-compteur .sous{ font-style:normal; opacity:.8; }
.mk-compteur .sous{ margin-left:5px; }
/* ⚠⚠ L'ASSIETTE D'UN COMPTEUR PASSE SOUS SON LIBELLÉ, elle ne se glisse pas à
     côté : « · sur 3 session(s) comparée(s) au bordereau » à la suite de
     « écart(s) de tarif » faisait éclater la case en trois lignes et poussait
     les quatre autres compteurs hors de leur ligne. Elle reste DITE — le §22
     l'impose —, en petit et sous le libellé.
   ⚠ Et le compteur s'empile en colonne : le nombre, puis le libellé, puis
     l'assiette. Sur une bande de cinq, c'est la seule disposition qui tienne. */
.mk-compteur{ flex-direction:column; gap:2px; text-align:center; }
.mk-compteur .sous{
  margin-left:0; display:block; font-size:11px; line-height:1.3;
  max-width:22ch; color:var(--muted);
}

/* ⚠⚠ LES TROIS ACTIONS DU PLANNING VIVENT DANS LA CARTE (28/09, demandé par
     Julien sur capture annotée). Elles flottaient au-dessus, à droite du
     titre, détachées de la carte qui porte tout le reste des commandes de cet
     écran : on cherchait « Exporter » en haut alors que « Période » et
     « Durée » sont en bas. Même geste que Qualiopi, Clients et Fiches
     pratiques les 27 et 28/09.
   ⚠ Poussées à droite ICI et non par `.pousse` : le socle ne définit celle-ci
     que sous `.barre` (l. 156), et posée ailleurs elle ne pousse rien — une
     règle qui ne peint pas, sans erreur ni avertissement.
   ⚠ `margin-left:auto` cède quand la barre enroule : sur une fenêtre étroite
     le groupe passe à la ligne et reprend le bord gauche, ce qui est juste. */
/* ⚠⚠ PAS DE `margin-left:auto` ICI, ET C'EST MESURÉ (28/09). Poussée à droite,
   la poignée des actions s'enroulait sur une SECONDE LIGNE en laissant le tiers
   gauche vide — un blanc collé à gauche se lit comme un trou, pas comme une
   marge (leçon du 12/09 sur la carte du quiz : « on ne centre pas pour
   supprimer un blanc, on rétrécit le contenant »). Laissée à sa place
   naturelle, elle suit la durée, et quand la ligne est pleine elle repart au
   BORD GAUCHE — là où l'œil revient — en laissant le blanc en fin de ligne. */
.gantt-outils-actions .gantt-outils-champs{ display:flex; flex-wrap:wrap; gap:8px; }

/* ═══════════════════════════════════════════════════════════════════════════
   RÉPÉTITION DU 29/09 — `hidden` GAGNE PARTOUT, ET LE PIED D'UNE FENÊTRE
   ENROULE (lot rab-sessions-clients-c, dev A ; zone commune, signalé à dev B)
   ═══════════════════════════════════════════════════════════════════════════ */
/* ⚠⚠ L'ATTRIBUT `hidden` L'EMPORTE SUR TOUTE RÈGLE D'AUTEUR. Le `[hidden]{
     display:none}` du navigateur perd contre n'importe quelle classe qui pose
     un `display` : la pastille « Client : … ✕ » de l'écran Sessions (`.chip`,
     `display:inline-flex`) restait affichée après le clic sur ✕, et même sans
     filtre (« Client : fiche n° ✕ »), alors que sessions.js la masque
     (`b.hidden = !etat.client_id`) — mesuré dans Chromium, répétition du 29/09.
     « Toute la période », sa voisine, avait le même défaut.
   ⚠ CINQUIÈME fois que ce piège se paie : `.alerte[hidden]`, `.espace-nav[hidden]`
     (28/09), `.dem-filtres-2[hidden]` et `.dem-filtres-2 .f[hidden]` l'ont
     réparé écran par écran. La règle générale ferme la famille ; celles-là
     restent, inoffensives.
   ⚠ Vérifié : aucun écran ne s'appuyait sur l'inverse (un élément `hidden`
     rendu visible exprès) — relevé des gabarits qui posent `hidden` dans
     public/views et public/assets, et de ce qui porte `hidden` sur les écrans
     que monte le banc de tools/tests_rab_sessions_clients_c.php (seules les
     deux pastilles l'étaient). C'est la règle du « reboot » de Bootstrap,
     pour la même raison. */
[hidden]{ display:none !important; }

/* ⚠⚠ LE PIED D'UNE FENÊTRE PASSE À LA LIGNE AU LIEU DE DÉBORDER. `.modal>.pied`
     est un flex SANS `flex-wrap`, aligné à droite, et `.btn` ne se coupe pas
     (`white-space:nowrap`) : quand les boutons ne tiennent pas, ils sortent par
     la GAUCHE, et `.modal{overflow:hidden}` les rogne. Le menu « ⋯ » d'un
     client (fenêtre étroite, 400 px, quatre boutons) montrait « pièces » pour
     « Dossier de pièces », rien d'« Activité et chiffres », et un clic au
     centre de ce bouton tombait sur le FOND — qui ferme la fenêtre (répétition
     du 29/09, à 1 440 et 1 366 px).
   ⚠ Sans effet quand tout tient : `flex-wrap` n'agit que sur ce qui déborde, et
     un `.btn` ne rétrécit pas sous son texte — aucun pied qui tient aujourd'hui
     ne change d'un pixel. `gap` vaut aussi entre les lignes. */
.modal>.pied{ flex-wrap:wrap; }

/* --- dev A : l'espace du stagiaire tient dans un téléphone (29/09) ----------
   ⚠⚠ UN TITRE LONG FAISAIT DÉBORDER TOUTE LA PAGE (répétition de la recette du
      29/09, téléphone à 412 px : page de 472 px, puis de 1 184 px avec un titre
      de quiz de 130 signes). Les entrées des sous-listes de l'espace — fiches,
      quiz, défis, « Vérifier que j'ai compris » — et les liens de « Mes
      documents » portent `.btn`, qui interdit le retour à la ligne
      (`white-space:nowrap`, section 7) : le titre restait sur une ligne, « 1
      question » partait hors de l'écran et la page défilait de côté.
   ⚠ Le retour à la ligne est rendu à CES DEUX FAMILLES seulement : `.btn` garde
     sa règle partout ailleurs. `anywhere` coupe aussi un mot plus long que la
     carte (un sigle collé, une adresse) au lieu de le laisser pousser.
   ⚠ Ajout en fin de fichier, annoncé au point de coordination (CONVENTIONS). */
.espace-entree, .espace-docs a{ white-space:normal; overflow-wrap:anywhere; }
/* ⚠ Et dans ces sous-listes (`.btn.espace-entree`, qui n'ont pas le conteneur
     `.c` des entrées du menu), le titre et sa ligne d'aide s'EMPILENT, comme au
     menu. Côte à côte, c'est l'aide qui cédait la place au titre : « 1
     question » se serrait en une colonne de deux lettres (mesuré à 412 px). */
.btn.espace-entree{ flex-direction:column; align-items:stretch; gap:0; }

/* ─── 44. DÉPÔTS CHORUS PRO — l'en-tête et la consigne (30/09, maquette) ────
   ⚠⚠ LA CONSIGNE DE DÉPÔT SORT DU SOUS-TITRE. Elle y tenait sur deux lignes
     serrées qui mêlaient ce que l'écran MONTRE et ce qu'il faut FAIRE ; le
     dessin en fait un encadré, avec trois étapes numérotées. Une consigne en
     trois temps se lit en trois temps.
   ⚠ Le fond est `--info-bg` et le titre `--info` (le ton `info` du socle),
     jamais une alerte : rien n'est anormal sur cet écran, c'est la marche
     normale. Une couleur encode l'urgence, jamais le type (16/08).
     ⚠ 01/10 (relecture du soir, M5) : ce commentaire disait déjà « le ton
       info », mais les règles peignaient `--soft` et `--poppy-dark` — la
       palette de `.cc-bandeau-alerte` —, pendant que le bandeau des blocages,
       juste dessous, sortait bleu : l'urgence se lisait à l'envers. Mêmes
       jetons que `.pm-anonymat`. */
.page-sur{
  font-size:11px; font-weight:700; letter-spacing:.09em; text-transform:uppercase;
  color:var(--muted); margin-bottom:2px;
}
.ch-consigne{ background:var(--info-bg); border-color:var(--info-bg); }
.ch-consigne > .corps{
  display:flex; align-items:center; gap:26px; flex-wrap:wrap;
}
.ch-consigne-dit{ display:flex; align-items:flex-start; gap:14px; flex:1 1 420px; min-width:0; }
.ch-consigne-ic{
  display:inline-flex; align-items:center; justify-content:center;
  width:44px; height:44px; flex:0 0 44px; border-radius:12px;
  background:var(--carte); color:var(--info);
}
.ch-consigne-t{ font-weight:700; color:var(--info); margin-bottom:2px; }
.ch-consigne-m{ font-size:13px; color:var(--ink); }
/* ⚠ Les trois étapes ne rétrécissent pas sous leur texte (`min-width:0` sur
     chacune, pas sur la liste) : un libellé coupé en deux ferait lire « Relever
     la date dans » sans son objet. En dessous de la place, elles ENROULENT. */
.ch-etapes{
  list-style:none; margin:0; padding:0;
  display:flex; align-items:center; gap:10px; flex-wrap:wrap; flex:0 1 auto;
}
.ch-etape{ display:flex; align-items:center; gap:9px; min-width:0; }
/* ⚠ Le chevron est posé par le CSS, pas écrit dans la vue : c'est une
     séparation visuelle, elle n'a rien à dire à un lecteur d'écran — qui
     annonce déjà « 1 sur 3 ». Et il ne suit pas la dernière étape. */
.ch-etape + .ch-etape::before{
  content:'›'; color:var(--muted); font-size:18px; line-height:1; margin-right:1px;
}
.ch-etape-n{
  display:inline-flex; align-items:center; justify-content:center;
  width:26px; height:26px; flex:0 0 26px; border-radius:50%;
  background:var(--carte); color:var(--info);
  font-size:12.5px; font-weight:700;
}
.ch-etape-t{ font-size:13px; line-height:1.25; }
/* ⚠ Le titre de la carte est un `h2` du socle, ramené à la taille d'un titre de
     section : `.panel > .corps` n'en stylise aucun, et sans règle il sortait à
     la taille par défaut du navigateur — deux fois trop gros. */
.ch-titre-carte{ font-size:16px; margin:0 0 12px; color:var(--ink); }

/* ─── 45. ANALYSES DE PRATIQUES — la modération (30/09, maquette) ───────────
   ⚠⚠ LES CONTRIBUTIONS PORTAIENT `.carte`, QUI NE PEINT RIEN ICI : elle n'est
     déclarée que sous `.kanban .carte`, donc à l'intérieur d'un tableau kanban
     qui n'existe pas sur cet écran. Elles flottaient sans fond ni bord, et
     `audit_vues` la comptait existante — son relevé prend tout ce qui ressemble
     à un nom de classe, contexte compris, et son encadré assume ce choix.
     *Une classe déclarée sous un ancêtre est une classe inventée partout
     ailleurs.* Elles emploient désormais `.panel`, la carte du socle. */
.pm-contrib{ margin-bottom:12px; }
.pm-contrib > .corps > header{ margin-bottom:8px; }
.pm-contrib > .corps > p{ margin:0 0 12px; }
/* ⚠ Le rappel d'anonymat prend la facture de la consigne des dépôts Chorus Pro,
     livrée le même jour : deux écrans qui posent la même chose de deux façons
     font chercher la différence. */
.pm-anonymat{ background:var(--info-bg); border-color:var(--info-bg); }
.pm-anonymat > .corps{ display:flex; align-items:flex-start; gap:14px; }
.pm-anonymat-ic{
  display:inline-flex; align-items:center; justify-content:center;
  width:40px; height:40px; flex:0 0 40px; border-radius:50%;
  background:var(--carte); color:var(--info);
}
.pm-anonymat-t{ font-weight:700; color:var(--info); margin-bottom:2px; }
.pm-anonymat-m{ font-size:13px; color:var(--ink); }
.pm-titre-carte{ font-size:16px; margin:0 0 12px; color:var(--ink); }

/* ⚠ L'icône de « Questions difficiles » (30/09) : alignée sur le texte, et
     posée par la vue avec un nom du catalogue — jamais un caractère.
   ⚠ (30/09 au soir, M8) `.pq-ratees > h3` est retirée : le titre n'est plus
     un enfant direct du bloc depuis 80e6a18 (il vit dans `.pq-ratees-tete`). */
.pq-ratees-ic{ display:inline-flex; vertical-align:-3px; margin-right:7px; color:var(--poppy-dark); }

/* ─── 46. QUIZ — la liste sur son dessin (30/09) ───────────────────────────
   ⚠ Le séparateur du sous-titre : les deux phrases ne disent pas la même chose
     — à quoi l'écran sert, et ce qu'il ne garde pas. Collées, on lisait une
     seule phrase et l'anonymat passait pour une précision de la première. */
.pq-sep{
  display:inline-block; width:1px; height:12px; margin:0 12px; vertical-align:-1px;
  background:var(--line);
}
/* ⚠ La tête du bloc : l'icône à gauche, le titre et sa phrase à droite. */
.pq-ratees-tete{ display:flex; align-items:flex-start; gap:14px; margin-bottom:14px; }
.pq-ratees-tete > div{ min-width:0; }
.pq-ratees-tete h3{ margin:0 0 2px; font-size:17px; }
.pq-ratees-tete p{ margin:0; }
.pq-ratees-ic{ display:inline-flex; flex:0 0 auto; color:var(--poppy); }
/* ⚠ L'encadré de l'état vide : il DIT pourquoi il n'y a rien, avec le seuil.
     Masquer la section ferait croire que les quiz ne comptent rien (11/09). */
/* ⚠⚠ `--fond` ET NON `--soft2` : ce dernier est un aplat COQUELICOT très clair
     (#fdf6f5), pas un gris — la leçon des week-ends roses de la frise (03/09).
     L'encadré serait rosé sur un écran où rien n'est anormal, à côté d'un
     bandeau qui, lui, est légitimement rouge : deux roses voisins dont un seul
     alerte ne signalent plus rien. Le dessin le montre gris, et il a raison. */
.pq-ratees-note{
  display:flex; align-items:flex-start; gap:12px;
  padding:14px 16px; border-radius:10px; background:var(--fond);
}
.pq-ratees-note-ic{ display:inline-flex; flex:0 0 auto; color:var(--muted); }
.pq-ratees-note-t{ font-weight:700; margin-bottom:2px; }
.pq-ratees-note-m{ font-size:13px; color:var(--muted); }

/* ⚠ L'icône d'un bouton de retrait (30/09, maquette) : collée au libellé, et
     de sa couleur. Elle est `aria-hidden` — le bouton porte déjà son nom. */
.pm-ic-btn{ display:inline-flex; vertical-align:-3px; margin-right:6px; }
/* ⚠⚠ L'icône d'anonymat est en ROND PLEIN (maquette) : sur un fond bleu très
     clair, un dessin bleu sur blanc ne se détachait pas — c'est le seul objet
     coloré d'un bandeau qui, lui, doit se remarquer une fois. */
.pm-anonymat-ic{ background:var(--info); color:#fff; }

/* ─── 47. LE BANDEAU NOMMÉ DU SOCLE (30/09, maquettes Défis et Nouveautés) ──
   ⚠⚠ Il était composé dans CHAQUE vue, et les formes avaient divergé : quatre
     écrans de POPPY montrent le même avertissement de porte, trois en ligne de
     prose, un en bandeau structuré. `CC.htmlBandeau()` est le point unique.
   ⚠ Deux tons seulement — information et alerte. Une couleur encode l'urgence,
     jamais le type (16/08) : le bleu dit « voici comment ça marche », le rouge
     « il y a quelque chose à faire », et le bouton n'existe que dans le second
     cas, parce qu'il n'y a rien à faire dans le premier. */
.cc-bandeau{
  display:flex; align-items:center; gap:16px; flex-wrap:wrap;
  padding:16px 18px; border-radius:12px; margin-bottom:14px;
  border:1px solid transparent;
}
.cc-bandeau-ic{
  display:inline-flex; align-items:center; justify-content:center;
  width:44px; height:44px; flex:0 0 44px; border-radius:50%;
  color:#fff;
}
/* ⚠ `flex:1 1 320px` et non `flex:1` : dans un conteneur qui ENROULE, le
     navigateur place d'abord chaque enfant à sa taille de BASE et n'applique la
     croissance qu'après, ligne par ligne. Sans base déclarée, le texte restait
     étroit et le bouton partait à la ligne — quatrième fois que cette règle de
     flexbox se paie dans ce projet. */
.cc-bandeau-txt{ flex:1 1 320px; min-width:0; }
.cc-bandeau-t{ font-weight:700; margin-bottom:2px; }
.cc-bandeau-m{ font-size:13.5px; }
.cc-bandeau-btn{ flex:0 0 auto; background:var(--carte); }
.cc-bandeau-info{ background:var(--info-bg); border-color:var(--info-bg); }
.cc-bandeau-info .cc-bandeau-ic{ background:var(--info); }
.cc-bandeau-info .cc-bandeau-t{ color:var(--info); }
.cc-bandeau-alerte{ background:var(--soft); border-color:var(--soft); }
.cc-bandeau-alerte .cc-bandeau-ic{ background:var(--poppy); }
.cc-bandeau-alerte .cc-bandeau-t{ color:var(--poppy-dark); }
.cc-bandeau-alerte .cc-bandeau-btn{ border-color:var(--poppy); color:var(--poppy-dark); }

/* ─── 48. NOUVEAUTÉS POPPY — deux cartes côte à côte (30/09, maquette) ──────
   ⚠⚠ `auto-fit` + `minmax()` mesure la place DISPONIBLE, jamais la fenêtre :
     sur un écran étroit les deux cartes s'empilent d'elles-mêmes, sans seuil
     écrit à la main qu'il faudrait tenir à jour (leçon du 12/09). */
.an-duo{ display:grid; grid-template-columns:repeat(auto-fit, minmax(380px, 1fr)); gap:16px; }
.an-duo > .panel{ margin-bottom:0; }
/* ⚠ La tête d'une carte : l'icône, le titre, et ce qui s'y rattache poussé à
     droite (le badge « Actuellement masqué »). */
.an-carte-tete{ display:flex; align-items:center; gap:12px; margin-bottom:14px; }
.an-carte-tete h2{ margin:0; font-size:17px; }
.an-carte-tete > .b{ margin-left:auto; }
.an-carte-ic{
  display:inline-flex; align-items:center; justify-content:center;
  width:38px; height:38px; flex:0 0 38px; border-radius:10px;
  background:var(--soft); color:var(--poppy-dark);
}
.an-masque{ display:inline-flex; align-items:center; gap:5px; }
.an-masque-ic{ display:inline-flex; }
.an-regle{ display:flex; align-items:center; gap:8px; margin:0 0 14px; }
.an-regle-ic{ display:inline-flex; color:var(--muted); }
/* ⚠⚠ `.carte` ne peint rien hors d'un kanban (elle n'y est déclarée que sous
     `.kanban .carte`) : les entrées du fil flottaient sans fond ni séparation.
     Elles ont leur propre liste, avec un filet entre elles. */
.an-fil{ list-style:none; margin:0; padding:0; }
.an-fil-e{ display:flex; align-items:flex-start; gap:12px; padding:12px 0; }
.an-fil-e + .an-fil-e{ border-top:1px solid var(--line); }
.an-fil-ic{
  display:inline-flex; align-items:center; justify-content:center;
  width:36px; height:36px; flex:0 0 36px; border-radius:9px;
  background:var(--soft2); color:var(--muted);
}
.an-fil-txt{ min-width:0; }
.an-fil-txt p{ margin:4px 0 0; }
/* ⚠ Le troisième ton du bandeau (30/09) : l'attente. Entre le bleu qui informe
     et le rouge qui alerte — ici, rien n'est anormal, mais quelque chose n'est
     pas encore arrivé. */
.cc-bandeau-attente{ background:var(--att-bg); border-color:var(--att-bg); }
.cc-bandeau-attente .cc-bandeau-ic{ background:var(--att); }
/* ⚠ `--att` et NON `var(--att-txt, …)` : ce jeton n'existe pas, et son repli
     l'aurait masqué à jamais — le contrôle 20 d'audit_vues garde exactement
     ça, et `--att-txt` a déjà été inventé ici le 03/09. */
.cc-bandeau-attente .cc-bandeau-t{ color:var(--att); }

/* ─── 49. FACTURATION MENSUELLE — les deux réglages et la réserve (30/09) ───
   ⚠⚠ Les deux champs étaient dans `.actions` de l'en-tête — la place des
     BOUTONS — et n'avaient qu'un `aria-label` : un aveugle les entendait,
     personne d'autre ne les lisait. Ce ne sont pas des actions : ce sont les
     réglages qui décident ce que tout l'écran montre.
   ⚠ Le troisième champ (le marché) n'apparaît qu'à partir de deux marchés, et
     la rangée s'y adapte sans seuil écrit à la main : elle ENROULE quand la
     place manque (téléphone, fenêtre étroite).
   ⚠⚠ 01/10 (relecture du soir, M2) : CHAQUE LISTE A LA PART DE SA PLUS
     LONGUE VALEUR. En trois colonnes ÉGALES (`auto-fit`), la liste « Client »
     tombait à 341 px à 1 366 px dès que le client avait deux marchés —
     c'est-à-dire exactement quand son libellé s'allonge (« …, 3 marchés ») :
     « DEPARTEMENT DE L'AUBE-DRH (0/3 émargé… » était coupé.
     ⚠ Et tout donner au client coupait l'AUTRE liste (reprise du 01/10) : à
       quatre parts pour lui, le marché tombait à 224 px, et le plus long code
       de la cliente, « MARCHE-EDUCATION-ANIMATION-JEUNESSE-3-12 » (CNFPT IDF,
       40 caractères), se lisait « MARCHE-EDUCATION-ANIM ». Le client part de
       420 px et prend trois parts de la place restante, le marché part de
       360 px et en prend deux, le mois part de 200 px et en prend une : à
       1 366 px, 442, 375 et 207 px, le libellé de l'Aube et ce code entiers ;
       sous 1 320 px environ, le marché passe à la ligne, sur toute la largeur.
       Relevé en lecture seule le 01/10 à 1 h 00 : 17 couples (client, mois) à
       deux marchés ou plus, libellé le plus long 50 caractères (l'Aube), codes
       de 17 à 40 caractères. `tests_factu_mensuel` § 26.5 mesure les deux. */
.fm-filtres > .corps{
  display:flex; flex-wrap:wrap; gap:14px 18px;
}
.fm-filtres .f{ display:flex; flex-direction:column; gap:4px; min-width:0; flex:1 1 200px; }
.fm-filtres .f.fm-f-client{ flex:3 1 420px; }
.fm-filtres .f.fm-f-marche{ flex:2 1 360px; }
.fm-filtres .f > label{ font-size:12px; color:var(--muted); font-weight:600; }
/* ⚠ La réserve est SOUS l'état vide et séparée par un filet : elle conditionne
     ce qui s'affichera, elle ne le décrit pas. Placée au-dessus, on la lirait
     avant d'avoir la question qu'elle répond. */
.fm-reserve{
  margin:0; padding-top:14px; border-top:1px solid var(--line); text-align:center;
}

/* ═══════════════════════════════════════════════════════════════════════════
   SOCLE : POINTS DE DEV B (30/09) — dev A, annoncé au point de coordination.
   Trois règles, chacune bornée à ce qu'elle corrige. (Les tuiles à icône,
   `.tuile-ic`, passent désormais par `CC.ajusterTuiles` : c'est dans core.js,
   aucune règle ici — leurs nombres sont écrits par `CC.nombre`, dont les
   séparateurs sont insécables : ils ne passent pas à la ligne, ils débordent,
   et c'est ce débord que l'ajustement mesure.)
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 1. LA LIGNE SÉLECTIONNÉE SE VOIT AUTREMENT QUE PAR SA TEINTE ──────────
   ⚠⚠ Sur une ligne zébrée, `--soft` (la sélection) ne fait plus que 1,08:1
      contre `--soft2` (1,15:1 contre le blanc) : c'était sa seule teinte
      rosée qui la distinguait, le raisonnement que l'en-tête de cette feuille
      déconseille pour un état durable (bloc du zébrage, 25/09). Un liseré
      coquelicot de 3 px, à gauche de la ligne, la désigne sans la couleur de
      fond : 5,13:1 sur `--soft` (le fond de la ligne choisie), 4,75:1 sur le
      survol (au moins 3:1 pour un composant non textuel, WCAG 1.4.11). La
      feuille n'a pas de thème sombre : rien à mesurer de ce côté.
   ⚠ `box-shadow` inset et non `border-left` : en `border-collapse`, une
     bordure posée sur une seule ligne change la largeur de la colonne, et le
     tableau sauterait à chaque sélection. L'ombre se peint dans la cellule.
   ⚠ `:not([colspan])` : la ligne DÉPLIÉE des Marchés et des Modèles porte
     `.selectionnee` sur UNE cellule qui couvre tout le tableau — c'est un
     panneau de détail, pas une ligne choisie ; il n'est pas rayé.
   ⚠ `tr.ecartee > td:first-child` pose une bordure grise de 3 px : l'ombre
     se dessine en dedans, les deux restent lisibles côte à côte.
   ⚠ Une première cellule qui porte déjà SA propre ombre la perd le temps de
     la sélection : c'est le cas de la colonne collée du Suivi détaillé
     (`.suivi-fixe`, `1px 0 0 var(--line)`) — dont aucune ligne n'est jamais
     choisie (relu dans suivi.js le 30/09 : seules les lignes du pilotage le
     sont). Une grille qui choisirait un jour ses lignes devra composer les
     deux ombres. */
table.tbl tbody tr.selectionnee > td:first-child:not([colspan]){ box-shadow:inset 3px 0 0 var(--poppy); }

/* ── 2. SUIVI, ENTRE 1 251 ET 1 340 px : « prochainement » TIENT ────────────
   ⚠⚠ Mesuré le 30/09 dans la police du serveur (banc tests_tuiles_kpi,
      --mesures) : à 1 280 px la tuile « À traiter prochainement » fait
      146 px, sa colonne de texte 86, et le MOT « prochainement » en demande
      94 — il mordait de 8 px sur le rembourrage. La tuile est la plus
      étroite à 1 251 px (142 px) : sous ce seuil les deux groupes
      s'empilent (`.suivi-kpis`) et elle s'élargit ; au-delà de 1 340 px elle
      tient déjà. Entre les deux, on rend 13 px à la colonne de texte : 5 px
      de rembourrage de chaque côté, 3 px d'écart avec l'icône — 95 px à
      1 251, 99 à 1 280. Le texte n'est ni coupé ni tronqué.
   ⚠ MESURÉ SANS BARRE DE DÉFILEMENT : le banc la masque. Une barre classique
     de 17 px prend sa largeur à la page, pas à la requête média — une
     fenêtre de 1 251 px ne laisse alors que 1 234 px à la mise en page, la
     règle s'applique quand même, et dans la police du serveur le mot mord
     encore de 2 px (revue contradictoire du 30/09). La police de Windows est
     plus étroite que celle du serveur ; ce cas n'y a pas été mesuré. */
@media (min-width:1251px) and (max-width:1340px){
  .suivi-mesures-actions .kpi{ padding-left:6px; padding-right:6px; gap:5px; }
}

/* ── 3. STATISTIQUES : UNE TUILE SEULE NE PREND PAS TOUTE LA RANGÉE ─────────
   ⚠⚠ `.kpis` vaut `repeat(auto-fit, minmax(170px, 1fr))` : une tuile seule
      dans sa rangée en prenait TOUTE la largeur — 974 px sur 974 à 1 280 px,
      pour un nombre (banc tests_tuiles_kpi, 30/09). C'est le cas de TROIS
      tuiles pour un profil sans la facturation (`factu.read`, le
      `$voitPieces` d'includes/stats.php) : « CA signé » (ou « Sessions
      signées ») sans le facturé ni l'encaissé, « Demandes validées » sans les
      devis, « Relances sur les demandes » sans les relances de paiement.
      Elle prend désormais la largeur d'une tuile d'une rangée de trois :
      317 px.
   ⚠ Borné à l'écran des Statistiques : les seize autres écrans à tuiles ne
     bougent pas (`.kpis` n'est pas touché). Et au-dessus de 700 px
     seulement : en dessous, les tuiles s'empilent pleine largeur, et une
     tuile seule plus étroite que ses voisines se lirait comme un défaut. */
@media (min-width:701px){
  #vue-stats .kpis > .kpi:only-child{ max-width:calc((100% - 24px) / 3); }
}
