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

:root {
  /* ══════════════════════════════════════════════════════════════════
         DESIGN TOKENS - single source of truth for the whole UI.
         ▶ RE-THEME HERE: edit a SEED below and the solid colour, its
           translucent "-pale", and border variants all update together
           (they're derived via rgb()/color-mix - no need to touch alphas).
         ▶ Dark theme lives here; the light theme only re-overrides the
           SEEDS in [data-theme="light"] further down - nothing else.
         ▶ Reserved meanings (see CLAUDE.md → "Colour semantics"):
             --accent  = BRAND / chrome only (buttons, nav, links, logo)
             --jade    = correct / good        --amber  = caution / mid
             --crimson = wrong / at-risk        (never use --accent for status)
             --sky = Physics    --violet = Accountancy   (categorical)
             --ochre = chart series "Input"   --orchid = chart series "Output"
             (generic 2-series categorical pair for stacked/grouped charts -
             deliberately NOT --sky/--violet, which mean Physics/Accountancy
             wherever else they appear. Validated CVD-safe as a pair via
             dataviz skill's validate_palette.js, not eyeballed.)
             --rose = calendar public/school holiday   --iris = calendar
             non-holiday school event (calColor(), views/calendar/) - same
             "own categorical pair, not a reserved status/subject hue"
             rationale as ochre/orchid; validated the same way.
             --engage = DAU/WAU/MAU engagement ramp: ONE hue at 3 opacities
             (sequential, since MAU ⊇ WAU ⊇ DAU is a strict nesting, not
             independent categories) - deliberately muted/neutral rather than
             a vivid subject/status hue, since it's an analytics magnitude,
             not an identity.
         ══════════════════════════════════════════════════════════════════ */

  /* ── colour seeds - "R G B" triplets (edit to re-theme) ── */
  --accent-rgb: 52 211 153; /* brand emerald */
  --good-rgb: 46 198 92; /* correct / good (green) */
  --caution-rgb: 230 169 62; /* caution / mid (amber)  */
  --bad-rgb: 255 133 146; /* wrong / at-risk (red) - lightened for WCAG AA on --surface-3, then again (WCAG audit) so --text-bad/.c-lo still clear 4.5:1 on --card-bg, the background they most often actually render against */
  --phy-rgb: 79 163 245; /* Physics  (azure)  */
  --acc-rgb: 155 140 250; /* Accountancy (violet) */
  --line-rgb: 200 215 235; /* hairlines / borders */
  --ochre-rgb: 176 142 28; /* chart series "Input" (gold) */
  --orchid-rgb: 182 88 182; /* chart series "Output" (plum) */
  --engage-rgb: 96 116 168; /* chart series DAU/WAU/MAU ramp (muted slate-blue) */
  --rose-rgb: 224 88 139; /* calendar: public/school holiday - categorical, NOT --bad (validated via dataviz skill's validate_palette.js against surface + --bad/--acc) */
  --iris-rgb: 147 112 219; /* calendar: non-holiday school event - categorical, NOT --acc (Accountancy is reserved for subject identity) */

  /* ── surfaces & text (solid) ── */
  --bg: #0d1421;
  --surface: #121d2b;
  --surface-2: #182433;
  --surface-3: #223044;
  --text: #edeff9;
  --text-2: #aeb4ce;
  --text-3: #a2a9cc; /* lightened past the old razor-thin 4.59:1 (was #8c93af) to a solid 5.5:1+ on --surface-3, then again (WCAG audit) to clear 4.5:1 on --card-bg too - the original tuning only checked --surface-3, but --card-bg is the more common surface muted text actually sits on */
  --on-accent: #0b0e1a; /* text/icon on a filled accent surface */

  /* ── type & shape ── */
  --font-d: "Cormorant Garamond", Georgia, serif;
  --font-b: "DM Sans", system-ui, sans-serif;
  --font-m: "DM Mono", "Courier New", monospace;
  /* F11 (UX audit), completed: html{zoom:.9375} used to shrink every
     font-size (and, as a side effect, every other px dimension) by the
     same 0.9375 factor, which compounds with a reader's own browser
     zoom/OS text-size setting instead of resetting against it - the
     actual accessibility bug. zoom is gone (below); every one of the 770
     font-size declarations that existed under it (144 in this file, 626
     inline across views/) has been consolidated onto these 10 steps and
     now reads one directly, so text renders at its literal declared size
     with no hidden multiplier. Steps are ordinary round px (not the old
     values' pre-zoom equivalents) - each one previously rendered ~6.25%
     smaller than its number said, so this is a small, deliberate, uniform
     size-up across all text, not a preserved-pixel migration. A handful
     of one-off display/hero sizes (a big dashboard stat number, etc) were
     left as their own literal declarations rather than forced onto this
     scale - each is used once, so tokenizing it wouldn't dedupe anything,
     and it's more likely a deliberate choice than a stray magic number.
     Non-text px dimensions (padding/margin/gap/border/width/height) were
     deliberately left out of this pass - the same size-up applies to them
     too now that zoom is gone, uniformly and without code changes, and
     they don't carry font-size's own accessibility argument. */
  --font-size-3xs: 11px;
  --font-size-2xs: 11.5px;
  --font-size-xs: 12px;
  --font-size-sm: 13px;
  --font-size-base: 14px;
  --font-size-md: 15px;
  --font-size-lg: 16px;
  --font-size-xl: 18px;
  --font-size-2xl: 22px;
  --font-size-3xl: 26px;
  /* Spacing scale (padding/margin/gap), named by px so a value maps back at a glance.
     Only exact on-scale values in this file were moved onto it (zero visual change);
     the off-grid odd values (3/5/7/9/11/13/15px...) and inline TSX spacing are still
     literal - snap those onto this scale when touching that code, not in bulk. */
  --space-2: 2px;
  --space-4: 4px;
  --space-6: 6px;
  --space-8: 8px;
  --space-10: 10px;
  --space-12: 12px;
  --space-14: 14px;
  --space-16: 16px;
  --space-18: 18px;
  --space-20: 20px;
  --space-24: 24px;
  --space-28: 28px;
  --space-32: 32px;
  /* Breakpoints - CSS vars can't be used in @media conditions, so this is the
     canonical list instead; keep new media queries on it. 900/901 is deliberate
     (disjoint max-/min-width ranges), not an off-by-one. JS mirrors: lib/breakpoints.ts.
       1100px  AI Tutor collapses to a single column (aside hidden)
        900px  sidebar becomes an off-canvas drawer (min-width: 901px = desktop)
        640px  AI Tutor stacks with a student-resizable chat height
        600px  page/stat-grid compaction for phones
        400px  smallest phones: topbar label/gap trimming */
  --radius-sm: 8px;
  --radius: 14px;
  --radius-lg: 20px;
  --radius-pill: 999px;
  --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.12);
  --shadow: 0 6px 20px rgba(0, 0, 0, 0.18);
  --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.32);
  --ease-out: cubic-bezier(
    0.16,
    1,
    0.3,
    1
  ); /* strong ease-out — matches react/src/lib/motion.ts's EASE_OUT */
  /* Card fill: neutral and solid (non-alpha), one step up from --surface so a
     card reads as raised. Solid because cards nest (e.g. MyClassCard inside
     MyClassesPanel's own .card) and a translucent fill would double up. Brand
     accent stays chrome-only; a tinted card is reserved for deliberate focus. */
  --card-bg: #182433;

  /* ── DERIVED - do not edit (computed from the seeds above) ── */
  --accent: rgb(var(--accent-rgb));
  --accent-lt: color-mix(in srgb, rgb(var(--accent-rgb)), #fff 26%);
  /* -pale tokens: solid (color-mix against --surface), not an alpha blend -
     same "cards nest, a translucent fill goes muddy/inconsistent" reasoning
     as --card-bg above, extended here after the WCAG audit found the actual
     failure mode: an alpha blend's rendered contrast depends on whatever's
     underneath it, so a badge tuned to pass against --surface silently drops
     below 4.5:1 the moment it's nested inside a --card-bg card - which is
     most of the time, since these badges live in cards. Solid removes the
     dependency entirely; percentages re-solved per theme so each still
     reads as a light tint of its hue, just no longer context-dependent. */
  --accent-pale: color-mix(in srgb, var(--surface), rgb(var(--accent-rgb)) 15%);
  --border-accent: rgb(var(--accent-rgb) / 0.46);
  --jade: rgb(var(--good-rgb));
  --jade-pale: color-mix(in srgb, var(--surface), rgb(var(--good-rgb)) 14%);
  --amber: rgb(var(--caution-rgb));
  --amber-pale: color-mix(in srgb, var(--surface), rgb(var(--caution-rgb)) 14%);
  --crimson: rgb(var(--bad-rgb));
  --crimson-pale: color-mix(in srgb, var(--surface), rgb(var(--bad-rgb)) 13%);
  --sky: rgb(var(--phy-rgb));
  --sky-pale: color-mix(in srgb, var(--surface), rgb(var(--phy-rgb)) 12%);
  --violet: rgb(var(--acc-rgb));
  --violet-pale: color-mix(in srgb, var(--surface), rgb(var(--acc-rgb)) 14%);
  --ochre: rgb(var(--ochre-rgb));
  --ochre-pale: rgb(var(--ochre-rgb) / 0.14);
  --orchid: rgb(var(--orchid-rgb));
  --orchid-pale: rgb(var(--orchid-rgb) / 0.14);
  --rose: rgb(var(--rose-rgb));
  --rose-pale: rgb(var(--rose-rgb) / 0.14);
  --iris: rgb(var(--iris-rgb));
  --iris-pale: rgb(var(--iris-rgb) / 0.14);
  --engage-dau: rgb(var(--engage-rgb) / 0.5);
  --engage-wau: rgb(var(--engage-rgb) / 0.75);
  --engage-mau: rgb(var(--engage-rgb));
  /* Categorical identity for an arbitrary N-way series set (e.g. one line per
     class/section in a multi-class trend chart) - NOT --sky/--violet (those
     mean Physics/Accountancy specifically, wherever else they appear) and NOT
     --ochre/--orchid (that pair is reserved for chart series "Input"/"Output").
     Fixed hex per mode (not seed-derived like the tokens above) straight from
     the dataviz skill's validated 8-hue default palette, minus its blue/violet
     slots (too close to --sky/--violet here) - the remaining 6 in this order
     pass the categorical CVD/contrast checks against Atrium's own surfaces in
     both modes (validate_palette.js; light has 2 WARNs - CVD 7.2 on the
     class-5/6 pair, contrast on class-2/3/4 - both legal given this is always
     rendered with a legend + hover tooltips, i.e. never color-alone).
     Assign by fixed order (class-1, class-2, ...), never cycled/reused past 6 -
     a 7th class folds into "Other" or a paginated/faceted view instead. */
  --class-1: #d95926; /* orange */
  --class-2: #199e70; /* aqua */
  --class-3: #c98500; /* yellow */
  --class-4: #d55181; /* magenta */
  --class-5: #008300; /* green */
  --class-6: #e66767; /* red */
  --border: rgb(var(--line-rgb) / 0.1);
  --border-h: rgb(var(--line-rgb) / 0.18);
  /* Outline for card-like containers (.card/.stat/.q-card) - a touch stronger
     than the generic --border hairline (table rows, inputs, dividers) so cards
     read as separate surfaces. Neutral, not brand-tinted: accent is kept for
     active/focus states (--border-accent). */
  --border-card: rgb(var(--line-rgb) / 0.12);
  --border-card-h: rgb(var(--line-rgb) / 0.22);
}

html,
body {
  height: 100%;
  font-family: var(--font-b);
  background: var(--bg);
  color: var(--text);
  -webkit-font-smoothing: antialiased;
  transition:
    background 0.3s,
    color 0.3s;
}

/* Global keyboard-focus ring. Past .f-input/.inp's own considered
   border+glow treatment below (kept, and excluded here so the two don't
   visually stack), the ONLY focus styling in this stylesheet used to be the
   plain browser default - inconsistent across browsers, and dropped into a
   UI this deliberately art-directed it read as something the design forgot
   rather than a state it chose. :focus-visible (not :focus) so this only
   engages for keyboard/AT navigation, never a mouse click - buttons, nav
   items, and cards shouldn't ring on click, only on Tab. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.f-input:focus-visible,
.inp:focus-visible {
  outline: none;
}

/* ─────────────────────────────────────────
       LIGHT THEME
    ───────────────────────────────────────── */
[data-theme="light"] {
  /* light theme = re-override the SEEDS only; everything derives from :root */
  --accent-rgb: 1 117 84; /* brand emerald (deepened for WCAG AA on its own pale badge bg, then again - WCAG audit - to also clear 4.5:1 as plain text directly on --card-bg, e.g. .ai-chip/.td-mono) */
  --good-rgb: 18 117 58; /* deepened for WCAG AA on its own pale badge bg, then again (WCAG audit) for plain text on --card-bg */
  --caution-rgb: 135 95 9; /* deepened for WCAG AA on its own pale badge bg, then again (WCAG audit) for plain text on --card-bg */
  --bad-rgb: 186 50 74; /* deepened for WCAG AA on its own pale badge bg, then again (WCAG audit) for plain text on --card-bg, e.g. .text-bad/.c-lo */
  --phy-rgb: 46 94 216; /* deepened for WCAG AA on white bg */
  --acc-rgb: 102 71 192; /* 5.3:1+ on every light surface incl. its own pale badge bg */
  --line-rgb: 28 32 72;
  --ochre-rgb: 138 109 0; /* deepened for WCAG AA on white bg */
  --orchid-rgb: 138 46 138; /* deepened for WCAG AA on white bg */
  --engage-rgb: 60 75 130; /* deepened for WCAG AA on white bg */
  --rose-rgb: 184 51 106; /* deepened for WCAG AA on white bg */
  --iris-rgb: 111 75 201; /* deepened for WCAG AA on white bg */
  /* Warm neutral canvas, crisp white content, emerald for actions and focus. */
  --bg: #f7f7f4;
  --surface: #ffffff;
  --surface-2: #f1f2ef;
  --surface-3: #e9ebe7;
  --text: #1a1b2e;
  --text-2: #3d4060; /* 8.2:1+ on every light surface */
  --text-3: #454967; /* 7.1:1+ on every light surface; muted text is often small, so kept well above the 4.5:1 floor */
  --on-accent: #faf6ee; /* light text on the deep-emerald accent */
  /* A quieter elevation scale lets spacing and content carry the hierarchy. */
  --shadow-sm: 0 2px 8px rgba(20, 32, 40, 0.05);
  --shadow: 0 6px 20px rgba(20, 32, 40, 0.07);
  --shadow-lg: 0 12px 32px rgba(20, 22, 55, 0.24);
  --border-card: rgb(var(--line-rgb) / 0.14);
  --border-card-h: rgb(var(--line-rgb) / 0.26);
  --card-bg: #ffffff;
  /* Light-mode steps of the categorical class-identity set above - not
     seed-derived, so re-declared explicitly per mode like --bg/--surface. */
  --class-1: #eb6834;
  --class-2: #1baf7a;
  --class-3: #eda100;
  --class-4: #e87ba4;
  --class-5: #008300;
  --class-6: #e34948;
}
[data-theme="light"] .bg-grad {
  background:
    radial-gradient(
      ellipse 90% 70% at 72% 18%,
      rgb(var(--accent-rgb) / 0.08) 0%,
      transparent 60%
    ),
    radial-gradient(
      ellipse 60% 80% at 18% 85%,
      rgba(48, 96, 216, 0.05) 0%,
      transparent 60%
    ),
    radial-gradient(ellipse 100% 100% at 50% 50%, #f5f6f6 0%, #fbfbfb 100%);
}
[data-theme="light"] .login-card {
  box-shadow:
    0 0 0 1px rgba(0, 0, 0, 0.04),
    0 24px 64px rgba(0, 0, 0, 0.12);
}
[data-theme="light"] tr:hover td {
  background: rgba(0, 0, 0, 0.02);
}

/* Smooth transitions for themed elements */
.sidebar,
.topbar,
.card,
.stat,
.student-focus,
.login-card,
.f-input,
.sb-item,
.sb-nav,
.sb-user,
.page,
.main,
#app {
  transition:
    background 0.3s,
    border-color 0.3s,
    color 0.3s;
}

/* Theme toggle button */
.theme-btn {
  width: 33px;
  height: 33px;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: var(--text-3);
  transition:
    border-color 0.15s,
    color 0.15s,
    background 0.3s;
}
.theme-btn:hover {
  border-color: var(--border-accent);
  color: var(--accent);
}

/* Login page theme button (fixed top-right) */
.login-theme-btn {
  position: fixed;
  top: 18px;
  right: 20px;
  z-index: 20;
  width: 36px;
  height: 36px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: var(--text-3);
  transition:
    border-color 0.15s,
    color 0.15s,
    background 0.3s;
}
.login-theme-btn:hover {
  border-color: var(--border-accent);
  color: var(--accent);
}

/* ─────────────────────────────────────────
       SCROLLBAR
    ───────────────────────────────────────── */
