/* =============================================================================
   TheSATMentor — styles.css
   Rewritten from scratch. See DESIGN_SYSTEM.md for tokens and the Mode A/B rules,
   REDESIGN_AUDIT.md for what the previous stylesheet did and why it is gone.

   Layer order (do not reorder — later layers depend on earlier cascade position):
     01 fonts
     02 tokens
     03 reset + base
     04 utilities
     05 shell + navigation
     06 controls
     07 forms
     08 surfaces (cards, pills, modals, toast, empty states)
     09 student home
     10 student panels
     11 practice player
     12 practice sub-components
     13 AI tutor
     14 review page
     15 admin
     16 responsive
     17 print

   Hard rules encoded here:
     - Red is answer state. Brand red never appears inside .dsat-shell / .rp-page.
     - .app-watermark must stay visible (opacity >= .01) or script.js locks the test.
     - Mode B section rhythm is capped at --s-12. "Do not make the UI huge."
     - .dsat-nav-btn keeps a 48px hit target with quiet visual weight.
     - Long content scrolls inside its own panel; the page never scrolls.
     - The navigator uses one restrained state system, not a rainbow.
   ============================================================================= */

/* =============================================================================
   01 — FONTS
   Heuristica is the exam reading face. Self-hosted, already present in Font/.
   The UI face is a system stack: no webfont, no FOUT, correct Vietnamese
   diacritics on every target platform.
   ============================================================================= */

@font-face {
  font-family: "Heuristica";
  src: url("Font/Heuristica-Regular.otf") format("opentype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Heuristica";
  src: url("Font/Heuristica-Italic.otf") format("opentype");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}

@font-face {
  font-family: "Heuristica";
  src: url("Font/Heuristica-Bold.otf") format("opentype");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Heuristica";
  src: url("Font/Heuristica-BoldItalic.otf") format("opentype");
  font-weight: 700;
  font-style: italic;
  font-display: swap;
}

/* =============================================================================
   02 — TOKENS
   ============================================================================= */

:root {
  color-scheme: light;

  /* -- brand: fixed in both themes ---------------------------------------- */
  --brand-dark: #bc070c;
  --brand: #ca252b;
  --brand-lite: #d6434b;
  --brand-knockout: #fdfdfd;
  --brand-gradient: linear-gradient(100deg, #bc070c 0%, #d6434b 100%);

  /* -- neutral ramp: warm, red-cast --------------------------------------- */
  --paper: #fbf9f8;
  --paper-2: #f4f0ee;
  --paper-3: #ebe5e2;
  --line: #e4dcd8;
  --line-2: #d0c4bf;
  --ink-4: #9e908b;
  --ink-3: #796b66;
  --ink-2: #4b3f3b;
  --ink: #1f1917;

  /* -- state: product only ------------------------------------------------ */
  --ok: #12734a;
  --ok-wash: #e7f3ec;
  --ok-line: #a6cdb8;
  --wrong: #b3261e;
  --wrong-wash: #fbeae8;
  --wrong-line: #e4a9a3;
  --warn: #8a5a12;
  --warn-wash: #faf1e0;
  --warn-line: #e0c48b;
  --info: #2c5c8a;
  --info-wash: #eaf0f6;
  --info-line: #b3c8db;
  --danger: var(--wrong);

  /* -- semantic aliases: Mode B values. Mode A overrides these on :root. --- */
  --accent: var(--ink);
  --accent-contrast: var(--paper);
  --focus: var(--ink);

  /* -- heatmap ramp: warm neutral -> ink, deliberately not green ----------- */
  --level-0: #efe9e6;
  --level-1: #d9cbc4;
  --level-2: #b39c93;
  --level-3: #7d655c;
  --level-4: #3a2b26;

  /* -- data displays: one shared geometry ---------------------------------
     SCOPE NOTE: layer 02 is out of scope for the layout pass. These three are
     added anyway because the brief's section 5 asks the charts to "read as one
     system", and a shared system needs shared values — .score-meter (6px),
     .wrong-answer-column-track and the heatmap were each sized in isolation.
     Consumers live in layers 09, 10 and 15.

     No hue is introduced. Magnitude is carried by the same neutral ramp the
     heatmap uses, because green and red are spoken for: they mean correct and
     incorrect, and a categorical palette that reuses either for a non-answer
     meaning is a bug, not a taste choice. --wrong appears on the admin chart
     only, where the quantity being measured genuinely is wrong answers. */
  --bar-size: 8px;          /* thickness of any linear meter */
  --bar-track: var(--paper-3);
  --bar-max: 72px;          /* a column never grows wider than this */

  /* -- typography --------------------------------------------------------- */
  --font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI Variable Text", "Segoe UI",
    Inter, Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
  --font-read: Heuristica, "Iowan Old Style", Charter, "Bitstream Charter",
    "Source Serif 4", "Noto Serif", Georgia, "Times New Roman", serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", "Cascadia Mono", Menlo,
    Consolas, monospace;

  --fs-2xs: 0.6875rem; /* 11 */
  --fs-xs: 0.75rem;    /* 12 */
  --fs-sm: 0.8125rem;  /* 13 */
  --fs-md: 0.9375rem;  /* 15 */
  --fs-base: 1rem;     /* 16 */
  --fs-lg: 1.125rem;   /* 18 */
  --fs-xl: 1.3125rem;  /* 21 */
  --fs-2xl: 1.5625rem; /* 25 */
  --fs-3xl: 1.9375rem; /* 31 */

  --lh-tight: 1.15;
  --lh-snug: 1.35;
  --lh-base: 1.55;
  --lh-read: 1.7;

  --tr-tight: -0.02em;
  --tr-flat: 0;
  --tr-wide: 0.06em;

  /* -- spacing: 4px base. Mode B stops at --s-12. -------------------------- */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 20px;
  --s-6: 24px;
  --s-8: 32px;
  --s-10: 40px;
  --s-12: 48px;
  --s-16: 64px;
  --s-20: 80px;
  --s-24: 96px;
  --s-32: 128px;

  /* -- radius ------------------------------------------------------------- */
  --r-xs: 4px;
  --r-sm: 6px;
  --r-md: 10px;
  --r-lg: 14px;
  --r-xl: 20px;
  --r-full: 999px;

  /* -- elevation. Mode B: --e-1 in flow, --e-2/--e-3 for true overlays. ---- */
  --e-1: 0 1px 2px rgba(31, 25, 23, 0.06);
  --e-2: 0 4px 12px -2px rgba(31, 25, 23, 0.1), 0 2px 4px -2px rgba(31, 25, 23, 0.06);
  --e-3: 0 16px 40px -8px rgba(31, 25, 23, 0.18), 0 4px 10px -4px rgba(31, 25, 23, 0.08);

  /* -- motion. Mode B ceiling is --t-base, under the 150ms budget. --------- */
  --t-fast: 120ms;
  --t-base: 150ms;
  --t-slow: 280ms;
  --ease: cubic-bezier(0.2, 0, 0, 1);

  /* -- hit targets -------------------------------------------------------- */
  --hit-sm: 32px;
  --hit: 40px;
  --hit-lg: 48px;

  /* -- layout ------------------------------------------------------------- */
  --sidebar-w: 236px;
  --content-max: 1320px;
  --measure: 68ch;

  --scrollbar-thumb: rgba(121, 107, 102, 0.38);
  --scrollbar-thumb-hover: rgba(121, 107, 102, 0.62);
  --backdrop: rgba(31, 25, 23, 0.55);
}

:root[data-theme="dark"] {
  color-scheme: dark;

  --paper: #171210;
  --paper-2: #1f1917;
  --paper-3: #2a2320;
  --line: #372e2a;
  --line-2: #4b3f3a;
  --ink-4: #7e706a;
  --ink-3: #a2938d;
  --ink-2: #cbbfb9;
  --ink: #f3edea;

  --ok: #4fbe8b;
  --ok-wash: #12281f;
  --ok-line: #2e5c45;
  --wrong: #f1786e;
  --wrong-wash: #33100d;
  --wrong-line: #6b241e;
  --warn: #dfa84c;
  --warn-wash: #2e2109;
  --warn-line: #5c4517;
  --info: #7fb0dc;
  --info-wash: #132330;
  --info-line: #2c445c;

  --level-0: #2a2320;
  --level-1: #4a3b35;
  --level-2: #6f584f;
  --level-3: #9d8177;
  --level-4: #d5c0b6;

  --e-1: 0 1px 2px rgba(0, 0, 0, 0.4);
  --e-2: 0 4px 12px -2px rgba(0, 0, 0, 0.5), 0 2px 4px -2px rgba(0, 0, 0, 0.4);
  --e-3: 0 16px 40px -8px rgba(0, 0, 0, 0.62), 0 4px 10px -4px rgba(0, 0, 0, 0.45);

  --scrollbar-thumb: rgba(162, 147, 141, 0.34);
  --scrollbar-thumb-hover: rgba(162, 147, 141, 0.58);
  --backdrop: rgba(0, 0, 0, 0.66);
}

/* =============================================================================
   03 — RESET + BASE
   ============================================================================= */

*,
*::before,
*::after {
  box-sizing: border-box;
}

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

body {
  min-width: 320px;
  margin: 0;
  background: var(--paper-2);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: var(--fs-md);
  line-height: var(--lh-base);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1,
h2,
h3,
h4,
h5,
h6 {
  margin: 0;
  font-weight: 650;
  line-height: var(--lh-snug);
  letter-spacing: var(--tr-tight);
  color: var(--ink);
}

h1 { font-size: var(--fs-2xl); }
h2 { font-size: var(--fs-xl); }
h3 { font-size: var(--fs-base); }
h4 { font-size: var(--fs-md); }

p {
  margin: 0;
}

a {
  color: var(--ink);
  text-decoration: underline;
  text-decoration-color: var(--line-2);
  text-underline-offset: 2px;
  transition: text-decoration-color var(--t-fast) var(--ease);
}

a:hover {
  text-decoration-color: var(--ink);
}

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

button {
  cursor: pointer;
  background: none;
  border: 0;
  padding: 0;
  color: inherit;
}

img,
svg,
video,
canvas {
  max-width: 100%;
}

img {
  height: auto;
}

hr {
  height: 1px;
  margin: var(--s-4) 0;
  border: 0;
  background: var(--line);
}

code,
kbd,
samp {
  font-family: var(--font-mono);
  font-size: 0.92em;
}

strong,
b {
  font-weight: 650;
}

details > summary {
  cursor: pointer;
}

/* -- lucide: script.js injects <i data-lucide> and swaps in an <svg> -------- */
[data-lucide],
.lucide {
  width: 16px;
  height: 16px;
  flex: none;
  stroke-width: 1.75;
  vertical-align: -0.15em;
}

/* -- focus: one rule, everywhere, never removed by a component ------------- */
:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

:focus:not(:focus-visible) {
  outline: none;
}

::selection {
  background: var(--paper-3);
  color: var(--ink);
}

/* -- scrollbars: real and usable. The old 1px scrollbar hid the single most
      important affordance in a test app — that a passage has more below. ----- */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--scrollbar-thumb) transparent;
}

*::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

*::-webkit-scrollbar-track,
*::-webkit-scrollbar-corner {
  background: transparent;
}

*::-webkit-scrollbar-thumb {
  background: var(--scrollbar-thumb);
  border: 3px solid transparent;
  border-radius: var(--r-full);
  background-clip: content-box;
}

*::-webkit-scrollbar-thumb:hover {
  background: var(--scrollbar-thumb-hover);
  background-clip: content-box;
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* =============================================================================
   04 — UTILITIES
   Emitted by script.js and index.html; contract, not convenience.
   ============================================================================= */

.is-hidden {
  display: none !important;
}

.grow {
  flex: 1 1 auto;
  min-width: 0;
}

.full-width {
  width: 100%;
  justify-content: center;
}

.span-2 {
  grid-column: span 2;
}

.muted-copy {
  margin: 0;
  color: var(--ink-3);
  font-size: var(--fs-sm);
}

.button-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
}

.split-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  width: 100%;
}

.split-actions > div {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
}

.math-rendered {
  font-family: var(--font-read);
}

.math-fallback {
  font-family: var(--font-mono);
  font-size: 0.95em;
  color: var(--ink-2);
}

/* =============================================================================
   05 — SHELL + NAVIGATION
   Warm paper sidebar on a slightly recessed app ground. Hairline borders, no
   shadow: the sidebar is part of the page, not floating over it.
   ============================================================================= */

.app-shell {
  min-height: 100vh;
  min-height: 100dvh;
  display: grid;
  grid-template-columns: var(--sidebar-w) minmax(0, 1fr);
}

.sidebar {
  position: sticky;
  top: 0;
  z-index: 3;
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  height: 100vh;
  height: 100dvh;
  padding: var(--s-5) var(--s-4);
  background: var(--paper);
  border-right: 1px solid var(--line);
}

/* -- brand lockup: the one place brand red appears in Mode B --------------- */
.brand {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s-3);
}

.brand-mark {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  overflow: hidden;
  border-radius: var(--r-sm);
  background: var(--brand-gradient);
}

.brand-mark img,
.brand-mark svg {
  width: 34px;
  height: 34px;
  object-fit: cover;
  border-radius: var(--r-sm);
}

.brand > div:not(.brand-mark) {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.2;
}

.brand strong {
  font-size: var(--fs-base);
  font-weight: 700;
  letter-spacing: var(--tr-tight);
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.brand span {
  font-size: var(--fs-2xs);
  font-weight: 550;
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  color: var(--ink-3);
}

.theme-toggle {
  display: grid;
  place-items: center;
  width: var(--hit-sm);
  height: var(--hit-sm);
  border-radius: var(--r-sm);
  color: var(--ink-2);
  transition: background-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}

.theme-toggle:hover {
  background: var(--paper-3);
  color: var(--ink);
}

/* -- account chip ---------------------------------------------------------- */
.account-card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper-2);
}

.account-card > div:not(.avatar) {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.25;
}

.account-card strong {
  font-size: var(--fs-sm);
  font-weight: 650;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.account-card span {
  font-size: var(--fs-xs);
  color: var(--ink-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.avatar {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: var(--r-full);
  background: var(--ink);
  color: var(--paper);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: var(--tr-wide);
}

/* -- nav ------------------------------------------------------------------- */
.nav-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  margin: 0 calc(var(--s-2) * -1);
  padding: 0 var(--s-2);
}

.nav-item {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--s-3);
  width: 100%;
  min-height: var(--hit);
  padding: 0 var(--s-3);
  border-radius: var(--r-sm);
  color: var(--ink-2);
  font-size: var(--fs-md);
  font-weight: 550;
  text-align: left;
  transition: background-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}

.nav-item svg {
  color: var(--ink-4);
  transition: color var(--t-fast) var(--ease);
}

.nav-item span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.nav-item:hover {
  background: var(--paper-2);
  color: var(--ink);
}

.nav-item:hover svg {
  color: var(--ink-2);
}

/* Active state is ink, not brand red — see DESIGN_SYSTEM.md section 1. */
.nav-item.is-active {
  background: var(--paper-3);
  color: var(--ink);
  font-weight: 650;
}

.nav-item.is-active svg {
  color: var(--ink);
}

.nav-item.is-active::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  top: 50%;
  width: 3px;
  height: 18px;
  transform: translateY(-50%);
  border-radius: var(--r-full);
  background: var(--ink);
}

.sidebar-footer {
  padding-top: var(--s-3);
  border-top: 1px solid var(--line);
}

/* -- main column ----------------------------------------------------------- */
.main-shell {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 100vh;
  min-height: 100dvh;
  background: var(--paper-2);
}

.topbar {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-5) var(--s-6) var(--s-4);
  background: var(--paper-2);
  border-bottom: 1px solid var(--line);
}

.topbar h1 {
  font-size: var(--fs-2xl);
  letter-spacing: var(--tr-tight);
}

.eyebrow {
  margin-bottom: 2px;
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  color: var(--ink-3);
}

.topbar-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
}

.auth-bar input {
  width: 130px;
}

.content {
  flex: 1 1 auto;
  width: 100%;
  max-width: var(--content-max);
  padding: var(--s-6);
}

.student-view,
.admin-view {
  display: flex;
  flex-direction: column;
  gap: var(--s-6);
}

/* -- section heads: type carries hierarchy, not a coloured band ------------ */
.section-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s-4);
}

.section-head h2 {
  font-size: var(--fs-xl);
  letter-spacing: var(--tr-tight);
}

.section-head p {
  max-width: 68ch;
  margin-top: var(--s-1);
  color: var(--ink-3);
  font-size: var(--fs-sm);
}

.section-head.compact h2 {
  font-size: var(--fs-lg);
}

/* =============================================================================
   06 — CONTROLS
   ============================================================================= */

.primary-button,
.secondary-button,
.ghost-button,
.text-button,
.file-button,
.teacher-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: var(--hit);
  padding: 0 var(--s-4);
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  transition: background-color var(--t-fast) var(--ease),
    border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease),
    opacity var(--t-fast) var(--ease);
}

/* Primary is ink, not brand red. Brand red is answer-state adjacent and is
   reserved for Mode A plus the logo lockup — DESIGN_SYSTEM.md section 1. */
.primary-button {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

.primary-button:hover {
  background: var(--ink-2);
  border-color: var(--ink-2);
}

.secondary-button,
.file-button,
.teacher-button {
  background: var(--paper);
  border-color: var(--line-2);
  color: var(--ink);
}

.secondary-button:hover,
.file-button:hover,
.teacher-button:hover {
  background: var(--paper-3);
  border-color: var(--ink-4);
}

.ghost-button {
  background: transparent;
  color: var(--ink-2);
}

.ghost-button:hover {
  background: var(--paper-3);
  color: var(--ink);
}

.text-button {
  min-height: auto;
  padding: 0;
  background: none;
  color: var(--ink-2);
  text-decoration: underline;
  text-decoration-color: var(--line-2);
  text-underline-offset: 3px;
}

.text-button:hover {
  color: var(--ink);
  text-decoration-color: var(--ink);
}

.small-button {
  min-height: var(--hit-sm);
  padding: 0 var(--s-3);
  font-size: var(--fs-xs);
}

/* Destructive is outline-only. A solid red fill means exactly one thing in this
   app: an incorrect answer. */
.danger-button,
.secondary-button.danger-button,
.ghost-button.danger-button {
  background: transparent;
  border-color: var(--wrong-line);
  color: var(--danger);
}

.danger-button:hover,
.secondary-button.danger-button:hover,
.ghost-button.danger-button:hover {
  background: var(--wrong-wash);
  border-color: var(--danger);
  color: var(--danger);
}

button:disabled,
.is-disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.file-button {
  position: relative;
  overflow: hidden;
  cursor: pointer;
}

.file-button input {
  position: absolute;
  inset: 0;
  opacity: 0;
  cursor: pointer;
}

/* -- chips, tabs, filters -------------------------------------------------- */
.chip-group,
.exam-tabs,
.tag-cluster {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
}

.exam-tabs {
  padding-bottom: var(--s-1);
  border-bottom: 1px solid var(--line);
}

.chip,
.tab-button {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-height: var(--hit-sm);
  padding: 0 var(--s-3);
  border: 1px solid var(--line-2);
  border-radius: var(--r-full);
  background: var(--paper);
  color: var(--ink-2);
  font-size: var(--fs-sm);
  font-weight: 550;
  transition: background-color var(--t-fast) var(--ease),
    border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}

.chip:hover,
.tab-button:hover {
  background: var(--paper-3);
  color: var(--ink);
}

.tab-button span {
  padding: 1px var(--s-2);
  border-radius: var(--r-full);
  background: var(--paper-3);
  color: var(--ink-3);
  font-size: var(--fs-2xs);
  font-weight: 650;
  font-variant-numeric: tabular-nums;
}

.chip.is-active,
.tab-button.is-active {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

/* Neutral mid-grey overlay: reads as a subtle inset on the ink-filled pill in
   both themes, where a fixed white or black overlay would vanish in one. */
.tab-button.is-active span {
  background: rgba(128, 128, 128, 0.35);
  color: var(--paper);
}

.category-tag {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  padding: 2px var(--s-2);
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
  background: var(--paper-2);
  color: var(--ink-3);
  font-size: var(--fs-2xs);
  font-weight: 600;
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
}

.category-tag svg {
  width: 12px;
  height: 12px;
}

.nav-arrow {
  display: grid;
  place-items: center;
  width: var(--hit-sm);
  height: var(--hit-sm);
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  background: var(--paper);
  color: var(--ink-2);
  transition: background-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}

.nav-arrow:hover {
  background: var(--paper-3);
  color: var(--ink);
}

/* =============================================================================
   07 — FORMS
   ============================================================================= */

input[type="text"],
input[type="search"],
input[type="password"],
input[type="number"],
input[type="date"],
input[type="email"],
input[type="url"],
select,
textarea {
  width: 100%;
  min-height: var(--hit);
  padding: var(--s-2) var(--s-3);
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  background: var(--paper);
  color: var(--ink);
  font-size: var(--fs-sm);
  transition: border-color var(--t-fast) var(--ease), background-color var(--t-fast) var(--ease);
}

textarea {
  min-height: 0;
  padding: var(--s-3);
  line-height: var(--lh-base);
  resize: vertical;
}

select {
  appearance: none;
  padding-right: var(--s-8);
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 18px) 52%, calc(100% - 13px) 52%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  cursor: pointer;
}

input:hover,
select:hover,
textarea:hover {
  border-color: var(--ink-4);
}

input:focus,
select:focus,
textarea:focus {
  border-color: var(--ink);
  outline: none;
}

input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 1px;
}

input::placeholder,
textarea::placeholder {
  color: var(--ink-4);
}

input:disabled,
select:disabled,
textarea:disabled {
  background: var(--paper-3);
  color: var(--ink-3);
  cursor: not-allowed;
}

input[type="checkbox"],
input[type="radio"] {
  width: 16px;
  height: 16px;
  min-height: 0;
  flex: none;
  accent-color: var(--ink);
  cursor: pointer;
}

.form-field,
.editor-field,
.saved-note-field,
.paste-leech-box,
.media-upload-field,
.compact-media-upload {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  min-width: 0;
}

.form-field > span,
.editor-field > span,
.saved-note-field > span,
.paste-leech-box > span,
.media-upload-field > span,
.mistake-controls label > span,
.mistake-why-label > span {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  color: var(--ink-3);
}

.form-field.grow {
  grid-column: span 2;
}

.form-field.compact-field {
  grid-column: span 1;
}

.filter-panel {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(220px, 320px) minmax(0, 1fr);
  align-items: start;
  gap: var(--s-4);
  padding: var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper);
}

.filter-row {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  min-width: 0;
}

.filter-row > span,
.search-box > span {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  color: var(--ink-3);
}

.search-box {
  display: grid;
  grid-template-areas: "label label" "icon input";
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: var(--s-2);
  min-width: 0;
}

.search-box > span {
  grid-area: label;
}

.search-box > svg {
  grid-area: icon;
  margin-left: var(--s-3);
  color: var(--ink-4);
}

.search-box input {
  grid-area: input;
  margin-left: calc(var(--s-8) * -1);
  padding-left: var(--s-8);
}

.check-row {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  font-size: var(--fs-sm);
  color: var(--ink-2);
  cursor: pointer;
}

.dropzone {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s-4);
  padding: var(--s-5);
  border: 1px dashed var(--line-2);
  border-radius: var(--r-md);
  background: var(--paper-2);
}

.dropzone > svg {
  width: 26px;
  height: 26px;
  color: var(--ink-4);
}

