/* ==========================================================================
   Security+ Study App — Global Styles
   Single source of truth for design tokens (Section 7 / 12 / 13 of the spec).
   No component-specific styling belongs in this section — components define
   their own rules in later phases, referencing ONLY the custom properties
   below. Never hardcode a color, font, spacing, radius, or transition value
   directly in a component; add/extend a token here instead.
   ========================================================================== */

:root {
  /* --------------------------------------------------------------------
     Color — base surfaces
     -------------------------------------------------------------------- */
  --color-bg: #0a0e14;              /* page background — near-black navy */
  --color-bg-alt: #0d131c;          /* secondary background (e.g. sidebar) */
  --color-surface: #121926;         /* card / panel surface */
  --color-surface-raised: #1a2333;  /* hovered / elevated surface */
  --color-border: #232d40;          /* default hairline border */
  --color-border-strong: #2f3b52;   /* emphasized border / divider */

  /* Color — text */
  --color-text: #e6edf5;            /* primary body/heading text */
  --color-text-muted: #93a2b8;      /* secondary text, captions, meta */
  --color-text-faint: #5b6b85;      /* least prominent text, placeholders */

  /* Color — brand accent (electric cyan). Kept distinct from the semantic
     success/error colors below so "brand" never gets confused with
     "correct answer" (Section 12). */
  --color-accent: #22d3ee;
  --color-accent-hover: #67e2f5;
  --color-accent-muted: #17414c;    /* accent tint for subtle backgrounds */
  --color-on-accent: #04141a;       /* text placed on top of accent fills */

  /* Color — semantic feedback (reserved strictly for feedback states,
     never decoration, per Section 12) */
  --color-success: #4ade80;
  --color-success-muted: #16321f;
  --color-error: #f87171;
  --color-error-muted: #3a1a1a;
  --color-warning: #fbbf24;
  --color-warning-muted: #3a2c0f;

  /* Color — decorative icon-chip accents (js/domainVisuals.js, dashboard
     stat cards). Presentation only — distinct from the semantic feedback
     colors above, which stay strictly reserved for correct/incorrect/
     in-progress state (Section 12). Never repurpose one of THESE for a
     feedback state, or vice versa. */
  --icon-teal: #22d3ee;
  --icon-teal-muted: #123540;
  --icon-rose: #fb7185;
  --icon-rose-muted: #3a1a22;
  --icon-purple: #a78bfa;
  --icon-purple-muted: #241f3d;
  --icon-amber: #fb923c;
  --icon-amber-muted: #3a2712;
  --icon-blue: #60a5fa;
  --icon-blue-muted: #16283d;

  /* --------------------------------------------------------------------
     Typography
     -------------------------------------------------------------------- */
  --font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
  --font-mono: "JetBrains Mono", "Space Mono", ui-monospace, "SFMono-Regular",
    Menlo, Consolas, "Liberation Mono", monospace;

  --font-size-xs: 0.75rem;    /* 12px */
  --font-size-sm: 0.875rem;   /* 14px */
  --font-size-base: 1rem;     /* 16px */
  --font-size-md: 1.125rem;   /* 18px */
  --font-size-lg: 1.375rem;   /* 22px */
  --font-size-xl: 1.75rem;    /* 28px */
  --font-size-2xl: 2.25rem;   /* 36px */

  --line-height-tight: 1.2;
  --line-height-base: 1.55;

  --font-weight-regular: 400;
  --font-weight-medium: 500;
  --font-weight-bold: 700;

  /* --------------------------------------------------------------------
     Spacing scale
     -------------------------------------------------------------------- */
  --space-2xs: 0.25rem;  /* 4px */
  --space-xs: 0.5rem;    /* 8px */
  --space-sm: 0.75rem;   /* 12px */
  --space-md: 1rem;      /* 16px */
  --space-lg: 1.5rem;    /* 24px */
  --space-xl: 2rem;      /* 32px */
  --space-2xl: 3rem;     /* 48px */
  --space-3xl: 4rem;     /* 64px */

  /* --------------------------------------------------------------------
     Shape
     -------------------------------------------------------------------- */
  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 16px;
  --radius-full: 999px;

  /* --------------------------------------------------------------------
     Elevation
     -------------------------------------------------------------------- */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.35);
  --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.4);
  --shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.45);

  /* --------------------------------------------------------------------
     Motion (Section 13) — every animation in the app references one of
     these three; no component invents its own one-off duration/easing.
     Collapsed to near-zero under prefers-reduced-motion below (Section 14).
     -------------------------------------------------------------------- */
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  /* Trimmed down from an earlier 150/250/400ms — small, in-page reveals
     (an answer's correct/incorrect color, a toast, a menu) should feel
     like they respond instantly to a click, with the transition only
     smoothing OUT the change, never delaying when it starts. */
  --transition-fast: 100ms var(--ease-out);   /* hover, button press */
  --transition-base: 160ms var(--ease-out);   /* small in-page reveals */
  --transition-slow: 350ms var(--ease-out);   /* progress bar fills */
  /* A full-screen route change (router.js swapping .main-content's
     content entirely, e.g. switching domains) is a much bigger visual
     event than a small reveal — at --transition-base's snappy 160ms it
     read as an instant hard cut rather than a fade, even though a fade
     genuinely was playing. This is deliberately its own token, not reused
     from --transition-base above, so route fades can stay clearly visible
     while in-page reveals stay snappy — the two serve different purposes
     (Section 13: "screens change... smooth fade" vs "instantly respond to
     clicks"), not one duration doing both jobs. */
  --transition-page: 220ms var(--ease-out);

  /* Continuous decorative loop (app-loading__spinner) — a distinct category
     from the three one-shot transition tokens above (this one repeats
     forever rather than playing once), but still a single named token
     here rather than a bare "900ms" living in the component rule below
     (Section 7). Also collapsed under prefers-reduced-motion below via the
     universal animation-duration override, same as everything else. */
  --anim-spin-duration: 900ms;

  /* --------------------------------------------------------------------
     Layout
     -------------------------------------------------------------------- */
  --sidebar-width: 260px;
  --topbar-height: 64px;
  /* Caps + centers each view's content column so it reads as a
     deliberate, consistently-margined page rather than text/cards
     hugging the sidebar on one side and running off into empty space on
     the other. */
  --content-max-width: 1600px;
  --focus-ring: 0 0 0 3px var(--color-accent-muted), 0 0 0 1px var(--color-accent);

  /* --------------------------------------------------------------------
     Background texture (Section 12) — a faint circuit-style grid, applied
     once below on <body> only. It reads only in large empty canvas areas
     (page padding, gaps between cards/rows) because every card/sidebar/
     surface element already paints its own opaque --color-surface/
     --color-bg-alt background on top of it — never behind text, and never
     at an opacity that could move text/background contrast (Section 14).
     -------------------------------------------------------------------- */
  --bg-grid-size: 44px;
  --bg-grid-line: rgba(103, 226, 245, 0.05);
}

/* Respect the OS-level "reduce motion" preference: collapse every timing
   token to near-zero so nothing meaningfully animates, without having to
   special-case every individual transition/animation declaration. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --transition-fast: 0.01ms linear;
    --transition-base: 0.01ms linear;
    --transition-slow: 0.01ms linear;
    --transition-page: 0.01ms linear;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ==========================================================================
   Reset
   ========================================================================== */

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

