/* portal/fil.css — le FIL vivant (le Conducteur), partagé prof (Régie) ↔ élève (miroir).
   Namespace neutre `fil-*`, par-dessus le design system (`core/ui/*`). Aucun re-style des
   `.shell-*` / `.edu-*`. Chargé côté prof (admin) ET élève (chrome) : un seul rendu, deux vues.
   Teintes de module natives (cours=violet/accent · evals=bleu · depots=ambre · live=vert). */

/* ---- retour « ← Régie » de l'éditeur de parcours (ticket M) ----
   Montage rétrogradé : plus de bascule à deux onglets de même rang. `.fil-mode` ne sert plus qu'au
   lien de retour discret en tête de l'éditeur (`fil.render_plan_back`). L'accès À l'éditeur, lui,
   est un bouton compact du poste de commande (`.fil-cmd-ico`, « ⚙ Ajuster le plan »). */
.fil-mode {
  display: inline-flex; align-items: center; line-height: 1.15;
  padding: 6px 14px; border-radius: var(--edu-pill); text-decoration: none;
  font-weight: 700; font-size: 13.5px; color: var(--edu-muted);
  transition: background .12s, color .12s;
}
.fil-mode:hover { color: var(--edu-text); }

/* ---- POSTE DE COMMANDE FIGÉ (ticket J) : barre d'antenne + ligne de publication de session ----
   Sticky SOUS la topbar (`--fil-cmd-top` = hauteur de topbar, posée par REGIE_JS ; repli 64px avant
   le JS). Fond OPAQUE (le fil défile derrière sans transparence), séparation nette + ombre légère
   quand il est collé. Un seul élément sticky : l'ancienne barre d'antenne perd son propre `sticky`. */
.fil-cmd {
  position: sticky; top: var(--fil-cmd-top, 64px); z-index: 20;
  display: flex; flex-direction: column; gap: 10px;
  margin: 0 0 16px; padding: 12px 14px;
  background: var(--edu-bg); border: 1px solid var(--edu-border-strong);
  border-radius: var(--edu-radius); box-shadow: var(--edu-shadow-sm);
}

/* ---- la barre d'antenne (les scènes qu'on met à l'antenne) ---- */
/* Elle vit désormais DANS le poste de commande : plus de carte ni de sticky à elle (le `.fil-cmd`
   porte les deux). Reste une simple ligne de puces de scène. */
.fil-antenne {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 0; margin: 0;
}

/* La rangée d'OUTILS de session, COMPACTE, sous la barre d'antenne (ticket K) : publication de
   session + « Nouveau message » (accordéon) + « Note élève » (bouton discret) sur une même ligne
   qui s'enroule — un filet de séparation d'avec l'antenne, pas trois pavés empilés qui mangent la
   hauteur. Chaque enfant perd sa carte/marge propre (les règles à 2 classes ci-dessous surclassent
   les styles inline de fil.py — spécificité, pas ordre de source). */
.fil-cmd-tools {
  display: flex; align-items: flex-start; gap: 8px; flex-wrap: wrap;
  padding-top: 10px; border-top: 1px solid var(--edu-border);
}
.fil-cmd-tools > .fil-puball {
  margin: 0; padding: 0; border: 0; border-radius: 0; background: transparent;
}
.fil-cmd-tools > .fil-msgbox { margin: 0; }

/* Ticket L — poste de commande RESSERRÉ : les 4 commandes (Tout dépublier · Tout publier ·
   Nouveau message · Note élève) = 4 boutons CARRÉS homogènes, icône seule. Le sens passe par
   `aria-label` + `title` (obligatoires, cf. fil.py). Scopé sous `.fil-cmd-tools` (spécificité 0,2,0)
   pour surclasser les classes d'identité (`edu-btn`, `fil-obsbtn`) sans `!important`. Theme-aware
   (tokens `--edu-*`), focus visible conservé. « Nouveau message » reste un <summary> (accordéon). */