::-webkit-scrollbar {
  width: 4px;
  height: 4px;
}
::-webkit-scrollbar-track {
  background: var(--bg);
}
::-webkit-scrollbar-thumb {
  background: var(--surface-3);
  border-radius: 2px;
}

/* ─────────────────────────────────────────
       TRANSITION OVERLAY
    ───────────────────────────────────────── */
#overlay {
  position: fixed;
  inset: 0;
  background: var(--bg);
  z-index: 9999;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.28s ease;
}
#overlay.on {
  opacity: 1;
  pointer-events: all;
}

/* ═════════════════════════════════════════
       LOGIN PAGE
    ═════════════════════════════════════════ */
#login-page {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  overflow: hidden;
}

/* Background layers */
.bg-grad {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(
      ellipse 90% 70% at 72% 18%,
      rgb(var(--accent-rgb) / 0.12) 0%,
      transparent 60%
    ),
    radial-gradient(
      ellipse 60% 80% at 18% 85%,
      rgba(106, 160, 255, 0.07) 0%,
      transparent 60%
    ),
    radial-gradient(ellipse 100% 100% at 50% 50%, #172537 0%, #0d1421 100%);
}
.bg-dots {
  position: absolute;
  inset: 0;
  background-image: radial-gradient(
    circle,
    rgb(var(--accent-rgb) / 0.12) 1px,
    transparent 1px
  );
  background-size: 32px 32px;
  opacity: 0.35;
  mask-image: radial-gradient(
    ellipse 80% 80% at 50% 50%,
    black 30%,
    transparent 75%
  );
}
/* Mountain silhouette */
.bg-mtns {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 42%;
  pointer-events: none;
}

/* Login card */
.login-card {
  position: relative;
  z-index: 10;
  width: 400px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius);
  padding: 44px 44px 36px;
  box-shadow:
    0 0 0 1px rgba(255, 255, 255, 0.02),
    0 48px 96px rgba(0, 0, 0, 0.65),
    0 0 140px rgb(var(--accent-rgb) / 0.025);
  opacity: 0;
  transform: translateY(18px);
  animation: cardIn 0.75s var(--ease-out) 0.1s forwards;
}
@keyframes cardIn {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
@media (prefers-reduced-motion: reduce) {
  .login-card {
    animation-duration: 1ms;
    transform: none;
  }
}

/* Logo */
.l-logo {
  display: flex;
  align-items: center;
  gap: 11px;
  margin-bottom: 34px;
}
.l-logo-mark {
  height: 34px;
  width: auto;
  flex-shrink: 0;
}
.l-logo-name {
  font-family: var(--font-d);
  font-size: var(--font-size-2xl);
  font-weight: 600;
  letter-spacing: 0.03em;
  line-height: 1;
}
.l-logo-name .learn {
  font-weight: 400;
  font-style: italic;
  color: var(--text-2);
}
.l-logo-tag {
  font-size: var(--font-size-xs);
  font-weight: 400;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-3);
  margin-top: var(--space-4);
}
.ai-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: var(--font-size-xs);
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent);
  background: var(--accent-pale);
  border: 1px solid var(--border-accent);
  padding: 3px 9px;
  border-radius: 999px;
  margin-top: 7px;
}

/* Headline */
.l-headline {
  font-family: var(--font-d);
  font-size: 36.5px;
  font-weight: 600;
  line-height: 1.05;
  margin-bottom: 7px;
}
.l-headline em {
  font-style: italic;
  font-weight: 400;
}
.l-sub {
  font-size: var(--font-size-md);
  font-weight: 400;
  color: var(--text-3);
  margin-bottom: 30px;
  line-height: 1.55;
}

/* Form */
.f-group {
  margin-bottom: 15px;
}
.f-label {
  display: block;
  font-size: var(--font-size-sm);
  font-weight: 500;
  color: var(--text-3);
  margin-bottom: 7px;
}
.f-input {
  width: 100%;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 11px 13px;
  font-family: var(--font-b);
  font-size: var(--font-size-lg);
  font-weight: 400;
  color: var(--text);
  outline: none;
  transition:
    border-color 0.18s,
    box-shadow 0.18s;
}
/* Password field with a show/hide button inside its right edge */
.pw-field {
  position: relative;
}
.pw-field > input {
  padding-right: 42px;
}
.pw-toggle {
  position: absolute;
  top: 50%;
  right: 6px;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-3);
  cursor: pointer;
}
.pw-toggle:hover {
  color: var(--text);
}
.pw-toggle:focus-visible {
  outline: 2px solid var(--accent);
}
.pw-toggle svg {
  width: 18px;
  height: 18px;
}
.f-input::placeholder {
  color: var(--text-3);
}
.f-input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgb(var(--accent-rgb) / 0.09);
}

/* CTA */
.l-btn {
  width: 100%;
  margin-top: var(--space-8);
  padding: var(--space-12) var(--space-24);
  background: var(--accent);
  color: var(--on-accent);
  border: none;
  border-radius: var(--radius-sm);
  font-family: var(--font-b);
  font-size: var(--font-size-md);
  font-weight: 600;
  cursor: pointer;
  transition:
    background 0.18s,
    transform 0.1s;
  position: relative;
  overflow: hidden;
}
.l-btn::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    135deg,
    rgba(255, 255, 255, 0.1) 0%,
    transparent 55%
  );
  pointer-events: none;
}
.l-btn:hover:not(:disabled) {
  background: var(--accent-lt);
}
.l-btn:active:not(:disabled) {
  transform: scale(0.985);
}
.l-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* Error */
.l-err {
  margin-top: 11px;
  padding: 9px var(--space-12);
  background: var(--crimson-pale);
  border: 1px solid rgba(232, 99, 126, 0.2);
  border-radius: var(--radius-sm);
  font-size: var(--font-size-md);
  color: var(--crimson);
  display: none;
}
.l-err.show {
  display: block;
}
.l-forgot {
  background: none;
  border: none;
  color: var(--text-3);
  font-size: var(--font-size-base);
  cursor: pointer;
  font-family: var(--font-b);
  padding: 0;
  transition: color 0.15s;
}
.l-forgot:hover {
  color: var(--accent);
}

/* Demo creds */
.demo-wrap {
  margin-top: 26px;
  padding-top: var(--space-18);
  border-top: 1px solid var(--border);
}
.demo-ttl {
  font-size: var(--font-size-xs);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-3);
  margin-bottom: var(--space-10);
}
.demo-row {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 5px 0;
  cursor: pointer;
  /* A real <button> now (was a <div onClick> - unreachable by keyboard, see
     LoginPage.tsx) - reset its native chrome back to the plain flex row this
     class has always drawn, so becoming a button changes nothing visually. */
  width: 100%;
  border: none;
  background: none;
  font: inherit;
  text-align: left;
  color: inherit;
}
.demo-row:hover .demo-txt {
  color: var(--text-2);
}
.demo-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  flex-shrink: 0;
}
.demo-txt {
  font-size: var(--font-size-base);
  font-weight: 500;
  color: var(--text-2);
  transition: color 0.15s;
  flex: 1;
}
.demo-role {
  font-family: var(--font-m);
  font-size: var(--font-size-sm);
  color: var(--text-3);
  transition: color 0.15s;
}
.demo-row:hover .demo-role {
  color: var(--text-2);
}

/* ═════════════════════════════════════════
       APP SHELL
    ═════════════════════════════════════════ */
/* Fixed app shell: the sidebar + topbar stay put and only .page scrolls internally
       (height:100vh + min-height:0 down the chain) — so a view never grows the document body. */
#app {
  display: none;
  /* The text-size setting zooms the root (services/fontScale.ts) and zoom scales vh too,
     so an uncompensated 100vh shell overflows the window and the window itself scrolls. */
  height: calc(100vh / var(--font-zoom, 1));
}
#app.on {
  display: flex;
}

/* Sidebar */
.sidebar {
  width: 244px;
  flex-shrink: 0;
  background: var(--surface);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  height: calc(100vh / var(--font-zoom, 1));
  position: sticky;
  top: 0;
}
@keyframes sbIn {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: translateX(0);
  }
}
/* Desktop-only entrance slide - scoped to the same min-width the mobile
   off-canvas drawer (below) assumes is free of it. `animation-fill-mode:
   forwards` pins the element's transform at the animation's final keyframe
   value INDEFINITELY once it finishes, and that pinned value beats an
   ordinary stylesheet `transform` declaration for the same property
   regardless of specificity or source order - so left unscoped, this
   permanently overrode the mobile media query's own `translateX(-100%)`
   closed state below, no matter the viewport width. On a fresh load at a
   real phone width (not a resize of an already-loaded desktop tab, which
   never replays a finished animation) the drawer rendered permanently open
   with no way to dismiss it. */
@media (min-width: 901px) {
  .sidebar {
    animation: sbIn 0.45s var(--ease-out) forwards;
  }
}
@media (prefers-reduced-motion: reduce) {
  .sidebar {
    animation-duration: 1ms;
  }
}

.sb-logo {
  padding: 22px var(--space-18);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: var(--space-10);
}
.sb-logo-mark {
  height: 28px;
  width: auto;
  flex-shrink: 0;
}
.sb-logo-name {
  font-family: var(--font-d);
  font-size: var(--font-size-xl);
  font-weight: 600;
  line-height: 1.1;
}
.sb-logo-sub {
  font-size: var(--font-size-xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-3);
  margin-top: var(--space-2);
}

.sb-nav {
  flex: 1;
  padding: var(--space-12) 0;
  overflow-y: auto;
  min-height: 0;
}
.sb-section {
  font-size: var(--font-size-xs);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-3);
  padding: var(--space-10) var(--space-18) 5px;
  margin-top: var(--space-4);
}

.sb-item {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 1px var(--space-8);
  padding: 9px var(--space-10);
  border-radius: var(--radius-pill);
  cursor: pointer;
  text-decoration: none;
  transition: background 0.12s;
  position: relative;
}
.sb-item:hover {
  background: rgba(255, 255, 255, 0.028);
}
.sb-item.act {
  background: var(--accent-pale);
}
.sb-ico {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
  color: var(--text-3);
  transition: color 0.12s;
}
.sb-item.act .sb-ico,
.sb-item:hover .sb-ico {
  color: var(--text-2);
}
.sb-item.act .sb-ico {
  color: var(--accent);
}
.sb-lbl {
  font-size: var(--font-size-md);
  font-weight: 400;
  color: var(--text-2);
  transition: color 0.12s;
  flex: 1;
}
.sb-item.act .sb-lbl,
.sb-item:hover .sb-lbl {
  color: var(--text);
}
.sb-badge {
  background: var(--crimson);
  color: #fff;
  font-size: var(--font-size-xs);
  font-weight: 600;
  padding: 1.5px 5px;
  border-radius: var(--radius-pill);
  min-width: 17px;
  text-align: center;
}
.sb-subitem {
  padding-left: 40px;
}
.sb-subitem .sb-lbl {
  font-size: var(--font-size-base);
}
.sb-chevron {
  background: none;
  border: none;
  padding: var(--space-4);
  margin: -4px -6px -4px 0;
  display: flex;
  align-items: center;
  flex-shrink: 0;
  cursor: pointer;
  color: var(--text-3);
  border-radius: 4px;
}
.sb-chevron:hover {
  color: var(--text-2);
  background: rgba(255, 255, 255, 0.06);
}
.sb-chevron svg {
  width: 13px;
  height: 13px;
  transition: transform 0.15s;
}
.sb-chevron.open svg {
  transform: rotate(180deg);
}
.sb-badge.accent {
  background: var(--accent);
  color: var(--on-accent);
}

