/* ------------------------------------------------------------------------
   Thème « Ardoise & Abricot ».

   Chaque couleur est déclarée une fois en canaux (« 27 31 36 ») pour que
   Tailwind puisse y appliquer une opacité, et la variable lisible qui suit en
   est dérivée : « var(--accent) » reste utilisable dans du CSS ordinaire sans
   qu'il existe deux sources de vérité à tenir synchronisées.

   Répartition des rôles : l'ardoise porte l'interface (texte, surfaces,
   bordures) ; l'abricot est réservé aux points d'accroche — marque, bouton
   principal, élément de navigation actif, anneau de focus, bulle du chatbot.
   ------------------------------------------------------------------------ */

:root {
  /* Polices du système, sans rien télécharger : une police distante est un
     appel réseau de plus sur la connexion d'un élève, et le texte changerait
     de dessin selon qu'elle arrive ou non. */
  --font-ui: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-code: ui-monospace, "Cascadia Code", Consolas, monospace;

  --bg-rgb: 250 248 245;
  --panel-rgb: 255 255 255;
  --column-rgb: 240 237 232;
  --ink-rgb: 27 31 36;
  --muted-rgb: 103 110 120;

  /* L'abricot foncé : 4,6:1 sur le fond clair, il passe donc AA en texte.
     L'abricot vif de la version sombre ne le passerait pas ici. */
  --accent-rgb: 194 99 45;
  --accent-ink-rgb: 141 70 30;
  --accent-soft-rgb: 250 236 226;
  --accent-hover-rgb: 165 83 36;
  /* Encre posée SUR un aplat d'accent (bouton principal). */
  --sur-accent-rgb: 255 250 246;

  --border-rgb: 231 226 219;
  --border-strong-rgb: 199 192 182;

  --danger-rgb: 193 51 51;
  --danger-ink-rgb: 158 40 40;
  --danger-soft-rgb: 252 234 234;
  --success-rgb: 42 122 88;
  --success-ink-rgb: 30 90 65;
  --success-soft-rgb: 229 242 235;
  --warning-rgb: 146 96 12;
  --warning-ink-rgb: 112 74 9;
  --warning-soft-rgb: 250 241 224;

  --radius: 10px;
  --radius-large: 14px;
  --shadow: 0 1px 2px rgb(27 31 36 / .05), 0 1px 3px rgb(27 31 36 / .07);
  --shadow-forte: 0 4px 14px rgb(27 31 36 / .10), 0 1px 3px rgb(27 31 36 / .06);
  --ring: 0 0 0 3px rgb(var(--accent-rgb) / .40);
}

[data-theme="nuit"] {
  --bg-rgb: 21 24 28;
  --panel-rgb: 29 33 38;
  --column-rgb: 37 42 48;
  --ink-rgb: 236 238 241;
  --muted-rgb: 154 161 172;

  /* L'abricot s'éclaircit en nuit : le ton foncé du mode clair tomberait à
     3:1 sur l'ardoise et deviendrait illisible en texte. */
  --accent-rgb: 242 149 92;
  --accent-ink-rgb: 246 173 128;
  --accent-soft-rgb: 52 38 29;
  --accent-hover-rgb: 248 172 124;
  --sur-accent-rgb: 26 18 12;

  --border-rgb: 47 53 61;
  --border-strong-rgb: 72 80 90;

  --danger-rgb: 248 129 129;
  --danger-ink-rgb: 250 154 154;
  --danger-soft-rgb: 56 28 28;
  --success-rgb: 108 205 158;
  --success-ink-rgb: 134 216 175;
  --success-soft-rgb: 20 45 35;
  --warning-rgb: 229 178 96;
  --warning-ink-rgb: 236 194 129;
  --warning-soft-rgb: 51 40 18;

  --shadow: 0 1px 3px rgb(0 0 0 / .45);
  --shadow-forte: 0 4px 16px rgb(0 0 0 / .55);
  --ring: 0 0 0 3px rgb(var(--accent-rgb) / .35);
}

/* Formes lisibles des mêmes couleurs, pour le CSS écrit à la main. */
:root, [data-theme="nuit"] {
  --bg: rgb(var(--bg-rgb));
  --panel: rgb(var(--panel-rgb));
  --column: rgb(var(--column-rgb));
  --ink: rgb(var(--ink-rgb));
  --muted: rgb(var(--muted-rgb));
  --accent: rgb(var(--accent-rgb));
  --accent-ink: rgb(var(--accent-ink-rgb));
  --accent-soft: rgb(var(--accent-soft-rgb));
  --accent-hover: rgb(var(--accent-hover-rgb));
  --sur-accent: rgb(var(--sur-accent-rgb));
  --border: rgb(var(--border-rgb));
  --border-strong: rgb(var(--border-strong-rgb));
  --danger: rgb(var(--danger-rgb));
  --danger-ink: rgb(var(--danger-ink-rgb));
  --danger-soft: rgb(var(--danger-soft-rgb));
  --success: rgb(var(--success-rgb));
  --success-ink: rgb(var(--success-ink-rgb));
  --success-soft: rgb(var(--success-soft-rgb));
  --warning: rgb(var(--warning-rgb));
  --warning-ink: rgb(var(--warning-ink-rgb));
  --warning-soft: rgb(var(--warning-soft-rgb));
}