.fil-cmd-tools .fil-cmd-ico {
  display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box;
  width: 40px; height: 40px; padding: 0; margin: 0; flex: none;
  font-size: 18px; line-height: 1; cursor: pointer; text-decoration: none; list-style: none;
  border: 1px solid var(--edu-border); border-radius: var(--edu-radius-sm);
  background: var(--edu-surface); color: var(--edu-text);
  transition: border-color .12s, background .12s, color .12s;
}
.fil-cmd-tools .fil-cmd-ico:hover,
.fil-cmd-tools .fil-cmd-ico:focus-visible {
  border-color: var(--edu-accent); background: var(--edu-accent-soft);
  color: var(--edu-accent-ink, var(--edu-accent));
}
.fil-cmd-tools .fil-cmd-ico:focus-visible { outline: 2px solid var(--edu-accent); outline-offset: 2px; }
/* « Tout dépublier » = le geste sensible (cache tout aux élèves) → accent danger au survol/focus. */
.fil-cmd-tools .fil-cmd-ico--danger:hover,
.fil-cmd-tools .fil-cmd-ico--danger:focus-visible {
  border-color: var(--edu-danger); color: var(--edu-danger);
  background: color-mix(in srgb, var(--edu-danger) 12%, var(--edu-surface));
}
.fil-cmd-tools summary.fil-cmd-ico::-webkit-details-marker { display: none; }
.fil-cmd-tools summary.fil-cmd-ico::marker { content: ""; }

/* B1-T2 — LES DEUX COMMANDES QUI ÉCRIVENT DE LA PROSE portent leur LIBELLÉ EN CLAIR (📋 « Améliorer
   la formation-type » · 🪧 « Le mot au suivant »). C'est une exception ASSUMÉE au resserrage
   icône-seule du ticket L : ce sont deux DESTINATIONS pour le même geste (le gabarit durable de la
   formation-type / cette seule session), et une destination reléguée à l'infobulle n'est lue par
   personne — c'est ainsi qu'une phrase de séance partait corriger le programme de la formation.
   Même hauteur (40 px) et mêmes tokens que les carrés : la rangée reste une rangée. */
.fil-cmd-tools .fil-cmd-lbl {
  display: inline-flex; align-items: center; gap: 7px; box-sizing: border-box;
  height: 40px; padding: 0 12px; margin: 0; flex: none;
  font-size: 12.5px; font-weight: 600; line-height: 1; white-space: nowrap;
  cursor: pointer; text-decoration: none;
  border: 1px solid var(--edu-border); border-radius: var(--edu-radius-sm);
  background: var(--edu-surface); color: var(--edu-text);
  transition: border-color .12s, background .12s, color .12s;
}
.fil-cmd-tools .fil-cmd-lbl:hover,
.fil-cmd-tools .fil-cmd-lbl:focus-visible {
  border-color: var(--edu-accent); background: var(--edu-accent-soft);
  color: var(--edu-accent-ink, var(--edu-accent));
}
.fil-cmd-tools .fil-cmd-lbl:focus-visible { outline: 2px solid var(--edu-accent); outline-offset: 2px; }
.fil-cmd-tools .fil-cmd-lbl-ico { font-size: 16px; line-height: 1; }
.fil-antenne-lbl {
  font-size: 10px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--edu-faint); white-space: nowrap;
}
.fil-scenes { display: flex; gap: 8px; flex-wrap: wrap; flex: 1; }
.fil-scene {
  display: inline-flex; align-items: center; gap: 7px; cursor: pointer;
  padding: 8px 14px; border-radius: var(--edu-radius-sm);
  border: 1px solid var(--edu-accent); background: var(--edu-accent); color: #fff;
  font-weight: 700; font-size: 13.5px; text-decoration: none; white-space: nowrap;
  transition: filter .12s;
}
.fil-scene:hover { filter: brightness(1.08); }
.fil-scene-i { font-size: 15px; line-height: 1; }
/* Deux états DISTINCTS, même vêtement (désactivé, discret) : `is-soon` = la scène n'existe pas
   encore (« bientôt ») ; `is-empty` = la scène EXISTE mais n'a rien à piocher (« aucun document »).
   Le mot diffère, l'infobulle dit quoi faire — cf. `portal/fil._scene_pick`. */
.fil-scene.is-soon,
.fil-scene.is-empty {
  background: var(--edu-surface-2); color: var(--edu-faint);
  border: 1px dashed var(--edu-border); cursor: not-allowed;
}
.fil-soon {
  font-size: 9px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  padding: 1px 6px; border-radius: var(--edu-pill);
  color: var(--edu-muted); background: var(--edu-surface);
}

/* ---- scène « pioché » (Doc) : SEUL le déclencheur vit ici (la puce d'antenne + son caret).
   Le déroulant lui-même — cadre, position, recherche, items, contraste, ombre (tokenisée
   `--edu-shadow-lg`), anim d'ouverture — est le **picker partagé** `.edu-pick*` (core/ui/components),
   car l'élément porte déjà `edu-pick`/`edu-pick-menu`/`edu-pick-item`. On ne re-style donc PAS le
   menu ici (règle : le portail ne redéfinit pas le cœur) ; on n'ajoute que le caret propre à la puce. */
