/* ==========================================================================
   UPJV L2 — feuille de style unique
   Identité « Nocturne d'étude » — v2
   Aucune classe renommée, aucune classe retirée. Uniquement de l'apparence.
   ========================================================================== */

/* --- Polices --------------------------------------------------------------
   Déposer les fichiers dans fonts/ (voir la note de livraison pour les liens).
   Si les fichiers manquent, la pile de secours prend le relais sans casse. */
@font-face {
  font-family: "Newsreader"; font-style: normal; font-weight: 300 700;
  font-display: swap; src: url("../fonts/Newsreader.woff2") format("woff2-variations");
}
@font-face {
  font-family: "Newsreader"; font-style: italic; font-weight: 300 700;
  font-display: swap; src: url("../fonts/Newsreader-Italic.woff2") format("woff2-variations");
}
/* Geist remplace Inter pour tout le texte courant. Inter est devenu la police
   par défaut de la moitié du web : elle ne se remarque plus, ce qui est une
   qualité pour une interface et une fadeur pour une identité. Geist est plus
   géométrique, plus large d'œil, et se lit comme quelque chose de récent.

   Deux fichiers plutôt qu'un, chacun avec sa plage de caractères : le second
   ne se télécharge que si une page contient vraiment un caractère rare. Les
   accents français, les œ et les æ sont tous dans le premier. */
@font-face {
  font-family: "Geist"; font-style: normal; font-weight: 100 900;
  font-display: swap; src: url("../fonts/Geist-latin.woff2") format("woff2-variations");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
                 U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
                 U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Geist"; font-style: normal; font-weight: 100 900;
  font-display: swap; src: url("../fonts/Geist-latin-ext.woff2") format("woff2-variations");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                 U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                 U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "JetBrains Mono"; font-style: normal; font-weight: 100 800;
  font-display: swap; src: url("../fonts/JetBrainsMono.woff2") format("woff2-variations");
}

/* --- Thème clair ---------------------------------------------------------- */
:root {
  --bg: #ffffff;
  --bg-elev: #ffffff;
  --bg-sunk: #f2f2f2;
  --text: #000000;
  --text-soft: #4d4d4d;
  --text-faint: #6e6e6e;
  --border: #d6d6d6;
  --border-strong: #909090;
  --accent: #000000;
  --accent-soft: #ececec;
  --accent-text: #000000;
  --good: #16704e;
  --good-soft: #e2f2ea;
  --warn: #8a5a0a;
  --warn-soft: #f9eeda;
  --bad: #b02a41;
  --bad-soft: #fbe8eb;

  /* Le texte posé SUR un aplat plein (--good, --bad, --accent). En clair les
     aplats sont sombres, il faut donc du blanc ; en sombre ce sont des pastels
     et le blanc devient illisible — d'où un jeton plutôt qu'un #fff en dur. */
  --sur-plein: #ffffff;

  --shadow: 0 1px 2px rgba(0, 0, 0, .05), 0 8px 24px -12px rgba(0, 0, 0, .18);
  --shadow-lg: 0 2px 4px rgba(0, 0, 0, .06), 0 26px 52px -20px rgba(0, 0, 0, .30);
  --lueur: 0 0 0 1px color-mix(in srgb, var(--accent) 26%, transparent),
           0 14px 40px -18px color-mix(in srgb, var(--accent) 60%, transparent);

  --radius: 8px;
  --radius-sm: 6px;
  --maxw: 1120px;

  --font: "Geist", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-titre: "Newsreader", "Source Serif 4", Georgia, "Times New Roman", serif;
  --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* Courbes et durées : un seul vocabulaire pour tout le site. */
  --t-vif: 120ms;
  --t-court: 180ms;
  --t-moyen: 320ms;
  --t-long: 560ms;
  --e-sortie: cubic-bezier(.16, .84, .44, 1);   /* entre vite, s'installe */
  --e-doux:  cubic-bezier(.4, 0, .2, 1);        /* transitions d'état */
  --e-elan:  cubic-bezier(.2, .9, .25, 1.06);   /* léger dépassement */
}

/* --- Thème sombre --------------------------------------------------------- */
:root[data-theme="dark"] {
  /* Pas #000000 pile : au noir absolu, le texte clair fait un halo et les
     cartes (--bg-elev) n'ont plus aucune teinte pour se détacher du fond,
     seule l'ombre les sépare -- et une ombre se voit mal sur du noir pur. */
  --bg: #060606;
  --bg-elev: #121212;
  --bg-sunk: #1a1a1a;
  --text: #ffffff;
  --text-soft: #b3b3b3;
  --text-faint: #828282;
  --border: #333333;
  --border-strong: #616161;
  --accent: #ffffff;
  --accent-soft: #262626;
  --accent-text: #ffffff;
  --good: #63cb9c;
  --good-soft: #13291f;
  --warn: #e3ab63;
  --warn-soft: #2d2216;
  --bad: #f5828f;
  --bad-soft: #2f171c;
  --sur-plein: #000000;

  --shadow: 0 1px 2px rgba(0, 0, 0, .45), 0 8px 24px -12px rgba(0, 0, 0, .7);
  --shadow-lg: 0 2px 6px rgba(0, 0, 0, .55), 0 26px 52px -20px rgba(0, 0, 0, .8);
  --lueur: 0 0 0 1px color-mix(in srgb, var(--accent) 32%, transparent),
           0 14px 44px -16px color-mix(in srgb, var(--accent) 45%, transparent);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #060606;
    --bg-elev: #121212;
    --bg-sunk: #1a1a1a;
    --text: #ffffff;
    --text-soft: #b3b3b3;
    --text-faint: #828282;
    --border: #333333;
    --border-strong: #616161;
    --accent: #ffffff;
    --accent-soft: #262626;
    --accent-text: #ffffff;
    --good: #63cb9c;
    --good-soft: #13291f;
    --warn: #e3ab63;
    --warn-soft: #2d2216;
    --bad: #f5828f;
    --bad-soft: #2f171c;
    --sur-plein: #000000;
    --shadow: 0 1px 2px rgba(0, 0, 0, .45), 0 8px 24px -12px rgba(0, 0, 0, .7);
    --shadow-lg: 0 2px 6px rgba(0, 0, 0, .55), 0 26px 52px -20px rgba(0, 0, 0, .8);
    --lueur: 0 0 0 1px color-mix(in srgb, var(--accent) 32%, transparent),
             0 14px 44px -16px color-mix(in srgb, var(--accent) 45%, transparent);
  }
}

/* --- Propriétés animables --------------------------------------------------
   Enregistrées auprès du moteur : le navigateur peut les interpoler, ce qui
   permet d'animer un angle, une position de dégradé ou une longueur de tracé
   sans une ligne de JavaScript. */
@property --angle { syntax: "<angle>"; inherits: false; initial-value: 0deg; }
@property --avance { syntax: "<percentage>"; inherits: false; initial-value: 0%; }
@property --halo { syntax: "<length>"; inherits: false; initial-value: 0px; }

/* --- Base ----------------------------------------------------------------- */
* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: var(--font);
  background: var(--bg);
  color: var(--text);
  font-size: 16px;
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
  font-synthesis-weight: none;
}

/* Un grain fin casse l'aplat en mode sombre -- sans lui, un dégradé sur fond
   quasi noir bande visiblement, et la page entière lit comme un seul bloc
   trop uniforme. Bruit vectoriel (feTurbulence), pas une image : léger à
   charger, net à toute résolution, carrelé en tuile de 180 px. */
:root[data-theme="dark"] body {
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)' opacity='0.05'/%3E%3C/svg%3E");
  background-repeat: repeat;
  background-size: 180px 180px;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) body {
    background-image:
      url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)' opacity='0.05'/%3E%3C/svg%3E");
    background-repeat: repeat;
    background-size: 180px 180px;
  }
}

a { color: var(--accent-text); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: .18em; }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }

h1, h2, h3, h4 {
  font-family: var(--font-titre);
  line-height: 1.18;
  margin: 0 0 .5em;
  font-weight: 560;
  letter-spacing: -.012em;
}
h1 { font-size: 2rem; letter-spacing: -.022em; }
h2 { font-size: 1.34rem; }
h3 { font-size: 1.1rem; }

p { margin: 0 0 1em; text-wrap: pretty; }

button, input, select, textarea { font: inherit; color: inherit; }

::selection { background: color-mix(in srgb, var(--accent) 24%, transparent); }

.wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 20px; }
.narrow { max-width: 760px; }

/* --- Barre de navigation -------------------------------------------------- */
.topbar {
  position: sticky; top: 0; z-index: 50;
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  backdrop-filter: saturate(150%) blur(14px);
  border-bottom: 1px solid var(--border);
}
/* La ligne du bas est la barre d'avancement de la page : elle suit le
   défilement, sans script, sans compteur, sans à-coup. */