.dropzone div {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.dropzone strong {
  font-size: var(--fs-sm);
}

.dropzone span {
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

.paste-leech-box textarea {
  min-height: 160px;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  line-height: 1.6;
}

.answer-key-select {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
}

.answer-key-select input {
  width: 68px;
  min-height: var(--hit-sm);
  text-align: center;
  font-weight: 650;
}

.media-upload-field input,
.compact-media-upload input {
  padding: var(--s-1);
  border-style: dashed;
  font-size: var(--fs-xs);
}

.media-upload-field input::file-selector-button,
.compact-media-upload input::file-selector-button {
  margin-right: var(--s-3);
  padding: var(--s-1) var(--s-3);
  border: 1px solid var(--line-2);
  border-radius: var(--r-xs);
  background: var(--paper-3);
  color: var(--ink-2);
  font-size: var(--fs-xs);
  font-weight: 600;
  cursor: pointer;
}

.compact-media-upload {
  gap: var(--s-1);
}

/* =============================================================================
   08 — SURFACES
   Cards, pills, modals, toasts, empty states.
   ============================================================================= */

/* -- pills: low chroma. A grid of cards must not read as confetti. --------- */
.status-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  padding: 3px var(--s-2);
  border: 1px solid var(--line-2);
  border-radius: var(--r-full);
  background: var(--paper-2);
  color: var(--ink-3);
  font-size: var(--fs-2xs);
  font-weight: 650;
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  white-space: nowrap;
}

.status-pill.green {
  border-color: var(--ok-line);
  background: var(--ok-wash);
  color: var(--ok);
}

.status-pill.red {
  border-color: var(--wrong-line);
  background: var(--wrong-wash);
  color: var(--wrong);
}

.status-pill.amber {
  border-color: var(--warn-line);
  background: var(--warn-wash);
  color: var(--warn);
}

.status-pill.blue {
  border-color: var(--info-line);
  background: var(--info-wash);
  color: var(--info);
}

.status-pill.gray {
  border-color: var(--line-2);
  background: var(--paper-3);
  color: var(--ink-3);
}

.status-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  flex: none;
  border-radius: var(--r-full);
  background: var(--ink-4);
}

.status-dot.green { background: var(--ok); }
.status-dot.red { background: var(--wrong); }
.status-dot.amber { background: var(--warn); }
.status-dot.blue { background: var(--info); }

.teacher-mode-pill,
.teacher-header-chip,
.dsat-q-pill,
.folder-count {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  padding: 3px var(--s-3);
  border: 1px solid var(--line-2);
  border-radius: var(--r-full);
  background: var(--paper-2);
  color: var(--ink-2);
  font-size: var(--fs-xs);
  font-weight: 600;
  white-space: nowrap;
}

.badge-stack,
.tag-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
}

.tag-row {
  justify-content: space-between;
}

.card-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-3);
}

.card-head p {
  max-width: 68ch;
  margin-top: var(--s-1);
  color: var(--ink-3);
  font-size: var(--fs-sm);
}

.card-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
}

.meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-4);
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

.meta span {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
}

.meta svg {
  width: 13px;
  height: 13px;
  color: var(--ink-4);
}

/* -- empty states ---------------------------------------------------------- */
.empty-state,
.folder-empty,
.teacher-empty,
.analytics-combo-empty,
.review-unavailable,
.practice-unavailable {
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
  padding: var(--s-8) var(--s-6);
  border: 1px dashed var(--line-2);
  border-radius: var(--r-md);
  background: var(--paper);
  text-align: center;
  color: var(--ink-3);
  font-size: var(--fs-sm);
}

.empty-state strong {
  color: var(--ink-2);
  font-size: var(--fs-md);
}

.empty-state.compact,
.folder-empty,
.teacher-empty,
.analytics-combo-empty,
.review-unavailable {
  padding: var(--s-4);
  font-size: var(--fs-xs);
}

.review-unavailable {
  border-style: solid;
  border-color: var(--line);
}

/* -- modals ---------------------------------------------------------------- */
.practice-modal-backdrop,
.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: center;
  padding: var(--s-6);
  background: var(--backdrop);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  overflow-y: auto;
}

.practice-modal,
.modal-box,
.auth-modal,
.confirm-modal,
.note-popup,
.json-editor-modal,
.delete-test-modal,
.ai-key-modal,
.vocab-lookup-modal,
.admin-question-modal,
.admin-edit-modal,
.module-review-modal {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  width: min(560px, 100%);
  max-height: 86vh;
  padding: var(--s-6);
  overflow-y: auto;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--paper);
  box-shadow: var(--e-3);
}

.admin-question-modal,
.admin-edit-modal,
.json-editor-modal {
  width: min(880px, 100%);
}

.vocab-lookup-modal,
.module-review-modal {
  width: min(640px, 100%);
}

.practice-modal h2,
.modal-box h2,
.auth-modal h2,
.confirm-modal h2 {
  font-size: var(--fs-xl);
}

.practice-modal p,
.confirm-modal p {
  color: var(--ink-2);
  font-size: var(--fs-sm);
}

.modal-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-3);
}

.modal-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s-2);
  padding-top: var(--s-2);
  border-top: 1px solid var(--line);
}

.modal-close {
  display: grid;
  place-items: center;
  width: var(--hit);
  height: var(--hit);
  flex: none;
  border-radius: var(--r-sm);
  color: var(--ink-3);
  transition: background-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}

.modal-close:hover {
  background: var(--paper-3);
  color: var(--ink);
}

.modal-footnote {
  color: var(--ink-3);
  font-size: var(--fs-xs);
  text-align: center;
}

.modal-switch-link {
  background: none;
  color: var(--ink);
  font-size: var(--fs-xs);
  font-weight: 650;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.delete-warning {
  padding: var(--s-3);
  border: 1px solid var(--wrong-line);
  border-radius: var(--r-sm);
  background: var(--wrong-wash);
  color: var(--wrong);
  font-size: var(--fs-sm);
}

/* -- toast + sync ---------------------------------------------------------- */
/* Top-centred: the anti-inspection guard uses this, and a message about a key
   the student just pressed has to land where they are already looking. Above
   every overlay including the practice player (30), teacher panel (40), AI
   drawer (45) and modals (60). */
.app-toast {
  position: fixed;
  inset-inline: 0;
  top: var(--s-4);
  z-index: 80;
  width: max-content;
  max-width: min(420px, calc(100vw - var(--s-8)));
  margin-inline: auto;
  padding: var(--s-3) var(--s-5);
  border: 1px solid var(--line-2);
  border-radius: var(--r-full);
  background: var(--ink);
  color: var(--paper);
  font-size: var(--fs-sm);
  font-weight: 550;
  box-shadow: var(--e-3);
  transition: opacity var(--t-base) var(--ease);
}

/* --paper is the correct foreground on any --ok/--wrong fill in BOTH themes,
   because the state tokens flip light/dark in the opposite direction to the
   neutral ramp. A literal #ffffff here would be 1.9:1 on dark theme's salmon. */
.app-toast.error {
  border-color: var(--wrong);
  background: var(--wrong);
  color: var(--paper);
}

.app-toast.is-hiding {
  opacity: 0;
}

.sync-indicator {
  position: fixed;
  right: var(--s-4);
  bottom: var(--s-4);
  z-index: 70;
  display: none;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-3);
  border: 1px solid var(--line-2);
  border-radius: var(--r-full);
  background: var(--paper);
  color: var(--ink-3);
  font-size: var(--fs-xs);
  box-shadow: var(--e-2);
}

.sync-indicator.is-visible {
  display: inline-flex;
}

.sync-indicator.is-offline {
  border-color: var(--warn-line);
  background: var(--warn-wash);
  color: var(--warn);
}

/* =============================================================================
   09 — STUDENT HOME
   ============================================================================= */

/* Direction B gives the opening row a hierarchy it did not have.

   Measured on the old rule: `repeat(4, minmax(0,1fr))` produced four cards of
   exactly 259 × 106.7 — "Tests completed", "Completed Drills", "Average
   accuracy", "Mistake Notebook". Four equal columns assert four equally
   important numbers. They are not: accuracy is the one a student acts on, and
   two of the four read 0 for every new account, so the app opened on a row of
   zeroes given the same billing as the only figure that matters.

   Accuracy is the third card in source order; `order: -1` moves it into the
   wide leading column without touching the markup. */
.metric-grid {
  display: grid;
  grid-template-columns: 1.7fr repeat(3, minmax(0, 1fr));
  gap: var(--s-3);
  align-items: stretch;
}

.metric-grid > .metric-card:nth-child(3) {
  order: -1;
  border-color: var(--line-2);
  background: var(--paper-2);
}

.metric-grid > .metric-card:nth-child(3) strong {
  font-size: var(--fs-3xl);
}

/* The two standing counts stop competing with it. */
.metric-grid > .metric-card:nth-child(1) strong,
.metric-grid > .metric-card:nth-child(2) strong {
  font-size: var(--fs-xl);
  color: var(--ink-2);
}

.metric-card {
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
  padding: var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper);
}

.metric-card span {
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  color: var(--ink-3);
}

.metric-card strong {
  font-size: var(--fs-2xl);
  font-weight: 650;
  letter-spacing: var(--tr-tight);
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}

.metric-card small {
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

/* The tests were 354px below the fold.

   Measured: the student home is 1584px tall against a 768px viewport, and
   #testGrid began at y 1122 — behind four metric cards, two insight cards, a
   completed-tests panel and a full-year heatmap. Starting a test is the reason
   the app gets opened.

   #studentView is a flex column, so `order` moves the heatmap below the grid
   without touching index.html, where every id is a contract with script.js.
   The heatmap is 385px on its own — it is the single largest thing standing
   between a student and the thing they came for, and it is a reward for having
   practised, not a task. It reads better after the tests than before them. */
.student-view > #studentActivity {
  order: 1;
}

.student-insights {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-3);
}

.insight-card {
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
  padding: var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper);
}

.insight-card strong {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  font-size: var(--fs-sm);
}

.insight-card strong svg {
  color: var(--ink-4);
}

.insight-card span {
  color: var(--ink-2);
  font-size: var(--fs-sm);
}

.access-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper);
  font-size: var(--fs-sm);
}

.access-strip div {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-width: 0;
  color: var(--ink-2);
}

.access-strip svg {
  color: var(--ink-4);
}

.access-strip strong {
  font-size: var(--fs-xs);
  font-weight: 650;
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  color: var(--ink-3);
}

/* -- test cards ------------------------------------------------------------ */
/* Was a fixed 3-up of 349.3 × 241.9 cards, every one identical — the layout
   did none of the sorting a student has to do otherwise.

   auto-fill lets the row count follow the viewport instead of the stylesheet,
   and any test the student has already touched — the emitter only writes
   [data-delete-progress] when there is an attempt or saved progress — takes
   two columns. "Carry on with the thing you started" is the most likely next
   action on this screen, so it is the one the grid makes largest. */
.test-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: var(--s-3);
}

.test-grid > .test-card:has([data-delete-progress]) {
  grid-column: span 2;
  border-color: var(--line-2);
}

/* At one column there is nothing to span, and the rule would overflow. */
@media (max-width: 719px) {
  .test-grid > .test-card:has([data-delete-progress]) {
    grid-column: auto;
  }
}

.test-grid.is-grouped {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
}

.test-card {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  padding: var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper);
  transition: border-color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}

.test-card:hover {
  border-color: var(--line-2);
  box-shadow: var(--e-1);
}

.test-card-header {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}

.test-card h3 {
  font-size: var(--fs-base);
  line-height: var(--lh-snug);
}

.test-card .tag-row {
  margin-top: auto;
  padding-top: var(--s-3);
  border-top: 1px solid var(--line);
}

/* -- folder grouping (student view) ---------------------------------------- */
.student-folder-group,
.folder-group {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper);
  overflow: hidden;
}

.folder-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  padding: var(--s-2) var(--s-3);
  background: var(--paper-2);
  border-bottom: 1px solid var(--line);
}

.student-folder-group.is-collapsed .folder-header,
.folder-group.is-collapsed .folder-header {
  border-bottom-color: transparent;
}

.student-folder-group.is-collapsed .folder-body,
.folder-group.is-collapsed .folder-body {
  display: none;
}

.folder-toggle {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  min-height: var(--hit-sm);
  min-width: 0;
  flex: 1 1 auto;
  color: var(--ink);
  text-align: left;
}

.folder-toggle svg {
  color: var(--ink-4);
}

.folder-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--fs-sm);
  font-weight: 650;
}

.folder-count {
  padding: 1px var(--s-2);
  font-size: var(--fs-2xs);
  font-variant-numeric: tabular-nums;
}

.folder-header-actions {
  display: flex;
  align-items: center;
  gap: var(--s-1);
}

.folder-header-actions .ghost-button {
  min-height: var(--hit-sm);
  padding: 0 var(--s-2);
}

.folder-body {
  padding: var(--s-3);
}

.inner-test-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s-3);
}

.folder-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper);
}

.folder-toolbar-hint {
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

.folder-group.is-unfiled {
  border-style: dashed;
}

/* -- completed tests ------------------------------------------------------- */
.completed-tests-panel {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  padding: var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper);
}

.completed-tests-head strong {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  font-size: var(--fs-sm);
}

.completed-tests-head strong svg {
  color: var(--ink-4);
}

.completed-tests-head span {
  display: block;
  margin-top: 2px;
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

.completed-test-list {
  display: flex;
  flex-direction: column;
}

.completed-test-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(200px, 260px) auto;
  align-items: center;
  gap: var(--s-4);
  padding: var(--s-3) 0;
  border-top: 1px solid var(--line);
}

.completed-test-main {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.completed-test-main strong {
  font-size: var(--fs-sm);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.completed-test-main span {
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

.completed-test-score {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s-1) var(--s-3);
}

.completed-test-score strong {
  font-size: var(--fs-md);
  font-variant-numeric: tabular-nums;
}

.completed-test-score span {
  grid-column: 1 / -1;
  color: var(--ink-3);
  font-size: var(--fs-2xs);
}

.completed-test-actions {
  display: flex;
  align-items: center;
  gap: var(--s-2);
}

/* Accuracy meter: one neutral track, one ink fill. The tier classes shift
   weight, not hue — a 42% score is not an error state. */
/* Shares --bar-size and --bar-track with the admin column chart, so a meter
   reads the same whichever screen it is on. Was a bespoke 6px. */
.score-meter {
  height: var(--bar-size);
  overflow: hidden;
  border-radius: var(--r-full);
  background: var(--bar-track);
}

.score-meter span {
  display: block;
  height: 100%;
  border-radius: var(--r-full);
  background: var(--ink-4);
}

.score-meter .strong { background: var(--ink); }
.score-meter .steady { background: var(--ink-3); }
.score-meter .needs-work { background: var(--line-2); }

/* -- Phase 3: Direction A, Study Brief --------------------------------------
   The previous pass left four metric cards, two sentence cards and three
   separate filter bands in place. At 1366x768 the library began at y=719.
   This composition removes the metric row as an object, leads with one
   interpreted study brief, turns the former heatmap slot into ranked evidence,
   and makes tests a ledger instead of cards in a dashboard grid. */
.student-view > #studentHomeHead { order: -6; }
.student-view > #studentMetrics { order: -5; }
.student-view > #studentActivity { order: -4; }
.student-view > .student-library-toolbar { order: -3; }
.student-view > #testGrid { order: -2; }
.student-view > #studentInsights { order: -1; }

.student-view {
  gap: var(--s-4);
}

#studentMetrics:empty,
#studentActivity:empty {
  display: none;
}

#studentHomeHead {
  display: block;
}

.student-study-brief {
  display: grid;
  grid-template-columns: minmax(0, 1.9fr) minmax(240px, 0.65fr);
  gap: var(--s-5);
  padding: var(--s-4);
  border-top: 3px solid var(--ink);
  background: var(--paper);
}

.student-brief-copy {
  display: grid;
  align-content: start;
  justify-items: start;
  gap: var(--s-2);
}

.student-brief-copy h2 {
  max-width: 30ch;
  margin: 0;
  font-size: clamp(1.8rem, 2.4vw, 2.15rem);
  line-height: var(--lh-tight);
  letter-spacing: var(--tr-tight);
}

.student-brief-copy > p {
  max-width: 68ch;
  margin: 0;
  color: var(--ink-2);
  font-size: var(--fs-sm);
  line-height: var(--lh-base);
}

.student-brief-number {
  display: flex;
  align-items: baseline;
  gap: var(--s-3);
  margin-top: var(--s-1);
}

.student-brief-number strong {
  font-size: clamp(2.1rem, 3.2vw, 2.8rem);
  font-weight: 700;
  line-height: 0.95;
  letter-spacing: -0.05em;
  font-variant-numeric: tabular-nums;
}

.student-brief-number span,
.student-brief-side p,
.student-brief-facts {
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

.student-brief-side {
  display: grid;
  align-content: space-between;
  gap: var(--s-4);
  padding-left: var(--s-4);
  border-left: 1px solid var(--line);
}

.student-brief-side h3 {
  margin: var(--s-1) 0;
  font-size: var(--fs-lg);
  line-height: var(--lh-snug);
}

.student-brief-side p {
  margin: 0;
}

.student-brief-facts {
  display: none;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-4);
}

.student-brief-action {
  width: 100%;
}

.section-kicker {
  color: var(--ink-3);
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
}

.status-pill.neutral {
  border-color: var(--line);
  color: var(--ink-2);
  background: var(--paper-2);
}

/* This is the heatmap replacement. At n=0 it is a bounded empty state; at
   n>=1 it is a zero-based horizontal ranking with the denominator printed. */
.student-progress-panel {
  display: grid;
  gap: var(--s-2);
  padding: var(--s-3);
  border: 1px solid var(--line);
  background: var(--paper);
}

.student-progress-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--s-6);
  align-items: center;
}

.student-progress-head h2 {
  margin: var(--s-1) 0 0;
  font-size: var(--fs-lg);
}

.student-progress-head p {
  margin: var(--s-1) 0 0;
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

.exam-countdown {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  min-width: 235px;
  min-height: 38px;
}

.exam-countdown > div {
  display: flex;
  align-items: baseline;
  gap: var(--s-2);
}

.exam-countdown strong {
  font-size: var(--fs-xl);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.exam-countdown span,
.exam-countdown small {
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

.exam-countdown.is-unset {
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0 var(--s-2);
  align-items: center;
  min-width: 0;
  padding: var(--s-2) 0;
  cursor: pointer;
}

.exam-countdown.is-unset > span {
  color: var(--ink);
  font-size: var(--fs-sm);
  font-weight: 650;
  border-bottom: 1px solid var(--ink-3);
}

.exam-countdown.is-unset > small {
  align-self: center;
}

.exam-countdown.is-unset input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}

.exam-date-edit {
  position: relative;
  margin-left: auto;
  color: var(--ink-3);
  font-size: var(--fs-xs);
  cursor: pointer;
}

.exam-date-edit input {
  position: absolute;
  inset: 0;
  width: 100%;
  opacity: 0;
  cursor: pointer;
}

.fix-next-list {
  display: grid;
}

.fix-next-row {
  display: grid;
  grid-template-columns: minmax(150px, 1.2fr) minmax(160px, 2fr) 54px auto;
  gap: var(--s-4);
  align-items: center;
  min-height: 40px;
  border-top: 1px solid var(--line);
}

.fix-next-label {
  display: grid;
  gap: 1px;
  min-width: 0;
}

.fix-next-label strong {
  overflow: hidden;
  font-size: var(--fs-sm);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.fix-next-label span {
  color: var(--ink-3);
  font-size: var(--fs-2xs);
}

.fix-next-bar,
.analytics-bar,
.wrong-answer-bar-track {
  height: var(--bar-size);
  overflow: hidden;
  border-radius: var(--r-full);
  background: var(--bar-track);
}

.fix-next-bar > span,
.analytics-bar > span,
.wrong-answer-bar-track > span {
  display: block;
  width: var(--bar-value);
  height: 100%;
  border-radius: inherit;
  background: var(--ink-3);
}

.fix-next-row.is-weak .fix-next-bar > span {
  background: var(--warn);
}

.fix-next-row output {
  color: var(--ink-2);
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.fix-next-row .text-button {
  color: var(--ink-2);
  font-size: var(--fs-xs);
}

.student-progress-empty {
  min-height: 112px;
}

/* One toolbar replaces the exam chip band, access card and 133px filter card. */
.student-library-toolbar {
  display: grid;
  grid-template-columns: 180px minmax(0, 1fr);
  gap: var(--s-3) var(--s-5);
  align-items: center;
  padding: var(--s-4);
  border-top: 1px solid var(--ink);
  border-bottom: 1px solid var(--line);
  background: var(--paper);
}

.student-library-heading strong {
  display: block;
  margin-top: 2px;
  font-size: var(--fs-sm);
}

.student-library-toolbar .access-strip {
  display: none;
}

.student-library-toolbar .access-strip > div {
  display: none;
}

.student-library-toolbar .exam-tabs {
  display: none;
}

.student-library-toolbar .filter-panel {
  grid-column: 2;
  display: grid;
  grid-template-columns: minmax(420px, 1.25fr) minmax(260px, 0.75fr);
  gap: var(--s-4);
  align-items: end;
  padding: 0;
  border: 0;
  background: transparent;
}

.student-library-toolbar .filter-row {
  min-width: 0;
}

.student-library-toolbar .chip-group {
  gap: var(--s-1);
}

/* Tests are now rows. The in-progress row is structurally and visually
   different from both completed and untouched tests. */
#testGrid,
.inner-test-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
  border-top: 1px solid var(--line);
  background: var(--paper);
}

#testGrid:not(.is-grouped) {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  overflow: hidden;
}

#testGrid.is-grouped {
  gap: 16px;
  border-top: 0;
  background: transparent;
}

.student-folder-group .folder-body {
  padding: 0;
  background: var(--paper);
}

.student-folder-group .inner-test-grid {
  border-top: 0;
  background: transparent;
}

.student-folder-group .test-ledger-row:last-child,
#testGrid:not(.is-grouped) > .test-ledger-row:last-child {
  border-bottom: 0;
}

#testGrid > .test-card:has([data-delete-progress]),
.inner-test-grid > .test-card:has([data-delete-progress]) {
  grid-column: auto;
}

.test-ledger-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 112px auto;
  gap: var(--s-4);
  align-items: center;
  min-height: 76px;
  padding: var(--s-3) var(--s-4);
  border: 0;
  border-bottom: 1px solid var(--line);
  border-radius: 0;
  background: var(--paper);
  box-shadow: none;
}

.test-ledger-row:hover {
  box-shadow: none;
  background: var(--paper-2);
}

.test-ledger-main {
  min-width: 0;
}

.test-ledger-main h3 {
  margin: var(--s-1) 0;
  overflow: hidden;
  font-size: var(--fs-md);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.test-ledger-main > p {
  display: flex;
  align-items: center;
  gap: var(--s-1);
  margin: 0;
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

.test-ledger-main > p svg {
  width: 13px;
  height: 13px;
}

.test-ledger-kicker {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
  color: var(--ink-3);
  font-size: var(--fs-2xs);
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
}

.test-ledger-score {
  display: grid;
  justify-items: end;
  font-variant-numeric: tabular-nums;
}

.test-ledger-score strong {
  font-size: var(--fs-md);
}

.test-ledger-score span {
  color: var(--ink-3);
  font-size: var(--fs-2xs);
}

.test-ledger-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s-2);
}

.test-ledger-row.is-featured {
  order: -1;
  margin: var(--s-2) 0;
  border: 1px solid var(--ink);
  background: var(--ink);
  color: var(--paper);
}

.test-ledger-row.is-featured:hover {
  background: var(--ink);
}

.test-ledger-row.is-featured .test-ledger-main > p,
.test-ledger-row.is-featured .test-ledger-kicker,
.test-ledger-row.is-featured .test-ledger-score span {
  color: var(--paper-3);
}

.test-ledger-row.is-featured .test-ledger-main h3,
.test-ledger-row.is-featured .test-ledger-score strong {
  color: var(--paper);
}

.test-ledger-row.is-featured .secondary-button,
.test-ledger-row.is-featured .primary-button {
  border-color: var(--paper);
  background: var(--paper);
  color: var(--ink);
}

.test-ledger-row.is-featured .ghost-button {
  color: var(--paper-3);
}

.completed-tests-disclosure {
  padding: 0;
  border-radius: 0;
}

.completed-tests-disclosure > summary {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s-4);
  min-height: 52px;
  padding: var(--s-3) var(--s-4);
  cursor: pointer;
  list-style: none;
}

.completed-tests-disclosure > summary::-webkit-details-marker {
  display: none;
}

.completed-tests-disclosure > summary > span:first-child {
  display: grid;
  gap: 2px;
}