body { font-family: var(--font-ui); }
code, kbd, pre, samp { font-family: var(--font-code); }

/* La barre de défilement ne doit pas décaler la page : son emplacement est
   réservé en permanence. Sans cela, une liste filtrée qui devient assez courte
   pour tenir dans la fenêtre fait disparaître l'ascenseur, et tout le contenu
   centré saute latéralement d'une quinzaine de pixels. */
html { scrollbar-gutter: stable; background-color: var(--bg); }

/* Largeur de l'application, définie une seule fois. L'en-tête, les messages et
   le contenu s'alignent parce qu'ils partagent cette classe : trois
   « max-w-* » recopiés dans trois gabarits finissent toujours par diverger. */
.shell {
  width: 100%;
  max-width: 76rem;
  margin-inline: auto;
  padding-inline: 1rem;
}
/* Colonne étroite : la conversation, qui se lit d'un trait. Une bulle de chat
   étirée sur 1 200 px ne se lit plus. */
.shell-etroit { max-width: 46rem; }

/* --- « hidden » veut dire caché -------------------------------------------
   Le seul `!important` du projet, et il en évite une classe entière.

   Tailwind écrit `[hidden]{display:none}` dans son preflight, avec la
   spécificité d'un sélecteur d'attribut — exactement celle d'une classe. Toute
   règle d'affichage à une classe fait donc jeu égal avec lui, et c'est l'ordre
   des feuilles qui départage : `.flex`, écrit plus loin dans le MÊME fichier
   que le preflight, l'emporte toujours ; `.btn`, écrit dans style.css,
   l'emporte en production (chargé après tailwind.css) et perd en développement
   (le CDN s'injecte à l'exécution, donc après).

   Autrement dit, un bloc rendu `hidden` par le serveur pouvait s'afficher
   quand même, et pas de la même façon en local et en ligne. Mesuré : un
   `class="flex" hidden` calcule `display: flex`, un `class="btn" hidden`
   calcule `inline-flex`. C'est ce qui rendait visibles sans JavaScript les
   commandes de `_trame.html`, que le gabarit écrit pourtant `hidden`
   précisément pour qu'elles n'apparaissent que si `trame.js` les prend en
   charge — des boutons qui n'agissent sur rien.

   `hidden` est une déclaration de l'HTML, pas un choix de mise en forme : il
   n'y a rien à arbitrer, et cette ligne remplace les rustines au cas par cas
   (`.voile[hidden]` en était une). Les scripts qui révèlent un bloc retirent
   l'attribut, ce que `!important` ne gêne en rien. */
[hidden] { display: none !important; }

/* --- Encoches et barre d'accueil ------------------------------------------
   Installée sur un téléphone, l'application occupe l'écran entier : plus de
   barre de navigateur pour la tenir à l'écart de l'encoche et de la barre
   d'accueil. « viewport-fit=cover » (cf. base.html) est ce qui donne accès aux
   marges sûres, et il faut alors les reprendre soi-même.

   Les marges latérales sont posées sur « body » et non sur « .shell » :
   l'en-tête collant d'une séance annule la gouttière de « .shell » par un
   « -mx-4 » pour que son fond aille d'un bord à l'autre, et le décaler ici
   laisserait une bande de fond de page le long de l'encoche. Elles valent zéro
   partout ailleurs qu'en paysage sur un téléphone encoché.

   La marge basse, elle, va au pied de page — le dernier élément de la colonne.
   Il porte « pt-6 » et non « py-6 » : Tailwind écrirait sinon la même
   déclaration que celle d'en dessous, et laquelle l'emporte dépendrait de
   l'ordre des feuilles, qui n'est pas le même en développement et en
   production. */
body {
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
}
.pied { padding-bottom: calc(1.5rem + env(safe-area-inset-bottom)); }

/* Le focus se voit, et il se voit en abricot. */
a:focus-visible, button:focus-visible, summary:focus-visible,
[role="button"]:focus-visible, input:focus-visible,
select:focus-visible, textarea:focus-visible {
  outline: none;
  box-shadow: var(--ring);
  border-radius: 0.375rem;
}

/* --- Les pièces de la charte -------------------------------------------- */