.topbar::after {
  content: ""; position: absolute; left: 0; bottom: -1px; height: 2px; width: 100%;
  background: linear-gradient(90deg, var(--accent), color-mix(in srgb, var(--accent) 35%, transparent));
  transform-origin: left; transform: scaleX(0);
  pointer-events: none;
}
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    .topbar::after {
      animation: avancee-page linear both;
      animation-timeline: scroll(root block);
    }
  }
}
@keyframes avancee-page { from { transform: scaleX(0); } to { transform: scaleX(1); } }
/* La barre occupe toute la largeur : la marque tient le bord gauche, la
   navigation et les actions tiennent le bord droit. Rien ne flotte au centre. */
.topbar-inner {
  max-width: none; margin: 0; padding: 0 14px 0 8px;
  display: flex; align-items: center; gap: 24px; min-height: 60px;
}
/* Le bloc de marque colle au bord : c'est le point d'ancrage de la page. */
.brand {
  display: flex; align-items: center; gap: 14px;
  margin-left: 0;
  font-family: var(--font);
  font-weight: 600; font-size: .96rem;
  color: var(--text); letter-spacing: -.03em;
  line-height: 1.12;
}
.brand:hover { text-decoration: none; }
.brand-mark {
  width: 30px; height: 30px; border-radius: 9px;
  display: grid; place-items: center; flex: none;
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--accent-text);
  border: 1px solid color-mix(in srgb, var(--accent) 34%, transparent);
  transition: background var(--t-court) var(--e-doux), transform var(--t-court) var(--e-elan);
}
.brand-mark svg { width: 17px; height: 17px; display: block; }
.brand:hover .brand-mark {
  background: color-mix(in srgb, var(--accent) 20%, transparent);
  transform: rotate(-4deg);
}
.brand small {
  display: block; font-family: var(--mono); font-size: 9.5px; font-weight: 500;
  color: var(--text-faint); letter-spacing: .16em; text-transform: uppercase;
  margin-top: 2px;
}

.nav { display: flex; gap: 2px; margin-left: auto; overflow-x: auto; scrollbar-width: none; }
.nav::-webkit-scrollbar { display: none; }
.nav a {
  position: relative;
  padding: 8px 13px 9px; border-radius: var(--radius-sm); color: var(--text-soft);
  font-size: 14px; font-weight: 500; white-space: nowrap;
  transition: color var(--t-court) var(--e-doux), background var(--t-court) var(--e-doux);
}
.nav a::after {
  content: ""; position: absolute; left: 13px; right: 13px; bottom: 2px; height: 1.5px;
  background: var(--accent); border-radius: 2px;
  transform: scaleX(0); transform-origin: left;
  transition: transform var(--t-court) var(--e-sortie);
}
.nav a:hover { background: var(--bg-sunk); color: var(--text); text-decoration: none;
  text-shadow: 0 0 18px color-mix(in srgb, var(--accent) 40%, transparent); }
.nav a:hover::after { transform: scaleX(1); }
.nav a.is-active { background: transparent; color: var(--accent-text); font-weight: 600; }
.nav a.is-active::after { transform: scaleX(1); height: 2px; }
/* Chaque onglet porte son nom long et son nom court ; le média choisit. */
.nav-court { display: none; }

.topbar-actions { display: flex; gap: 6px; flex: none; }

.icon-btn {
  width: 34px; height: 34px; border-radius: var(--radius-sm); border: 1px solid var(--border);
  background: transparent; cursor: pointer; display: grid; place-items: center;
  font-size: 15px; color: var(--text-soft);
  transition: background var(--t-court) var(--e-doux), border-color var(--t-court) var(--e-doux),
              color var(--t-court) var(--e-doux), transform var(--t-vif) var(--e-doux);
}
.icon-btn:hover { background: var(--bg-sunk); border-color: var(--border-strong); color: var(--text); }
.icon-btn:active { transform: scale(.94); }

/* --- Mise en page --------------------------------------------------------- */
main { padding: 34px 0 96px; }

.page-head { margin-bottom: 28px; }
.page-head h1 { margin-bottom: 6px; }
.page-head .sub { color: var(--text-soft); margin: 0; max-width: 62ch; }

.eyebrow {
  font-family: var(--mono);
  font-size: 11px; font-weight: 500; letter-spacing: .12em; text-transform: uppercase;
  color: var(--text-faint); margin-bottom: 10px;
}

.grid { display: grid; gap: 14px; }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.grid-4 { grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }

.row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.spacer { flex: 1; }
.stack { display: flex; flex-direction: column; gap: 12px; }

/* ==========================================================================
   Apparition au défilement — pilotée par la position dans la fenêtre, pas par
   une minuterie. Le bloc arrive au rythme du poignet : on remonte, il repart.

   Le geste : le bloc monte et se remet au net. Volontairement PAS de clip-path
   ici — un `clip-path` en fill-mode both resterait appliqué après l'animation
   et couperait les ombres portées (et le coin coupé de l'encadré Exemple).
   L'opacité, la montée et le flou suffisent à donner l'impression d'émergence.

   Aucun JavaScript, aucun observateur, et rien ne peut rester coincé invisible
   (fill both + plage « entry » : au-dessus de la ligne de flottaison, l'état
   final est déjà appliqué au chargement).
   ========================================================================== */
@keyframes entree-vue {
  from {
    opacity: 0;
    transform: translateY(38px) scale(.97);
  }
  55% { opacity: 1; }
  to {
    opacity: 1;
    transform: none;
  }
}
/* Les titres de section et les filets ne se dévoilent pas : ils se tracent
   depuis la gauche. Deux gestes distincts, donc une hiérarchie lisible.
   Ces éléments n'ont ni ombre ni forme propre : le clip-path y est sans risque. */
@keyframes entree-trace {
  from { opacity: 0; transform: translateX(-22px); clip-path: inset(0 100% 0 0); }
  to   { opacity: 1; transform: none; clip-path: inset(0 -10% 0 0); }
}
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .card, .stat, .encadre, .matiere, .chapter-item, .file-item,
    .schema, .appel, .flashcard {
      animation: entree-vue linear both;
      animation-timeline: view();
      animation-range: entry 2% entry 48%;
    }
    .section-title, .ue-entete, .exo-titre, .partie-titre {
      animation: entree-trace linear both;
      animation-timeline: view();
      animation-range: entry 4% entry 42%;
    }
    /* Décalage naturel dans une grille : chaque colonne entre légèrement
       après la précédente, sans délai codé en dur. */
    .grid > *:nth-child(2n),  .matieres > *:nth-child(2n),
    .chapter-list > *:nth-child(2n), .file-list > *:nth-child(2n) { animation-range: entry 7% entry 53%; }
    .grid > *:nth-child(3n),  .matieres > *:nth-child(3n),
    .chapter-list > *:nth-child(3n), .file-list > *:nth-child(3n) { animation-range: entry 12% entry 58%; }

    /* La flashcard garde son retournement propre : le défilement ne doit pas
       lui voler son geste. */
    .review-stage .flashcard { animation-range: entry 0% entry 24%; }
  }
}

/* --- Cartes --------------------------------------------------------------- */
.card {
  position: relative;
  background: var(--bg-elev); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 20px; box-shadow: var(--shadow);
}
.card-tight { padding: 14px; }

/* Filets qui s'effacent aux extrémités — la signature du système. */
.section-title {
  display: flex; align-items: baseline; gap: 12px; margin: 42px 0 16px;
  position: relative; padding-bottom: 12px;
}
.section-title::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: linear-gradient(90deg, transparent, var(--border) 48px,
              var(--border) calc(100% - 48px), transparent);
}
.section-title h2 { margin: 0; }
.section-title .count {
  font-family: var(--mono); color: var(--text-faint); font-size: 12px;
  font-variant-numeric: tabular-nums;
}

/* --- Titre de semestre ----------------------------------------------------
   Le niveau au-dessus des matières : c'est le semestre qui range le site, une
   matière n'est qu'une subdivision. Il lui faut donc un poids visuel supérieur
   à .section-title, sans devenir un second titre de page. */
.semestre-titre {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 10px 14px;
  margin: 54px 0 4px; padding-bottom: 10px;
  border-bottom: 2px solid var(--accent);
}
.semestre-titre:first-child { margin-top: 8px; }
.semestre-titre h2 {
  margin: 0; font-size: 1.62rem; letter-spacing: -.028em;
}
.semestre-titre .count {
  font-family: var(--mono); color: var(--text-faint); font-size: 12px;
  font-variant-numeric: tabular-nums;
}
.semestre-titre .semestre-matieres {
  margin-left: auto; font-family: var(--mono); font-size: 11px;
  letter-spacing: .08em; text-transform: uppercase; color: var(--text-faint);
}
/* Juste sous un titre de semestre, la matière se resserre : les deux barres
   à quelques pixels l'une de l'autre feraient un double trait disgracieux. */
.semestre-titre + .section-title { margin-top: 22px; }

@media (max-width: 720px) {
  .semestre-titre { margin-top: 38px; }
  .semestre-titre h2 { font-size: 1.34rem; }
  .semestre-titre .semestre-matieres { margin-left: 0; width: 100%; }
}