.completed-tests-disclosure > summary small,
.completed-tests-disclosure > summary > span:last-child {
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

.completed-tests-disclosure .completed-test-list {
  padding: 0 var(--s-4) var(--s-3);
}

/* =============================================================================
   10 — STUDENT PANELS
   Saved questions, materials, skill analytics, mixed test, mistake notebook,
   vocab, settings. All share one stacked panel rhythm capped at
   --s-12, per the owner constraint "do not make the UI huge".
   ============================================================================= */

.saved-questions-panel,
.materials-panel,
.skill-analytics-panel,
.mixed-test-panel,
.mistake-notebook-panel,
.vocab-panel,
.student-settings-panel {
  display: flex;
  flex-direction: column;
  gap: var(--s-5);
}

/* -- saved questions ------------------------------------------------------- */
.saved-question-list,
.material-list,
.mistake-list,
.mixed-list {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}

.saved-question-card,
.material-card,
.mistake-card,
.mixed-card,
.analytics-card,
.settings-card,
.drill-card,
.vocab-card,
.attempt-extra-card,
.class-card {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  padding: var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper);
}

.saved-question-head,
.material-head,
.mistake-head,
.vocab-card-head,
.explanation-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-3);
}

.saved-question-head > div:first-child {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.saved-question-head strong {
  font-size: var(--fs-sm);
}

.saved-question-head span {
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

/* Reading copy inside product surfaces uses the exam serif. */
.saved-question-body,
.mistake-body,
.material-content {
  max-width: var(--measure);
  color: var(--ink-2);
  font-family: var(--font-read);
  font-size: var(--fs-md);
  line-height: var(--lh-read);
}

.saved-question-body p + p,
.mistake-body p + p,
.material-content p + p {
  margin-top: var(--s-3);
}

.material-content a {
  color: var(--ink);
}

.saved-ai-details,
.explanation-editor {
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--paper-2);
}

.saved-ai-details > summary,
.explanation-editor > summary {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-3);
  font-size: var(--fs-xs);
  font-weight: 650;
  color: var(--ink-2);
}

.saved-ai-details > *:not(summary),
.explanation-editor > *:not(summary) {
  padding: 0 var(--s-3) var(--s-3);
}

.explanation-panel {
  padding: var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--paper-2);
  font-size: var(--fs-sm);
}

.explanation-panel ol {
  margin: 0;
  padding-left: var(--s-5);
}

/* -- materials ------------------------------------------------------------- */
.material-recommendations > div {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-2);
  padding: var(--s-2) 0;
  border-top: 1px solid var(--line);
  font-size: var(--fs-sm);
}

.material-recommendations > div span {
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

/* -- skill analytics ------------------------------------------------------- */
.analytics-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-3);
}

.analytics-card h3 {
  font-size: var(--fs-sm);
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  color: var(--ink-3);
}

.analytics-row-list {
  display: flex;
  flex-direction: column;
}

.analytics-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  padding: var(--s-2) 0;
  border-top: 1px solid var(--line);
}

.analytics-row > div {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.analytics-row strong {
  font-size: var(--fs-sm);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.analytics-row span {
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

.analytics-row b {
  font-size: var(--fs-md);
  font-variant-numeric: tabular-nums;
}

/* Weak skills are marked with warn, not wrong — a low score is a signal to
   study, not a graded mistake. */
.analytics-row.is-weak b {
  color: var(--warn);
}

/* -- mixed real test ------------------------------------------------------- */
.mixed-builder {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  padding: var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper);
}

.mixed-random-toggle {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  cursor: pointer;
}

.mixed-mode-toggles {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-3);
}

.mixed-mode-toggles .mixed-random-toggle {
  min-height: 68px;
  padding: var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--paper-2);
}

.mixed-mode-toggles .super-mix-toggle {
  background: color-mix(in srgb, var(--paper-2) 82%, #ca252b 18%);
}

.mixed-random-toggle input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.mixed-random-toggle > span:last-child {
  display: flex;
  flex-direction: column;
}

.mixed-random-toggle strong {
  font-size: var(--fs-sm);
}

.mixed-random-toggle small {
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

.mixed-switch {
  position: relative;
  width: 40px;
  height: 22px;
  flex: none;
  border-radius: var(--r-full);
  background: var(--line-2);
  transition: background-color var(--t-base) var(--ease);
}

.mixed-switch::after {
  content: "";
  position: absolute;
  top: 3px;
  left: 3px;
  width: 16px;
  height: 16px;
  border-radius: var(--r-full);
  background: var(--paper);
  transition: transform var(--t-base) var(--ease);
}

.mixed-random-toggle input:checked + .mixed-switch {
  background: var(--ink);
}

.mixed-random-toggle input:checked + .mixed-switch::after {
  transform: translateX(18px);
}

.mixed-random-toggle input:focus-visible + .mixed-switch {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.mixed-select-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-3);
}

.mixed-select-grid.is-disabled {
  opacity: 0.5;
}

.super-mix-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-4);
}

.super-mix-section {
  display: grid;
  gap: var(--s-3);
  min-width: 0;
}

.super-mix-section > header {
  display: flex;
  justify-content: space-between;
  gap: var(--s-3);
  padding-bottom: var(--s-2);
  border-bottom: 2px solid var(--ink);
}

.super-mix-section > header span {
  font-weight: 750;
}

.super-mix-section > header small {
  color: var(--ink-3);
}

.super-mix-slot {
  display: grid;
  gap: var(--s-3);
  padding: var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--paper);
}

.super-mix-slot-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3);
}

.super-mix-slot-head span {
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

.super-mix-slot-fields {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(160px, 0.65fr);
  gap: var(--s-3);
}

.super-mix-note {
  display: flex;
  align-items: flex-start;
  gap: var(--s-2);
  grid-column: 1 / -1;
  margin: 0;
  padding: var(--s-3);
  border-left: 3px solid var(--ink);
  background: var(--paper-2);
  color: var(--ink-2);
  font-size: var(--fs-sm);
}

.super-mix-note svg {
  flex: none;
  margin-top: 2px;
}

.mixed-pool-status {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  color: var(--ink-3);
  font-size: var(--fs-sm);
}

.mixed-pool-status svg {
  color: var(--ink-4);
}

.mixed-pool-status .mixed-pool-empty {
  color: var(--warn);
}

.mixed-card {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
}

.mixed-card-main {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.mixed-card-main strong {
  font-size: var(--fs-sm);
}

.mixed-card-sub,
.mixed-card-date {
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

.mixed-score {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  margin-top: var(--s-1);
  font-size: var(--fs-xs);
  font-variant-numeric: tabular-nums;
}

.mixed-score-correct { color: var(--ok); }
.mixed-score-incorrect { color: var(--wrong); }
.mixed-score-blank { color: var(--ink-3); }
.mixed-score-total { color: var(--ink-2); font-weight: 650; }

.mixed-card-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
}

/* -- mistake notebook ------------------------------------------------------ */
.mistake-info {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.mistake-info strong {
  font-size: var(--fs-sm);
}

.mistake-info span {
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

.mistake-controls,
.mistake-review-controls {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2fr) auto auto;
  align-items: end;
  gap: var(--s-3);
  padding-top: var(--s-3);
  border-top: 1px solid var(--line);
}

.mistake-controls label {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  min-width: 0;
}

.mistake-why-label {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}

.wrong-reason-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-3);
}

/* -- drill builder --------------------------------------------------------- */
.drill-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s-3);
}

.drill-status {
  color: var(--ink-3);
  font-size: var(--fs-sm);
}

/* -- vocab ----------------------------------------------------------------- */
.vocab-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-3);
}

.vocab-card h3 {
  font-size: var(--fs-sm);
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  color: var(--ink-3);
}

.vocab-dashboard {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: var(--s-2);
}

.vocab-dashboard article {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--paper-2);
}

.vocab-dashboard span {
  color: var(--ink-3);
  font-size: var(--fs-2xs);
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
}

.vocab-dashboard strong {
  font-size: var(--fs-lg);
  font-variant-numeric: tabular-nums;
}

.vocab-review-card {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  padding: var(--s-4);
  border: 1px solid var(--line-2);
  border-radius: var(--r-md);
  background: var(--paper-2);
}

.vocab-card-head > div:first-child {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.vocab-card-head span {
  color: var(--ink-3);
  font-size: var(--fs-2xs);
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
}

.vocab-card-head strong {
  font-size: var(--fs-2xl);
  font-weight: 650;
  letter-spacing: var(--tr-tight);
}

.vocab-card-head small {
  color: var(--ink-3);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
}

.vocab-meaning-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-3);
}

.vocab-meaning-grid > div {
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
  min-width: 0;
}

.vocab-meaning-grid span {
  color: var(--ink-3);
  font-size: var(--fs-2xs);
  font-weight: 600;
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
}

.vocab-meaning-grid p {
  color: var(--ink-2);
  font-family: var(--font-ui);
  font-size: var(--fs-md);
  font-weight: 450;
  line-height: 1.6;
  letter-spacing: normal;
  text-rendering: optimizeLegibility;
}

.vocab-review-actions {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--s-2);
}

/* One weight system for all four ratings. The old file gave each a saturated
   hue; the rating is a self-report, not a grade. */
.vocab-rating-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  min-height: var(--hit-lg);
  padding: var(--s-2);
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  background: var(--paper);
  color: var(--ink);
  font-size: var(--fs-sm);
  font-weight: 650;
  transition: background-color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}

.vocab-rating-btn small {
  color: var(--ink-3);
  font-size: var(--fs-2xs);
  font-weight: 500;
}

.vocab-rating-btn:hover {
  background: var(--paper-3);
  border-color: var(--ink-4);
}

.vocab-done-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-10) var(--s-4);
  border: 1px dashed var(--line-2);
  border-radius: var(--r-md);
  text-align: center;
}

.vocab-done-icon {
  font-size: 34px;
  line-height: 1;
}

.vocab-done-state strong {
  font-size: var(--fs-lg);
}

.vocab-done-state span {
  max-width: 46ch;
  color: var(--ink-3);
  font-size: var(--fs-sm);
}

.vocab-deck-list,
.vocab-card-list {
  display: flex;
  flex-direction: column;
}

.vocab-deck-row,
.vocab-card-row {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2) 0;
  border-top: 1px solid var(--line);
}

.vocab-deck-row:first-child,
.vocab-card-row:first-child {
  border-top: 0;
}

.vocab-deck-row.is-active {
  padding-inline: var(--s-2);
  border-radius: var(--r-sm);
  background: var(--paper-3);
}

.vocab-deck-select-btn,
.vocab-card-word-btn {
  display: flex;
  flex-direction: column;
  gap: 1px;
  flex: 1 1 auto;
  min-width: 0;
  min-height: var(--hit-sm);
  justify-content: center;
  text-align: left;
}

.vocab-deck-select-btn strong,
.vocab-card-word-btn strong {
  font-size: var(--fs-sm);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.vocab-deck-select-btn span,
.vocab-card-word-btn span {
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

.vocab-deck-select-btn:hover strong,
.vocab-card-word-btn:hover strong {
  text-decoration: underline;
  text-underline-offset: 2px;
}

.vocab-card-actions {
  display: flex;
  align-items: center;
  gap: var(--s-1);
}

.vocab-card-actions .ghost-button {
  min-height: var(--hit-sm);
  padding: 0 var(--s-2);
}

.vocab-form-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-3);
}

.vocab-lookup-modal {
  position: relative;
  width: min(920px, 100%);
  max-height: min(900px, calc(100dvh - 48px));
  gap: 0;
  padding: 0;
  overflow: hidden;
}

.vocab-lookup-modal > .modal-close {
  position: absolute;
  z-index: 2;
  top: var(--s-4);
  right: var(--s-4);
  border: 1px solid var(--line);
  background: color-mix(in srgb, var(--paper) 92%, transparent);
}

.vocab-lookup-head {
  padding: var(--s-5) calc(var(--s-6) + var(--hit)) var(--s-4) var(--s-6);
  border-bottom: 1px solid var(--line);
  background:
    linear-gradient(135deg, color-mix(in srgb, var(--paper-3) 56%, transparent), transparent 62%),
    var(--paper);
}

.vocab-lookup-kicker {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  margin-bottom: var(--s-2);
  color: var(--ink-3);
  font-size: var(--fs-xs);
  font-weight: 650;
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
}

.vocab-lookup-kicker .ai-tutor-mark {
  width: 18px;
  height: 18px;
  color: var(--ink);
}

.vocab-lookup-head h2 {
  max-width: 20ch;
  margin: 0;
  color: var(--ink);
  font-family: var(--font-display);
  font-size: clamp(2rem, 4.2vw, 3.35rem);
  font-weight: 650;
  line-height: 1;
  letter-spacing: -0.035em;
  overflow-wrap: anywhere;
}

.vocab-lookup-head p {
  max-width: 58ch;
  margin: var(--s-2) 0 0;
  color: var(--ink-3);
}

.vocab-lookup-status {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  min-height: 28px;
  margin: var(--s-4) var(--s-6) 0;
  padding: 0;
  color: var(--ink-2);
  font-size: var(--fs-sm);
}

.vocab-lookup-status .ai-loading-spinner {
  width: 19px;
  height: 19px;
}

.vocab-lookup-status svg {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
}

.vocab-lookup-status.is-error {
  color: var(--wrong);
}

.vocab-lookup-status.is-info {
  color: var(--ink-3);
}

.vocab-lookup-layout {
  display: grid;
  grid-template-columns: minmax(0, 1.16fr) minmax(280px, 0.84fr);
  gap: var(--s-6);
  min-height: 0;
  padding: var(--s-5) var(--s-6);
  overflow-y: auto;
}

.vocab-lookup-primary,
.vocab-lookup-meta {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  min-width: 0;
}

.vocab-lookup-primary {
  padding-right: var(--s-6);
  border-right: 1px solid var(--line);
}

.vocab-lookup-modal .form-field > span {
  letter-spacing: 0.04em;
}

.vocab-lookup-modal textarea {
  resize: vertical;
}

.vocab-lookup-meaning-vi textarea {
  min-height: 176px;
  border-color: var(--line-2);
  font-size: var(--fs-md);
  line-height: 1.55;
}

.vocab-lookup-primary > .form-field:not(.vocab-lookup-meaning-vi, .vocab-mnemonic-field) textarea {
  min-height: 168px;
}

.vocab-mnemonic-field {
  padding: var(--s-3);
  border-left: 3px solid var(--warn);
  background: var(--warn-wash);
}

.vocab-mnemonic-field > span {
  color: var(--warn);
}

.vocab-mnemonic-field textarea {
  min-height: 78px;
  border-color: var(--warn-line);
  background: color-mix(in srgb, var(--paper) 84%, transparent);
}

.vocab-example-field {
  flex: 1 1 auto;
}

.vocab-example-field textarea {
  min-height: 224px;
}

.vocab-lookup-modal .modal-actions {
  position: sticky;
  bottom: 0;
  flex-wrap: nowrap;
  padding: var(--s-3) var(--s-6);
  background: var(--paper);
}

.vocab-cache-note {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  margin-right: auto;
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

.vocab-cache-note svg {
  width: 16px;
  height: 16px;
  flex: 0 0 auto;
}

.vocab-mnemonic-review {
  border-left: 3px solid var(--warn);
  background: var(--warn-wash);
}

.import-button-stack {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}

@media (max-width: 760px) {
  .vocab-lookup-modal {
    width: 100%;
    max-height: calc(100dvh - 24px);
  }

  .vocab-lookup-head {
    padding: var(--s-5) calc(var(--s-5) + var(--hit)) var(--s-4) var(--s-4);
  }

  .vocab-lookup-status {
    margin: var(--s-3) var(--s-4) 0;
  }

  .vocab-lookup-layout {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--s-5);
    padding: var(--s-4);
  }

  .vocab-lookup-primary {
    padding-right: 0;
    padding-bottom: var(--s-5);
    border-right: 0;
    border-bottom: 1px solid var(--line);
  }

  .vocab-lookup-modal .modal-actions {
    flex-wrap: wrap;
    padding: var(--s-3) var(--s-4);
  }

  .vocab-cache-note {
    width: 100%;
  }
}

/* -- settings -------------------------------------------------------------- */
.settings-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-3);
}

.settings-card h3 {
  font-size: var(--fs-sm);
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  color: var(--ink-3);
}

/* -- shared small grids ---------------------------------------------------- */
.tag-form,
.time-summary-grid,
.question-taxonomy-grid {
  display: grid;
  gap: var(--s-3);
}

.tag-form {
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: end;
}

.time-summary-grid {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.question-taxonomy-grid {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.summary-tile {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--paper-2);
}

.summary-tile small {
  color: var(--ink-3);
  font-size: var(--fs-2xs);
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
}

.summary-tile strong {
  font-size: var(--fs-lg);
  font-variant-numeric: tabular-nums;
}

/* -- Phase 3 panel composition ---------------------------------------------
   Layer 10 previously rendered every record as a fully expanded card, making
   8 mistakes 2242px tall, vocab 2440px and 12 saved questions 3465px. Long
   records are disclosures now. */
.learning-evidence-workspace,
.mixed-practice-workspace {
  display: grid;
  grid-template-columns: minmax(300px, 0.72fr) minmax(0, 1.55fr);
  gap: var(--s-5);
  align-items: start;
  min-width: 0;
}

.mixed-practice-workspace {
  grid-template-columns: minmax(0, 1fr);
}

.learning-evidence-workspace > *,
.mixed-practice-workspace > * {
  min-width: 0;
}

.skill-analytics-panel {
  padding: var(--s-4);
  border-top: 3px solid var(--ink);
  background: var(--paper);
}

.learning-evidence-workspace .section-head,
.mixed-practice-workspace .section-head {
  align-items: start;
}

.learning-evidence-workspace .skill-analytics-panel .section-head p {
  max-width: 48ch;
}

.skill-analytics-panel .analytics-grid {
  grid-template-columns: 1fr;
}

.skill-analytics-panel .analytics-card,
.skill-analytics-panel .material-recommendations {
  padding: var(--s-3) 0;
  border: 0;
  border-top: 1px solid var(--line);
  border-radius: 0;
  background: transparent;
}

.analytics-row {
  display: grid;
  grid-template-columns: minmax(105px, 1fr) minmax(76px, 1.2fr) 44px;
  gap: var(--s-3);
  align-items: center;
  min-height: 42px;
  padding: var(--s-2) 0;
}

.analytics-row > div:first-child {
  display: grid;
  gap: 1px;
  min-width: 0;
}

.analytics-row > div:first-child strong {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.analytics-row b {
  color: var(--ink-2);
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.analytics-row.is-weak .analytics-bar > span {
  background: var(--warn);
}

.panel-disclosure {
  width: 100%;
}

.panel-disclosure > summary {
  min-height: 58px;
  padding: var(--s-3) var(--s-4);
  cursor: pointer;
  list-style: none;
}

.panel-disclosure > summary::-webkit-details-marker {
  display: none;
}

.panel-disclosure > summary::after {
  content: "+";
  float: right;
  margin-top: calc(-1 * var(--s-5));
  color: var(--ink-3);
  font-size: var(--fs-lg);
  line-height: 1;
}

.panel-disclosure[open] > summary::after {
  content: "−";
}

.panel-disclosure-body {
  display: grid;
  gap: var(--s-3);
  padding: 0 var(--s-4) var(--s-4);
  border-top: 1px solid var(--line);
}

.saved-question-card,
.material-card,
.mistake-card {
  display: block;
  padding: 0;
  overflow: hidden;
  border-radius: var(--r-sm);
}

.saved-question-list,
.material-list,
.mistake-list {
  gap: var(--s-2);
}

.panel-disclosure .saved-question-head,
.panel-disclosure .material-head,
.panel-disclosure .mistake-head {
  padding-right: var(--s-6);
}

.panel-disclosure .saved-question-body,
.panel-disclosure .material-content,
.panel-disclosure .mistake-body {
  max-height: 13rem;
  overflow: auto;
  padding-top: var(--s-3);
}

.mistake-controls {
  grid-template-columns: minmax(150px, 0.7fr) minmax(220px, 1.6fr) auto auto;
  align-items: end;
}

.mistake-controls textarea {
  min-height: 72px;
}

.mixed-practice-workspace .mixed-test-panel {
  gap: var(--s-3);
}

.mixed-practice-workspace .mixed-builder {
  padding: var(--s-3);
}

.mixed-practice-workspace .mixed-card {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  padding: var(--s-3);
  border-radius: var(--r-sm);
}

/* Vocab becomes a study workspace: the live review is primary, while lookup,
   decks and imports form the tool rail. Recent cards remain a flat ledger. */
.vocab-grid {
  grid-template-columns: minmax(0, 1.45fr) minmax(300px, 0.72fr);
  align-items: start;
}

.vocab-grid > .vocab-card {
  grid-column: auto;
  border-radius: var(--r-sm);
}

.vocab-grid > [data-vocab-area="study"] {
  grid-column: 1;
  grid-row: 1 / span 3;
}

.vocab-grid > [data-vocab-area="lookup"],
.vocab-grid > [data-vocab-area="imports"],
.vocab-grid > [data-vocab-area="decks"] {
  grid-column: 2;
}

.vocab-grid > [data-vocab-area="recent"] {
  grid-column: 1 / -1;
}

.vocab-dashboard {
  display: flex;
  flex-wrap: wrap;
  gap: 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

.vocab-dashboard article {
  min-width: 110px;
  flex: 1 1 20%;
  padding: var(--s-2) var(--s-3);
  border: 0;
  border-right: 1px solid var(--line);
  border-radius: 0;
  background: transparent;
}

.vocab-dashboard article:last-child {
  border-right: 0;
}

.vocab-review-card {
  padding: var(--s-4) 0 0;
  border: 0;
  border-radius: 0;
  background: transparent;
}

.vocab-meaning-grid > div {
  padding: var(--s-3);
  background: var(--paper-2);
}

.vocab-card-list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: var(--s-5);
}

.vocab-card-row {
  min-width: 0;
}

.vocab-grid > [data-vocab-area="lookup"] .tag-form {
  grid-template-columns: 1fr;
}

.vocab-grid > [data-vocab-area="lookup"] [data-vocab-lookup-term] {
  width: 100%;
}

.settings-grid {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.exam-date-settings-card {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s-5);
  border-top: 3px solid var(--ink);
  border-radius: 0;
}

.exam-date-settings-card .exam-countdown {
  min-width: 270px;
}

/* =============================================================================
   11 — PRACTICE PLAYER
   The surface a student sits inside for up to three hours.

   No brand red exists in this subtree. Every red pixel here is an incorrect
   answer or the watermark — see DESIGN_SYSTEM.md section 1.

   The shell is a fixed grid whose middle row is minmax(0, 1fr): each panel
   scrolls itself and the page never scrolls. That is the structural form of the
   owner constraint "long passages and long choices scroll within their own
   panels instead of breaking the page".
   ============================================================================= */

.practice-player {
  position: fixed;
  inset: 0;
  z-index: 30;
  /* auto, not hidden: .rp-page (the summary/review view) is taller than the
     viewport and must scroll. .dsat-shell is exactly 100% and clips itself. */
  overflow-y: auto;
  background: var(--paper);
  color: var(--ink);
  -webkit-user-select: text;
  user-select: text;
}

/* The player is a fixed overlay, so the app shell behind it keeps its own
   scrollbar and can be wheel-scrolled underneath the test. Lock it. */
html:has(.practice-player:not(.is-hidden)),
body:has(.practice-player:not(.is-hidden)) {
  overflow: hidden;
}

/* Direction B ("Strip"). Child order after layout-overrides.js has run:
   header / navigator strip / review bar / main / footer.

   This is a flex column rather than a grid with fixed rows, deliberately.
   Three of those five children disappear depending on state — .dsat-review-bar
   is display:none outside review and teacher mode, and both .dsat-stripe
   elements are display:none in this direction — and a positional
   grid-template-rows silently mis-assigns the 1fr when a child drops out. That
   is not hypothetical: with `auto auto auto minmax(0,1fr) auto` and the review
   bar hidden, .dsat-main took an auto row and the footer took the 1fr, giving
   a 91.5px footer and no flexible content area.

   Flex skips display:none children, so the rule stays true whatever renders. */
.dsat-shell {
  position: relative;
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
  background: var(--paper);
}

/* min-height:0 is what lets the panels inside scroll instead of the page —
   without it a flex item refuses to shrink below its content. */
.dsat-shell > .dsat-main {
  flex: 1 1 auto;
  min-height: 0;
}

.dsat-shell > :not(.dsat-main) {
  flex: 0 0 auto;
}

.dsat-shell > :not(.app-watermark) {
  position: relative;
  z-index: 14;
}

/* MUST stay visible. verifyPracticeWatermark() in script.js clears the student's
   test if display is none, visibility is hidden, or opacity drops below 0.01. */
.app-watermark {
  position: absolute;
  top: 50%;
  left: 50%;
  /* The watermark is a top overlay, not a panel background. It stays above
     passage/choice surfaces while pointer-events:none keeps the UI usable. */
  z-index: 18;
  width: min(52vw, 680px);
  height: auto;
  max-width: 72%;
  max-height: 44vh;
  object-fit: contain;
  transform: translate(-50%, -50%);
  opacity: 0.055;
  pointer-events: none;
  user-select: none;
  -webkit-user-drag: none;
}

/* -- header ---------------------------------------------------------------- */
/* Direction B de-centres the header. The old rule was
   `minmax(0,1fr) auto minmax(0,1fr)` — two equal outer cells, which put the
   centre cell, and the timer inside it, on the exact geometric middle of the
   screen (measured: centre x 683.0 against a viewport centre of 683.0). That
   is the Bluebook header, and it parks the clock on the axis the eye returns
   to between every question and every answer choice.

   Now: the section label takes all slack on the left; the timer joins Exit and
   Calculator in a right-hand utility cluster, ~479px off the reading axis. */
.dsat-header {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: var(--s-3);
  min-height: 48px;
  padding: var(--s-2) var(--s-5);
  background: var(--paper);
}

.dsat-hd-left {
  display: flex;
  align-items: center;
  min-width: 0;
}

/* Was a centred column stacking the clock over its Hide button. Now a row in
   the right-hand cluster. */
.dsat-hd-center {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: var(--s-1);
}

.dsat-hd-right {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s-2);
}

.dsat-section-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--fs-sm);
  font-weight: 650;
  color: var(--ink-2);
}