.fil-scene-caret { font-size: 11px; opacity: .85; transition: transform .12s ease; }
.fil-scene-pick[open] .fil-scene-caret { transform: rotate(180deg); }

/* (L'aide « ? À quoi sert la Régie ? » a été RETIRÉE — ticket K : le prof veut le direct, pas une
   notice. Plus aucun `.fil-help*`.) */

.fil-thread { display: flex; flex-direction: column; gap: 4px; position: relative; }

/* ---- en-têtes de zone + barre MAINTENANT (playhead) ---- */
.fil-zone { display: flex; flex-direction: column; gap: 8px; }
.fil-zonehead {
  font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--edu-faint); padding: 2px 2px;
}
.fil-zone--trace { opacity: .82; }
.fil-cards { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }

.fil-playhead {
  display: flex; align-items: center; gap: 10px; margin: 10px 0;
}
.fil-playhead::before, .fil-playhead::after {
  content: ""; flex: 1; height: 2px; border-radius: 2px;
  background: color-mix(in srgb, var(--edu-accent) 55%, var(--edu-surface));
}
.fil-playhead span {
  font-family: var(--edu-font-mono); font-size: 11.5px; font-weight: 700; letter-spacing: .1em;
  color: #fff; background: var(--edu-accent); padding: 4px 12px; border-radius: var(--edu-pill);
  white-space: nowrap;
}

/* ---- la carte (= un bloc) ---- */
.fil-card {
  border: 1px solid var(--edu-border); border-radius: var(--edu-radius);
  background: var(--edu-surface-2); padding: 12px 14px; border-left: 3px solid var(--edu-border-strong);
}
.fil-card--direct { border-left-color: var(--edu-accent); background: var(--edu-accent-soft); }
.fil-card.is-live { border-left-color: var(--edu-ok); box-shadow: 0 0 0 1px color-mix(in srgb, var(--edu-ok) 45%, transparent); }
.fil-card.is-promise { border-style: dashed; background: transparent; color: var(--edu-muted); }
.fil-card.is-skip { border-left-color: var(--edu-danger); }

/* ---- liseré de GROUPE (= une SÉQUENCE = N parties) + GLUE (SEQUENCE_MODEL §4a/§4b) ----
   Le liseré vit sur le bord DROIT : le bord GAUCHE reste sémantique (zone / live / skip), donc aucune
   régression sur les cartes existantes. COULEUR TOUJOURS (relie même à distance) ; GLUE seulement quand
   deux cartes du même groupe sont adjacentes (classes posées par `_zone`). Les teintes sont des tokens
   `--edu-*` mélangés → theme-aware sans média-query dédiée. `--fil-grp` par défaut = transparent (pas de
   groupe → rien ne change). 6 teintes stables, choisies par hash(seq_id) côté serveur. */
.fil-card.fil-grp { --fil-grp: transparent; border-right: 3px solid var(--fil-grp); }
.fil-grp--0 { --fil-grp: var(--edu-accent); }
.fil-grp--1 { --fil-grp: color-mix(in srgb, var(--edu-accent) 45%, var(--edu-ok)); }
.fil-grp--2 { --fil-grp: var(--edu-ok); }
.fil-grp--3 { --fil-grp: var(--edu-warn); }
.fil-grp--4 { --fil-grp: color-mix(in srgb, var(--edu-warn) 55%, var(--edu-danger)); }
.fil-grp--5 { --fil-grp: color-mix(in srgb, var(--edu-accent) 60%, var(--edu-danger)); }
/* GLUE : on mange la gouttière flex (`.fil-cards { gap: 8px }`) par une marge négative sur la carte du
   BAS, et on carre les coins joints + on retire le bord haut doublé → les cartes lisent comme un bloc,
   le liseré droit continu. La coupure au playhead (3 zones = 3 <ul>) et un jalon intercalé n'obtiennent
   jamais ces classes → fusion honnête, jamais forcée. */
.fil-card.is-glued-bottom { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
.fil-card.is-glued-top { border-top-left-radius: 0; border-top-right-radius: 0; border-top: none; margin-top: -8px; }

.fil-card-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 8px; }
.fil-when {
  font-family: var(--edu-font-mono); font-size: 11px; color: var(--edu-muted);
  background: var(--edu-surface); padding: 2px 7px; border-radius: var(--edu-radius-sm);
  border: 1px solid var(--edu-border);
}
.fil-card-title { font-weight: 700; font-size: 15px; color: var(--edu-text); }
.fil-card.is-promise .fil-card-title { color: var(--edu-muted); font-weight: 600; }