.sb-user {
  padding: var(--space-14) var(--space-18);
  border-top: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: 9px;
  cursor: pointer;
}
.sb-avatar {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-d);
  font-size: var(--font-size-md);
  font-weight: 600;
  background: var(--accent-pale);
  border: 1px solid var(--border-accent);
  color: var(--accent);
}
.sb-uname {
  font-size: var(--font-size-md);
  font-weight: 500;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sb-urole {
  font-size: var(--font-size-sm);
  color: var(--text-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-top: 1px;
}
.app-version {
  font-size: var(--font-size-xs);
  color: var(--text-3);
  font-variant-numeric: tabular-nums;
  user-select: all;
}
.sb-version {
  padding: 0 var(--space-18) var(--space-10);
}
.login-version {
  position: absolute;
  bottom: var(--space-12);
  left: 0;
  right: 0;
  text-align: center;
}
.sb-logout {
  margin-left: auto;
  background: none;
  border: none;
  cursor: pointer;
  padding: var(--space-4);
  color: var(--text-3);
  transition: color 0.15s;
  flex-shrink: 0;
}
.sb-logout:hover {
  color: var(--crimson);
}

/* Main */
.main {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  animation: fadeUp 0.45s ease 0.18s both;
}
@keyframes fadeUp {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
@media (prefers-reduced-motion: reduce) {
  .main {
    animation-duration: 1ms;
    transform: none;
  }
}

/* Topbar */
.topbar {
  height: 56px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  /* Native shell (mobile/ - see mobile/CLAUDE.md): env(safe-area-inset-*) is 0 on a plain
     browser (viewport-fit=cover only affects native/notched contexts), so this is a no-op
     there - on a notched/dynamic-island device it keeps the topbar's content clear of the
     notch instead of rendering under it. Grows the box taller than 56px via padding
     (content-box), which is the intended behaviour. */
  padding: env(safe-area-inset-top, 0px)
    max(var(--space-24), env(safe-area-inset-right, 0px)) 0
    max(var(--space-24), env(safe-area-inset-left, 0px));
  gap: var(--space-12);
  position: sticky;
  top: 0;
  z-index: 50;
  flex-shrink: 0;
}
.bc {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  font-size: var(--font-size-md);
  min-width: 0;
  flex-shrink: 1;
}
.bc-sep {
  color: var(--text-3);
}
button.bc-dim {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  cursor: pointer;
}
button.bc-dim:hover {
  color: var(--text);
}
/* Phones: just the page title, so it doesn't get cut to "Dash…". */
@media (max-width: 640px) {
  .bc-home {
    display: none;
  }
}
.bc-dim {
  color: var(--text-3);
}
.bc-cur {
  color: var(--text);
  font-weight: 500;
  /* A long, unbreakable single-word page title (e.g. "Announcements") won't
     wrap and, as a flex item, won't shrink below its content width by
     default - forcing .tb-right off the right edge of the topbar at narrow
     viewports. Truncate instead of letting it force an overflow. */
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tb-right {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 7px;
  flex-shrink: 0;
}
.subj-chip {
  /* hod's "Head of Department" and coordinator's "<Stream> Coordinator" run
     ~170-180px wide - much longer than the short role/subject codes (e.g.
     "Accountancy" at ~121px, the widest of those) this chip was styled for.
     Cap it so those two long labels ellipsize instead of pushing the rest of
     .tb-right (bell/settings/theme) off the topbar's right edge at ~414px
     viewports. 130px clears every short chip untouched. */
  max-width: 130px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--font-size-xs);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 3px 9px;
  border-radius: var(--radius-pill);
}
.chip-phy {
  background: var(--sky-pale);
  color: var(--sky);
}
.chip-acc {
  background: var(--violet-pale);
  color: var(--violet);
}
.chip-adm {
  background: var(--crimson-pale);
  color: var(--crimson);
}
.chip-stu {
  background: var(--surface-3);
  color: var(--text-2);
}
.tb-btn {
  width: 33px;
  height: 33px;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: var(--text-3);
  transition:
    border-color 0.15s,
    color 0.15s;
  position: relative;
}
.tb-btn:hover {
  border-color: var(--border-accent);
  color: var(--text-2);
}
.topbar-pop {
  transform-origin: top right;
  opacity: 0;
  transform: scale(0.96) translateY(-6px);
  animation: topbarPopIn 180ms var(--ease-out) forwards;
}
@keyframes topbarPopIn {
  to {
    opacity: 1;
    transform: scale(1) translateY(0);
  }
}
@media (prefers-reduced-motion: reduce) {
  .topbar-pop {
    animation-duration: 80ms;
    transform: none;
  }
}
/* Top-bar search */
.gsearch {
  position: relative;
  display: flex;
  align-items: center;
}
.gsearch-toggle,
.gsearch-close {
  display: none;
}
.gsearch-field {
  position: relative;
  display: flex;
  align-items: center;
}
.gsearch-ico {
  position: absolute;
  left: var(--space-10);
  color: var(--text-3);
  pointer-events: none;
}
/* .gsearch prefix: .inp (later in this file) would otherwise reset the padding. */
.gsearch .gsearch-input {
  width: 220px;
  margin: 0;
  padding-left: 34px;
}
.gsearch-input::-webkit-search-cancel-button {
  display: none;
}
.gsearch-pop {
  position: fixed;
  top: calc(58px + env(safe-area-inset-top, 0px));
  right: max(18px, env(safe-area-inset-right, 0px));
  width: 380px;
  max-height: 70vh;
  overflow-y: auto;
  padding: var(--space-6);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
  z-index: 200;
}
.gsearch-group {
  margin: var(--space-8) var(--space-10) var(--space-4);
}
.gsearch-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: 100%;
  padding: var(--space-8) var(--space-10);
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--text);
  font-family: var(--font-b);
  text-align: left;
  cursor: pointer;
}
.gsearch-item.act {
  background: var(--surface-2);
}
.gsearch-label {
  font-size: var(--font-size-sm);
}
.gsearch-sub {
  font-size: var(--font-size-xs);
  color: var(--text-3);
}
@media (max-width: 640px) {
  .gsearch-toggle {
    display: flex;
  }
  .gsearch-field {
    display: none;
  }
  /* Opened on a phone: a full-width field over the topbar. */
  .gsearch.open .gsearch-field {
    display: flex;
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: calc(56px + env(safe-area-inset-top, 0px));
    padding: env(safe-area-inset-top, 0px) var(--space-12) 0;
    gap: var(--space-8);
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    z-index: 210;
  }
  .gsearch.open .gsearch-ico {
    left: calc(var(--space-12) + var(--space-10));
  }
  .gsearch.open .gsearch .gsearch-input,
  .gsearch.open .gsearch-input {
    flex: 1;
    width: auto;
  }
  .gsearch.open .gsearch-close {
    display: flex;
    background: none;
    border: none;
    color: var(--text-2);
    font-size: 20px;
    cursor: pointer;
  }
  .gsearch-pop {
    left: var(--space-8);
    right: var(--space-8);
    width: auto;
  }
}

/* Account menu (top-right gear) */
.acct-pop {
  /* fixed, not absolute: on phones the page scrolls under the topbar, and an
     absolute panel drifted up over it. */
  position: fixed;
  top: calc(58px + env(safe-area-inset-top, 0px));
  right: max(12px, env(safe-area-inset-right, 0px));
  width: 240px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
  z-index: 200;
  padding: var(--space-6);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.acct-head {
  padding: var(--space-6) var(--space-10) var(--space-10);
  margin-bottom: var(--space-4);
  border-bottom: 1px solid var(--border);
}
.acct-name {
  font-weight: 600;
  color: var(--text);
  font-size: var(--font-size-sm);
}
.acct-role {
  color: var(--text-3);
  font-size: var(--font-size-xs);
  margin-top: 2px;
}
.acct-item {
  display: flex;
  align-items: center;
  gap: var(--space-10);
  width: 100%;
  padding: var(--space-8) var(--space-10);
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--text);
  font-family: var(--font-b);
  font-size: var(--font-size-sm);
  text-align: left;
  cursor: pointer;
}
.acct-item:hover,
.acct-item:focus-visible {
  background: var(--surface-2);
}
.acct-danger {
  color: var(--crimson);
}
/* Calendar */
.cal-date-btn {
  width: auto;
  padding: 0 11px;
  gap: 7px;
  font-size: var(--font-size-base);
  color: var(--text-2);
}
.cal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-14);
}
.cal-nav-btn {
  width: 30px;
  height: 30px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: none;
  color: var(--text-2);
  cursor: pointer;
  font-size: var(--font-size-xl);
  line-height: 1;
}
.cal-nav-btn:hover {
  border-color: var(--border-accent);
  color: var(--accent);
}
.cal-month {
  font-family: var(--font-d);
  font-size: var(--font-size-3xl);
  font-weight: 600;
}
.cal-grid {
  display: grid;
  /* minmax(0, …): plain 1fr won't shrink below a cell's content, so phones
     pushed Saturday past the card edge. */
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: var(--space-6);
}
.cal-dow {
  font-size: var(--font-size-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-3);
  text-align: center;
  padding: var(--space-2) 0;
}
.cal-cell {
  min-height: 62px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: var(--space-6);
  background: var(--surface);
  position: relative;
  transition: border-color 0.15s;
}
.cal-cell.empty {
  background: transparent;
  border-color: transparent;
}
.cal-cell.has {
  cursor: pointer;
}
.cal-cell.has:hover {
  border-color: var(--border-accent);
}
.cal-cell.today {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}
.cal-cell.sel {
  background: var(--accent-pale);
  border-color: var(--accent);
}
.cal-cell.off {
  background: var(--surface-2);
}
.cal-cell.off .cal-daynum {
  color: var(--text-3);
}
.cal-off-tag {
  position: absolute;
  top: 5px;
  right: 6px;
  font-size: var(--font-size-3xs);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  /* No opacity - it composited --text-3 down to an effective ~3.4:1 against
     --surface-2 (WCAG audit), below the 6.7:1 --text-3 gets on its own. The
     muted look --text-3 already provides doesn't need a second dimming on
     top of it. */
  color: var(--text-3);
}
.cal-pop .cal-off-tag {
  display: none;
}
.cal-daynum {
  font-size: var(--font-size-base);
  font-weight: 600;
  color: var(--text-2);
}
.cal-ad-day {
  font-size: var(--font-size-3xs);
  font-weight: 400;
  color: var(--text-3);
  margin-left: 3px;
}
.cal-cell.today .cal-daynum {
  color: var(--accent);
}
.cal-dots {
  display: flex;
  flex-wrap: wrap;
  gap: 3px;
  margin-top: 5px;
}
.cal-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
}
.cal-ev-list {
  margin-top: var(--space-18);
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
}
.cal-ev {
  display: flex;
  align-items: center;
  gap: var(--space-10);
  padding: 9px var(--space-12);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
}
.cal-ev-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}
.cal-ev-date {
  font-family: "DM Mono", ui-monospace, monospace;
  font-size: var(--font-size-sm);
  color: var(--text-3);
  width: 52px;
  flex-shrink: 0;
}
.cal-pop .cal-cell {
  min-height: 30px;
  padding: 3px 0;
  text-align: center;
}
.cal-pop .cal-daynum {
  font-size: var(--font-size-sm);
}
.cal-pop .cal-dots {
  justify-content: center;
  margin-top: var(--space-2);
}
.cal-pop .cal-grid {
  gap: 3px;
}
/* Inbox: two panes on desktop; one at a time on phones/tablets. */
.inbox-layout {
  display: flex;
  height: calc(100vh - 210px);
  min-height: 420px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface);
}
.inbox-list {
  width: 320px;
  flex-shrink: 0;
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.inbox-pane {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  background: var(--bg);
}
.inbox-back {
  display: none;
  border: none;
  background: none;
  padding: 0;
  margin-bottom: var(--space-8);
  color: var(--accent);
  font: inherit;
  font-size: var(--font-size-sm);
  cursor: pointer;
}
/* Settings */
.set-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-14);
  padding: 13px 0;
  border-bottom: 1px solid var(--border);
}
.set-row:last-child {
  border-bottom: none;
}
.set-row-lbl {
  font-size: var(--font-size-md);
  font-weight: 500;
  color: var(--text);
}
.set-row-sub {
  font-size: var(--font-size-base);
  color: var(--text-3);
  margin-top: var(--space-2);
}
/* Two-step sign-in setup (MfaSetupModal.tsx). The QR stays white in dark mode so
   phone cameras can read it. */
.mfa-qr {
  display: block;
  width: 180px;
  height: 180px;
  margin: var(--space-8) auto;
  background: #fff;
  border-radius: var(--radius-sm);
}
.mfa-key,
.mfa-codes {
  font-family: var(--font-m);
  font-size: var(--font-size-base);
  overflow-wrap: anywhere;
}
.mfa-codes {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-6) var(--space-16);
  padding: var(--space-12);
  margin: var(--space-8) 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.seg {
  display: inline-flex;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.seg button {
  padding: var(--space-6) var(--space-16);
  font-size: var(--font-size-base);
  background: none;
  border: none;
  color: var(--text-2);
  cursor: pointer;
  transition:
    background 0.15s,
    color 0.15s;
}
.seg button.on {
  background: var(--accent);
  color: var(--on-accent);
}
.sw {
  position: relative;
  display: inline-block;
  width: 42px;
  height: 24px;
  flex-shrink: 0;
}
.sw input {
  opacity: 0;
  width: 0;
  height: 0;
}
.sw-track {
  position: absolute;
  inset: 0;
  background: rgba(128, 128, 128, 0.35);
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.2s;
}
.sw-track::before {
  content: "";
  position: absolute;
  width: 18px;
  height: 18px;
  left: 3px;
  top: 3px;
  background: #fff;
  border-radius: 50%;
  transition: transform 0.2s;
}
.sw input:checked + .sw-track {
  background: var(--accent);
}
.sw input:checked + .sw-track::before {
  transform: translateX(18px);
}

/* Page */
.page {
  flex: 1;
  /* Containing block for absolute descendants (e.g. the visually hidden MCQ radio): without
     it they position against the document, grow it past the window, and focusing one
     scrolls the whole shell. */
  position: relative;
  /* Native shell (mobile/): bottom safe-area inset so scrolled-to-the-end content clears the
     home indicator instead of sitting flush behind it - 0 on a plain browser, see .topbar's
     comment above. */
  padding: 26px var(--space-28) max(26px, env(safe-area-inset-bottom, 0px));
  overflow-y: auto;
  min-height: 0;
}
.pg-header {
  margin-bottom: 26px;
}
.pg-title {
  font-family: var(--font-d);
  font-size: 34.5px;
  font-weight: 600;
  line-height: 1.05;
}
.pg-title em {
  font-style: italic;
  font-weight: 400;
}
.pg-sub {
  font-size: var(--font-size-md);
  color: var(--text-3);
  margin-top: var(--space-4);
}

/* Student dashboard: one clear learning action, with the rest of the queue
   and the summary counts treated as supporting information. */
.student-focus {
  padding: var(--space-24);
  border: 1px solid var(--border-card);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-lg);
  background: linear-gradient(
    120deg,
    color-mix(in srgb, var(--card-bg), var(--accent) 5%),
    var(--card-bg) 68%
  );
  box-shadow: var(--shadow-sm);
}
.student-focus-head,
.student-focus-main,
.student-focus-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-20);
}
.student-focus-head {
  align-items: flex-start;
  margin-bottom: var(--space-20);
}
.student-focus-eyebrow {
  color: var(--accent);
  font-size: var(--font-size-xs);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  margin-bottom: var(--space-4);
}
.student-focus h2 {
  font-family: var(--font-b);
  font-size: var(--font-size-lg);
  font-weight: 600;
  line-height: 1.3;
}
.student-focus-count {
  color: var(--text-2);
  font-size: var(--font-size-sm);
  white-space: nowrap;
}
.student-focus-copy {
  min-width: 0;
}
.student-focus-subject {
  color: var(--text-2);
  font-size: var(--font-size-sm);
  font-weight: 600;
  margin-bottom: var(--space-6);
}
.student-focus h3 {
  font-family: var(--font-b);
  font-size: clamp(22px, 2.5vw, 28px);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.025em;
}
.student-focus-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-8);
  color: var(--text-2);
  font-size: var(--font-size-md);
  margin-top: var(--space-10);
}
.student-focus-meta span + span::before {
  content: "·";
  margin-right: var(--space-8);
  color: var(--text-3);
}
.student-focus-action {
  flex-shrink: 0;
  min-height: 44px;
  padding-inline: var(--space-20);
}
.student-focus-submitted {
  display: inline-flex;
  align-items: center;
  gap: var(--space-6);
  color: var(--jade);
  font-size: var(--font-size-sm);
  font-weight: 600;
  white-space: nowrap;
}
.student-focus-more {
  border-top: 1px solid var(--border);
  margin-top: var(--space-24);
  padding-top: var(--space-8);
}
.student-focus-row {
  padding: var(--space-10) 0;
}
.student-focus-row + .student-focus-row {
  border-top: 1px solid var(--border);
}
.student-focus-row-name,
.student-focus-row-detail {
  display: block;
}
.student-focus-row-name {
  color: var(--text);
  font-size: var(--font-size-md);
  font-weight: 600;
}
.student-focus-row-detail {
  color: var(--text-2);
  font-size: var(--font-size-sm);
  margin-top: var(--space-4);
}
.student-focus-empty-copy {
  color: var(--text-2);
  font-size: var(--font-size-md);
  margin-top: var(--space-10);
}
.student-summary .stat {
  box-shadow: none;
  border-radius: var(--radius);
}
.student-summary .stat::after {
  display: none;
}

.teacher-dashboard-controls {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-10);
  margin-bottom: var(--space-14);
}
.teacher-dashboard-controls .inp {
  width: auto;
  min-width: 220px;
  margin: 0;
}
.teacher-support {
  padding: var(--space-20) var(--space-24);
  margin-bottom: var(--space-18);
  background: var(--card-bg);
  border: 1px solid var(--border-card);
  border-left: 3px solid var(--crimson);
  border-radius: var(--radius-lg);
}
.teacher-support-clear {
  border-left-color: var(--jade);
}
.teacher-support-heading,
.teacher-support-row,
.teacher-support-row-action {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-18);
}
.teacher-support-heading {
  align-items: flex-start;
}
.teacher-support-eyebrow {
  color: var(--text-2);
  font-size: var(--font-size-xs);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  margin-bottom: var(--space-4);
}
.teacher-support h2 {
  font-size: var(--font-size-xl);
  font-weight: 600;
}
.teacher-support-heading > span {
  color: var(--crimson);
  font-size: var(--font-size-sm);
  font-weight: 600;
  white-space: nowrap;
}
.teacher-support-clear .teacher-support-heading > span {
  color: var(--jade);
}
.teacher-support-list {
  margin-top: var(--space-12);
}
.teacher-support-row {
  border-top: 1px solid var(--border);
  padding: var(--space-10) 0;
}
.teacher-support-row > div:first-child {
  min-width: 0;
}
.teacher-support-row strong,
.teacher-support-row > div:first-child > span {
  display: block;
}
.teacher-support-row strong {
  color: var(--text);
  font-size: var(--font-size-md);
  font-weight: 600;
}
.teacher-support-row > div:first-child > span,
.teacher-support-empty {
  color: var(--text-2);
  font-size: var(--font-size-sm);
  margin-top: var(--space-4);
}
.teacher-support-row-action {
  justify-content: flex-end;
  flex-shrink: 0;
  gap: var(--space-10);
}
.teacher-support-more {
  color: var(--accent);
  background: none;
  border: 0;
  font: inherit;
  font-size: var(--font-size-sm);
  font-weight: 600;
  cursor: pointer;
  padding: var(--space-10) 0 0;
}
.teacher-support-empty {
  margin-top: var(--space-14);
}
.teacher-summary .stat {
  box-shadow: none;
}
.teacher-summary .stat::after {
  display: none;
}
.copilot-flow {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-10);
  margin-bottom: var(--space-20);
}
.copilot-flow > div {
  display: flex;
  align-items: center;
  gap: var(--space-10);
  background: var(--card-bg);
  border: 1px solid var(--border-card);
  border-radius: var(--radius);
  padding: var(--space-12) var(--space-14);
}
.copilot-flow span {
  width: 25px;
  height: 25px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  background: var(--accent-pale);
  color: var(--accent);
  border-radius: var(--radius-pill);
  font-family: var(--font-m);
  font-size: var(--font-size-sm);
}
.copilot-flow strong {
  color: var(--text);
  font-size: var(--font-size-sm);
  font-weight: 600;
}

/* Stat cards */
.stats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 11px;
  margin-bottom: 22px;
}
.stat {
  /* --tile-bg (StatCard's optional `tone` prop, F2 in the UX audit) lets a
     specific tile break from the brand-emerald default to actually signal
     good/bad/neutral; unset, it falls back to the same fill every other
     card uses. */
  background: var(--tile-bg, var(--card-bg));
  border: 1px solid var(--border-card);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  padding: var(--space-18) var(--space-18) 15px;
  position: relative;
  overflow: hidden;
  transition: border-color 0.15s;
}
.stat:hover {
  border-color: var(--border-card-h);
}
.stat::after {
  content: "";
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--c, var(--accent));
  opacity: 0.4;
}
.stat-lbl {
  font-size: var(--font-size-xs);
  font-weight: 500;
  color: var(--text-3);
  margin-bottom: 9px;
}
.stat-val {
  font-family: var(--font-m);
  font-size: 30.5px;
  font-weight: 400;
  color: var(--text);
  line-height: 1;
}
.stat-delta {
  font-size: var(--font-size-sm);
  font-weight: 400;
  margin-top: 5px;
}
.up {
  color: var(--jade);
}
.down {
  color: var(--crimson);
}
.neu {
  color: var(--text-3);
}

/* F10 (UX audit): the six most-repeated EXACT inline style={{...}} blocks
   across views/ (zero value variance, so extracting them changes no
   computed style) - a mechanical de-duplication, not a redesign. Promote
   the next pattern that hits this kind of repetition here too, same as
   the Card/List pattern (ViewToggle/FilterPills/CardGrid) already was. */
.text-muted {
  color: var(--text-3);
}
.text-muted-sm {
  font-size: var(--font-size-xs);
  color: var(--text-3);
}
.text-bad {
  color: var(--crimson);
}
.clickable {
  cursor: pointer;
}
/* A whole list row (or a table cell's label) that navigates: a real <button> so it's
   reachable by Tab and Enter, styled to look like the row it replaces. */