/* The clock loses its emphasis as well as its position. It was --fs-xl (21px)
   in full --ink; a student reads the time far more often than they need to,
   and size was doing the recruiting. Now --fs-md in --ink-2, with a small
   standing label so the number is identifiable without being loud.
   --warn and --wrong still escalate it, which is when it should be loud. */
.dsat-timer {
  display: flex;
  align-items: baseline;
  gap: var(--s-2);
  font-variant-numeric: tabular-nums;
}

.dsat-timer::before {
  content: "Time left";
  font-size: var(--fs-2xs);
  font-weight: 600;
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  color: var(--ink-4);
}

.dsat-timer-val {
  font-size: var(--fs-md);
  font-weight: 650;
  letter-spacing: var(--tr-tight);
  color: var(--ink-2);
}

.dsat-timer.is-warning::before {
  color: var(--warn);
}

.dsat-timer.is-overdue::before {
  color: var(--wrong);
}

/* NOTE: the emitter puts `is-hidden` on .dsat-timer itself, and the utility in
   layer 04 is `display: none !important`, so the element is gone rather than
   merely blanked. The rule that used to live here — `.dsat-timer.is-hidden
   .dsat-timer-val { visibility: hidden }` — could never match a rendered
   element and has been removed. Hiding the clock now collapses it out of the
   right-hand cluster; the header keeps its height because .dsat-header has
   min-height and the Exit button sets the row. */

.dsat-timer.is-warning .dsat-timer-val {
  color: var(--warn);
}

.dsat-timer.is-overdue .dsat-timer-val {
  color: var(--wrong);
  animation: dsat-timer-blink 1s steps(1, end) infinite;
}

@keyframes dsat-timer-blink {
  0%, 49% { opacity: 1; }
  50%, 100% { opacity: 0.35; }
}

@media (prefers-reduced-motion: reduce) {
  .dsat-timer.is-overdue .dsat-timer-val {
    animation: none;
  }
}

.dsat-hide-timer-btn {
  padding: 1px var(--s-2);
  border: 1px solid var(--line);
  border-radius: var(--r-full);
  color: var(--ink-3);
  font-size: var(--fs-2xs);
  font-weight: 600;
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  transition: background-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}

.dsat-hide-timer-btn:hover {
  background: var(--paper-3);
  color: var(--ink);
}

.dsat-hd-btn {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  min-width: 60px;
  min-height: var(--hit);
  padding: var(--s-1) var(--s-2);
  border-radius: var(--r-sm);
  color: var(--ink-2);
  font-size: var(--fs-2xs);
  font-weight: 600;
  transition: background-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}

.dsat-hd-btn svg {
  width: 18px;
  height: 18px;
}

.dsat-hd-btn:hover {
  background: var(--paper-3);
  color: var(--ink);
}

.dsat-hd-btn.is-active {
  background: var(--paper-3);
  color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--line-2);
}

/* The old file drew a four-hue repeating rainbow here, twice per screen, one
   band of which was red. It is a hairline now. */
/* Direction B removes both. The rainbow these carried was already deleted in
   the previous pass (REDESIGN_AUDIT.md §2.3); what survived was the slot — two
   full-width 1px rules at y 63.6 and y 645.1, immediately below the header and
   immediately above the footer, restating a boundary that both regions already
   express through their own background and border. The navigator strip's own
   rules now do that job at the top, and .dsat-bottom-nav's border-top does it
   at the bottom.

   The element is kept in the DOM (script.js emits it) and only collapsed, so
   nothing depends on a node that stopped existing. */
.dsat-stripe {
  display: none;
}

/* -- review / teacher bar -------------------------------------------------- */
.dsat-review-bar {
  display: none;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-5);
  background: var(--paper-2);
  border-bottom: 1px solid var(--line);
}

.dsat-review-bar.is-visible {
  display: flex;
}

.answer-reveal-btn,
.review-add-mistake-btn,
.teacher-choices-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-height: var(--hit-sm);
  padding: 0 var(--s-3);
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  background: var(--paper);
  color: var(--ink);
  font-size: var(--fs-sm);
  font-weight: 600;
  transition: background-color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease),
    color var(--t-fast) var(--ease);
}

.answer-reveal-btn:hover,
.review-add-mistake-btn:hover,
.teacher-choices-toggle:hover {
  background: var(--paper-3);
  border-color: var(--ink-4);
}

.answer-reveal-btn.is-revealed {
  border-color: var(--ok-line);
  background: var(--ok-wash);
  color: var(--ok);
}

.review-add-mistake-btn.in-notebook,
.teacher-choices-toggle.is-open {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

.review-add-mistake-btn.in-notebook:hover,
.teacher-choices-toggle.is-open:hover {
  background: var(--ink-2);
  border-color: var(--ink-2);
}

/* -- main split ------------------------------------------------------------ */
.dsat-main {
  display: grid;
  min-height: 0;
  gap: 0;
}

/* Direction B replaces the fixed 50/50 with a split the student owns.

   Measured on the old rule at 1366×768: `grid-template-columns: 683px 683px`
   for every question with a passage. On a long passage with a short prompt the
   question column wasted 156px; on a short passage with a long prompt the
   passage column wasted 491.5px — 95% of it — while the question column ran to
   within 16px of the bottom. Same ratio, opposite needs.

   --split is written by the drag handle in layout-overrides.js and persisted
   per student. data-shape is written by the same file, which measures the
   passage and prompt lengths CSS cannot see, and only supplies the starting
   ratio: once the student drags, --split wins. */
.dsat-shell:not(.is-math) .dsat-main:has(> .split-handle) {
  grid-template-columns: var(--split, 1fr) 8px minmax(0, 1fr);
}

.dsat-shell:not(.is-math) .dsat-main[data-shape="passage-heavy"]:has(> .split-handle) {
  grid-template-columns: var(--split, 1.35fr) 8px minmax(0, 1fr);
}

.dsat-shell:not(.is-math) .dsat-main[data-shape="prompt-heavy"]:has(> .split-handle) {
  grid-template-columns: var(--split, 1fr) 8px minmax(0, 1.5fr);
}

/* Fallback for the frame between the original render and the reflow, and for
   the case where layout-overrides.js failed to load at all. */
.dsat-shell:not(.is-math) .dsat-main:not(:has(> .split-handle)) {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}

/* -- the split handle ------------------------------------------------------ */
.split-handle {
  display: grid;
  place-items: center;
  cursor: col-resize;
  background: var(--paper-2);
  border-inline: 1px solid var(--line);
  touch-action: none;
}

.split-handle::before {
  content: "";
  width: 2px;
  height: 28px;
  border-radius: var(--r-full);
  background: var(--line-2);
  transition: background-color var(--t-fast) var(--ease);
}

.split-handle:hover::before,
.split-handle:focus-visible::before {
  background: var(--ink-3);
}

.split-handle:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: -2px;
}

/* The handle supplies the boundary, so the passage panel drops its own. */
.dsat-main:has(> .split-handle) > .dsat-passage-panel {
  border-right: 0;
}

.dsat-shell:not(.is-math) .dsat-main.is-single,
.dsat-shell.is-math .dsat-main.no-calc {
  grid-template-columns: minmax(0, 1fr);
}

/* --calc-width is written inline by initCalcResize(); accept it, do not fight it. */
.dsat-shell.is-math .dsat-main.has-calc {
  grid-template-columns: var(--calc-width, 46%) 8px minmax(0, 1fr);
}

.dsat-passage-panel,
.dsat-q-panel {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  min-width: 0;
  min-height: 0;
  padding: var(--s-5) var(--s-6);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.dsat-passage-panel {
  border-right: 1px solid var(--line);
}

.dsat-shell.is-math .dsat-main.has-calc .dsat-q-panel {
  border-left: 0;
}

/* Single-column layouts get a measure cap so the line length stays readable
   even on a wide monitor. */
.dsat-main.is-single .dsat-q-panel,
.dsat-main.no-calc .dsat-q-panel {
  width: 100%;
  max-width: 860px;
  margin-inline: auto;
}

.dsat-passage-panel .passage-text {
  max-width: var(--measure);
}

/* -- passage + prompt reading copy: the exam serif ------------------------- */
.passage-text,
.dsat-prompt {
  max-width: var(--measure);
  color: var(--ink);
  font-family: var(--font-read);
  font-size: var(--fs-md);
  line-height: var(--lh-read);
}

.dsat-math-q .dsat-prompt {
  max-width: none;
}

.passage-text p + p,
.dsat-prompt p + p {
  margin-top: var(--s-4);
}

.passage-text blockquote,
.dsat-prompt blockquote {
  margin: var(--s-4) 0;
  padding: var(--s-1) 0 var(--s-1) var(--s-4);
  border-left: 2px solid var(--line-2);
  color: var(--ink-2);
  font-style: italic;
}

.passage-text u,
.dsat-prompt u {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

/* Poetry: script.js emits <br>-separated lines. Preserve the break, hang the
   wrap so a long line reads as one line, not two stanzas. */
.passage-text br + br {
  content: "";
  display: block;
  height: var(--s-3);
}

.passage-text em,
.dsat-prompt em {
  font-style: italic;
}

/* -- calculator ------------------------------------------------------------ */
.dsat-calc-panel {
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  background: var(--paper-2);
  border-right: 1px solid var(--line);
}

.dsat-calc-frame,
.dsat-calc-panel iframe {
  width: 100%;
  height: 100%;
  border: 0;
}

.calc-resize-handle {
  display: grid;
  place-items: center;
  width: 8px;
  cursor: col-resize;
  background: var(--paper-2);
  touch-action: none;
}

.calc-resize-grip {
  width: 2px;
  height: 28px;
  border-radius: var(--r-full);
  background: var(--line-2);
  transition: background-color var(--t-fast) var(--ease);
}

.calc-resize-handle:hover .calc-resize-grip,
.calc-resize-handle.is-dragging .calc-resize-grip {
  background: var(--ink-3);
}

.calc-resize-overlay {
  position: fixed;
  inset: 0;
  z-index: 90;
  cursor: col-resize;
}

.is-calc-resizing,
.is-ai-drawer-resizing {
  user-select: none;
  cursor: col-resize;
}

/* -- question banner ------------------------------------------------------- */
.dsat-q-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
  padding-bottom: var(--s-3);
  border-bottom: 1px solid var(--line);
}

.dsat-q-num {
  display: grid;
  place-items: center;
  min-width: 26px;
  height: 26px;
  padding: 0 var(--s-2);
  border-radius: var(--r-xs);
  background: var(--ink);
  color: var(--paper);
  font-size: var(--fs-sm);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.dsat-q-tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-1);
  margin-left: auto;
}

.dsat-mark-btn,
.dsat-sm-btn,
.dsat-abc-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  min-height: var(--hit-sm);
  padding: 0 var(--s-2);
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  color: var(--ink-2);
  font-size: var(--fs-xs);
  font-weight: 600;
  transition: background-color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease),
    color var(--t-fast) var(--ease);
}

.dsat-mark-btn svg,
.dsat-sm-btn svg {
  width: 14px;
  height: 14px;
}

.dsat-mark-btn:hover,
.dsat-sm-btn:hover,
.dsat-abc-btn:hover {
  background: var(--paper-3);
  color: var(--ink);
}

/* Flag state is a filled bookmark in ink — not red. Red is answer state. */
.dsat-mark-btn.is-marked {
  border-color: var(--line-2);
  background: var(--paper-3);
  color: var(--ink);
}

.dsat-mark-btn.is-marked svg {
  fill: currentColor;
}

.dsat-sm-btn.is-active {
  border-color: var(--line-2);
  background: var(--paper-3);
  color: var(--ink);
}

.dsat-abc-btn {
  position: relative;
  font-family: var(--font-mono);
  font-weight: 700;
  letter-spacing: 0.04em;
}

.dsat-abc-btn::after {
  content: "";
  position: absolute;
  left: var(--s-2);
  right: var(--s-2);
  top: 50%;
  height: 1px;
  background: currentColor;
  opacity: 0;
  transition: opacity var(--t-fast) var(--ease);
}

.dsat-abc-btn.is-active {
  border-color: var(--line-2);
  background: var(--paper-3);
  color: var(--ink);
}

.dsat-abc-btn.is-active::after {
  opacity: 1;
}

/* -- choices ---------------------------------------------------------------
   Selection is ink. Colour on a choice always means graded.
   -------------------------------------------------------------------------- */
.dsat-choice-list {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  max-width: var(--measure);
}

.dsat-math-q .dsat-choice-list {
  max-width: 720px;
}

.dsat-choice-stack {
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
}

.dsat-choice {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s-3);
  width: 100%;
  min-height: var(--hit-lg);
  padding: var(--s-3);
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  background: var(--paper);
  text-align: left;
  transition: background-color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease),
    box-shadow var(--t-fast) var(--ease);
}

.dsat-choice:hover:not(:disabled) {
  background: var(--paper-3);
}

.dsat-choice-text::selection,
.dsat-choice-text *::selection {
  background: #a9cfff;
  color: #17243a;
  text-shadow: none;
}

.dsat-choice:disabled {
  cursor: default;
}

.dsat-choice-letter {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  flex: none;
  border: 1px solid var(--line-2);
  border-radius: var(--r-full);
  background: var(--paper);
  color: var(--ink-2);
  font-size: var(--fs-sm);
  font-weight: 700;
  transition: background-color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease),
    color var(--t-fast) var(--ease);
}

.dsat-choice-text {
  min-width: 0;
  color: var(--ink);
  font-family: var(--font-read);
  font-size: var(--fs-md);
  line-height: var(--lh-base);
}

.dsat-choice-text p + p {
  margin-top: var(--s-2);
}

.dsat-choice.is-selected {
  border-color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--ink);
}

.dsat-choice.is-selected .dsat-choice-letter {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

.dsat-choice.can-eliminate {
  cursor: crosshair;
}

.dsat-choice.is-eliminated {
  opacity: 0.45;
}

.dsat-choice.is-eliminated .dsat-choice-text {
  text-decoration: line-through;
}

/* Review states are declared after selection so a wrong pick reads as wrong,
   not as selected. */
.dsat-choice.is-correct-review {
  border-color: var(--ok-line);
  background: var(--ok-wash);
  box-shadow: inset 0 0 0 1px var(--ok-line);
}

.dsat-choice.is-correct-review .dsat-choice-letter {
  background: var(--ok);
  border-color: var(--ok);
  color: var(--paper);
}

.dsat-choice.is-wrong-review {
  border-color: var(--wrong-line);
  background: var(--wrong-wash);
  box-shadow: inset 0 0 0 1px var(--wrong-line);
}

.dsat-choice.is-selected.is-wrong-review .dsat-choice-letter,
.dsat-choice.is-wrong-review .dsat-choice-letter {
  background: var(--wrong);
  border-color: var(--wrong);
  color: var(--paper);
}

.result-icon {
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  flex: none;
  border-radius: var(--r-full);
}

.result-icon svg {
  width: 14px;
  height: 14px;
  stroke-width: 3;
}

.result-icon.correct {
  background: var(--ok);
  color: var(--paper);
}

.result-icon.wrong {
  background: var(--wrong);
  color: var(--paper);
}

/* -- bottom navigation -----------------------------------------------------
   Owner constraint: Back / Next need a large clickable area with modest visual
   weight. 48px tall, 132px wide, quiet paper fill.
   -------------------------------------------------------------------------- */
/* Direction B pairs Back and Next.

   Measured on the old rule: .dsat-nav-left and .dsat-nav-right were each
   574.1px wide holding a single 132px button, so Submit sat at x 20 and Next
   at x 1214 — 1062px between the two controls a student presses more than
   everything else combined. The centre cell held a 145.9px pill whose only job
   was to summon the navigator, which is now permanent.

   Now: Submit stays hard left, deliberately far from Next so the two are never
   confused; Back and Next pair on the right, --s-2 apart. */
.dsat-bottom-nav {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s-4);
  padding: var(--s-2) var(--s-5);
  background: var(--paper);
  border-top: 1px solid var(--line);
}

/* The pill is redundant once the strip is permanent: it toggled showNav, and
   layout-overrides.js forces showNav true on every render. Collapsed rather
   than removed, because script.js still emits it and still binds its handler. */
.dsat-bottom-nav .dsat-q-pill {
  display: none;
}

/* .dsat-nav-center is left empty by the reflow (the navigator moved to the
   strip, the pill is hidden). It keeps its grid cell so the two side groups
   stay pinned to the edges. */
.dsat-bottom-nav .dsat-nav-center:empty,
.dsat-bottom-nav .dsat-nav-center {
  min-width: 0;
}

.dsat-nav-side {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  min-width: 0;
}

.dsat-nav-left {
  justify-content: flex-start;
}

.dsat-nav-right {
  justify-content: flex-end;
}

.dsat-nav-center {
  position: relative;
  display: flex;
  justify-content: center;
}

.dsat-nav-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-width: 132px;
  min-height: var(--hit-lg);
  padding: 0 var(--s-5);
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  background: var(--paper);
  color: var(--ink);
  font-size: var(--fs-sm);
  font-weight: 650;
  transition: background-color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease),
    opacity var(--t-fast) var(--ease);
}

.dsat-nav-btn svg {
  width: 16px;
  height: 16px;
}

.dsat-nav-btn:hover:not([disabled]):not(.is-disabled) {
  background: var(--paper-3);
  border-color: var(--ink-4);
}

.dsat-nav-btn[disabled],
.dsat-nav-btn.is-disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.dsat-nav-btn.is-next-module,
.dsat-nav-submit {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

.dsat-nav-btn.is-next-module:hover,
.dsat-nav-submit:hover:not([disabled]) {
  background: var(--ink-2);
  border-color: var(--ink-2);
}

.dsat-nav-end-review {
  min-width: auto;
}

.teacher-bottom-label {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  color: var(--ink-3);
  font-size: var(--fs-sm);
  font-weight: 600;
}

.dsat-q-pill {
  min-height: var(--hit);
  padding: 0 var(--s-4);
  font-size: var(--fs-sm);
  font-weight: 650;
  transition: background-color var(--t-fast) var(--ease);
}

.dsat-q-pill:hover {
  background: var(--paper-3);
}

/* =============================================================================
   12 — PRACTICE SUB-COMPONENTS
   ============================================================================= */

/* -- question navigator ----------------------------------------------------
   Owner constraint: "No random colorful question-number boxes."
   Four states while taking, none of them coloured. Colour appears only after
   the test is graded, where it is the point.
   -------------------------------------------------------------------------- */
/* Direction B: the navigator stops being a popover and becomes a permanent
   44px strip directly under the header.

   It used to be `position: absolute; z-index: 20` floating 640×110 over the
   content area, and it did not exist in the DOM at all until the footer pill
   was clicked — renderQuestionNavigator() early-returns "" unless
   practice.showNav. layout-overrides.js forces showNav true and re-parents
   this node from .dsat-nav-center (in the footer) to a direct child of
   .dsat-shell, so the shell grid can place it here.

   "How many left, and which did I flag?" is the most frequent non-answering
   question in a timed section. It now costs zero clicks and covers nothing. */
.question-navigator {
  position: static;
  z-index: auto;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  align-items: center;
  gap: var(--s-3);
  width: auto;
  max-height: none;
  min-height: 44px;
  padding: 0 var(--s-5);
  overflow: visible;
  transform: none;
  border: 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  border-radius: 0;
  background: var(--paper-2);
  box-shadow: none;
}

/* The emitter's child order is prev-arrow, legend, next-arrow, grid. In a
   strip the useful order is prev, grid, legend, next — the numbers should sit
   between the two arrows that step through them. `order` reorders the grid
   items without touching the markup. */
.question-navigator > .nav-arrow:first-of-type { order: 1; }
.question-navigator > .navigator-grid { order: 2; }
.question-navigator > .navigator-legend { order: 3; }
.question-navigator > .nav-arrow:last-of-type { order: 4; }

.navigator-legend {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s-3);
  color: var(--ink-3);
  font-size: var(--fs-2xs);
  white-space: nowrap;
}

.navigator-legend span {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
}

.navigator-legend svg {
  width: 13px;
  height: 13px;
}

.legend-box {
  display: inline-block;
  width: 13px;
  height: 13px;
  border: 1px solid var(--line-2);
  border-radius: var(--r-xs);
  background: var(--paper);
}

.legend-box.marked {
  position: relative;
  overflow: hidden;
}

.legend-box.marked::after {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  border-width: 0 5px 5px 0;
  border-style: solid;
  border-color: transparent var(--ink-3) transparent transparent;
}

/* Was a full-width wrapping grid inside a popover. In the strip it is a single
   scrolling row: a real Reading and Writing module is 27 questions, and the
   strip has to hold them without growing taller than 44px. Horizontal overflow
   is scoped to this element, so the page still never scrolls. */
.navigator-grid {
  grid-column: auto;
  display: flex;
  gap: var(--s-1);
  min-width: 0;
  padding: var(--s-1) 0;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: thin;
}

.navigator-grid > button {
  flex: 0 0 auto;
}

.navigator-grid button {
  position: relative;
  display: grid;
  place-items: center;
  min-width: 36px;
  min-height: var(--hit-sm);
  border: 1px solid var(--line-2);
  border-radius: var(--r-xs);
  background: var(--paper);
  color: var(--ink);
  font-size: var(--fs-sm);
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  transition: background-color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}

.navigator-grid button:hover {
  border-color: var(--ink-4);
}

.navigator-grid button.is-unanswered {
  background: var(--paper);
  color: var(--ink-3);
}

.navigator-grid button.is-answered {
  background: var(--paper-3);
  color: var(--ink);
}

/* Flagged: a corner notch, not a colour. */
.navigator-grid button.is-marked::after {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  border-width: 0 6px 6px 0;
  border-style: solid;
  border-color: transparent var(--ink-3) transparent transparent;
}

.navigator-grid button.is-current {
  box-shadow: inset 0 0 0 2px var(--ink);
  border-color: var(--ink);
}

/* Review mode only — these classes are emitted only when practice.mode is
   "review" (renderQuestionNavigator in script.js). */
.navigator-grid button.correct {
  border-color: var(--ok-line);
  background: var(--ok-wash);
  color: var(--ok);
}

.navigator-grid button.wrong {
  border-color: var(--wrong-line);
  background: var(--wrong-wash);
  color: var(--wrong);
}

.navigator-grid button.navigator-add-question {
  border-style: dashed;
  color: var(--ink-3);
}

.navigator-grid button.navigator-add-question:hover {
  background: var(--paper-3);
  color: var(--ink);
}

/* -- free response / grid-in ----------------------------------------------- */
.free-response-area {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  max-width: 420px;
}

.free-response-row {
  display: flex;
  align-items: flex-end;
  gap: var(--s-3);
}

.free-response-box {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  flex: 1 1 auto;
  min-width: 0;
}