.fil-prov, .fil-badge {
  font-size: 10px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  padding: 2px 7px; border-radius: var(--edu-pill);
}
.fil-prov--live { color: var(--edu-ok); background: var(--edu-ok-bg); }
.fil-prov--collecte { color: var(--edu-warn); background: color-mix(in srgb, var(--edu-warn) 16%, var(--edu-surface)); }
.fil-badge--done { color: var(--edu-ok); background: var(--edu-ok-bg); }
.fil-badge--skip { color: var(--edu-danger); background: color-mix(in srgb, var(--edu-danger) 14%, var(--edu-surface)); }

.fil-promise { font-size: 13px; font-style: italic; color: var(--edu-muted); }
.fil-empty { font-size: 12.5px; color: var(--edu-faint); }

/* ---- l'étape (= un élément atomique) ---- */
.fil-etapes { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.fil-etape {
  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
  padding: 7px 9px; border: 1px solid var(--edu-border); border-radius: var(--edu-radius-sm);
  background: var(--edu-surface);
}
.fil-etape.is-live { border-color: var(--edu-ok); background: color-mix(in srgb, var(--edu-ok) 10%, var(--edu-surface)); }
.fil-etype {
  font-size: 10px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  padding: 2px 7px; border-radius: var(--edu-radius-sm);
  color: var(--edu-accent-ink); background: var(--edu-accent-soft);
}
.fil-etype--quiz, .fil-etype--evaluation { color: var(--edu-evals); background: var(--edu-evals-bg); }
.fil-etype--depot { color: var(--edu-depots); background: var(--edu-depots-bg); }
.fil-etype--snapshot { color: var(--edu-ok); background: var(--edu-ok-bg); }
.fil-etype--note { color: var(--edu-muted); background: var(--edu-surface-2); }

/* `min-width` non nul (ticket L) : le titre ne peut plus être ÉCRASÉ à zéro par les contrôles pour
   « tenir sur une ligne » (d'où « Quiz d'e… ») — sous la contrainte, c'est le groupe de contrôles
   qui s'enroule (`.fil-etape` a `flex-wrap`), le titre reste lisible. */
.fil-etitle { font-size: 13.5px; color: var(--edu-text); flex: 1 1 auto; min-width: 150px; }
.fil-etitle--link { color: var(--edu-accent-ink); font-weight: 600; text-decoration: none; }
.fil-etitle--link:hover { text-decoration: underline; }
/* Groupe des contrôles de l'étape (ticket L) : UN seul enfant flex à droite du titre, qui s'enroule
   comme un bloc quand la ligne est étroite — plus de chevauchement, le titre garde sa place. */
.fil-ectrl { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-left: auto; }
.fil-elive { font-size: 10.5px; font-weight: 700; color: var(--edu-ok); letter-spacing: .04em; }
/* T4 — étape du plan NON publiée (élève) : lisible, non cliquable, marquée « 🔒 à venir ». */
.fil-etape.is-locked { background: var(--edu-surface-2); border-style: dashed; }
.fil-etape.is-locked .fil-etitle { color: var(--edu-muted); }
.fil-elock {
  font-size: 10.5px; font-weight: 600; color: var(--edu-faint); letter-spacing: .02em;
  cursor: default; white-space: nowrap;
}

.fil-epub {
  font-size: 10px; font-weight: 600; padding: 1px 6px; border-radius: var(--edu-radius-sm);
  color: var(--edu-muted); background: var(--edu-surface-2);
}
.fil-epub--publie { color: var(--edu-ok); background: var(--edu-ok-bg); }
.fil-epub--programme { color: var(--edu-warn); background: color-mix(in srgb, var(--edu-warn) 14%, var(--edu-surface)); }
/* Le badge devenu COMMANDE (BUG 2) : même livrée, mais c'est un <select> actionnable. */
select.fil-epub { border: 1px solid var(--edu-border); cursor: pointer; font: inherit;
  font-size: 10px; font-weight: 600; }
/* D5 : le retrait d'un tir sans mémoire ne touche pas la publication — on le dit AU BOUTON. */
.fil-ewarn {
  font-size: 10.5px; font-weight: 600; line-height: 1.2; max-width: 200px;
  color: var(--edu-warn); cursor: help;
}
/* « ✕ Détacher » : discret (geste rare), mais présent là où l'erreur se rattrape. */
.fil-edetach {
  font-size: 12px; line-height: 1; cursor: pointer; padding: 5px 8px;
  border: 1px solid var(--edu-border); border-radius: var(--edu-radius-sm);
  color: var(--edu-muted); background: transparent;
}
.fil-edetach:hover { color: var(--edu-danger); border-color: var(--edu-danger); }

.fil-golive {
  font-size: 12px; font-weight: 600; cursor: pointer; white-space: nowrap;
  padding: 5px 11px; border-radius: var(--edu-radius-sm);
  border: 1px solid var(--edu-accent); color: #fff; background: var(--edu-accent);
  transition: filter .12s;
}
.fil-golive:hover { filter: brightness(1.08); }

/* DÉPART SYNCHRO sur la carte du quiz (ticket K) : l'action PRIMAIRE quand l'épreuve est en salle
   d'attente — un aplat vert « prêt à lancer » qui saute aux yeux, distinct des contrôles secondaires
   (publication, retirer, détacher). Simple ancre vers la console des épreuves (`#epr-<id>`) où le
   prof règle durée/mode et déclenche. Placée EN TÊTE de la barre de contrôles de l'étape. */
.fil-eparti {
  display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; text-decoration: none;
  font-size: 12.5px; font-weight: 800; letter-spacing: .01em; cursor: pointer;
  padding: 6px 13px; border-radius: var(--edu-radius-sm);
  color: #fff; background: var(--edu-ok); border: 1px solid var(--edu-ok);
  box-shadow: var(--edu-shadow-sm); transition: filter .12s;
}
.fil-eparti:hover, .fil-eparti:focus-visible { filter: brightness(1.08); }
/* À l'antenne : bouton « retirer de l'antenne » — vert (état en direct), mais ACTIONNABLE
   (le geste inverse du tir), d'où cursor pointer + le hover partagé de .fil-golive. */
.fil-golive.is-live {
  color: var(--edu-ok); background: var(--edu-ok-bg);
  border-color: var(--edu-ok); cursor: pointer;
}

/* ---- JALONS (T1) : une LIGNE DE REPÈRE, jamais une carte ----
   Contrepartie de l'arbitrage « le jalon est dans le fil » : la forme empêche la logistique de
   diluer le fil pédagogique. Densité moindre (une ligne, pas un bloc), distinction nette (filet
   pointillé + fond neutre, aucune teinte de module), zéro bouton de tir. */
.fil-jalons { list-style: none; margin: 0 0 8px; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.fil-jalon {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 4px 10px; font-size: 12.5px; color: var(--edu-muted);
  border-left: 2px dashed var(--edu-border-strong); background: transparent;
}
.fil-jalon.is-action { border-left-color: var(--edu-warn); }
.fil-jalon.is-annule { opacity: .6; }
.fil-jalon.is-annule .fil-jtitre { text-decoration: line-through; }   /* barré, JAMAIS effacé */
.fil-jwhen { font-weight: 700; color: var(--edu-text); white-space: nowrap; }
.fil-jfin { color: var(--edu-faint); white-space: nowrap; }
.fil-jtitre { color: var(--edu-text); }
.fil-jaction { font-size: 10px; font-weight: 700; letter-spacing: .04em; white-space: nowrap;
  padding: 1px 6px; border-radius: var(--edu-radius-sm);
  color: var(--edu-warn); background: color-mix(in srgb, var(--edu-warn) 14%, var(--edu-surface)); }
.fil-jsalle { font-size: 11.5px; color: var(--edu-muted); white-space: nowrap; }
.fil-jannule { font-size: 10px; font-weight: 700; color: var(--edu-danger); }
.fil-jbtn { margin-left: auto; font-size: 11px; cursor: pointer; padding: 2px 8px;
  border: 1px solid var(--edu-border); border-radius: var(--edu-radius-sm);
  color: var(--edu-muted); background: transparent; }
.fil-jbtn + .fil-jbtn { margin-left: 0; }
.fil-jbtn:hover { color: var(--edu-text); border-color: var(--edu-border-strong); }
.fil-jbtn--off:hover { color: var(--edu-danger); border-color: var(--edu-danger); }

/* Le composeur (prof) : une seule ligne, tabulable — titre → créneau → heure → Entrée. */
.fil-jform { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-bottom: 12px; padding: 8px 10px; font-size: 13px;
  border: 1px dashed var(--edu-border-strong); border-radius: var(--edu-radius);
  background: var(--edu-surface); }
.fil-jform-lbl { font-weight: 700; color: var(--edu-muted); }
.fil-jform input, .fil-jform select { font: inherit; padding: 4px 8px;
  border: 1px solid var(--edu-border); border-radius: var(--edu-radius-sm);
  color: var(--edu-text); background: var(--edu-surface-2); }
.fil-jf-titre { flex: 1; min-width: 180px; }
/* `hidden` était ÉCRASÉ par `.edu-btn { display: flex }` → un « Annuler » visible en permanence,
   ambigu à côté d'« Ajouter ». L'attribut doit gagner. */
.fil-jform [hidden] { display: none !important; }
.fil-jf-act { display: flex; align-items: center; gap: 4px; color: var(--edu-muted); font-size: 12px; }
.fil-jform-off { margin-bottom: 12px; font-size: 12.5px; color: var(--edu-faint); }

/* ---- toast (feedback d'action Régie) ---- */
.fil-toast {
  position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%) translateY(20px);
  background: var(--edu-text); color: var(--edu-surface-2); padding: 10px 18px;
  border-radius: var(--edu-pill); font-size: 13px; font-weight: 600;
  opacity: 0; pointer-events: none; transition: opacity .2s, transform .2s; z-index: 60;
}
.fil-toast.is-on { opacity: 1; transform: translateX(-50%) translateY(0); }

@media (max-width: 640px) {
  .fil-card-head { gap: 6px; }
  .fil-golive { flex: 1; }
}

/* ---- panneau « Épreuves » (Régie, prof) : cycle d'une épreuve tirée (evals T2b) ---- */
.fil-epreuves {
  margin-bottom: 16px; padding: 12px 14px;
  background: var(--edu-surface); border: 1px solid var(--edu-border-strong);
  border-radius: var(--edu-radius);
}
/* Accordéon natif : le <summary> EST l'en-tête (plus de bandeau séparé). Replié, il ne coûte qu'une
   ligne en haut du fil ; le chevron ▸ tourne à l'ouverture. `list-style` + ::-webkit-details-marker
   retirés = on dessine notre propre marqueur (le triangle natif ne suit pas nos tokens). */
.fil-epreuves-hd {
  display: flex; align-items: center; gap: 10px; cursor: pointer;
  font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--edu-faint);
  border-radius: var(--edu-radius-sm); padding: 2px 4px; margin: -2px -4px;
}
.fil-epreuves-hd::-webkit-details-marker { display: none; }
.fil-epreuves-hd::marker { content: ""; }
.fil-epreuves-hd:hover { color: var(--edu-text); background: var(--edu-surface-2); }
.fil-epreuves-hd:focus-visible { outline: 2px solid var(--edu-accent); outline-offset: 2px; }
.fil-epreuves-ttl::before {
  content: "\25B8"; display: inline-block; margin-right: 8px;
  /* `rotate(0deg)` EXPLICITE : sans lui l'état replié n'a aucun `transform` (calculé « none »), et
     l'interpolation none -> rotate part d'un état indéfini. Le rendu était juste à l'écran mais la
     valeur calculée était instable d'une lecture à l'autre — on fixe les deux bouts. */
  transform: rotate(0deg); transition: transform .15s ease;
}
.fil-epreuves-acc[open] > .fil-epreuves-hd .fil-epreuves-ttl::before { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) {
  .fil-epreuves-ttl::before { transition: none; }
}
/* Le RÉSUMÉ (compte par statut) : c'est lui qui rend l'accordéon utile — il dit ce qu'il y a dedans
   sans l'ouvrir. `margin-left:auto` le pousse à droite, il reste lisible replié comme déplié. */