.carte {
  background-color: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-large);
  box-shadow: var(--shadow);
}

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  border-radius: 0.5rem;
  border: 1px solid transparent;
  padding: 0.55rem 1rem;
  font-weight: 600;
  line-height: 1.2;
  cursor: pointer;
  background-color: var(--panel);
  color: var(--ink);
  text-decoration: none;
}
.btn:disabled, .btn[aria-disabled="true"] { opacity: .5; cursor: not-allowed; }

/* Bouton principal : l'aplat abricot est la signature du thème, et il est
   réservé à UNE action par écran. */
.btn-principal { background-color: var(--accent); color: var(--sur-accent); }
.btn-principal:hover:not(:disabled) { background-color: var(--accent-hover); }

.btn-neutre { border-color: var(--border); }
.btn-neutre:hover:not(:disabled) { border-color: var(--border-strong); background-color: var(--column); }

.btn-danger { border-color: var(--danger); color: var(--danger-ink); background-color: var(--danger-soft); }

.btn-petit { padding: 0.3rem 0.6rem; font-size: 0.8125rem; }

/* Élément de navigation. Inactif il est transparent ; actif il prend l'aplat
   d'accent. */
.nav-item {
  padding: 0.35rem 0.7rem;
  border-radius: 0.5rem;
  color: var(--ink);
  white-space: nowrap;
  text-decoration: none;
}
.nav-item:hover { background-color: var(--column); }
.nav-item-actif, .nav-item-actif:hover {
  background-color: var(--accent);
  color: var(--sur-accent);
  font-weight: 600;
}

/* Liens de contenu : encre d'accent, souligné abricot. Le trait est lisible là
   où un texte abricot clair ne le serait pas — c'est la façon de faire entrer
   la couleur dans du texte courant sans perdre le contraste. */
.lien-charte {
  color: var(--accent-ink);
  text-decoration: underline;
  text-decoration-color: var(--accent);
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
}

.pastille {
  display: inline-block;
  border-radius: 999px;
  padding: 0.15rem 0.55rem;
  font-size: 0.75rem;
  font-weight: 600;
  white-space: nowrap;
  border: 1px solid transparent;
}
.pastille-neutre { background-color: var(--column); color: var(--muted); }
.pastille-accent { background-color: var(--accent-soft); color: var(--accent-ink); border-color: var(--accent); }
.pastille-succes { background-color: var(--success-soft); color: var(--success-ink); }
.pastille-attente { background-color: var(--warning-soft); color: var(--warning-ink); }
.pastille-echec { background-color: var(--danger-soft); color: var(--danger-ink); }