/* --- Statistiques --------------------------------------------------------- */
.stat {
  background: var(--bg-elev); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 16px 18px; box-shadow: var(--shadow);
}
.stat .label {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--text-faint); font-weight: 500;
}
.stat .value {
  font-family: var(--font-titre); font-size: 2.15rem; font-weight: 500;
  letter-spacing: -.03em; line-height: 1.05; margin-top: 6px;
  font-variant-numeric: tabular-nums;
}
.stat .hint { font-size: 12.5px; color: var(--text-faint); margin-top: 2px; }
.stat.accent .value { color: var(--accent-text); }
.stat.good .value { color: var(--good); }
.stat.warn .value { color: var(--warn); }

/* --- Boutons -------------------------------------------------------------- */
.btn {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 9px 16px; border-radius: var(--radius-sm); border: 1px solid var(--border-strong);
  background: transparent; color: var(--text); font-size: 14px; font-weight: 520;
  cursor: pointer; text-decoration: none;
  transition: background var(--t-court) var(--e-doux), border-color var(--t-court) var(--e-doux),
              color var(--t-court) var(--e-doux), transform var(--t-vif) var(--e-doux),
              box-shadow var(--t-court) var(--e-doux);
}
.btn:hover { background: var(--bg-sunk); border-color: var(--text-faint); text-decoration: none; }
.btn:active { transform: translateY(1px) scale(.985); }
.btn:disabled { opacity: .45; cursor: not-allowed; }

/* Action principale : contour d'accent, pas d'aplat. Au survol, une lumière
   balaie le contour — 900 ms, une seule traversée, puis plus rien. */
.btn-primary {
  background: transparent; border-color: var(--accent); color: var(--accent-text);
  font-weight: 600; overflow: hidden;
}
.btn-primary::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(100deg, transparent 40%,
              color-mix(in srgb, var(--accent) 30%, transparent) 50%, transparent 60%);
  transform: translateX(-120%);
  transition: transform 900ms var(--e-doux);
}
.btn-primary:hover::before { transform: translateX(120%); }
.btn-primary:hover {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border-color: var(--accent); color: var(--accent-text);
  box-shadow: var(--lueur);
}
.btn-ghost { border-color: transparent; background: transparent; color: var(--text-soft); }
.btn-ghost:hover { background: var(--bg-sunk); color: var(--text); }
.btn-sm { padding: 6px 11px; font-size: 13px; }
.btn-lg { padding: 12px 24px; font-size: 15px; }
.btn-danger { color: var(--bad); border-color: color-mix(in srgb, var(--bad) 45%, transparent); }
.btn-danger:hover { background: var(--bad-soft); border-color: var(--bad); }

/* --- Étiquettes ----------------------------------------------------------- */
.tag {
  display: inline-flex; align-items: center; gap: 5px; padding: 3px 9px;
  border-radius: 999px; font-size: 11.5px; font-weight: 550; letter-spacing: .01em;
  background: var(--bg-sunk); color: var(--text-soft);
  border: 1px solid transparent;
}
.tag-accent { background: var(--accent-soft); color: var(--accent-text);
  border-color: color-mix(in srgb, var(--accent) 28%, transparent); }
.tag-good { background: var(--good-soft); color: var(--good);
  border-color: color-mix(in srgb, var(--good) 30%, transparent); }
.tag-warn { background: var(--warn-soft); color: var(--warn);
  border-color: color-mix(in srgb, var(--warn) 30%, transparent); }
.tag-bad { background: var(--bad-soft); color: var(--bad);
  border-color: color-mix(in srgb, var(--bad) 30%, transparent); }

.dot { width: 9px; height: 9px; border-radius: 50%; flex: none; }

/* --- Matières ------------------------------------------------------------- */
.subject-card {
  display: block; background: var(--bg-elev); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 20px; box-shadow: var(--shadow); color: var(--text);
  position: relative; overflow: hidden;
  transition: transform var(--t-court) var(--e-sortie), box-shadow var(--t-court) var(--e-doux),
              border-color var(--t-court) var(--e-doux);
}
.subject-card:hover {
  text-decoration: none; transform: translateY(-3px);
  box-shadow: var(--shadow-lg); border-color: var(--border-strong);
}
.subject-card::before {
  content: ""; position: absolute; inset: 0 auto 0 0; width: 3px;
  background: var(--border-strong);
  transition: width var(--t-court) var(--e-sortie), background var(--t-court) var(--e-doux);
}
.subject-card:hover::before { background: var(--accent); }
.subject-card:hover::before { width: 5px; }
.subject-card .emoji { font-size: 24px; }
.subject-card h3 { margin: 10px 0 2px; }
.subject-card .meta { color: var(--text-soft); font-size: 13px; }

/* Barre de progression : remplissage 640 ms, puis un souffle lumineux qui
   parcourt la portion acquise toutes les 3,2 s. On sait où on en est même
   du coin de l'œil. */
.progress {
  height: 6px; border-radius: 99px; background: var(--bg-sunk);
  overflow: hidden; margin-top: 14px; position: relative;
  border: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
}
.progress > i {
  display: block; height: 100%; border-radius: 99px; position: relative;
  overflow: hidden;
  background: var(--accent);
  transition: width 640ms var(--e-sortie);
  box-shadow: 0 0 12px -2px color-mix(in srgb, var(--accent) 60%, transparent);
}
.progress > i::after {
  content: ""; position: absolute; inset: 0; border-radius: 99px;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .6) 50%, transparent);
  transform: translateX(-120%);
  animation: reflet-barre 3.2s var(--e-doux) .3s infinite;
}
@keyframes reflet-barre {
  0%       { transform: translateX(-120%); }
  36%, 100% { transform: translateX(220%); }
}

/* --- Listes de chapitres -------------------------------------------------- */
.chapter-list { display: flex; flex-direction: column; gap: 6px; }
.chapter-item {
  display: flex; align-items: center; gap: 14px; padding: 13px 16px;
  background: var(--bg-elev); border: 1px solid var(--border); border-radius: var(--radius-sm);
  color: var(--text); position: relative;
  transition: border-color var(--t-court) var(--e-doux), background var(--t-court) var(--e-doux),
              transform var(--t-court) var(--e-sortie);
}
.chapter-item::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 10%, transparent), transparent 55%);
  opacity: 0; transition: opacity var(--t-court) var(--e-doux);
}
.chapter-item:hover {
  text-decoration: none; border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  background: var(--bg-elev); transform: translateX(5px);
}
.chapter-item:hover::after { opacity: 1; }
.chapter-item .num {
  width: 28px; height: 28px; border-radius: var(--radius-sm); flex: none;
  display: grid; place-items: center;
  background: var(--bg-sunk); font-family: var(--mono); font-size: 12px; font-weight: 500;
  color: var(--text-soft);
  transition: background var(--t-court) var(--e-doux), color var(--t-court) var(--e-doux);
}
.chapter-item:hover .num { background: var(--accent-soft); color: var(--accent-text); }
.chapter-item .body { min-width: 0; flex: 1; }
/* Ces deux éléments peuvent être des <span> dans le balisage généré : on force
   la boîte en bloc, sans quoi ils se collent l'un à l'autre et l'ellipse ne
   s'applique pas. */