.fil-epreuves-n {
  margin-left: auto; font-size: 11px; font-weight: 600; letter-spacing: .03em;
  text-transform: none; color: var(--edu-muted);
}
.fil-epreuves-body { margin-top: 10px; }
.fil-epr {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px 16px;
  padding: 10px 0; border-top: 1px solid var(--edu-border);
}
.fil-epr:first-of-type { border-top: 0; }
/* CIBLE de l'ancre « ▶ C'est parti » (ticket K) : la ligne du quiz visé s'illumine brièvement pour
   dire « c'est ICI qu'on règle et qu'on déclenche ». `scroll-margin-top` = repli avant que REGIE_JS
   ne pose l'offset EXACT (hauteur topbar + poste de commande) ; sans lui l'ancre passe sous le
   poste de commande figé. Sous `prefers-reduced-motion`, un cadre statique remplace le battement. */
.fil-epr { scroll-margin-top: 120px; border-radius: var(--edu-radius-sm); }
/* Le surlignage `:target` = un ENCADRÉ propre (ticket L) : la ligne du quiz visé a `padding: 10px 0`
   (aucune gouttière horizontale), donc le liseré collait au texte et « coupait » le contenu. On lui
   donne un vrai padding horizontal + une marge négative égale → le cadre respire sans décaler la mise
   en page. Coins arrondis (radius-sm ci-dessus). Reste illuminé après le flash (repère persistant). */
