/* ============ SAT Practice Hub · "Focused Modern" system (hybrid direction) ============
   Design reference: DESIGN_RESEARCH.md · semantic tokens; restyle via :root + [data-theme] */
@font-face { font-family: "Inter"; font-style: normal; font-weight: 400; font-display: swap; src: url("../vendor/fonts/Inter-400.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: normal; font-weight: 500; font-display: swap; src: url("../vendor/fonts/Inter-500.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: normal; font-weight: 600; font-display: swap; src: url("../vendor/fonts/Inter-600.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: normal; font-weight: 700; font-display: swap; src: url("../vendor/fonts/Inter-700.woff2") format("woff2"); }
@font-face { font-family: "Source Serif 4"; font-style: normal; font-weight: 400; font-display: swap; src: url("../vendor/fonts/SourceSerif4-400.woff2") format("woff2"); }
@font-face { font-family: "Source Serif 4"; font-style: normal; font-weight: 600; font-display: swap; src: url("../vendor/fonts/SourceSerif4-600.woff2") format("woff2"); }
@font-face { font-family: "Source Serif 4"; font-style: normal; font-weight: 700; font-display: swap; src: url("../vendor/fonts/SourceSerif4-700.woff2") format("woff2"); }

/* Cross-document view transitions. The landing page opts in as well, so picking a
   hub (and navigating back) crossfades instead of hard-cutting. Deliberately left
   unstyled: the browser default is the same crossfade the app already gets from
   document.startViewTransition() for its own view changes. */
@view-transition { navigation: auto; }

:root {
  /* Follow the OS scheme for native UI (scrollbars, form controls, canvas)
     until a theme is pinned with [data-theme] by the pre-paint script. */
  color-scheme: light dark;

  --font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-serif: "Source Serif 4", "Iowan Old Style", Georgia, "Times New Roman", serif;
  --font-mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, "Liberation Mono", monospace;

  /* Type ramp: 12 / 14 / 16 / 20 / 24 / 28 + fluid display steps */
  --text-xs: 0.75rem;
  --text-sm: 0.875rem;
  --text-md: 1rem;
  --text-lg: 1.25rem;
  --text-xl: 1.5rem;
  --text-2xl: 1.75rem;
  --fs-hero: clamp(2.5rem, 2.1rem + 1.6vw, 3rem);
  --fs-display: clamp(2.25rem, 1.85rem + 1.6vw, 2.75rem);

  /* Spacing (4/8pt grid) */
  --space-1: 0.25rem; --space-2: 0.5rem; --space-3: 0.75rem; --space-4: 1rem;
  --space-5: 1.25rem; --space-6: 1.5rem; --space-8: 2rem; --space-10: 3rem; --space-12: 4rem;

  /* Surfaces */
  --bg: #fafafa;
  --bg-subtle: #f4f4f5;
  --card: #ffffff;
  --card-elevated: #ffffff;

  /* Ink */
  --text: #09090b;
  --text-secondary: #27272a;
  --muted: #52525b;
  /* Darkened from #71717a: muted text must clear 4.5:1 on tinted surfaces. */
  --muted-light: #62626b;

  /* Lines */
  --line: #e4e4e7;
  --line-strong: #d4d4d8;
  --line-subtle: #f4f4f5;
  /* Form controls (text fields, checkboxes) need a visible boundary where the
     decorative line tokens compute to ~1.2:1. ~5:1 on white. */
  --line-control: #62626b;

  /* Accent (Focused Modern indigo) */
  --accent: #4f46e5;
  --accent-hover: #4338ca;
  --accent-active: #3730a3;
  --accent-soft: #eef2ff;
  --accent-soft-hover: #e0e7ff;
  --accent-ring: #4f46e5; /* solid: focus/state indicators must pass 3:1 (WCAG 1.4.11) */

  /* Status (text-safe on white) */
  --good: #15803d;
  --good-hover: #166534;
  --good-soft: #f0fdf4;
  --good-border: #bbf7d0;
  --good-ring: #15803d;

  --bad: #dc2626;
  --bad-hover: #b91c1c;
  --bad-soft: #fef2f2;
  --bad-border: #fecaca;
  --bad-ring: #dc2626;

  --warn: #b45309;
  --warn-hover: #92400e;
  --warn-soft: #fffbeb;
  --warn-border: #fde68a;
  --warn-ring: #b45309;

  /* Domains */
  --rw: #7c3aed;
  --rw-soft: #f5f3ff;
  --rw-border: #ddd6fe;

  --math: #0f766e;
  --math-soft: #f0fdfa;
  --math-border: #99f6e4;

  /* Elevation */
  --shadow-xs: 0 1px 2px 0 rgba(24, 24, 27, 0.05);
  --shadow-sm: 0 1px 3px 0 rgba(24, 24, 27, 0.08), 0 1px 2px -1px rgba(24, 24, 27, 0.05);
  --shadow: 0 4px 6px -1px rgba(24, 24, 27, 0.06), 0 2px 4px -2px rgba(24, 24, 27, 0.04);
  --shadow-md: 0 10px 15px -3px rgba(24, 24, 27, 0.07), 0 4px 6px -4px rgba(24, 24, 27, 0.04);
  --shadow-lg: 0 20px 25px -5px rgba(24, 24, 27, 0.08), 0 8px 10px -6px rgba(24, 24, 27, 0.03);

  /* Geometry */
  --radius-sm: 8px;
  --radius: 12px;
  --radius-lg: 16px;
  --radius-full: 9999px;

  /* Motion */
  --ease: cubic-bezier(0.2, 0, 0, 1);
  --ease-enter: cubic-bezier(0.05, 0.7, 0.1, 1);
  --ease-exit: cubic-bezier(0.3, 0, 0.8, 0.15);
  --dur-fast: 0.12s;
  --dur-normal: 0.2s;
  /* --dur-slow is the calculator's scale. It was referenced by the panel
     open/close, the button entrance, and the #view padding shift, but never
     declared here, so every one of those transitions resolved to an invalid
     value and animated at 0s. The generated landing page has always declared
     its own copy; this is the hub's. */
  --dur-slow: 0.36s;
}
[data-theme="dark"] {
  color-scheme: dark;

  /* Sibling system: no pure black/white, desaturated accent, elevation via surface tone */
  color-scheme: dark;

  --bg: #0f0f12;
  --bg-subtle: #1c1c22;
  --card: #16161a;
  --card-elevated: #1e1e24;

  --text: #f4f4f5;
  --text-secondary: #d4d4d8;
  --muted: #a1a1aa;
  --muted-light: #8e8e99;

  --line: #26262b;
  --line-strong: #3f3f46;
  --line-subtle: #1c1c22;
  --line-control: #8e8e99; /* ~5.6:1 against the dark card, where --line is ~1.2:1 */

  --accent: #818cf8;
  --accent-hover: #a5b4fc;
  --accent-active: #6366f1;
  --accent-soft: rgba(129, 140, 248, 0.15);
  --accent-soft-hover: rgba(129, 140, 248, 0.22);
  --accent-ring: #a5b4fc;

  --good: #4ade80;
  --good-hover: #86efac;
  --good-soft: rgba(74, 222, 128, 0.12);
  --good-border: rgba(74, 222, 128, 0.4);
  --good-ring: #4ade80;

  --bad: #f87171;
  --bad-hover: #fca5a5;
  --bad-soft: rgba(248, 113, 113, 0.12);
  --bad-border: rgba(248, 113, 113, 0.4);
  --bad-ring: #f87171;

  --warn: #fbbf24;
  --warn-hover: #fcd34d;
  --warn-soft: rgba(251, 191, 36, 0.12);
  --warn-border: rgba(251, 191, 36, 0.4);
  --warn-ring: #fbbf24;

  --rw: #a78bfa;
  --rw-soft: rgba(124, 58, 237, 0.18);
  --rw-border: rgba(167, 139, 250, 0.4);

  --math: #2dd4bf;
  --math-soft: rgba(13, 148, 136, 0.16);
  --math-border: rgba(45, 212, 191, 0.4);

  --shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.35);
  --shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.45), 0 1px 2px -1px rgba(0, 0, 0, 0.35);
  --shadow: 0 4px 8px -1px rgba(0, 0, 0, 0.45), 0 2px 4px -2px rgba(0, 0, 0, 0.3);
  --shadow-md: 0 10px 18px -3px rgba(0, 0, 0, 0.5), 0 4px 8px -4px rgba(0, 0, 0, 0.35);
  --shadow-lg: 0 20px 28px -5px rgba(0, 0, 0, 0.55), 0 8px 12px -6px rgba(0, 0, 0, 0.4);
}

/* System-preference default: the same dark tokens apply before JS has pinned a
   theme, so the pre-JS paint follows the OS setting. A pinned light theme wins
   via the :not guard, and a pinned dark theme matches the identical
   [data-theme="dark"] block above. Only the custom properties are duplicated
   here; the component-level dark rules stay in [data-theme="dark"]. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0f0f12;
    --bg-subtle: #1c1c22;
    --card: #16161a;
    --card-elevated: #1e1e24;

    --text: #f4f4f5;
    --text-secondary: #d4d4d8;
    --muted: #a1a1aa;
    --muted-light: #8e8e99;

    --line: #26262b;
    --line-strong: #3f3f46;
    --line-subtle: #1c1c22;
    --line-control: #8e8e99;

    --accent: #818cf8;
    --accent-hover: #a5b4fc;
    --accent-active: #6366f1;
    --accent-soft: rgba(129, 140, 248, 0.15);
    --accent-soft-hover: rgba(129, 140, 248, 0.22);
    --accent-ring: #a5b4fc;

    --good: #4ade80;
    --good-hover: #86efac;
    --good-soft: rgba(74, 222, 128, 0.12);
    --good-border: rgba(74, 222, 128, 0.4);
    --good-ring: #4ade80;

    --bad: #f87171;
    --bad-hover: #fca5a5;
    --bad-soft: rgba(248, 113, 113, 0.12);
    --bad-border: rgba(248, 113, 113, 0.4);
    --bad-ring: #f87171;

    --warn: #fbbf24;
    --warn-hover: #fcd34d;
    --warn-soft: rgba(251, 191, 36, 0.12);
    --warn-border: rgba(251, 191, 36, 0.4);
    --warn-ring: #fbbf24;

    --rw: #a78bfa;
    --rw-soft: rgba(124, 58, 237, 0.18);
    --rw-border: rgba(167, 139, 250, 0.4);

    --math: #2dd4bf;
    --math-soft: rgba(13, 148, 136, 0.16);
    --math-border: rgba(45, 212, 191, 0.4);

    --shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.35);
    --shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.45), 0 1px 2px -1px rgba(0, 0, 0, 0.35);
    --shadow: 0 4px 8px -1px rgba(0, 0, 0, 0.45), 0 2px 4px -2px rgba(0, 0, 0, 0.3);
    --shadow-md: 0 10px 18px -3px rgba(0, 0, 0, 0.5), 0 4px 8px -4px rgba(0, 0, 0, 0.35);
    --shadow-lg: 0 20px 28px -5px rgba(0, 0, 0, 0.55), 0 8px 12px -6px rgba(0, 0, 0, 0.4);
  }
}

/* Dark theme: bright fills need dark foregrounds for contrast */
[data-theme="dark"] .btn.primary,
[data-theme="dark"] .btn.good,
[data-theme="dark"] .pill.on,
[data-theme="dark"] .opt.sel .letter,
[data-theme="dark"] .opt.good .letter,
[data-theme="dark"] .opt.bad .letter,
[data-theme="dark"] .opt.gone .letter {
  color: #0f0f12;
}
[data-theme="dark"] ::selection { color: #0f0f12; }

*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  background: var(--bg);
  color: var(--text);
  font: 1rem/1.6 var(--font-sans);
  /* Fallback metric stabilization: while the Inter faces swap in, the fallback
     is scaled to the primary font's x-height so text does not reflow. */
  font-size-adjust: from-font;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  transition: background-color var(--dur-normal) var(--ease), color var(--dur-normal) var(--ease);
}

::selection { background: var(--accent); color: #ffffff; }

/* Themed scrollbars: standard properties (Chrome 121+, Firefox 64+, Safari 18.2+).
   var() works here and follows [data-theme] automatically. */
* { scrollbar-width: thin; scrollbar-color: var(--line-strong) transparent; }
/* Legacy engines only (Safari pre-26.2, Chrome pre-121): literal colors because var()
   is unreliable inside webkit pseudo-elements; gated so modern engines keep native
   overlay scrollbars instead of being forced into classic mode. */
@supports not (scrollbar-color: auto) {
  ::-webkit-scrollbar { width: 10px; height: 10px; }
  ::-webkit-scrollbar-track { background: transparent; }
  ::-webkit-scrollbar-thumb {
    background: #d4d4d8; border-radius: 8px;
    border: 2px solid transparent; background-clip: padding-box;
  }
  ::-webkit-scrollbar-thumb:hover { background: #a1a1aa; }
  ::-webkit-scrollbar-corner { background: transparent; }
  [data-theme="dark"] ::-webkit-scrollbar-thumb { background: #3f3f46; }
  [data-theme="dark"] ::-webkit-scrollbar-thumb:hover { background: #52525b; }
}

/* The page's own scrollbar stays invisible (the page still scrolls with the
   wheel/keyboard); inner scroll areas keep the themed thin bar above. */
html, body { scrollbar-width: none; }
html::-webkit-scrollbar, body::-webkit-scrollbar { width: 0; height: 0; }

:where(a, button, input, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ---------- touch behaviour ----------
   manipulation drops the 300ms double-tap-zoom delay on every control, and
   the tap highlight is set deliberately (rather than left to the UA's grey
   flash) so it matches the accent. Without the first, a phone student tapping
   through a 40-question block pays the delay on every option. */
:where(a, button, [role="button"], summary, label[for]) {
  touch-action: manipulation;
  -webkit-tap-highlight-color: color-mix(in srgb, var(--accent) 18%, transparent);
}
:where(a, button, [role="button"]):active {
  -webkit-tap-highlight-color: color-mix(in srgb, var(--accent) 30%, transparent);
}

/* Headings are short and centered on nothing, so balance costs nothing and
   stops a one-word second line on narrow screens. */
h1, h2, h3, .prose h1, .prose h2, .prose h3, .bigscore .v, .big-score {
  text-wrap: balance;
}
/* Long body copy reads better ragged-right than greedily justified. */
.sub, .lede, .prose p, .prose li, .next-why, .stem, .rationale, .stimulus {
  text-wrap: pretty;
}

/* Inline stroke icons (Lucide-style; injected by app.js or static in the HTML) */
.ic { display: inline-block; line-height: 0; flex: 0 0 auto; }
.ic svg {
  width: 100%; height: 100%; display: block;
  fill: none; stroke: currentColor; stroke-width: 2.4;
  stroke-linecap: round; stroke-linejoin: round;
}

/* Shared entrance + feedback motion */
@keyframes rise {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}
@keyframes shake-x {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-4px); }
  40% { transform: translateX(4px); }
  60% { transform: translateX(-3px); }
  80% { transform: translateX(2px); }
}

/* Route changes get a quick crossfade where the View Transitions API exists */
::view-transition-old(root),
::view-transition-new(root) { animation-duration: 0.2s; }

/* ---------- top bar ---------- */
.topbar {
  position: sticky;
  top: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 12px 24px;
  background: var(--card);
  border-bottom: 1px solid var(--line);
  box-shadow: var(--shadow-xs);
  transition: background-color var(--dur-normal) var(--ease), border-color var(--dur-normal) var(--ease);
}
.brand {
  font-weight: 700;
  font-size: var(--text-md);
  color: var(--text);
  text-decoration: none;
  white-space: nowrap;
  letter-spacing: -0.2px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  transition: opacity var(--dur-fast) var(--ease);
}
.brand:hover { opacity: 0.85; }
.brand span { letter-spacing: -0.2px; }

.top-title {
  font-weight: 600;
  font-size: var(--text-sm);
  color: var(--text);
  margin-right: auto;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

#nav {
  display: flex;
  gap: 6px;
  flex: 1;
  flex-wrap: wrap;
  align-items: center;
}
#nav a {
  color: var(--muted);
  text-decoration: none;
  font-weight: 600;
  font-size: var(--text-sm);
  padding: 7px 14px;
  border-radius: var(--radius-sm);
  position: relative;
  transition: color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}
#nav a:hover { color: var(--text); background: var(--accent-soft); }
#nav a.active { color: var(--accent); background: var(--accent-soft); font-weight: 700; }
#nav .badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  background: var(--bad);
  color: #fff;
  border-radius: var(--radius-full);
  font-size: var(--text-xs);
  padding: 0 5px;
  margin-left: 6px;
  font-weight: 700;
  line-height: 1;
  box-shadow: 0 1px 2px rgba(220, 38, 38, 0.35);
}

.iconbtn {
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--text);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  padding: 6px 11px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background-color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease),
    color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.iconbtn:hover { background: var(--accent-soft); border-color: var(--accent); }
.iconbtn:active { transform: scale(0.97); }
.iconbtn.tgl { width: 40px; height: 40px; padding: 0; }
.iconbtn.tgl .ic { width: 18px; height: 18px; }
.iconbtn.tgl .ic svg { stroke-width: 2; }
.iconbtn.tgl .ic-sun { display: none; }
[data-theme="dark"] .iconbtn.tgl .ic-sun { display: inline-block; }
[data-theme="dark"] .iconbtn.tgl .ic-moon { display: none; }

/* ---------- layout ---------- */
#view {
  flex: 1;
  /* Flex items floor at their min-content width. A guide holds long code paths
     and wide tables, so without this the whole page scrolls sideways on a phone. */
  min-width: 0;
  width: 100%;
  max-width: 1120px;
  margin: 0 auto;
  padding: 28px 24px 72px;
}
#view.view-wide { max-width: 1440px; }   /* guides: wider reading shell on desktop */
.foot {
  text-align: center;
  color: var(--muted);
  font-size: var(--text-xs);
  padding: 24px 16px 32px;
  border-top: 1px solid var(--line);
  background: var(--card);
  margin-top: auto;
}
.loading {
  text-align: center;
  color: var(--muted);
  padding: 80px 0;
  font-weight: 600;
  font-size: var(--text-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
}

.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 20px;
  margin-bottom: 16px;
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}