.chiffre-cle {
  color: var(--ink);
  font-weight: 800;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.progression {
  height: 0.4rem;
  border-radius: 999px;
  background-color: var(--column);
  overflow: hidden;
}
.progression > span {
  display: block;
  height: 100%;
  border-radius: 999px;
  background-color: var(--accent);
  transition: width .25s ease;
}

/* --- Jauge de trame : une case par notion, la couleur dit le motif ------- */

/* Un pourcentage unique confond « compris » et « sauté ». Ici l'avancement se
   lit à la longueur remplie, la maîtrise à la couleur : une notion acquise est
   en aplat, une notion traversée sans être comprise reste en creux. Sauter fait
   donc avancer sans remplir — c'est un frein plus honnête qu'un bouton grisé,
   et l'élève voit d'un coup d'œil ce qu'il lui reste à revoir. */
.jauge {
  display: flex;
  gap: 2px;
  height: 0.4rem;
}
.jauge-case {
  flex: 1;
  border-radius: 2px;
  background-color: var(--column);
  transition: background-color .25s ease;
}
.jauge-case:first-child { border-top-left-radius: 999px; border-bottom-left-radius: 999px; }
.jauge-case:last-child { border-top-right-radius: 999px; border-bottom-right-radius: 999px; }

.jauge-acquise { background-color: var(--accent); }

/* Passée après blocage : l'abricot en filet, le fond reste creux. La rayure
   dit « on est passé dessus » sans prétendre que c'est acquis. */
.jauge-bloquee {
  background-color: var(--accent-soft);
  background-image: repeating-linear-gradient(
    -45deg,
    rgb(var(--accent-rgb) / .55) 0 2px,
    transparent 2px 4px
  );
}
/* Passée par l'élève : même creux, sans rayure — c'est un choix, pas un échec. */
.jauge-passee {
  background-color: var(--accent-soft);
  box-shadow: inset 0 0 0 1px rgb(var(--accent-rgb) / .45);
}
.jauge-avenir { background-color: var(--column); }

/* La notion en cours est désignée par la **hauteur**, pas par une couleur : les
   trois motifs occupent déjà la teinte, et un liseré de plus se perdait sur une
   case de quatre pixels de haut — essayé, invisible. Le débordement vertical
   est voulu, `.jauge` ne coupe pas. */
.jauge-courante {
  transform: scaleY(2);
  background-color: var(--border-strong);
}
.jauge-courante.jauge-acquise { background-color: var(--accent); }

/* Un témoin de motif isolé — la puce d'une liste de notions, une légende.
   `.jauge-case` ne convient pas hors d'une jauge : son `flex: 1` l'étire sur
   toute la ligne, ce qui désalignait les intitulés du parcours de trame. Les
   couleurs, elles, restent celles des `.jauge-*` ci-dessus : un carré et une
   case de jauge doivent dire la même chose. */
.temoin {
  flex: none;
  display: inline-block;
  width: 0.7rem;
  height: 0.7rem;
  border-radius: 3px;
  vertical-align: -1px;
}

/* --- Colonne de navigation (l'explorateur de disciplines) ---------------- */

/* L'élément actif prend l'accroche douce et un filet abricot, pas l'aplat de
   `.nav-item-actif` : l'aplat rendrait illisible la ligne de comptes, qui est
   en gris sous le nom. */
.item-liste {
  display: block;
  padding: 0.45rem 0.65rem;
  border-radius: 0.5rem;
  border-left: 3px solid transparent;
  color: var(--ink);
  text-decoration: none;
}
.item-liste:hover { background-color: var(--column); }
.item-liste-actif, .item-liste-actif:hover {
  background-color: var(--accent-soft);
  border-left-color: var(--accent);
}
.item-liste-actif .item-liste-nom { color: var(--accent-ink); }

/* --- Champs de saisie ---------------------------------------------------- */

/* Le préfixe « body » est nécessaire : le preflight de Tailwind pose
   « padding: 0 » sur input/select/textarea avec la même spécificité, et il est
   injecté après cette feuille. On passe donc à 0,0,2 pour primer.
   « input:not([type]) » est indispensable : les champs écrits sans attribut
   « type » n'entrent dans aucun des sélecteurs typés ci-dessous et se
   retrouveraient sans bordure ni padding. */
body input:not([type]),
body input[type="text"], body input[type="email"], body input[type="password"],
body input[type="search"], body input[type="number"], body input[type="url"],
body input[type="date"], body select, body textarea {
  border: 1px solid var(--border);
  border-radius: 0.5rem;
  padding: 0.55rem 0.75rem;
  background-color: var(--panel);
  color: var(--ink);
  line-height: 1.45;
  width: 100%;
}
body textarea { padding: 0.65rem 0.75rem; }
body input[type="file"] {
  border: 1px solid var(--border);
  border-radius: 0.5rem;
  padding: 0.45rem 0.6rem;
  background-color: var(--panel);
  color: var(--ink);
  font-size: 0.875rem;
  width: 100%;
}
body input:focus, body select:focus, body textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: var(--ring);
}
body input[type="checkbox"], body input[type="radio"] {
  padding: 0;
  width: auto;
  accent-color: var(--accent);
}
input::placeholder, textarea::placeholder { color: var(--muted); opacity: .75; }

/* Le code d'accès se lit caractère par caractère : chasse fixe, lettres
   espacées, majuscules imposées à la saisie. */
.champ-code {
  font-family: var(--font-code);
  font-size: 1.5rem;
  letter-spacing: 0.22em;
  text-align: center;
  text-transform: uppercase;
}

/* --- La conversation ----------------------------------------------------- */

.bulle {
  border-radius: var(--radius-large);
  padding: 0.8rem 1rem;
  max-width: 88%;
  line-height: 1.55;
}
/* Le chatbot prend l'accroche douce et s'aligne à gauche ; l'élève prend le
   panneau bordé et s'aligne à droite. La couleur distingue les deux voix plus
   vite qu'une étiquette. */
.bulle-assistant {
  background-color: var(--accent-soft);
  border: 1px solid rgb(var(--accent-rgb) / .35);
  align-self: flex-start;
  border-bottom-left-radius: 0.3rem;
}
.bulle-eleve {
  background-color: var(--panel);
  border: 1px solid var(--border);
  align-self: flex-end;
  border-bottom-right-radius: 0.3rem;
}

/* Les paragraphes d'une bulle : le preflight de Tailwind leur retire toute
   marge, et deux paragraphes se colleraient en un bloc illisible. */
.bulle p + p { margin-top: 0.6rem; }
.bulle ul, .bulle ol { margin: 0.5rem 0 0.5rem 1.25rem; list-style: revert; }
.bulle code { background-color: rgb(var(--ink-rgb) / .07); padding: 0.1em 0.3em; border-radius: 4px; }
.bulle pre { background-color: rgb(var(--ink-rgb) / .07); padding: 0.7rem; border-radius: 8px; overflow-x: auto; margin: 0.5rem 0; }
.bulle pre code { background: none; padding: 0; }