.fil-epr:target {
  animation: fil-epr-flash 2s ease-out 1;
  background: var(--edu-accent-soft);
  box-shadow: inset 0 0 0 2px var(--edu-accent);
  padding-left: 12px; padding-right: 12px;
  margin-left: -12px; margin-right: -12px;
}
@keyframes fil-epr-flash {
  0% { background: var(--edu-accent-soft); box-shadow: inset 0 0 0 2px var(--edu-accent); }
  100% { background: transparent; box-shadow: inset 0 0 0 2px transparent; }
}
@media (prefers-reduced-motion: reduce) {
  .fil-epr:target { animation: none; background: var(--edu-accent-soft);
    box-shadow: inset 0 0 0 2px var(--edu-accent); }
}
.fil-epr-hd { display: flex; align-items: center; gap: 10px; flex: 1; min-width: 200px; }
.fil-epr-t { font-weight: 700; font-size: 14px; color: var(--edu-text); }
.fil-epr-st {
  font-size: 10px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  padding: 2px 8px; border-radius: var(--edu-pill);
  background: var(--edu-surface-2); color: var(--edu-muted);
}
.fil-epr-st--attente { color: var(--edu-warn); }
.fil-epr-st--ouverte { color: var(--edu-ok); background: var(--edu-ok-bg); }
.fil-epr-st--fermee  { color: var(--edu-danger); }
.fil-epr-form, .fil-epr-ctrl, .fil-epr-tous, .fil-epr-solo {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
/* DEUX ZONES QUI NE SE CONFONDENT PAS (incident 2026-07-28) : « toute la classe » et « un seul
   élève » vivaient dans la même rangée, si bien que le sélecteur d'élève jouxtait le faux départ —
   qui, lui, ne cible personne. Elles sont désormais encadrées, étiquetées, et la zone nominative
   passe à la ligne dès que la place manque (elle ne se retrouve jamais accolée au geste collectif). */
.fil-epr-ctrl { gap: 8px 14px; }
.fil-epr-tous, .fil-epr-solo {
  padding: 6px 10px; border: 1px solid var(--edu-border);
  border-radius: var(--edu-radius-sm); background: var(--edu-surface-2);
}
.fil-epr-solo { flex: 1 1 380px; }
.fil-epr-lbl {
  font-size: 10px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--edu-muted); white-space: nowrap;
}
/* Le sélecteur appartient VISIBLEMENT à la zone nominative : sur fond de zone, il garde le fond de
   surface pour se lire comme un champ (et non comme une étiquette de plus). */