.free-response-box span {
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  color: var(--ink-3);
}

/* Use the UI numeral design here: Cascadia/SF Mono draw a slashed zero that
   students can mistake for theta. Tabular figures keep multi-digit answers
   aligned without changing the shape of 0. */
.free-response-box input {
  min-height: var(--hit-lg);
  font-family: var(--font-ui);
  font-size: var(--fs-lg);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "zero" 0;
  letter-spacing: 0.02em;
}

.free-response-area.is-correct .free-response-box input {
  border-color: var(--ok);
  background: var(--ok-wash);
  color: var(--ok);
}

.free-response-area.is-wrong .free-response-box input {
  border-color: var(--wrong);
  background: var(--wrong-wash);
  color: var(--wrong);
}

.free-response-correct-box {
  display: grid;
  place-items: center;
  min-height: var(--hit-lg);
  padding: 0 var(--s-4);
  border: 1px solid var(--ok-line);
  border-radius: var(--r-sm);
  background: var(--ok-wash);
}

.free-response-correct-box strong {
  color: var(--ok);
  font-family: var(--font-ui);
  font-size: var(--fs-lg);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "zero" 0;
}

.free-response-status {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  color: var(--wrong);
  font-size: var(--fs-xs);
  font-weight: 600;
}

.free-response-area.is-unavailable {
  max-width: var(--measure);
}

.free-response-area.is-unavailable .free-response-status {
  padding: var(--s-3);
  border: 1px solid var(--warn-line);
  border-radius: var(--r-sm);
  background: var(--warn-wash);
  color: var(--warn);
  font-size: var(--fs-sm);
}

.free-response-import-hint {
  color: var(--ink-3);
  font-size: var(--fs-xs);
  font-style: italic;
}

/* -- media, tables, images ------------------------------------------------- */
.practice-media-list {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  max-width: var(--measure);
}

.dsat-math-q .practice-media-list {
  max-width: 720px;
}

.practice-media-item {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}

.practice-media-item.has-image {
  padding: var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--paper);
}

.practice-media-item.has-image img,
.pdf-media-item img {
  display: block;
  max-width: 100%;
  height: auto;
  margin-inline: auto;
  border-radius: var(--r-xs);
  -webkit-user-drag: none;
  user-select: none;
  pointer-events: none;
}

.practice-media-item figcaption {
  color: var(--ink-3);
  font-family: var(--font-read);
  font-size: var(--fs-xs);
  text-align: center;
}

.practice-media-item.missing-image {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-3);
  border: 1px dashed var(--warn-line);
  border-radius: var(--r-sm);
  background: var(--warn-wash);
  color: var(--warn);
  font-size: var(--fs-xs);
}

/* A wide table scrolls inside its own figure instead of widening the panel. */
.practice-table-wrap,
.media-table-preview {
  overflow-x: auto;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--paper);
}

.practice-table-wrap table,
.media-table-preview table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
}

.practice-table-wrap caption,
.media-table-preview caption {
  padding: var(--s-2) var(--s-3);
  border-bottom: 1px solid var(--line);
  color: var(--ink-3);
  font-size: var(--fs-xs);
  font-weight: 650;
  text-align: left;
}

.practice-table-wrap th,
.media-table-preview th {
  padding: var(--s-2) var(--s-3);
  border-bottom: 1px solid var(--line-2);
  background: var(--paper-3);
  color: var(--ink-2);
  font-size: var(--fs-xs);
  font-weight: 650;
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  text-align: left;
  white-space: nowrap;
}

.practice-table-wrap td,
.media-table-preview td {
  padding: var(--s-2) var(--s-3);
  border-bottom: 1px solid var(--line);
  color: var(--ink);
}

.practice-table-wrap tr:last-child td,
.media-table-preview tr:last-child td {
  border-bottom: 0;
}

/* -- locked / unavailable -------------------------------------------------- */
.practice-locked,
.practice-unavailable {
  position: relative;
  display: grid;
  place-items: center;
  min-height: 100vh;
  padding: var(--s-6);
  background: var(--paper);
}

.practice-locked > div {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-3);
  max-width: 46ch;
  text-align: center;
}

.practice-locked h2 {
  font-size: var(--fs-xl);
}

.practice-locked p {
  color: var(--ink-3);
  font-size: var(--fs-sm);
}

/* -- note popover ---------------------------------------------------------- */
.note-popup {
  position: fixed;
  bottom: var(--s-6);
  left: 50%;
  z-index: 80;
  width: min(460px, 100%);
  gap: var(--s-3);
  transform: translateX(-50%);
}

@media (max-width: 560px) {
  .note-popup {
    bottom: var(--s-3);
    width: calc(100% - (2 * var(--s-3)));
  }
}

.note-popup-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
}

.note-popup-head .modal-close {
  margin: calc(var(--s-2) * -1);
}

.note-popup-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s-2);
}

/* -- text selection toolbar + highlights ----------------------------------- */
.selection-toolbar {
  position: absolute;
  z-index: 70;
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2);
  border: 1px solid var(--line-2);
  border-radius: var(--r-md);
  background: var(--paper);
  box-shadow: var(--e-3);
}

#practiceSelectionToolbar {
  position: fixed;
  z-index: 100;
  transform: translateX(-50%);
}

::highlight(tsm-pending-selection) {
  background: #a9cfff;
  color: #17243a;
}

.practice-pending-selection-overlay {
  position: fixed;
  inset: 0;
  z-index: 65;
  overflow: hidden;
  pointer-events: none;
}

.practice-pending-selection-overlay span {
  position: absolute;
  border-radius: 2px;
  background: rgba(169, 207, 255, 0.62);
  mix-blend-mode: multiply;
}

.selection-toolbar::after {
  content: "";
  position: absolute;
  bottom: -5px;
  left: 50%;
  width: 9px;
  height: 9px;
  transform: translateX(-50%) rotate(45deg);
  border-right: 1px solid var(--line-2);
  border-bottom: 1px solid var(--line-2);
  background: var(--paper);
}

.selection-toolbar.is-below::after {
  bottom: auto;
  top: -5px;
  border: 0;
  border-left: 1px solid var(--line-2);
  border-top: 1px solid var(--line-2);
}

.selection-color-row {
  display: flex;
  align-items: center;
  gap: var(--s-1);
  padding-right: var(--s-2);
  border-right: 1px solid var(--line);
}

.selection-color {
  display: grid;
  place-items: center;
  width: var(--hit-sm);
  height: var(--hit-sm);
  border-radius: var(--r-sm);
  transition: background-color var(--t-fast) var(--ease);
}

.selection-color:hover {
  background: var(--paper-3);
}

.selection-color span {
  display: block;
  width: 16px;
  height: 16px;
  border: 1px solid rgba(31, 25, 23, 0.2);
  border-radius: var(--r-xs);
}

.selection-action {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  min-height: var(--hit-sm);
  padding: 0 var(--s-2);
  border-radius: var(--r-sm);
  color: var(--ink-2);
  font-size: var(--fs-xs);
  font-weight: 600;
}

.selection-action:hover {
  background: var(--paper-3);
  color: var(--ink);
}

/* Highlight colours are user-chosen annotations, not app state. Muted so they
   never read as correct/incorrect marking. */
/* The pastel fills are fixed in both themes (they are the student's own marks,
   and a highlight that changes colour with the theme is not a highlight), so
   the text on them is pinned to ink rather than inheriting a light --ink. */
.practice-highlight {
  border-radius: 2px;
  background: rgba(214, 178, 74, 0.34);
  color: #1f1917;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}

.hl-delete-btn {
  display: inline-grid;
  place-items: center;
  width: 18px;
  height: 18px;
  margin-left: 2px;
  border-radius: var(--r-full);
  background: var(--paper-3);
  color: var(--ink-3);
  font-size: 10px;
  vertical-align: middle;
}

.hl-delete-btn:hover {
  background: var(--ink);
  color: var(--paper);
}

/* The legacy floating X is superseded by the contextual highlight toolbar.
   The script may still create the node, but it must never cover reading text. */
.hl-delete-btn {
  display: none !important;
}

.highlight-edit-toolbar {
  position: fixed;
  transform: translateX(-50%);
}

.highlight-edit-toolbar .selection-color[aria-pressed="true"] {
  background: var(--paper-3);
  box-shadow: inset 0 0 0 1px var(--ink-3);
}

.highlight-delete-action {
  color: var(--wrong);
}

.highlight-delete-action:hover {
  background: var(--wrong-wash);
  color: var(--wrong);
}

/* -- Desmos window --------------------------------------------------------- */
.desmos-window {
  position: fixed;
  z-index: 50;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--line-2);
  border-radius: var(--r-md);
  background: var(--paper);
  box-shadow: var(--e-3);
}

.desmos-titlebar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-3);
  border-bottom: 1px solid var(--line);
  background: var(--paper-2);
  cursor: move;
  font-size: var(--fs-xs);
  font-weight: 650;
}

.desmos-titlebar button {
  display: grid;
  place-items: center;
  width: var(--hit-sm);
  height: var(--hit-sm);
  border-radius: var(--r-sm);
  color: var(--ink-3);
}

.desmos-titlebar button:hover {
  background: var(--paper-3);
  color: var(--ink);
}

.desmos-window iframe {
  flex: 1 1 auto;
  width: 100%;
  border: 0;
}

/* -- reference sheet + module picker --------------------------------------- */
.reference-sheet {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  font-size: var(--fs-sm);
}

.reference-sheet h4 {
  font-size: var(--fs-xs);
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  color: var(--ink-3);
}

.reference-sheet ul {
  margin: 0;
  padding-left: var(--s-5);
  color: var(--ink-2);
}

.reference-sheet li {
  margin-bottom: var(--s-1);
}

.module-pick-list {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}

.module-pick-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  width: 100%;
  padding: var(--s-3);
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  background: var(--paper);
  text-align: left;
  transition: background-color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}

.module-pick-row.is-complete:hover {
  background: var(--paper-3);
  border-color: var(--ink-4);
}

.module-pick-row.is-incomplete {
  opacity: 0.6;
  cursor: not-allowed;
}

.module-pick-label {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.module-pick-label strong {
  font-size: var(--fs-sm);
}

.module-pick-label small {
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

.module-pick-cta {
  font-size: var(--fs-xs);
  font-weight: 650;
}

.module-pick-locked {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  color: var(--ink-4);
  font-size: var(--fs-xs);
}

.module-pick-locked svg {
  width: 13px;
  height: 13px;
}

/* -- teacher panel --------------------------------------------------------- */
.teacher-panel {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 40;
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  width: min(340px, 92vw);
  padding: var(--s-4);
  overflow-y: auto;
  border-left: 1px solid var(--line-2);
  background: var(--paper);
  box-shadow: var(--e-3);
}

.teacher-panel-head {
  display: flex;
  align-items: center;
  gap: var(--s-2);
}

.teacher-panel-head strong {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  font-size: var(--fs-xs);
  letter-spacing: var(--tr-wide);
  color: var(--ink-3);
}

.teacher-panel-head select {
  flex: 1 1 auto;
  min-width: 0;
  min-height: var(--hit-sm);
  font-size: var(--fs-xs);
}

.teacher-refresh {
  display: grid;
  place-items: center;
  width: var(--hit-sm);
  height: var(--hit-sm);
  flex: none;
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  color: var(--ink-2);
}

.teacher-refresh:hover {
  background: var(--paper-3);
}

.teacher-panel-summary {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-1) var(--s-3);
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

.teacher-stat-strip {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  padding: var(--s-2);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--paper-2);
  font-size: var(--fs-xs);
  font-variant-numeric: tabular-nums;
}

.teacher-stat-strip .correct { color: var(--ok); }
.teacher-stat-strip .wrong { color: var(--wrong); }
.teacher-stat-strip .blank { color: var(--ink-3); }
.teacher-stat-strip .answered { color: var(--info); }

.teacher-student-list {
  display: flex;
  flex-direction: column;
}

.teacher-student-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-1);
  border-top: 1px solid var(--line);
}

.teacher-student-row > div {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.teacher-student-row strong {
  font-size: var(--fs-xs);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.teacher-student-row > div span {
  color: var(--ink-4);
  font-size: var(--fs-2xs);
}

.teacher-status {
  color: var(--ink-3);
  font-size: var(--fs-2xs);
  white-space: nowrap;
}

.teacher-answer-pill {
  display: grid;
  place-items: center;
  min-width: 26px;
  padding: 2px var(--s-1);
  border: 1px solid var(--line-2);
  border-radius: var(--r-xs);
  background: var(--paper-2);
  font-size: var(--fs-2xs);
  font-weight: 700;
}

.teacher-student-row.correct .teacher-answer-pill {
  border-color: var(--ok-line);
  background: var(--ok-wash);
  color: var(--ok);
}

.teacher-student-row.wrong .teacher-answer-pill {
  border-color: var(--wrong-line);
  background: var(--wrong-wash);
  color: var(--wrong);
}

.teacher-student-row.unanswered .teacher-answer-pill,
.teacher-student-row.not-started .teacher-answer-pill {
  color: var(--ink-4);
}

.teacher-student-row.answered .teacher-answer-pill {
  border-color: var(--info-line);
  background: var(--info-wash);
  color: var(--info);
}

.teacher-student-row.pending {
  opacity: 0.65;
}

/* =============================================================================
   13 — AI TUTOR
   A side drawer over the player. Reading copy uses the exam serif; the section
   system is one neutral card treatment, not six coloured ones.
   ============================================================================= */

.ai-explain-drawer {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 45;
  display: flex;
  flex-direction: column;
  width: var(--ai-drawer-width, min(440px, 94vw));
  border-left: 1px solid var(--line-2);
  background: var(--paper);
  box-shadow: var(--e-3);
}

.ai-drawer-resize-handle {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 8px;
  cursor: col-resize;
  touch-action: none;
}

.ai-drawer-resize-handle::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 3px;
  width: 2px;
  height: 28px;
  transform: translateY(-50%);
  border-radius: var(--r-full);
  background: var(--line-2);
  transition: background-color var(--t-fast) var(--ease);
}

.ai-drawer-resize-handle:hover::after,
.is-ai-drawer-resizing .ai-drawer-resize-handle::after {
  background: var(--ink-3);
}

.ai-drawer-scroll {
  flex: 1 1 auto;
  min-height: 0;
  padding: var(--s-4);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.ai-drawer-close {
  position: absolute;
  top: var(--s-2);
  right: var(--s-2);
  z-index: 2;
  display: grid;
  place-items: center;
  width: var(--hit-sm);
  height: var(--hit-sm);
  border-radius: var(--r-sm);
  color: var(--ink-3);
}

.ai-drawer-close:hover {
  background: var(--paper-3);
  color: var(--ink);
}

.ai-tutor-panel {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}

.ai-tutor-head {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding-right: var(--s-8);
  font-size: var(--fs-sm);
  font-weight: 650;
}

.ai-conversation {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}

.ai-turn {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}

.ai-user-message {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-sm);
  background: var(--paper-3);
}

.ai-user-message strong {
  color: var(--ink-3);
  font-size: var(--fs-2xs);
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
}

.ai-user-message span {
  font-size: var(--fs-sm);
}

.ai-output {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  padding: var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--paper-2);
}

.ai-output.has-concern {
  border-color: var(--warn-line);
  background: var(--warn-wash);
}

.ai-output-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-2);
  color: var(--ink-3);
  font-size: var(--fs-2xs);
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
}

.ai-output > ol {
  margin: 0;
  padding-left: var(--s-5);
}

.ai-section {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  padding-top: var(--s-3);
  border-top: 1px solid var(--line);
}

.ai-section:first-of-type {
  padding-top: 0;
  border-top: 0;
}

.ai-section-title {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  color: var(--ink-3);
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
}

.ai-section-title svg {
  width: 13px;
  height: 13px;
}

.ai-section-body {
  color: var(--ink-2);
  font-family: var(--font-read);
  font-size: var(--fs-sm);
  line-height: var(--lh-read);
}

.ai-section-body p + p,
.ai-step-copy p + p {
  margin-top: var(--s-2);
}

.ai-step-list {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.ai-step-list li {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--s-2);
  align-items: start;
}

.ai-step-index {
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  border-radius: var(--r-full);
  background: var(--paper-3);
  color: var(--ink-2);
  font-size: var(--fs-2xs);
  font-weight: 700;
}

.ai-step-copy {
  min-width: 0;
  color: var(--ink-2);
  font-family: var(--font-read);
  font-size: var(--fs-sm);
  line-height: var(--lh-read);
}

.ai-step-title {
  display: block;
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: var(--fs-xs);
  font-weight: 650;
}

.ai-step-copy strong:not(.ai-step-title) {
  color: var(--ink);
}

.ai-step-copy p {
  margin: 0;
}

.ai-wrong-choice-list {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.ai-wrong-choice-list > li {
  padding: var(--s-2);
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
  background: var(--paper);
  font-size: var(--fs-sm);
}

.ai-wrong-choice-list > li > strong {
  margin-right: var(--s-1);
  color: var(--ink);
}

.ai-final-answer {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-3);
  border: 1px solid var(--ok-line);
  border-radius: var(--r-sm);
  background: var(--ok-wash);
}

.ai-final-answer span {
  color: var(--ink-3);
  font-size: var(--fs-2xs);
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
}

.ai-final-answer strong {
  color: var(--ok);
  font-size: var(--fs-md);
  font-weight: 700;
}

.ai-quick-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-2);
}

.ai-action-row,
.ai-custom-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
}

.ai-custom-row input {
  flex: 1 1 180px;
  min-width: 0;
  min-height: var(--hit-sm);
}

.ai-loading {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  color: var(--ink-3);
  font-size: var(--fs-sm);
}

.ai-loading svg {
  animation: spin 900ms linear infinite;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

.ai-hint-passage,
.ai-hint-choice {
  padding: var(--s-2) var(--s-3);
  border: 1px solid var(--line);
  border-left-width: 2px;
  border-radius: var(--r-xs);
  background: var(--paper-2);
  color: var(--ink-2);
  font-family: var(--font-read);
  font-size: var(--fs-sm);
  line-height: var(--lh-base);
}

.ai-hint-passage.has-concern {
  border-color: var(--warn-line);
  background: var(--warn-wash);
}

.ai-hint-choice strong {
  color: var(--ink);
}

.ai-hint-choice.is-correct {
  border-left-color: var(--ok);
}

.ai-hint-choice.is-wrong {
  border-left-color: var(--wrong);
}

.saved-ai-details .katex,
.ai-section-body .katex {
  font-size: 1.02em;
}

.saved-ai-details .katex-display,
.ai-section-body .katex-display {
  margin: var(--s-2) 0;
  overflow-x: auto;
  overflow-y: hidden;
}

/* Phase 3: the drawer is a conversation, not a stack of cards. The controls
   stay fixed at the top, the student's question is a quiet right-aligned
   prompt, and the tutor answer reads as one editorial document. */
.ai-drawer-scroll {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  padding: 0 var(--s-4) var(--s-4);
}

.ai-tutor-head {
  position: sticky;
  top: 0;
  z-index: 2;
  min-height: 64px;
  padding: var(--s-3) var(--s-8) var(--s-3) 0;
  border-bottom: 1px solid var(--line);
  background: var(--paper);
}

.ai-tutor-head > div:first-child {
  display: grid;
  gap: 2px;
}

.ai-tutor-head > div:first-child > span {
  color: var(--ink-3);
  font-size: var(--fs-xs);
  font-weight: 400;
}

.ai-quick-grid {
  display: flex;
  gap: var(--s-2);
  padding-bottom: var(--s-1);
  overflow-x: auto;
  scrollbar-width: thin;
}

.ai-quick-grid .secondary-button {
  flex: none;
  border-radius: var(--r-full);
  color: var(--ink-2);
  background: transparent;
}

.ai-custom-row {
  padding-bottom: var(--s-3);
  border-bottom: 1px solid var(--line);
}

.ai-conversation {
  gap: var(--s-6);
}

.ai-turn {
  gap: var(--s-3);
}

.ai-output {
  gap: var(--s-4);
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
}

.ai-output.has-concern {
  padding: var(--s-3);
  border: 1px solid var(--warn-line);
  background: var(--warn-wash);
}

.ai-output-head {
  padding-bottom: var(--s-2);
  border-bottom: 2px solid var(--ink);
}

.ai-user-message {
  width: fit-content;
  max-width: 86%;
  margin-left: auto;
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-md) var(--r-md) 0 var(--r-md);
  background: var(--paper-3);
}

.ai-user-message span {
  order: 2;
  color: var(--ink-3);
  font-size: var(--fs-2xs);
}

.ai-user-message strong {
  order: 1;
  color: var(--ink);
  font-size: var(--fs-sm);
  letter-spacing: 0;
  text-transform: none;
}

.ai-section {
  padding-top: var(--s-4);
}

.ai-section-body {
  font-size: var(--fs-md);
}

.ai-wrong-choice-list > li {
  padding: var(--s-2) 0;
  border: 0;
  border-top: 1px solid var(--line);
  border-radius: 0;
  background: transparent;
}

.ai-action-row {
  padding-top: var(--s-3);
  border-top: 1px solid var(--line);
}

.ai-action-row .secondary-button {
  flex: 1 1 calc(50% - var(--s-2));
}

/* =============================================================================
   14 — REVIEW / SUMMARY PAGE (.rp-*)
   No brand red in this subtree either. The score badge is deliberately neutral:
   a student's score is a number, not a pass/fail verdict.
   ============================================================================= */

.rp-page {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  background: var(--paper-2);
}

.rp-topbar {
  position: sticky;
  top: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-3) var(--s-5);
  background: var(--paper);
  border-bottom: 1px solid var(--line);
}

.rp-topbar-side {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  min-width: 0;
}

.rp-topbar-right {
  justify-content: flex-end;
}

.rp-topbar-side svg {
  color: var(--ink-4);
}

.rp-test-title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--fs-base);
  font-weight: 650;
}

.rp-date {
  color: var(--ink-3);
  font-size: var(--fs-xs);
  white-space: nowrap;
}

.rp-play-btn,
.rp-close-btn {
  display: grid;
  place-items: center;
  width: var(--hit);
  height: var(--hit);
  flex: none;
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  background: var(--paper);
  color: var(--ink-2);
  transition: background-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}

.rp-play-btn svg,
.rp-close-btn svg {
  width: 16px;
  height: 16px;
}

.rp-play-btn:hover,
.rp-close-btn:hover {
  background: var(--paper-3);
  color: var(--ink);
}

.rp-main {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  width: 100%;
  max-width: 980px;
  margin-inline: auto;
  padding: var(--s-6) var(--s-5) var(--s-10);
}

.rp-overview {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-5);
  padding: var(--s-5);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper);
}

.rp-overview-left {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  min-width: 0;
}

.rp-overview-title {
  font-size: var(--fs-xl);
}

.rp-stats-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-6);
}

.rp-stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.rp-stat span {
  color: var(--ink-3);
  font-size: var(--fs-2xs);
  font-weight: 600;
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
}

.rp-stat strong {
  font-size: var(--fs-2xl);
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}

.rp-stat-correct strong { color: var(--ok); }
.rp-stat-incorrect strong { color: var(--wrong); }
.rp-stat-blank strong { color: var(--ink-3); }

.rp-score-badge {
  display: flex;
  align-items: baseline;
  gap: var(--s-1);
  padding: var(--s-3) var(--s-5);
  border: 1px solid var(--line-2);
  border-radius: var(--r-md);
  background: var(--paper-2);
}

/* The score was --fs-3xl (31px), the largest numeral on the page, against
   --fs-2xl (25px) for the correct/incorrect/blank counts and body text for the
   timing analysis. "3 / 5" is a number the student already knows the moment
   they submit; "Q2 wrong in 5s, Q5 blank after 31s" is the pair of facts that
   changes what they do next. The hierarchy was inverted against the
   information. The badge is still the first thing found — it keeps its box,
   its position and tabular figures — it just stops out-typing the findings. */
.rp-score-num {
  font-size: var(--fs-2xl);
  font-weight: 700;
  letter-spacing: var(--tr-tight);
  font-variant-numeric: tabular-nums;
}

.rp-score-denom {
  color: var(--ink-3);
  font-size: var(--fs-lg);
  font-variant-numeric: tabular-nums;
}

/* -- timing analysis: promoted ---------------------------------------------
   renderTimeSummary() emits a generic .attempt-extra-card, so on the debrief
   the most actionable block on the page arrived with the same weight as any
   other panel. Scoped to .rp-page so the card keeps its neutral styling
   everywhere else it is used. */