/* Trois points qui respirent pendant que le modèle rédige. Sous
   « prefers-reduced-motion », ils restent immobiles : l'information (« ça
   travaille ») est portée par leur présence, pas par leur mouvement. */
.points span {
  display: inline-block;
  width: 0.4rem; height: 0.4rem;
  margin-right: 0.2rem;
  border-radius: 999px;
  background-color: var(--accent);
  animation: respirer 1.2s infinite ease-in-out;
}
.points span:nth-child(2) { animation-delay: .15s; }
.points span:nth-child(3) { animation-delay: .3s; }
@keyframes respirer {
  0%, 60%, 100% { opacity: .3; transform: translateY(0); }
  30% { opacity: 1; transform: translateY(-3px); }
}
@media (prefers-reduced-motion: reduce) {
  .points span { animation: none; opacity: .6; }
  .progression > span { transition: none; }
}

/* --- La séance : une conversation, pas une page qui défile ----------------

   Un élève révise sur un téléphone, et une messagerie n'y défile pas comme un
   article : la saisie tient le bas de l'écran, l'en-tête tient le haut, et
   seules les bulles bougent entre les deux. La page de séance prend donc la
   hauteur exacte de la fenêtre et distribue le reste en colonne.

   Le pied de page n'y est pas (cf. le bloc `pied` de base.html) : il pousserait
   la saisie hors de l'écran, ce qui est exactement ce qu'on veut éviter.

   La hauteur est écrite deux fois. `100vh` est le repli des navigateurs qui ne
   connaissent pas `dvh` ; `100dvh` suit les barres d'outils qui se rétractent
   au défilement sur mobile, là où `100vh` reste figé sur la fenêtre la plus
   grande et fait passer la saisie sous la barre d'adresse. Le clavier natif,
   lui, n'entre dans aucune des deux unités : c'est `seance.js` qui recale la
   hauteur sur la fenêtre visuelle. */

/* La page elle-même ne défile pas. Sans cela, iOS fait glisser le document
   entier à l'ouverture du clavier et la barre de saisie part avec lui. */
.corps-seance { overflow: hidden; }

.cadre-seance {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  height: 100vh;
  height: 100dvh;
}
/* Rien ne se comprime : ni le bandeau, ni les bandeaux d'alerte. Seule la
   colonne de la conversation prend la place qui reste — et `min-height: 0`
   est ce qui l'autorise à devenir plus courte que son contenu, faute de quoi
   un élément en `overflow: auto` imbriqué ne défile jamais. */
.cadre-seance > *:not(.seance-main) { flex: none; }
.seance-main {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding-top: 0.25rem;
}

/* L'en-tête de séance : matière, titre, notion en cours et jauge. Il n'a pas
   besoin d'être `sticky` ici — dans une colonne à hauteur fixe, il ne bouge
   pas parce que rien ne le pousse. */
.seance-entete {
  padding-bottom: 0.55rem;
  border-bottom: 1px solid var(--border);
}