.row-btn {
  appearance: none;
  background: none;
  border: 0;
  margin: 0;
  padding: 0;
  width: 100%;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.scroll-x {
  overflow-x: auto;
}
.ta-right {
  text-align: right;
}

/* WCAG audit (F13): the very first focusable element on every page, so a
   keyboard user can bypass the 10-16+ item sidebar instead of re-tabbing
   through it on every single navigation. Off-screen (not display:none/
   visibility:hidden, which would also pull it out of the tab order) until
   it receives keyboard focus, then pinned to the top-left corner above
   everything else. */
.skip-link {
  position: fixed;
  top: -100px;
  left: 12px;
  z-index: 5000;
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 600;
  font-size: var(--font-size-sm);
  padding: var(--space-10) var(--space-18);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lg);
  transition: top 0.15s;
}
.skip-link:focus-visible {
  top: 12px;
}

/* WCAG audit (F14): visible-to-screen-readers-only text - for a column
   header (e.g. a table's actions column) that's meaningful to convey but
   deliberately blank on screen. aria-label alone isn't enough here (axe's
   empty-table-header rule, correctly, wants real text content - a sighted
   non-screen-reader user staring at the DOM/inspector benefits too), so
   this is real text, just clipped to a 1px box instead of hidden outright
   (display:none/visibility:hidden would also remove it from the a11y tree). */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* lucide-react icons (every one carries class="lucide"): sized in em so an icon scales
   with its surrounding text the way the emoji it replaced did, and stroked at the same
   1.6px screen width as the hand-drawn sidebar set (chrome/navConfig.ts ICOS) regardless
   of size - so call sites never pass size/strokeWidth props. */
.lucide {
  width: 1.1em;
  height: 1.1em;
  stroke-width: 1.6;
  vertical-align: -0.2em;
  flex-shrink: 0;
}
.lucide * {
  vector-effect: non-scaling-stroke;
}

/* Grid */
.grid-2 {
  display: grid;
  grid-template-columns: 1fr 310px;
  gap: var(--space-14);
}
.col-r {
  display: flex;
  flex-direction: column;
  gap: var(--space-14);
}

/* Cards */
.card {
  background: var(--card-bg);
  border: 1px solid var(--border-card);
  border-radius: var(--radius);
  /* A quiet rim separates dark surfaces; priority content gets elevation. */
  box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.05);
  transition: border-color 0.15s;
}
.card:hover {
  border-color: var(--border-card-h);
}
/* Horizontal-scroll affordance for wide tables (e.g. Class Analytics' per-
   department columns) - an inset edge glow at whichever side(s) still have
   hidden content, toggled by JS tracking scrollLeft/scrollWidth, so a
   scrollable table doesn't look like a silently-clipped one. Uses
   --border-accent (chrome, not a status color) rather than a plain black
   shadow or background-color fade - either of those reads fine on the
   light theme but all but disappears against the dark theme's already-dark
   card surface. */
.table-scroll-wrap {
  position: relative;
}
.table-scroll {
  overflow-x: auto;
  transition: box-shadow 0.15s;
}
.table-scroll.scroll-fade-l {
  box-shadow: inset 12px 0 10px -8px var(--border-accent);
}
.table-scroll.scroll-fade-r {
  box-shadow: inset -12px 0 10px -8px var(--border-accent);
}
.table-scroll.scroll-fade-l.scroll-fade-r {
  box-shadow:
    inset 12px 0 10px -8px var(--border-accent),
    inset -12px 0 10px -8px var(--border-accent);
}
.card-hd {
  padding: var(--space-14) var(--space-18);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.card-title {
  font-family: var(--font-b);
  font-size: var(--font-size-lg);
  font-weight: 600;
  letter-spacing: -0.01em;
}
.card-link {
  font-size: var(--font-size-base);
  color: var(--accent);
  cursor: pointer;
  text-decoration: none;
  transition: color 0.15s;
}
.card-link:hover {
  color: var(--accent-lt);
}
.card-body {
  padding: var(--space-14) var(--space-18);
}

/* Table */
table {
  width: 100%;
  border-collapse: collapse;
}
th {
  font-size: var(--font-size-xs);
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-3);
  padding: var(--space-8) 11px;
  text-align: left;
  border-bottom: 1px solid var(--border);
}
td {
  padding: 10.5px 11px;
  font-size: var(--font-size-md);
  color: var(--text-2);
  border-bottom: 1px solid rgba(255, 255, 255, 0.025);
  transition: background 0.1s;
}
tr:last-child td {
  border-bottom: none;
}
tr:hover td {
  background: rgba(255, 255, 255, 0.018);
}
.td-name {
  color: var(--text);
  font-weight: 500;
}
.td-mono {
  font-family: var(--font-m);
  font-size: var(--font-size-base);
}

/* Score chips */
.chip {
  font-family: var(--font-m);
  font-size: var(--font-size-base);
  font-weight: 500;
  padding: var(--space-2) 7px;
  border-radius: var(--radius-pill);
}
.c-hi {
  background: var(--jade-pale);
  color: var(--jade);
}
.c-mid {
  background: var(--amber-pale);
  color: var(--amber);
}
.c-lo {
  background: var(--crimson-pale);
  color: var(--crimson);
}

/* Progress bar */
.prog {
  height: 3px;
  background: var(--surface-3);
  border-radius: var(--radius-pill);
  overflow: hidden;
}
.prog-fill {
  height: 100%;
  width: 100%;
  border-radius: var(--radius-pill);
  transform-origin: left;
  transform: scaleX(0);
  transition: transform 0.7s var(--ease-out);
}
@media (prefers-reduced-motion: reduce) {
  .prog-fill {
    transition-duration: 1ms;
  }
}

/* At-risk item */
.risk-item {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: var(--space-10) 0;
  border-bottom: 1px solid var(--border);
}
.risk-item:last-child {
  border-bottom: none;
}
.risk-av {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  flex-shrink: 0;
  background: var(--crimson-pale);
  border: 1px solid rgba(232, 99, 126, 0.25);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--crimson);
}
.risk-name {
  font-size: var(--font-size-md);
  font-weight: 500;
  color: var(--text);
  overflow-wrap: break-word;
}
.risk-detail {
  font-size: var(--font-size-sm);
  color: var(--text-3);
  margin-top: 1px;
  overflow-wrap: break-word;
}
.risk-score {
  font-family: var(--font-m);
  font-size: var(--font-size-md);
  font-weight: 500;
  color: var(--crimson);
  margin-left: auto;
}

/* Student progress keeps a fixed 0–100 scale, so a modest score cannot fill
   the chart simply because it is the highest score in a short sequence. */
.student-trend-summary {
  display: flex;
  align-items: baseline;
  gap: var(--space-10);
  margin-bottom: var(--space-16);
}
.student-trend-summary strong {
  color: var(--text);
  font-family: var(--font-m);
  font-size: var(--font-size-3xl);
  font-weight: 500;
}
.student-trend-summary span,
.student-trend-caption {
  color: var(--text-2);
  font-size: var(--font-size-sm);
}
.student-trend-chart {
  display: flex;
  gap: var(--space-8);
  height: 142px;
  padding-top: var(--space-4);
  border-bottom: 1px solid var(--border-card-h);
}
.student-trend-column {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.student-trend-track {
  width: 100%;
  max-width: 28px;
  flex: 1;
  display: flex;
  align-items: flex-end;
}
.student-trend-bar {
  width: 100%;
  min-height: 2px;
  border-radius: 5px 5px 0 0;
  transform-origin: bottom;
}
.student-trend-column > span {
  color: var(--text-3);
  font-family: var(--font-m);
  font-size: var(--font-size-xs);
  padding: var(--space-6) 0;
}
.student-trend-caption {
  margin-top: var(--space-10);
}

/* Status indicator */
.dot-status {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  margin-right: 5px;
  vertical-align: middle;
}

/* Tag */
.tag {
  font-size: var(--font-size-xs);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: var(--space-2) var(--space-6);
  border-radius: var(--radius-pill);
}
.tag-phy {
  background: var(--sky-pale);
  color: var(--sky);
}
.tag-acc {
  background: var(--violet-pale);
  color: var(--violet);
}

/* Motivational card */
.moto {
  background: linear-gradient(
    135deg,
    rgb(var(--accent-rgb) / 0.06) 0%,
    transparent 100%
  );
}
.moto-headline {
  font-family: var(--font-d);
  font-size: var(--font-size-3xl);
  font-weight: 600;
  margin-bottom: 5px;
}
.moto-headline em {
  font-style: italic;
  font-weight: 400;
}
.moto-body {
  font-size: var(--font-size-md);
  color: var(--text-3);
  line-height: 1.6;
}

/* Empty state */
/* Parent dashboard "At a glance" summary. */
.summary-text {
  margin: 0 0 var(--space-10);
  font-size: var(--font-size-lg);
  line-height: 1.55;
  color: var(--text);
}
/* Devanagari text: Mukta renders Nepali clearly; DM Sans has no Devanagari glyphs. */
.lang-ne {
  font-family: "Mukta", var(--font-b);
}

/* Shared layout helpers - use these instead of one-off inline styles. */
.section-gap {
  margin-bottom: var(--space-20);
}
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-10);
}
.card > .filter-bar {
  padding: 0 var(--space-16) var(--space-12);
}
.filter-search {
  flex: 1 1 240px;
  max-width: 420px;
}
.filter-select {
  width: auto;
  min-width: 140px;
  max-width: 220px;
  margin: 0;
}
.filter-select.wide {
  max-width: 320px;
}
.pager-row {
  padding: var(--space-10) 0;
}
.nowrap {
  white-space: nowrap;
}
.meter-row {
  display: flex;
  align-items: center;
  gap: var(--space-8);
}
/* Horizontal progress meter; the fill's width is the one inline value. */
.meter {
  height: 6px;
  min-width: 80px;
  border-radius: 3px;
  background: var(--border);
  overflow: hidden;
}
.meter > span {
  display: block;
  height: 100%;
  background: var(--accent);
}
.text-accent {
  color: var(--accent);
}

.empty {
  padding: 22px var(--space-18);
  color: var(--text-3);
  font-size: var(--font-size-md);
  text-align: center;
}

/* Skeleton */
.skel {
  background: linear-gradient(
    90deg,
    var(--surface-2) 25%,
    var(--surface-3) 50%,
    var(--surface-2) 75%
  );
  background-size: 200% 100%;
  animation: skel 1.4s infinite;
  border-radius: 1px;
}
@keyframes skel {
  from {
    background-position: 200% 0;
  }
  to {
    background-position: -200% 0;
  }
}

/* Connection status pill */
.conn-pill {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  font-size: var(--font-size-sm);
  color: var(--text-3);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  padding: 3px var(--space-10) 3px 7px;
  margin-bottom: 22px;
}
.conn-led {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--text-3);
  transition: background 0.3s;
}
.conn-led.ok {
  background: var(--jade);
  animation: pulse-led 2.5s linear infinite;
}
.conn-led.err {
  background: var(--crimson);
}
.conn-led.chk {
  animation: pulse-led 1s linear infinite;
}
@keyframes pulse-led {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.4;
  }
}

/* ── NEW COMPONENT STYLES ── */

/* Tabs */
.tabs {
  display: flex;
  gap: 0;
  border-bottom: 1px solid var(--border);
  margin-bottom: var(--space-20);
}
.tab {
  padding: var(--space-8) var(--space-16);
  cursor: pointer;
  font-size: var(--font-size-md);
  color: var(--text-3);
  border-bottom: 2px solid transparent;
  transition: all 0.15s;
  background: none;
  border-top: none;
  border-left: none;
  border-right: none;
  font-family: var(--font-b);
}
.tab:hover {
  color: var(--text-2);
}
.tab.act {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

/* Inputs */
.inp-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-14);
  margin-bottom: var(--space-14);
}
.inp-full {
  margin-bottom: var(--space-14);
}
.inp-lbl {
  display: block;
  font-size: var(--font-size-sm);
  font-weight: 500;
  color: var(--text-3);
  margin-bottom: var(--space-6);
}
.inp-hint {
  font-size: var(--font-size-2xs);
  color: var(--text-3);
  margin: var(--space-6) 0 0;
}
.inp {
  width: 100%;
  background: var(--surface-2);
  border: 1px solid var(--border);
  padding: 9px var(--space-12);
  font-family: var(--font-b);
  font-size: var(--font-size-md);
  color: var(--text);
  outline: none;
  transition:
    border-color 0.18s,
    box-shadow 0.18s;
  border-radius: var(--radius-sm);
}
.inp::placeholder {
  color: var(--text-3);
}
.inp:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgb(var(--accent-rgb) / 0.09);
}
textarea.inp {
  resize: vertical;
  min-height: 72px;
  line-height: 1.5;
}
select.inp {
  cursor: pointer;
  appearance: none;
}

/* Upload dropzone - dashed-card idiom generalized from the ad hoc inline
   version in QuestionStage.tsx's proof-of-work upload panel. */
.dropzone {
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  background: var(--surface-2);
  padding: var(--space-28) var(--space-20);
  text-align: center;
  cursor: pointer;
  transition:
    border-color 0.15s,
    background 0.15s;
}
.dropzone:hover {
  border-color: var(--border-h);
}
.dropzone.is-drag-over {
  border-color: var(--accent);
  background: var(--accent-pale);
}
.dropzone.is-disabled {
  cursor: not-allowed;
  opacity: 0.5;
}
.dropzone-icon {
  font-size: var(--font-size-3xl);
  margin-bottom: var(--space-6);
}
.dropzone-label {
  font-size: var(--font-size-base);
  color: var(--text-2);
}
.dropzone-browse {
  color: var(--accent);
  text-decoration: underline;
}
.dropzone-hint {
  font-size: var(--font-size-xs);
  color: var(--text-3);
  margin-top: var(--space-6);
}

/* Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-6);
  padding: var(--space-10) var(--space-20);
  font-family: var(--font-b);
  font-size: var(--font-size-md);
  font-weight: 600;
  cursor: pointer;
  border-radius: var(--radius-sm);
  transition: all 0.15s;
  border: 1px solid transparent;
}
.btn:active:not(:disabled) {
  transform: translateY(1px);
}
.btn-primary {
  background: var(--accent);
  color: var(--on-accent);
  box-shadow: 0 2px 12px rgb(var(--accent-rgb) / 0.38);
}
.btn-primary:hover:not(:disabled) {
  background: var(--accent-lt);
  box-shadow: 0 4px 18px rgb(var(--accent-rgb) / 0.5);
}
.btn-primary:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  box-shadow: none;
}
.btn-ghost {
  background: var(--surface);
  color: var(--text);
  border-color: var(--border-h);
}
.btn-ghost:hover:not(:disabled) {
  border-color: var(--accent);
  background: var(--accent-pale);
  color: var(--accent);
}
.btn-ghost:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.btn-danger {
  background: var(--crimson-pale);
  color: var(--crimson);
  border-color: rgba(232, 99, 126, 0.35);
}
.btn-sm {
  padding: var(--space-6) 13px;
  font-size: var(--font-size-base);
}

/* Back link */
.back-lnk {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: var(--font-size-md);
  color: var(--text-3);
  cursor: pointer;
  margin-bottom: var(--space-18);
  transition: color 0.15s;
  background: none;
  border: none;
  font-family: var(--font-b);
  padding: 0;
}
.back-lnk:hover {
  color: var(--text);
}

/* Checkboxes */
.cb-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.cb-row {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 7px var(--space-10);
  border: 1px solid var(--border);
  cursor: pointer;
  transition:
    border-color 0.15s,
    background 0.15s;
}
.cb-row:hover {
  border-color: var(--border-h);
}
.cb-row.checked {
  border-color: var(--accent);
  background: var(--accent-pale);
}
.cb {
  width: 14px;
  height: 14px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  appearance: none;
  cursor: pointer;
  border-radius: 4px; /* was 1px - a 14px checkbox reads better with a small
    fixed radius than --radius-sm (8px would round almost to a circle at
    this size); a deliberate one-off, same spirit as .mcq-opt-badge's fixed
    28px circle */
  flex-shrink: 0;
  transition: all 0.15s;
  position: relative;
}
.cb:checked {
  border-color: var(--accent);
  background: var(--accent);
}
.cb:checked::after {
  content: "✓";
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-size-2xs);
  color: var(--on-accent);
  font-weight: 700;
}
.cb-lbl {
  font-size: var(--font-size-md);
  color: var(--text-2);
}

/* Small radio dot - standalone extraction of .mcq-opt-badge's circle-fill
   idea for inline label/radio rows that don't want the full .mcq-opt
   option-card treatment. */
.rd {
  width: 14px;
  height: 14px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  appearance: none;
  cursor: pointer;
  border-radius: var(--radius-pill);
  flex-shrink: 0;
  transition: all 0.15s;
  position: relative;
}
.rd:checked {
  border-color: var(--accent);
}
.rd:checked::after {
  content: "";
  position: absolute;
  inset: 3px;
  border-radius: var(--radius-pill);
  background: var(--accent);
}