/* Any normal-importance author rule that sets `display` on an element
   (e.g. `.question-card { display: flex; }`) beats the browser's built-in
   `[hidden] { display: none }` UA-stylesheet rule regardless of
   specificity — origin/importance is resolved before specificity in the
   cascade. That silently broke every `el.hidden = true/false` toggle in
   this app (examMode.js/domain.js's one-question-at-a-time stepper,
   dashboard.js's backup-menu panel) wherever the component's own CSS also
   declares a `display`. This restores the attribute's actual meaning
   app-wide — the `!important` here is intentional and standard practice
   (most CSS resets carry this same rule) precisely because no component
   rule should ever need to "fight" a `hidden` element back into view. */
[hidden] {
  display: none !important;
}

html {
  color-scheme: dark;
  -webkit-text-size-adjust: 100%;
  /* Reserves the scrollbar's width permanently, whether or not the current
     route's content actually overflows. Without this, navigating between a
     tall route (scrollbar present) and a short one (scrollbar absent) shifts
     the viewport's available width by the scrollbar's own width, which was
     enough to flip .stat-cards' auto-fit grid between a 3-column and
     2-column layout on every navigation — the cards visibly "jumped". */
  scrollbar-gutter: stable;
}

body,
h1,
h2,
h3,
h4,
p,
figure,
blockquote,
dl,
dd {
  margin: 0;
}

body {
  min-height: 100vh;
  background-color: var(--color-bg);
  /* Section 12's "subtle, low-opacity grid or circuit-line pattern" — a
     faint two-line grid using the --bg-grid-* tokens above. Lives on <body>
     underneath everything: .sidebar/.question-card/.stat-card/etc. all
     paint their own opaque surface color over it, so in practice it only
     ever shows through in genuinely empty canvas space, never behind text
     (Section 12/14). */
  background-image: linear-gradient(var(--bg-grid-line) 1px, transparent 1px),
    linear-gradient(90deg, var(--bg-grid-line) 1px, transparent 1px);
  background-size: var(--bg-grid-size) var(--bg-grid-size);
  color: var(--color-text);
  font-family: var(--font-sans);
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-regular);
  line-height: var(--line-height-base);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img,
picture,
svg {
  display: block;
  max-width: 100%;
}

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

button {
  background: none;
  border: none;
  cursor: pointer;
}

a {
  color: var(--color-accent);
  text-decoration: none;
  transition: color var(--transition-fast);
}

a:hover {
  color: var(--color-accent-hover);
}

ul,
ol {
  list-style: none;
  padding: 0;
  margin: 0;
}

/* Consistent, visible keyboard focus everywhere (Section 14). Mouse users
   don't see this thanks to :focus-visible; keyboard users always do. */
:focus {
  outline: none;
}

:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
  border-radius: var(--radius-sm);
}

/* ==========================================================================
   Typography
   ========================================================================== */

h1,
h2,
h3,
h4 {
  font-family: var(--font-mono);
  font-weight: var(--font-weight-bold);
  line-height: var(--line-height-tight);
  letter-spacing: -0.01em;
  color: var(--color-text);
}

h1 {
  font-size: var(--font-size-2xl);
}

h2 {
  font-size: var(--font-size-xl);
}

h3 {
  font-size: var(--font-size-lg);
}

h4 {
  font-size: var(--font-size-md);
}

p {
  color: var(--color-text-muted);
}

code,
pre {
  font-family: var(--font-mono);
}

/* ==========================================================================
   Skip link (Section 14 — keyboard accessibility)
   ========================================================================== */

.skip-link {
  position: absolute;
  top: -100%;
  left: var(--space-md);
  z-index: 100;
  padding: var(--space-xs) var(--space-md);
  background-color: var(--color-accent);
  color: var(--color-on-accent);
  font-family: var(--font-mono);
  font-weight: var(--font-weight-medium);
  border-radius: var(--radius-sm);
  transition: top var(--transition-fast);
}

.skip-link:focus {
  top: var(--space-md);
}

/* ==========================================================================
   App shell — base layout scaffolding only. Sidebar/topbar/card component
   styling is added in later phases (js/components/navSidebar.js etc.).
   ========================================================================== */

.app-shell {
  display: flex;
  min-height: 100vh;
}

.sidebar {
  flex: 0 0 var(--sidebar-width);
  width: var(--sidebar-width);
  background-color: var(--color-bg-alt);
  border-right: 1px solid var(--color-border);
  /* Pins the sidebar to exactly the viewport height and keeps it in view
     while .main-content scrolls, rather than relying on flexbox's default
     stretch-to-tallest-sibling behavior — on any page taller than one
     viewport (e.g. the dashboard's full domain list), that left the
     sidebar's own background/border ending partway down the page instead
     of running the full height the user actually sees and scrolls
     through. align-self: flex-start is required alongside position:
     sticky for a flex item — stretch (the flex default) overrides sticky
     positioning's own sizing otherwise. */
  position: sticky;
  top: 0;
  align-self: flex-start;
  height: 100vh;
  overflow-y: auto;
  /* `position: sticky` unconditionally creates a new stacking context on
     this element (regardless of z-index) — without an explicit z-index
     here, that context defaults to the same "z-index: auto" tier as
     .main-content's own stacking context (triggered by its `transform`
     above), and .main-content wins that tie because it comes later in the
     DOM. That let the domain/exam page's content paint OVER the sidebar's
     compact-mode overlay panel (z-index: 151, but scoped inside THIS
     stacking context) instead of under it. An explicit z-index here fixes
     the comparison at the .app-shell level, where it actually matters. */
  z-index: 10;
}

/* View-transition fade (Section 13, --transition-page) lives on the same
   rule as the base layout below — router.js toggles `.is-transitioning`
   off/on around each route render, the ONE place this fade is defined. */
.main-content {
  flex: 1 1 auto;
  min-width: 0;
  /* Still tight enough to fit the dashboard's content within one screen
     without scrolling, just slightly more generous than the first pass. */
  padding: var(--space-lg) var(--space-2xl);
  /* .main-content itself is stretched to the sidebar's full 100vh height
     (flex row default). When the current view's content is SHORTER than
     that (the dashboard, once compacted), it was sitting flush at the
     top with all the leftover space dumped at the bottom. Centering it
     vertically distributes any leftover space evenly above and below
     instead — done via `margin: auto` on each view-root class itself
     (.dashboard etc., below/elsewhere in this file), not
     `justify-content` here, specifically because flex auto-margins
     collapse to 0 (falling back to normal top alignment) when content is
     TALLER than the container, with no extra keyword needed — unlike
     `justify-content: center`, which can push oversized content up out of
     scroll reach. This still needs `display: flex` here for those child
     auto-margins to take effect at all. */
  display: flex;
  flex-direction: column;
  opacity: 1;
  transform: translateY(0);
  transition: opacity var(--transition-page), transform var(--transition-page);
}

.main-content:focus-visible {
  box-shadow: none; /* programmatic focus target after route change; no visible ring */
}

.main-content.is-transitioning {
  opacity: 0;
  transform: translateY(10px);
}

/* Collapse the sidebar to a top bar below the mobile breakpoint (full
   hamburger/nav behavior implemented in the polish phase, Section 15). */
@media (max-width: 768px) {
  .app-shell {
    flex-direction: column;
  }

  .sidebar {
    flex: 0 0 auto;
    width: 100%;
    height: auto;
    /* On mobile the sidebar is a compact top bar, not a full-height side
       column — sticky-to-100vh (set above for desktop) would otherwise
       pin a full-viewport-tall element at the top of the page here. */
    position: static;
    border-right: none;
    border-bottom: 1px solid var(--color-border);
  }

  .main-content {
    padding: var(--space-lg);
  }
}

/* ==========================================================================
   Utilities
   ========================================================================== */

.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;
}