.fil {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* Le seul élément qui défile de toute la page. `overscroll-behavior: contain`
   empêche le geste de continuer sur le document une fois le fil au bout —
   c'est ce qui déclenche le rechargement par traction sur Android et le
   rebond élastique sur iOS, deux façons de perdre sa place en lisant. */
.fil-defilement {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  padding-block: 0.85rem;
  /* L'ascenseur du fil prend les couleurs du thème. Sans cela il reste blanc
     en mode nuit — le projet ne déclare aucun `color-scheme` —, et une barre
     claire au milieu d'une conversation sombre se voit plus que la
     conversation. Le réglage est posé ici et nulle part ailleurs : c'est le
     seul ascenseur que l'application dessine à l'intérieur d'une page. Sur un
     téléphone il ne se voit pas, l'ascenseur y est flottant. */
  scrollbar-width: thin;
  scrollbar-color: var(--border-strong) transparent;
}
/* `margin-top: auto` colle la conversation au bas tant qu'elle est courte, ce
   qu'un `justify-content: flex-end` ferait aussi — mais celui-ci, posé sur
   l'élément qui défile, met les premières bulles hors d'atteinte dès que la
   conversation dépasse la hauteur disponible. */
.fil-pile {
  margin-top: auto;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

/* La barre de saisie, posée sur le fond de la page pour que les bulles ne
   transparaissent pas derrière elle. La marge basse reprend la zone sûre du
   téléphone, rôle que tenait le pied de page partout ailleurs. */
.composeur {
  border-top: 1px solid var(--border);
  background-color: var(--bg);
  padding-top: 0.6rem;
  padding-bottom: calc(0.6rem + env(safe-area-inset-bottom));
}
.composeur-commandes {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.5rem;
}
/* `font-size: 1rem` n'est pas décoratif : sous 16 px, iOS zoome sur le champ à
   la mise au point et ne dézoome jamais tout à fait. La hauteur est menée par
   `seance.js`, qui la fait grandir avec le texte jusqu'à ce plafond ; passé
   lui, c'est le champ qui défile. */
.composeur-champ textarea {
  resize: none;
  overflow-y: auto;
  max-height: 8rem;
  font-size: 1rem;
}

/* --- Le bandeau du site n'entre pas dans une séance sur téléphone --------

   Une séance est une tâche bornée, comme un tunnel de paiement : ce genre
   d'écran ne porte pas la navigation globale du site, il porte une sortie et
   l'état de la tâche. Sur un téléphone, le bandeau et l'en-tête de séance
   prenaient ensemble un quart de l'écran avant même que le clavier ne monte —
   pour un logo, un menu à deux entrées et un lien « Mes chapitres » que
   l'en-tête de séance donne déjà.

   Il est retiré, **franchement**, et non révélé au défilement. Remonter dans
   une conversation veut dire « je relis », pas « je cherche la navigation » :
   l'élève remonte précisément quand il n'a pas compris, et faire glisser une
   barre par-dessus la conversation à cet instant-là ne rend service à
   personne. S'y ajoute un coût mécanique : toute barre qui apparaît change la
   hauteur de la zone qui défile, donc fait sauter un fil collé en bas.

   Ce qu'on y perd — le thème et « Quitter » — est à un geste, par
   « Mes chapitres », qui reste dans l'en-tête et devient la sortie unique
   (d'où sa flèche). Sur grand écran le bandeau reste : la hauteur n'y manque
   pas, et il situe la page. Rien ne devient inatteignable d'un côté ni de
   l'autre. */
@media (max-width: 39.9375rem) {
  .cadre-seance [data-bandeau] { display: none; }
  /* L'en-tête de séance devient le haut de l'écran : il lui faut la marge que
     le bandeau lui tenait lieu, et la zone sûre d'un téléphone encoché — nulle
     tant que la barre d'état reste opaque, non nulle en paysage. */
  .cadre-seance .seance-main { padding-top: calc(0.5rem + env(safe-area-inset-top)); }
}

/* --- Quand il ne reste presque rien de haut ------------------------------

   Un téléphone en paysage, clavier natif ouvert, laisse parfois deux cents
   pixels : le bandeau, l'en-tête de séance et la barre de saisie en demandent
   trois cents, et c'est le dernier de la colonne — la saisie, avec son bouton
   « Envoyer » — que `overflow: hidden` rogne. La page a l'air normale et ne
   permet plus rien d'envoyer.

   `seance.js` mesure ce qui reste au fil et pose le serrage : d'abord le
   bandeau du site, puis l'en-tête de séance si cela ne suffit pas. La
   conversation et la saisie ne s'effacent jamais, et tout revient dès que le
   clavier se referme.

   Trois sélecteurs de classe et d'attribut : les règles restent hors de portée
   des `.flex` et `.block` de Tailwind quel que soit l'ordre des feuilles, ce
   qui est la convention du projet pour toute règle d'affichage. */
.cadre-seance[data-serrage] [data-bandeau] { display: none; }
.cadre-seance[data-serrage="tout"] .seance-entete { display: none; }

/* Effacer deux blocs ne suffit pas : les marges qui les séparaient restent, et
   elles se comptent en dizaines de pixels sur un écran qui n'en a que deux
   cents. La respiration de la page est le dernier réglage avant de rogner
   quelque chose d'utile. */
.cadre-seance[data-serrage] .seance-main { padding-top: 0; }
.cadre-seance[data-serrage] .fil-defilement { padding-block: 0.4rem; }
.cadre-seance[data-serrage="tout"] .composeur {
  padding-top: 0.35rem;
  padding-bottom: calc(0.35rem + env(safe-area-inset-bottom));
}
.cadre-seance[data-serrage="tout"] .composeur-commandes { margin-top: 0.35rem; }

/* Sur un écran tactile, la touche Entrée va à la ligne et n'envoie rien :
   l'astuce y serait un mensonge. La condition est la même que celle de
   `seance.js` — un pointeur grossier sans survol —, et elle doit le rester :
   deux critères différents finiraient par promettre l'inverse de ce que fait
   le clavier. */
@media (hover: none) and (pointer: coarse) {
  .astuce-clavier { display: none; }
}

/* --- Le texte canonique et son aperçu ------------------------------------ */

/* L'éditeur et l'aperçu se font face et doivent s'aligner : la hauteur est
   écrite une seule fois. */
.hauteur-relecture { height: min(60vh, 34rem); }

.rendu { line-height: 1.65; }
.rendu > * + * { margin-top: 0.85rem; }
.rendu h2 { font-size: 1.3rem; font-weight: 700; margin-top: 1.6rem; }
.rendu h3 { font-size: 1.1rem; font-weight: 700; margin-top: 1.3rem; }
.rendu h4 { font-weight: 700; }
.rendu ul { list-style: disc; margin-left: 1.4rem; }
.rendu ol { list-style: decimal; margin-left: 1.4rem; }
.rendu li + li { margin-top: 0.25rem; }
.rendu code { background-color: rgb(var(--ink-rgb) / .07); padding: 0.1em 0.3em; border-radius: 4px; font-size: 0.9em; }
.rendu pre { background-color: rgb(var(--ink-rgb) / .07); padding: 0.8rem; border-radius: 8px; overflow-x: auto; }
.rendu pre code { background: none; padding: 0; }
.rendu table { border-collapse: collapse; display: block; overflow-x: auto; }
.rendu th, .rendu td { border: 1px solid var(--border); padding: 0.35rem 0.6rem; text-align: left; }
.rendu th { background-color: var(--column); }
/* Une citation, dans un texte de cours, c'est une transcription de figure :
   le filet abricot la désigne comme « à vérifier » sans un mot de plus. */
.rendu blockquote {
  border-left: 3px solid var(--accent);
  background-color: var(--accent-soft);
  padding: 0.6rem 0.9rem;
  border-radius: 0 8px 8px 0;
}
.rendu blockquote > * + * { margin-top: 0.5rem; }
.rendu a { color: var(--accent-ink); text-decoration: underline; }

/* --- Le générateur de mot de passe --------------------------------------- */

/* La barre est posée par `mot-de-passe.js`, sous le champ marqué
   `data-mot-de-passe` : ces deux classes n'apparaissent dans aucun gabarit.
   Sans JavaScript, rien de tout cela n'existe et le champ reste une saisie
   ordinaire — le formulaire fonctionne toujours.

   L'admin Django a sa propre feuille (`admin-mot-de-passe.css`) : la barre y
   emprunte ses boutons, pour ressembler à la page où elle se trouve. */
.barre-mot-de-passe {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.5rem;
}

.aide { font-size: 0.8125rem; color: var(--muted); }
.barre-mot-de-passe .aide { flex: 1 1 12rem; min-width: 0; }

/* Le lien de réinitialisation fait deux cents caractères : il se lit en
   chasse fixe, à taille réduite, et on vient ici pour le sélectionner. */
.champ-lien {
  font-family: var(--font-code);
  font-size: 0.8125rem;
}

/* --- Le voile d'attente -------------------------------------------------- */

/* La génération d'une trame est synchrone : trente secondes pendant lesquelles
   la page ne bouge pas d'un pixel. Le voile dit que quelque chose se passe, et
   il barre l'écran — c'est ce qui empêche le second clic, qui partirait pour
   rien sur un quota compté en requêtes par jour. */
.voile {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  background-color: rgb(var(--bg-rgb) / .84);
  backdrop-filter: blur(3px);
  animation: voile-apparait .2s ease-out;
}

.voile-carte {
  width: min(27rem, 100%);
  padding: 2rem 1.75rem 1.5rem;
  text-align: center;
  box-shadow: var(--shadow-forte);
}

/* L'anneau tourne, le halo respire : deux mouvements de vitesses différentes,
   sans quoi l'ensemble ressemble à une image figée mal chargée. */
.anneau {
  position: relative;
  width: 3.25rem;
  height: 3.25rem;
  margin: 0 auto 1.2rem;
  border-radius: 999px;
  border: 3px solid var(--border);
  border-top-color: var(--accent);
  animation: tourner .9s linear infinite;
}
.anneau::after {
  content: "";
  position: absolute;
  inset: -0.7rem;
  border-radius: 999px;
  background: radial-gradient(closest-side, rgb(var(--accent-rgb) / .22), transparent);
  animation: respirer-halo 2.2s ease-in-out infinite;
}

/* Les phrases s'échangent sur place : une hauteur réservée évite que la carte
   ne saute d'une ligne à chaque changement d'étape. */
.etapes { min-height: 3rem; }
.etape { display: none; font-weight: 600; }
.etape-visible { display: block; animation: etape-entre .45s ease-out; }

.ruban {
  height: 0.35rem;
  border-radius: 999px;
  background-color: var(--column);
  overflow: hidden;
  margin: 1rem 0 0.85rem;
}
.ruban > span {
  display: block;
  height: 100%;
  width: 45%;
  border-radius: 999px;
  background: linear-gradient(
    90deg, rgb(var(--accent-rgb) / 0), var(--accent), rgb(var(--accent-rgb) / 0)
  );
  animation: glisser 1.8s ease-in-out infinite;
}

@keyframes voile-apparait { from { opacity: 0; } to { opacity: 1; } }
@keyframes tourner { to { transform: rotate(360deg); } }
@keyframes respirer-halo {
  0%, 100% { opacity: .45; transform: scale(.92); }
  50% { opacity: 1; transform: scale(1.06); }
}
@keyframes glisser {
  0% { transform: translateX(-110%); }
  100% { transform: translateX(240%); }
}
@keyframes etape-entre {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: none; }
}

/* Sans mouvement, l'information « ça travaille » doit rester lisible : elle
   passe alors par la présence des éléments, pas par leur animation. */
@media (prefers-reduced-motion: reduce) {
  .voile, .etape-visible, .anneau, .anneau::after { animation: none; }
  .ruban > span { animation: none; width: 100%; opacity: .55; }
}

/* --- La poignée de réordonnancement de la trame -------------------------- */

/* Posée par `trame.js`, jamais par un gabarit : sans JavaScript il n'y a rien
   à glisser. Le curseur est le seul indice qu'une poignée se prend — d'où
   `grab` au survol et `grabbing` pendant le geste. */
.poignee {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.1rem;
  height: 1.6rem;
  color: var(--muted);
  cursor: grab;
  /* Le geste appartient au glisser-déposer : sans cela, un appui prolongé
     sélectionne les six points au lieu de prendre la ligne. */
  user-select: none;
  touch-action: none;
}
.poignee:hover { color: var(--ink); }
.poignee:active { cursor: grabbing; }
.poignee svg { fill: currentColor; }

/* La ligne prise s'efface et se teinte : elle reste visible — c'est elle qu'on
   déplace — mais cesse de se confondre avec les lignes en place. */
tr.ligne-saisie { opacity: .55; }
tr.ligne-saisie td { background-color: var(--accent-soft); }

/* --- Divers -------------------------------------------------------------- */

.messages { list-style: none; margin: 0; padding: 0; }
.message {
  border-radius: var(--radius);
  padding: 0.7rem 1rem;
  border: 1px solid var(--border);
  background-color: var(--panel);
}
.message.success { background-color: var(--success-soft); border-color: var(--success); color: var(--success-ink); }
.message.error { background-color: var(--danger-soft); border-color: var(--danger); color: var(--danger-ink); }
.message.warning { background-color: var(--warning-soft); border-color: var(--warning); color: var(--warning-ink); }

/* htmx pose « htmx-request » sur l'élément indicateur pendant la requête. */
.indicateur { display: none; }
.htmx-request .indicateur, .htmx-request.indicateur { display: inline-flex; }

/* --- Le bandeau replié ---------------------------------------------------

   Quand la barre n'a plus la place d'aligner ses commandes, `menu.js` les
   range dans un panneau ouvert par un bouton à trois points. Le script ne
   fait que poser des classes et des attributs : tout ce qui se voit est
   décidé ici.

   Chaque règle d'affichage porte **deux** sélecteurs de classe, jamais un.
   Tailwind pose « display » avec des classes simples (« .flex »,
   « .inline-flex ») : à égalité de spécificité, c'est l'ordre des feuilles
   qui tranche — style.css l'emporte en production, où il est chargé après
   tailwind.css, mais le CDN de développement injecte sa feuille à l'exécution,
   donc après. Une règle à une seule classe se comporterait autrement en
   développement et en production, ce qui est précisément le genre d'écart
   qu'on ne voit qu'une fois en ligne. */

.nav-item.bandeau-bouton { display: none; }
.nav-item.bandeau-bouton[data-visible] { display: inline-flex; }

.bandeau-nav.bandeau-nav-repliee {
  display: none;
  position: absolute;
  top: 100%;
  right: 1rem;          /* aligné sur le padding du « shell » */
  z-index: 40;
  margin-top: 0.35rem;
  min-width: 12rem;
  flex-direction: column;
  align-items: stretch;
  gap: 0.1rem;
  padding: 0.4rem;
  background-color: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-large);
  box-shadow: var(--shadow-forte);
}
.bandeau-nav.bandeau-nav-repliee[data-ouvert] { display: flex; }

/* Dans le panneau, chaque commande prend une ligne entière : une cible large
   comme son seul libellé se rate au pouce. */
.bandeau-nav-repliee .nav-item { display: block; padding: 0.5rem 0.7rem; }

/* Le pseudonyme n'est pas une commande : il coiffe le panneau et s'en sépare
   par un filet, sinon il se lit comme un lien qui ne réagit pas. */
.bandeau-nav-repliee .identite {
  padding: 0.35rem 0.7rem 0.5rem;
  border-bottom: 1px solid var(--border);
  margin-bottom: 0.2rem;
}

.bandeau-nav-repliee .bascule-theme {
  width: auto;
  height: auto;
  justify-content: flex-start;
  gap: 0.5rem;
  padding: 0.5rem 0.7rem;
  opacity: 1;
}

.etiquette-repli { display: none; }
.bandeau-nav-repliee .etiquette-repli { display: inline; }