.fil-epr-solo .fil-epr-eleve, .fil-epr-solo .fil-epr-min { background: var(--edu-surface); }
.fil-epr-f {
  display: inline-flex; flex-direction: column; gap: 2px;
  font-size: 10px; font-weight: 600; letter-spacing: .03em; text-transform: uppercase;
  color: var(--edu-muted);
}
.fil-epr-duree, .fil-epr-min, .fil-epr-mode, .fil-epr-eleve {
  padding: 6px 8px; border-radius: var(--edu-radius-sm);
  border: 1px solid var(--edu-border); background: var(--edu-surface-2);
  color: var(--edu-text); font-size: 13px; font-weight: 600;
}
.fil-epr-duree, .fil-epr-min { width: 72px; }
/* Ticket L — le <select> de mode tient dans sa largeur (avant : « …e ferme) » tronqué). Largeur
   maîtrisée qui accueille le plus long libellé (« hard — couperet à l'heure ») ; `max-width` = repli
   propre sur un écran étroit (la ligne s'enroule au lieu de déborder). */
.fil-epr-mode { width: 210px; max-width: 100%; text-overflow: ellipsis; }
.fil-epr-u { font-size: 12px; color: var(--edu-muted); }
/* Faux départ : un geste RARE et lourd (il annule les copies rendues) — il se voit, il ne s'effleure
   pas. Teinte danger en contour (pas un aplat plein : ce n'est pas l'action primaire de la ligne). */