.chapter-item .title { display: block; font-weight: 550; }
.chapter-item .sub {
  display: block; font-size: 12.5px; color: var(--text-faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* --- Prose (fiches de cours) ----------------------------------------------
   Le corps est en Inter ; les titres sont en Newsreader, qui partage l'ossature
   sérif de KaTeX. Les formules ne cohabitent plus par accident. */
.prose { font-size: 16px; line-height: 1.72; max-width: 72ch; }
.prose > *:first-child { margin-top: 0; }
.prose h2 {
  font-size: 1.3rem; margin: 2em 0 .6em; padding-bottom: .35em; position: relative;
}
.prose h2::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: linear-gradient(90deg, var(--border-strong), var(--border) 60%, transparent);
}
.prose h3 { font-size: 1.08rem; margin: 1.7em 0 .5em; color: var(--text); }
.prose ul, .prose ol { margin: 0 0 1.1em; padding-left: 1.35em; }
.prose li { margin-bottom: .38em; }
.prose li::marker { color: var(--accent); font-family: var(--mono); font-size: .85em; }
.prose strong { font-weight: 620; color: var(--text); }
.prose code {
  font-family: var(--mono); font-size: .86em; background: var(--bg-sunk);
  padding: .14em .38em; border-radius: 4px; border: 1px solid var(--border);
}
.prose blockquote {
  margin: 1.3em 0; padding: 4px 0 4px 20px; border-left: 2px solid var(--accent);
  background: transparent; border-radius: 0;
  color: var(--text); font-family: var(--font-titre); font-size: 1.06em; line-height: 1.55;
}
.prose blockquote p:last-child { margin-bottom: 0; }
.prose hr { border: 0; height: 1px; margin: 2.4em 0;
  background: linear-gradient(90deg, transparent, var(--border) 48px,
              var(--border) calc(100% - 48px), transparent); }

/* Formules : une plaque sobre, laissée à KaTeX. */
.formula {
  display: block; margin: 1.25em 0; padding: 16px 18px;
  background: color-mix(in srgb, var(--bg-sunk) 60%, transparent);
  border: 1px solid var(--border); border-left: 2px solid color-mix(in srgb, var(--accent) 55%, transparent);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  overflow-x: auto; overflow-y: hidden;
}
.formula .katex-display { margin: 0; }
.formula .katex { font-size: 1.1em; }
.tex .katex { color: var(--text); }
.tex-error {
  font-family: var(--mono); font-size: 13px; color: var(--bad);
  background: var(--bad-soft); padding: 2px 6px; border-radius: 4px;
}
.katex { color: inherit; }

/* --- Schémas -------------------------------------------------------------- */
.schema {
  margin: 1.8em 0; padding: 20px 16px; text-align: center;
  background: var(--bg-elev); border: 1px solid var(--border); border-radius: var(--radius);
  overflow-x: auto;
}
.schema svg { display: block; width: 100%; max-width: 620px; height: auto; margin: 0 auto; }
.schema figcaption {
  margin-top: 14px; font-size: 13px; color: var(--text-soft); text-align: center;
  font-family: var(--font-titre);
}
.schema svg { --s-trait: var(--text-soft); --s-fort: var(--accent); --s-fond: var(--bg-sunk);
              --s-bon: var(--good); --s-mauvais: var(--bad); --s-texte: var(--text); }
.schema .s-trait { stroke: var(--text-soft); }
.schema .s-fort { stroke: var(--accent); }
.schema .s-texte { fill: var(--text); }
.schema .s-doux { fill: var(--text-soft); }

/* Lecteur audio intégré à une fiche — voir :::audio dans app.js (contenu) et
   App.ameliorerAudio (comportement). .audio-bloc n'est qu'un cadre pour la
   légende : la carte visible est .audio-player, habillée à la main plutôt
   que les commandes natives du navigateur (peu personnalisables, et très
   différentes d'un navigateur à l'autre). */
.audio-bloc { margin: 1.8em 0; }
.audio-bloc figcaption {
  margin-top: 10px; font-size: 13px; color: var(--text-soft); text-align: center;
  font-family: var(--font-titre);
}

.audio-player {
  display: flex; align-items: center; gap: 16px;
  padding: 16px 20px;
  background: var(--bg-elev); border: 1px solid var(--border); border-radius: var(--radius);
}
.ap-jouer {
  flex: none; width: 46px; height: 46px; border-radius: 50%; border: none;
  background: var(--accent); color: var(--sur-plein); font-size: 15px; line-height: 1;
  display: grid; place-items: center; cursor: pointer;
  transition: opacity var(--t-court) var(--e-doux), transform var(--t-vif) var(--e-doux);
}
.ap-jouer:hover { opacity: .92; }
.ap-jouer:active { transform: scale(.92); }
/* Le glyphe pause (deux barres) n'a pas d'équivalent fiable en un seul
   caractère toutes plateformes — deux barres dessinées plutôt qu'un
   caractère ⏸ qui rend en emoji coloré sur certains systèmes. */
.ap-jouer.is-en-lecture { font-size: 0; position: relative; }
.ap-jouer.is-en-lecture::before,
.ap-jouer.is-en-lecture::after {
  content: ""; display: block; width: 4px; height: 15px; border-radius: 1px;
  background: currentColor; position: absolute;
}
.ap-jouer.is-en-lecture::before { transform: translateX(-4px); }
.ap-jouer.is-en-lecture::after { transform: translateX(4px); }

.ap-corps { flex: 1; min-width: 0; }

/* Forme d'onde : deux calques de barres identiques superposés (générées et
   dupliquées par App.ameliorerAudio, mêmes hauteurs, mêmes espacements) —
   .ap-onde-fond en dessous (couleur neutre), .ap-onde-rempli au-dessus
   (couleur d'accent), masqué par clip-path selon la progression. clip-path
   plutôt qu'une largeur : réduire la LARGEUR du calque relancerait le calcul
   flex de ses propres barres, qui ne tomberaient plus en face de celles du
   calque du dessous — le clip ne déplace jamais la mise en page, seulement
   ce qui est peint. Hauteur du conteneur = zone de clic, pas besoin d'un
   padding séparé comme pour l'ancienne barre plate. */
.ap-onde {
  position: relative; height: 40px; cursor: pointer;
}
.ap-onde-fond, .ap-onde-rempli {
  position: absolute; inset: 0; display: flex; align-items: center; gap: 2px;
}
.ap-onde-rempli {
  clip-path: inset(0 100% 0 0);
  transition: clip-path 120ms linear;
}
.ap-onde-fond span, .ap-onde-rempli span {
  flex: 1 0 3px; width: 3px; border-radius: 2px; min-height: 3px;
  transition: transform .18s var(--e-doux);
}
.ap-onde-fond span { background: var(--bg-sunk); }
.ap-onde-rempli span { background: var(--accent); }
/* Respiration pendant la lecture : chaque barre a son propre délai (posé en
   ligne par JS), pour une ondulation qui parcourt la forme au lieu d'un
   clignotement synchronisé — décoratif, pas une vraie analyse du signal. */
.ap-onde.en-lecture span { animation: ap-onde-respire 1.1s ease-in-out infinite; }
@keyframes ap-onde-respire {
  0%, 100% { transform: scaleY(.8); }
  50% { transform: scaleY(1.15); }
}

.ap-temps {
  display: flex; justify-content: space-between; margin-top: 8px;
  font-family: var(--mono); font-size: 11.5px; color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}

.ap-volume { flex: none; display: flex; align-items: center; gap: 6px; }
.ap-muet {
  flex: none; width: 30px; height: 30px; border-radius: 50%; border: none;
  background: transparent; color: var(--text-soft);
  display: grid; place-items: center; cursor: pointer;
  transition: background var(--t-court) var(--e-doux), color var(--t-court) var(--e-doux);
}
.ap-muet:hover { color: var(--text); background: var(--bg-sunk); }
/* Même curseur que .at-curseur (jeux.css) : accent-color plutôt qu'un
   habillage complet — cohérent avec le seul autre curseur du site, sans
   dupliquer son style dans deux feuilles différentes. */
.ap-vol-curseur { width: 80px; accent-color: var(--accent); cursor: pointer; }

@media (max-width: 560px) {
  .audio-player { padding: 14px 16px; gap: 12px; }
  .ap-jouer { width: 42px; height: 42px; }
  .ap-onde { height: 34px; }
  /* Le volume du téléphone fait déjà le travail ; un curseur précis au
     doigt sur 80px est plus pénible qu'utile. Le bouton muet reste. */
  .ap-vol-curseur { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .ap-onde.en-lecture span { animation: none; }
}

/* ==========================================================================
   Les cinq encadrés
   Chacun se reconnaît sans couleur : épaisseur de rail, trame de l'en-tête,
   trait plein ou pointillé, coin coupé. PIÈGE est le plus fort des cinq.
   ========================================================================== */
.encadre {
  margin: 1.7em 0; border-radius: var(--radius); border: 1px solid var(--border);
  background: var(--bg-elev); overflow: hidden; position: relative;
}
.encadre-tete {
  padding: 10px 16px; font-size: 12px; font-weight: 650;
  font-family: var(--mono); letter-spacing: .07em; text-transform: uppercase;
  border-bottom: 1px solid var(--border); background: var(--bg-sunk);
}
.encadre-corps { padding: 4px 18px 14px; }
.encadre-corps > *:first-child { margin-top: .8em; }
.encadre-corps > *:last-child { margin-bottom: .4em; }

/* MÉTHODE — rail plein d'accent, en-tête teinté, étapes numérotées. */
.encadre-methode {
  border-color: color-mix(in srgb, var(--accent) 38%, transparent);
  border-left-width: 3px; border-left-color: var(--accent);
  border-radius: 0 var(--radius) var(--radius) 0;
}
.encadre-methode .encadre-tete {
  background: var(--accent-soft); color: var(--accent-text);
  border-bottom-color: color-mix(in srgb, var(--accent) 30%, transparent);
}
.encadre-methode .encadre-corps ol { padding-left: 1.4em; counter-reset: etape; list-style: none; }
.encadre-methode .encadre-corps ol > li { margin-bottom: .7em; position: relative; }
.encadre-methode .encadre-corps ol > li::before {
  counter-increment: etape; content: counter(etape, decimal-leading-zero);
  position: absolute; left: -1.45em; top: .18em;
  font-family: var(--mono); font-size: .74em; font-weight: 600; color: var(--accent-text);
  letter-spacing: .04em;
}

/* ASTUCE — trait pointillé, pas de rail, en-tête nu : la plus légère. */
.encadre-astuce {
  border-style: dashed;
  border-color: color-mix(in srgb, var(--good) 50%, transparent);
  background: transparent;
}
.encadre-astuce .encadre-tete {
  background: transparent; color: var(--good);
  border-bottom: 1px dashed color-mix(in srgb, var(--good) 40%, transparent);
}

/* EXEMPLE — la feuille de copie : coin supérieur droit coupé, aucun rail. */
.encadre-exemple {
  border-color: var(--border-strong);
  clip-path: polygon(0 0, calc(100% - 16px) 0, 100% 16px, 100% 100%, 0 100%);
}
.encadre-exemple .encadre-tete {
  background: var(--bg-sunk); color: var(--text);
  padding-right: 34px;
}

/* PIÈGE — le plus fort des cinq, mais sobrement : pas d'aplat, pas de trame.
   Un rail épais, un fond à peine teinté et un filet net sous l'en-tête.
   C'est l'épaisseur du rail qui le distingue, pas la saturation — lisible en
   noir et blanc comme pour un daltonien. */
.encadre-piege {
  border-width: 1px;
  border-color: color-mix(in srgb, var(--bad) 40%, transparent);
  border-left: 3px solid var(--bad);
  border-radius: 0 var(--radius) var(--radius) 0;
  box-shadow: 0 10px 30px -22px color-mix(in srgb, var(--bad) 55%, transparent);
}
.encadre-piege .encadre-tete {
  background: color-mix(in srgb, var(--bad) 9%, transparent);
  color: var(--bad);
  font-weight: 650;
  letter-spacing: .13em;
  border-bottom: 1px solid color-mix(in srgb, var(--bad) 35%, transparent);
}
.encadre-piege::before {
  content: ""; position: absolute; inset: 0 auto 0 -3px; width: 3px;
  background: var(--bad);
  animation: piege-respire 3.4s ease-in-out infinite;
}

/* ATTENTION — trait double, en-tête plein, rail fin : ferme mais pas criard. */
.encadre-attention {
  border-width: 2px; border-style: double;
  border-color: color-mix(in srgb, var(--warn) 62%, transparent);
  border-left: 3px solid var(--warn);
  border-radius: 0 var(--radius) var(--radius) 0;
}
.encadre-attention .encadre-tete {
  background: var(--warn-soft); color: var(--warn);
  border-bottom-color: color-mix(in srgb, var(--warn) 35%, transparent);
}

/* --- Tableaux -------------------------------------------------------------- */
.prose table {
  width: 100%; border-collapse: collapse; margin: 1.4em 0; font-size: 14.5px;
}
.prose th, .prose td { border: 1px solid var(--border); padding: 9px 13px; text-align: left; }
.prose th {
  background: var(--bg-sunk); font-family: var(--mono); font-weight: 500;
  font-size: 11.5px; letter-spacing: .06em; text-transform: uppercase; color: var(--text-soft);
}
.prose tbody tr:hover td { background: color-mix(in srgb, var(--accent) 5%, transparent); }
.prose mark {
  background: color-mix(in srgb, var(--warn) 32%, transparent);
  color: var(--text); padding: 0 3px; border-radius: 3px;
}
/* Un tableau large défile dans son conteneur, jamais la page. */
.table-scroll { overflow-x: auto; max-width: 100%; overscroll-behavior-x: contain; }
.prose { overflow-wrap: break-word; }
.prose table { display: table; }
.prose > table { min-width: 0; }

/* --- Formulaires ---------------------------------------------------------- */
.field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
.field label {
  font-family: var(--mono); font-size: 11px; letter-spacing: .08em;
  text-transform: uppercase; font-weight: 500; color: var(--text-faint);
}
.input, .textarea, .select {
  width: 100%; padding: 10px 13px; border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  background: var(--bg-elev); color: var(--text); outline: none;
  transition: border-color var(--t-court) var(--e-doux), box-shadow var(--t-court) var(--e-doux);
}
.input:focus, .textarea:focus, .select:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent);
}
.textarea { min-height: 120px; resize: vertical; font-family: var(--mono); font-size: 13.5px; line-height: 1.65; }
.search { position: relative; }
.search .input { padding-left: 36px; }
.search::before {
  content: "🔍"; position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
  font-size: 13px; opacity: .55; pointer-events: none; z-index: 1;
}