h1 {
  font-size: var(--text-2xl);
  margin: 0 0 6px;
  letter-spacing: -0.4px;
  font-weight: 800;
  color: var(--text);
  text-wrap: balance;
}
h2 {
  font-size: var(--text-lg);
  margin: 0 0 14px;
  letter-spacing: -0.2px;
  font-weight: 700;
  color: var(--text);
  text-wrap: balance;
}
.sub {
  color: var(--muted);
  font-size: var(--text-sm);
  line-height: 1.55;
  margin: 0 0 20px;
}

.row { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.spread { justify-content: space-between; }
.grow { flex: 1; }

/* Small-caps section label used across dashboard cards */
.label {
  display: block;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 10px;
}

/* ---------- buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  justify-content: center;
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--text);
  font: 600 var(--text-sm)/1.2 var(--font-sans);
  padding: 10px 18px;
  border-radius: 10px;
  cursor: pointer;
  text-decoration: none;
  box-shadow: var(--shadow-xs);
  transition: background-color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease),
    color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
  user-select: none;
  -webkit-user-select: none;
}
.btn:hover {
  background: var(--bg-subtle);
  border-color: var(--line-strong);
  box-shadow: var(--shadow-sm);
}
.btn:active { transform: scale(0.98); box-shadow: var(--shadow-xs); }
/* Focus rings are real outlines everywhere: box-shadow rings are erased in
   forced-colors mode (along with borders-as-shadows), leaving keyboard users
   with no indicator. */
.btn:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 2px; }

.btn.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #ffffff;
  box-shadow: 0 2px 4px rgba(79, 70, 229, 0.25);
}
.btn.primary:hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
  box-shadow: 0 4px 8px rgba(79, 70, 229, 0.35);
}
.btn.primary:active { background: var(--accent-active); border-color: var(--accent-active); }

.btn.good {
  background: var(--good);
  border-color: var(--good);
  color: #ffffff;
  box-shadow: 0 2px 4px rgba(21, 128, 61, 0.25);
}
.btn.good:hover {
  background: var(--good-hover);
  border-color: var(--good-hover);
  box-shadow: 0 4px 8px rgba(21, 128, 61, 0.35);
}

.btn.small { padding: 6px 13px; font-size: var(--text-xs); border-radius: var(--radius-sm); }
.btn .ic { width: 14px; height: 14px; }
.btn.small .ic { width: 13px; height: 13px; }
.btn.ghost {
  border-color: transparent;
  background: transparent;
  color: var(--muted);
  box-shadow: none;
}
.btn.ghost:hover {
  color: var(--text);
  background: var(--accent-soft);
  border-color: transparent;
  box-shadow: none;
}
.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
  box-shadow: none;
  transform: none;
}
.btn.danger { color: var(--bad); border-color: var(--bad-border); }
.btn.danger:hover { background: var(--bad-soft); border-color: var(--bad); }