/* Filter pills */
.pills {
  display: flex;
  gap: var(--space-6);
  flex-wrap: wrap;
  margin-bottom: var(--space-14);
}
.pill {
  position: relative;
  padding: var(--space-4) 11px;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  font-size: var(--font-size-base);
  cursor: pointer;
  color: var(--text-3);
  transition:
    border-color 0.15s,
    color 0.15s;
  background: none;
  font-family: var(--font-b);
}
.pill:hover {
  border-color: var(--border-h);
  color: var(--text-2);
}
.pill.act {
  border-color: var(--accent);
  color: var(--accent);
}
.pill-active-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  background: var(--accent-pale);
}

/* Section eyebrow label grouping cards on a page (same look as the sidebar's
   .sb-section headings, so a "group" reads the same everywhere). */
.group-label {
  font-size: var(--font-size-xs);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-3);
  margin: var(--space-18) 0 var(--space-10);
}

/* Question card (assign test) */
.q-card {
  border: 1px solid var(--border-card);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 13px var(--space-14);
  cursor: pointer;
  transition:
    border-color 0.15s,
    background 0.15s;
  display: flex;
  gap: var(--space-12);
  align-items: flex-start;
}
.q-card:hover {
  border-color: var(--border-card-h);
  background: rgba(255, 255, 255, 0.02);
}
.q-card.sel {
  border-color: var(--accent);
  background: var(--accent-pale);
}
.q-card-chk {
  width: 16px;
  height: 16px;
  border: 1px solid var(--border);
  border-radius: 1px;
  flex-shrink: 0;
  margin-top: var(--space-2);
  transition: all 0.15s;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-size-xs);
  color: var(--on-accent);
  font-weight: 700;
}
.q-card.sel .q-card-chk {
  border-color: var(--accent);
  background: var(--accent);
}
.q-card-body {
  flex: 1;
  min-width: 0;
}
.q-card-text {
  font-size: var(--font-size-md);
  color: var(--text);
  line-height: 1.5;
  margin-bottom: var(--space-6);
}
.q-card-meta {
  display: flex;
  gap: var(--space-8);
  align-items: center;
  flex-wrap: wrap;
}

/* Steps indicator */
.steps {
  display: flex;
  align-items: center;
  margin-bottom: var(--space-24);
}
.step-item {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: var(--font-size-base);
  color: var(--text-3);
}
.step-item.cur {
  color: var(--text);
  font-weight: 500;
}
.step-item.done {
  color: var(--jade);
}
.step-n {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 1px solid currentColor;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-size-xs);
  font-weight: 600;
  flex-shrink: 0;
}
.step-sep {
  width: 28px;
  height: 1px;
  background: var(--border);
  margin: 0 var(--space-8);
}

/* Grading block */
.qa-block {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: var(--space-18) var(--space-20);
  margin-bottom: var(--space-14);
  box-shadow: var(--shadow);
}
.qa-qtext {
  font-size: var(--font-size-lg);
  font-weight: 500;
  color: var(--text);
  line-height: 1.5;
  margin-bottom: var(--space-10);
}
/* The student's answer is the focal element of the card. */
.qa-ans {
  font-size: var(--font-size-lg);
  color: var(--text);
  line-height: 1.65;
  padding: var(--space-12) var(--space-14);
  border: 1px solid var(--border);
  border-radius: 8px;
  margin-bottom: var(--space-14);
  background: var(--surface-2);
}
/* The AI is a quiet assistant: one recessed, neutral block (no competing accent borders).
   Status colour (jade/amber) is reserved for flagged answers, not for AI panels. */
.qa-ai {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: var(--space-12) var(--space-14);
  margin-bottom: var(--space-14);
}
.qa-ai-lbl {
  font-size: var(--font-size-xs);
  font-weight: 700;
  color: var(--text-3);
  margin-bottom: 5px;
}
.qa-ai-div {
  margin-top: var(--space-12);
  padding-top: var(--space-12);
  border-top: 1px solid var(--border);
}
.qa-ai-txt {
  font-size: var(--font-size-md);
  color: var(--text-2);
  line-height: 1.55;
}
.qa-ai-empty {
  color: var(--text-3);
  font-style: italic;
}
.qa-review-choice {
  border: 0;
  padding: 0;
  margin: 0 0 var(--space-16);
  min-width: 0;
}
.qa-review-choice legend {
  padding: 0 0 var(--space-8);
  font-size: var(--font-size-sm);
  font-weight: 700;
  color: var(--text);
}
.qa-review-options {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-10);
}
.qa-review-option {
  display: flex;
  align-items: flex-start;
  gap: var(--space-10);
  padding: var(--space-12);
  border: 1px solid var(--border-h);
  border-radius: 8px;
  cursor: pointer;
  background: var(--surface);
}
.qa-review-option.is-selected {
  border-color: var(--accent);
  background: var(--accent-pale);
}
.qa-review-option input {
  margin-top: 2px;
  flex-shrink: 0;
}
.qa-review-option strong,
.qa-review-option small {
  display: block;
}
.qa-review-option strong {
  font-size: var(--font-size-sm);
  color: var(--text);
}
.qa-review-option small {
  margin-top: 3px;
  font-size: var(--font-size-xs);
  color: var(--text-2);
}
.qa-review-hint {
  margin: var(--space-8) 0 0;
  font-size: var(--font-size-xs);
  color: var(--text-2);
}
@media (max-width: 640px) {
  .qa-review-options {
    grid-template-columns: 1fr;
  }
}
.due-date-exact {
  display: block;
  margin-top: 2px;
  font-size: var(--font-size-xs);
  color: var(--text-3);
}
.copilot-workflow-note {
  display: flex;
  gap: var(--space-4);
  flex-direction: column;
  margin: 0 0 var(--space-16);
  padding: var(--space-10) var(--space-14);
  border-left: 3px solid var(--accent);
  border-radius: 0 6px 6px 0;
  background: var(--accent-pale);
  color: var(--text-2);
  font-size: var(--font-size-sm);
  line-height: 1.45;
}
.copilot-workflow-note strong {
  color: var(--text);
}
.gh-attention {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  flex-wrap: wrap;
  margin-top: var(--space-8);
  font-size: var(--font-size-xs);
  color: var(--text-2);
}
.gh-attention button {
  min-height: 30px;
  padding: 4px 9px;
  border: 1px solid var(--amber);
  border-radius: 999px;
  background: var(--amber-pale);
  color: var(--text);
  font: inherit;
  cursor: pointer;
}
.gh-attention button:hover {
  border-color: var(--text);
}
.gh-next {
  margin-left: var(--space-10);
}

/* Auto-graded MCQs are deterministic, not a judgment call - collapsed into
   one summary the teacher can expand, so they don't compete for attention
   with constructed answers that actually need a decision. */
.qa-auto-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-10);
  width: 100%;
  border: 1px solid var(--border-h);
  border-radius: 8px;
  padding: 11px 15px;
  margin-bottom: var(--space-12);
  font-size: var(--font-size-sm);
  font-family: var(--font-b);
  color: var(--text-2);
  background: var(--surface);
  cursor: pointer;
  text-align: left;
}
.qa-auto-summary:hover {
  border-color: var(--border-accent);
}
/* Keyboard-focused constructed question (J/K shortcuts) */
.qa-focused {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.grade-shortcuts {
  position: sticky;
  bottom: 14px;
  margin: var(--space-18) 0 0 auto;
  width: fit-content;
  display: flex;
  gap: var(--space-14);
  flex-wrap: wrap;
  background: var(--surface);
  border: 1px solid var(--border-accent);
  border-radius: 8px;
  padding: var(--space-8) 13px;
  box-shadow: var(--shadow-lg);
  font-size: var(--font-size-2xs);
  color: var(--text-3);
  z-index: 30;
}
.grade-shortcuts kbd {
  font-family: var(--font-m);
  font-size: var(--font-size-3xs);
  background: var(--surface-3);
  color: var(--text-2);
  border-radius: 4px;
  padding: 1px 5px;
  margin-right: var(--space-4);
}

/* Grade Submissions - calm sticky command bar */
.grade-sticky {
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--surface);
  margin: 0 -28px var(--space-16);
  padding: var(--space-10) var(--space-28) var(--space-12);
  border-bottom: 1px solid var(--border);
  box-shadow: var(--shadow);
}
.gh-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-12);
  flex-wrap: wrap;
}
.gh-name {
  font-size: var(--font-size-2xl);
  font-weight: 600;
  color: var(--text);
  line-height: 1.15;
}
.gh-ctx {
  font-size: var(--font-size-base);
  color: var(--text-3);
  margin-top: var(--space-2);
}
.gh-actions {
  display: flex;
  gap: var(--space-8);
  flex-wrap: wrap;
}
.gh-summary {
  font-size: var(--font-size-base);
  color: var(--text-3);
  margin-top: var(--space-8);
}
.gh-summary strong {
  color: var(--text);
  font-weight: 600;
}
.gh-dot {
  margin: 0 var(--space-8);
  color: var(--border-h);
}
.gh-flag {
  color: var(--amber);
  font-weight: 600;
}
.gh-link {
  border: none;
  background: none;
  padding: 0;
  font: inherit;
  color: var(--accent);
  cursor: pointer;
}
.gh-link:hover {
  text-decoration: underline;
}
.gh-jump {
  display: flex;
  gap: var(--space-6);
  align-items: center;
  margin-top: 9px;
  overflow-x: auto;
  scrollbar-width: thin;
  padding-bottom: var(--space-2);
}
.gh-jump-lbl {
  font-size: var(--font-size-xs);
  color: var(--text-3);
  white-space: nowrap;
  margin-right: var(--space-2);
}
.gq-chip {
  flex: 0 0 auto;
  font-size: var(--font-size-sm);
  font-weight: 600;
  padding: 3px 9px;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text-3);
  cursor: pointer;
  transition:
    border-color 0.12s,
    color 0.12s;
}
.gq-chip:hover {
  border-color: var(--border-accent);
  color: var(--text);
}
.gq-chip.is-flagged {
  border-color: var(--amber);
  color: var(--amber);
  background: var(--amber-pale);
}
.reject-ai {
  font-size: var(--font-size-sm);
  color: var(--text-3);
  cursor: pointer;
  border: none;
  background: none;
  font-family: var(--font-b);
  padding: 0;
  transition: opacity 0.15s;
}
.reject-ai:hover {
  opacity: 0.7;
}
/* AI-output disclaimer — shown wherever AI-generated content is displayed.
       Intentionally muted/neutral (NOT amber/crimson, which are reserved for grading status). */
/* AI Tutor — a big, chat-first interface. The chat fills the page height and the
       PAGE ITSELF never scrolls (only the chat log scrolls internally), so there's no
       empty-page scrolling. On wide screens a right column adds progress + focus areas;
       on narrower screens (incl. laptops at 125–150% OS scaling) that column drops away
       and the chat takes the full width, STILL height-contained — never the old
       page-scrolling 60vh stack. We flex the .page for this view (data-view="s-plan") so
       the child's flex:1 height resolves reliably — a plain `height:100%` would not,
       because .page is a flex item whose computed height is `auto`. Tighter padding here
       (vs the default .page) buys the chat more height. */
.page[data-view="s-plan"] {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding: var(--space-18) 22px;
}
.tutor-view {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}
/* Compact header on this view so the chat gets the vertical space. */
.tutor-view .pg-header {
  margin-bottom: var(--space-12);
}
.tutor-grid {
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr) 288px;
  /* Pin the row to the container height (not content) so an overflowing chat
       log scrolls INTERNALLY instead of growing the grid and scrolling the page. */
  grid-template-rows: minmax(0, 1fr);
  gap: var(--space-16);
  align-items: stretch;
}
.tutor-chat {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
}
.tutor-log {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}
/* Right column: the two cards stacked; scrolls internally if taller than the view. */
.tutor-aside-col {
  display: flex;
  flex-direction: column;
  gap: var(--space-16);
  min-height: 0;
  overflow-y: auto;
}
.tutor-aside-col .card {
  flex: 0 0 auto;
}
/* Drag-to-resize handles (frontend/react/src/components/ResizeHandle.tsx). Each is its
   own explicit grid column/row-adjacent element (not an overlay), so it never disturbs
   the surrounding flex/grid flow - see AiTutor.tsx's gridTemplateColumns computation. */
.tutor-resize-x,
.tutor-resize-y {
  background: transparent;
  transition: background-color 0.12s ease;
}
.tutor-resize-x:hover,
.tutor-resize-x:active {
  background: var(--border-accent);
}
.tutor-resize-y {
  /* Width-resize is a desktop-only concept (see the 1100px block below); height-resize
     is mobile-only (see the 640px block) - hidden by default, shown there instead. */
  display: none;
  height: 6px;
  margin-top: var(--space-4);
  border-radius: 3px;
}
.tutor-resize-y:hover,
.tutor-resize-y:active {
  background: var(--border-accent);
}
/* Not enough width for a comfortable two-column chat (a 244px sidebar + 288px aside
       needs ~1100px of window): drop the aside and let the chat use the FULL width, still
       height-contained so the page never scrolls. Progress + focus areas stay available
       on the Dashboard / Performance pages. This is the case that laptops at 125–150% OS
       scaling land in, and it's now a big chat instead of a cramped scrolling stack. */
@media (max-width: 1100px) {
  .tutor-grid {
    grid-template-columns: minmax(0, 1fr);
  }
  .tutor-aside-col {
    display: none;
  }
  .tutor-resize-x {
    display: none;
  }
}
/* Genuinely small screens (real phones/tablets): stack and let the page scroll, with a
       tall chat rather than a stub. Height is student-adjustable (AiTutor.tsx's
       --tutor-chat-h custom property, dragged via .tutor-resize-y) - falls back to
       today's fixed 80vh if no preference has been set yet. */
@media (max-width: 640px) {
  .page[data-view="s-plan"] {
    display: block;
    overflow-y: auto;
  }
  .tutor-chat {
    height: var(--tutor-chat-h, 80vh);
  }
  .tutor-resize-y {
    display: block;
  }
}
/* Copy + thumbs feedback buttons under a tutor reply. */
.tutor-act {
  width: 27px;
  height: 24px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: transparent;
  color: var(--text-3);
  cursor: pointer;
  transition:
    border-color 0.15s,
    color 0.15s;
}
.tutor-act:hover {
  border-color: var(--border-accent);
  color: var(--accent);
}
.tutor-act.on-up {
  color: var(--jade);
  border-color: var(--jade);
}
.tutor-act.on-down {
  color: var(--crimson);
  border-color: var(--crimson);
}
.tutor-act svg {
  width: 13px;
  height: 13px;
}
/* Animated "thinking" dots shown while waiting on an AI response. */
@keyframes ai-bounce {
  0%,
  80%,
  100% {
    transform: translateY(0);
    opacity: 0.45;
  }
  40% {
    transform: translateY(-4px);
    opacity: 1;
  }
}
.ai-dots {
  display: inline-flex;
  gap: var(--space-4);
  align-items: center;
}
.ai-dots span {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
  animation: ai-bounce 1.2s infinite ease-in-out;
}
@media (prefers-reduced-motion: reduce) {
  .ai-dots span {
    animation: none;
    opacity: 1;
  }
}
.ai-dots span:nth-child(2) {
  animation-delay: 0.16s;
}
.ai-dots span:nth-child(3) {
  animation-delay: 0.32s;
}
/* Spinner for a button that's waiting on an AI response. */
@keyframes ai-spin {
  to {
    transform: rotate(360deg);
  }
}
.ai-note {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  font-size: var(--font-size-xs);
  color: var(--text-3);
  letter-spacing: 0.01em;
  margin-top: var(--space-8);
}
.ai-note-i {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 1px solid var(--border-h);
  font-size: var(--font-size-2xs);
  line-height: 1;
  flex-shrink: 0;
}
.warn-note {
  display: flex;
  align-items: flex-start;
  gap: 7px;
  font-size: var(--font-size-xs);
  font-weight: 700;
  line-height: 1.45;
  color: var(--crimson);
  background: var(--crimson-pale);
  border: 1px solid var(--crimson);
  border-radius: var(--radius);
  padding: var(--space-8) var(--space-10);
  margin: var(--space-10) 0;
}
.warn-note-i {
  flex-shrink: 0;
  font-size: var(--font-size-sm);
  line-height: 1.45;
}
.accept-ai {
  font-size: var(--font-size-sm);
  color: var(--accent);
  cursor: pointer;
  border: none;
  background: none;
  font-family: var(--font-b);
  padding: 0;
  transition: opacity 0.15s;
}
.accept-ai:hover {
  opacity: 0.7;
}