/* --- Segments (filtres) --------------------------------------------------- */
.segments { display: flex; gap: 4px; flex-wrap: wrap; }
.segments button, .segments a {
  padding: 6px 13px; border-radius: 999px; border: 1px solid var(--border);
  background: transparent; color: var(--text-soft); font-size: 13px; font-weight: 500;
  cursor: pointer; text-decoration: none; display: inline-block;
  transition: border-color var(--t-court) var(--e-doux), color var(--t-court) var(--e-doux),
              background var(--t-court) var(--e-doux);
}
.segments button:hover, .segments a:hover { border-color: var(--border-strong); color: var(--text); background: var(--bg-sunk); }
.segments button.is-active, .segments a.is-active {
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  border-color: var(--accent); color: var(--accent-text); font-weight: 600;
}
.segments button[disabled] { opacity: .38; cursor: not-allowed; }
.segments button[disabled]:hover { border-color: var(--border); color: var(--text-soft); background: transparent; }

/* Bascule entraînement / partiel : plus haute que les filtres ordinaires,
   parce qu'elle change de mode et pas seulement de tri. */
.mode-onglets { margin-bottom: 20px; }
.mode-onglets a { padding: 9px 18px; font-size: 14px; }

/* ==========================================================================
   Flashcards — le geste le plus répété du site
   La carte est reconstruite à chaque affichage : elle se retourne donc à
   chaque passage, sans une ligne de JavaScript. 340 ms, sortie franche.
   ========================================================================== */
.review-stage { max-width: 720px; margin: 0 auto; }

.review-stage { perspective: 1700px; perspective-origin: 50% 44%; }

.flashcard {
  position: relative; overflow: hidden;
  background: var(--bg-elev); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-lg); padding: 38px 32px; min-height: 300px;
  display: flex; flex-direction: column; justify-content: center;
  transform-origin: 50% 50%; transform-style: preserve-3d;
  animation: carte-retourne 340ms var(--e-sortie) both;
}
/* Vraie rotation dans l'espace : la perspective vit sur le conteneur, donc la
   carte tourne autour de son axe et l'ombre suit. */
@keyframes carte-retourne {
  0%   { transform: rotateY(-42deg) translateZ(-70px) scale(.97); opacity: 0;
         box-shadow: 0 30px 60px -40px rgba(0,0,0,.6); }
  55%  { opacity: 1; }
  100% { transform: rotateY(0deg) translateZ(0) scale(1); opacity: 1;
         box-shadow: var(--shadow-lg); }
}
/* Un éclat traverse la carte pendant qu'elle se remet à plat : c'est ce qui
   rend le geste satisfaisant à la centième fois. */
.flashcard::before {
  content: ""; position: absolute; inset: -40%; pointer-events: none; z-index: 2;
  background: linear-gradient(104deg, transparent 42%,
              color-mix(in srgb, var(--accent) 22%, transparent) 50%, transparent 58%);
  transform: translateX(-70%);
  animation: eclat-carte 620ms var(--e-doux) 60ms both;
}
@keyframes eclat-carte { to { transform: translateX(70%); } }
.flashcard .fc-front {
  font-family: var(--font-titre);
  font-size: 1.5rem; font-weight: 480; line-height: 1.32; text-align: center;
  letter-spacing: -.012em; text-wrap: balance;
}
.flashcard .fc-sep {
  height: 1px; margin: 28px 0; background: var(--border);
  transform: scaleX(0); transform-origin: center;
  animation: sep-tire 280ms var(--e-sortie) 120ms both;
}
@keyframes sep-tire { to { transform: scaleX(1); } }
.flashcard .fc-back {
  font-size: 1rem; line-height: 1.75; white-space: pre-wrap; color: var(--text);
  animation: dos-monte 300ms var(--e-sortie) 160ms both;
}
@keyframes dos-monte {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}
.flashcard .fc-back .formula { text-align: left; }
.fc-meta { display: flex; justify-content: center; gap: 8px; margin-bottom: 18px; }

.grade-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin-top: 20px; }
.grade {
  padding: 12px 6px; border-radius: var(--radius-sm); border: 1px solid var(--border-strong);
  background: transparent; cursor: pointer; text-align: center;
  transition: transform var(--t-vif) var(--e-doux), background var(--t-court) var(--e-doux),
              border-color var(--t-court) var(--e-doux);
  animation: dos-monte 260ms var(--e-sortie) 220ms both;
}
.grade:active { transform: translateY(1px) scale(.97); }
.grade b { display: block; font-size: 14px; font-weight: 580; }
.grade span { display: block; font-family: var(--mono); font-size: 10.5px; color: var(--text-faint); margin-top: 3px; }
.grade.g1 { border-color: color-mix(in srgb, var(--bad) 45%, transparent); }
.grade.g1:hover { background: var(--bad-soft); border-color: var(--bad); }
.grade.g2 { border-color: color-mix(in srgb, var(--warn) 45%, transparent); }
.grade.g2:hover { background: var(--warn-soft); border-color: var(--warn); }
.grade.g3 { border-color: color-mix(in srgb, var(--good) 45%, transparent); }
.grade.g3:hover { background: var(--good-soft); border-color: var(--good); }
.grade.g4 { border-color: color-mix(in srgb, var(--accent) 45%, transparent); }
.grade.g4:hover { background: var(--accent-soft); border-color: var(--accent); }