/* ==========================================================================
   Buttons — shared by app.js (retry), dashboard.js (continue/recommended
   CTAs), and every later phase's export/import/reset/submit actions.
   ========================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  padding: var(--space-xs) var(--space-lg);
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  font-family: var(--font-mono);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  line-height: 1;
  cursor: pointer;
  transition: background-color var(--transition-fast), border-color var(--transition-fast),
    color var(--transition-fast), transform var(--transition-fast);
}

.btn:active {
  transform: translateY(1px);
}

/* First used by examMode.js's Previous/Next navigation at the first/last
   question (Section 7 — a general .btn rule so any future disabled button
   elsewhere in the app gets the same treatment for free). */
.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

.btn--primary {
  background-color: var(--color-accent);
  color: var(--color-on-accent);
}

.btn--primary:hover {
  background-color: var(--color-accent-hover);
}

.btn--secondary {
  background-color: transparent;
  border-color: var(--color-border-strong);
  color: var(--color-text);
}

.btn--secondary:hover {
  border-color: var(--color-accent);
  color: var(--color-accent);
}

/* Trailing chevron (continue-card / recommended-next CTAs) — a small
   nudge-right on hover, using the same lift language --transition-fast
   already gives every other hover state in this file. */
.btn__chevron {
  display: inline-flex;
  width: 14px;
  height: 14px;
  transition: transform var(--transition-fast);
}

.btn:hover .btn__chevron {
  transform: translateX(2px);
}

/* ==========================================================================
   App-level loading / error states (js/app.js)
   ========================================================================== */

/* Rendered directly by app.js, outside router.js's control (the router
   isn't initialized yet at this point) — so these get their own fade-in
   (--transition-base, Section 13) rather than popping in abruptly, using
   the same token every other view-swap fade in the app uses. */
@keyframes fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

.app-loading,
.app-error {
  animation: fade-in var(--transition-base) var(--ease-out) both;
}

.app-loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-md);
  max-width: 420px;
  margin: var(--space-3xl) auto;
  text-align: center;
  color: var(--color-text-muted);
}

.app-loading__spinner {
  width: 32px;
  height: 32px;
  border-radius: var(--radius-full);
  border: 3px solid var(--color-border-strong);
  border-top-color: var(--color-accent);
  animation: spin var(--anim-spin-duration) linear infinite;
  /* The global prefers-reduced-motion rule at :root (top of this file)
     already forces every animation's duration/iteration-count down via
     !important, so this spinner is automatically frozen rather than
     spinning under that OS preference — no separate override needed. */
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

.app-error {
  max-width: 480px;
  margin: var(--space-3xl) auto;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-sm);
}

.app-error__detail {
  font-family: var(--font-mono);
  font-size: var(--font-size-xs);
  /* --color-text-muted (7.46:1 on --color-bg), not --color-text-faint
     (3.58:1) — this is a real error message, not incidental/placeholder
     text, so it needs to clear WCAG AA's 4.5:1 body-text threshold
     (Section 14). */
  color: var(--color-text-muted);
  word-break: break-word;
}

.app-error .btn {
  margin-top: var(--space-sm);
}

/* ==========================================================================
   Route placeholder / error (js/router.js) — shown for routes whose view
   module hasn't been built yet, or whose render() threw.
   ========================================================================== */

.route-placeholder {
  max-width: 560px;
  margin: var(--space-3xl) auto;
  text-align: center;
}

.route-placeholder__eyebrow {
  font-family: var(--font-mono);
  font-size: var(--font-size-sm);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-accent);
  margin-bottom: var(--space-sm);
}

.route-placeholder--error .route-placeholder__eyebrow {
  color: var(--color-error);
}

/* ==========================================================================
   Progress bar (js/components/progressBar.js) — the ONE implementation,
   reused on the dashboard and (later) domain/exam-results pages.
   ========================================================================== */

.progress-bar {
  width: 100%;
}

.progress-bar__track {
  width: 100%;
  height: 8px;
  border-radius: var(--radius-full);
  background-color: var(--color-border);
  overflow: hidden;
}

.progress-bar__fill {
  height: 100%;
  border-radius: var(--radius-full);
  background-color: var(--color-accent);
  width: 0%;
  transition: width var(--transition-slow);
}

/* ==========================================================================
   Sidebar nav (js/components/navSidebar.js)
   ========================================================================== */

.sidebar-nav {
  display: flex;
  flex-direction: column;
  /* No height here — .sidebar (the SAME element; navSidebar.js adds both
     classes to one <nav>) already sets height: 100vh. A `height: 100%`
     here used to win the cascade (same specificity, later in the file)
     and, since .app-shell has no definite height for a percentage to
     resolve against, collapsed back to the sidebar's own content height —
     which is exactly why it stopped partway down the page instead of
     reaching the bottom. */
  padding: var(--space-lg) var(--space-md);
}

.sidebar-nav__brand {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-xs) var(--space-sm);
  margin-bottom: var(--space-lg);
  color: var(--color-text);
  font-family: var(--font-mono);
  font-weight: var(--font-weight-bold);
  font-size: var(--font-size-sm);
}

.sidebar-nav__brand-mark {
  display: inline-flex;
  width: 22px;
  height: 22px;
  color: var(--color-accent);
}

.sidebar-nav__brand-mark svg {
  width: 100%;
  height: 100%;
}