.rp-page .attempt-extra-card {
  gap: var(--s-4);
  padding: var(--s-5);
  border-color: var(--line-2);
}

.rp-page .attempt-extra-card > h3 {
  font-size: var(--fs-lg);
}

/* The average is the reference every bucket below is judged against, so it
   reads as a figure rather than as a sentence. */
.rp-page .attempt-extra-card > p strong {
  font-size: var(--fs-lg);
  font-weight: 650;
  font-variant-numeric: tabular-nums;
}

.rp-page .time-summary-grid {
  gap: var(--s-4);
}

/* renderTimeRowList() emits a bare <div><strong>title</strong>…</div> — no
   class of its own — so these are addressed positionally. Order is fixed in
   renderTimeSummary(): slowest, fast-but-wrong, slow-and-wrong.

   "Fast but wrong" and "Slow and wrong" are the two findings a student can act
   on; "Slowest questions" is context. The two named buckets get a marker in
   the state colour they describe — this is inside .rp-page, where red means an
   incorrect answer, which is exactly what these rows are. */
.rp-page .time-summary-grid > div {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--paper-2);
}

.rp-page .time-summary-grid > div > strong {
  font-size: var(--fs-2xs);
  font-weight: 650;
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  color: var(--ink-3);
}

.rp-page .time-summary-grid > div:nth-child(2),
.rp-page .time-summary-grid > div:nth-child(3) {
  border-left: 3px solid var(--wrong-line);
  background: var(--paper);
}

.rp-page .time-summary-grid ul {
  margin: 0;
  padding-left: var(--s-4);
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
}

.rp-page .time-summary-grid li {
  margin-bottom: var(--s-1);
}

/* An empty bucket is good news, so it should not look like a broken panel. */
.rp-page .time-summary-grid > div > p {
  margin: 0;
  color: var(--ink-4);
  font-size: var(--fs-sm);
}

.rp-qlist {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper);
  overflow: hidden;
}

.rp-section-hd {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  background: var(--paper-2);
  border-bottom: 1px solid var(--line);
}

.rp-section-hd strong {
  font-size: var(--fs-sm);
}

.rp-section-count {
  margin-left: auto;
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

/* Direction B stops the debrief being a table.

   Measured on the old rule: `92px 82px 90px 60px minmax(0,1fr)` across 938px.
   Actions took 534px — 57% of the widest element on the page — to repeat the
   same two buttons on every row. AI (90px) and Flag (60px) rendered empty in
   all five rows of a normal attempt. Time, the one column that carries a
   finding, got 82px. A fixed grid forces every row to reserve space for data
   most rows do not have, which is exactly what makes it read as a database
   dump.

   Now each row is a flex line that carries only what it has. The column
   header goes with it: five uppercase labels above five columns is the thing
   that announces "table", and once the row is self-describing the header is
   restating what is already legible. */
.rp-qlist-head {
  display: none;
}

.rp-qrow {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  padding: var(--s-2) var(--s-4);
}

/* The emitter always writes five children; AI and Flag are empty <span>s when
   the student used neither. :empty collapses them, so a row with no AI and no
   flag is three items, not five with two holes. */
.rp-qrow > span:empty {
  display: none;
}

.rp-qrow {
  border-bottom: 1px solid var(--line);
  transition: background-color var(--t-fast) var(--ease);
}

.rp-qrow:last-child {
  border-bottom: 0;
}

.rp-qrow:hover {
  background: var(--paper-2);
}

.rp-qrow-num {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-width: 60px;
  font-size: var(--fs-sm);
  font-weight: 650;
  font-variant-numeric: tabular-nums;
}

/* Time is the datum that changes behaviour, so it stops being the narrowest
   column on the row and gets a standing label instead of a table header. */
.rp-qrow .rp-time-val {
  font-size: var(--fs-md);
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

.rp-qrow .rp-time-val::before {
  content: "Time ";
  font-size: var(--fs-2xs);
  font-weight: 600;
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  color: var(--ink-4);
}

/* Actions stop reserving 57% of the row and sit at their natural width,
   pushed to the trailing edge. */
.rp-qrow .rp-actions {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin-left: auto;
}

.rp-status-icon {
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  flex: none;
  border-radius: var(--r-full);
  background: var(--paper-3);
  color: var(--ink-3);
  font-size: var(--fs-xs);
  font-weight: 700;
  line-height: 1;
}

.rp-qrow.correct .rp-status-icon {
  background: var(--ok-wash);
  color: var(--ok);
}

.rp-qrow.wrong .rp-status-icon {
  background: var(--wrong-wash);
  color: var(--wrong);
}

.rp-qrow.unanswered .rp-status-icon {
  background: var(--paper-3);
  color: var(--ink-4);
}

.rp-time-val {
  color: var(--ink-2);
  font-size: var(--fs-xs);
  font-variant-numeric: tabular-nums;
}

.rp-qrow.correct .rp-time-val { color: var(--ok); }
.rp-qrow.wrong .rp-time-val { color: var(--wrong); }
.rp-qrow.unanswered .rp-time-val { color: var(--ink-4); }

.rp-ai-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  padding: 1px var(--s-2);
  border: 1px solid var(--info-line);
  border-radius: var(--r-full);
  background: var(--info-wash);
  color: var(--info);
  font-size: var(--fs-2xs);
  font-weight: 650;
}

.rp-ai-badge svg {
  width: 11px;
  height: 11px;
}

.rp-flag-icon {
  display: inline-flex;
  color: var(--ink-3);
}

.rp-flag-icon svg {
  width: 14px;
  height: 14px;
  fill: currentColor;
}

.rp-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s-2);
}

.rp-btn-practice,
.rp-btn-review {
  min-height: var(--hit-sm);
  padding: 0 var(--s-3);
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  background: var(--paper);
  color: var(--ink);
  font-size: var(--fs-xs);
  font-weight: 650;
  transition: background-color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}

.rp-btn-practice:hover,
.rp-btn-review:hover {
  background: var(--paper-3);
  border-color: var(--ink-4);
}

.rp-btn-review {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

.rp-btn-review:hover {
  background: var(--ink-2);
  border-color: var(--ink-2);
}

.rp-main .attempt-extra-card {
  background: var(--paper);
}

/* =============================================================================
   15 — ADMIN
   ============================================================================= */

.admin-grid,
.access-grid,
.materials-admin-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-3);
}

.admin-grid.is-import-only .admin-card:not(.span-2) {
  display: none;
}

.admin-card {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  padding: var(--s-5);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper);
}

.admin-card h3 {
  font-size: var(--fs-base);
}

.admin-card-note {
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

.library-panel,
.access-admin-panel,
.platform-analytics-panel,
.materials-admin-panel,
.backup-admin-panel,
.import-results-panel,
.deployment-status-panel {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
}

.import-results-panel:not(.has-results) .import-summary,
.import-results-panel:not(.has-results) .quality-list,
.import-results-panel:not(.has-results) .question-review-list {
  display: none;
}

/* -- import pipeline ------------------------------------------------------- */
.pipeline {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--s-2);
}

.pipeline-step {
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
  padding: var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--paper-2);
}

.pipeline-step svg {
  width: 18px;
  height: 18px;
  color: var(--ink-4);
}

.pipeline-step strong {
  font-size: var(--fs-sm);
}

.pipeline-step span {
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

.pipeline-step.is-active {
  border-color: var(--info-line);
  background: var(--info-wash);
}

.pipeline-step.is-active svg {
  color: var(--info);
}

.import-form {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--s-3);
}

.import-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
}

.import-progress {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

.import-progress > div {
  height: 4px;
  overflow: hidden;
  border-radius: var(--r-full);
  background: var(--paper-3);
}

.import-progress > div span {
  display: block;
  height: 100%;
  background: var(--ink);
  transition: width var(--t-slow) var(--ease);
}

.import-summary {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--s-3);
}

/* -- stat stack + review queue --------------------------------------------- */
.stat-stack,
.queue-list,
.tag-roster,
.admin-material-list,
.admin-rank-list,
.class-test-list,
.class-test-matrix {
  display: flex;
  flex-direction: column;
}

.stat-stack div,
.queue-list > div {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-2);
  padding: var(--s-2) 0;
  border-top: 1px solid var(--line);
}

.stat-stack div:first-child,
.queue-list > div:first-child {
  border-top: 0;
}

.stat-stack span,
.queue-list small {
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

.stat-stack strong,
.queue-list strong {
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
}

.queue-missing-visuals {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  width: 100%;
}

.queue-visual-action {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2);
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
  background: var(--paper-2);
}

.queue-visual-action > span {
  flex: 1 1 140px;
  min-width: 0;
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

/* -- quality flags --------------------------------------------------------- */
.quality-list {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}

.quality-item {
  display: flex;
  align-items: flex-start;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--paper);
  font-size: var(--fs-sm);
}

.quality-item svg {
  margin-top: 2px;
  color: var(--ink-4);
}

.quality-item.warning {
  border-color: var(--warn-line);
  background: var(--warn-wash);
  color: var(--warn);
}

.quality-item.warning svg { color: var(--warn); }

.quality-item.error {
  border-color: var(--wrong-line);
  background: var(--wrong-wash);
  color: var(--wrong);
}

.quality-item.error svg { color: var(--wrong); }

.quality-item.success {
  border-color: var(--ok-line);
  background: var(--ok-wash);
  color: var(--ok);
}

.quality-item.success svg { color: var(--ok); }

/* -- imported question review ---------------------------------------------- */
.question-review-list {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}

.question-review-card {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  padding: var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper);
}

.question-review-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
}

.question-review-head h3 {
  font-size: var(--fs-sm);
}

.editor-head-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
}

.question-review-card.is-focus-pulse {
  animation: import-card-focus 1.2s var(--ease) 1;
}

@keyframes import-card-focus {
  0% { box-shadow: 0 0 0 0 rgba(128, 128, 128, 0.45); }
  100% { box-shadow: 0 0 0 12px rgba(128, 128, 128, 0); }
}

.choice-list,
.admin-question-choice-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-2);
}

.choice-item,
.admin-choice-item {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: var(--s-2);
  padding: var(--s-2);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--paper-2);
}

.choice-item.is-correct {
  border-color: var(--ok-line);
  background: var(--ok-wash);
}

.choice-letter {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border: 1px solid var(--line-2);
  border-radius: var(--r-full);
  background: var(--paper);
  font-size: var(--fs-xs);
  font-weight: 700;
}

.choice-item textarea,
.admin-choice-item textarea {
  min-height: 56px;
  font-size: var(--fs-sm);
}

.question-flags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-1);
}

.admin-question-answer-grid,
.admin-question-media-fields,
.test-edit-grid,
.material-form {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-3);
}

.admin-question-edit-form,
.test-edit-form {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
}

.admin-question-media-editor {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  padding-top: var(--s-3);
  border-top: 1px solid var(--line);
}

.admin-question-editor-section-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-2);
}

.admin-question-editor-section-head strong {
  font-size: var(--fs-xs);
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  color: var(--ink-3);
}

.admin-question-media-list,
.media-placeholder-list,
.missing-visual-list {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}

.admin-question-media-row {
  display: grid;
  grid-template-columns: 120px minmax(0, 1fr);
  gap: var(--s-3);
  padding: var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--paper-2);
}

.admin-question-media-preview {
  display: grid;
  place-items: center;
  min-height: 84px;
  padding: var(--s-2);
  border: 1px dashed var(--line-2);
  border-radius: var(--r-xs);
  background: var(--paper);
  color: var(--ink-4);
  font-size: var(--fs-2xs);
  text-align: center;
}

.admin-question-media-preview.has-media {
  border-style: solid;
}

.admin-question-media-preview img {
  max-height: 120px;
  border-radius: var(--r-xs);
}

.admin-question-media-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
}

/* -- missing visuals ------------------------------------------------------- */
.missing-visual-panel {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  padding: var(--s-4);
  border: 1px solid var(--warn-line);
  border-radius: var(--r-md);
  background: var(--warn-wash);
}

.missing-visual-panel h3 {
  font-size: var(--fs-sm);
  color: var(--warn);
}

.missing-visual-panel p {
  color: var(--ink-2);
  font-size: var(--fs-xs);
}

.missing-visual-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-2) var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--paper);
}

.missing-visual-row span {
  font-size: var(--fs-sm);
}

.missing-visual-row small {
  display: block;
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

.missing-visual-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
}

.media-placeholder {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  padding: var(--s-3);
  border: 1px dashed var(--line-2);
  border-radius: var(--r-sm);
  background: var(--paper-2);
}

.media-placeholder.has-uploaded-image {
  border-style: solid;
  border-color: var(--ok-line);
  background: var(--ok-wash);
}

.media-placeholder-copy {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.media-placeholder span {
  font-size: var(--fs-sm);
}

.media-placeholder small {
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

.media-placeholder img {
  max-height: 200px;
  border-radius: var(--r-xs);
}

/* -- test library ---------------------------------------------------------- */
.admin-table {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}

.admin-row,
.admin-material-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper);
}

.admin-row h3 {
  font-size: var(--fs-sm);
}

.admin-row-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-1) var(--s-3);
  margin-top: 2px;
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

.admin-row-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s-2);
}

.admin-row-actions .secondary-button,
.admin-row-actions .teacher-button,
.admin-row-actions .ghost-button {
  min-height: var(--hit-sm);
  padding: 0 var(--s-3);
  font-size: var(--fs-xs);
}

.admin-material-list {
  gap: var(--s-2);
}

/* -- class management ------------------------------------------------------ */
.class-card-head,
.analytics-rank-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  padding: var(--s-3);
  cursor: pointer;
  list-style: none;
}

.class-test-card > summary.class-card-head::-webkit-details-marker {
  display: none;
}

.class-test-card {
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--paper);
}

.class-test-card > summary.class-card-head::after {
  content: "+";
  color: var(--ink-3);
  font-size: var(--fs-lg);
  line-height: 1;
}

.class-test-card[open] > summary.class-card-head::after {
  content: "\2212";
}

.class-student-list,
.chip-group {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
}

.class-student-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  padding: 2px var(--s-2);
  border: 1px solid var(--line-2);
  border-radius: var(--r-full);
  background: var(--paper-2);
  font-size: var(--fs-xs);
}

.class-student-chip button {
  display: grid;
  place-items: center;
  width: 18px;
  height: 18px;
  border-radius: var(--r-full);
  color: var(--ink-3);
}

.class-student-chip button:hover {
  background: var(--wrong-wash);
  color: var(--wrong);
}

.class-student-chip svg {
  width: 11px;
  height: 11px;
}

.class-test-matrix {
  gap: var(--s-2);
  max-height: 420px;
  overflow-y: auto;
}

.class-test-list {
  gap: var(--s-1);
  padding: 0 var(--s-3) var(--s-3);
}

.class-test-toggle {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-1) var(--s-2);
  border-radius: var(--r-xs);
  font-size: var(--fs-sm);
  cursor: pointer;
}

.class-test-toggle:hover {
  background: var(--paper-2);
}

.class-test-toggle small {
  margin-left: auto;
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

/* -- platform analytics ---------------------------------------------------- */
.analytics-filter-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(200px, 280px));
  gap: var(--s-3);
}

.analytics-select-field {
  min-width: 0;
}

.analytics-combobox {
  position: relative;
}

.analytics-combo-button {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-2);
  width: 100%;
  min-height: var(--hit);
  padding: 0 var(--s-3);
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  background: var(--paper);
  color: var(--ink);
  font-size: var(--fs-sm);
  text-align: left;
}

.analytics-combo-button span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.analytics-combo-button svg {
  color: var(--ink-4);
}

.analytics-combo-button:hover {
  border-color: var(--ink-4);
}

.analytics-combo-button:disabled {
  background: var(--paper-3);
  color: var(--ink-3);
}

.analytics-combo-menu {
  position: absolute;
  top: calc(100% + var(--s-1));
  left: 0;
  right: 0;
  z-index: 20;
  display: none;
  flex-direction: column;
  max-height: 320px;
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  background: var(--paper);
  box-shadow: var(--e-3);
  overflow: hidden;
}

.analytics-combobox.is-open .analytics-combo-menu {
  display: flex;
}

.analytics-combo-search {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2);
  border-bottom: 1px solid var(--line);
  background: var(--paper-2);
}

.analytics-combo-search svg {
  color: var(--ink-4);
}

.analytics-combo-search input {
  min-height: var(--hit-sm);
  border-color: var(--line);
}

.analytics-combo-options {
  display: flex;
  flex-direction: column;
  overflow-y: auto;
}

.analytics-combo-option {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  width: 100%;
  min-height: var(--hit-sm);
  padding: var(--s-1) var(--s-3);
  font-size: var(--fs-sm);
  text-align: left;
}

.analytics-combo-option span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.analytics-combo-option:hover {
  background: var(--paper-2);
}

.analytics-combo-option.is-selected {
  background: var(--paper-3);
  font-weight: 650;
}

.analytics-rank-card {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper);
}

.analytics-rank-head span {
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

.admin-rank-row {
  border-top: 1px solid var(--line);
}

.admin-rank-row summary {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-2) var(--s-3);
  font-size: var(--fs-sm);
  list-style: none;
}

.rank-number {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  flex: none;
  border-radius: var(--r-full);
  background: var(--paper-3);
  color: var(--ink-2);
  font-size: var(--fs-xs);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.admin-answer-table {
  display: flex;
  flex-direction: column;
  padding: 0 var(--s-3) var(--s-3);
}

.admin-answer-row,
.admin-answer-table-head {
  display: grid;
  grid-template-columns: 56px minmax(0, 1fr) 90px 90px;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-1) var(--s-2);
  border-top: 1px solid var(--line);
  font-size: var(--fs-xs);
}

.admin-answer-table-head {
  border-top: 0;
  color: var(--ink-3);
  font-weight: 650;
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
}

.admin-answer-row.correct {
  background: var(--ok-wash);
  color: var(--ok);
}

.admin-answer-row.unanswered {
  color: var(--ink-4);
}

/* -- wrong-answer chart ---------------------------------------------------- */
.wrong-answer-chart {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  padding: var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper);
}

.wrong-answer-chart-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
}

.wrong-answer-chart-head strong {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  font-size: var(--fs-sm);
}

.wrong-answer-chart-head strong svg {
  color: var(--ink-4);
}

.wrong-answer-chart-head span,
.wrong-answer-note {
  color: var(--ink-3);
  font-size: var(--fs-xs);
}

.wrong-answer-column-chart {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--s-2);
}

.wrong-answer-y-axis {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: flex-end;
  color: var(--ink-4);
  font-size: var(--fs-2xs);
  font-variant-numeric: tabular-nums;
}

.wrong-answer-columns {
  display: flex;
  align-items: flex-end;
  gap: var(--s-1);
  height: 180px;
  padding: var(--s-2);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--paper-2);
  overflow-x: auto;
}

/* Was `flex: 1 0 26px` with no cap. A column chart showing a single question
   therefore stretched that one column across the whole card — measured at
   983.2 × 104.9 filled solid rgb(179,38,30), which made a one-question sample
   the loudest object in the product. A bar's width should mean nothing; only
   its height carries data. --bar-max stops it growing. */
.wrong-answer-column {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  gap: 2px;
  flex: 0 1 26px;
  min-width: 26px;
  max-width: var(--bar-max);
  height: 100%;
}

/* Left-align the row so a two-bar chart does not float in the middle of a
   1000px card pretending to be a distribution. */
.wrong-answer-columns {
  justify-content: flex-start;
}

.wrong-answer-column-value {
  color: var(--ink-2);
  font-size: var(--fs-2xs);
  font-variant-numeric: tabular-nums;
}

.wrong-answer-column-track {
  display: flex;
  align-items: flex-end;
  width: 100%;
  flex: 1 1 auto;
  border-radius: var(--r-xs);
  background: var(--paper-3);
}

.wrong-answer-column-fill {
  width: 100%;
  border-radius: var(--r-xs);
  background: var(--wrong);
}

.wrong-answer-q,
.wrong-answer-column small {
  color: var(--ink-4);
  font-size: var(--fs-2xs);
}

/* -- deployment ------------------------------------------------------------ */
.deployment-card {
  padding: var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper);
}

.deployment-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--s-3);
}

.deployment-grid div {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--paper-2);
}

.deployment-grid span {
  color: var(--ink-3);
  font-size: var(--fs-2xs);
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
}

.deployment-grid strong {
  font-size: var(--fs-sm);
  word-break: break-word;
}

/* -- json editor ----------------------------------------------------------- */
#questionsJsonTextarea,
.json-editor-modal textarea {
  min-height: 420px;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  line-height: 1.6;
}

.admin-edit-modal input#editTestTimed {
  width: 90px;
}

/* Phase 3: "which question is worst?" is a horizontal sorted ranking.
   The track has a fixed geometry, every bar starts at zero, and the printed
   denominator remains the fact students and teachers can act on. */
.wrong-answer-bar-list {
  display: grid;
  gap: 0;
  margin-top: var(--s-3);
  border-top: 1px solid var(--line);
}

.wrong-answer-bar-row {
  display: grid;
  grid-template-columns: minmax(140px, 0.8fr) minmax(180px, 1.6fr) 48px 92px;
  gap: var(--s-3);
  align-items: center;
  min-height: 52px;
  border-bottom: 1px solid var(--line);
}

.wrong-answer-bar-row > div:first-child {
  display: grid;
  gap: 1px;
  min-width: 0;
}

.wrong-answer-bar-row > div:first-child strong,
.wrong-answer-bar-row > div:first-child span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.wrong-answer-bar-row > div:first-child strong {
  font-size: var(--fs-sm);
}

.wrong-answer-bar-row > div:first-child span,
.wrong-answer-bar-row small {
  color: var(--ink-3);
  font-size: var(--fs-2xs);
}

.wrong-answer-bar-track > span {
  background: var(--wrong);
}