.review-progress { display: flex; gap: 3px; margin-bottom: 20px; }
.review-progress i {
  flex: 1; height: 4px; border-radius: 99px; background: var(--bg-sunk);
  transition: background var(--t-moyen) var(--e-doux);
}
.review-progress i.done { background: var(--good); }
.review-progress i.current {
  background: var(--accent);
  animation: jalon-pose 300ms var(--e-elan) both;
}
@keyframes jalon-pose {
  from { transform: scaleY(1); }
  45%  { transform: scaleY(2.6); }
  to   { transform: scaleY(1); }
}

/* ==========================================================================
   QCM — juste et faux, deux réactions franchement différentes
   Juste : la carte se pose, un halo se referme. 420 ms.
   Faux  : aucun tremblement, aucune sanction. La proposition s'assombrit et
           se range en 260 ms ; c'est la bonne réponse qui bouge, pas l'erreur.
   ========================================================================== */
.quiz-stage { max-width: 720px; margin: 0 auto; }

.choice {
  display: flex; align-items: flex-start; gap: 12px; width: 100%; text-align: left;
  padding: 14px 16px; border-radius: var(--radius-sm); border: 1px solid var(--border-strong);
  background: transparent; cursor: pointer; margin-bottom: 8px; position: relative;
  transition: border-color var(--t-court) var(--e-doux), background var(--t-court) var(--e-doux),
              transform var(--t-vif) var(--e-doux);
}
.choice:hover:not(:disabled) {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  transform: translateX(2px);
}
.choice:disabled { cursor: default; }
.choice .key {
  width: 24px; height: 24px; border-radius: var(--radius-sm); flex: none;
  display: grid; place-items: center;
  background: var(--bg-sunk); font-family: var(--mono); font-size: 12px; font-weight: 600;
  color: var(--text-soft);
  transition: background var(--t-court) var(--e-doux), color var(--t-court) var(--e-doux);
}

.choice.correct {
  border-color: var(--good); background: var(--good-soft); border-width: 2px;
  animation: juste 420ms var(--e-elan) both;
}
.choice.correct .key {
  background: var(--good); color: var(--sur-plein);
  animation: pastille-juste 380ms var(--e-elan) 60ms both;
}
@keyframes pastille-juste {
  0%   { transform: scale(.7) rotate(-12deg); }
  55%  { transform: scale(1.16) rotate(3deg); }
  100% { transform: scale(1) rotate(0); }
}
.choice.correct::after {
  content: ""; position: absolute; inset: -2px; border-radius: inherit;
  border: 2px solid var(--good); pointer-events: none;
  animation: halo-juste 520ms var(--e-sortie) both;
}
@keyframes juste {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.018); }
  100% { transform: scale(1); }
}
@keyframes halo-juste {
  from { transform: scale(1.06); opacity: .9; }
  to   { transform: scale(1); opacity: 0; }
}

.choice.wrong {
  border-color: color-mix(in srgb, var(--bad) 60%, transparent);
  background: var(--bad-soft);
  animation: faux 260ms var(--e-doux) both;
}
.choice.wrong .key { background: var(--bad); color: var(--sur-plein); }
@keyframes faux {
  from { transform: translateY(-3px); opacity: .8; }
  to   { transform: translateY(0); opacity: 1; }
}
/* Les propositions non retenues s'effacent : le regard va à la bonne. */
.choice:disabled:not(.correct):not(.wrong) { opacity: .5; }

.explain {
  margin-top: 16px; padding: 14px 16px; border-radius: var(--radius-sm);
  background: transparent; border: 1px solid var(--border);
  border-left: 2px solid var(--accent);
  font-size: 14.5px;
  animation: explication 280ms var(--e-sortie) 140ms both;
}
@keyframes explication {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}
.explain b { color: var(--text); font-weight: 620; }

/* --- Score de fin --------------------------------------------------------
   Le seul moment spectaculaire : 700 ms, le chiffre arrive de loin et se pose. */
.score-ring {
  display: grid; place-items: center; margin: 10px auto 20px;
  position: relative;
}
/* Deux anneaux partent du centre et se dissipent — 1,1 s, décalés de 260 ms.
   Le seul instant du site où l'on s'autorise à être spectaculaire. */
.score-ring::before, .score-ring::after {
  content: ""; position: absolute; inset: 50% auto auto 50%;
  width: 130px; height: 130px; margin: -65px 0 0 -65px;
  border-radius: 50%; border: 2px solid var(--good);
  pointer-events: none;
  animation: onde-score 1100ms cubic-bezier(.16, 1, .3, 1) both;
}
.score-ring::after { animation-delay: 260ms; }
@keyframes onde-score {
  from { transform: scale(.35); opacity: .85; }
  to   { transform: scale(2.4); opacity: 0; }
}
.score-big {
  font-family: var(--font-titre);
  font-size: 4.2rem; font-weight: 500; letter-spacing: -.045em; line-height: 1;
  font-variant-numeric: tabular-nums;
  animation: score-arrive 700ms cubic-bezier(.16, 1, .3, 1) both;
}
@keyframes score-arrive {
  0%   { opacity: 0; transform: scale(.68) translateY(18px); filter: blur(12px); }
  60%  { opacity: 1; filter: blur(0); }
  78%  { transform: scale(1.045) translateY(0); }
  100% { opacity: 1; transform: scale(1) translateY(0); filter: blur(0); }
}
.score-sub {
  color: var(--text-soft);
  animation: dos-monte 400ms var(--e-sortie) 260ms both;
}

/* --- Aperçu d'une carte dans une liste ------------------------------------
   Le verso d'une carte fait souvent dix lignes et porte des formules. Dans
   une liste de deux cents cartes, on n'en montre que le début : trois lignes,
   puis on coupe. Les formules y sont rendues en ligne (voir App.mdLigne) —
   centrées, elles feraient de chaque ligne un paragraphe. */
.apercu-carte {
  margin-top: 4px;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
}
.apercu-carte .katex { font-size: 1.02em; }

/* --- Mémoire des erreurs --------------------------------------------------
   La dette de révision doit se voir sans crier : la teinte d'alerte reste
   diluée, seul le liseré de gauche est franc. */
.reprise-carte {
  border-color: color-mix(in srgb, var(--warn) 42%, var(--border));
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--warn) 12%, transparent), var(--bg-elev) 62%);
  border-left-width: 3px;
  border-left-color: var(--warn);
}
.faibles { display: flex; flex-direction: column; gap: 12px; }
.faible-ligne { display: block; text-decoration: none; color: inherit; }
.faible-ligne:hover .faible-titre { color: var(--accent-text); }
.faible-tete { display: flex; align-items: baseline; gap: 10px; margin-bottom: 6px; }
.faible-titre { font-weight: 600; flex: 1; min-width: 0; }
.faible-taux {
  font-variant-numeric: tabular-nums; font-weight: 600;
  color: var(--bad); white-space: nowrap;
}
.faible-barre {
  height: 6px; border-radius: 99px; background: var(--bg-sunk); overflow: hidden;
  border: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
}
.faible-barre > i {
  display: block; height: 100%; border-radius: 99px;
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--bad) 72%, var(--warn)), var(--bad));
  transition: width 640ms var(--e-sortie);
}

/* --- Partiel blanc ---------------------------------------------------------
   Tout ici sert à reproduire la pression de l'épreuve : le chrono ne quitte
   jamais l'écran, et la grille montre d'un coup d'œil ce qui reste en blanc. */
.partiel-barre {
  display: flex; align-items: center; gap: 12px; margin-bottom: 12px;
  position: sticky; top: 0; z-index: 5;
  padding: 8px 0; background: var(--bg);
}
.p-chrono {
  font-variant-numeric: tabular-nums; font-weight: 600; font-size: 1.15rem;
  font-family: var(--mono);
  padding: 4px 12px; border-radius: 999px;
  border: 1px solid var(--border); color: var(--text);
  transition: color var(--t-court) var(--e-doux), border-color var(--t-court) var(--e-doux);
}
/* Sous 15 % du temps, le chrono passe au rouge et respire — une seconde par
   battement, exactement le rythme qu'il compte. */
.p-chrono.presse {
  color: var(--bad); border-color: var(--bad);
  animation: chrono-presse 1s var(--e-doux) infinite;
}
@keyframes chrono-presse {
  0%, 100% { transform: scale(1); }
  12%      { transform: scale(1.06); }
}