.sidebar-nav__brand-text-wrap {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.sidebar-nav__brand-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sidebar-nav__brand-tagline {
  font-family: var(--font-sans);
  font-weight: var(--font-weight-regular);
  font-size: var(--font-size-xs);
  color: var(--color-text-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sidebar-nav__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}

.sidebar-nav__link {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-sm);
  border-radius: var(--radius-sm);
  color: var(--color-text-muted);
  border-left: 2px solid transparent;
  transition: background-color var(--transition-fast), color var(--transition-fast),
    border-color var(--transition-fast), transform var(--transition-fast);
}

.sidebar-nav__link:hover {
  background-color: var(--color-surface);
  color: var(--color-text);
}

/* Pressed/tactile feedback (Section 13's audit explicitly names "nav
   links" alongside buttons/domain rows/question options). */
.sidebar-nav__link:active {
  transform: translateY(1px);
}

.sidebar-nav__link.is-active {
  background-color: var(--color-accent-muted);
  color: var(--color-accent);
  border-left-color: var(--color-accent);
}

.sidebar-nav__icon {
  display: inline-flex;
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
}

.sidebar-nav__icon svg {
  width: 100%;
  height: 100%;
}

/* Per-domain accent (js/domainVisuals.js): the icon keeps its own domain
   color as an identity marker even when the link is active — active state
   is still unambiguous via the label text color + left border above, so
   this doesn't compete with or duplicate that signal (Section 14). */
.sidebar-nav__icon--teal {
  color: var(--icon-teal);
}

.sidebar-nav__icon--rose {
  color: var(--icon-rose);
}

.sidebar-nav__icon--purple {
  color: var(--icon-purple);
}

.sidebar-nav__icon--amber {
  color: var(--icon-amber);
}

.sidebar-nav__icon--blue {
  color: var(--icon-blue);
}

.sidebar-nav__label {
  font-size: var(--font-size-sm);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@media (max-width: 768px) {
  .sidebar-nav {
    padding: var(--space-sm);
  }

  .sidebar-nav__list {
    flex-direction: row;
    flex-wrap: wrap;
  }

  /* Each item gets a target width (flex-basis) but is allowed to shrink
     all the way to 0 (min-width: 0, overriding the flex default of
     "auto" which would otherwise floor its shrink at the label's full
     unwrapped text width) so a long domain name never forces the item —
     and the page — wider than the viewport (Section 15's ~360px floor).
     Domain names that don't fit ellipsize via .sidebar-nav__label below
     instead of overflowing. */
  .sidebar-nav__item {
    flex: 1 1 140px;
    min-width: 0;
  }

  .sidebar-nav__link {
    width: 100%;
    border-left: none;
    border-bottom: 2px solid transparent;
  }

  .sidebar-nav__label {
    min-width: 0;
  }

  .sidebar-nav__link.is-active {
    border-left-color: transparent;
    border-bottom-color: var(--color-accent);
  }
}

/* --------------------------------------------------------------------
   Sidebar footer: "Keep going" tip + maker credit (js/components/
   navSidebar.js's buildFooter()). Pinned to the bottom of the sidebar
   column via margin-top: auto, whether the nav list above is short or
   long — matches this file's existing flex-column sidebar layout, no new
   positioning scheme needed.
   -------------------------------------------------------------------- */

.sidebar-nav__footer {
  margin-top: auto;
  padding-top: var(--space-lg);
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.sidebar-nav__tip {
  display: flex;
  align-items: flex-start;
  gap: var(--space-sm);
  padding: var(--space-md);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}

.sidebar-nav__tip-heading {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-text);
  margin-bottom: 2px;
}

.sidebar-nav__tip-message {
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
  line-height: var(--line-height-base);
}

.sidebar-nav__credit {
  text-align: center;
  padding: var(--space-xs);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  font-size: var(--font-size-xs);
  color: var(--color-text-faint);
}

.sidebar-nav__credit strong {
  color: var(--color-text-muted);
  font-weight: var(--font-weight-medium);
}

/* --------------------------------------------------------------------
   Question-mode compact sidebar (js/components/navSidebar.js). Hidden by
   default; only shown on the domain/exam routes, at every viewport width
   (deliberately not scoped to a mobile media query — see that module's
   file header). `.sidebar-nav__panel` normally participates directly in
   `.sidebar-nav`'s own flex layout (display: contents — the brand/list
   markup is unchanged from the non-compact case); in compact mode it
   becomes a real, fixed-position slide-in overlay instead.
   -------------------------------------------------------------------- */

.sidebar-nav__panel {
  display: contents;
}

.sidebar-nav__toggle {
  display: none;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  padding: 0;
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border);
  background-color: var(--color-surface);
  color: var(--color-text);
  transition: background-color var(--transition-fast), border-color var(--transition-fast),
    color var(--transition-fast), transform var(--transition-fast);
}

.sidebar-nav__toggle svg {
  width: 20px;
  height: 20px;
}

.sidebar-nav__toggle:hover {
  border-color: var(--color-accent);
  color: var(--color-accent);
}

.sidebar-nav__toggle:active {
  transform: translateY(1px);
}

.sidebar-nav__backdrop {
  display: none;
}

.sidebar.is-compact {
  flex: 0 0 auto;
  width: auto;
  padding: var(--space-sm);
  background-color: transparent;
  border-right: none;
  border-bottom: none;
}

.sidebar.is-compact .sidebar-nav__toggle {
  display: inline-flex;
}

.sidebar.is-compact .sidebar-nav__backdrop {
  display: block;
  position: fixed;
  inset: 0;
  z-index: 150;
  background-color: rgba(4, 8, 14, 0.6);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--transition-base);
}

.sidebar.is-compact .sidebar-nav__backdrop.is-open {
  opacity: 1;
  pointer-events: auto;
}

.sidebar.is-compact .sidebar-nav__panel {
  display: flex;
  flex-direction: column;
  position: fixed;
  top: 0;
  left: 0;
  z-index: 151;
  height: 100vh;
  width: min(280px, 85vw);
  padding: var(--space-lg) var(--space-md);
  background-color: var(--color-bg-alt);
  border-right: 1px solid var(--color-border);
  box-shadow: var(--shadow-lg);
  transform: translateX(-100%);
  transition: transform var(--transition-base);
  overflow-y: auto;
}

.sidebar.is-compact .sidebar-nav__panel.is-open {
  transform: translateX(0);
}

/* Inside the overlay, always lay the nav out as a plain vertical list —
   overrides the ≤768px horizontal-row treatment above, which would
   otherwise still apply based on viewport width alone. Two chained class
   selectors out-specificities that single-class media-query rule
   regardless of source order, so this applies at every width. */
.sidebar.is-compact .sidebar-nav__list {
  flex-direction: column;
  flex-wrap: nowrap;
}

.sidebar.is-compact .sidebar-nav__item {
  flex: initial;
  min-width: 0;
}

.sidebar.is-compact .sidebar-nav__link {
  width: auto;
  border-left: 2px solid transparent;
  border-bottom: none;
}

.sidebar.is-compact .sidebar-nav__link.is-active {
  border-left-color: var(--color-accent);
  border-bottom-color: transparent;
}

/* ==========================================================================
   Icon chips — the ONE decorative icon-in-a-colored-box treatment (Section
   7), used by dashboard.js's stat cards / domain rows / recommended-next,
   and navSidebar.js's per-domain nav icons + footer tip icon. Color comes
   from a `.icon-chip--{accent}` modifier matching js/domainVisuals.js's
   accent names; size from an optional `.icon-chip--sm`/`--lg` modifier
   (default matches the stat-card/domain-row size).
   ========================================================================== */

.icon-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-md);
}

.icon-chip svg {
  width: 20px;
  height: 20px;
}

.icon-chip--sm {
  width: 28px;
  height: 28px;
  border-radius: var(--radius-sm);
}

.icon-chip--sm svg {
  width: 15px;
  height: 15px;
}

.icon-chip--lg {
  width: 56px;
  height: 56px;
  border-radius: var(--radius-lg);
}

.icon-chip--lg svg {
  width: 28px;
  height: 28px;
}

.icon-chip--teal {
  background-color: var(--icon-teal-muted);
  color: var(--icon-teal);
}

.icon-chip--rose {
  background-color: var(--icon-rose-muted);
  color: var(--icon-rose);
}

.icon-chip--purple {
  background-color: var(--icon-purple-muted);
  color: var(--icon-purple);
}

.icon-chip--amber {
  background-color: var(--icon-amber-muted);
  color: var(--icon-amber);
}

.icon-chip--blue {
  background-color: var(--icon-blue-muted);
  color: var(--icon-blue);
}

/* ==========================================================================
   Dashboard (js/views/dashboard.js) — Section 9 reference-mapping table
   ========================================================================== */

/* Every top-level view (this one, .domain-view/.review-view below,
   .exam-view, .exam-results) is capped at --content-max-width and
   centered (`margin: 0 auto`) within .main-content, so there's equal,
   consistent breathing room on both sides rather than content running
   flush against the sidebar on the left. .stat-card etc. additionally
   cap their own width so cards stay well-proportioned even at this
   column's widest. */
.dashboard {
  width: 100%;
  max-width: var(--content-max-width);
  margin: auto;
  display: flex;
  flex-direction: column;
  /* Still fits one screen height without scrolling, just a touch more
     breathing room between sections than the first compact pass. */
  gap: var(--space-lg);
}

.dashboard__heading {
  margin-bottom: var(--space-2xs);
}

.dashboard__subtitle {
  color: var(--color-text-muted);
  margin-bottom: calc(-1 * var(--space-sm));
}

.dashboard__section h2 {
  margin-bottom: var(--space-md);
}

.dashboard__topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-md);
}

.search-box {
  width: 100%;
  max-width: 420px;
  flex: 1 1 260px;
}

.search-box__input {
  width: 100%;
  padding: var(--space-sm) var(--space-md);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  color: var(--color-text);
  transition: border-color var(--transition-fast), background-color var(--transition-fast);
}