.btn-ghost {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 7px 14px;
  border-radius: var(--radius-sm);
  color: var(--muted);
  font-size: var(--text-sm);
  font-weight: 600;
  text-decoration: none;
  background: transparent;
  border: 1px solid var(--line);
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease),
    background-color var(--dur-fast) var(--ease);
}
.btn-ghost:hover { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }

/* ---------- chips & level tags ---------- */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--text-xs);
  font-weight: 700;
  padding: 4px 11px;
  border-radius: var(--radius-full);
  background: var(--accent-soft);
  color: var(--accent);
  white-space: nowrap;
  letter-spacing: 0.2px;
  border: 1px solid transparent;
  transition: background-color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease),
    color var(--dur-fast) var(--ease);
}
.chip.rw { background: var(--rw-soft); color: var(--rw); border-color: var(--rw-border); }
.chip.math { background: var(--math-soft); color: var(--math); border-color: var(--math-border); }
.chip.band { background: var(--warn-soft); color: var(--warn); border-color: var(--warn-border); }
.chip.dim { background: var(--bg-subtle); color: var(--muted); border-color: var(--line); }
.chip.skill { background: var(--bg-subtle); color: var(--text-secondary); border-color: var(--line); font-weight: 600; }
.chip.notechip { background: var(--warn-soft); color: var(--warn); border-color: var(--warn-border); cursor: help; }
.chip.goal { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }

.lvl {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.6875rem;
  font-weight: 700;
  padding: 3px 10px;
  border-radius: var(--radius-full);
  border: 1px solid var(--line);
  color: var(--muted);
  white-space: nowrap;
  letter-spacing: 0.02em;
}
.lvl.lvl-strong { color: var(--good); background: var(--good-soft); border-color: var(--good-border); }
.lvl.lvl-mid { color: var(--warn); background: var(--warn-soft); border-color: var(--warn-border); }
.lvl.lvl-early { color: var(--muted); background: var(--bg-subtle); border-color: var(--line); }
.lvl.lvl-none { color: var(--muted-light); background: transparent; border-color: var(--line); }

/* ---------- question palette (grid + legend) ---------- */
.palette-wrap {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--line-subtle);
}
.palette { display: flex; flex-wrap: wrap; gap: 8px; }
.palette-btn,
.opt-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: inherit;
  cursor: pointer;
  user-select: none;
  transition: background-color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease),
    color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.palette-btn {
  width: 34px;
  height: 34px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--muted);
  font-weight: 700;
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
}
.palette-btn:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.palette-btn:active { transform: scale(0.94); }
.palette-btn.active {
  border: 2px solid var(--accent);
  color: var(--accent);
  background: var(--accent-soft);
  font-weight: 800;
}
.palette-btn.answered { background: var(--good-soft); border-color: var(--good); color: var(--good); }
.palette-btn.flagged { border-color: var(--warn); background: var(--warn-soft); color: var(--warn); box-shadow: inset 0 -3px 0 var(--warn); }
.palette-btn.omitted { opacity: 0.6; border-style: dashed; }
.palette-btn:focus-visible,
.opt-btn:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 2px; }

.legend {
  display: flex;
  gap: 18px;
  flex-wrap: wrap;
  margin-top: 12px;
  font-size: var(--text-xs);
  color: var(--muted);
}
.legend i { display: inline-block; width: 11px; height: 11px; border-radius: 3px; margin-right: 6px; }
.leg-ans { background: var(--good); }
.leg-flag { background: var(--warn); }
.leg-cur { background: var(--accent-soft); border: 2px solid var(--accent); width: 9px; height: 9px; }
.leg-todo { background: var(--card); border: 1px solid var(--line-strong); }

/* ---------- dashboard (hybrid layout) ---------- */
.dash-hero { display: grid; grid-template-columns: 1.15fr 0.8fr 1.35fr; gap: 16px; margin-bottom: 16px; }
.dash-hero .card, .dash-mid .card { margin-bottom: 0; }
/* uniform heights: shorter cards pin their last block to the bottom so the
   leftover space spreads between sections instead of pooling at the bottom */
.dash-hero .card { display: flex; flex-direction: column; }
.dash-hero .card > :last-child { margin-top: auto; }
.score-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.big-score {
  font-size: var(--fs-hero); font-weight: 800; line-height: 1;
  letter-spacing: -1px; font-variant-numeric: tabular-nums;
}
.delta { font-size: var(--text-sm); color: var(--good); font-weight: 600; margin-top: 12px; }
.streak-line { font-size: var(--text-md); font-weight: 700; margin-bottom: 12px; }
.ring-row { display: flex; align-items: center; gap: 16px; }
.ring { line-height: 0; flex: 0 0 auto; }
.ring-num { font-size: var(--text-lg); font-weight: 800; font-variant-numeric: tabular-nums; }
.ring-num.done { color: var(--good); }
.ring-cap { font-size: var(--text-xs); color: var(--muted); margin-top: 2px; }
.weekchart { display: flex; gap: 8px; margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--line-subtle); }
.wcol { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 5px; }
.wslot { height: 56px; width: 100%; display: flex; align-items: flex-end; justify-content: center; }
.wbar { width: 72%; max-width: 22px; border-radius: 4px; background: var(--accent); opacity: 0.4; }
.wbar.today { opacity: 1; }
.wl { font-size: 0.625rem; color: var(--muted-light); }
.wl.today { color: var(--accent); font-weight: 700; }
.weekmeta { margin: 12px 0 0; padding-top: 10px; border-top: 1px solid var(--line-subtle); }
.next-title { font-size: var(--text-lg); font-weight: 700; letter-spacing: -0.2px; margin: 2px 0 6px; }
.next-why { font-size: var(--text-sm); color: var(--muted); line-height: 1.55; margin: 0 0 14px; }