.p-grille { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 16px; }
.p-grille button {
  width: 30px; height: 30px; border-radius: 8px; cursor: pointer;
  border: 1px solid var(--border); background: transparent;
  color: var(--text-soft); font-size: 12px; font-weight: 600;
  font-variant-numeric: tabular-nums;
  transition: border-color var(--t-court) var(--e-doux), background var(--t-court) var(--e-doux),
              color var(--t-court) var(--e-doux), transform var(--t-court) var(--e-sortie);
}
.p-grille button:hover { transform: translateY(-2px); border-color: var(--border-strong); color: var(--text); }
.p-grille button.est-repondue {
  background: color-mix(in srgb, var(--accent) 20%, transparent);
  border-color: color-mix(in srgb, var(--accent) 50%, var(--border)); color: var(--accent-text);
}
.p-grille button.est-marquee { border-color: var(--warn); color: var(--warn); }
.p-grille button.est-marquee::after { content: "⚑"; font-size: 9px; vertical-align: super; }
.p-grille button.est-courante {
  border-color: var(--accent); color: var(--accent-text);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 32%, transparent);
}

.choice.est-choisie {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}

.regles { margin: 0; padding-left: 20px; color: var(--text-soft); }
.regles li { margin-bottom: 8px; line-height: 1.55; }
.regles li:last-child { margin-bottom: 0; }
.partiel-regles { background: var(--bg-sunk); }

.p-corrige { border-left-width: 3px; }
.p-corrige.juste   { border-left-color: var(--good); }
.p-corrige.fausse  { border-left-color: var(--bad); }
.p-corrige.blanche { border-left-color: var(--border-strong); }