.search-box__input::placeholder {
  /* --color-text-muted, not --color-text-faint (Section 14 — 7.46:1 vs
     3.58:1 against --color-bg; automated a11y audits treat placeholder
     text as real text for the 4.5:1 AA threshold, not exempt as
     purely-decorative). */
  color: var(--color-text-muted);
}

.search-box__input:hover {
  border-color: var(--color-border-strong);
}

.search-box__input:focus-visible {
  border-color: var(--color-accent);
}

.stat-cards {
  display: grid;
  /* A fixed column count switched at explicit breakpoints (matching the
     rest of this file's breakpoint-driven components), not a continuous
     auto-fit/minmax — auto-fit recalculates against the container's exact
     pixel width, which made the cards reflow (and visibly jump) from small,
     incidental width changes between routes. A discrete breakpoint only
     reflows at that one known width, consistently. */
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-md);
  /* The row itself fills the full container width (no capped/centered
     layout at the page level — see .dashboard etc. below), but each
     column's card is left-aligned within its track rather than
     stretching to fill it — otherwise a 1fr track on a very wide screen
     stretches each card far past its icon+number+label content, reading
     as a big empty void inside the card. Left-aligning caps each card at
     its own max-width (below) and lets the leftover track width sit as
     extra gap after it instead of dead space inside it. */
  justify-items: start;
}

@media (max-width: 900px) {
  .stat-cards {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 480px) {
  .stat-cards {
    grid-template-columns: 1fr;
  }
}

.stat-card {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-lg);
  width: 100%;
  max-width: 420px;
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
}

.stat-card__value {
  font-family: var(--font-mono);
  font-size: var(--font-size-2xl);
  font-weight: var(--font-weight-bold);
  color: var(--color-text);
  margin-bottom: var(--space-2xs);
}

.stat-card__label {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

.continue-card,
.welcome-banner {
  padding: var(--space-lg) var(--space-xl);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
}

.continue-card {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  overflow: hidden;
  /* A subtle glow emanating from where the illustration sits, fading back
     to the plain surface color toward the text — distinguishes this as a
     highlighted "hero" card rather than a plain stat-card/domain-row
     surface, matching the reference design's emphasis on this card. */
  background: radial-gradient(120% 140% at 88% 30%, var(--icon-teal-muted) 0%, var(--color-surface) 55%);
  border-color: var(--color-border-strong);
}

.continue-card__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  flex: 1 1 auto;
  min-width: 0;
  max-width: 480px;
}

.continue-card__illustration {
  flex: 0 0 auto;
  /* Pins the illustration to the card's right edge regardless of how wide
     the card itself grows — .continue-card__body caps out at 480px for
     text readability, so without this the illustration would sit right
     after the text instead of filling out the rest of a wide card. */
  margin-left: auto;
  width: 120px;
  height: 120px;
  color: var(--color-accent);
  opacity: 0.5;
}

.continue-card__illustration svg {
  width: 100%;
  height: 100%;
}

@media (max-width: 640px) {
  .continue-card__illustration {
    display: none;
  }
}

.continue-card__eyebrow {
  font-family: var(--font-mono);
  font-size: var(--font-size-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-accent);
}

.continue-card__title {
  margin: 0;
}

.continue-card__meta {
  font-size: var(--font-size-sm);
}

.continue-card__cta {
  align-self: flex-start;
  margin-top: var(--space-2xs);
}

.welcome-banner h2 {
  margin-bottom: var(--space-xs);
}

.domain-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.domain-row__link {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  padding: var(--space-md) var(--space-lg);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  transition: background-color var(--transition-fast), border-color var(--transition-fast),
    transform var(--transition-fast);
}

.domain-row__link:hover {
  background-color: var(--color-surface-raised);
  border-color: var(--color-border-strong);
  transform: translateY(-1px);
}

/* Pressed state (Section 13's audit explicitly names "domain rows"):
   cancels the hover lift rather than inventing a second direction, so a
   press reads as "settling back down" from the hover state. */
.domain-row__link:active {
  transform: translateY(0);
}

.domain-row__info {
  flex: 1 1 220px;
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  min-width: 0;
}

.domain-row__name {
  color: var(--color-text);
  font-weight: var(--font-weight-medium);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.domain-row__count {
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
  font-family: var(--font-mono);
}

.domain-row__progress {
  flex: 1 1 160px;
  max-width: 220px;
}

.domain-row__chevron {
  display: inline-flex;
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  color: var(--color-text-faint);
  transition: transform var(--transition-fast), color var(--transition-fast);
}

.domain-row__link:hover .domain-row__chevron {
  color: var(--color-accent);
  transform: translateX(2px);
}

@media (max-width: 560px) {
  .domain-row__chevron {
    display: none;
  }
}

.empty-state {
  padding: var(--space-md);
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
}

.recommended-next__card {
  display: flex;
  align-items: flex-start;
  gap: var(--space-lg);
  padding: var(--space-lg) var(--space-xl);
  /* Same "hero card" glow treatment as .continue-card above (Section 7 —
     both are highlighted cards, so they share the same visual language). */
  background: radial-gradient(120% 160% at 92% 70%, var(--icon-teal-muted) 0%, var(--color-surface) 55%);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.recommended-next__body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

/* Without this, the CTA (a flex child of the column above) defaults to
   align-self: stretch and fills the full card width edge-to-edge instead
   of sizing to its own label — the same fix .continue-card__cta already
   has for the identical layout shape. */
.recommended-next__cta {
  align-self: flex-start;
  margin-top: var(--space-2xs);
}

/* Two chained classes so this beats the general `.dashboard__section h2`
   margin rule above (equal-or-higher specificity, not source order — see
   the scrollbar-gutter/stat-cards comment earlier in this file for the
   same reasoning applied to a layout bug rather than a style override). */
.recommended-next__body .recommended-next__eyebrow {
  font-family: var(--font-mono);
  font-size: var(--font-size-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-accent);
  margin: 0;
}

.recommended-next__illustration {
  flex: 0 0 auto;
  align-self: center;
  width: 130px;
  height: 100px;
  color: var(--color-accent);
  opacity: 0.4;
}

.recommended-next__illustration svg {
  width: 100%;
  height: 100%;
}

@media (max-width: 640px) {
  .recommended-next__illustration {
    display: none;
  }
}

.recommended-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  margin-bottom: var(--space-md);
}

.recommended-item__link {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-md);
  padding: var(--space-sm) var(--space-md);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  transition: background-color var(--transition-fast), border-color var(--transition-fast),
    transform var(--transition-fast);
}

.recommended-item__link:hover {
  background-color: var(--color-surface-raised);
  border-color: var(--color-border-strong);
}

.recommended-item__link:active {
  transform: translateY(1px);
}