/* Take test */
.test-hd-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-14);
  flex-wrap: wrap;
  background: var(--surface);
  border: 1px solid var(--border-h);
  border-top: 3px solid var(--accent);
  padding: var(--space-16) var(--space-20);
  margin-bottom: var(--space-16);
  box-shadow: var(--shadow);
}
.test-hd-info {
  display: flex;
  align-items: center;
  gap: var(--space-12);
  min-width: 0;
}
.test-hd-icon {
  font-size: var(--font-size-3xl);
  line-height: 1;
  flex-shrink: 0;
}
.test-hd-text {
  min-width: 0;
}
.test-hd-name {
  font-family: var(--font-d);
  font-size: var(--font-size-2xl);
  font-weight: 600;
  line-height: 1.2;
}
.test-hd-meta {
  font-size: var(--font-size-sm);
  color: var(--text-3);
  margin-top: 3px;
}
.test-hd-side {
  display: flex;
  align-items: center;
  gap: var(--space-10);
  flex-shrink: 0;
}
.test-hd-pill {
  font-family: var(--font-m);
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--accent);
  background: var(--accent-pale);
  border: 1px solid var(--border-accent);
  border-radius: var(--radius-pill);
  padding: 7px var(--space-12);
  white-space: nowrap;
}
.test-nav-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-10);
  flex-wrap: wrap;
  background: var(--surface);
  border: 1px solid var(--border-h);
  padding: var(--space-12) var(--space-18);
  margin-top: var(--space-14);
  box-shadow: var(--shadow);
}
.test-nav-status {
  font-family: var(--font-m);
  font-size: var(--font-size-sm);
  color: var(--text-2);
  white-space: nowrap;
}
.test-prog {
  background: var(--surface);
  border: 1px solid var(--border-h);
  padding: var(--space-12) var(--space-18);
  margin-bottom: var(--space-20);
  display: flex;
  align-items: center;
  gap: var(--space-14);
  flex-wrap: wrap;
  box-shadow: var(--shadow);
}
.test-prog-lbl {
  font-size: var(--font-size-base);
  color: var(--text-3);
  white-space: nowrap;
}
.test-prog-bar {
  flex: 1;
  min-width: 120px;
  height: 7px;
  background: var(--surface-3);
  border-radius: 20px;
  overflow: hidden;
}
.test-prog-fill {
  height: 100%;
  /* Progress through the sitting isn't a correctness signal, so this stays
     on brand accent - --jade is reserved for "correct". */
  background: var(--accent);
  border-radius: 20px;
  transition: width 0.3s;
}
.test-prog-count {
  font-family: var(--font-m);
  font-size: var(--font-size-base);
  color: var(--text-2);
  white-space: nowrap;
}
.test-save-state {
  font-size: var(--font-size-sm);
  color: var(--text-3);
  white-space: nowrap;
}
.test-save-state.is-warn {
  color: var(--amber);
  white-space: normal;
}
.test-q-card {
  background: var(--surface);
  border: 1px solid var(--border-h);
  border-top: 3px solid var(--accent);
  padding: 22px var(--space-24);
  margin-bottom: var(--space-12);
  box-shadow: var(--shadow-lg);
}
.test-q-hd {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-12);
}
.test-q-num {
  font-family: var(--font-m);
  font-size: var(--font-size-sm);
  color: var(--text-3);
}
.test-q-marks {
  font-family: var(--font-m);
  font-size: var(--font-size-sm);
  color: var(--accent);
}
.test-q-text {
  font-size: var(--font-size-lg);
  color: var(--text);
  line-height: 1.6;
  margin-bottom: 5px;
  font-weight: 500;
}
.test-q-chapter {
  font-size: var(--font-size-sm);
  color: var(--text-3);
  margin-bottom: var(--space-14);
}
.char-count {
  font-size: var(--font-size-xs);
  color: var(--text-3);
  margin-top: var(--space-4);
  text-align: right;
  font-family: var(--font-m);
}
.proof-thumb .proof-del {
  position: absolute;
  top: -7px;
  right: -7px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 1px solid var(--border-h);
  background: var(--surface);
  color: var(--crimson);
  font-size: var(--font-size-lg);
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: var(--shadow-lg);
  padding: 0;
}
.proof-thumb .proof-del:hover {
  background: var(--crimson);
  color: #fff;
  border-color: var(--crimson);
}
/* Question navigator (test-taking): one chip per question encoding fill-state so the
   student can jump around and see what's answered / sheet-covered / still open. Brand
   accent = chrome/progress + current selection; the reserved green/amber/red are NOT
   used here — this is progress, not correctness. */
.qnav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
  margin-bottom: var(--space-14);
}
.qnav-chip {
  position: relative;
  min-width: 34px;
  height: 34px;
  padding: 0 var(--space-8);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  font-family: var(--font-m);
  font-size: var(--font-size-base);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text-3);
  cursor: pointer;
  transition:
    background 0.12s,
    border-color 0.12s,
    color 0.12s;
}
.qnav-chip:hover {
  border-color: var(--border-accent);
  color: var(--text);
}
.qnav-chip.is-done {
  border-color: var(--border-accent);
  background: var(--accent-pale);
  color: var(--accent);
}
.qnav-chip.is-sheet {
  border-style: dashed;
  color: var(--text-2);
}
.qnav-chip.is-current {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
  font-weight: 600;
}
.qnav-mk {
  font-size: var(--font-size-xs);
  line-height: 1;
}

/* Shown in place of an editor for a question whose answer is on a shared answer sheet. */
.covered-note {
  border: 1px dashed var(--border-h);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  padding: var(--space-14) var(--space-16);
  margin-top: var(--space-6);
}
.covered-hd {
  font-weight: 600;
  color: var(--text);
  margin-bottom: var(--space-4);
}
.covered-sub {
  font-size: var(--font-size-base);
  color: var(--text-3);
  line-height: 1.5;
  margin: 0 0 var(--space-6);
}

/* Start-test gate: a genuine caution notice (amber is appropriate here - it warns, it does
   not encode performance), shown once before the timed sitting begins. */
.start-warn {
  background: var(--amber-pale);
  border: 1px solid var(--amber);
  border-left-width: 3px;
  border-radius: 8px;
  padding: var(--space-12) var(--space-14);
  font-size: var(--font-size-md);
  color: var(--text);
  line-height: 1.6;
}

.mcq-opt {
  display: flex;
  align-items: center;
  gap: var(--space-12);
  padding: 11px var(--space-14);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: var(--font-size-md);
  color: var(--text-2);
  background: var(--surface);
  transition:
    border-color 0.15s,
    background 0.15s;
}
.mcq-opt:hover {
  border-color: var(--border-h);
  background: var(--surface-2);
}
.mcq-opt.sel {
  border-color: var(--accent);
  background: var(--accent-pale);
  color: var(--text);
}
/* Radio stays in the DOM for keyboard/screen-reader semantics; the badge
   below is the visible affordance, so the native control is clipped rather
   than removed. */
.mcq-opt input[type="radio"] {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
.mcq-opt-badge {
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border-h);
  border-radius: var(--radius-pill);
  font-family: var(--font-m);
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--text-3);
  background: var(--surface-2);
  transition:
    background 0.15s,
    border-color 0.15s,
    color 0.15s;
}
.mcq-opt.sel .mcq-opt-badge {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}
.mcq-opt-text {
  line-height: 1.4;
}

/* Performance bars */
.perf-row {
  display: flex;
  align-items: center;
  gap: var(--space-10);
  margin-bottom: 9px;
}
.perf-lbl {
  font-size: var(--font-size-md);
  color: var(--text-2);
  width: 150px;
  flex-shrink: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.perf-track {
  flex: 1;
  height: 5px;
  background: var(--surface-3);
  border-radius: 3px;
  overflow: hidden;
}
.perf-fill {
  height: 100%;
  width: 100%;
  border-radius: 3px;
  transform-origin: left;
  transform: scaleX(0);
  transition: transform 0.7s var(--ease-out);
}
@media (prefers-reduced-motion: reduce) {
  .perf-fill {
    transition-duration: 1ms;
  }
}
.perf-pct {
  font-family: var(--font-m);
  font-size: var(--font-size-sm);
  color: var(--text-3);
  width: 34px;
  text-align: right;
  flex-shrink: 0;
}

/* Toast */
.toast {
  position: fixed;
  bottom: 22px;
  right: 22px;
  background: var(--surface-3);
  border: 1px solid var(--border-h);
  padding: 11px var(--space-16);
  font-size: var(--font-size-md);
  color: var(--text);
  z-index: 2000;
  max-width: 300px;
  line-height: 1.4;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
  animation: toastIn 0.25s ease;
}
@keyframes toastIn {
  from {
    transform: translateY(8px);
    opacity: 0;
  }
  to {
    transform: translateY(0);
    opacity: 1;
  }
}
.toast.ok {
  border-color: rgba(61, 214, 140, 0.3);
}
.toast.err {
  border-color: rgba(232, 99, 126, 0.3);
  color: var(--crimson);
}
.toast.warn {
  border-color: rgb(var(--caution-rgb) / 0.3);
  color: var(--amber);
}
.toast.leaving {
  animation: toastOut 200ms ease forwards;
}
@keyframes toastOut {
  to {
    transform: translateY(8px);
    opacity: 0;
  }
}
@media (prefers-reduced-motion: reduce) {
  .toast {
    animation: toastInReduced 80ms ease;
  }
  .toast.leaving {
    animation: toastOutReduced 80ms ease forwards;
  }
}
@keyframes toastInReduced {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
@keyframes toastOutReduced {
  to {
    opacity: 0;
  }
}

/* Confirmation dialog */
.dialog-bg {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  z-index: 1500;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-24);
  opacity: 0;
  animation: dialogBgIn 220ms ease-out forwards;
}
@keyframes dialogBgIn {
  to {
    opacity: 1;
  }
}
.dialog {
  background: var(--surface);
  border: 1px solid var(--border-h);
  padding: var(--space-28) var(--space-32);
  max-width: 380px;
  width: 90%;
  max-height: 100%;
  overflow-y: auto;
  opacity: 0;
  transform: scale(0.96) translateY(8px);
  animation: dialogIn 240ms var(--ease-out) forwards;
}
@keyframes dialogIn {
  to {
    opacity: 1;
    transform: scale(1) translateY(0);
  }
}
@media (prefers-reduced-motion: reduce) {
  .dialog-bg,
  .dialog {
    animation-duration: 1ms;
    transform: none;
  }
}
.dialog-title {
  font-family: var(--font-d);
  font-size: var(--font-size-2xl);
  font-weight: 600;
  margin-bottom: var(--space-8);
}
.dialog-body {
  font-size: var(--font-size-md);
  color: var(--text-2);
  line-height: 1.6;
  margin-bottom: 22px;
}
.dialog-actions {
  display: flex;
  gap: var(--space-10);
  justify-content: flex-end;
}

/* ── PRINT (item 16) ── */
.print-hd {
  display: none;
}
@media print {
  .sidebar,
  .topbar,
  .back-lnk,
  .btn,
  .tabs,
  .mob-menu-btn,
  .no-print,
  #offline-banner,
  .conn-pill,
  .login-theme-btn,
  #login-page {
    display: none !important;
  }
  body {
    background: #fff !important;
    color: #111 !important;
  }
  [data-theme],
  [data-theme="dark"],
  [data-theme="light"] {
    --bg: #fff;
    --surface: #fff;
    --card-bg: #fff;
    --surface-2: #f5f5f5;
    --surface-3: #eee;
    --text: #111;
    --text-2: #333;
    --text-3: #555;
    --border: #ddd;
    --border-h: #bbb;
    --accent: #7a5410;
    --jade: #1a7a50;
    --crimson: #b02020;
    --accent-pale: #fdf3e0;
    --jade-pale: #e8f7f0;
    --crimson-pale: #fce8e8;
  }
  #app {
    display: flex !important;
  }
  .main {
    width: 100% !important;
  }
  .page {
    padding: 0 !important;
  }
  .card {
    border: 1px solid #ccc !important;
    box-shadow: none !important;
    page-break-inside: avoid;
  }
  .grid-2 {
    grid-template-columns: 1fr 1fr !important;
  }
  .perf-track {
    border: 1px solid #ccc !important;
  }
  .print-hd {
    display: block !important;
    border-bottom: 2px solid #111;
    padding-bottom: var(--space-14);
    margin-bottom: 22px;
  }
  .print-hd-title {
    font-family: var(--font-d);
    font-size: var(--font-size-3xl);
    font-weight: 700;
  }
  .print-hd-sub {
    font-size: var(--font-size-base);
    color: #555;
    margin-top: 3px;
  }
}

/* ── EXECUTIVE STUDENT REPORT ── */
.exec-hero {
  position: relative;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background:
    radial-gradient(
      ellipse 70% 120% at 88% -10%,
      var(--accent-pale) 0%,
      transparent 60%
    ),
    linear-gradient(135deg, var(--surface) 0%, var(--surface-2) 100%);
  padding: var(--space-24) 26px;
  margin-bottom: var(--space-16);
}
.exec-hero::after {
  content: "";
  position: absolute;
  right: -40px;
  top: -40px;
  width: 220px;
  height: 220px;
  background: radial-gradient(circle, var(--accent-pale) 0%, transparent 70%);
  pointer-events: none;
}
.exec-hero-grid {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--space-20);
  align-items: center;
  position: relative;
  z-index: 1;
}
.exec-name {
  font-family: var(--font-d);
  font-size: 34.5px;
  font-weight: 600;
  line-height: 1.04;
}
.exec-name em {
  font-style: italic;
  font-weight: 400;
}
.exec-meta {
  font-size: var(--font-size-md);
  color: var(--text-3);
  margin-top: 5px;
  display: flex;
  gap: var(--space-10);
  flex-wrap: wrap;
  align-items: center;
}
.exec-score-wrap {
  text-align: right;
}
.exec-score {
  font-family: var(--font-m);
  font-size: 58.5px;
  font-weight: 500;
  line-height: 0.9;
}
.exec-score-sub {
  font-size: var(--font-size-sm);
  color: var(--text-3);
  margin-top: var(--space-4);
}
.exec-chips {
  display: flex;
  gap: var(--space-8);
  margin-top: var(--space-14);
  flex-wrap: wrap;
}
.exec-chip {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: var(--space-8) 13px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  min-width: 84px;
}
.exec-chip-lbl {
  font-size: var(--font-size-2xs);
  color: var(--text-3);
}
.exec-chip-val {
  font-family: var(--font-m);
  font-size: var(--font-size-xl);
  font-weight: 500;
}
.at-risk-banner {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: var(--space-10) var(--space-14);
  margin-bottom: var(--space-16);
  background: var(--crimson-pale);
  border: 1px solid rgba(232, 99, 126, 0.28);
  border-radius: var(--radius-sm);
  font-size: var(--font-size-md);
  color: var(--crimson);
}
.at-risk-banner.decline-banner {
  background: var(--amber-pale);
  border-color: color-mix(in srgb, var(--amber) 30%, transparent);
  color: var(--amber);
}

/* Trend chart */
.trend-svg {
  width: 100%;
  height: auto;
  display: block;
}
.chart-tip {
  position: absolute;
  transform: translate(-50%, -130%);
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-sm);
  padding: var(--space-4) 9px;
  font-size: var(--font-size-xs);
  color: var(--text-1);
  white-space: nowrap;
  pointer-events: none;
  z-index: 1;
}
.trend-line {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
  stroke-dasharray: 1200;
  stroke-dashoffset: 1200;
  animation: trendDraw 0.65s var(--ease-out) forwards 0.15s;
}
@keyframes trendDraw {
  to {
    stroke-dashoffset: 0;
  }
}
.trend-area {
  opacity: 0;
  animation: trendAreaIn 0.5s var(--ease-out) forwards 0.4s;
}
@keyframes trendAreaIn {
  to {
    opacity: 1;
  }
}
@media (prefers-reduced-motion: reduce) {
  .trend-area {
    animation-duration: 1ms;
  }
}
.trend-dot {
  fill: var(--bg);
  stroke: var(--accent);
  stroke-width: 2;
  opacity: 0;
  animation: trendDot 0.3s var(--ease-out) forwards;
}
@keyframes trendDot {
  to {
    opacity: 1;
  }
}
.trend-avg {
  stroke: var(--text-3);
  stroke-width: 1;
  stroke-dasharray: 3 3;
  opacity: 0.6;
}
.trend-grid {
  stroke: var(--border);
  stroke-width: 1;
}
.trend-lbl {
  fill: var(--text-3);
  font-family: var(--font-m);
  font-size: var(--font-size-2xs);
}
/* Bar chart */
.bar-grow {
  transform: scaleY(0);
  animation: barGrow 0.6s var(--ease-out) forwards;
}
@keyframes barGrow {
  to {
    transform: scaleY(1);
  }
}
@media (prefers-reduced-motion: reduce) {
  .bar-grow {
    animation-duration: 1ms;
  }
}
/* Radar chart */
.radar-svg {
  width: 100%;
  max-width: 320px;
  height: auto;
  display: block;
  margin: 0 auto;
}

/* Failure (why) segmented bar */
.fail-bar {
  display: flex;
  height: 26px;
  border-radius: var(--radius-pill);
  overflow: hidden;
  border: 1px solid var(--border);
}
.fail-seg {
  height: 100%;
  transition: flex-grow 0.6s ease;
}
.fail-legend {
  display: flex;
  flex-direction: column;
  gap: 7px;
  margin-top: var(--space-14);
}
.fail-row {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: var(--font-size-md);
}
.fail-dot {
  width: 9px;
  height: 9px;
  border-radius: 2px;
  flex-shrink: 0;
}
.fail-cat {
  color: var(--text-2);
  flex: 1;
}
.fail-stat {
  font-family: var(--font-m);
  font-size: var(--font-size-base);
  color: var(--text-3);
}