.fil-epr-relance {
  color: var(--edu-danger); border-color: var(--edu-danger);
  background: transparent; box-shadow: none;
}
.fil-epr-relance:hover { background: var(--edu-danger); color: #fff; border-color: var(--edu-danger); }

/* ── LE NAVIGATEUR DU FIL (colonne de droite) ─ ticket UX 2026-07-22 ─────────────────────────────
   Sommaire de SAUT du fil (jour → demi-journée → séquence), MÊME look que le rail de lecture des
   docs : on RÉUTILISE `.read-rail`/`.read-h`/`.read-toc`/`.read-row`/`.read-link` (core/ui/
   components.css, déjà chargé). Ici = PUR navigateur (liens d'ancre natifs, zéro JS). Le `.filnav`
   est un <details> collant ouvert sur desktop, repliable sur mobile. Tokens `--edu-*` seulement,
   theme-aware ; aucun re-style des `.edu-*`. */
/* La Régie SORT du cap de lecture du dashboard — MÊME patron que le Calendrier
   (`calendrier.css` : `.shell-content:has(> .cal) { max-width: none }`), ⛔ on n'en invente pas un
   second. `.shell-content` plafonne à 1040px (colonne de LECTURE, faite pour du texte/formulaires) :
   la Régie n'en est pas une, c'est un ÉTABLI (poste de commande + fil + navigateur de saut). Sans
   cette règle, à ~2000px de fenêtre, le contenu restait boxé à 1040px flush-gauche → ~300-960px de
   vide mort à droite et le navigateur du fil flottait loin du bord au lieu d'y coller (constaté à
   l'écran par Gabriel, 22/08). Scopé `:has(> .filnav-wrap)` → n'affecte QUE la Régie (et le miroir
   élève) quand le fil a un navigateur ; les autres pages du dashboard gardent leurs 1040px. */
.shell-content:has(> .filnav-wrap) { max-width: none; }
.filnav-wrap { display: grid; grid-template-columns: minmax(0, 1fr) 240px; gap: 20px; align-items: start; }
.filnav-wrap > .fil-thread { grid-column: 1; grid-row: 1; min-width: 0; }
/* Le navigateur COLLE — explicitement, on ne se repose pas sur le `.read-rail` (dont le `top:78px`
   est réglé pour un DOCUMENT). Ici, le fil a un POSTE DE COMMANDE déjà collant et OPAQUE en haut
   (`.fil-cmd`, top ~64px + sa hauteur, z-index:20) : un `top` trop petit épinglerait le navigateur
   DERRIÈRE lui → invisible (« il disparaît en scrollant »). `--filnav-top` = la clairance EXACTE
   sous le poste de commande, posée par REGIE_JS (`place()`, hauteur mesurée, donc robuste). Côté
   ÉLÈVE il n'y a pas de poste de commande → le repli 90px dégage la simple topbar. `align-self:start`
   = la clé d'un sticky en grille (sans lui l'item s'étire sur toute la hauteur de rangée → aucune
   marge pour coller). */
.filnav-wrap > .filnav {
  grid-column: 2; grid-row: 1; align-self: start;
  position: sticky; top: var(--filnav-top, 90px);
  max-height: calc(100vh - var(--filnav-top, 90px) - 16px); overflow-y: auto;
}
.filnav .filnav-sum { cursor: pointer; margin-bottom: 4px; }
.filnav .filnav-toc { gap: 1px; }
.filnav-lvl1 .read-link { font-weight: 700; color: var(--edu-text); }
.filnav-lvl2 .read-link { padding-left: 16px; }
.filnav-lvl3 .read-link { padding-left: 30px; font-size: 12px; }
.filnav-lvl3 .read-link::before { content: "◆"; margin-right: 6px; color: var(--edu-faint); font-size: 9px; vertical-align: 1px; }
/* le saut dégage le poste de commande collant : `--filnav-anchor` = même clairance (REGIE_JS) ;
   repli 96px côté élève (pas de poste de commande, juste la topbar). */
.fil-grille .fil-g-fil, .fil-grille .fil-g-half, .fil-grille .fil-g-seq[id] { scroll-margin-top: var(--filnav-anchor, 96px); }
@media (max-width: 900px) {
  .filnav-wrap { grid-template-columns: 1fr; }
  .filnav-wrap > .fil-thread, .filnav-wrap > .filnav { grid-column: 1; grid-row: auto; }
  .filnav-wrap > .filnav { position: static; max-height: none; }  /* mobile : plus collant, repliable */
}