.recommended-item__text {
  color: var(--color-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.recommended-item__meta {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
}

@media (max-width: 480px) {
  .recommended-item__link {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-2xs);
  }
}

/* ==========================================================================
   Status indicator (js/components/statusIndicator.js) — the ONE
   correct/incorrect/unattempted badge, color PAIRED with an icon + text
   label (Section 14 — never color alone).
   ========================================================================== */

.status-indicator {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  padding: var(--space-2xs) var(--space-sm);
  border-radius: var(--radius-full);
  border: 1px solid transparent;
  font-family: var(--font-mono);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  white-space: nowrap;
  transition: background-color var(--transition-fast), border-color var(--transition-fast),
    color var(--transition-fast);
}

.status-indicator__icon {
  display: inline-flex;
  width: 14px;
  height: 14px;
  flex: 0 0 auto;
}

.status-indicator__icon svg {
  width: 100%;
  height: 100%;
}

.status-indicator--correct {
  background-color: var(--color-success-muted);
  border-color: var(--color-success);
  color: var(--color-success);
}

.status-indicator--incorrect {
  background-color: var(--color-error-muted);
  border-color: var(--color-error);
  color: var(--color-error);
}

.status-indicator--unattempted {
  background-color: var(--color-surface);
  border-color: var(--color-border-strong);
  color: var(--color-text-muted);
}

/* ==========================================================================
   Question card (js/components/questionCard.js) — the ONE standard MC
   question renderer, reused by domain pages (this phase) and, later, Exam
   Mode + Review (showFeedback flag controls the reveal, Section 7).
   ========================================================================== */

.question-card {
  padding: var(--space-lg);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.question-card__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
}

.question-card__number {
  font-family: var(--font-mono);
  font-size: var(--font-size-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-muted);
}

.question-card__text {
  color: var(--color-text);
  font-size: var(--font-size-md);
  line-height: var(--line-height-base);
}

.question-card__options {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  border: none;
  margin: 0;
  padding: 0;
}

/* --option-transition (Section 13): defaults to --transition-fast so the
   instant "is-selected" highlight snaps in briskly; the is-revealed rule
   below swaps it to --transition-base so the SAME background/border-color
   transition plays at the slower, more deliberate "reveal" pace once the
   correct/incorrect state is shown — one property, two durations, no
   invented third timing value. */
.question-card__option {
  --option-transition: var(--transition-fast);
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  background-color: var(--color-bg-alt);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: background-color var(--option-transition), border-color var(--option-transition),
    transform var(--transition-fast);
}

.question-card__options.is-revealed .question-card__option {
  --option-transition: var(--transition-base);
}

.question-card__option:hover {
  border-color: var(--color-border-strong);
  background-color: var(--color-surface-raised);
}

.question-card__option:active {
  transform: translateY(1px);
}

.question-card__option:focus-within {
  box-shadow: var(--focus-ring);
}

.question-card__radio {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  opacity: 0;
}

.question-card__option-key {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: var(--radius-full);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  font-family: var(--font-mono);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-bold);
  color: var(--color-text-muted);
  transition: background-color var(--option-transition), border-color var(--option-transition),
    color var(--option-transition);
}

.question-card__option-text {
  flex: 1 1 auto;
  color: var(--color-text);
}

.question-card__option-tag {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
}

.question-card__option-tag svg {
  width: 14px;
  height: 14px;
}

.question-card__option.is-selected {
  border-color: var(--color-accent);
  background-color: var(--color-accent-muted);
}

.question-card__option.is-selected .question-card__option-key {
  border-color: var(--color-accent);
  color: var(--color-accent);
}

.question-card__options.is-revealed .question-card__option.is-correct {
  border-color: var(--color-success);
  background-color: var(--color-success-muted);
}

.question-card__options.is-revealed .question-card__option.is-correct .question-card__option-key {
  border-color: var(--color-success);
  color: var(--color-success);
}

.question-card__options.is-revealed .question-card__option.is-correct .question-card__option-tag {
  color: var(--color-success);
}

/* Deliberately NOT scoped to .is-revealed, unlike .is-correct above — a
   wrong try needs to mark itself as wrong immediately (Section 7's
   QUESTION_MAX_ATTEMPTS flow), before the question is fully revealed, and
   crucially without that scoping ever accidentally letting .is-correct's
   rule leak the right answer early. */
.question-card__option.is-incorrect {
  border-color: var(--color-error);
  background-color: var(--color-error-muted);
}

.question-card__option.is-incorrect .question-card__option-key {
  border-color: var(--color-error);
  color: var(--color-error);
}

.question-card__option.is-incorrect .question-card__option-tag {
  color: var(--color-error);
}

.question-card__option:has(.question-card__radio:disabled) {
  cursor: not-allowed;
  opacity: 0.7;
}

.question-card__tries-remaining {
  font-family: var(--font-mono);
  font-size: var(--font-size-sm);
  color: var(--color-warning);
  opacity: 0;
  transform: translateY(-4px);
  transition: opacity var(--transition-base), transform var(--transition-base);
}

.question-card__tries-remaining.is-visible {
  opacity: 1;
  transform: translateY(0);
}

.question-card__feedback {
  padding: var(--space-md);
  background-color: var(--color-bg-alt);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  opacity: 0;
  transform: translateY(-4px);
  transition: opacity var(--transition-base), transform var(--transition-base);
}

.question-card__feedback.is-visible {
  opacity: 1;
  transform: translateY(0);
}

.question-card__feedback-heading {
  font-family: var(--font-mono);
  font-weight: var(--font-weight-bold);
  margin-bottom: var(--space-2xs);
  color: var(--color-text);
}

.question-card__feedback-heading--correct {
  color: var(--color-success);
}

.question-card__feedback-heading--incorrect {
  color: var(--color-error);
}

.question-card__explanation {
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
}

/* Shown only when incorrect (questionCard.js) — two short, plainly-labeled
   lines restating what was picked and what was right, ahead of the fuller
   prose explanation below. Deliberately just text + a divider, not its
   own boxed card, so it reads as part of the same feedback panel. */
.question-card__answer-summary {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  margin-bottom: var(--space-sm);
  padding-bottom: var(--space-sm);
  border-bottom: 1px solid var(--color-border);
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

.question-card__answer-summary strong {
  color: var(--color-text);
  font-weight: var(--font-weight-medium);
}

/* ==========================================================================
   Domain page (js/views/domain.js) — Section 9
   ========================================================================== */

.domain-view,
.review-view {
  width: 100%;
  max-width: var(--content-max-width);
  margin: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-xl);
}

.domain-view__header,
.review-view__header {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding-bottom: var(--space-lg);
  border-bottom: 1px solid var(--color-border);
}

.domain-view__eyebrow,
.review-view__eyebrow {
  font-family: var(--font-mono);
  font-size: var(--font-size-sm);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-accent);
}

.domain-view__heading,
.review-view__heading {
  margin: 0;
}