.wrong-answer-bar-row output {
  color: var(--wrong);
  font-size: var(--fs-sm);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.wrong-answer-bar-row small {
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* =============================================================================
   16 — RESPONSIVE
   Primary target is 1366x768. 390px is verified for no horizontal page scroll.
   ============================================================================= */

@media (min-width: 1400px) {
  .content {
    margin-inline: auto;
  }
}

/* -- 1366x768: keep the player's question and choices reachable ------------- */
@media (max-height: 800px) {
  .dsat-header {
    min-height: 46px;
    padding-block: var(--s-1);
  }

  .dsat-passage-panel,
  .dsat-q-panel {
    gap: var(--s-3);
    padding: var(--s-4) var(--s-5);
  }

  .dsat-bottom-nav {
    padding-block: var(--s-1);
  }

  /* Back / Next keep the full 48px target here. The vertical budget is bought
     from panel padding instead — the owner asked for a large clickable area,
     and 1366x768 is the machine most students are on. */

  .question-navigator {
    max-height: 44vh;
  }
}

@media (max-width: 1180px) {
  .metric-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .test-grid,
  .inner-test-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .admin-grid,
  .access-grid,
  .materials-admin-grid,
  .analytics-grid,
  .settings-grid,
  .vocab-grid,
  .wrong-reason-grid,
  .analytics-filter-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .span-2 {
    grid-column: span 1;
  }

  .pipeline,
  .import-form,
  .drill-grid,
  .question-taxonomy-grid,
  .time-summary-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vocab-dashboard {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .filter-panel {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .mistake-controls,
  .mistake-review-controls {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .form-field.grow {
    grid-column: span 2;
  }
}

@media (max-width: 880px) {
  /* Sidebar becomes a horizontal bar so the player and content get full width. */
  .app-shell {
    grid-template-columns: minmax(0, 1fr);
  }

  .sidebar {
    position: static;
    height: auto;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s-3);
    padding: var(--s-3) var(--s-4);
    border-right: 0;
    border-bottom: 1px solid var(--line);
  }

  .brand,
  .account-card {
    flex: 0 0 auto;
  }

  .account-card {
    display: none;
  }

  .nav-list {
    flex-direction: row;
    flex: 1 1 100%;
    gap: var(--s-1);
    overflow-x: auto;
    padding-bottom: var(--s-1);
  }

  .nav-item {
    width: auto;
    flex: 0 0 auto;
    white-space: nowrap;
  }

  .nav-item.is-active::before {
    top: auto;
    bottom: 0;
    left: var(--s-3);
    right: var(--s-3);
    width: auto;
    height: 2px;
    transform: none;
  }

  .sidebar-footer {
    padding-top: 0;
    border-top: 0;
    margin-left: auto;
  }

  .topbar,
  .content {
    padding-inline: var(--s-4);
  }

  .topbar {
    align-items: flex-start;
    flex-direction: column;
  }

  /* Player: passage above question, each still scrolling itself.

     The Direction B split handle is a third child of .dsat-main, so it has to
     be collapsed here or it would claim a row of the stacked layout. Dragging
     a vertical divider is meaningless once the panels are stacked anyway —
     same reasoning as .calc-resize-handle below. The :has() selectors that set
     the desktop ratio are overridden by these rules, so --split has no effect
     at this width. */
  .split-handle {
    display: none;
  }

  .dsat-shell:not(.is-math) .dsat-main,
  .dsat-shell:not(.is-math) .dsat-main:has(> .split-handle),
  .dsat-shell:not(.is-math) .dsat-main[data-shape="passage-heavy"]:has(> .split-handle),
  .dsat-shell:not(.is-math) .dsat-main[data-shape="prompt-heavy"]:has(> .split-handle),
  .dsat-shell.is-math .dsat-main.has-calc {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
  }

  .dsat-passage-panel,
  .dsat-main:has(> .split-handle) > .dsat-passage-panel {
    border-right: 0;
    border-bottom: 1px solid var(--line);
  }

  .calc-resize-handle {
    display: none;
  }

  /* The strip keeps the numbers and the arrows; the legend is the first thing
     to go, because the notch and the fill are legible without it. */
  .question-navigator {
    padding-inline: var(--s-3);
  }

  .navigator-legend {
    display: none;
  }

  .dsat-passage-panel,
  .dsat-q-panel {
    padding: var(--s-4);
  }

  .dsat-header,
  .dsat-bottom-nav,
  .dsat-review-bar {
    padding-inline: var(--s-3);
  }

  .ai-explain-drawer,
  .teacher-panel {
    width: min(100vw, 420px);
  }

  .ai-drawer-resize-handle {
    display: none;
  }

  /* .rp-qrow is a flex line now and .rp-qlist-head is display:none, so the
     old five-column override here no longer has anything to size. What the row
     still needs at this width is permission to wrap, so the actions drop under
     the number and time instead of forcing a horizontal scroll. */
  .rp-qrow {
    flex-wrap: wrap;
    gap: var(--s-2) var(--s-3);
    padding-inline: var(--s-3);
  }

  .rp-qrow .rp-actions {
    margin-left: 0;
    width: 100%;
  }

  .completed-test-row {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--s-2);
  }

  .completed-test-actions {
    justify-content: flex-start;
  }

  .admin-row,
  .admin-material-row,
  .missing-visual-row,
  .admin-question-media-row {
    grid-template-columns: minmax(0, 1fr);
  }

  .admin-row-actions {
    justify-content: flex-start;
  }

  .filter-panel {
    grid-template-columns: minmax(0, 1fr);
  }

  .vocab-dashboard {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 560px) {
  .content {
    padding: var(--s-4) var(--s-3);
  }

  .metric-grid,
  .student-insights,
  .test-grid,
  .inner-test-grid,
  .pipeline,
  .import-form,
  .drill-grid,
  .mixed-mode-toggles,
  .mixed-select-grid,
  .super-mix-slot-fields,
  .vocab-meaning-grid,
  .vocab-form-grid,
  .admin-question-answer-grid,
  .admin-question-media-fields,
  .test-edit-grid,
  .material-form,
  .choice-list,
  .admin-question-choice-grid,
  .question-taxonomy-grid,
  .time-summary-grid,
  .mistake-controls,
  .mistake-review-controls,
  .ai-quick-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .form-field.grow {
    grid-column: span 1;
  }

  .vocab-dashboard {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vocab-review-actions {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .tag-form {
    grid-template-columns: minmax(0, 1fr);
  }

  /* Back / Next keep their 48px target even when the bar wraps. */
  .dsat-bottom-nav {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--s-2);
  }

  .dsat-nav-center {
    grid-column: 1 / -1;
    order: -1;
  }

  .dsat-nav-btn {
    min-width: 0;
    flex: 1 1 auto;
    padding-inline: var(--s-3);
  }

  .dsat-hd-btn span {
    display: none;
  }

  .dsat-q-banner {
    gap: var(--s-1);
  }

  .dsat-q-tools {
    width: 100%;
    margin-left: 0;
  }

  .question-navigator {
    width: calc(100vw - var(--s-4));
    max-height: 40vh;
  }

  .rp-qlist-head,
  .rp-qrow {
    grid-template-columns: 62px 56px minmax(0, 1fr);
  }

  .rp-qlist-head > span:nth-child(3),
  .rp-qlist-head > span:nth-child(4),
  .rp-qrow > span:nth-child(3),
  .rp-qrow > span:nth-child(4) {
    display: none;
  }

  .rp-overview {
    padding: var(--s-4);
  }

  .rp-stats-row {
    gap: var(--s-4);
  }

  .rp-stat strong {
    font-size: var(--fs-xl);
  }

  .mixed-card {
    flex-direction: column;
    align-items: stretch;
  }

  .selection-toolbar {
    flex-wrap: wrap;
    max-width: calc(100vw - var(--s-6));
  }

  .selection-color-row {
    padding-right: 0;
    border-right: 0;
  }

  .teacher-panel {
    width: 100vw;
  }

  .teacher-student-row {
    grid-template-columns: minmax(0, 1fr) auto;
  }

  .teacher-status {
    grid-column: 1 / -1;
  }

  .app-toast {
    max-width: calc(100vw - var(--s-6));
  }
}

/* -- Phase 3 student surfaces --------------------------------------------- */
@media (max-width: 1040px) {
  .learning-evidence-workspace,
  .mixed-practice-workspace,
  .vocab-grid {
    grid-template-columns: 1fr;
  }

  .vocab-grid > [data-vocab-area] {
    grid-column: 1;
    grid-row: auto;
  }

  .vocab-grid > [data-vocab-area="study"] { order: 1; }
  .vocab-grid > [data-vocab-area="lookup"] { order: 2; }
  .vocab-grid > [data-vocab-area="decks"] { order: 3; }
  .vocab-grid > [data-vocab-area="recent"] { order: 4; }
  .vocab-grid > [data-vocab-area="imports"] { order: 5; }

  .super-mix-grid {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 760px) {
  .student-study-brief,
  .student-progress-head {
    grid-template-columns: 1fr;
  }

  .student-study-brief {
    padding: var(--s-4);
  }

  .student-brief-side {
    padding: var(--s-4) 0 0;
    border-top: 1px solid var(--line);
    border-left: 0;
  }

  .student-progress-panel {
    padding: var(--s-4);
  }

  .exam-countdown {
    min-width: 0;
  }

  .fix-next-row {
    grid-template-columns: minmax(100px, 1fr) 48px auto;
    gap: var(--s-2) var(--s-3);
    padding: var(--s-2) 0;
  }

  .fix-next-bar {
    grid-column: 1 / -1;
    grid-row: 2;
  }

  .fix-next-row output {
    grid-column: 2;
    grid-row: 1;
  }

  .fix-next-row .text-button {
    grid-column: 3;
    grid-row: 1;
  }

  .student-library-toolbar {
    grid-template-columns: 1fr;
    padding: var(--s-3);
  }

  .student-library-toolbar .access-strip,
  .student-library-toolbar .filter-panel {
    grid-column: 1;
  }

  .student-library-toolbar .access-strip {
    justify-content: flex-start;
  }

  .student-library-toolbar .filter-panel {
    grid-template-columns: 1fr;
  }

  .test-ledger-row {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: var(--s-3);
  }

  .test-ledger-score {
    grid-column: 2;
    grid-row: 1;
  }

  .test-ledger-actions {
    grid-column: 1 / -1;
    justify-content: flex-start;
  }

  .completed-test-row {
    grid-template-columns: minmax(0, 1fr) 96px;
  }

  .completed-test-actions {
    grid-column: 1 / -1;
  }

  .mistake-controls,
  .settings-grid,
  .exam-date-settings-card {
    grid-template-columns: 1fr;
  }

  .mistake-controls > *,
  .exam-date-settings-card {
    grid-column: 1 / -1;
  }

  .exam-date-settings-card .exam-countdown {
    min-width: 0;
  }

  .vocab-card-list,
  .vocab-meaning-grid {
    grid-template-columns: 1fr;
  }

  .vocab-review-actions {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .wrong-answer-bar-row {
    grid-template-columns: minmax(0, 1fr) 48px;
    gap: var(--s-2) var(--s-3);
    padding: var(--s-2) 0;
  }

  .wrong-answer-bar-track {
    grid-column: 1 / -1;
    grid-row: 2;
  }

  .wrong-answer-bar-row output {
    grid-column: 2;
    grid-row: 1;
  }

  .wrong-answer-bar-row small {
    grid-column: 1 / -1;
    grid-row: 3;
    text-align: left;
  }
}

@media (max-width: 420px) {
  .student-brief-copy h2 {
    font-size: var(--fs-xl);
  }

  .student-brief-number strong {
    font-size: var(--fs-3xl);
  }

  .test-ledger-main > p {
    flex-wrap: wrap;
  }

  .test-ledger-actions > .small-button {
    flex: 1 1 auto;
  }

  .panel-disclosure > summary,
  .panel-disclosure-body {
    padding-inline: var(--s-3);
  }

  .ai-action-row .secondary-button {
    flex-basis: 100%;
  }
}

/* =============================================================================
   17 — PRINT
   ============================================================================= */

@media print {
  .sidebar,
  .topbar-actions,
  .theme-toggle,
  .dsat-bottom-nav,
  .dsat-header,
  .dsat-review-bar,
  .question-navigator,
  .selection-toolbar,
  .ai-explain-drawer,
  .teacher-panel,
  .desmos-window,
  .app-toast,
  .sync-indicator,
  .app-watermark {
    display: none !important;
  }

  body,
  .main-shell,
  .practice-player,
  .dsat-shell,
  .rp-page {
    background: #ffffff;
    color: #000000;
  }

  .app-shell {
    grid-template-columns: minmax(0, 1fr);
  }

  .practice-player {
    position: static;
    overflow: visible;
  }

  .dsat-main,
  .dsat-shell:not(.is-math) .dsat-main {
    grid-template-columns: minmax(0, 1fr);
  }

  .dsat-passage-panel,
  .dsat-q-panel {
    overflow: visible;
  }

  .test-card,
  .admin-card,
  .rp-qrow,
  .dsat-choice {
    break-inside: avoid;
  }
}

/* =============================================================================
   18 — REMAINING EMITTED HOOKS
   Every class script.js can emit that is not already covered above. Kept in one
   place so the coverage check stays easy to re-run:
     grep -oE 'class="[^"]+"' script.js | ... | comm against the selector list.
   ============================================================================= */

/* -- media kinds ----------------------------------------------------------- */
.practice-media-item.has-table {
  gap: 0;
}

.practice-media-item.has-table .practice-table-wrap {
  max-width: 100%;
}

/* -- player mode flags ----------------------------------------------------- */
/* Review and teacher modes both show the reveal bar, so the main area loses a
   row of height. Tighten the panels rather than letting content overflow. */
.dsat-shell.is-reviewing .dsat-passage-panel,
.dsat-shell.is-reviewing .dsat-q-panel,
.dsat-shell.is-teacher .dsat-passage-panel,
.dsat-shell.is-teacher .dsat-q-panel {
  padding-block: var(--s-4);
}

/* Teacher data is an overlay: it never enters the shell's flex flow or
   resizes the question surface when the answer state changes. */
.dsat-shell > .teacher-panel {
  position: fixed;
  inset: var(--s-4) var(--s-4) var(--s-4) auto;
  width: min(380px, calc(100vw - 32px));
  max-height: calc(100dvh - 32px);
  border: 1px solid var(--line-2);
  border-radius: var(--r-md);
  box-shadow: var(--e-3);
}

.dsat-shell.is-reviewing .dsat-choice:disabled:not(.is-eliminated),
.dsat-shell.is-teacher .dsat-choice:disabled:not(.is-eliminated) {
  opacity: 1;
  cursor: default;
}

@media (max-width: 880px) {
  .dsat-shell > .teacher-panel {
    inset: var(--s-2);
    width: auto;
    max-height: calc(100dvh - 16px);
  }
}

/* An untimed practice run still renders the clock wrapper; mute it so a
   count-up reading is not mistaken for a countdown. */
.dsat-timer:not(.is-limited) .dsat-timer-val {
  color: var(--ink-3);
  font-weight: 550;
}

/* -- header / nav variants ------------------------------------------------- */
.dsat-exit-btn {
  color: var(--ink-3);
}

.dsat-exit-btn:hover {
  background: var(--wrong-wash);
  color: var(--wrong);
}

.dsat-nav-back svg {
  margin-right: calc(var(--s-1) * -1);
}

.dsat-nav-next svg {
  margin-left: calc(var(--s-1) * -1);
}

.admin-edit-question-btn {
  border-color: var(--line-2);
  color: var(--ink-2);
}

/* -- highlighter ----------------------------------------------------------- */
/* Annotation colours: the student's own marks. Held at low saturation so they
   never read as correct/incorrect grading. */
.selection-color.swatch-yellow span { background: #fff3a3; }
.selection-color.swatch-blue span { background: #dbeafe; }
.selection-color.swatch-green span { background: #dcfce7; }
.selection-color.swatch-pink span { background: #fce7f3; }

.practice-highlight.highlight-yellow { background: rgba(255, 243, 163, 0.85); color: #1f1917; }
.practice-highlight.highlight-blue { background: rgba(219, 234, 254, 0.9); color: #1f1917; }
.practice-highlight.highlight-green { background: rgba(220, 252, 231, 0.9); color: #1f1917; }
.practice-highlight.highlight-pink { background: rgba(252, 231, 243, 0.9); color: #1f1917; }

/* -- AI section kinds ------------------------------------------------------
   script.js builds the class as ai-section-${kind}. The base .ai-section rule
   above carries the treatment; these two need layout of their own. */
.ai-section-steps .ai-section-body {
  font-family: var(--font-ui);
}

.ai-section-wrong-choices .ai-section-title {
  color: var(--wrong);
}

/* -- misc state ------------------------------------------------------------ */
.wrong-answer-chart.is-empty {
  color: var(--ink-3);
  font-size: var(--fs-sm);
}

.wrong-answer-chart.is-empty .wrong-answer-columns {
  display: none;
}

/* =============================================================================
   OUT OF SCOPE — .pdf-*
   .pdf-topbar .pdf-two-panel .pdf-passage .pdf-question-panel .pdf-question
   .pdf-question-banner .pdf-prompt .pdf-choice-list .pdf-choice
   .pdf-choice-bubble .pdf-choice-text .pdf-written-answer .pdf-media-list
   .pdf-media-item .pdf-missing-media .pdf-table-wrap .pdf-answer-key

   These belong to the PDF export document, which script.js builds with its own
   inline <style> block (script.js:7025). Rules here would have no effect, so
   none are written. Do not "fix" their absence.
   ============================================================================= */

/* =============================================================================
   19 - BRAND RAIL + FULL-WIDTH PRACTICE COPY

   The old left rail is retired. Its existing DOM remains the navigation source,
   but the shell now presents it as a red brand rail at the top of the viewport.
   Desktop pointer hover and keyboard focus reveal one horizontal menu; touch
   uses #topnavTrigger. Only transform and opacity animate.
   ============================================================================= */

:root {
  --topnav-height: 72px;
  --topnav-panel-height: 68px;

  /* -- brand travel motion -------------------------------------------------
     The rail rests with the lockup centred and parks it in the left corner on
     hover. That is roughly half a viewport of travel, which makes it the one
     gesture in the app that is deliberately over the --t-base ceiling: at
     150ms a 600px slide reads as a cut, not a move. These three values exist
     for this gesture only — nothing else borrows them, so the budget in
     DESIGN_LIMITATIONS.md still holds everywhere else.

     --topnav-trail is longer than --topnav-travel on purpose. The colour
     finishes stretching after the logo has landed, so it reads as a wake the
     logo dragged behind it rather than a panel that appeared with it. */
  --topnav-travel: 980ms;
  --topnav-trail: 1200ms;
  --topnav-glide: cubic-bezier(0.22, 1, 0.36, 1);
  --topnav-settle: cubic-bezier(0.34, 1.18, 0.44, 1);
}

.app-shell {
  display: block;
}

.sidebar.topnav-shell {
  position: fixed;
  inset: 0 0 auto;
  z-index: 20;
  display: block;
  width: 100%;
  height: var(--topnav-height);
  --topnav-pad: var(--s-6);
  padding: 0 var(--topnav-pad);
  overflow: visible;
  background-color: var(--brand-dark);
  background-image: url("/assets/topnav-brand-background.png");
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
  border: 0;
}

/* The colour the lockup drags with it. Collapsed to nothing against the left
   edge at rest; on hover it stretches out along the rail behind the logo, so
   the corner the logo lands in is somewhere the brand colour deepened into
   rather than a position the logo teleported to.

   Only scale and opacity animate, so the stretch costs no layout work. The
   gradient fades to transparent at its right end: a hard edge chasing the
   logo would read as a panel sliding in, a soft one reads as a wake. */
.sidebar.topnav-shell::before {
  content: "";
  position: absolute;
  /* Absolute children resolve against the rail's PADDING box, so left: 0
     would start the slab --topnav-pad in from the viewport and leave a strip
     of flat brand red along the edge. The negative offset puts it flush. */
  inset: 0 auto 0 calc(var(--topnav-pad) * -1);
  z-index: 0;
  width: min(640px, 54vw);
  background: linear-gradient(
    90deg,
    rgba(139, 6, 10, 0.92) 0%,
    rgba(154, 8, 12, 0.7) 38%,
    rgba(172, 14, 18, 0.32) 70%,
    rgba(202, 37, 43, 0) 100%
  );
  transform-origin: left center;
  scale: 0 1;
  opacity: 0;
  pointer-events: none;
  transition: scale var(--topnav-trail) var(--topnav-glide),
    opacity var(--t-base) var(--ease);
}

/* translate and scale are written as their own properties instead of one
   transform shorthand. The shorthand forced the browser to interpolate
   between mismatched function lists (translateX -> translateX + scale) by
   decomposing matrices, which is what made the old move look like a cut.
   Separate properties also let the size settle on its own curve after the
   slide, instead of shrinking the wordmark while it is still travelling. */
.sidebar.topnav-shell .brand {
  position: absolute;
  top: 0;
  left: var(--s-6);
  z-index: 2;
  display: flex;
  align-items: center;
  gap: var(--s-3);
  height: var(--topnav-height);
  translate: calc(50vw - 50% - var(--s-6)) 0;
  scale: 1;
  transform-origin: left center;
  will-change: translate, scale;
  transition: translate var(--topnav-travel) var(--topnav-glide),
    scale 420ms var(--topnav-settle);
}

.sidebar.topnav-shell .brand-mark,
.sidebar.topnav-shell .brand-mark img,
.sidebar.topnav-shell .brand-mark svg {
  width: 42px;
  height: 42px;
}

.sidebar.topnav-shell .brand-mark {
  flex: 0 0 auto;
  border: 0;
  background: transparent;
  box-shadow: none;
}

.sidebar.topnav-shell .brand > div:not(.brand-mark) {
  line-height: 1.08;
  opacity: 1;
  translate: 0 0;
  transition: opacity 600ms var(--ease), translate 740ms var(--topnav-glide);
}

.sidebar.topnav-shell .brand strong {
  max-width: 260px;
  color: var(--brand-knockout);
  font-size: var(--fs-xl);
  font-weight: 750;
}

.sidebar.topnav-shell .brand span {
  margin-top: 3px;
  color: rgba(253, 253, 253, 0.78);
  font-size: var(--fs-xs);
}

.topnav-trigger,
.sidebar.topnav-shell > .theme-toggle {
  position: absolute;
  top: 14px;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border: 1px solid rgba(253, 253, 253, 0.38);
  border-radius: var(--r-sm);
  color: var(--brand-knockout);
  transition: background-color var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}

.topnav-trigger {
  right: var(--s-6);
}

.sidebar.topnav-shell > .theme-toggle {
  right: 76px;
}

.topnav-trigger:hover,
.sidebar.topnav-shell > .theme-toggle:hover {
  background: rgba(253, 253, 253, 0.14);
  color: var(--brand-knockout);
}

.topnav-trigger:active,
.sidebar.topnav-shell > .theme-toggle:active {
  transform: scale(0.96);
}

.sidebar.topnav-shell .account-card,
.sidebar.topnav-shell .sidebar-footer {
  position: absolute;
  top: 12px;
  z-index: 1;
  height: 48px;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateY(-6px);
  transition: opacity var(--t-base) var(--ease), transform var(--t-base) var(--ease),
    visibility 0s linear var(--t-base);
}

.sidebar.topnav-shell .account-card {
  right: 174px;
  grid-template-columns: auto minmax(0, 1fr);
  width: 210px;
  padding: var(--s-2) var(--s-3);
  border-color: rgba(253, 253, 253, 0.34);
  border-radius: var(--r-sm);
  background: rgba(31, 25, 23, 0.12);
}

.sidebar.topnav-shell .account-card strong,
.sidebar.topnav-shell .account-card span {
  color: var(--brand-knockout);
}

.sidebar.topnav-shell .account-card span {
  opacity: 0.74;
}

.sidebar.topnav-shell .avatar {
  width: 30px;
  height: 30px;
  background: var(--brand-knockout);
  color: var(--brand-dark);
}

.sidebar.topnav-shell .sidebar-footer {
  top: 14px;
  right: 124px;
  height: 44px;
  padding: 0;
  border: 0;
}

.sidebar.topnav-shell .sidebar-footer .ghost-button {
  width: 44px;
  height: 44px;
  min-height: 44px;
  padding: 0;
  border: 1px solid rgba(253, 253, 253, 0.38);
  color: var(--brand-knockout);
}

.sidebar.topnav-shell .sidebar-footer .ghost-button:hover {
  background: rgba(253, 253, 253, 0.14);
}

.sidebar.topnav-shell .sidebar-footer .ghost-button span {
  display: none;
}

.sidebar.topnav-shell .nav-list {
  position: absolute;
  top: var(--topnav-height);
  left: 0;
  right: 0;
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: var(--s-1);
  height: var(--topnav-panel-height);
  min-height: 0;
  margin: 0;
  padding: var(--s-3) var(--s-6);
  overflow-x: auto;
  overflow-y: hidden;
  background: var(--paper);
  border-bottom: 1px solid var(--line);
  box-shadow: var(--e-1);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateY(-10px);
  transition: opacity var(--t-base) var(--ease), transform var(--t-base) var(--ease),
    visibility 0s linear var(--t-base);
}

.sidebar.topnav-shell .nav-item {
  flex: 0 0 auto;
  width: auto;
  min-height: var(--hit);
  padding: 0 var(--s-3);
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  /* Items start hidden so they can arrive in sequence behind the panel. The
     three selectors that reveal .nav-list are the only ways this list is ever
     on screen, and all three put these back — see the reveal block below and
     the reduced-motion block at the end of the section. */
  opacity: 0;
  translate: 0 -8px;
  transition: opacity 260ms var(--ease), translate 260ms var(--topnav-glide),
    background-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
  /* --nav-i is the item's place in the row; --nav-on is flipped to 1 by each
     reveal state. Multiplying by --nav-on means the cascade only applies on
     the way in — leaving the rail collapses everything at once, because a
     staggered exit reads as the menu being slow to let go. */
  --nav-i: 0;
  --nav-on: 0;
  transition-delay: calc(var(--nav-on) * (120ms + var(--nav-i) * 34ms)),
    calc(var(--nav-on) * (120ms + var(--nav-i) * 34ms)), 0s, 0s;
}

.sidebar.topnav-shell .nav-item:nth-child(2) { --nav-i: 1; }
.sidebar.topnav-shell .nav-item:nth-child(3) { --nav-i: 2; }
.sidebar.topnav-shell .nav-item:nth-child(4) { --nav-i: 3; }
.sidebar.topnav-shell .nav-item:nth-child(5) { --nav-i: 4; }
.sidebar.topnav-shell .nav-item:nth-child(6) { --nav-i: 5; }
.sidebar.topnav-shell .nav-item:nth-child(7) { --nav-i: 6; }
.sidebar.topnav-shell .nav-item:nth-child(8) { --nav-i: 7; }
.sidebar.topnav-shell .nav-item:nth-child(n + 9) { --nav-i: 8; }

.sidebar.topnav-shell .nav-item.is-active {
  background: var(--wrong-wash);
  color: var(--brand-dark);
}

.sidebar.topnav-shell .nav-item.is-active svg {
  color: var(--brand);
}

.sidebar.topnav-shell .nav-item.is-active::before {
  inset: auto var(--s-3) -1px;
  width: auto;
  height: 2px;
  transform: none;
  background: var(--brand);
}

:root[data-theme="dark"] .sidebar.topnav-shell .nav-item.is-active {
  background: rgba(202, 37, 43, 0.24);
  color: var(--brand-knockout);
}

:root[data-theme="dark"] .sidebar.topnav-shell .nav-item.is-active svg {
  color: var(--brand-knockout);
}

:root[data-theme="dark"] .sidebar.topnav-shell .nav-item.is-active::before {
  background: var(--brand-lite);
}

/* -- reveal ---------------------------------------------------------------
   Three states open the rail: .is-open (the touch trigger), :focus-within
   (keyboard) and :hover (pointer, in the media query below). They share one
   choreography — the lockup leaves first, the colour stretches after it, the
   panel and the right-hand controls follow once the logo is already moving.
   The delays live here, in the open state, so closing reverses immediately
   instead of hesitating. */
.sidebar.topnav-shell.is-open .brand,
.sidebar.topnav-shell:focus-within .brand {
  translate: 0 0;
  scale: 0.92;
  transition-delay: 0s, 90ms;
}

.sidebar.topnav-shell.is-open .brand > div:not(.brand-mark),
.sidebar.topnav-shell:focus-within .brand > div:not(.brand-mark) {
  opacity: 0;
  translate: -10px 0;
  pointer-events: none;
  transition-delay: 50ms, 0ms;
}

.sidebar.topnav-shell.is-open::before,
.sidebar.topnav-shell:focus-within::before {
  scale: 1 1;
  opacity: 1;
}

.sidebar.topnav-shell.is-open .nav-item,
.sidebar.topnav-shell:focus-within .nav-item {
  --nav-on: 1;
  opacity: 1;
  translate: 0 0;
}

.sidebar.topnav-shell.is-open .nav-list,
.sidebar.topnav-shell.is-open .account-card,
.sidebar.topnav-shell.is-open .sidebar-footer,
.sidebar.topnav-shell:focus-within .nav-list,
.sidebar.topnav-shell:focus-within .account-card,
.sidebar.topnav-shell:focus-within .sidebar-footer {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: translateY(0);
  transition-delay: 70ms;
}

/* Desktop uses the red rail itself as the menu surface. When the lockup moves
   left, its copy clears and the navigation occupies that newly available
   line. The account card stays a mobile-drawer detail instead of becoming a
   second boxed object in the rail. */
@media (min-width: 881px) {
  .sidebar.topnav-shell .brand {
    pointer-events: none;
  }

  .sidebar.topnav-shell .account-card {
    display: none;
  }

  .sidebar.topnav-shell .nav-list {
    top: 0;
    left: 82px;
    right: 176px;
    z-index: 1;
    justify-content: center;
    gap: 2px;
    height: var(--topnav-height);
    margin: 0;
    padding: 0;
    overflow-x: auto;
    overflow-y: hidden;
    background: transparent;
    border: 0;
    box-shadow: none;
    scrollbar-width: none;
    transform: translateY(-6px);
  }

  .sidebar.topnav-shell .nav-list::-webkit-scrollbar {
    display: none;
  }

  .sidebar.topnav-shell .nav-item {
    min-height: 40px;
    gap: var(--s-2);
    padding: 0 var(--s-2);
    color: rgba(253, 253, 253, 0.86);
    font-size: var(--fs-xs);
  }

  .sidebar.topnav-shell .nav-item svg {
    width: 17px;
    height: 17px;
    color: currentColor;
  }

  .sidebar.topnav-shell .nav-item:hover,
  .sidebar.topnav-shell .nav-item.is-active,
  :root[data-theme="dark"] .sidebar.topnav-shell .nav-item.is-active {
    background: rgba(253, 253, 253, 0.14);
    color: var(--brand-knockout);
  }

  .sidebar.topnav-shell .nav-item:hover svg,
  .sidebar.topnav-shell .nav-item.is-active svg,
  :root[data-theme="dark"] .sidebar.topnav-shell .nav-item.is-active svg {
    color: var(--brand-knockout);
  }

  .sidebar.topnav-shell .nav-item.is-active::before,
  :root[data-theme="dark"] .sidebar.topnav-shell .nav-item.is-active::before {
    inset: auto var(--s-2) 4px;
    background: rgba(253, 253, 253, 0.9);
  }
}

@media (min-width: 881px) and (max-width: 1120px) {
  .sidebar.topnav-shell .nav-list {
    right: 170px;
    justify-content: space-evenly;
  }

  .sidebar.topnav-shell .nav-item {
    width: 42px;
    padding: 0;
    justify-content: center;
  }

  .sidebar.topnav-shell .nav-item span {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
}

@media (hover: hover) and (min-width: 881px) {
  .sidebar.topnav-shell .topnav-trigger,
  .sidebar.topnav-shell > .theme-toggle {
    opacity: 0;
    pointer-events: none;
    transform: translateY(-6px);
    transition: opacity var(--t-base) var(--ease), transform var(--t-base) var(--ease),
      background-color var(--t-fast) var(--ease);
  }

  .sidebar.topnav-shell:hover .topnav-trigger,
  .sidebar.topnav-shell:hover > .theme-toggle,
  .sidebar.topnav-shell.is-open .topnav-trigger,
  .sidebar.topnav-shell.is-open > .theme-toggle,
  .sidebar.topnav-shell:focus-within .topnav-trigger,
  .sidebar.topnav-shell:focus-within > .theme-toggle {
    opacity: 1;
    pointer-events: auto;
    transform: translateY(0);
  }

  /* Pointer devices already open the rail on hover, so the menu trigger is
     redundant there. It remains available on touch and mobile layouts. */
  .sidebar.topnav-shell .topnav-trigger,
  .sidebar.topnav-shell:hover .topnav-trigger,
  .sidebar.topnav-shell.is-open .topnav-trigger,
  .sidebar.topnav-shell:focus-within .topnav-trigger {
    display: none;
  }

  .sidebar.topnav-shell > .theme-toggle {
    right: var(--s-6);
  }

  .sidebar.topnav-shell .sidebar-footer {
    right: 76px;
  }

  .sidebar.topnav-shell .nav-list {
    right: 132px;
  }

  .sidebar.topnav-shell:hover .brand {
    translate: 0 0;
    scale: 0.92;
    transition-delay: 0s, 90ms;
  }

  .sidebar.topnav-shell:hover .brand > div:not(.brand-mark) {
    opacity: 0;
    translate: -10px 0;
    pointer-events: none;
    transition-delay: 50ms, 0ms;
  }

  .sidebar.topnav-shell:hover::before {
    scale: 1 1;
    opacity: 1;
  }

  .sidebar.topnav-shell:hover .nav-item {
    --nav-on: 1;
    opacity: 1;
    translate: 0 0;
  }

  .sidebar.topnav-shell:hover .nav-list,
  .sidebar.topnav-shell:hover .account-card,
  .sidebar.topnav-shell:hover .sidebar-footer {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: translateY(0);
    transition-delay: 70ms;
  }
}

.main-shell {
  min-height: 100dvh;
  padding-top: var(--topnav-height);
}

.topbar {
  top: var(--topnav-height);
}

/* Practice copy follows the panel instead of stopping at the old 68ch cap.
   Padding still controls readable breathing room and the user-owned split
   continues to control the relationship between passage and question. */
.dsat-passage-panel .passage-text,
.passage-text,
.dsat-prompt,
.dsat-choice-list,
.dsat-math-q .dsat-choice-list {
  width: 100%;
  max-width: none;
}

.dsat-choice-text,
.dsat-choice-text > * {
  max-width: none;
}

@media (max-width: 880px) {
  :root {
    --topnav-height: 64px;
  }

  /* Half a phone width is a much shorter trip than half a desktop, so the
     desktop duration would read as sluggish here. */
  .sidebar.topnav-shell {
    position: fixed;
    height: var(--topnav-height);
    --topnav-pad: var(--s-3);
    --topnav-travel: 480ms;
    --topnav-trail: 600ms;
    padding: 0 var(--topnav-pad);
    border: 0;
  }

  .sidebar.topnav-shell .brand {
    left: var(--s-3);
    height: var(--topnav-height);
    /* Must stay on translate/scale, not transform: the two are separate
       properties and a transform here would compose on top of the desktop
       translate rather than replace it, doubling the offset. */
    translate: calc(50vw - 50% - var(--s-3)) 0;
    scale: 0.9;
  }

  .sidebar.topnav-shell .brand-mark,
  .sidebar.topnav-shell .brand-mark img,
  .sidebar.topnav-shell .brand-mark svg {
    width: 36px;
    height: 36px;
  }

  .sidebar.topnav-shell .brand strong {
    max-width: 190px;
    font-size: var(--fs-base);
  }

  .sidebar.topnav-shell .brand span {
    display: none;
  }

  .sidebar.topnav-shell.is-open .brand,
  .sidebar.topnav-shell:focus-within .brand {
    translate: 0 0;
    scale: 0.82;
  }

  .sidebar.topnav-shell::before {
    width: min(320px, 76vw);
  }

  .topnav-trigger,
  .sidebar.topnav-shell > .theme-toggle {
    top: 12px;
    width: var(--hit);
    height: var(--hit);
  }

  .topnav-trigger {
    right: var(--s-3);
    opacity: 1;
    pointer-events: auto;
  }

  .sidebar.topnav-shell > .theme-toggle {
    right: 60px;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transform: translateY(-6px);
  }

  .sidebar.topnav-shell.is-open > .theme-toggle,
  .sidebar.topnav-shell:focus-within > .theme-toggle {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: translateY(0);
  }

  .sidebar.topnav-shell .account-card {
    position: fixed;
    top: 76px;
    left: var(--s-3);
    right: var(--s-3);
    display: grid;
    width: auto;
    height: 52px;
    background: var(--paper-2);
    border-color: var(--line);
  }

  .sidebar.topnav-shell .account-card strong {
    color: var(--ink);
  }

  .sidebar.topnav-shell .account-card span {
    color: var(--ink-3);
    opacity: 1;
  }

  .sidebar.topnav-shell .avatar {
    background: var(--ink);
    color: var(--paper);
  }

  .sidebar.topnav-shell .nav-list {
    position: fixed;
    top: 140px;
    bottom: 68px;
    left: var(--s-3);
    right: var(--s-3);
    display: flex;
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    gap: var(--s-1);
    width: auto;
    height: auto;
    padding: var(--s-2);
    overflow-x: hidden;
    overflow-y: auto;
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    box-shadow: var(--e-2);
  }

  .sidebar.topnav-shell .nav-item {
    flex: 0 0 auto;
    width: 100%;
    justify-content: flex-start;
    padding-inline: var(--s-3);
    font-size: var(--fs-md);
  }

  .sidebar.topnav-shell .nav-item.is-active::before {
    inset: var(--s-2) auto var(--s-2) 0;
    width: 3px;
    height: auto;
  }

  .sidebar.topnav-shell .sidebar-footer {
    position: fixed;
    top: auto;
    right: var(--s-3);
    bottom: var(--s-3);
    left: var(--s-3);
    height: 44px;
  }

  .sidebar.topnav-shell .sidebar-footer .ghost-button {
    width: 100%;
    height: 44px;
    border-color: var(--line);
    background: var(--paper);
    color: var(--ink);
  }

  .sidebar.topnav-shell .sidebar-footer .ghost-button span {
    display: inline;
  }

  .main-shell {
    padding-top: var(--topnav-height);
  }

  .topbar {
    top: var(--topnav-height);
  }
}

/* -- the rail opts OUT of the reduced-motion reset ---------------------------
   The global reset at the top of this file collapses every transition in the
   app to 0.01ms with `*` + !important when the visitor's OS asks for reduced
   motion. That is the right default and it still governs everywhere else —
   but the brand rail is deliberately exempt: this movement is the identity of
   the top bar, and it was decided that every visitor sees it.

   Overriding an !important rule needs !important back, and only
   transition-duration is contested, so only that is re-declared. The value
   lists below must stay in the same order as each element's transition-property
   list above; if a transition is edited up there, fix its twin here.

   The durations resolve through var(), so the shorter mobile timings still
   apply — the phone branch overrides the tokens on the shell itself. */
@media (prefers-reduced-motion: reduce) {
  /* scale, opacity */
  .sidebar.topnav-shell::before {
    transition-duration: var(--topnav-trail), var(--t-base) !important;
  }

  /* translate, scale */
  .sidebar.topnav-shell .brand {
    transition-duration: var(--topnav-travel), 420ms !important;
  }

  /* opacity, translate */
  .sidebar.topnav-shell .brand > div:not(.brand-mark) {
    transition-duration: 600ms, 740ms !important;
  }

  /* opacity, translate, background-color, color */
  .sidebar.topnav-shell .nav-item {
    transition-duration: 260ms, 260ms, var(--t-fast), var(--t-fast) !important;
  }

  /* opacity, transform, visibility */
  .sidebar.topnav-shell .nav-list,
  .sidebar.topnav-shell .account-card,
  .sidebar.topnav-shell .sidebar-footer {
    transition-duration: var(--t-base), var(--t-base), 0s !important;
  }
}

@media print {
  .main-shell {
    padding-top: 0;
  }

  .topbar {
    top: 0;
  }
}

/* =============================================================================
   AI TUTOR V4 — docked tutor workspace
   The portal and practice player are siblings. On a wide desktop they reserve
   separate columns; on narrower screens the tutor becomes an explicit focus
   view. At no breakpoint does a translucent drawer sit over answer choices.
   ============================================================================= */

.ai-explain-drawer {
  display: grid;
  grid-template-rows: auto auto minmax(0, 1fr) auto;
  gap: 0;
  width: var(--ai-drawer-width, 440px);
  max-width: 100vw;
  overflow: hidden;
  border-left: 1px solid var(--line-2);
  background: var(--paper);
  box-shadow: -10px 0 30px color-mix(in srgb, var(--ink) 8%, transparent);
}

.ai-tutor-head {
  position: relative;
  top: auto;
  z-index: 3;
  display: flex;
  min-height: 68px;
  justify-content: space-between;
  padding: var(--s-3) var(--s-4);
  border-bottom: 1px solid var(--line);
  background: var(--paper);
}

.ai-tutor-head > div:first-child {
  min-width: 0;
}

.ai-tutor-head strong {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  color: var(--ink);
}

.ai-head-actions {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  gap: var(--s-1);
}

.ai-drawer-close {
  position: static;
  width: 40px;
  height: 40px;
  min-width: 40px;
  padding: 0;
  border-color: var(--line-2);
}

.ai-quick-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-2);
  padding: var(--s-3) var(--s-4);
  overflow: visible;
  border-bottom: 1px solid var(--line);
  background: var(--paper-2);
}

.ai-quick-grid .secondary-button {
  min-width: 0;
  min-height: 36px;
  height: auto;
  justify-content: flex-start;
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-sm);
  white-space: normal;
  text-align: left;
  line-height: var(--lh-snug);
}

.ai-drawer-scroll,
.ai-tutor-conversation-scroll {
  display: block;
  min-height: 0;
  padding: var(--s-4);
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-gutter: stable;
}

.ai-conversation {
  gap: var(--s-5);
}

.ai-output {
  gap: var(--s-3);
  padding: 0;
  border: 0;
  background: transparent;
}

.ai-output-head {
  padding-bottom: var(--s-2);
  border-bottom: 1px solid var(--line);
}

.ai-section {
  padding-top: var(--s-3);
}

.ai-section-title {
  color: var(--ink-2);
}

.ai-section-evidence .ai-section-body {
  padding: var(--s-3);
  border-left: 3px solid var(--ink-3);
  border-radius: 0 var(--r-xs) var(--r-xs) 0;
  background: var(--paper-2);
}

.ai-logic-flow {
  max-width: 100%;
  padding: 0;
  overflow: visible;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: var(--fs-md);
  font-weight: 500;
  line-height: 1.65;
  overflow-wrap: anywhere;
  white-space: normal;
}

.ai-final-line {
  padding: var(--s-3);
  border-left: 3px solid var(--ok);
  border-radius: 0 var(--r-xs) var(--r-xs) 0;
  background: var(--ok-wash);
}

.ai-choice-analysis-list {
  display: grid;
  gap: var(--s-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.ai-choice-analysis-list > li {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr);
  gap: var(--s-2);
  padding: var(--s-2);
  border: 1px solid var(--line);
  border-left-width: 3px;
  border-radius: var(--r-xs);
  background: var(--paper);
}

.ai-choice-analysis-list > li.is-correct {
  border-left-color: var(--ok);
}

.ai-choice-analysis-list > li.is-wrong {
  border-left-color: var(--wrong);
}

.ai-choice-analysis-list > li > strong {
  display: grid;
  place-items: center;
  align-self: start;
  width: 30px;
  height: 30px;
  border-radius: var(--r-xs);
  background: var(--paper-3);
  font-family: var(--font-ui);
}

.ai-choice-analysis-list > li > div {
  display: grid;
  gap: 2px;
  min-width: 0;
}

.ai-choice-analysis-list b {
  color: var(--ink-3);
  font-family: var(--font-ui);
  font-size: var(--fs-2xs);
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
}

.ai-composer {
  position: relative;
  z-index: 3;
  display: grid;
  gap: var(--s-2);
  padding: var(--s-3) var(--s-4);
  border-top: 1px solid var(--line-2);
  background: var(--paper);
  box-shadow: 0 -8px 20px color-mix(in srgb, var(--ink) 5%, transparent);
}

.ai-composer .ai-custom-row {
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr) auto;
  gap: var(--s-2);
  padding: 0;
  border: 0;
}

.ai-composer textarea {
  width: 100%;
  min-width: 0;
  min-height: 42px;
  max-height: 120px;
  resize: vertical;
  padding: 10px var(--s-3);
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  background: var(--paper-2);
  color: var(--ink);
  font: inherit;
  line-height: var(--lh-base);
}

.ai-composer textarea:focus {
  outline: 2px solid var(--focus);
  outline-offset: 1px;
}

.ai-attach-button,
.ai-send-button {
  min-height: 42px;
}

.ai-attach-button {
  width: 40px;
  padding: 0;
}

.ai-composer > small {
  color: var(--ink-4);
  font-size: var(--fs-2xs);
}

.ai-explain-drawer,
.ai-explain-drawer .ai-section-body,
.ai-explain-drawer .ai-step-copy,
.ai-explain-drawer .ai-hint-passage,
.ai-explain-drawer .ai-hint-choice {
  font-family: var(--font-ui);
  font-synthesis: none;
  text-rendering: optimizeLegibility;
}

.ai-explain-drawer .ai-section-body,
.ai-explain-drawer .ai-step-copy {
  font-size: var(--fs-md);
  line-height: 1.65;
}

.ai-tutor-mark {
  display: inline-grid;
  flex: 0 0 auto;
  place-items: center;
  width: 22px;
  height: 22px;
  color: currentColor;
}

.ai-tutor-mark svg {
  width: 100%;
  height: 100%;
  overflow: visible;
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 1.65;
}

.ai-tutor-mark circle {
  fill: currentColor;
  stroke: none;
}

.dsat-ai-tutor-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.dsat-ai-tutor-btn .ai-tutor-mark {
  width: 17px;
  height: 17px;
}

.ai-loading {
  min-height: 48px;
  padding: var(--s-3) 0;
  color: var(--ink-2);
  font-family: var(--font-ui);
  font-size: var(--fs-sm);
  font-weight: 550;
}

.ai-loading-spinner {
  display: inline-block;
  flex: 0 0 auto;
  width: 23px;
  height: 23px;
  border: 2px solid var(--line-2);
  border-top-color: var(--ink);
  border-right-color: var(--ink-3);
  border-radius: 50%;
  animation: ai-tutor-spin 720ms linear infinite;
}

@keyframes ai-tutor-spin {
  to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  /* The loading indicator communicates that the request is still alive.
     Keep one slow, low-amplitude motion even when decorative motion is off. */
  .ai-loading-spinner {
    animation-duration: 1.4s !important;
    animation-iteration-count: infinite !important;
  }
}

.ai-section-vocabulary .ai-section-body {
  color: var(--ink);
}

.ai-vocabulary-list {
  display: grid;
  gap: 0;
  margin: 0;
  padding: 0;
  list-style: none;
}

.ai-vocabulary-list > li {
  display: grid;
  grid-template-columns: minmax(110px, 0.38fr) minmax(0, 1fr);
  gap: var(--s-3);
  padding: var(--s-2) 0;
  border-top: 1px solid var(--line);
}

.ai-vocabulary-list > li:first-child {
  padding-top: 0;
  border-top: 0;
}

.ai-vocabulary-list > li > strong {
  color: var(--ink);
  font-weight: 650;
  overflow-wrap: anywhere;
}

.ai-vocabulary-list > li > span {
  min-width: 0;
  color: var(--ink-2);
  overflow-wrap: anywhere;
}

@media (max-width: 560px) {
  .ai-vocabulary-list > li {
    grid-template-columns: minmax(0, 1fr);
    gap: 2px;
  }
}

.ai-output-layout-logic-flow .ai-section-logic,
.ai-output-layout-find-evidence .ai-section-evidence,
.ai-output-layout-choice-analysis .ai-section-choice-analysis,
.ai-output-layout-foundation .ai-section-steps,
.ai-output-layout-hint-only .ai-section-steps {
  padding-top: var(--s-4);
}

.ai-attachment-strip {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--s-2);
}

.ai-attachment-thumb {
  position: relative;
  min-width: 0;
  margin: 0;
}

.ai-attachment-thumb > button:first-child {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
  background: var(--paper-3);
}

.ai-attachment-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.ai-attachment-thumb figcaption {
  margin-top: 3px;
  overflow: hidden;
  color: var(--ink-3);
  font-size: var(--fs-2xs);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ai-attachment-remove {
  position: absolute;
  top: -6px;
  right: -6px;
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border: 1px solid var(--line-2);
  border-radius: var(--r-full);
  background: var(--paper);
  color: var(--ink);
  box-shadow: var(--e-1);
}

.ai-attachment-remove svg {
  width: 14px;
  height: 14px;
}

.ai-attachment-preview-modal {
  width: min(920px, calc(100vw - 32px));
  max-height: calc(100dvh - 32px);
  overflow: auto;
}

.ai-attachment-preview-modal img {
  display: block;
  max-width: 100%;
  max-height: calc(100dvh - 150px);
  margin-inline: auto;
  object-fit: contain;
}

@media (min-width: 1200px) {
  body.is-ai-tutor-open .practice-player {
    right: var(--ai-dock-width, 440px);
  }

  .ai-explain-drawer {
    width: var(--ai-drawer-width, 440px);
    min-width: 400px;
    max-width: 480px;
  }
}

@media (max-width: 1199px) {
  body.is-ai-tutor-open .practice-player {
    visibility: hidden;
  }

  .ai-explain-drawer {
    inset: 0;
    width: 100vw;
    height: 100dvh;
    border-left: 0;
    box-shadow: none;
  }

  .ai-drawer-resize-handle {
    display: none;
  }

  .ai-tutor-head,
  .ai-quick-grid,
  .ai-composer {
    padding-inline: clamp(var(--s-3), 4vw, var(--s-6));
  }

  .ai-tutor-conversation-scroll {
    padding-inline: clamp(var(--s-3), 4vw, var(--s-6));
  }
}

@media (max-width: 560px) {
  .ai-head-actions .secondary-button span,
  .ai-head-actions .status-pill,
  .ai-send-button span {
    display: none;
  }

  .ai-quick-grid {
    grid-template-columns: minmax(0, 1fr);
    max-height: 154px;
    overflow-y: auto;
  }

  .ai-composer .ai-custom-row {
    grid-template-columns: 40px minmax(0, 1fr) 42px;
  }

  .ai-send-button {
    width: 42px;
    padding: 0;
  }
}