/* ── USER MANAGEMENT GROUPING ── */
.um-badge {
  font-family: var(--font-m);
  font-size: var(--font-size-sm);
  color: var(--text-3);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  padding: 1px 9px;
}
.um-table th:last-child,
.um-table td:last-child {
  text-align: right;
}
/* Safety net: if a row ever has enough columns/content to exceed the page width
   (e.g. Subject + Department + Assigned Classes on the Teachers table), scroll
   horizontally within the table itself rather than forcing the whole page wider
   or squeezing every cell illegibly. */
.um-table {
  display: block;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.um-table thead,
.um-table tbody,
.um-table tr {
  width: 100%;
}
/* Foldable sections - only DeptCard's own drill-down and Admin Overview's
   "Teaching Staff" panel still use this (single-item disclosures, not
   grouping trees); every multi-level fold tree that used to live here was
   replaced by Card/List + filters (see project card/list-view rollout). */
.um-fold > summary {
  list-style: none;
  cursor: pointer;
  user-select: none;
}
.um-fold > summary::-webkit-details-marker {
  display: none;
}
.fold-chev {
  color: var(--text-3);
  flex-shrink: 0;
  transition: transform 0.18s ease;
}
.um-fold[open] > summary .fold-chev {
  transform: rotate(0deg);
}
.um-fold:not([open]) > summary .fold-chev {
  transform: rotate(-90deg);
}
.um-fold > summary:hover .fold-chev {
  color: var(--accent);
}
details.card > summary.card-hd {
  border-bottom: 1px solid var(--border);
}
details.card:not([open]) > summary.card-hd {
  border-bottom: none;
}
.um-fold[open] > summary ~ * {
  animation: umFoldContentIn 200ms var(--ease-out);
}
@keyframes umFoldContentIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
@media (prefers-reduced-motion: reduce) {
  .um-fold[open] > summary ~ * {
    animation-duration: 1ms;
  }
}

/* ── RESPONSIVE (item 15) ── */
.mob-menu-btn {
  display: none;
  background: none;
  border: 1px solid var(--border);
  width: 40px;
  height: 40px;
  border-radius: var(--radius-sm);
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: var(--text-3);
  transition:
    border-color 0.15s,
    color 0.15s;
}
.mob-menu-btn:hover {
  border-color: var(--border-accent);
  color: var(--accent);
}
.sidebar-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: 199;
}
.sidebar-overlay.on {
  display: block;
}

/* Narrow phones: the role tag is decoration; drop it so the page title fits. */
@media (max-width: 640px) {
  .subj-chip {
    display: none;
  }
}
/* Phone/tablet tab bar: the main destinations stay one tap away once the
   sidebar collapses into a drawer (same 900px breakpoint). */
.tabbar {
  display: none;
}
.tab {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  padding: var(--space-6) var(--space-2);
  border: none;
  background: none;
  color: var(--text-3);
  font-family: var(--font-b);
  font-size: var(--font-size-xs);
  letter-spacing: -0.01em;
  cursor: pointer;
}
.tab.act {
  color: var(--accent);
  font-weight: 600;
}
.tab-ico {
  position: relative;
  font-size: 20px;
  line-height: 0;
}
.tab-lbl {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tab-badge {
  position: absolute;
  top: -4px;
  right: -9px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 8px;
  background: var(--crimson);
  color: #fff;
  font-size: var(--font-size-xs);
  font-weight: 700;
  line-height: 16px;
  text-align: center;
}

@media (max-width: 900px) {
  .tabbar {
    display: flex;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 150;
    min-height: 58px;
    padding-bottom: env(safe-area-inset-bottom, 0px);
    background: var(--surface);
    border-top: 1px solid var(--border);
    box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.06);
  }
  .page {
    padding-bottom: calc(80px + env(safe-area-inset-bottom, 0px));
  }
  .sidebar {
    position: fixed;
    left: 0;
    transform: translateX(-100%);
    top: 0;
    bottom: 0;
    width: 280px;
    z-index: 200;
    /* Native shell (mobile/): keeps the drawer's own top item / bottom avatar-logout row
       clear of a notch/dynamic-island or home-indicator - see .topbar's own comment above
       for why env() here is a harmless 0 on a plain browser. */
    padding-top: env(safe-area-inset-top, 0px);
    padding-bottom: env(safe-area-inset-bottom, 0px);
    /* 100vh locks to the LARGEST possible viewport in mobile Safari (address
       bar + toolbar hidden), so when the toolbar is showing (the common
       case) the sidebar overflows below the visible screen — pushing the
       footer (avatar/logout) off-screen with no way to scroll it into view.
       100dvh tracks the actual visible viewport; 100vh stays as a fallback
       for browsers that don't support dvh.
       --font-zoom (services/fontScale.ts) compensates for a SEPARATE overflow
       source: CSS `zoom` (how the text-size setting is applied, default 112%)
       paints this fixed, viewport-anchored element `zoom` times taller than the
       real viewport, since vh/dvh resolve against the true un-zoomed viewport
       while zoom scales the painted result - dividing by --font-zoom here
       shrinks the LAYOUT height so painting it back up at `zoom` lands exactly
       on the real viewport height instead of overflowing past it. */
    height: calc(100vh / var(--font-zoom, 1));
    height: calc(100dvh / var(--font-zoom, 1));
    /* Fallback if .sb-nav's own internal scroll (min-height:0 above) ever fails to
       contain a long nav list again: scroll the whole drawer rather than silently
       clipping the footer (avatar/logout) off the bottom edge. */
    overflow-y: auto;
    transition: transform 0.25s var(--ease-out);
  }
  .sidebar.mob-open {
    transform: translateX(0);
  }
  .stats {
    grid-template-columns: repeat(2, 1fr) !important;
  }
  .grid-2 {
    grid-template-columns: 1fr !important;
  }
  /* Generate/Import (vAIQuestions) uses a hardcoded 340px-sidebar grid via
     inline style, not the .grid-2 class, so it doesn't collapse on its own -
     needs !important to beat the inline style's specificity. */
  #mode-topic,
  #mode-import {
    grid-template-columns: 1fr !important;
  }
  .inp-row {
    grid-template-columns: 1fr !important;
  }
  .col-r {
    flex-direction: row;
    flex-wrap: wrap;
  }
  .col-r > * {
    flex: 1 1 260px;
  }
  /* Create Test Step 2's unit-picker/question-list column + "Your selection"
     summary card (Step2Questions.tsx) is a hardcoded 2-column flex row via
     inline style, same situation as #mode-topic/#mode-import above - needs
     !important to beat the inline width/flex-shrink and stack instead of
     squeezing the main column into a barely-usable sliver next to a fixed
     210px sidebar. */
  .step2-layout {
    flex-direction: column !important;
    /* the inline alignItems:"flex-start" (meant to top-align the two side-by-
       side columns) also controls cross-axis sizing once stacked - without
       overriding it too, children shrink-to-fit their own unwrapped content
       width instead of stretching to the full column width, so text that
       should wrap (e.g. a unit name + banked-count badge) doesn't. */
    align-items: stretch !important;
  }
  .step2-sidebar {
    width: 100% !important;
  }
  .inbox-layout {
    height: calc(100dvh - 230px);
  }
  .inbox-list {
    width: 100%;
    border-right: none;
  }
  .inbox-layout .inbox-pane,
  .inbox-layout.has-sel .inbox-list {
    display: none;
  }
  .inbox-layout.has-sel .inbox-pane {
    display: flex;
  }
  .inbox-back {
    display: inline-block;
  }
}

@media (max-width: 400px) {
  .tb-right {
    gap: var(--space-4);
  }
  #cal-btn-lbl {
    display: none;
  }
  .cal-date-btn {
    width: 33px;
    padding: 0;
    gap: 0;
  }
}

@media (max-width: 600px) {
  /* Too narrow for the BS day, AD day and "off" tag on one line: stack them. */
  .cal-grid {
    gap: var(--space-4);
  }
  .cal-cell {
    padding: var(--space-4);
    min-height: 56px;
  }
  .cal-ad-day {
    display: block;
    margin-left: 0;
  }
  .cal-off-tag {
    position: static;
    display: block;
  }
  .cal-pop .cal-ad-day {
    display: inline;
    margin-left: 3px;
  }
  .copilot-flow {
    grid-template-columns: 1fr;
    gap: var(--space-6);
  }
  .teacher-dashboard-controls,
  .teacher-dashboard-controls .inp,
  .teacher-dashboard-controls .btn {
    width: 100%;
  }
  .teacher-support {
    padding: var(--space-18);
  }
  .teacher-support-row,
  .teacher-support-row-action {
    align-items: flex-start;
    flex-direction: column;
    gap: var(--space-8);
  }
  .teacher-support-row-action {
    width: 100%;
  }
  .teacher-support-row-action .btn {
    min-height: 44px;
  }
  .student-focus {
    padding: var(--space-20);
  }
  .student-focus-main {
    align-items: stretch;
    flex-direction: column;
    gap: var(--space-18);
  }
  .student-focus-action {
    width: 100%;
    min-height: 48px;
  }
  .student-focus-row {
    align-items: flex-start;
    flex-direction: column;
    gap: var(--space-10);
  }
  .student-focus-row .btn {
    min-height: 44px;
  }
  .student-focus-row-name,
  .student-focus-row-detail,
  .student-focus-empty-copy {
    font-size: var(--font-size-lg);
  }
  .page {
    /* Same landmine as .topbar below: a flat `padding: 14px` here used to override the base
       .page rule's max(26px, env(safe-area-inset-bottom)) bottom padding (same specificity,
       later in source order), losing home-indicator clearance on exactly the phone widths
       that have one. */
    /* Bottom clears the fixed .tabbar (shown at this width) plus the home indicator. */
    padding: var(--space-14) var(--space-14)
      calc(80px + env(safe-area-inset-bottom, 0px));
  }
  .stats {
    grid-template-columns: 1fr 1fr !important;
  }
  .stat-val {
    font-size: var(--font-size-3xl);
  }
  .pg-title {
    font-size: 28.5px;
  }
  .topbar {
    /* Same shorthand as the base .topbar rule above (env(safe-area-inset-top) etc) - just
       with a smaller 14px horizontal minimum for narrow phone widths. A plain `padding: 0
       14px` here used to win the cascade (same specificity, later in source order) and
       silently zero out the safe-area top padding on exactly the viewport widths real
       phones use - the topbar (hamburger, notifications, avatar) rendered flush under the
       real status bar, whose OS-reserved strip can swallow taps meant for those buttons. */
    padding: env(safe-area-inset-top, 0px)
      max(var(--space-14), env(safe-area-inset-right, 0px)) 0
      max(var(--space-14), env(safe-area-inset-left, 0px));
  }
  .bc-dim,
  .bc-sep {
    display: none;
  }
  .dialog {
    padding: var(--space-20);
  }

  /* Tables with a trailing action button (e.g. "Take Test") don't fit a phone
     width and used to overflow the page sideways with no scroll affordance —
     stack each row into a card instead. */
  table.rtable-actions thead {
    display: none;
  }
  table.rtable-actions,
  table.rtable-actions tbody,
  table.rtable-actions tr {
    display: block;
    width: 100%;
  }
  table.rtable-actions tr {
    padding: var(--space-12) 0;
    border-bottom: 1px solid var(--border);
  }
  table.rtable-actions tr:last-child {
    border-bottom: none;
  }
  table.rtable-actions td {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-10);
    padding: 3px 0;
    border-bottom: none;
  }
  table.rtable-actions td.empty {
    display: block;
  }
  table.rtable-actions td.td-name {
    font-size: var(--font-size-lg);
    padding-bottom: var(--space-6);
  }
  table.rtable-actions td[data-label]::before {
    content: attr(data-label);
    font-size: var(--font-size-xs);
    font-weight: 500;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-3);
  }
  table.rtable-actions td.rtable-action {
    padding-top: var(--space-8);
  }
  table.rtable-actions td.rtable-action > button,
  table.rtable-actions td.rtable-action > .btn {
    width: 100%;
  }

  /* Compact variant for long lists (students, classes, results): one line per
     row - the name, the cells marked .m-key, and the action. Everything else
     is on the detail view the row/action opens. */
  .filter-bar > .filter-select {
    flex: 1 1 140px;
    max-width: none;
  }
  table.rtable-compact tr {
    display: flex;
    align-items: center;
    gap: var(--space-10);
    padding: var(--space-10) var(--space-14);
  }
  table.rtable-compact td {
    display: none;
  }
  table.rtable-compact td.td-name,
  table.rtable-compact td.m-key,
  table.rtable-compact td.rtable-action,
  table.rtable-compact td.empty {
    display: flex;
    padding: 0;
  }
  table.rtable-compact td.td-name {
    flex: 1;
    min-width: 0;
    font-size: var(--font-size-md);
  }
  table.rtable-compact td.empty,
  table.rtable-compact td.m-full {
    flex: 1;
    display: block;
    padding: 0;
  }
  table.rtable-compact td.m-key::before {
    content: none;
  }
  table.rtable-compact td.rtable-action > button,
  table.rtable-compact td.rtable-action > .btn {
    width: auto;
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   Student constructed-response answer editor (vendored EasyMDE) - only
   QuestionStage.tsx's own hand-rolled integration still uses this; every
   other markdown-capable text box in the app is RichTextEditor.tsx (a plain
   textarea + Preview toggle, styled further down, no CodeMirror involved).
   The toolbar/editor are re-themed to the app tokens (works in dark & light),
   and since FontAwesome is intentionally NOT loaded (self-contained), each
   toolbar button gets a text glyph via ::after.
   ═══════════════════════════════════════════════════════════════════════════ */
.EasyMDEContainer {
  background: var(--surface);
}
.EasyMDEContainer .CodeMirror {
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-top: none;
  border-radius: 0 0 var(--radius) var(--radius);
  font-family: var(--font-b);
  font-size: var(--font-size-md);
  line-height: 1.6;
}
.EasyMDEContainer .CodeMirror-cursor {
  border-color: var(--accent);
}
.EasyMDEContainer .CodeMirror-selected {
  background: rgba(var(--accent-rgb) / 0.18) !important;
}
.EasyMDEContainer .CodeMirror-placeholder {
  color: var(--text-3);
}
.editor-toolbar {
  background: var(--surface-3);
  border: 1px solid var(--border);
  border-radius: var(--radius) var(--radius) 0 0;
  opacity: 1;
}
.editor-toolbar button {
  color: var(--text-2) !important;
  border: 1px solid transparent;
  border-radius: 4px;
}
.editor-toolbar button:hover {
  background: var(--accent-pale);
  border-color: var(--border-accent);
}
.editor-toolbar button.active {
  background: var(--accent-pale);
  color: var(--accent) !important;
}
.editor-toolbar i.separator {
  border-left: 1px solid var(--border);
  border-right: none;
}
.editor-toolbar button i {
  display: none;
} /* hide empty FA placeholders */
.editor-toolbar button::after {
  font-family: var(--font-b);
  font-size: var(--font-size-base);
  font-weight: 600;
  line-height: 1;
}
.editor-toolbar button.bold::after {
  content: "B";
  font-weight: 800;
}
.editor-toolbar button.italic::after {
  content: "I";
  font-style: italic;
}
.editor-toolbar button.heading::after {
  content: "H";
}
.editor-toolbar button.unordered-list::after {
  content: "•—";
  letter-spacing: -1px;
}
.editor-toolbar button.ordered-list::after {
  content: "1.";
}
.editor-toolbar button.code::after {
  content: "</>";
  font-family: var(--font-m);
  font-size: var(--font-size-xs);
}
.editor-toolbar button.quote::after {
  content: "\201C";
}
.editor-toolbar button.preview::after {
  content: "Preview";
  font-size: var(--font-size-sm);
  font-weight: 500;
}
.editor-toolbar button.guide::after {
  content: "?";
}
.editor-preview,
.editor-preview-side {
  background: var(--surface-2);
  color: var(--text);
  font-family: var(--font-b);
  font-size: var(--font-size-md);
  line-height: 1.6;
}

/* Rendered markdown (EasyMDE preview + teacher review + student result detail) */
.md-body {
  display: block;
}
.md-body > :first-child,
.editor-preview > :first-child {
  margin-top: 0;
}
.md-body > :last-child,
.editor-preview > :last-child {
  margin-bottom: 0;
}
.md-body p,
.editor-preview p {
  margin: 0 0 var(--space-8);
}
.md-body ul,
.editor-preview ul {
  list-style: disc;
  padding-left: 1.4em;
  margin: var(--space-6) 0;
}
.md-body ol,
.editor-preview ol {
  list-style: decimal;
  padding-left: 1.5em;
  margin: var(--space-6) 0;
}
.md-body li,
.editor-preview li {
  margin: var(--space-2) 0;
}
.md-body strong,
.editor-preview strong {
  font-weight: 700;
  color: var(--text);
}
.md-body em,
.editor-preview em {
  font-style: italic;
}
.md-body code,
.editor-preview code {
  font-family: var(--font-m);
  font-size: 0.92em;
  background: var(--surface-3);
  padding: 1px 5px;
  border-radius: 4px;
}
.md-body pre,
.editor-preview pre {
  background: var(--surface-3);
  padding: var(--space-10) var(--space-12);
  border-radius: 6px;
  overflow: auto;
}
.md-body pre code,
.editor-preview pre code {
  background: none;
  padding: 0;
}
/* KaTeX overflow guard - a long derivation/fraction chain can render wider than
   a phone screen. Without this the equation forces .page's own overflow-x:auto
   to kick in, letting the WHOLE PAGE scroll sideways to reach it (invisible,
   undiscoverable - the same failure mode as an unreachable action button).
   Scroll the equation within its own box instead; block math ($$...$$, inside
   .katex-display) keeps its normal centered layout, only the outer wrapper
   scrolls. Unscoped (not just .md-body) since math renders on many surfaces:
   question cards, test-taking, results, tutor chat, MCQ options via mdInline. */
/* Vendored katex.min.css hides the accessible MathML copy of every equation via
   `.katex-mathml { position: absolute; clip: rect(1px,1px,1px,1px); ... }` but never
   gives `.katex` itself `position: relative` - so with no positioned ancestor nearby,
   that 1x1px box positions against whatever ancestor IS positioned (e.g. the sticky
   .topbar/.sidebar), which can land it far down the document. It's visually invisible
   either way, but its offset still counts toward that ancestor's scrollHeight - on a
   fixed-height shell (#app: height:100vh, only .page/.tutor-log scroll internally)
   this silently grows #app's scrollHeight past the viewport, making the WHOLE PAGE
   scroll to reveal blank space, even though nothing is visibly wrong. Scoping the
   absolute position to .katex itself keeps it fully local and inert. */
.katex {
  position: relative;
}
.katex-display {
  overflow-x: auto;
  overflow-y: hidden;
  padding: var(--space-2) 0;
}
.katex:not(.katex-display .katex) {
  display: inline-block;
  max-width: 100%;
  overflow-x: auto;
  overflow-y: hidden;
  vertical-align: middle;
}
.md-body h1,
.md-body h2,
.md-body h3,
.editor-preview h1,
.editor-preview h2,
.editor-preview h3 {
  font-family: var(--font-d);
  margin: var(--space-10) 0 var(--space-6);
  line-height: 1.25;
}
.md-body h1,
.editor-preview h1 {
  font-size: 1.3em;
}
.md-body h2,
.editor-preview h2 {
  font-size: 1.18em;
}
.md-body h3,
.editor-preview h3 {
  font-size: 1.06em;
}
.md-body blockquote,
.editor-preview blockquote {
  border-left: 3px solid var(--border-accent);
  margin: var(--space-6) 0;
  padding: var(--space-2) 0 var(--space-2) var(--space-12);
  color: var(--text-2);
}
.md-body a,
.editor-preview a {
  color: var(--accent);
}

/* ── Gamification (student rewards) — brand emerald + categorical only; locked items
   are muted grey, NEVER the reserved green/amber/red (those stay for performance). ── */
.xp-ring {
  --p: 0;
  width: 72px;
  height: 72px;
  border-radius: 50%;
  flex: none;
  background: conic-gradient(
    var(--accent) calc(var(--p) * 1%),
    rgb(var(--line-rgb) / 0.25) 0
  );
  display: grid;
  place-items: center;
}
.xp-ring > span {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: var(--surface);
  display: grid;
  place-items: center;
  font-weight: 700;
  font-size: var(--font-size-xl);
  color: var(--text);
  line-height: 1;
}
.xp-ring small {
  display: block;
  font-size: var(--font-size-3xs);
  font-weight: 600;
  letter-spacing: 0.1em;
  color: var(--text-3);
}

.badge-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: var(--space-12);
}
.badge-tile {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: var(--space-14) var(--space-12);
  text-align: center;
  background: var(--surface);
}
.badge-tile.earned {
  border-color: rgb(var(--accent-rgb) / 0.4);
  background: var(--accent-pale);
}
.badge-tile.badge-new {
  box-shadow: 0 0 0 2px var(--accent);
}
.badge-tile.locked {
  opacity: 0.6;
  filter: grayscale(0.6);
}
.badge-ico {
  font-size: 31.5px;
  line-height: 1;
}
.badge-name {
  font-weight: 600;
  font-size: var(--font-size-base);
  margin-top: 7px;
}
.badge-desc {
  font-size: var(--font-size-sm);
  color: var(--text-3);
  margin-top: 3px;
  line-height: 1.4;
}
.badge-when {
  font-size: var(--font-size-xs);
  color: var(--accent);
  margin-top: var(--space-6);
  font-weight: 600;
}