.domain-view__progress {
  max-width: 480px;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.domain-view__progress-meta,
.review-view__subtitle {
  font-family: var(--font-mono);
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

.domain-view__questions {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}

/* ==========================================================================
   Review page (js/views/review.js) — Section 9: every attempted:true,
   correct:false question, grouped by domain. Shares .domain-view's page
   shell, header treatment, and .domain-view__questions list layout above
   (Section 7 — one rule for "a column of question cards", not a second copy
   of it) — only the per-domain group heading/count below is new.
   ========================================================================== */

.review-view__domain {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}

.review-view__domain h2 {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
  margin-bottom: calc(-1 * var(--space-sm));
}

.review-view__domain-count {
  font-family: var(--font-mono);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-regular);
  color: var(--color-text-muted);
}

/* ==========================================================================
   Matching card (js/components/matchingCard.js) — the ONE matching/PBQ
   renderer, one instance per domain's matching_questions entry (Section 9).
   Mirrors question-card's surface/spacing so the two card types read as
   the same family, while a "Check Answers" model (Section 5's "not all
   will be used") replaces per-option radios with one <select> per row.
   ========================================================================== */

.matching-card {
  padding: var(--space-lg);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.matching-card__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
}

.matching-card__number {
  font-family: var(--font-mono);
  font-size: var(--font-size-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-muted);
}

.matching-card__prompt {
  color: var(--color-text);
  font-size: var(--font-size-md);
  line-height: var(--line-height-base);
}

.matching-card__rows {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.matching-card__row {
  padding: var(--space-sm) var(--space-md);
  background-color: var(--color-bg-alt);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  transition: border-color var(--transition-base), background-color var(--transition-base);
}

.matching-card__row-main {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.matching-card__row-index {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: var(--radius-full);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  font-family: var(--font-mono);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-bold);
  color: var(--color-text-muted);
}

.matching-card__description {
  flex: 1 1 260px;
  min-width: 0;
  color: var(--color-text);
}

.matching-card__select-wrap {
  position: relative;
  flex: 1 1 220px;
  min-width: 180px;
}

.matching-card__select {
  width: 100%;
  appearance: none;
  padding: var(--space-xs) var(--space-xl) var(--space-xs) var(--space-sm);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  color: var(--color-text);
  font-size: var(--font-size-sm);
  cursor: pointer;
  transition: border-color var(--transition-fast), background-color var(--transition-fast);
}

.matching-card__select:hover {
  border-color: var(--color-accent);
}

.matching-card__select:focus-visible {
  border-color: var(--color-accent);
}

.matching-card__select-chevron {
  position: absolute;
  top: 50%;
  right: var(--space-sm);
  width: 14px;
  height: 14px;
  transform: translateY(-50%);
  color: var(--color-text-muted);
  pointer-events: none;
}

.matching-card__select-chevron svg {
  width: 100%;
  height: 100%;
}

.matching-card__rows.is-checked .matching-card__row.is-correct {
  border-color: var(--color-success);
  background-color: var(--color-success-muted);
}

.matching-card__rows.is-checked .matching-card__row.is-correct .matching-card__select {
  border-color: var(--color-success);
}

.matching-card__rows.is-checked .matching-card__row.is-incorrect {
  border-color: var(--color-error);
  background-color: var(--color-error-muted);
}

.matching-card__rows.is-checked .matching-card__row.is-incorrect .matching-card__select {
  border-color: var(--color-error);
}

/* Unattempted rows (Check Answers pressed with no term chosen) stay
   neutral rather than warning-amber — matching statusIndicator.js's own
   "--unattempted" treatment (Section 7: one visual language per state, not
   a second one invented here for the same badge shown inside the row). */
.matching-card__rows.is-checked .matching-card__row.is-unattempted {
  border-color: var(--color-border-strong);
}

.matching-card__rows.is-checked .matching-card__row.is-unattempted .matching-card__select {
  border-color: var(--color-border-strong);
}

.matching-card__row-feedback {
  margin-top: var(--space-sm);
  padding-top: var(--space-sm);
  border-top: 1px solid var(--color-border);
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  opacity: 0;
  transform: translateY(-4px);
  transition: opacity var(--transition-base), transform var(--transition-base);
}

.matching-card__row-feedback.is-visible {
  opacity: 1;
  transform: translateY(0);
}

.matching-card__why {
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
}

.matching-card__actions {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  flex-wrap: wrap;
}

.matching-card__summary {
  font-family: var(--font-mono);
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

@media (max-width: 560px) {
  .matching-card__row-main {
    flex-wrap: wrap;
  }

  .matching-card__description {
    flex-basis: 100%;
  }

  .matching-card__select-wrap {
    flex-basis: 100%;
  }
}

/* ==========================================================================
   Exam Mode (js/views/examMode.js) — Section 9/13. Reuses .btn/.route-
   placeholder/.sr-only and question-card.js's own styling wholesale; only
   the exam-specific chrome (topbar/timer/navigator/toolbar/stage) is new.
   ========================================================================== */

/* Intro/rules gate (Section 9) — shown before the timer or any question
   loads; the user must click Begin Exam to proceed. Styled as a centered
   card (heavier than .route-placeholder's plain centered text) since this
   is a deliberate step the user reads and acts on, not a transient
   placeholder/error message. */
.exam-intro {
  max-width: 640px;
  margin: var(--space-3xl) auto;
  padding: var(--space-2xl);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  text-align: center;
}

.exam-intro__eyebrow {
  font-family: var(--font-mono);
  font-size: var(--font-size-sm);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-accent);
  margin-bottom: var(--space-sm);
}

.exam-intro__lead {
  color: var(--color-text-muted);
  margin-top: var(--space-sm);
  margin-bottom: var(--space-lg);
}

.exam-intro__rules {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  margin: 0 0 var(--space-xl);
  padding-left: var(--space-lg);
  color: var(--color-text-muted);
  font-size: var(--font-size-sm);
  text-align: left;
}

.exam-intro__rules li {
  line-height: var(--line-height-base);
}

.exam-intro__begin {
  padding: var(--space-sm) var(--space-2xl);
  font-size: var(--font-size-base);
}

.exam-view {
  width: 100%;
  max-width: var(--content-max-width);
  margin: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}

.exam-view__topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-lg);
  padding-bottom: var(--space-lg);
  border-bottom: 1px solid var(--color-border);
}

.exam-view__meta {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  min-width: 0;
}

.exam-view__eyebrow {
  font-family: var(--font-mono);
  font-size: var(--font-size-sm);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-accent);
}

.exam-view__progress-text {
  font-family: var(--font-mono);
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

/* The Section 13 "smooth fade, not an abrupt snap" color shift: every
   property here (color/border/background) transitions on --transition-slow,
   and .is-warning (toggled by examMode.js once EXAM_TIMER_WARNING_MINUTES
   remain) is the ONLY thing that changes — no separate keyframe/snap. */
.exam-timer {
  flex: 0 0 auto;
  padding: var(--space-xs) var(--space-lg);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-md);
  font-family: var(--font-mono);
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-bold);
  font-variant-numeric: tabular-nums;
  color: var(--color-text);
  transition: color var(--transition-slow), border-color var(--transition-slow),
    background-color var(--transition-slow);
}

.exam-timer.is-warning {
  color: var(--color-warning);
  border-color: var(--color-warning);
  background-color: var(--color-warning-muted);
}

.exam-view__submit {
  flex: 0 0 auto;
}

.exam-navigator {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  padding: var(--space-md);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
}

.exam-navigator__btn {
  position: relative;
  width: 36px;
  height: 36px;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background-color: var(--color-bg-alt);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  color: var(--color-text-muted);
  font-family: var(--font-mono);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  transition: background-color var(--transition-fast), border-color var(--transition-fast),
    color var(--transition-fast), transform var(--transition-fast);
}

.exam-navigator__btn:hover {
  border-color: var(--color-accent);
  color: var(--color-accent);
}

.exam-navigator__btn:active {
  transform: translateY(1px);
}

.exam-navigator__btn.is-answered {
  background-color: var(--color-accent-muted);
  border-color: var(--color-accent);
  color: var(--color-accent);
}

.exam-navigator__btn.is-current {
  box-shadow: inset 0 0 0 2px var(--color-accent);
}

/* Flagged is a supplementary visual hint only — the authoritative signal
   is the text baked into updateNavigatorButton()'s aria-label, so a corner
   marker alone (no separate icon) is fine here (Section 14's "never color
   alone" rule targets correct/incorrect, which statusIndicator.js owns). */
.exam-navigator__btn.is-flagged::after {
  content: "";
  position: absolute;
  top: -3px;
  right: -3px;
  width: 8px;
  height: 8px;
  border-radius: var(--radius-full);
  background-color: var(--color-warning);
  border: 1px solid var(--color-bg);
}

.exam-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-md);
}

.exam-toolbar__flag {
  display: inline-flex;
  align-items: center;
}

.exam-toolbar__flag-icon {
  display: inline-flex;
  width: 16px;
  height: 16px;
}

.exam-toolbar__flag-icon svg {
  width: 100%;
  height: 100%;
}