.dash-mid { display: grid; grid-template-columns: 1fr; gap: 16px; margin-bottom: 16px; }
.mrow {
  display: grid;
  /* fixed widths for count + actions so rows with a Misses button
     and rows without share the exact same column edges */
  grid-template-columns: minmax(190px, 1.3fr) auto minmax(130px, 1.1fr) 118px 232px;
  gap: 12px; align-items: center;
  padding: 8px 0;
  border-bottom: 1px solid var(--line-subtle);
}
.mrow:last-child { border-bottom: 0; }
.mname { font-size: var(--text-sm); font-weight: 500; min-width: 0; }
.mname strong { font-weight: 800; }
.mbar { display: block; }
.mcnt {
  font-size: var(--text-xs); color: var(--muted); text-align: right;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.macts { display: flex; gap: 6px; flex-wrap: nowrap; justify-content: flex-end; }

/* trend chart and recent blocks: two islands side by side */
.dash-trendrow { display: grid; grid-template-columns: 1.55fr 1fr; gap: 16px; }
.recent-card { display: flex; flex-direction: column; }
.recent-foot {
  margin-top: auto; padding-top: 12px; border-top: 1px solid var(--line-subtle);
  font-size: var(--text-xs); color: var(--muted);
}
.trendbox { display: block; position: relative; }
.trend { width: 100%; height: auto; display: block; }
.t-goalline { stroke: var(--accent); stroke-width: 1.5; stroke-dasharray: 5 4; opacity: 0.6; }
.t-grid { stroke: var(--line); stroke-width: 1; }
.t-grid0 { stroke: var(--line-strong); }
.t-ax, .t-xax {
  font-size: 10.5px; fill: var(--muted); font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
}
.t-bar { transition: fill-opacity 0.15s var(--ease); }
.t-bar:hover { fill-opacity: 1; }
/* The bars are now keyboard-focusable, so they need a visible focus ring of
   their own. SVG has no outline by default; the stroke is inset so it draws
   inside the bar rather than clipping against the plot edge. */
.t-bar:focus { outline: none; }
.t-bar:focus-visible {
  /* The bars are paths with a square base, so a centred stroke would straddle
     the bottom edge and spill onto the axis. Clipping the ring to the inside
     keeps the base looking sharp while the bar is focused. */
  stroke: var(--text); stroke-width: 4; stroke-linejoin: round;
  paint-order: stroke; clip-path: inset(0 round 0 0);
  fill-opacity: 1;
}
.t-goalband { fill: var(--accent); opacity: 0.08; }
[data-theme="dark"] .t-goalband { opacity: 0.16; }
.trendtip {
  position: absolute; z-index: 5; pointer-events: none;
  opacity: 0; transform: translate(-50%, calc(-100% - 16px));
  transition: opacity 0.16s var(--ease), transform 0.16s var(--ease);
  background: var(--card); border: 1px solid var(--line); border-radius: 8px;
  padding: 9px 12px; font-size: var(--text-xs); line-height: 1.5;
  color: var(--text-secondary); box-shadow: var(--shadow-md); white-space: nowrap;
}
.trendtip::after {
  content: ""; position: absolute; bottom: -5.5px; left: var(--caret-x, 50%);
  width: 10px; height: 10px; margin-left: -5px;
  background: var(--card); border-right: 1px solid var(--line); border-bottom: 1px solid var(--line);
  transform: rotate(45deg);
}
.trendtip.below::after {
  bottom: auto; top: -5.5px;
  border: none; border-left: 1px solid var(--line); border-top: 1px solid var(--line);
}
.trendtip.below { transform: translate(-50%, 12px); }
.trendtip strong { color: var(--text); }
.tiphead { font-weight: 700; color: var(--text); margin-bottom: 2px; display: flex; align-items: center; gap: 6px; }
.tipdot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; flex: 0 0 auto; }
.tipmeta { color: var(--muted); font-variant-numeric: tabular-nums; }
.trendtip.show { opacity: 1; transform: translate(-50%, calc(-100% - 12px)); }
.trendtip.show.below { transform: translate(-50%, 8px); }
.upcard { display: flex; flex-direction: column; }
.upbtns { display: grid; grid-template-columns: 1fr; gap: 8px; }
.upbtns .btn { justify-content: center; }
.upmeta {
  margin-top: auto; padding-top: 12px; border-top: 1px solid var(--line-subtle);
  display: grid; gap: 6px; font-size: var(--text-xs); color: var(--muted);
}
.upmeta .hint { margin: 0; }
.upline { font-size: var(--text-xs); color: var(--muted); }
.minisplit {
  display: grid; grid-template-columns: 1fr 1fr; gap: 10px;
  margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--line-subtle);
}
.mini {
  background: var(--bg-subtle); border: 1px solid var(--line);
  border-radius: 10px; padding: 12px 14px;
}
.mini-l { display: block; font-size: 0.6875rem; color: var(--muted); margin-bottom: 2px; }
.mini-v { display: block; font-size: var(--text-xl); font-weight: 800; font-variant-numeric: tabular-nums; }
.mini-p { display: block; font-size: 0.6875rem; color: var(--muted-light); margin-top: 2px; }
.rblock {
  display: flex; justify-content: space-between; gap: 10px; align-items: baseline;
  padding: 9px 0; border-bottom: 1px solid var(--line-subtle); font-size: var(--text-sm);
}
.rblock:last-child { border-bottom: 0; }
.rblock .rmeta { color: var(--muted); font-size: var(--text-xs); font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ---------- setup ---------- */
.skilllist {
  /* flex rows (not grid): every row's tiles grow to fill that row, so the
     last row never leaves an empty tail */
  display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0 4px;
}
.skilllist label {
  display: flex; gap: 10px; align-items: center; padding: 9px 12px; min-height: 46px;
  flex: 1 1 250px;
  border: 1px solid var(--line); border-radius: 10px; cursor: pointer; font-size: var(--text-sm);
  background: var(--card);
  transition: background-color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.skilllist label:hover { background: var(--accent-soft); border-color: var(--accent); }
.skilllist label:has(input:checked) { background: var(--accent-soft); border-color: var(--accent); }
.skilllist input {
  appearance: none; -webkit-appearance: none;
  width: 18px; height: 18px; flex: 0 0 auto; cursor: pointer; margin: 0;
  border: 1.5px solid var(--line-control); border-radius: 5px;
  background: var(--card); display: grid; place-items: center;
  transition: background-color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.skilllist input:checked { background: var(--accent); border-color: var(--accent); }
.skilllist input:checked::after {
  content: ""; width: 9px; height: 5px; margin-top: -1px;
  border-left: 2px solid #ffffff; border-bottom: 2px solid #ffffff;
  transform: rotate(-45deg);
}
[data-theme="dark"] .skilllist input:checked::after { border-color: #0f0f12; }
.skilllist input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.skilllist .cnt { margin-left: auto; color: var(--muted); font-size: var(--text-xs); font-variant-numeric: tabular-nums; }
.pillset { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 8px; }
/* grid (not flex) so every pill in a row is exactly the same width and the
   option rows line up across the two columns */
.pillset .pill { text-align: center; white-space: nowrap; }
.pill {
  border: 1px solid var(--line); background: var(--card); color: var(--muted);
  border-radius: 99px; padding: 8px 16px; font: 600 var(--text-sm)/1 inherit; cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease),
    color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.pill:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.pill:active { transform: scale(0.97); }
.pill:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 2px; }
.pill.on { background: var(--accent); border-color: var(--accent); color: #fff; }
.pill.on:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.setgroup { margin: 16px 0; }
.setgroup > .glbl {
  font-weight: 700; font-size: 0.6875rem; margin-bottom: 8px; color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.6px;
}
.setgrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 4px 32px; }
.setup-meta { margin: 8px 0 0; }

/* Block builder: content left, live summary card right (checkout pattern) */
.setup-layout { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; align-items: start; }
.setup-layout .card { margin-bottom: 0; }
.setup-summary { position: static; }
/* the back link reads as a real outlined button under Start block */
.setup-summary .btn.ghost { border-color: var(--line); background: var(--card); }
.setup-summary .btn.ghost:hover { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
.sumrows { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 10px; }
.sumrow {
  display: flex; flex-direction: column; gap: 3px; align-items: flex-start;
  background: var(--bg-subtle); border: 1px solid var(--line); border-radius: 10px;
  padding: 11px 13px;
}
.sumrow > span { font-size: 0.6875rem; color: var(--muted); }
.sumrow > strong { font-size: var(--text-sm); font-weight: 700; color: var(--text); max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (max-width: 980px) {
  .sumrows { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 640px) {
  .sumrows { grid-template-columns: 1fr; }
}
@keyframes sumIn { from { opacity: 0; transform: translateY(4px); } }
.sum-flash { animation: sumIn 0.22s var(--ease); }
.upbar { min-width: 0; }
.upbar .bar { height: 6px; }
.upbar .hint { margin-top: 6px; }
.quicklinks { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
.quicklinks .btn { width: 100%; }
@media (max-width: 720px) {
  .quicklinks { grid-template-columns: repeat(2, 1fr); }
}
.domlist { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.sum-foot { margin: 12px 0 0; }
.btn-block { width: 100%; margin-top: 14px; }
.btn-block + .btn-block { margin-top: 8px; }
@media (max-width: 980px) {
  .setup-layout { grid-template-columns: 1fr; }
  .setup-summary { position: static; }
}

/* ---------- practice runner ---------- */
.runhead {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 16px;
  padding: 10px 14px; background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow-xs);
}
.runhead .timer {
  font-variant-numeric: tabular-nums; font-weight: 700; color: var(--text); font-size: var(--text-sm);
  background: var(--bg-subtle); border: 1px solid var(--line);
  border-radius: var(--radius-full); padding: 3px 11px;
  transition: color var(--dur-normal) var(--ease), border-color var(--dur-normal) var(--ease),
    background-color var(--dur-normal) var(--ease);
}
.runhead .timer.warn { color: var(--warn); border-color: var(--warn-border); background: var(--warn-soft); }
.runhead .timer.crit { color: var(--bad); border-color: var(--bad-border); background: var(--bad-soft); }
.pbar { flex: 1; min-width: 120px; }
.bar { height: 8px; background: var(--line); border-radius: 99px; overflow: hidden; }
/* The fill is scaled with transform, not width. Animating `width` re-runs layout
   on every frame, and this bar advances while the student is tapping as fast as
   they can - the worst moment to cause a reflow. scaleX is compositor-only and
   looks identical, because the track is a fixed width with overflow:hidden. */
.bar > i {
  display: block; height: 100%; width: 100%; background: var(--accent);
  border-radius: 99px; transform: scaleX(0); transform-origin: left center;
  transition: transform 0.3s var(--ease);
}
.bar.good > i { background: var(--good); }
.qmeta {
  display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
  margin-bottom: 14px; padding-bottom: 12px; border-bottom: 1px solid var(--line-subtle);
  animation: rise var(--dur-normal) var(--ease) both;
}
.flagbtn {
  margin-left: auto; border: 1px solid var(--line); background: var(--card);
  border-radius: var(--radius-full); padding: 6px 13px; cursor: pointer; font-size: var(--text-sm);
  font-weight: 600; color: var(--muted);
  display: inline-flex; align-items: center;
  transition: background-color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.flagbtn .ic { width: 13px; height: 13px; margin-right: 6px; }
.flagbtn .flabel { white-space: nowrap; }
/* "ged" wipes in and out; a symmetric ease at one duration so expand and
   shrink read at exactly the same speed (the theme's ease-out made the
   expansion feel instant next to the collapse) */
.flagbtn .fsuffix {
  display: inline-block; max-width: 0; opacity: 0; overflow: hidden; white-space: nowrap;
  transition: max-width 0.28s cubic-bezier(0.45, 0, 0.55, 1), opacity 0.28s cubic-bezier(0.45, 0, 0.55, 1);
}
.flagbtn.on .fsuffix { max-width: 48px; opacity: 1; }
.flagbtn:hover { color: var(--warn); border-color: var(--warn); background: var(--warn-soft); }
.flagbtn:focus-visible { outline: 2px solid var(--warn-ring); outline-offset: 2px; }
.flagbtn.on { color: var(--warn); border-color: var(--warn); background: var(--warn-soft); font-weight: 700; }
.togglebtn {
  border: 1px solid var(--line); background: var(--card); color: var(--muted);
  border-radius: var(--radius-full); padding: 6px 13px; cursor: pointer;
  font-size: var(--text-sm); font-weight: 600; white-space: nowrap;
  transition: background-color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.togglebtn:hover { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
.togglebtn:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 2px; }
.togglebtn.on { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); font-weight: 700; }
.stimulus {
  margin-bottom: 16px; padding: 14px 16px; line-height: 1.65;
  background: var(--bg-subtle); color: var(--text-secondary);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}
.stem { margin-bottom: 18px; font-size: var(--text-md); font-weight: 600; line-height: 1.6; }

/* Smooth question swaps: every re-rendered piece gets the same entrance */
.qcontent { animation: rise var(--dur-normal) var(--ease) both; }
.palette-wrap { animation: rise var(--dur-normal) var(--ease) 0.04s both; }
#qcard {
  min-height: 420px; scroll-margin-top: 84px;
  transition: opacity 0.11s var(--ease), transform 0.11s var(--ease);
  view-transition-name: question-card; /* question swaps crossfade only this card */
}
/* In-place updates (answers, flags): rebuild without replaying entrances,
   but keep the wrong-answer shake */
#qcard.no-anim .qmeta, #qcard.no-anim .qcontent, #qcard.no-anim .opts, #qcard.no-anim .sprbox,
#qcard.no-anim .feedback, #qcard.no-anim .rationale, #qcard.no-anim .runfoot, #qcard.no-anim .palette-wrap {
  animation: none !important;
}
#qcard.no-anim .feedback.bad { animation: shake-x 0.35s var(--ease) 0.1s 1 !important; }
/* correct-answer celebration survives the in-place (no-anim) rebuild */
@keyframes popIn {
  0% { opacity: 0; transform: translateY(8px) scale(0.96); }
  60% { opacity: 1; transform: translateY(0) scale(1.015); }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes goodFlash { 0% { box-shadow: 0 0 0 6px var(--good-ring); } 100% { box-shadow: 0 0 0 0 rgba(0, 0, 0, 0); } }
@keyframes goodPop { 0% { transform: scale(0.985); } 60% { transform: scale(1.006); } 100% { transform: scale(1); } }
@keyframes drawCheck { to { stroke-dashoffset: 0; } }
.feedback.pop-in { animation: popIn 0.32s var(--ease) both; }
.opt.good.good-in { animation: goodFlash 0.6s var(--ease), goodPop 0.3s var(--ease); }
.feedback.good.pop-in .ic svg path {
  stroke-dasharray: 26; stroke-dashoffset: 26;
  animation: drawCheck 0.3s ease-out 0.08s forwards;
}
#qcard.no-anim .feedback.pop-in { animation: popIn 0.32s var(--ease) both !important; }
#qcard.no-anim .opt.good.good-in { animation: goodFlash 0.6s var(--ease), goodPop 0.3s var(--ease) !important; }
#qcard.no-anim .feedback.good.pop-in .ic svg path { animation: drawCheck 0.3s ease-out 0.08s forwards !important; }
/* rationale entrance, only when a question newly settles */
@keyframes ratIn { from { opacity: 0; transform: translateY(6px); } }
.rationale.rat-in { animation: ratIn 0.3s var(--ease) both; }
#qcard.no-anim .rationale.rat-in { animation: ratIn 0.3s var(--ease) both !important; }

.opts { display: flex; flex-direction: column; gap: 10px; animation: rise var(--dur-normal) var(--ease) both; }
.opt {
  display: flex; gap: 12px; align-items: flex-start; text-align: left; width: 100%;
  border: 1.5px solid var(--line); background: var(--card); color: var(--text);
  border-radius: 12px; padding: 13px 15px; cursor: pointer; font: inherit; font-size: var(--text-md);
  transition: border-color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease),
    box-shadow var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.opt:hover { border-color: var(--accent); background: var(--accent-soft); box-shadow: var(--shadow-sm); }
.opt:active { transform: scale(0.995); }
.opt:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 2px; border-color: var(--accent); }
.opt .letter {
  flex: 0 0 auto; width: 26px; height: 26px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--line); color: var(--muted); font-weight: 800; font-size: var(--text-sm);
  transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.opt.sel { border-color: var(--accent); background: var(--accent-soft); box-shadow: 0 0 0 1px var(--accent); }
.opt.sel .letter { background: var(--accent); color: #fff; }
.opt.good { border-color: var(--good); background: var(--good-soft); }
.opt.good .letter { background: var(--good); color: #fff; }
.opt.bad { border-color: var(--bad); background: var(--bad-soft); }
.opt.bad .letter { background: var(--bad); color: #fff; }
.opt.dim { opacity: .55; cursor: default; }
.opt.gone { opacity: .6; cursor: default; border-style: dashed; border-color: var(--bad-border); background: var(--bad-soft); }
.opt.gone .letter { background: var(--bad); color: #fff; }
.opt.gone .otext { text-decoration: line-through; text-decoration-thickness: 1.5px; text-decoration-color: var(--bad); }
.opt.gone .gonemark { margin-left: auto; flex: 0 0 auto; color: var(--bad); line-height: 1.5; }
.opt.gone .gonemark .ic { width: 15px; height: 15px; }
.opt.good:hover, .opt.bad:hover, .opt.dim:hover, .opt.gone:hover { transform: none; box-shadow: none; }
.opt.dim:hover { border-color: var(--line); background: var(--card); }
.opt.gone:hover { border-color: var(--bad-border); background: var(--bad-soft); }
.opt p { margin: 0; }

.sprbox { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; animation: rise var(--dur-normal) var(--ease) both; }
.sprbox input {
  font: 700 var(--text-xl)/1 inherit; padding: 12px 16px; width: 200px;
  border: 2px solid var(--line-control); border-radius: 12px; background: var(--card); color: var(--text);
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
.sprbox input:focus { outline: 2px solid var(--accent-ring); outline-offset: 2px; border-color: var(--accent); }
.sprbox input.good { border-color: var(--good); background: var(--good-soft); }
.sprbox input.bad { border-color: var(--bad); background: var(--bad-soft); }
.sprhint { font-size: var(--text-xs); color: var(--muted); }

.feedback {
  border-radius: 12px; padding: 13px 17px; font-weight: 700; margin: 18px 0 0;
  border: 1px solid transparent;
  animation: rise var(--dur-normal) var(--ease) both;
}
.feedback .ic { width: 15px; height: 15px; margin-right: 8px; vertical-align: -2px; }
.feedback.good { background: var(--good-soft); color: var(--good); border-color: var(--good-border); }
.feedback.bad {
  background: var(--bad-soft); color: var(--bad); border-color: var(--bad-border);
  animation: rise var(--dur-normal) var(--ease) both, shake-x 0.35s var(--ease) 0.1s 1;
}
.feedback.try { background: var(--warn-soft); color: var(--warn); border-color: var(--warn-border); }

.rationale {
  margin-top: 16px; border: 1px solid var(--line);
  background: var(--accent-soft); border-radius: 12px; padding: 15px 18px;
  line-height: 1.65; font-size: var(--text-md);
  font-family: var(--font-serif);
  animation: rise var(--dur-normal) var(--ease) .05s both;
}
.rationale .rtitle {
  font-family: var(--font-sans); font-weight: 800; font-size: var(--text-xs);
  text-transform: uppercase; letter-spacing: .5px; color: var(--accent); margin-bottom: 6px;
}
.rationale p { margin: 0 0 9px; }
.rationale p:last-child { margin-bottom: 0; }

.runfoot { display: flex; gap: 10px; margin-top: 18px; flex-wrap: wrap; align-items: center; animation: rise var(--dur-normal) var(--ease) both; }
kbd {
  background: var(--card); border: 1px solid var(--line-strong); border-bottom-width: 2px;
  border-radius: 5px; padding: 1px 6px;
  font: 600 var(--text-xs)/1.4 var(--font-mono); color: var(--text-secondary);
  box-shadow: var(--shadow-xs);
}
.hint { color: var(--muted); font-size: var(--text-xs); }

.notewell { margin-top: 12px; }
.notewell textarea {
  width: 100%; min-height: 60px; border: 1px solid var(--line-control); border-radius: 10px;
  padding: 10px 12px; font: inherit; font-size: var(--text-sm); background: var(--card); color: var(--text); resize: vertical;
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
.notewell textarea:focus { outline: 2px solid var(--accent-ring); outline-offset: 2px; border-color: var(--accent); }
.notewell .nlbl { font-size: var(--text-xs); font-weight: 700; color: var(--muted); margin-bottom: 5px; }

/* ---------- results ---------- */
.bigscore { text-align: center; padding: 10px 0 4px; }
.bigscore .v { font-size: var(--fs-display); font-weight: 800; letter-spacing: -1.5px; font-variant-numeric: tabular-nums; color: var(--text); }
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .bigscore .v {
    background: linear-gradient(135deg, var(--accent), #7c3aed);
    -webkit-background-clip: text; background-clip: text; color: transparent;
  }
}
/* background-clip: text + color: transparent reads as nothing at all in forced
   colors (gradients are stripped); fall back to plain text there. */
@media (forced-colors: active) {
  .bigscore .v {
    color: CanvasText;
    background: none;
    -webkit-background-clip: initial;
    background-clip: initial;
  }
}
.bigscore .l { color: var(--muted); font-weight: 600; font-size: var(--text-sm); }
.rlist { display: flex; flex-direction: column; gap: 10px; margin-top: 8px; }
.rrow {
  border: 1px solid var(--line); border-radius: 12px; overflow: hidden; background: var(--card);
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
.rrow:hover { border-color: var(--line-strong); box-shadow: var(--shadow-sm); }
.rrow.open { border-color: var(--line-strong); box-shadow: var(--shadow-sm); }
.rrow > .rhead {
  display: flex; gap: 10px; align-items: center; width: 100%; text-align: left;
  background: var(--card); border: 0; padding: 12px 14px; cursor: pointer; font: inherit; color: var(--text);
  transition: background-color var(--dur-fast) var(--ease);
}
.rrow > .rhead:hover { background: var(--accent-soft); }
.rrow > .rhead:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: -2px; }
.rrow > .rhead::after {
  content: ""; flex: 0 0 auto; width: 7px; height: 7px;
  border-right: 2px solid var(--muted); border-bottom: 2px solid var(--muted);
  transform: rotate(45deg) translateY(-2px);
  transition: transform var(--dur-fast) var(--ease);
}
.rrow.open > .rhead::after { transform: rotate(-135deg) translateY(-2px); }
.rrow .mark {
  flex: 0 0 auto; width: 24px; height: 24px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
}
.rrow .mark .ic { width: 13px; height: 13px; }
.rrow.ok .mark { color: var(--good); background: var(--good-soft); }
.rrow.no .mark { color: var(--bad); background: var(--bad-soft); }
.rrow.no .mark.m-solved { color: var(--warn); background: var(--warn-soft); }
.rrow .lbl {
  font-weight: 700; font-size: var(--text-sm); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rrow .your {
  color: var(--muted); font-size: var(--text-xs); margin-left: auto; flex: 0 0 auto; white-space: nowrap;
}
.rrow .detail { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 0.26s var(--ease); }
.rrow .detail > .detail-in { overflow: hidden; min-height: 0; background: var(--bg-subtle); }
.rrow .detail-pad { padding: 16px; }
.rrow.open .detail { grid-template-rows: 1fr; }
.detail .qtext { margin-bottom: 10px; line-height: 1.65; }

/* ---------- error log / misses ---------- */
table.log { width: 100%; border-collapse: collapse; font-size: var(--text-sm); overscroll-behavior: contain; }
table.log th {
  text-align: left; font-size: var(--text-xs); text-transform: uppercase; letter-spacing: .5px;
  color: var(--muted); border-bottom: 1px solid var(--line-strong); padding: 8px 10px;
  background: var(--bg-subtle); white-space: nowrap;
}
table.log td { padding: 9px 10px; border-bottom: 1px solid var(--line); vertical-align: middle; }
table.log tr:hover td { background: var(--accent-soft); }
table.log tr.mastered td { opacity: .5; }
table.log tr.mastered:hover td { background: transparent; }
table.log .lq strong { font-family: var(--font-mono); font-size: var(--text-xs); }
table.log .lmono { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-secondary); white-space: nowrap; }
table.log .lans { font-weight: 700; color: var(--text); }
table.log .lacts { white-space: nowrap; text-align: right; }
table.log .lacts > * { margin-left: 6px; }
.note-input {
  width: 100%; min-width: 150px; border: 1px solid var(--line-control); border-radius: 8px;
  padding: 7px 10px; font: inherit; font-size: var(--text-sm); background: var(--card); color: var(--text);
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
.note-input:focus { outline: 2px solid var(--accent-ring); outline-offset: 2px; border-color: var(--accent); }
.note-input::placeholder { color: var(--muted-light); }
.xbtn .ic { width: 14px; height: 14px; }
.empty { text-align: center; color: var(--muted); padding: 44px 10px; font-size: var(--text-sm); }
.empty .big { font-size: var(--text-2xl); margin-bottom: 10px; line-height: 1.2; font-weight: 700; }

/* ---------- guides ---------- */
.guidechips { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 14px; }
.guidechips a {
  border: 1px solid var(--line); border-radius: 99px; padding: 6px 13px;
  font-size: var(--text-sm); font-weight: 600; color: var(--muted); text-decoration: none; background: var(--card);
  transition: background-color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease),
    color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
.guidechips a:hover { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
.guidechips a.on { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); font-weight: 700; box-shadow: 0 1px 2px var(--accent-ring); }
.guidechips a:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 2px; }
.guideset { margin-bottom: 16px; }
.guidelinks { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 10px; }
.guidelinks a,
/* .lnkcard is the <button> form used where a card starts a drill instead of
   navigating (see the Misses view). Same look, correct element. */
.guidelinks .lnkcard {
  display: flex; justify-content: space-between; gap: 8px; align-items: center;
  border: 1px solid var(--line); background: var(--card); border-radius: 12px;
  padding: 13px 16px; font-weight: 600; font-size: var(--text-sm); color: var(--text); text-decoration: none;
  font-family: inherit; text-align: left; cursor: pointer; width: 100%;
  transition: background-color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease),
    box-shadow var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.guidelinks a:hover, .guidelinks .lnkcard:hover { border-color: var(--accent); background: var(--accent-soft); transform: translateY(-2px); box-shadow: var(--shadow); }
.guidelinks a:focus-visible, .guidelinks .lnkcard:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 2px; }
.guidelinks .n {
  color: var(--muted); font-size: var(--text-xs); font-weight: 700; text-transform: uppercase;
  letter-spacing: .4px; background: var(--bg-subtle); border: 1px solid var(--line);
  padding: 2px 8px; border-radius: var(--radius-full); white-space: nowrap; flex: 0 0 auto;
}
iframe#guideframe { width: 100%; height: calc(100vh - 175px); height: calc(100dvh - 175px); border: 1px solid var(--line); border-radius: var(--radius); background: var(--card); box-shadow: var(--shadow-sm); }

/* ---------- guide reader (inlined page + TOC) ---------- */
.guide-layout { display: grid; grid-template-columns: minmax(0, 1fr) 232px; gap: 24px; align-items: start; }
.guide-article { margin-bottom: 0; }
.guide-article h2 { scroll-margin-top: 84px; }
.toc {
  position: sticky; top: 84px;
  display: flex; flex-direction: column; gap: 2px;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 14px; box-shadow: var(--shadow-xs);
}
.toc .label { margin-bottom: 6px; }
.toc a {
  color: var(--muted); text-decoration: none; font-size: var(--text-xs);
  padding: 5px 8px; border-radius: 6px; line-height: 1.45;
  transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.toc a:hover { color: var(--text); background: var(--accent-soft); }
.toc a.active { color: var(--accent); background: var(--accent-soft); font-weight: 600; }
@media (max-width: 980px) {
  .guide-layout { grid-template-columns: minmax(0, 1fr); }
  .toc { position: static; flex-flow: row wrap; }
  .toc .label { flex: 1 1 100%; margin-bottom: 4px; }
}

/* ---------- guide pages (standalone) ---------- */
/* width:100% matters: `body` is a column flex container, and `margin: 0 auto`
   makes a flex item shrink-to-fit instead of stretching, so without it the guide
   sizes itself to its longest line and the page scrolls sideways on a phone. */
body.guide-page main.guide-body { width: 100%; max-width: 1200px; margin: 0 auto; padding: 26px 22px 70px; }
.prose {
  line-height: 1.68; font-family: var(--font-serif); font-size: 1.0625rem;
  /* Measure cap: past roughly 80 characters per line, reading speed and
     comprehension drop because the eye loses its place at the line turn. The
     cap bounds the article body and centers it in whatever space the layout
     gives it; the shell keeps its own width, so a wide screen gets margins
     rather than a stretched measure. Code blocks and tables may exceed it via
     their own overflow rules. */
  max-width: 72ch;
  margin-inline: auto;
}
.prose h1 { font-size: var(--text-xl); margin: 6px 0 14px; font-family: var(--font-serif); }
.prose h2 { font-size: var(--text-lg); margin: 26px 0 10px; border-bottom: 1px solid var(--line); padding-bottom: 6px; font-family: var(--font-serif); }
.prose h3 { font-size: var(--text-md); margin: 20px 0 8px; font-family: var(--font-serif); }
.prose p { margin: 9px 0; }
.prose ul, .prose ol { margin: 9px 0; padding-left: 26px; }
.prose li { margin: 5px 0; }
/* Orphan control for long-form copy; headings use balance instead. */
.prose p, .prose li { text-wrap: pretty; }
.prose mark {
  background: #fef3c7; color: inherit;
  padding: 1px 3px; border-radius: 3px; font-weight: 600;
}
[data-theme="dark"] .prose mark { background: rgba(251, 191, 36, 0.22); color: #fde68a; }
.prose code {
  background: var(--line); border-radius: 5px; padding: 1.5px 6px;
  font: 600 var(--text-sm)/1.5 Consolas, monospace;
}
.prose pre {
  background: var(--line); border-radius: 10px; padding: 13px 16px; overflow-x: auto;
  overscroll-behavior: contain;
}
.prose pre code { background: none; padding: 0; }
.prose blockquote {
  margin: 12px 0; padding: 10px 18px; border-left: 4px solid var(--accent);
  background: var(--accent-soft); border-radius: 0 10px 10px 0;
}
.prose table { border-collapse: collapse; width: 100%; margin: 12px 0; font-size: var(--text-sm); }
.prose th, .prose td { border: 1px solid var(--line); padding: 7px 11px; text-align: left; }
.prose th { background: var(--line); }
.prose hr { border: 0; border-top: 1px solid var(--line); margin: 22px 0; }
.prose a { color: var(--accent); }

/* Long paths and wide tables must not widen the page. `anywhere` (not
   `break-word`) is what actually reduces min-content width. */
.prose { overflow-wrap: break-word; }
.prose code { overflow-wrap: anywhere; }
.prose pre { max-width: 100%; overflow-x: auto; }
.prose img, .prose svg { max-width: 100%; height: auto; }

/* ---------- question content ---------- */
.qcontent p { margin: 0 0 10px; }
.qcontent p:last-child { margin-bottom: 0; }
.qcontent figure { margin: 14px auto; text-align: center; max-width: 100%; }
.qcontent figure svg { max-width: 100%; height: auto; }

/* Official figures are inline SVG with print-black ink baked into the data
   (fill="#000000" text and bar series, #000/#231f20 axes and frames). CSS
   overrides those presentation attributes, so in dark theme flip the ink:
   text and black shapes to the theme text color, black strokes to the muted
   tone. Mid-gray data bars keep their fills; their outlines flip with the
   strokes. Must not touch the .trend chart, whose text is themed via .t-ax. */
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg text,
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [fill="#000000"],
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [fill="#000"],
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [fill="#231f20"],
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [fill="black"] {
  fill: var(--text);
}
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [stroke="#000000"],
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [stroke="#000"],
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [stroke="#202020"],
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [stroke="#231f20"],
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [stroke="black"] {
  stroke: var(--muted);
}
/* The math figures are matplotlib exports: their colors live in the inline
   style attribute (not presentation attributes) and every tick label is a
   <use> glyph path with no fill of its own, so the attribute selectors above
   see nothing in them. Inline style beats any normal rule, so these need
   !important: white label plates to the card color, black-styled strokes to
   the muted tone, glyph ink to the text color. */
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [style*="fill:#fff"],
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [style*="fill:white"] {
  fill: var(--card) !important;
}
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [style*="stroke:#000000"],
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [style*="stroke:black"] {
  stroke: var(--muted) !important;
}
/* The inequality and segment-bar figures are a second exporter (d3-style):
   attribute fills, currentColor text, and style-attribute whites written with
   a space ("fill: white" label plates). The `i` flag absorbs spacing/case
   variants. Gray data segments keep their lightness ORDER by inverting it
   (darkest ink becomes the lightest tone) so the four segments of one bar
   stay distinguishable. The single mix-blend-mode shading veil becomes a
   plain translucent light layer so the grid shows through it. */
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [style*="fill: white" i],
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [style*="fill: #fff" i] {
  fill: var(--card) !important;
}
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [fill="#fff" i],
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [fill="#ffffff" i],
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [fill="white" i] {
  fill: var(--card);
}
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [fill="#333333" i] {
  fill: var(--text);
}
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [fill="#444444" i] {
  fill: var(--text-secondary);
}
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [fill="#666666" i] {
  fill: var(--muted);
}
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [fill="#999" i],
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [fill="#999999" i] {
  fill: var(--muted-light);
}
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [fill="#b3b3b3" i],
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [fill="#bbb" i],
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [fill="#cccccc" i] {
  fill: #52525b;
}
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [fill="#cdcdcd" i] {
  fill: var(--line-strong);
}
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [fill="#b7b7b7" i] {
  fill: rgba(244, 244, 245, 0.14);
  mix-blend-mode: normal !important;
}
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [stroke="#757576" i],
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [stroke="#999" i],
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg [stroke="#999999" i] {
  stroke: var(--muted);
}
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg use {
  fill: var(--text);
}
/* The College Board illustrator exports convert every label and caption to
   glyph-outline paths with no fill attribute at all - and SVG's default ink is
   black, which none of the attribute selectors above can see. In dark theme
   that is the vertex letters (X, Y, Z) and captions like "Note: Figure not
   drawn to scale." rendering near-invisible on the card. Paint inkless paths
   with the text color; paths that carry their own fill (attribute or inline
   style) are untouched, and the [data-theme] scope keeps light mode on true
   black. Paths inside <defs>/<clipPath>/<mask> never render directly, so
   flipping them changes nothing. */
[data-theme="dark"] :is(.qcontent, .stem, .stimulus, .qtext, .prose, .rationale, .opt) svg path:not([fill]) {
  fill: var(--text);
}
.sr-only {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

/* ---------- skip link ----------
   Off-screen until focused, then pinned to the top-left. It is the first thing
   in the tab order, so a keyboard user can jump past the five nav links. */
.skip {
  position: absolute;
  left: 12px;
  top: 12px;
  z-index: 200;
  transform: translateY(-200%);
  background: var(--accent);
  color: #ffffff;
  padding: 10px 16px;
  border-radius: var(--radius-sm);
  font: 600 var(--text-sm)/1.2 var(--font-sans);
  text-decoration: none;
  box-shadow: var(--shadow-md);
  transition: transform var(--dur-fast) var(--ease);
}
[data-theme="dark"] .skip { color: #0f0f12; }
.skip:focus { transform: translateY(0); }
.skip:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }

/* #view is focusable only so the skip link can hand focus to it. It must not
   draw a ring for that, or a mouse click anywhere in the content area would
   ring the whole page. */
#view:focus { outline: none; }
#view:focus-visible { outline: none; }
mjx-container { overflow-x: auto; overflow-y: hidden; max-width: 100%; vertical-align: middle; }
mjx-container { overflow-x: auto; overflow-y: hidden; max-width: 100%; vertical-align: middle; overscroll-behavior: contain; }
math { font-size: 106%; }

.toast {
  position: fixed; bottom: 22px; left: 50%; transform: translateX(-50%) translateY(8px);
  /* Clear of the home indicator, and clamped so it never runs off a narrow
     screen. Safe-area bottom is added to the resting offset. */
  bottom: max(22px, calc(env(safe-area-inset-bottom) + 14px));
  background: var(--text); color: var(--bg); border-radius: 99px;
  padding: 9px 20px; font-size: var(--text-sm); font-weight: 600;
  box-shadow: var(--shadow); opacity: 0; pointer-events: none;
  transition: opacity .25s var(--ease), transform .25s var(--ease);
  z-index: 100;
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

@media (max-width: 980px) {
  .dash-hero { grid-template-columns: 1fr; }
  .dash-trendrow { grid-template-columns: 1fr; }
}

@media (max-width: 720px) {
  .topbar { padding: 10px 12px; gap: 8px; }
  .brand span { display: none; }
  #view { padding: 18px 12px 50px; }
  .card { padding: 16px; }
  .dash-hero { gap: 12px; }
  .domains, .guidelinks { grid-template-columns: 1fr; }
}

@media (max-width: 640px) {
  /* Touch-friendly targets */
  #nav a { padding: 9px 13px; }
  .btn { padding: 12px 16px; min-height: 44px; }
  .btn.small { padding: 10px 14px; }
  .iconbtn { min-width: 44px; min-height: 44px; }
  .flagbtn { min-height: 40px; padding: 8px 14px; }
  .palette-btn { width: 44px; height: 44px; font-size: var(--text-sm); }
  .pill { padding: 10px 16px; }
  /* pills wrap to a 2-up grid on phones so labels never squeeze */
  .pillset { grid-auto-flow: row; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .skilllist label { padding: 12px 14px; }

  /* Runner: progress bar gets its own line, actions go full-width */
  .runhead { gap: 8px; padding: 10px 12px; }
  .runhead .pbar { flex: 1 0 100%; order: 5; }
  .opt { padding: 14px; font-size: var(--text-md); }
  .sprbox { flex-direction: column; align-items: stretch; }
  .sprbox input { width: 100%; }
  .sprbox .btn { width: 100%; }
  .feedback, .rationale { padding: 12px 14px; }
  .runfoot { gap: 8px; }
  .runfoot .grow { display: none; }
  .runfoot .btn { flex: 1 1 auto; }
  .runfoot .hint:has(kbd) { display: none; } /* touch devices: no shortcut keys */
  .notewell textarea, .note-input { font-size: var(--text-md); } /* avoids iOS focus zoom */

  /* Mastery rows stack cleanly */
  .mrow { display: flex; flex-wrap: wrap; gap: 8px 10px; }
  .mname { flex: 1 1 auto; }
  .mbar { flex: 1 1 100%; }
  .mcnt { flex: 1 1 auto; text-align: left; }
  .macts { flex: 1 1 100%; justify-content: flex-start; }

  /* Review rows stack cleanly */
  .rrow > .rhead { flex-wrap: wrap; }
  .rrow .your { margin-left: 0; flex: 1 1 100%; padding-left: 34px; white-space: normal; }
  .rrow > .rhead::after { display: none; }
  .rrow .detail > .detail-in { min-height: 0; }
  .rrow .detail-pad { padding: 12px; }

  /* Log table scrolls horizontally instead of overflowing the card */
  table.log { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; }
  /* While the table is its own scroller, pin the header row so it stays
     readable; th already carries an opaque background to scroll under. */
  table.log thead { position: sticky; top: 0; z-index: 1; }

  iframe#guideframe { height: calc(100vh - 150px); height: calc(100dvh - 150px); }

  .toast { left: 16px; right: 16px; transform: none; }
  .toast.show { transform: none; }

  /* ---------- study guides on a phone ---------- */
  /* `body` is a flex column; min-width:0 lets the guide shrink to the screen
     instead of to its longest unbreakable line. */
  body.guide-page main.guide-body { min-width: 0; padding: 16px 14px 56px; }
  .guide-layout { gap: 16px; }
  .prose { font-size: 1rem; }
  .prose p, .prose li { max-width: none; }
  .prose h1 { font-size: var(--text-lg); }
  .prose h2, .prose h3 { font-size: var(--text-md); }
  .prose blockquote { padding: 10px 12px; }
  /* same treatment as table.log: scroll the table, don't widen the article */
  .prose table { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; }

  /* The standalone guide header carries brand + title + next; three is one too
     many on a phone, so the title drops to its own line. */
  body.guide-page .topbar { flex-wrap: wrap; gap: 8px 12px; padding: 10px 12px; }
  body.guide-page .top-title { order: 3; flex: 1 1 100%; font-size: var(--text-sm); }
  body.guide-page .topbar .btn { margin-left: auto; }
}

/* Same touch-target bumps for coarse pointers at any width (tablets), matching
   the phone block above. */
@media (pointer: coarse) {
  #nav a { padding: 9px 13px; }
  .btn { padding: 12px 16px; min-height: 44px; }
  .btn.small { padding: 10px 14px; }
  .iconbtn { min-width: 44px; min-height: 44px; }
  .flagbtn { min-height: 40px; padding: 8px 14px; }
  .palette-btn { width: 44px; height: 44px; font-size: var(--text-sm); }
  .pill { padding: 10px 16px; }
}

/* ---------- reduced motion ----------
   A global 0.01ms kill-switch is the anti-pattern the guidance prohibits: it
   turns every colour fade into an instant toggle (a flash, not calm) while
   leaving delayed-visibility elements half-broken. Each animation family
   instead gets the minimum treatment it needs, and transitions that only
   convey state (colour/background) keep their fade. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto !important; }

  /* Entrance keyframes are redefined to fade-only: same names, so every
     animation shorthand keeps its timing and fill minus the travel. */
  @keyframes rise  { from { opacity: 0; } }
  @keyframes sumIn { from { opacity: 0; } }
  @keyframes ratIn { from { opacity: 0; } }
  @keyframes popIn { 0% { opacity: 0; } 60%, 100% { opacity: 1; } }

  /* Movement-only feedback: the wrong-answer shake and the correct-answer
     pop/flash carry nothing the colours do not already say. */
  @keyframes shake-x   { 0%, 100% { transform: none; } }
  @keyframes goodPop   { 0%, 60%, 100% { transform: none; } }
  @keyframes goodFlash { 0%, 100% { box-shadow: none; } }

  /* The checkmark draws itself in; its pre-animation state hides the stroke
     (stroke-dashoffset: 26), so show the finished mark immediately. */
  .feedback.good.pop-in .ic svg path { animation: none; stroke-dashoffset: 0; }

  /* Transition-driven movement drops the travel. Press-scale feedback and the
     docked-column slide become instant; fades paired with movement keep only
     the opacity component. */
  .iconbtn:active, .btn:active, .pill:active, .opt:active,
  .palette-btn:active, .calcbtn:active { transition-property: none; }
  .flagbtn .fsuffix, .toast, #qcard, .trendtip { transition-property: opacity; }
  #view, .rrow .detail { transition-property: none; }
}

/* ---------- tablet band (641-960) ----------
   The mastery grid's five columns need ~870px and the log table ~813px, so the
   phone reflow has to start earlier than 640px or these overflow the page. */
@media (min-width: 641px) and (max-width: 960px) {
  .mrow { display: flex; flex-wrap: wrap; gap: 8px 10px; }
}
@media (min-width: 641px) and (max-width: 1024px) {
  table.log { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; }
  table.log thead { position: sticky; top: 0; z-index: 1; }
}

/* ---------- phones: setup option groups ----------
   Order/Mode side-by-side leaves ~150px per pill, but the long labels are
   nowrap and need ~180px - so the groups stack and each pillset gets the
   full row (the short 'In order / Shuffle' pair still sits 2-up). */
@media (max-width: 760px) {
  .setgrid { grid-template-columns: 1fr; gap: 0; }
  .setgrid .setgroup + .setgroup { margin-top: -8px; }
}
@media (max-width: 640px) {
  .setgrid .pillset { grid-template-columns: minmax(0, 1fr); }
}

/* ---------- Desmos graphing calculator ----------
   Present only for math questions (see syncCalcButton in app.js). It floats over
   the page instead of taking a slot in the layout, so opening it never reflows
   the question the student is reading - the same way Bluebook's calculator sits
   above the question rather than beside it.

   Contrast note: in dark mode --accent is a LIGHT indigo (#818cf8), so the white
   text the light theme wants is nearly unreadable on it. The app already solves
   this for .btn.primary with a dark-mode override; this follows the same rule. */
.calcbtn {
  position: fixed; right: 20px; bottom: 20px; z-index: 90;
  /* A fixed control on a notched phone hangs over the home indicator without
     this. The max() keeps the 20px look on devices that report a 0 inset. */
  right: max(20px, env(safe-area-inset-right));
  bottom: max(20px, env(safe-area-inset-bottom));
  display: inline-flex; align-items: center; gap: 9px;
  min-height: 48px; padding: 0 18px 0 15px;
  background: var(--accent); color: #ffffff;
  border: 1px solid transparent; border-radius: var(--radius-full);
  font: 600 var(--text-sm)/1 var(--font-sans);
  cursor: pointer;
  box-shadow: 0 6px 16px -4px rgba(79, 70, 229, 0.45),
              0 2px 6px -2px rgba(24, 24, 27, 0.28);
  transition: background-color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease),
              box-shadow var(--dur-normal) var(--ease),
              transform var(--dur-normal) var(--ease-enter),
              opacity var(--dur-normal) var(--ease);
}
[data-theme="dark"] .calcbtn { color: #0f0f12; }
.calcbtn[hidden] { display: none; }
.calcbtn .ic { width: 18px; height: 18px; transition: transform var(--dur-normal) var(--ease-enter); }
.calcbtn:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 2px; }

/* Entrance. Played once when the button appears on a math question, so it is
   noticed rather than just being there. No fill-mode on purpose: with `both` the
   animation would keep owning `transform` after it finished and the hover lift
   would never apply. */
@keyframes calcPop {
  from { opacity: 0; transform: translateY(12px) scale(0.9); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
.calcbtn.enter { animation: calcPop var(--dur-slow) var(--ease-enter); }

/* The "it's over here" ring, on its own layer so it can animate box-shadow
   without touching the button's own transform or shadow. Two passes then it
   stops - a button that pulses forever is noise, not an affordance. */
.calcbtn::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  pointer-events: none; opacity: 0;
  box-shadow: 0 0 0 0 rgba(79, 70, 229, 0.5);
}
@keyframes calcRing {
  0%   { opacity: 1; box-shadow: 0 0 0 0 rgba(79, 70, 229, 0.5); }
  70%  { opacity: 1; box-shadow: 0 0 0 14px rgba(79, 70, 229, 0); }
  100% { opacity: 0; box-shadow: 0 0 0 14px rgba(79, 70, 229, 0); }
}
.calcbtn.hint { animation: calcPop var(--dur-slow) var(--ease-enter); }
.calcbtn.hint::after { animation: calcRing 1.5s var(--ease) 0.35s 2; }

@media (hover: hover) {
  .calcbtn:hover {
    background: var(--accent-hover);
    transform: translateY(-2px);
    box-shadow: 0 12px 26px -6px rgba(79, 70, 229, 0.5),
                0 4px 10px -4px rgba(24, 24, 27, 0.32);
  }
  .calcbtn:hover .ic { transform: rotate(-9deg) scale(1.08); }
}
.calcbtn:active { transform: translateY(0) scale(0.96); }

/* Open: drop to a quiet surface so the panel reads as the thing you are using,
   and let the icon settle into a small tilt. */
.calcbtn.on {
  background: var(--card); color: var(--text);
  border-color: var(--line-strong); box-shadow: var(--shadow-sm);
}
.calcbtn.on .ic { transform: rotate(-9deg); }
@media (hover: hover) {
  .calcbtn.on:hover { background: var(--bg-subtle); }
}

/* ---------- the panel ----------
   Hidden with opacity + visibility rather than [hidden]: display:none cannot be
   animated, and visibility alone still leaves the panel able to swallow clicks
   for the length of its delay, so pointer-events is switched immediately. */
.calcpanel {
  position: fixed; right: 20px; bottom: 80px; z-index: 95;
  width: min(440px, calc(100vw - 40px));
  height: min(620px, calc(100vh - 130px));
  height: min(620px, calc(100dvh - 130px));
  display: flex; flex-direction: column; overflow: hidden;
  /* A floating panel should not chain the page behind it: reaching its end
     would otherwise start scrolling the question underneath. */
  overscroll-behavior: contain;
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: 0 24px 48px -12px rgba(0, 0, 0, 0.42),
              0 8px 20px -8px rgba(0, 0, 0, 0.28);
  transform-origin: bottom right;
  opacity: 0; visibility: hidden; pointer-events: none;
  transform: translateY(12px) scale(0.96);
  transition: opacity var(--dur-normal) var(--ease),
              transform var(--dur-slow) var(--ease-enter),
              visibility 0s linear var(--dur-slow);
}
.calcpanel.open {
  opacity: 1; visibility: visible; pointer-events: auto;
  transform: none;
  transition: opacity var(--dur-normal) var(--ease),
              transform var(--dur-slow) var(--ease-enter),
              visibility 0s;
}
.calchead {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 8px 8px 8px 14px; border-bottom: 1px solid var(--line);
}
.calctitle { font-size: var(--text-sm); font-weight: 600; }
.calcclose {
  display: grid; place-items: center; width: 36px; height: 36px; padding: 0;
  background: none; border: 1px solid transparent;
  border-radius: var(--radius-sm); color: var(--muted); cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease),
              transform var(--dur-normal) var(--ease-enter);
}
.calcclose:hover { background: var(--bg-subtle); color: var(--text); transform: rotate(90deg); }
.calcclose:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.calcclose .ic { width: 16px; height: 16px; }
/* Desmos sizes itself to this box; min-height:0 is what lets a flex child shrink
   below its content instead of pushing the panel taller than the viewport. */
.calc-host { flex: 1; min-height: 0; }
.calc-msg { margin: 0; padding: 20px; color: var(--muted); font-size: var(--text-sm); line-height: 1.5; }

/* ---------- the question steps aside ----------
   A 440px panel floating over a 1120px column hides the right third of the stem
   and the long answer choices. Above 1100px there is room to give the column the
   panel's width back, so the card slides left and both are readable at once. The
   padding animates with the panel so the two moves read as one gesture.

   Only while the panel is DOCKED (bottom-right, never moved). Once a student has
   put it somewhere themselves, moving the question out from under it would be
   second-guessing them - see syncCalcDock in app.js.

   Below 1100px there is genuinely not room for both, so the panel overlays (and
   below 640px it takes the whole screen anyway). */
#view { transition: padding-right var(--dur-slow) var(--ease-enter), max-width var(--dur-slow) var(--ease-enter); }
@media (min-width: 1100px) {
  body.calc-docked #view { max-width: 1440px; padding-right: 500px; }
}

/* ---------- move + resize ----------
   The header is the drag handle and the corner is the resize grip, both driven by
   pointer events so mouse, pen and touch behave the same. touch-action:none is
   what stops the browser claiming the gesture for scrolling on a phone. */
.calchead {
  cursor: grab; touch-action: none;
  user-select: none; -webkit-user-select: none;
}
.calchead:active { cursor: grabbing; }
.calcgrip { display: inline-flex; gap: 2px; margin-right: 2px; color: var(--muted-light); }
.calcgrip i { width: 2px; height: 12px; border-radius: 1px; background: currentColor; opacity: .55; }
.calcreset {
  display: none; place-items: center; width: 30px; height: 30px; padding: 0;
  background: none; border: 1px solid transparent; border-radius: var(--radius-sm);
  color: var(--muted-light); cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
body.calc-custom .calcreset { display: grid; }
.calcreset:hover { background: var(--bg-subtle); color: var(--text); }
.calcreset:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.calcreset .ic { width: 15px; height: 15px; }

.calcresize {
  position: absolute; right: 0; bottom: 0; width: 24px; height: 24px;
  cursor: nwse-resize; touch-action: none; color: var(--muted-light);
  display: grid; place-items: center;
  /* It is a real <button> now (so it can be focused and driven from the
     keyboard), which brings the UA's button chrome with it. Reset it to the
     bare grip the CSS below draws. */
  padding: 0; border: 0; background: none; font: inherit;
  -webkit-appearance: none; appearance: none;
}
.calcresize:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.calcresize::after {
  content: ""; width: 9px; height: 9px; opacity: .65;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  border-bottom-right-radius: 3px;
}
.calcresize:hover { color: var(--accent); }
.calcresize:hover::after { opacity: 1; }
/* 22px is the drawn grip; on touch the target has to be 44px. A transparent
   overlay grows the hit area without moving the corner marks. */
@media (pointer: coarse) {
  .calcresize::before {
    content: ""; position: absolute; inset: -11px;
  }
}

/* While a gesture is running: no text selection, no cursor flicker, and the
   Desmos canvas must not swallow the moves. */
body.calc-gesture { user-select: none; -webkit-user-select: none; }
body.calc-gesture .calcpanel canvas { pointer-events: none; }

/* Phones: take the whole screen. A 440px card on a 390px viewport leaves a graph
   too small to read, and the on-screen keypad needs the room. It rises from the
   bottom instead of scaling out of a corner, which reads better full-bleed.
   Moving and resizing are off here - the panel IS the screen, so there is nothing
   to move it around, and a saved desktop geometry must not leak onto a phone. */
@media (max-width: 640px) {
  .calcbtn { right: 12px; bottom: 12px; padding: 0 15px; min-height: 46px; }
  .calclabel { display: none; }
  .calcpanel {
    inset: 0; width: auto; height: auto;
    border: none; border-radius: 0;
    transform-origin: bottom center;
    transform: translateY(16px);
  }
  .calcpanel.open { transform: none; }
  .calchead { padding-top: max(8px, env(safe-area-inset-top)); cursor: default; }
  .calcclose { width: 44px; height: 44px; }
  .calcgrip, .calcreset, .calcresize { display: none; }
}

/* Under reduced motion the entrance keyframes above are already fade-only, but
   the panel's own TRANSITIONS still move it. Drop the transform component and
   cut the visibility delay, which would otherwise leave an invisible panel on
   screen after a close. The two-pass attention ring on the button goes too. */
@media (prefers-reduced-motion: reduce) {
  .calcpanel, .calcpanel.open { transition-property: opacity, visibility; transition-delay: 0s !important; }
  .calcbtn.enter, .calcbtn.hint { animation: none; }
  .calcbtn.hint::after { animation: none; }
}

/* ==== JS-side guidance additions (do not reformat) ==== */

/* The reset confirmation <dialog>. app.js builds it once on first use and opens
   it with showModal(), so the UA already supplies the top layer, focus trap and
   Esc handling; closedby="any" adds backdrop-click dismiss. */
.gdialog {
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-lg);
  background: var(--card);
  color: var(--text);
  padding: 22px;
  max-width: 420px;
  box-shadow: var(--shadow);
}
.gdialog::backdrop { background: rgba(9, 9, 11, 0.45); }
.gdialog h2 { margin: 0 0 8px; font-size: var(--text-lg); }
.gdialog-text { margin: 0; color: var(--muted); font-size: var(--text-sm); line-height: 1.55; }
.gdialog-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 18px; }

/* Toast over the Popover API: the UA popover box (pinned with inset:0 and
   centered by margin:auto, Canvas background, solid border) would fight the
   fixed pill, so undo just what the UA sets that .toast does not - top and
   right go back to auto so .toast's left/bottom anchor the pill - and keep the
   exact .toast look. In the top layer there is no .show toggling; visibility
   comes from showPopover()/hidePopover(). */
.toast[popover] {
  top: auto;
  right: auto;
  margin: 0;
  border: none;
  overflow: visible;
  opacity: 1;
  transform: translateX(-50%) translateY(8px);
}
@media (max-width: 640px) {
  /* Mirror the legacy phone placement: edge to edge, no centering transform. */
  .toast[popover] { left: 16px; right: 16px; transform: none; }
}