/* --- Fichiers / ressources ------------------------------------------------ */
.file-list { display: flex; flex-direction: column; gap: 5px; }
.file-item {
  display: flex; align-items: center; gap: 11px; padding: 11px 13px;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: transparent; color: var(--text); font-size: 14px;
  transition: border-color var(--t-court) var(--e-doux), background var(--t-court) var(--e-doux),
              transform var(--t-court) var(--e-sortie);
}
.file-item:hover {
  text-decoration: none; border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, transparent); transform: translateX(3px);
}
.file-item .ico { font-size: 16px; flex: none; }
.file-item .nm {
  display: block; flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* --- Divers --------------------------------------------------------------- */
.empty {
  text-align: center; padding: 52px 20px; color: var(--text-soft);
  border: 1px dashed var(--border-strong); border-radius: var(--radius); background: transparent;
}
.empty .big { font-size: 34px; margin-bottom: 10px; }

.muted { color: var(--text-soft); }
.faint { color: var(--text-faint); }
.small { font-size: 13px; }
.center { text-align: center; }
.mt { margin-top: 16px; }
.mb { margin-bottom: 16px; }
.nowrap { white-space: nowrap; }

.toast {
  position: fixed; left: 50%; bottom: 26px; transform: translate(-50%, 20px);
  background: var(--text); color: var(--bg); padding: 11px 20px; border-radius: 999px;
  font-size: 14px; font-weight: 550; box-shadow: var(--shadow-lg); z-index: 200;
  opacity: 0; pointer-events: none;
  transition: opacity var(--t-moyen) var(--e-doux), transform var(--t-moyen) var(--e-sortie);
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }

.modal-backdrop {
  position: fixed; inset: 0; background: color-mix(in srgb, #000000 62%, transparent);
  z-index: 150; display: grid; place-items: center; padding: 20px;
  backdrop-filter: blur(4px);
  animation: voile 200ms var(--e-doux) both;
}
@keyframes voile { from { opacity: 0; } to { opacity: 1; } }
.modal {
  background: var(--bg-elev); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-lg); width: min(560px, 100%); max-height: 88vh;
  overflow-y: auto; padding: 24px;
  animation: modal-monte 260ms var(--e-sortie) both;
}
@keyframes modal-monte {
  from { opacity: 0; transform: translateY(14px) scale(.985); }
  to   { opacity: 1; transform: none; }
}
.modal h2 { margin-bottom: 16px; }

.breadcrumb { font-family: var(--mono); font-size: 11.5px; letter-spacing: .04em; color: var(--text-faint); margin-bottom: 14px; }
.breadcrumb a { color: var(--text-soft); }
.breadcrumb span { margin: 0 6px; opacity: .5; }

.heat { display: flex; gap: 3px; align-items: flex-end; height: 44px; }
.heat i {
  flex: 1; min-width: 4px; border-radius: 2px 2px 0 0;
  background: var(--accent); opacity: .2;
  transition: height var(--t-moyen) var(--e-sortie), opacity var(--t-moyen) var(--e-doux);
}
.heat i.has { opacity: 1; }

.kbd {
  display: inline-block; padding: 1px 6px; border-radius: 4px; border: 1px solid var(--border-strong);
  border-bottom-width: 2px; background: var(--bg-sunk); font-family: var(--mono); font-size: 11px;
  color: var(--text-soft);
}

/* --- Adaptations mobiles --------------------------------------------------
   Le site est installable sur un écran d'accueil : il doit donc se tenir
   pour de bon sur 375 px, pas seulement « ne pas casser ». */
@media (max-width: 720px) {
  body { font-size: 15.5px; }
  h1 { font-size: 1.6rem; }
  .wrap { padding: 0 16px; }
  /* Sur deux rangs, l'en-tête fait 145 px : collé en haut, il mangerait 18 %
     d'un écran de téléphone pendant toute la lecture d'une fiche. Il défile
     donc avec la page — on remonte d'un geste quand on veut changer d'onglet. */
  .topbar { position: static; }
  .topbar-inner { gap: 12px; min-height: 54px; flex-wrap: wrap; padding: 8px 10px 0 8px; }
  .brand small { display: none; }

  /* La navigation passe à la ligne au lieu de défiler sur le côté : ce qui
     dépasse d'une barre horizontale n'existe pas pour qui ne pense pas à la
     faire glisser. Les noms courts ramènent les sept onglets sur deux rangs. */
  .nav {
    order: 3; width: 100%; margin: 0; padding: 0 0 8px;
    flex-wrap: wrap; overflow-x: visible; gap: 2px 4px;
  }
  .nav a { padding: 8px 11px; font-size: 13px; }
  .nav a::after { left: 11px; right: 11px; }
  .nav-long { display: none; }
  .nav-court { display: inline; }

  .topbar-actions { margin-left: auto; }
  /* 44 px : la taille sous laquelle un doigt ne vise plus rien. */
  .icon-btn { width: 40px; height: 40px; }

  /* Quatre chiffres empilés faisaient 504 px de haut à eux seuls. */
  .grid-4 { grid-template-columns: repeat(2, 1fr); gap: 10px; }
  .grid-3 { grid-template-columns: repeat(2, 1fr); gap: 10px; }
  .stat { padding: 14px 14px 15px; }
  .stat .value { font-size: 1.9rem; }

  .grade-row { grid-template-columns: repeat(2, 1fr); }
  .flashcard { padding: 26px 20px; min-height: 240px; }
  .flashcard .fc-front { font-size: 1.24rem; }
  /* Sans limite, un titre de chapitre long transformait l'étiquette en pavé. */
  .fc-meta { gap: 6px; flex-wrap: wrap; }
  .fc-meta .tag {
    display: inline-block; max-width: 44vw;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    font-size: 10.5px; padding: 4px 9px;
  }

  .score-big { font-size: 3.2rem; }
  main { padding: 24px 0 72px; }

  /* iOS agrandit la page dès qu'on touche un champ de moins de 16 px, et la
     mise en page saute. On force donc 16 px pile. */
  .input, .textarea, .select { font-size: 16px; }

  /* Une formule centrée trop large reste poussable du doigt (overflow-x est
     déjà en auto), mais autant réduire le nombre de celles qui l'exigent :
     un cran plus petit en fait rentrer un tiers d'un coup. */
  .tex.formula { font-size: .92em; }

  /* Filtres et propositions : assez hauts pour être touchés du pouce. */
  .segments button, .segments a { padding: 9px 14px; }
  .choice { padding: 15px 14px; }
  .btn { padding: 11px 16px; }
  .btn-sm { padding: 8px 12px; }
  /* La grille du partiel sert à sauter d'une question à l'autre : à 30 px,
     on tape la voisine une fois sur trois. */
  .p-grille { gap: 6px; }
  .p-grille button { width: 38px; height: 38px; font-size: 13px; }
}

@media print {
  .topbar, .no-print { display: none !important; }
  body { background: #fff; background-image: none; color: #000; }
  .encadre, .card, .flashcard { box-shadow: none; }
  .encadre-piege::before { animation: none; }
}

/* --- Respiration du rail « piège » ---------------------------------------- */
@keyframes piege-respire {
  0%, 100% { opacity: 1; }
  50%      { opacity: .55; }
}

/* ==========================================================================
   La correction pas à pas — le calcul d'un côté, le « pourquoi » de l'autre.

   Composant partagé : l'espace personnel s'en sert pour les 89 corrigés,
   la page « Comprendre » pour les marches. Il vivait dans admin.css tant
   qu'il n'avait qu'un seul client.
   ========================================================================== */
[hidden] { display: none !important; }

.partie-titre {
  display: flex; align-items: center; gap: 12px;
  margin: 56px 0 6px; padding-bottom: 12px;
  border-bottom: 1px solid color-mix(in srgb, var(--accent) 55%, transparent);
  font-family: var(--font-titre);
  font-size: 1.2rem; font-weight: 560; letter-spacing: -.015em;
}
.corriges > .partie-titre:first-child { margin-top: 24px; }
.partie-num {
  display: grid; place-items: center;
  width: 24px; height: 24px; flex: none;
  border-radius: var(--radius-sm);
  background: transparent; color: var(--accent-text);
  border: 1px solid var(--accent);
  font-family: var(--mono); font-size: 12px; font-weight: 600;
}

/* L'exercice reçoit son propre fond : le champ du bandeau passe derrière, et
   la correction reste lisible même quand une formule décorative la croise. */
.exo {
  margin-top: 50px;
  position: relative;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 24px 26px 26px;
}
.exo:first-child { margin-top: 28px; }
.partie-titre + .exo { margin-top: 30px; }
.exo-titre:first-child { margin-top: 0; }
@media (max-width: 720px) { .exo { padding: 18px 16px 20px; } }

.exo-titre {
  display: flex; align-items: baseline; gap: 12px;
  font-family: var(--font-titre);
  font-size: 1.35rem; font-weight: 540; letter-spacing: -.015em;
  margin-bottom: 16px; padding-bottom: 12px;
  border-bottom: 1px solid var(--border-strong);
}
.exo-num {
  font-family: var(--mono); font-size: 12px; letter-spacing: .08em;
  color: var(--accent-text); flex: none;
}

/* Astuces : dépliables, consultables sans ouvrir la correction. */
.astuces {
  margin: 20px 0;
  border-left: 2px solid var(--border-strong);
  padding-left: 16px;
  transition: border-color var(--t-court) var(--e-doux);
}
.astuces[open] { border-left-color: var(--accent); }
.astuces > summary {
  cursor: pointer; list-style: none;
  font-family: var(--mono);
  font-size: 11.5px; font-weight: 500; letter-spacing: .06em; text-transform: uppercase;
  color: var(--accent-text);
  padding: 4px 0;
}
.astuces > summary::-webkit-details-marker { display: none; }
.astuces > summary::before {
  content: "▸"; display: inline-block; margin-right: 8px;
  transition: transform var(--t-court) var(--e-sortie);
}
.astuces[open] > summary::before { transform: rotate(90deg); }
.astuces > summary:hover { text-decoration: underline; }
.astuces[open] > .prose, .astuces[open] > div, .astuces[open] > p {
  animation: etape-monte 300ms var(--e-sortie) both;
}

.exo-commandes {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin: 22px 0;
}
.exo-info, .exo-compteur { font-size: 12.5px; color: var(--text-faint); }
.exo-compteur {
  font-family: var(--mono); margin-left: auto;
  font-variant-numeric: tabular-nums; letter-spacing: .04em;
}

/* ==========================================================================
   La signature du site : la correction qui se déroule, étape par étape.
   L'étape arrive en 420 ms ; le calcul d'abord, le « pourquoi » 130 ms après,
   et le filet de séparation se trace pendant que l'œil descend.
   ========================================================================== */
.correction { margin-top: 6px; }

.etape {
  position: relative;
  border-top: 1px solid var(--border);
  padding: 26px 0;
  animation: etape-monte 420ms var(--e-sortie) both;
}
.etape.cachee { display: none; }

/* L'étape ne se contente pas de monter : elle se découvre, le masque suivant
   la ligne de lecture. 420 ms, sortie franche. */
@keyframes etape-monte {
  from { opacity: 0; transform: translateY(18px); clip-path: inset(0 0 100% 0); }
  55%  { opacity: 1; }
  to   { opacity: 1; transform: none; clip-path: inset(0 0 -30% 0); }
}
/* Le filet supérieur se trace de gauche à droite en 520 ms, juste devant
   le texte qui arrive. */
.etape::before {
  content: ""; position: absolute; left: 0; right: 0; top: -1px; height: 1px;
  background: var(--accent); transform-origin: left;
  animation: filet-horizontal 520ms var(--e-doux) both;
}
@keyframes filet-horizontal {
  from { transform: scaleX(0); opacity: .9; }
  65%  { transform: scaleX(1); opacity: .55; }
  to   { transform: scaleX(1); opacity: 0; }
}

.etape-num {
  font-family: var(--mono);
  font-size: 10.5px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--accent-text);
  margin-bottom: 18px;
  animation: num-arrive 320ms var(--e-elan) both;
}
@keyframes num-arrive {
  from { opacity: 0; transform: translateX(-10px); letter-spacing: .3em; }
  to   { opacity: 1; transform: none; letter-spacing: .14em; }
}
.etape-num span { color: var(--text-faint); }

/* Le calcul d'un côté, l'explication de l'autre. */
.etape-corps {
  display: grid;
  gap: 28px;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
}
.etape-calcul { animation: calcul-arrive 400ms var(--e-sortie) 60ms both; }
@keyframes calcul-arrive {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}
/* La formule elle-même se pose 100 ms après son bloc : le regard a le temps
   d'arriver dessus. */
.etape-calcul .formula { animation: formule-pose 420ms var(--e-elan) 160ms both; }
@keyframes formule-pose {
  from { opacity: 0; transform: translateY(8px) scale(.985); }
  to   { opacity: 1; transform: none; }
}
.etape-pourquoi {
  position: relative;
  border-left: 1px solid var(--border);
  padding-left: 28px;
  animation: calcul-arrive 400ms var(--e-sortie) 190ms both;
}
/* Le filet vertical se trace de haut en bas pendant la lecture du calcul. */
.etape-pourquoi::before {
  content: ""; position: absolute; left: -1px; top: 0; bottom: 0; width: 1px;
  background: var(--accent); transform-origin: top;
  animation: filet-trace 620ms var(--e-doux) 190ms both;
}
@keyframes filet-trace {
  from { transform: scaleY(0); opacity: .9; }
  70%  { transform: scaleY(1); opacity: .6; }
  to   { transform: scaleY(1); opacity: 0; }
}
.etape-libelle {
  font-family: var(--mono);
  font-size: 10.5px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: 12px;
}
.etape-calcul .prose, .etape-pourquoi .prose { max-width: none; }
.etape-pourquoi .prose { font-size: 15px; color: var(--text-soft); }
.etape-pourquoi .prose strong { color: var(--text); }

.exo-methode {
  border-top: 1px solid color-mix(in srgb, var(--accent) 55%, transparent);
  margin-top: 28px;
  padding-top: 24px;
}

@media (max-width: 820px) {
  /* minmax(0, …) et non 1fr : sans ça, une colonne de grille refuse de
     descendre sous la largeur de son contenu. Une matrice un peu large
     poussait alors la page entière à défiler de côté sur téléphone, au lieu
     de laisser la formule défiler seule dans son cadre. */
  .etape-corps { grid-template-columns: minmax(0, 1fr); gap: 20px; }
  .etape-pourquoi {
    border-left: 0;
    border-top: 1px solid var(--border);
    padding-left: 0;
    padding-top: 18px;
  }
  .etape-pourquoi::before {
    left: 0; right: 0; top: -1px; bottom: auto; width: auto; height: 1px;
    transform-origin: left;
    animation-name: filet-trace-h;
  }
  @keyframes filet-trace-h {
    from { transform: scaleX(0); opacity: .9; }
    70%  { transform: scaleX(1); opacity: .6; }
    to   { transform: scaleX(1); opacity: 0; }
  }
}

/* ==========================================================================
   Mouvement réduit — le site reste entièrement utilisable, sans exception.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
  .flashcard, .flashcard::before, .fc-back, .fc-sep, .grade, .explain,
  .score-big, .score-sub, .score-ring::before, .score-ring::after,
  .choice.correct, .choice.correct .key, .choice.wrong, .modal, .modal-backdrop,
  .card, .stat, .encadre, .matiere, .chapter-item, .file-item, .section-title,
  .ue-entete, .schema, .appel, .exo-titre, .partie-titre, .topbar::after,
  .etape, .etape::before, .etape-num, .etape-calcul, .etape-calcul .formula,
  .etape-pourquoi, .etape-pourquoi::before, .astuces[open] > .prose {
    animation: none !important; opacity: 1 !important; transform: none !important;
    filter: none !important; clip-path: none !important;
  }
  .flashcard::before, .score-ring::before, .score-ring::after { display: none; }
  .topbar::after { display: none; }
  .etape::before, .etape-pourquoi::before { display: none; }
  .fc-sep { transform: scaleX(1) !important; }
  .progress > i::after, .encadre-piege::before,
  .encadre-piege .encadre-tete { animation: none !important; }
  .btn-primary::before { display: none; }
  .encadre-piege::before { opacity: 1; }
}