.exam-toolbar__flag.is-flagged {
  color: var(--color-warning);
  border-color: var(--color-warning);
  background-color: var(--color-warning-muted);
}

.exam-stage {
  display: flex;
  flex-direction: column;
}

/* Every question card mounts once and stays in the DOM (examMode.js);
   showQuestion() moves programmatic focus here purely for screen-reader
   orientation on navigation, not as a click target — same reasoning as
   .main-content:focus-visible above, so no visible ring. */
.exam-stage .question-card:focus-visible {
  box-shadow: none;
}

/* ==========================================================================
   Exam Results (js/views/examResults.js) — Section 9/11. Reuses
   progressBar.js/statusIndicator.js for the breakdown table (Section 7).
   ========================================================================== */

.exam-results {
  width: 100%;
  max-width: var(--content-max-width);
  margin: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-xl);
}

.exam-results__heading {
  margin-bottom: calc(-1 * var(--space-md));
}

.exam-results__date {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

.exam-results__score {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2xs);
  padding: var(--space-2xl) var(--space-lg);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  text-align: center;
}

.exam-results__score-label {
  order: -1;
  font-family: var(--font-mono);
  font-size: var(--font-size-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-accent);
}

.exam-results__score-percent {
  order: 0;
  font-family: var(--font-mono);
  font-size: var(--font-size-2xl);
  font-weight: var(--font-weight-bold);
  line-height: var(--line-height-tight);
  color: var(--color-text);
}

.exam-results__score-raw {
  order: 1;
  font-family: var(--font-mono);
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

.exam-results__estimate {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  padding: var(--space-md) var(--space-lg);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-border-strong);
  border-radius: var(--radius-md);
  transition: border-color var(--transition-base);
}

.exam-results__estimate--met {
  border-left-color: var(--color-success);
}

.exam-results__estimate-eyebrow {
  font-family: var(--font-mono);
  font-size: var(--font-size-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-text-muted);
}

.exam-results__estimate--met .exam-results__estimate-eyebrow {
  color: var(--color-success);
}

.exam-results__estimate-text {
  color: var(--color-text);
  font-size: var(--font-size-sm);
}

.exam-results__breakdown {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.exam-results__breakdown h2 {
  margin-bottom: calc(-1 * var(--space-sm));
}

.exam-results__breakdown-caption {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

/* Generic "wide content scrolls inside its own container, never the page"
   utility — first used here, reusable by any future table (Section 7). */
.table-scroll {
  width: 100%;
  overflow-x: auto;
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
}

.exam-results__table {
  width: 100%;
  min-width: 480px;
  border-collapse: collapse;
  font-size: var(--font-size-sm);
}

.exam-results__table th,
.exam-results__table td {
  padding: var(--space-sm) var(--space-md);
  text-align: left;
  border-bottom: 1px solid var(--color-border);
  white-space: nowrap;
}

.exam-results__table tbody tr:last-child th,
.exam-results__table tbody tr:last-child td {
  border-bottom: none;
}

.exam-results__table th {
  font-family: var(--font-mono);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-text-muted);
}

.exam-results__table td {
  color: var(--color-text);
}

.exam-results__table td .progress-bar {
  min-width: 140px;
  max-width: 200px;
}

.exam-results__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md);
}

/* ==========================================================================
   Backup menu (js/views/dashboard.js) — Section 9's "Export/Import/Reset
   menu" in the dashboard topbar, next to the search box. A small disclosure
   menu rather than three loose buttons, so the topbar stays tidy at every
   width (Section 15).
   ========================================================================== */

.backup-menu {
  position: relative;
  flex: 0 0 auto;
}

.backup-menu__trigger {
  white-space: nowrap;
}

.backup-menu__trigger-icon {
  display: inline-flex;
  width: 14px;
  height: 14px;
  transition: transform var(--transition-fast);
}

.backup-menu__trigger-icon svg {
  width: 100%;
  height: 100%;
}

.backup-menu__trigger[aria-expanded="true"] .backup-menu__trigger-icon {
  transform: rotate(180deg);
}

.backup-menu__panel {
  position: absolute;
  top: calc(100% + var(--space-xs));
  right: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  min-width: 200px;
  padding: var(--space-2xs);
  background-color: var(--color-surface-raised);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  opacity: 0;
  transform: translateY(-4px);
  pointer-events: none;
  transition: opacity var(--transition-fast), transform var(--transition-fast);
}

.backup-menu__panel.is-open {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

.backup-menu__item {
  display: flex;
  align-items: center;
  width: 100%;
  padding: var(--space-sm);
  border-radius: var(--radius-sm);
  color: var(--color-text);
  font-family: var(--font-mono);
  font-size: var(--font-size-sm);
  text-align: left;
  transition: background-color var(--transition-fast), color var(--transition-fast),
    transform var(--transition-fast);
}

.backup-menu__item:hover {
  background-color: var(--color-surface);
  color: var(--color-accent);
}

.backup-menu__item:active {
  transform: translateY(1px);
}

.backup-menu__item--danger:hover {
  color: var(--color-error);
}

@media (max-width: 480px) {
  .backup-menu__panel {
    right: auto;
    left: 0;
  }
}

/* ==========================================================================
   Toast (js/components/toast.js) — the ONE confirmation/feedback message
   implementation (Section 7), used identically for export/import/reset
   confirmations, the storage-unavailable warning (Section 6), and any
   future "this action succeeded" moment. Fades/slides in, holds, fades out
   (Section 13) rather than a blocking alert().
   ========================================================================== */

.toast-region {
  position: fixed;
  z-index: 200;
  right: var(--space-lg);
  bottom: var(--space-lg);
  display: flex;
  flex-direction: column-reverse;
  gap: var(--space-sm);
  max-width: min(360px, calc(100vw - var(--space-xl)));
  pointer-events: none;
}

.toast {
  display: flex;
  align-items: flex-start;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  background-color: var(--color-surface-raised);
  border: 1px solid var(--color-border-strong);
  border-left: 3px solid var(--color-accent);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  opacity: 0;
  transform: translateY(12px);
  transition: opacity var(--transition-base), transform var(--transition-base);
  pointer-events: auto;
}

.toast.is-visible {
  opacity: 1;
  transform: translateY(0);
}

.toast.is-leaving {
  opacity: 0;
  transform: translateY(-6px);
}

.toast--success {
  border-left-color: var(--color-success);
}

.toast--error {
  border-left-color: var(--color-error);
}

.toast--warning {
  border-left-color: var(--color-warning);
}

.toast--info {
  border-left-color: var(--color-accent);
}

.toast__icon {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  margin-top: 1px;
}

.toast__icon svg {
  width: 100%;
  height: 100%;
}

.toast--success .toast__icon {
  color: var(--color-success);
}

.toast--error .toast__icon {
  color: var(--color-error);
}

.toast--warning .toast__icon {
  color: var(--color-warning);
}

.toast--info .toast__icon {
  color: var(--color-accent);
}

.toast__message {
  flex: 1 1 auto;
  margin: 0;
  color: var(--color-text);
  font-size: var(--font-size-sm);
  line-height: var(--line-height-base);
}

.toast__dismiss {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  margin-top: 1px;
  color: var(--color-text-faint);
  transition: color var(--transition-fast), transform var(--transition-fast);
}

.toast__dismiss:hover {
  color: var(--color-text);
}

.toast__dismiss:active {
  transform: scale(0.88);
}

.toast__dismiss svg {
  width: 100%;
  height: 100%;
}

@media (max-width: 480px) {
  .toast-region {
    left: var(--space-md);
    right: var(--space-md);
    bottom: var(--space-md);
    max-width: none;
  }
}