.cos-grid {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-8);
}
.cos-tile {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: var(--space-6) var(--space-10);
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  cursor: pointer;
  font-size: var(--font-size-base);
  color: var(--text-2);
}
.cos-tile.sel {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--accent-pale);
}
.cos-tile.locked {
  opacity: 0.55;
  cursor: not-allowed;
}
.cos-name {
  white-space: nowrap;
}
.cos-swatch {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  flex: none;
}
.cos-none {
  background: rgb(var(--line-rgb) / 0.5);
}
.cos-bronze {
  background: #cd7f32;
}
.cos-silver {
  background: #b8c0c8;
}
.cos-gold {
  background: #e3b341;
}
.cos-comeback {
  background: var(--sky);
}
.cos-scholar {
  background: var(--violet);
}

/* ═══════════════════════════════════════════════════════════════════════════
   RichTextEditor.tsx - the shared markdown text box (teacher feedback,
   question authoring, test instructions, announcements, LLM prompts): a
   plain textarea + an on-demand Preview toggle, not a live-highlighting
   editor. See the component's own doc comment for why.
   ═══════════════════════════════════════════════════════════════════════════ */
.rte-toolbar {
  display: flex;
  justify-content: flex-end;
  margin-bottom: var(--space-6);
}
.rte-preview {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-10);
  box-sizing: border-box;
  overflow-y: auto;
}

/* ═══════════════════════════════════════════════════════════════════════════
   Conversation reports: the Report link on a thread (ReportConversationDialog.tsx)
   and the leadership review queue (ConversationReviews.tsx).
   ═══════════════════════════════════════════════════════════════════════════ */
.thread-report {
  display: flex;
  justify-content: flex-end;
  margin-bottom: var(--space-6);
}
.thread-report .btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-6);
}
.report-filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-10);
  align-items: center;
  margin-bottom: var(--space-14);
}
.report-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-10);
}
.report-card {
  padding: var(--space-12) var(--space-14);
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}
.report-hd {
  display: flex;
  justify-content: space-between;
  gap: var(--space-10);
  flex-wrap: wrap;
}
.report-cat {
  font-size: var(--font-size-xs);
  color: var(--amber);
  margin-top: var(--space-2);
}
.report-cat.urgent {
  color: var(--crimson);
  font-weight: 600;
}
.report-note {
  font-size: var(--font-size-sm);
  font-style: italic;
}
.report-snapshot {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  max-height: 280px;
  overflow-y: auto;
  padding: var(--space-8) var(--space-10);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.report-msg {
  font-size: var(--font-size-sm);
  line-height: 1.5;
  white-space: pre-wrap;
}
.report-actions {
  display: flex;
  gap: var(--space-8);
  align-items: center;
  flex-wrap: wrap;
}
.report-actions .inp {
  flex: 1;
  min-width: 180px;
  margin: 0;
}

/* ═══════════════════════════════════════════════════════════════════════════
   Mindfulness break (MindfulBreak.tsx): a full-screen breathing pause. The orb
   grows on the in-breath and shrinks on the out-breath, over --breath-dur.
   ═══════════════════════════════════════════════════════════════════════════ */
.mindful-bg {
  position: fixed;
  inset: 0;
  z-index: 2000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-16);
  background: color-mix(in srgb, var(--bg) 92%, transparent);
  backdrop-filter: blur(6px);
}
.mindful-box {
  width: 100%;
  max-width: 420px;
  text-align: center;
  outline: none;
}
.mindful-title {
  font-size: var(--font-size-lg);
  font-weight: 600;
  margin: 0 0 var(--space-16);
}
.mindful-sub {
  color: var(--text-3);
  font-size: var(--font-size-sm);
  margin: 0 0 var(--space-12);
}
.mindful-stage {
  position: relative;
  width: 220px;
  height: 220px;
  margin: 0 auto var(--space-16);
}
.mindful-ring {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  transform: rotate(-90deg);
}
.mindful-ring-track,
.mindful-ring-fill {
  fill: none;
  stroke-width: 3;
}
.mindful-ring-track {
  stroke: var(--border);
}
.mindful-ring-fill {
  stroke: var(--accent);
  stroke-linecap: round;
  transition: stroke-dashoffset 250ms linear;
}
.mindful-orb {
  position: absolute;
  inset: 30px;
  border-radius: 50%;
  background: radial-gradient(circle, var(--accent-pale), var(--accent));
  opacity: 0.85;
  transition:
    transform var(--breath-dur) ease-in-out,
    opacity var(--breath-dur) ease-in-out;
}
.mindful-orb.out {
  transform: scale(0.55);
  opacity: 0.55;
}
.mindful-orb.in {
  transform: scale(1);
}
.mindful-cue {
  font-size: var(--font-size-lg);
  margin: 0 0 var(--space-16);
}
.mindful-cue-ne {
  display: block;
  font-size: var(--font-size-md);
  color: var(--text-3);
  margin-top: var(--space-4);
}
.mindful-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-8);
}
.mindful-moods {
  display: flex;
  justify-content: center;
  gap: var(--space-12);
  margin-bottom: var(--space-16);
}
.mindful-mood {
  font-size: 28px;
  line-height: 1;
  padding: var(--space-8);
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface-2);
  cursor: pointer;
}
.mindful-mood.act {
  border-color: var(--border-accent);
  background: var(--accent-pale);
}
@media (prefers-reduced-motion: reduce) {
  .mindful-orb,
  .mindful-orb.in,
  .mindful-orb.out {
    transform: none;
  }
  .mindful-orb.out {
    opacity: 0.35;
  }
}

/* Early-warning marker next to a score: recent results well below earlier ones. */
.decline-mark {
  margin-left: var(--space-6);
  font-size: var(--font-size-2xs);
  font-weight: 600;
  color: var(--amber);
  white-space: nowrap;
}

/* Saved grading comments under a feedback box (SavedComments.tsx). */
.saved-comments {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-8);
  margin-top: var(--space-8);
}
.saved-comments .filter-select {
  flex: 1;
  min-width: 200px;
}
.saved-comments-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  max-height: 50vh;
  overflow-y: auto;
  margin-bottom: var(--space-12);
}
.saved-comments-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-10);
  padding: var(--space-8) var(--space-10);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: var(--font-size-sm);
  white-space: pre-wrap;
}

/* Content Studio: one paper, grouped by main question. */
.cs-paper-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  padding: 10px 14px;
  margin-bottom: 12px;
}
.cs-paper-bar > span {
  font-family: var(--font-m);
  font-weight: 600;
  margin-right: auto;
}
.cs-main-q {
  border-left: 3px solid var(--border);
  padding-left: 12px;
  margin-bottom: 18px;
}
.cs-main-q-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 8px;
}
.cs-main-q-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.cs-main-q-meta {
  margin-left: 8px;
  font-size: var(--font-size-xs);
  color: var(--text-3);
}

/* Content Studio: Ready-made Papers (PaperSets.tsx) */
.cs-page-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
}
.cs-paper-set {
  margin-bottom: 12px;
}
.cs-paper-set-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.cs-paper-set-items {
  margin: 8px 0;
  font-family: var(--font-m);
  font-size: var(--font-size-2xs);
  color: var(--text-3);
  overflow-wrap: anywhere;
}
.cs-paper-set-notes {
  margin: 0 0 8px;
  font-size: var(--font-size-xs);
  color: var(--amber);
  white-space: pre-line;
}
.cs-paper-set-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.cs-paper-preview {
  margin-bottom: 16px;
}
.cs-paper-preview-title {
  margin: 20px 0 10px;
  font-size: var(--font-size-sm);
  color: var(--text-2);
}

/* Test builder: "From model papers" (PaperPicker.tsx) */
.paper-picker {
  padding: 0;
  margin-bottom: 16px;
  overflow: hidden;
  border: 1px solid var(--border-accent);
}
.paper-picker-toggle {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 11px 16px;
  border: none;
  background: var(--accent-pale);
  color: var(--accent);
  font-family: var(--font-b);
  font-size: var(--font-size-sm);
  font-weight: 600;
  letter-spacing: 0.02em;
  text-align: left;
  cursor: pointer;
}
.paper-picker-body {
  padding: 14px 16px;
  border-top: 1px solid var(--border);
}
.paper-picker-note {
  margin: 0;
  font-size: var(--font-size-2xs);
  color: var(--text-3);
}
.paper-picker-src + .paper-picker-src {
  margin-top: 16px;
}
.paper-picker-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
}
.paper-picker-row {
  display: grid;
  grid-template-columns: 44px 120px minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  padding: 6px 0;
  border-top: 1px solid var(--border);
}
.paper-picker-set {
  grid-template-columns: minmax(0, 1fr) auto auto;
}
.paper-picker-set-name {
  font-weight: 600;
  overflow-wrap: anywhere;
}
.paper-picker-num {
  font-family: var(--font-m);
  font-weight: 600;
}
.paper-picker-marks {
  font-size: var(--font-size-xs);
  color: var(--text-2);
}
@media (max-width: 600px) {
  .paper-picker-row {
    grid-template-columns: 44px minmax(0, 1fr) auto;
  }
  .paper-picker-marks {
    display: none;
  }
}

/* A main question's shared stem and figure (components/Stimulus.tsx) */
.q-stimulus {
  margin: 0 0 var(--space-10);
  padding: var(--space-10) var(--space-14);
  border-left: 3px solid var(--border);
  background: var(--surface-2);
  border-radius: var(--radius-sm);
}
.q-stimulus-text {
  color: var(--text);
  line-height: 1.55;
}
/* Figures are drawn dark-on-white; keep the white in dark mode so lines stay visible. */
.q-stimulus-figure {
  display: block;
  max-width: min(100%, 520px);
  height: auto;
  margin-top: var(--space-10);
  padding: 8px;
  background: #fff;
  border-radius: var(--radius-sm);
}
.q-stimulus-figure-loading {
  width: min(100%, 320px);
  height: 160px;
  margin-top: var(--space-10);
  border-radius: var(--radius-sm);
  background: var(--border);
}
.q-stimulus-missing {
  margin: var(--space-10) 0 0;
  font-size: var(--font-size-xs);
  color: var(--text-3);
}
.cs-stimulus-note {
  margin: 0 0 14px;
  font-size: var(--font-size-sm);
  color: var(--text-2);
}
.cs-stimulus-remove {
  display: block;
  margin-top: 10px;
  font-size: var(--font-size-sm);
}
/* Content Studio question-paper import */
.spec-grid {
  font-size: var(--font-size-xs);
  color: var(--text-2);
}
.spec-grid-source {
  margin: 0 0 6px;
  color: var(--text-3);
}
.spec-grid-table {
  width: 100%;
  margin-bottom: 8px;
}
.spec-grid-hint {
  margin: 0 0 8px;
  color: var(--text-3);
}
.spec-grid-notes {
  margin: 0;
  padding-left: 18px;
}
.paper-import-hint {
  margin: 4px 0 0;
  font-size: var(--font-size-xs);
  color: var(--text-3);
}
span.paper-import-hint {
  margin: 0;
}
.paper-import-title {
  margin: 0 0 8px;
  font-weight: 600;
}
.paper-import-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
  margin: 14px 0 8px;
}
.paper-import-code {
  font-size: var(--font-size-2xs);
  color: var(--text-3);
}
.paper-import-notes {
  margin: 0;
  max-width: 420px;
  font-size: var(--font-size-xs);
  color: var(--amber);
  white-space: pre-line;
}
.paper-import-recent {
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--font-size-sm);
  color: var(--text-2);
}
.q-card-stem {
  margin-bottom: 4px;
  font-size: var(--font-size-xs);
  color: var(--text-3);
}

/* TakeTest: a main question's written parts on one page (QuestionStage mainParts) */
.main-q-parts {
  list-style: none;
  margin: 0 0 var(--space-10);
  padding: 0;
}
.main-q-parts li {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 8px;
  align-items: baseline;
  padding: 6px 0;
  border-bottom: 1px solid var(--border);
}
.main-q-parts li:last-child {
  border-bottom: 0;
}
.main-q-tag {
  font-weight: 600;
  color: var(--text-2);
}
.main-q-parts .test-q-text {
  margin: 0;
}
.main-q-marks {
  font-size: var(--font-size-xs);
  color: var(--text-3);
  white-space: nowrap;
}

.answer-shown-at {
  margin: 6px 0 10px;
  font-size: var(--font-size-sm);
  font-style: italic;
  color: var(--text-3);
}
