/* Responsive convention (change 126):
   Mobile layout:  @media (max-width: 767.98px)
   Desktop/tablet: default (>= 768px), the existing base styles.
   768px is iPad-portrait width — tablets and up get the desktop layout by
   design. 767.98px (not 768) avoids the 1px double-match at exactly 768 on
   fractional-DPI devices. Component breakpoints at 640/820 are kept where a
   component genuinely needs an earlier reflow (documented inline). */
:root {
  color-scheme: light;
  --bg: #f6f7fb;
  --surface: #ffffff;
  --surface-muted: #f8fafc;
  --text: #111827;
  --muted: #64748b;
  --border: #dbe3ef;
  --border-strong: #b6c2d2;
  --primary: #1d4ed8;
  --primary-hover: #1e40af;
  --secondary: #475569;
  --danger: #b91c1c;
  --danger-bg: #fee2e2;
  --success: #166534;
  --success-bg: #dcfce7;
  --warning: #92400e;
  --warning-bg: #fef3c7;
  --info-bg: #e0f2fe;
  --info-fg: #075985;
  --radius: 0.85rem;
  --shadow: 0 1px 2px rgb(15 23 42 / 8%), 0 12px 30px rgb(15 23 42 / 5%);
  /* Spacing tokens (change 131): a single source of truth for the recurring
     spacing values so button groups, card action rows, and section rhythm are
     consistent everywhere instead of each class hardcoding its own rem value.
       --space-inline   gap between grouped inline controls (buttons in a row)
       --space-stack    gap between stacked blocks in a card/form
       --space-section  vertical rhythm between a section's parts / cards
     Values chosen to match the most common existing usage so adopting them is a
     near-no-op visually, only unifying the few outliers (e.g. .form-actions was
     0.75rem, now unified to --space-inline). */
  --space-inline: 0.6rem;
  --space-stack: 0.75rem;
  --space-section: 1rem;
  /* Header chrome tokens (change: themed header). The site header used to be
     a hard-coded #0f172a bar that ignored the theme entirely. Now it reads
     these tokens; each theme in themes.css overrides them so the most
     prominent element on every page gels with the chosen theme. Defaults
     below are the original dark navy so any surface without a theme (and the
     legacy look) is unchanged.
       --header-bg        header background
       --header-fg        header text/icons (brand + nav, resting)
       --header-fg-muted  nav links at rest (slightly dimmed)
       --header-hover-bg  nav hover/focus background wash
       --header-border    hairline under the header */
  --header-bg: #0f172a;
  --header-fg: #ffffff;
  --header-fg-muted: #e5e7eb;
  --header-hover-bg: rgb(255 255 255 / 12%);
  --header-border: rgb(255 255 255 / 12%);
  /* Font stack (change 93): Latin first, then system Arabic and CJK families so
     ar/zh-hans text renders with proper glyphs and shaping without shipping web
     fonts. "Noto Sans Arabic"/"Noto Sans SC" are picked up when installed. */
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
    "Noto Sans Arabic", "Segoe UI Arabic", "Noto Sans SC", "PingFang SC", "Microsoft YaHei",
    sans-serif;
}

/* Change 128 — per-script font stacks. Each :lang() rule prepends the
   script-appropriate self-hosted Noto face (declared in fonts.css) to the
   theme's chain, then system faces, so CJK/Arabic locales render with a
   consistent face regardless of the active theme's Latin --font-text. Scoped
   by :lang() so Latin locales are untouched. --font-text still wins for the
   Latin runs a CJK page may contain (it sits after the CJK face, which lacks
   Latin refinements, so Latin glyphs fall through to it). */
:root:lang(zh-Hans), :root:lang(zh) {
  font-family: "Noto Sans SC", var(--font-text), "PingFang SC", "Microsoft YaHei", sans-serif;
}
:root:lang(ja) {
  font-family: "Noto Sans JP", var(--font-text), "Hiragino Sans", Meiryo, sans-serif;
}
:root:lang(ko) {
  font-family: "Noto Sans KR", var(--font-text), "Malgun Gothic", "Apple SD Gothic Neo", sans-serif;
}
:root:lang(ar) {
  font-family: "Noto Sans Arabic", "Segoe UI Arabic", var(--font-text), sans-serif;
}
/* Headings in a CJK/Arabic locale use the same script face rather than the
   theme's Latin display face (which may have no CJK/Arabic glyphs). */
:lang(zh-Hans) h1, :lang(zh-Hans) h2, :lang(zh-Hans) h3,
:lang(zh) h1, :lang(zh) h2, :lang(zh) h3 { font-family: "Noto Sans SC", var(--font-display, inherit); }
:lang(ja) h1, :lang(ja) h2, :lang(ja) h3 { font-family: "Noto Sans JP", var(--font-display, inherit); }
:lang(ko) h1, :lang(ko) h2, :lang(ko) h3 { font-family: "Noto Sans KR", var(--font-display, inherit); }
:lang(ar) h1, :lang(ar) h2, :lang(ar) h3 { font-family: "Noto Sans Arabic", var(--font-display, inherit); }

/* RTL affordances (change 93): mirror the few elements whose visual direction is
   intrinsic. Logical properties handle layout automatically; these cover cases
   that need an explicit flip under [dir="rtl"]. */
[dir="rtl"] .breadcrumbs__item + .breadcrumbs__item::before { content: "\\"; }
[dir="rtl"] ol[type="A"], [dir="rtl"] ol[type="a"] { direction: rtl; }
/* Numbers, IRT parameters, and codes stay LTR even inside RTL prose. */
[dir="rtl"] .ltr-inline, [dir="rtl"] code, [dir="rtl"] .cert-number {
  direction: ltr; unicode-bidi: isolate; display: inline-block;
}

/*
 * Dark-mode tokens (change 60g): READINESS ONLY — defined and contrast-checked
 * now so no component hardcodes a colour that would break under dark mode later.
 * No user toggle ships yet; the tokens activate automatically for users whose OS
 * prefers dark. When an explicit toggle lands, it will stamp data-theme on <html>
 * and reuse these exact values. Contrast: text/bg and muted/bg meet WCAG AA.
 */
/* IMPORTANT: this readiness-only dark block must NOT override a named theme.
 * base.html always stamps an explicit data-theme (e.g. "cloudera-signal"),
 * and each named theme in themes.css defines its own full palette. If this
 * block matched any data-theme other than "light", it would stomp every
 * light theme whenever the OS prefers dark — making the whole app render in
 * this generic navy/blue fallback instead of its actual theme. So it applies
 * ONLY when no theme (or the neutral "dark" slug) is set. Named themes own
 * their own dark handling via themes.css. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]), :root[data-theme=""], :root[data-theme="dark"] {
    color-scheme: dark;
    --bg: #0b1120;
    --surface: #111827;
    --surface-muted: #1e293b;
    --text: #e5e7eb;
    --muted: #94a3b8;
    --border: #273244;
    --border-strong: #3b4a63;
    --primary: #60a5fa;
    --primary-hover: #93c5fd;
    --secondary: #94a3b8;
    --danger: #fca5a5;
    --danger-bg: #3f1d1d;
    --success: #86efac;
    --success-bg: #14331f;
    --warning: #fcd34d;
    --warning-bg: #3a2e0a;
    --info-bg: #0c2a3a;
    --info-fg: #7dd3fc;
    --shadow: 0 1px 2px rgb(0 0 0 / 40%), 0 12px 30px rgb(0 0 0 / 35%);
  }
}
/* Explicit opt-in for later toggle work (values mirror the media query). */
:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #0b1120; --surface: #111827; --surface-muted: #1e293b;
  --text: #e5e7eb; --muted: #94a3b8; --border: #273244; --border-strong: #3b4a63;
  --primary: #60a5fa; --primary-hover: #93c5fd; --secondary: #94a3b8;
  --danger: #fca5a5; --danger-bg: #3f1d1d; --success: #86efac; --success-bg: #14331f;
  --warning: #fcd34d; --warning-bg: #3a2e0a; --info-bg: #0c2a3a; --info-fg: #7dd3fc;
  --shadow: 0 1px 2px rgb(0 0 0 / 40%), 0 12px 30px rgb(0 0 0 / 35%);
}

* { box-sizing: border-box; }
html { min-height: 100%; scroll-behavior: smooth; }
body { margin: 0; min-height: 100%; background: var(--bg); color: var(--text); line-height: 1.5; }
/* Change 120: themed display face for headings; --heading/--font-display
   are only defined by theme blocks, so unthemed pages are unaffected. */
h1, h2, h3 { font-family: var(--font-display, inherit); color: var(--heading, inherit); }
a { color: var(--primary); text-decoration-thickness: 0.08em; text-underline-offset: 0.18em; }
a:hover { color: var(--primary-hover); }
img, svg, canvas { max-width: 100%; }
code { background: var(--surface-muted); padding: 0.1rem 0.25rem; border-radius: 0.25rem; }

.skip-link {
  position: absolute;
  inset-inline-start: 1rem;
  top: 0.5rem;
  transform: translateY(-180%);
  background: var(--surface);
  color: var(--text);
  border: 2px solid var(--primary);
  border-radius: 0.5rem;
  padding: 0.55rem 0.75rem;
  z-index: 1000;
}
.skip-link:focus { transform: translateY(0); }

.site-header { background: var(--header-bg); color: var(--header-fg); border-bottom: 1px solid var(--header-border); }
.site-header__inner {
  max-width: 1280px;
  margin: 0 auto;
  padding: 0.85rem 1.25rem;
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}
.brand { display: inline-flex; align-items: center; color: var(--header-fg); font-weight: 800; letter-spacing: -0.02em; text-decoration: none; }
/* Change 125: brand logo is height-capped so a themed logo (change 122)
   never changes the header's vertical metrics. --brand-logo-h defaults
   here and a theme may override it. */
.brand__logo { display: block; height: var(--brand-logo-h, 1.6rem); width: auto; }
.primary-nav { display: flex; align-items: center; gap: 0.35rem; flex-wrap: wrap; }
/* Change 126: hamburger + scrim are desktop-hidden; they only appear in the
   mobile block below. The toggle checkbox is always visually hidden as a
   checkbox (opacity:0) but stays keyboard-focusable and operable — its
   accessible name + aria live on the input itself. */
.nav-toggle { display: none; }
.nav-drawer-toggle { position: absolute; opacity: 0; }
.nav-scrim { display: none; }
.primary-nav a, .nav-button {
  color: var(--header-fg-muted);
  border-radius: 0.55rem;
  padding: 0.4rem 0.55rem;
  text-decoration: none;
}
.primary-nav a:hover, .primary-nav a:focus-visible, .nav-button:hover, .nav-button:focus-visible { background: var(--header-hover-bg); color: var(--header-fg); }
.logout-form { display: inline; margin: 0; }

/* Back-office nav groups (change 177). Desktop: a <details> disclosure
   styled to sit among the flat links; the panel drops below the summary.
   The summary mirrors .primary-nav a so grouped and flat entries read as
   one family. */
.nav-group { position: relative; }
.nav-group > summary.nav-group__toggle {
  list-style: none;
  cursor: pointer;
  color: var(--header-fg-muted);
  border-radius: 0.55rem;
  padding: 0.4rem 0.55rem;
  user-select: none;
}
.nav-group > summary.nav-group__toggle::-webkit-details-marker { display: none; }
.nav-group > summary.nav-group__toggle::after { content: " ▾"; font-size: 0.75em; }
.nav-group > summary.nav-group__toggle:hover,
.nav-group > summary.nav-group__toggle:focus-visible {
  background: var(--header-hover-bg);
  color: var(--header-fg);
}
.nav-group > summary.nav-group__toggle:focus-visible {
  outline: 3px solid var(--focus-ring, #93c5fd);
  outline-offset: 2px;
}
/* The group holding the current page keeps a quiet marker while closed. */
.nav-group--active > summary.nav-group__toggle {
  color: var(--header-fg);
  box-shadow: inset 0 -2px 0 currentColor;
}
.nav-group__panel {
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: 100%;
  min-inline-size: 13rem;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: 0.4rem;
  background: var(--header-bg, var(--surface));
  border: 1px solid var(--border, rgba(255, 255, 255, 0.15));
  border-radius: 0.55rem;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
  z-index: 40;
}
.nav-group__panel a { display: block; }
.nav-button { background: transparent; border: 0; cursor: pointer; font: inherit; }

.main-shell { max-width: 1280px; margin: 0 auto; padding: 1.5rem; }
.page-header { display: flex; justify-content: space-between; align-items: flex-start; gap: 1rem; flex-wrap: wrap; margin-bottom: 1rem; }
.page-header h1 { margin: 0; font-size: clamp(1.6rem, 4vw, 2.35rem); line-height: 1.12; letter-spacing: -0.035em; }
.page-kicker { color: var(--muted); font-size: 0.95rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; margin: 0 0 0.35rem; }
.page-actions { display: flex; gap: var(--space-inline); align-items: center; flex-wrap: wrap; }

/* A .page-actions row split into a primary cluster + a trailing "More"
   disclosure (My Tutor student UI pass) — space-between only applies
   here, not to .page-actions generally (other pages put bare buttons
   directly in .page-actions, where space-between would spread them
   apart instead of grouping them). */
.page-actions--split { justify-content: space-between; align-items: flex-start; }

/* The "More" disclosure's revealed items float as a small anchored panel
   under the trigger, rather than flowing inline — reads as "here are the
   extra actions" instead of a second disconnected button row. */
.page-actions__more { position: relative; }
.page-actions__more summary { list-style: none; cursor: pointer; }
.page-actions__more summary::-webkit-details-marker { display: none; }
.page-actions__more[open] summary { border-color: var(--primary); color: var(--primary); }
.page-actions__more-list {
  display: flex;
  position: absolute;
  inset-inline-end: 0;
  top: calc(100% + 0.35rem);
  z-index: 5;
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-inline);
  min-inline-size: 10rem;
  padding: 0.4rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.page-actions__more-list .button { justify-content: flex-start; }

/* Campaign roster row-expand (change 142 tier-2 detail fix): the
   per-student detail panel floats as an anchored panel under its own
   toggle cell, same pattern as .page-actions__more above — avoids
   needing a second, JS-synced <tr> (details/summary can't cleanly span
   table rows, so the panel lives inside the SAME cell as its trigger). */
.row-expand { position: relative; }
.row-expand summary { list-style: none; cursor: pointer; }
.row-expand summary::-webkit-details-marker { display: none; }
.row-expand[open] summary { border-color: var(--primary); color: var(--primary); }
.row-expand__panel {
  position: absolute;
  inset-inline-start: 0;
  top: calc(100% + 0.35rem);
  z-index: 5;
  display: grid;
  grid-template-columns: repeat(2, minmax(12rem, 1fr));
  gap: 1rem;
  min-inline-size: 26rem;
  max-inline-size: min(90vw, 34rem);
  padding: 0.75rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.row-expand__panel .area-bars { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.4rem; }
.row-expand__panel .area-bars li { display: grid; gap: 0.15rem; }

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1rem;
  margin-bottom: 1rem;
  box-shadow: var(--shadow);
}
.card h2:first-child, .card h3:first-child { margin-top: 0; }
.card--quiet { box-shadow: none; background: var(--surface-muted); }
.card--warning { border-color: var(--warning); background: var(--warning-bg); }
.card--danger { border-color: var(--danger); background: var(--danger-bg); }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(245px, 1fr)); gap: 1rem; }
/* Change 126: clamp the min track with min(100%, …) so a two-col grid never
   forces a track wider than a ~320px viewport (which would scroll the page). */
.grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); gap: 1rem; }
.stack { display: grid; gap: 1rem; }
.cluster { display: flex; gap: var(--space-inline); align-items: center; flex-wrap: wrap; }

.button, button, input[type="submit"] {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  border: 1px solid transparent;
  border-radius: 0.6rem;
  padding: 0.48rem 0.85rem;
  /* Themed CTA colour: --accent/--accent-contrast exist only under a
     change-120 theme; unthemed pages keep the legacy primary/white. */
  background: var(--accent, var(--primary));
  color: var(--accent-contrast, #fff);
  font-weight: 700;
  font-size: 0.93rem;
  cursor: pointer;
  text-decoration: none;
  min-height: 2.3rem;
}
.button:hover, button:hover, input[type="submit"]:hover { background: var(--accent-hover, var(--primary-hover)); color: var(--accent-contrast, #fff); text-decoration: none; }
/* Secondary = a quiet neutral action that belongs to the active theme:
   a muted surface with a strong border and body text, rather than a fixed
   slate that clashed with non-blue palettes (indigo, green, amber). */
.button.secondary, button.secondary { background: var(--surface-muted); color: var(--text); border-color: var(--border-strong); }
.button.secondary:hover, button.secondary:hover { background: var(--surface); color: var(--text); border-color: var(--text-muted); }
.button.danger, button.danger { background: var(--danger); }
.button.ghost { background: var(--surface); color: var(--primary); border-color: var(--border-strong); }
.button.ghost:hover { background: var(--surface-muted); color: var(--primary-hover); }
button[disabled], .button[aria-disabled="true"] { opacity: 0.55; cursor: not-allowed; }
/* Breathing room between sibling buttons in plain flow (cards, cells,
   paragraphs). Layout containers (.cluster, .form-actions, .page-actions)
   already space via gap and are untouched. This is a FALLBACK for ungrouped
   buttons; prefer wrapping card/page actions in .cluster (change 131). */
:where(p, section, td, .card) > :where(.button, button) + :where(.button, button) {
  margin-inline-start: var(--space-inline);
}

/* Standard card action row (change 131): a consistent group + top offset for the
   buttons at the bottom of a .card, so every card's actions sit at the same
   rhythm. Uses the shared inline gap; the top margin separates them from the
   card body text above. Cards adopt this by wrapping their actions in
   <div class="card-actions">…</div> instead of leaving bare sibling buttons. */
.card-actions {
  display: flex;
  gap: var(--space-inline);
  align-items: center;
  flex-wrap: wrap;
  margin-top: var(--space-section);
}

/* Searchable/sortable multi-select picker (change 135): a compact ONE-LINE-PER-
   ROW scrollable list with a search box, sort control, and a "Selected" chip
   tray below — usable for long option lists (100+ chapters/skills), unlike a
   card grid or a native <select multiple>. */
/* Sticky filter action bar (change 135): keeps Filter/Reset reachable at the top
   of a long filter form so you never scroll past the pickers to submit. */
.filter-actions {
  position: sticky; top: 0; z-index: 5;
  display: flex; gap: var(--space-inline); align-items: center; flex-wrap: wrap;
  padding: 0.6rem 0; margin-bottom: 0.6rem;
  background: color-mix(in srgb, var(--surface) 94%, transparent);
  backdrop-filter: blur(6px); border-bottom: 1px solid var(--border);
}
.picker__hint { font-weight: 500; }

/* Review-queue sticky action bar + item grouping (change 135). */
.review-actions { gap: 0.5rem; }
.review-actions__group { display: inline-flex; align-items: center; gap: 0.35rem; }
.review-actions__glabel { white-space: nowrap; }
.review-actions__reason { flex: 0 1 14rem; min-width: 6rem; }
.review-actions__sep { color: var(--border-strong); font-weight: 700; }
.review-actions [data-review-needs-selection][disabled] { opacity: 0.45; cursor: not-allowed; }
/* A thicker top border starts each item's language group so an item's 10
   languages read as one block, not an interleaved flat list. */
.review-row--group-start > td { border-top: 2px solid var(--border-strong); }
.review-row__actions { display: flex; gap: 0.3rem; align-items: center; white-space: nowrap; }
.review-row__actions .button--small { padding: 0.2rem 0.5rem; min-height: 1.9rem; }
.picker { border: 1px solid var(--border); border-radius: var(--radius); padding: 0.75rem; }
.picker > legend { font-weight: 700; padding: 0 0.4rem; }
.picker__count { font-weight: 600; font-size: 0.85rem; margin-inline-start: 0.4rem; }
.picker__controls { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; margin: 0.5rem 0; }
.picker__search { flex: 1 1 12rem; min-width: 0; }
.picker__sort { display: inline-flex; align-items: center; gap: 0.35rem; font-size: 0.9rem; }
.picker__sort select { width: auto; }
.picker__list {
  list-style: none; margin: 0; padding: 0; max-height: 11rem; overflow-y: auto;
  border: 1px solid var(--border); border-radius: 0.55rem;
}
.picker__row { border-bottom: 1px solid var(--border); }
.picker__row:last-child { border-bottom: 0; }
.picker__row[hidden] { display: none; }
.picker__label {
  display: flex; align-items: center; gap: 0.6rem; padding: 0.5rem 0.7rem;
  cursor: pointer; min-height: 2.5rem;
}
.picker__label:hover { background: var(--surface-muted); }
/* Selected row: accent wash + a leading bar so ticked rows read at a glance. */
.picker__row.is-selected,
.picker__row:has(input:checked) {
  background: color-mix(in srgb, var(--accent, var(--primary)) 10%, var(--surface));
  box-shadow: inset 3px 0 0 var(--accent, var(--primary));
  font-weight: 600;
}
.picker__tray {
  display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; margin-top: 0.6rem;
  padding-top: 0.6rem; border-top: 1px dashed var(--border);
}
.picker__chips { display: flex; flex-wrap: wrap; gap: 0.35rem; list-style: none; margin: 0; padding: 0; }
.picker__chip {
  display: inline-flex; align-items: center; gap: 0.3rem; padding: 0.2rem 0.55rem;
  border-radius: 999px; background: color-mix(in srgb, var(--accent, var(--primary)) 16%, var(--surface));
  border: 1px solid var(--accent, var(--primary)); font-size: 0.82rem; font-weight: 650;
}
.picker__chip button {
  all: unset; cursor: pointer; line-height: 1; padding: 0 0.15rem; color: var(--muted); min-height: auto;
}
.picker__chip button:hover { color: var(--danger); }

/* Inline confirm (change 124): the reveal-in-place Confirm/Cancel affordance
   that replaces blocking window.confirm on destructive actions. Themeable via
   tokens; wraps on narrow rows so it never overflows. */
.inline-confirm {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem 0.5rem;
  padding: 0.3rem 0.5rem;
  border: 1px solid var(--danger, #b91c1c);
  border-radius: 0.55rem;
  background: var(--danger-bg, #fef2f2);
}
.inline-confirm__prompt { font-size: 0.9rem; font-weight: 640; color: var(--text); }
.button--ghost { background: var(--surface); color: var(--primary); border: 1px solid var(--border-strong); }
.button--ghost:hover { background: var(--surface-muted); }

form.grid, .form-grid { align-items: start; }
label { display: block; font-weight: 720; margin: 0.75rem 0 0.25rem; }
input, select, textarea {
  width: 100%;
  max-width: 760px;
  padding: 0.58rem 0.65rem;
  border: 1px solid var(--border-strong);
  border-radius: 0.55rem;
  background: var(--surface);
  color: var(--text);
  font: inherit;
}
input[type="radio"], input[type="checkbox"] { width: auto; max-width: none; }
textarea { min-height: 7rem; }
input:focus, select:focus, textarea:focus, button:focus-visible, a:focus-visible {
  outline: 3px solid var(--focus-ring, #93c5fd);
  outline-offset: 2px;
}
.helptext, .muted, small, .help { color: var(--muted); font-size: 0.93rem; }
/* ``.help`` was used by several form templates (access codes, account) with no
   rule behind it, so field hints rendered at full body weight and competed
   with the label. Folded into the same declaration rather than given its own,
   so the three hint classes can never drift apart. */

/* Definition lists used for read-only "facts" panels — the account page and
   the access-code detail page both had markup for these with no CSS at all,
   so they rendered as an unstyled dt/dd stack with no alignment or spacing.
   Two columns on wide viewports, stacked on narrow ones. */
/* Result-page score breakdown (BUG-0008). ``.breakdown-subhead`` was used by
   the result template with no rule behind it, so the section headings had no
   separation from the table above them. */
.breakdown-subhead { margin: 1.4rem 0 0.6rem; font-size: 1.05rem; }
.breakdown-subhead:first-of-type { margin-block-start: 0.4rem; }

/* Grouped area scores: the meaningful rollup, shown as scannable cards rather
   than another dense table. The per-skill rows live behind ``.skill-detail``. */
.score-groups { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.75rem; }
.score-group { border: 1px solid var(--border); border-radius: var(--radius); padding: 0.75rem 0.85rem; background: var(--surface); }
.score-group__head { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; }
.score-group__name { font-weight: 650; }
.score-group__score { font-weight: 700; font-variant-numeric: tabular-nums; font-size: 1.15rem; }
.score-group__bar { margin: 0.5rem 0 0.35rem; height: 0.5rem; border-radius: 999px; background: var(--surface-muted); border: 1px solid var(--border); overflow: hidden; }
.score-group__bar > span { display: block; height: 100%; background: var(--primary); border-radius: inherit; }
.score-group__meta { margin: 0; }

.skill-detail { margin-block-start: 1.2rem; }
.skill-detail summary { cursor: pointer; font-weight: 600; padding: 0.4rem 0; }

.detail-list { margin: 0; display: grid; gap: 0.1rem; }
.detail-list__row {
  display: grid;
  grid-template-columns: minmax(9rem, 14rem) 1fr;
  gap: 0.35rem 1.25rem;
  padding: 0.6rem 0;
  border-block-end: 1px solid var(--border);
}
.detail-list__row:last-child { border-block-end: 0; padding-block-end: 0; }
.detail-list dt { color: var(--muted); font-size: 0.93rem; }
.detail-list dd { margin: 0; font-weight: 600; overflow-wrap: anywhere; }
/* Below ~34rem the two-column split leaves too little room for the value, so
   stack label above value instead of letting long emails overflow. */
@media (max-width: 34rem) {
  .detail-list__row { grid-template-columns: 1fr; gap: 0.1rem; }
}

/* Legacy alias: the access-code detail template predates ``.detail-list`` and
   uses a bare ``two-column`` dl. Same presentation, so alias it rather than
   leave that page unstyled or churn a template this change does not own. */
.two-column { margin: 0; }
.two-column dt { color: var(--muted); font-size: 0.93rem; margin-block-start: 0.6rem; }
.two-column dd { margin: 0 0 0.15rem; font-weight: 600; overflow-wrap: anywhere; }
.form-errors, .errorlist { color: var(--danger); font-weight: 650; }
.errorlist { margin: 0.35rem 0; padding-inline-start: 1.2rem; }

.messages { list-style: none; padding: 0; margin: 0 0 1rem; }
.messages li { padding: 0.82rem 0.95rem; border-radius: 0.65rem; margin-bottom: 0.55rem; background: var(--info-bg); border: 1px solid #bae6fd; color: var(--info-fg, #075985); }
.messages .success { background: var(--success-bg); border-color: #86efac; color: var(--success); }
.messages .error { background: var(--danger-bg); border-color: #fca5a5; color: var(--danger); }
.messages .warning { background: var(--warning-bg); border-color: #facc15; color: var(--warning); }

.table-wrap { overflow-x: auto; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); }
table { width: 100%; border-collapse: collapse; background: var(--surface); }
/* `.data-table` is emitted by ~39 templates and had no rule anywhere: it worked
   only because the bare `table` selector above happens to cover it. Naming it
   makes the dependency explicit — a future scoped table style would otherwise
   silently lose these defaults (change 161, found by the class-coverage test). */
.data-table { width: 100%; border-collapse: collapse; background: var(--surface); }
th, td { text-align: start; padding: 0.72rem; border-bottom: 1px solid var(--border); vertical-align: top; }
th { background: var(--surface-muted); font-weight: 800; color: var(--text); }
tr:last-child td { border-bottom: 0; }
caption { text-align: start; padding: 0 0 0.65rem; color: var(--muted); font-weight: 650; }

.pill { display: inline-flex; align-items: center; gap: 0.25rem; padding: 0.22rem 0.55rem; border-radius: 999px; background: var(--border); color: var(--text); font-size: 0.85rem; font-weight: 720; }
.pill.good { background: var(--success-bg); color: var(--success); }
.pill.warn { background: var(--warning-bg); color: var(--warning); }
.pill.bad { background: var(--danger-bg); color: var(--danger); }
.pill.info { background: var(--info-bg); color: var(--info-fg, #075985); }
.big-number { font-size: clamp(1.6rem, 3vw, 2.15rem); font-weight: 850; letter-spacing: -0.04em; }

/* Prediction ladder display (change 145 D4 / student UI pass). */
.prediction-line { display: flex; align-items: center; gap: var(--space-inline); flex-wrap: wrap; margin: 0.5rem 0 0; }

/* Readiness level dot-scale (Tutor UI pass): a 4-dot ladder — EARLY through
   ON_TRACK — so "how far along" reads at a glance, same as the wireframe's
   "●●●● ON TRACK". Text characters, not an image/canvas: themeable, no
   asset, scales with font-size. */
.readiness-level { display: flex; align-items: center; gap: var(--space-inline); flex-wrap: wrap; margin: 0.35rem 0 0; }
.readiness-dots { font-size: 1.1rem; letter-spacing: 0.12em; color: var(--primary); }
.readiness-dots__filled { color: var(--success); }
.readiness-dots__empty { color: var(--border); }
.trend { display: inline-flex; align-items: center; gap: 0.2rem; font-size: 0.85rem; font-weight: 700; }
.trend--up { color: var(--success); }
.trend--down { color: var(--danger); }

.exam-facts { margin: 0.35rem 0 1rem; }

/* "Other ways to practise" (My Tutor home): each study mode gets its own
   labelled tile with a one-line explanation — was a bare, unexplained row
   of ghost buttons crammed against the areas table and the "More" menu. */
.option-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr)); gap: 1rem; margin-top: 0.75rem; }
.option-grid__item { display: grid; gap: 0.4rem; align-content: start; padding: 0.85rem; border: 1px solid var(--border); border-radius: var(--radius); }
.option-grid__title { margin: 0; font-weight: 700; }
.readiness-headline { font-size: 1.05rem; font-weight: 600; margin: 0 0 0.5rem; }

/* Confidence tap (wireframe 01 step 13: "How sure were you? (Sure)
   (Not sure) — optional, 1 tap") — a compact, secondary control that
   must never compete visually with the actual answer choices above it. */
.confidence-tap { display: flex; align-items: center; gap: 1rem; border: 0; padding: 0.5rem 0 0.75rem; margin: 0; }
.confidence-tap legend { padding: 0; margin-bottom: 0.35rem; width: 100%; }
.choice--inline { display: inline-flex; align-items: center; gap: 0.35rem; font-size: 0.9rem; cursor: pointer; }

/* Exam requirements page: a per-area skill list inside a table cell —
   kept compact so a long "what's tested" list doesn't blow out row height. */
.requirements-skill-list { margin: 0; padding-inline-start: 1.1rem; font-size: 0.9rem; }
.requirements-skill-list li { margin-bottom: 0.15rem; }

/* "Why this readiness level?" disclosure — sits inline in a .card-actions
   button row rather than forcing a block-level break; its content floats
   below when opened, same anchored-panel pattern as .row-expand. */
.disclosure { position: relative; }
.disclosure summary { list-style: none; cursor: pointer; }
.disclosure summary::-webkit-details-marker { display: none; }
.disclosure[open] summary { border-color: var(--primary); color: var(--primary); }
.disclosure ul {
  position: absolute;
  inset-inline-start: 0;
  top: calc(100% + 0.35rem);
  z-index: 5;
  min-inline-size: 16rem;
  max-inline-size: min(90vw, 24rem);
  margin: 0;
  padding: 0.75rem 0.75rem 0.75rem 1.5rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}

/* Readiness review evidence lines: always-open by default (wireframe:
   an always-visible checklist, not a collapsed accordion the student has
   to think to expand) — still real <details>, so it stays collapsible. */
.evidence-line summary { cursor: pointer; }
.evidence-line__icon { display: inline-block; inline-size: 1.1rem; text-align: center; font-weight: 700; }
.evidence-line__icon--ok { color: var(--success); }
.evidence-line__icon--warn { color: var(--warning); }

/* Tutoring queue list (wireframe 01 step 6: numbered rows, skill name
   prominent, a subdued "why: ... · ~N min" line beneath, actions at the
   end) — was a plain 5-column data table with no visual hierarchy. */
.queue-list { list-style: none; counter-reset: queue-row; margin: 0; padding: 0; display: grid; gap: 0.75rem; }
.queue-list__item {
  counter-increment: queue-row;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  padding: 0.65rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
/* Only render the "N." counter when the area actually has more than one row
   to rank. With one skill per area, "1." on every row is chrome that reads
   as a bug — the ranking is visually top-to-bottom already. The counter
   remains available (and semantically correct via <ol>) for the multi-skill
   case, and screen readers still announce "list item 1 of N" regardless. */
.queue-list:has(> .queue-list__item:nth-child(2)) .queue-list__item::before {
  content: counter(queue-row) ".";
  font-weight: 700;
  color: var(--muted);
  margin-inline-end: 0.5rem;
}
.queue-list__item--learned, .queue-list__item--skipped { opacity: 0.75; }
.queue-list__main { display: flex; flex-direction: column; gap: 0.15rem; min-inline-size: 0; }
.queue-list__skill { margin: 0; font-weight: 700; display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.queue-list__why { margin: 0; }
.queue-list__actions { flex-shrink: 0; }

/* Per-skill state progress bar (learn_skill affirm stage): the wireframe's
   "Auto-scaling → LEARNING ██████░░░░" — a 4-stage ladder (Learning →
   Proving → Verified), filled to the stage this screen always lands on. */
.skill-progress { display: flex; align-items: center; gap: var(--space-inline); margin: 0.5rem 0; }
.skill-progress__bar { display: block; inline-size: 10rem; max-inline-size: 40vw; block-size: 0.6rem; border-radius: 999px; background: var(--surface-muted); overflow: hidden; }
.skill-progress__fill { display: block; block-size: 100%; border-radius: 999px; background: var(--info-fg, var(--primary)); }
.skill-progress__fill--learning { inline-size: 50%; }
.skill-progress__fill--proving { inline-size: 75%; }
.skill-progress__fill--verified { inline-size: 100%; background: var(--success); }

/* Verified-% goal bar (My Tutor areas table, HR roster row-expand):
   matches the wireframe's "████░░░░" area bars — was unstyled (no rule
   existed at all, so the bar rendered as an invisible zero-height div). */
.goal-gradient { block-size: 0.6rem; min-inline-size: 6rem; border-radius: 999px; background: var(--surface-muted); overflow: hidden; }
.goal-gradient__fill { display: block; block-size: 100%; background: var(--success); border-radius: 999px; }

/* Event analytics (change 127): funnel bars + pill rows. Fluid — the bar
   fills a percentage of its track, so it scales to any width and needs no
   fixed canvas. Themeable via tokens. */
.funnel { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.9rem; }
.funnel__meta { display: flex; align-items: baseline; gap: 0.6rem; flex-wrap: wrap; }
.funnel__label { font-weight: 700; min-inline-size: 6rem; }
.funnel__bar { margin-top: 0.35rem; block-size: 0.7rem; border-radius: 999px; background: var(--surface-muted); overflow: hidden; }
.funnel__fill { display: block; block-size: 100%; background: var(--accent, var(--primary)); border-radius: 999px; }
.pill-row { list-style: none; display: flex; flex-wrap: wrap; gap: 0.35rem; margin: 0; padding: 0; }

/* Event promotional landing hero (change 122). Themed via the session
   preview the /event/<code>/ view sets. Logical properties for RTL; a
   comfortable single-column column that reads well on mobile. */
.event-hero {
  max-inline-size: 40rem;
  margin-inline: auto;
  padding-block: clamp(1.5rem, 6vw, 4rem);
  text-align: center;
}
.event-hero__logo { display: block; margin-inline: auto; max-block-size: 4.5rem; width: auto; margin-block-end: 1.25rem; }
.event-hero__title { font-size: clamp(1.8rem, 5vw, 3rem); margin: 0 0 0.75rem; }
.event-hero__lead { font-size: 1.1rem; margin: 0 auto 1.5rem; max-inline-size: 32rem; }
.event-hero__button { font-size: 1.05rem; min-block-size: 3rem; padding-inline: 1.5rem; }
.event-hero__cta { justify-content: center; }
.event-hero__note { margin-block-start: 1rem; }
@media print {
  /* No coloured hero / logo on paper — fall back to the text title. */
  .event-hero__logo { display: none; }
}

/* Event-theme banner (change 122): shown while an override is active. */
.event-theme-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem 1rem;
  padding: 0.6rem 1rem;
  background: var(--surface-muted);
  border-block-end: 1px solid var(--border);
  color: var(--text);
}
.event-theme-banner__text { font-weight: 600; }
.event-theme-banner__actions { gap: 0.4rem; }
.event-theme-banner__dismiss {
  background: none; border: none; color: inherit; cursor: pointer;
  font-size: 1.3rem; line-height: 1; padding: 0 0.3rem; min-height: 44px;
}
@media print { .event-theme-banner { display: none; } }
.compact-dl { display: grid; grid-template-columns: minmax(9rem, max-content) 1fr; gap: 0.5rem 1rem; }
.compact-dl dt { font-weight: 800; color: var(--text); }
.compact-dl dd { margin: 0; }

.empty-state { text-align: center; padding: 2rem; background: var(--surface-muted); border: 1px dashed var(--border-strong); border-radius: var(--radius); }
.empty-state h2, .empty-state h3 { margin-top: 0; }
.alert { padding: 0.8rem 0.95rem; border-radius: 0.7rem; border: 1px solid var(--border); background: var(--surface-muted); }
.alert.warning { background: var(--warning-bg); border-color: var(--warning); color: var(--warning); }
.alert.danger { background: var(--danger-bg); border-color: var(--danger); color: var(--danger); }
.alert.success { background: var(--success-bg); border-color: var(--success); color: var(--success); }

.progress { width: 100%; height: 0.75rem; appearance: none; border: 0; border-radius: 999px; overflow: hidden; background: var(--surface-muted); }
.progress::-webkit-progress-bar { background: var(--surface-muted); }
.progress::-webkit-progress-value { background: var(--accent, var(--primary)); }
.progress::-moz-progress-bar { background: var(--accent, var(--primary)); }

.test-header { display: flex; justify-content: space-between; gap: 1rem; align-items: flex-start; flex-wrap: wrap; }
.timer-box { background: var(--surface-muted); border: 1px solid var(--border); border-radius: 0.8rem; padding: 0.8rem; min-width: 220px; text-align: end; }
.timer-box.warning { background: var(--warning-bg); border-color: #facc15; color: var(--warning); }
.timer-box.expired { background: var(--danger-bg); border-color: #fca5a5; color: var(--danger); }
.timer-value { font-size: 1.55rem; font-weight: 850; letter-spacing: -0.03em; }
.test-meta { display: flex; gap: 0.5rem; flex-wrap: wrap; margin: 0.5rem 0; }
.question-card { border: 1px solid var(--border); border-radius: 0.8rem; margin: 1rem 0; padding: 1rem; background: var(--surface); }
.question-card legend { padding: 0 0.35rem; font-weight: 800; }
.question-stem { font-size: 1.05rem; }
.answer-list { list-style: none; padding: 0; margin: 0.75rem 0 0; display: grid; gap: 0.62rem; }
.answer-option { display: flex; gap: 0.65rem; align-items: flex-start; padding: 0.75rem; border: 1px solid var(--border); border-radius: 0.7rem; background: var(--surface); cursor: pointer; }
.answer-option:hover, .answer-option:focus-within { border-color: var(--focus-ring, #93c5fd); background: var(--surface-muted); }
.answer-option input { margin-top: 0.25rem; flex: 0 0 auto; }
.sticky-actions { position: sticky; bottom: 0; background: color-mix(in srgb, var(--surface) 96%, transparent); backdrop-filter: blur(8px); border-top: 1px solid var(--border); padding: 1rem 0 0.25rem; margin-top: 1rem; z-index: 10; }

.chart-panel canvas { width: 100%; max-width: 580px; height: auto; }
.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; }

@media (max-width: 767.98px) {
  /* Keep CTAs at a comfortable touch size on mobile (change 120's
     44px target-size scenario) even though desktop buttons are compact. */
  .button, button, input[type="submit"] { min-height: 2.75rem; }
  .main-shell { padding: 1rem; }
  .test-header { display: grid; }
  .timer-box { width: 100%; text-align: start; }
  .compact-dl { grid-template-columns: 1fr; }
  th, td { padding: 0.62rem; }

  /* --- Nav drawer (change 126). The hamburger shows; the inline bar becomes
     a slide-in drawer over a scrim, toggled by the no-JS checkbox and
     enhanced by initNavDrawer(). Logical properties so it slides from the
     inline-start edge (RTL-safe). --- */
  .nav-toggle {
    position: relative; display: inline-flex; align-items: center; justify-content: center;
    inline-size: 44px; block-size: 44px; margin-inline-start: auto;
    border-radius: 0.5rem; color: inherit;
  }
  /* The transparent checkbox fills the 44px hit area, sits on top, and is the
     focusable/clickable control; :focus-visible draws a ring on the wrapper. */
  .nav-toggle .nav-drawer-toggle {
    position: absolute; inset: 0; inline-size: 100%; block-size: 100%;
    margin: 0; cursor: pointer; z-index: 1;
  }
  .nav-toggle:has(.nav-drawer-toggle:focus-visible) { outline: 3px solid var(--focus-ring, #93c5fd); outline-offset: 2px; }
  .nav-toggle__bars, .nav-toggle__bars::before, .nav-toggle__bars::after {
    content: ""; display: block; inline-size: 22px; block-size: 2px;
    background: currentColor; border-radius: 2px; position: relative;
  }
  .nav-toggle__bars::before { position: absolute; inset-block-start: -7px; }
  .nav-toggle__bars::after { position: absolute; inset-block-start: 7px; }

  /* Change 150: the drawer treatment applies only when there are 2+ links to
     hold. With exactly one, `.primary-nav` stays an inline flex row (the
     desktop behaviour) and the hamburger is not rendered at all — see
     base.html. Scoped via the header's data-nav-count so ANY user whose
     permissions yield a single link benefits, not just event attendees. */
  .site-header:not([data-nav-count="1"]) .primary-nav {
    position: fixed;
    inset-block: 0; inset-inline-start: 0;
    inline-size: min(80vw, 20rem);
    flex-direction: column; align-items: stretch; gap: 0;
    padding: 4rem 0.75rem 1rem;
    background: var(--surface); color: var(--text);
    border-inline-end: 1px solid var(--border);
    transform: translateX(-100%);
    transition: transform 0.2s ease;
    z-index: 60; overflow-y: auto;
  }
  [dir="rtl"] .site-header:not([data-nav-count="1"]) .primary-nav { transform: translateX(100%); }
  /* Single link: keep it inline and reachable without any menu control. */
  .site-header[data-nav-count="1"] .primary-nav { margin-inline-start: auto; }
  /* Change 177: inside the drawer a group is a labelled SECTION, not a
     second disclosure — JS holds every group open and inerts its summary
     (nav-group--static); the summary reads as a heading over its links.
     Without JS the native <details> still opens on tap. */
  .nav-group { position: static; inline-size: 100%; }
  .nav-group__panel {
    position: static;
    min-inline-size: 0;
    border: 0;
    box-shadow: none;
    padding: 0 0 0.35rem 0;
    background: transparent;
  }
  .nav-group--static > summary.nav-group__toggle { pointer-events: none; }
  .nav-group--static > summary.nav-group__toggle::after { content: ""; }
  .nav-group--static > summary.nav-group__toggle,
  .nav-group > summary.nav-group__toggle {
    display: block;
    font-size: 0.78rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--muted, var(--header-fg-muted));
    padding-block: 0.5rem 0.15rem;
  }
  .nav-group--active > summary.nav-group__toggle { box-shadow: none; }
  .primary-nav a {
    display: flex; align-items: center; min-block-size: 44px;
    padding: 0.55rem 0.75rem; color: var(--text); border-radius: 0.5rem;
  }
  .primary-nav a[aria-current="page"] { background: var(--surface-muted); font-weight: 700; }

  .nav-scrim {
    display: block; position: fixed; inset: 0; z-index: 55;
    background: rgb(15 23 42 / 45%); opacity: 0; pointer-events: none;
    transition: opacity 0.2s ease;
  }

  /* Open state — driven by the no-JS checkbox OR the JS `is-open` class.
     The checkbox is nested INSIDE .nav-toggle (not a sibling of
     .primary-nav/.nav-scrim itself), so a bare `~` combinator off the
     checkbox can never reach them — :has() on the wrapper (already used
     above for the focus-visible ring) is what actually bridges the gap. */
  .nav-toggle:has(.nav-drawer-toggle:checked) ~ .primary-nav,
  .primary-nav.is-open { transform: translateX(0); }
  .nav-toggle:has(.nav-drawer-toggle:checked) ~ .nav-scrim,
  .nav-scrim.is-open { opacity: 1; pointer-events: auto; }
  body.nav-open { overflow: hidden; }  /* scroll-lock while the drawer is open */

  /* --- 44px tap targets (change 126). Controls that fell short of the
     WCAG target size on mobile get a 44px min hit area. Desktop unchanged. --- */
  .identity-menu__trigger,
  .identity-menu__item,
  .nav-grid button,
  /* Change 163 (F12): every .button measured 37–40px, the primary action
     smallest at 37px. Global by design — the same undersized buttons render
     on every page, and a page-scoped fix would fork this block's purpose. */
  .button,
  .button--small,
  .utility-row a,
  .segmented__btn { min-block-size: 44px; }
  .identity-menu__trigger, .identity-menu__item { padding-block: 0.6rem; }
  .flash__dismiss { min-inline-size: 44px; min-block-size: 44px; }

  /* --- Crowded grids stack (change 126). --- */
  .lq-outcome__stats { grid-template-columns: 1fr; }

  /* --- Stacked-card tables (change 126). Opt-in via .table--stack: each row
     becomes a labelled card and each cell shows its column label via
     data-label, which reads far better one-handed than a horizontal scroll.
     Tables without the class keep the .table-wrap scroll baseline. --- */
  .table--stack thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
  .table--stack tr {
    display: block; margin-block-end: 0.75rem;
    border: 1px solid var(--border); border-radius: var(--radius);
    background: var(--surface); padding: 0.5rem 0.75rem;
  }
  .table--stack td {
    display: flex; justify-content: space-between; gap: 1rem;
    border: 0; padding: 0.35rem 0; text-align: end;
  }
  .table--stack td::before {
    content: attr(data-label); font-weight: 700; color: var(--muted);
    text-align: start;
  }

  /* --- Dedicated mobile player (change 126). Single column; a compact
     STICKY timer strip that stays visible while scrolling a long form; full
     -width ≥44px answer rows; the primary action pinned in a sticky bottom
     bar within thumb reach. Desktop player is unchanged (≥768px). --- */
  .test-header {
    position: sticky; inset-block-start: 0; z-index: 20;
    background: var(--surface); padding-block: 0.5rem;
    border-block-end: 1px solid var(--border); margin-block-end: 0.75rem;
  }
  .timer-box {
    display: flex; align-items: baseline; gap: 0.5rem;
    padding: 0.45rem 0.65rem; min-width: 0;
  }
  .timer-value { font-size: 1.25rem; }
  .answer-option { padding: 0.85rem 0.75rem; min-block-size: 44px; align-items: center; }
  .answer-list { gap: 0.5rem; }
  .sticky-actions {
    /* Full-bleed thumb-reach bar; the buttons stretch so the primary action
       is a large tap target. */
    margin-inline: calc(-1 * 1rem);
    padding-inline: 1rem;
    padding-block-end: max(0.5rem, env(safe-area-inset-bottom));
  }
  .sticky-actions .button, .sticky-actions button { flex: 1 1 auto; }

  /* Question navigator becomes a collapsible "Jump to question" sheet
     (details/summary) so it doesn't push the current question down. */
  .nav-grid-sheet { border: 1px solid var(--border); border-radius: var(--radius); margin-block: 0.75rem; background: var(--surface); }
  .nav-grid-sheet > summary { padding: 0.75rem 1rem; min-block-size: 44px; cursor: pointer; font-weight: 700; list-style: none; }
  .nav-grid-sheet > summary::-webkit-details-marker { display: none; }
  .nav-grid-sheet[open] > summary { border-block-end: 1px solid var(--border); }
  .nav-grid-sheet .nav-grid { padding: 0.75rem; }
}

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

/* Error pages (403 / 404) — calm, apologetic, one clear action. */
.error-page { max-width: 34rem; margin: 3rem auto; text-align: center; }
.error-page__code { font-size: 3.5rem; font-weight: 800; color: var(--muted); margin: 0; letter-spacing: -0.03em; }
.error-page__heading { font-size: 1.6rem; margin: 0.25rem 0 0.75rem; }
.error-page__detail { color: var(--muted); }
.error-page__actions { margin: 1.5rem 0 1rem; }
.error-page__hint { color: var(--muted); font-size: 0.92rem; }
.error-page__hint code { user-select: all; }

/* Site footer (change 60g app chrome). */
.site-footer {
  max-width: 1280px; margin: 2rem auto 0; padding: 1.5rem 1.25rem;
  border-top: 1px solid var(--border); color: var(--muted); font-size: 0.9rem;
  display: flex; flex-wrap: wrap; gap: 0.5rem 1.25rem; align-items: center; justify-content: space-between;
}
.site-footer a { color: var(--muted); }
.site-footer__links { display: flex; flex-wrap: wrap; gap: 1rem; list-style: none; margin: 0; padding: 0; }
.site-footer__version { font-variant-numeric: tabular-nums; }

/* Language switcher (change 134): globe + endonym select. The globe makes the
   control findable regardless of the active script; logical properties keep it
   correct under RTL. */
.language-switcher { display: inline-flex; align-items: center; gap: 0.4rem; }
.language-switcher__label { display: inline-flex; align-items: center; margin: 0; }
.language-switcher__globe { font-size: 1.1rem; line-height: 1; }
.language-switcher select {
  padding: 0.3rem 0.5rem; border: 1px solid var(--border); border-radius: 0.4rem;
  background: var(--surface, var(--ground)); color: inherit; font: inherit;
}

/* Site-wide notice banner (maintenance advance-warning). */
.site-banner {
  background: var(--warning-bg); color: var(--warning); text-align: center;
  padding: 0.6rem 1rem; font-size: 0.95rem; border-bottom: 1px solid rgb(146 64 14 / 20%);
}

/* Identity / mode-switch widget (change 60f). */
.identity-menu { position: relative; margin-inline-start: auto; }
.identity-menu__trigger {
  display: inline-flex; align-items: center; gap: 0.5rem; cursor: pointer;
  color: var(--header-fg-muted); list-style: none; padding: 0.3rem 0.55rem; border-radius: 0.55rem;
}
.identity-menu__trigger::-webkit-details-marker { display: none; }
.identity-menu__trigger:hover, .identity-menu[open] .identity-menu__trigger { background: var(--header-hover-bg); color: var(--header-fg); }
.identity-avatar {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2rem; height: 2rem; border-radius: 50%; background: var(--accent, var(--primary));
  color: var(--accent-contrast, #fff); font-size: 0.8rem; font-weight: 700;
}
.identity-menu__name { font-weight: 600; }
.identity-badge {
  background: var(--header-hover-bg); color: var(--header-fg); border-radius: 999px;
  padding: 0.1rem 0.55rem; font-size: 0.75rem; font-weight: 600;
}
.identity-menu__panel {
  position: absolute; inset-inline-end: 0; top: calc(100% + 0.35rem); min-width: 14rem;
  background: var(--surface); color: var(--text); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow); padding: 0.4rem; z-index: 900;
}
.identity-menu__section-label { margin: 0.3rem 0.5rem; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); }
.identity-menu__modes { list-style: none; margin: 0; padding: 0; }
.identity-menu__item {
  display: block; width: 100%; text-align: start; background: none; border: none;
  color: var(--text); padding: 0.5rem 0.6rem; border-radius: 0.5rem; cursor: pointer; font-size: 0.95rem;
}
.identity-menu__item:hover { background: var(--surface-muted); color: var(--text); text-decoration: none; }
.identity-menu__item[aria-checked="true"] { font-weight: 700; }
.identity-menu__item[aria-checked="true"]::after { content: " ✓"; color: var(--primary); }
.identity-menu__item--logout { color: var(--danger); }
.identity-menu__divider { border: none; border-top: 1px solid var(--border); margin: 0.35rem 0; }
.identity-menu__logout { margin: 0; }

@media print { .identity-menu, .site-banner, .primary-nav, .toast-region, .no-print, .site-header, .site-footer { display: none; } }

/* Breadcrumbs (change 60g). */
.breadcrumbs { max-width: 1280px; margin: 0 auto; padding: 0.75rem 1.25rem 0; }
.breadcrumbs__list { list-style: none; display: flex; flex-wrap: wrap; gap: 0.35rem; margin: 0; padding: 0; font-size: 0.9rem; color: var(--muted); }
.breadcrumbs__item + .breadcrumbs__item::before { content: "/"; margin-inline-end: 0.35rem; color: var(--border-strong); }
.breadcrumbs__item [aria-current="page"] { color: var(--text); font-weight: 600; }
@media (max-width: 640px) {
  /* Collapse the middle: keep first and current. */
  .breadcrumbs__item:not(:first-child):not(:last-child) { display: none; }
  .breadcrumbs__item:first-child::after { content: " / …"; color: var(--border-strong); }
}

/* Flash messages (change 60g). */
.messages { list-style: none; margin: 0 0 1rem; padding: 0; display: grid; gap: 0.5rem; }
.flash {
  display: flex; align-items: flex-start; gap: 0.75rem; justify-content: space-between;
  padding: 0.7rem 0.9rem; border-radius: 0.6rem; border: 1px solid transparent;
}
.flash--success, .flash--info { background: var(--success-bg); border-color: rgb(22 101 52 / 20%); color: var(--success); }
.flash--info { background: var(--info-bg); color: var(--info-fg, #075985); border-color: rgb(7 89 133 / 20%); }
.flash--warning { background: var(--warning-bg); color: var(--warning); border-color: rgb(146 64 14 / 25%); }
.flash--error, .flash--danger { background: var(--danger-bg); color: var(--danger); border-color: rgb(185 28 28 / 25%); }
.flash__dismiss { background: none; border: none; color: inherit; cursor: pointer; font-size: 1.15rem; line-height: 1; padding: 0; }
.flash__dismiss:hover { background: none; color: inherit; opacity: 0.7; }

/* Global toast region (HTMX errors etc.) — pinned, above content. */
.toast-region { position: fixed; inset-inline-end: 1rem; bottom: 1rem; display: grid; gap: 0.5rem; z-index: 1100; max-width: min(90vw, 26rem); }
.toast {
  background: var(--danger-bg); color: var(--danger); border: 1px solid rgb(185 28 28 / 30%);
  border-radius: 0.6rem; padding: 0.7rem 0.9rem; box-shadow: var(--shadow); font-size: 0.92rem;
}
.toast code { user-select: all; }

/* Empty states (change 60g). */
.empty-state { text-align: center; padding: 2.5rem 1rem; color: var(--muted); }
.empty-state__icon { font-size: 2rem; }
.empty-state__title { margin: 0.5rem 0 0.25rem; color: var(--text); font-size: 1.1rem; }
.empty-state__action { margin-top: 1rem; }

/* Pagination (change 60g). */
.pagination { display: flex; align-items: center; gap: 0.75rem; margin: 1.25rem 0; justify-content: center; font-size: 0.92rem; }
.pagination__status { color: var(--muted); }

/* HTMX request indicator. */
.htmx-indicator { opacity: 0; transition: opacity 0.15s; }
.htmx-request .htmx-indicator, .htmx-request.htmx-indicator { opacity: 1; }

/* Audit browser (change 69). */
.audit-filters { display: flex; flex-wrap: wrap; gap: 1rem; align-items: flex-end; margin-bottom: 1rem; }
.audit-table { width: 100%; border-collapse: collapse; }
.audit-table th, .audit-table td { padding: 0.5rem 0.65rem; border-bottom: 1px solid var(--border); text-align: start; vertical-align: top; }
.audit-table code { font-size: 0.85rem; }
.table-scroll { overflow-x: auto; }

/* Notification nav badge + list (change 66). */
.nav-badge {
  display: inline-block; min-width: 1.25rem; padding: 0 0.35rem; margin-inline-start: 0.15rem;
  background: var(--danger); color: #fff; border-radius: 999px; font-size: 0.72rem;
  font-weight: 700; text-align: center; line-height: 1.25rem;
}
.notifications__header { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.notifications__list { list-style: none; margin: 1rem 0 0; padding: 0; display: grid; gap: 0.5rem; }
.notification {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem;
  padding: 0.85rem 1rem; border: 1px solid var(--border); border-radius: 0.65rem; background: var(--surface);
}
.notification--unread { border-inline-start: 3px solid var(--primary); background: var(--surface-muted); }
.notification__subject { margin: 0 0 0.25rem; font-weight: 600; }
.notification__text { margin: 0 0 0.35rem; color: var(--muted); }
.notification__meta { margin: 0; color: var(--muted); font-size: 0.85rem; }

/* Form standards (change 60g). */
.form-field { margin-bottom: 1rem; }
.field-error { color: var(--danger); font-size: 0.88rem; margin: 0.25rem 0 0; }
.form-errors { background: var(--danger-bg); color: var(--danger); padding: 0.7rem 0.9rem; border-radius: 0.6rem; margin-bottom: 1rem; }
.required-marker { color: var(--danger); }
.form-actions { display: flex; gap: var(--space-inline); align-items: center; margin-top: var(--space-section); flex-wrap: wrap; }

/* Per-test attach + attempt-limit rows (change 149). */
.test-attach-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.test-attach-list li { display: flex; flex-wrap: wrap; align-items: center; gap: 0.75rem 1.25rem; padding: 0.5rem 0.7rem; border: 1px solid var(--border); border-radius: 0.5rem; }
.test-attempt-controls { display: flex; align-items: center; gap: 0.5rem; margin-inline-start: auto; }
.test-attempt-controls input[type="number"] { width: 5rem; }

/* Save-and-exit affordance (BUG-0006). Set apart from the primary Back/Next
   row so leaving is discoverable but never competes with finishing. */
.click-exit { margin-block-start: 1rem; display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }

/* Untimed mastery (BUG-0005): same box, no urgency styling — it must not read
   as a countdown that happens to be stopped. */
.timer-box--untimed { opacity: 0.85; }

/* Event-scoped navigation picker (change 150). Two columns on wider screens,
   one below — the summary line always spans the full width so it is never
   missed. Logical properties throughout (RTL-safe, per the lint gate). */
.nav-scope { border: 1px solid var(--border); border-radius: 0.5rem; padding: 0.9rem 1rem; }
.nav-scope > legend { font-weight: 700; padding-inline: 0.35rem; }
.nav-scope__list { list-style: none; margin: 0.5rem 0 0; padding: 0; display: grid; gap: 0.35rem 1.25rem; }
@media (min-width: 768px) { .nav-scope__list { grid-template-columns: 1fr 1fr; } }
.nav-scope__row { padding: 0.3rem 0; }
.nav-scope__note { margin: 0.15rem 0 0 1.6rem; }
.nav-scope__summary {
  grid-column: 1 / -1; margin: 0.85rem 0 0; padding: 0.55rem 0.7rem;
  border-inline-start: 3px solid var(--accent, var(--border));
  background: var(--surface-muted); border-radius: 0.35rem; font-weight: 600;
}
.nav-scope__duration { margin-block-start: 0.85rem; }
.nav-scope__duration input[type="number"] { width: 6rem; }
/* "Not applicable" while the allowlist is empty. Deliberately NOT :disabled —
   a disabled input is not submitted, which would silently reset a custom
   duration on save. Dimmed + aria-disabled instead, so it still posts. */
.nav-scope__duration.is-inert { opacity: 0.55; }

/* Feature toggle panels (change 97). */
.feature-panel .feature-table { width: 100%; border-collapse: collapse; margin-top: 1rem; }
.feature-panel .feature-table th, .feature-panel .feature-table td { padding: 0.65rem 0.7rem; border-bottom: 1px solid var(--border); text-align: start; vertical-align: middle; }
.feature-panel .feature-table thead th { font-size: 0.85rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; }
.feature-desc { display: block; color: var(--muted); font-size: 0.9rem; font-weight: 400; margin-top: 0.15rem; }
.inline-form { display: inline-flex; gap: 0.5rem; align-items: center; margin: 0; }
.company-picker { margin: 0.5rem 0 1rem; }
.pill--on { background: var(--success-bg); color: var(--success); }
.pill--off { background: var(--danger-bg); color: var(--danger); }

/* Background/feedback surveys (change 99). */
.survey-question { border: 1px solid var(--border); border-radius: 0.65rem; padding: 1rem 1.1rem; margin: 0 0 1rem; }
.survey-question legend { font-weight: 640; padding: 0 0.4rem; }
.choice-row { display: flex; align-items: center; gap: 0.6rem; padding: 0.5rem 0.2rem; min-height: 44px; cursor: pointer; }
.choice-row input { width: 1.1rem; height: 1.1rem; }
.survey-banner { border-inline-start: 3px solid var(--primary); }
.profile-list { display: grid; grid-template-columns: minmax(9rem, 1fr) 2fr; gap: 0.35rem 1rem; margin: 0; }
.profile-list dt { font-weight: 640; color: var(--muted); }
.profile-list dd { margin: 0; }

/* Promotions + events (change 101). */
.promo-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.75rem; }
.promo-list li { padding: 0.75rem; border: 1px solid var(--border); border-radius: 0.6rem; }
.promo-list a { font-weight: 640; }

/* View-as (impersonation) banner (change 104). */
.impersonation-banner { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; background: #92400e; color: #fff; padding: 0.6rem 1.25rem; font-weight: 640; }
.impersonation-banner .button.ghost { background: #fff; color: #92400e; border-color: #fff; }

/* Player v2 (change 72). */
.pretest-facts ul { margin: 0.5rem 0; }
.agreement-body { max-height: 12rem; overflow-y: auto; border: 1px solid var(--border); border-radius: 0.5rem; padding: 0.75rem; margin: 0.5rem 0; background: var(--surface-muted); }
.save-chip { display: inline-flex; align-items: center; gap: 0.3rem; padding: 0.15rem 0.5rem; border-radius: 999px; font-size: 0.8rem; font-weight: 640; }
.save-chip--saved { background: var(--success-bg); color: var(--success); }
.save-chip--saving { background: var(--info-bg); color: var(--info-fg, #075985); }
.save-chip--offline { background: var(--warning-bg); color: var(--warning); }
.nav-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(2.5rem, 1fr)); gap: 0.35rem; }
.nav-grid button { min-height: 2.5rem; padding: 0; }
.nav-grid .answered { background: var(--success); }
.nav-grid .flagged { background: var(--warning); color: #1f2937; }
.nav-grid .unanswered { background: var(--surface); color: var(--text); border-color: var(--border-strong); }

/* ------------------------------------------------------------------ *
 * Student "My tests" list (compact, scrollable, filterable).
 * Replaces the two-big-card grid so 12+ assignments stay scannable and
 * do not push "Recent results" below the fold. No JS framework: filtering
 * is progressive-enhancement in app.js; without JS the full list shows.
 * ------------------------------------------------------------------ */
.test-list__head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.test-list__head h2 { margin: 0; }
.test-list__count { margin: 0; font-weight: 650; }

.test-list__toolbar {
  position: sticky; top: 0; z-index: 2;
  display: flex; gap: 0.75rem; flex-wrap: wrap; align-items: center;
  /* Inline padding keeps the search field and the segmented filter inset from
     the toolbar edges (they used to run flush against both sides). */
  padding: 0.6rem 1rem; margin: 0.4rem 0 0.6rem;
  background: var(--bg);
}
.test-list__search { flex: 1 1 16rem; min-width: 12rem; }
.test-list__search input {
  width: 100%; padding: 0.55rem 0.8rem;
  border: 1px solid var(--border-strong); border-radius: 999px;
  background: var(--surface); color: var(--text); font-size: 0.95rem;
}
.test-list__search input:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }

.segmented { display: inline-flex; border: 1px solid var(--border-strong); border-radius: 999px; overflow: hidden; background: var(--surface); }
.segmented__btn {
  appearance: none; border: 0; margin: 0;
  padding: 0.5rem 0.9rem; background: transparent; color: var(--muted);
  font-weight: 700; font-size: 0.9rem; cursor: pointer;
  border-inline-start: 1px solid var(--border);
}
.segmented__btn:first-child { border-inline-start: 0; }
.segmented__btn:hover { background: var(--surface-muted); color: var(--text); }
.segmented__btn.is-active { background: var(--accent, var(--primary)); color: var(--accent-contrast, #fff); }
.segmented__btn:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }

/* Scrollable region: caps at ~5 rows tall, then scrolls; results stay reachable.
   The cap only engages when the content is tall, so a short list is not boxed. */
.test-list__rows {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 0.5rem;
  max-height: min(56vh, 34rem); overflow-y: auto;
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: 0.5rem; background: var(--surface-muted);
}
.test-row {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: 0.7rem 0.9rem;
  background: var(--surface); border: 1px solid var(--border); border-radius: 0.7rem;
}
.test-row[hidden] { display: none; }
.test-row__main { min-width: 0; }
.test-row__tags { margin-bottom: 0.45rem; row-gap: 0.35rem; }
.test-row__title { margin: 0 0 0.15rem; font-size: 1.02rem; }
.test-row__meta { margin: 0; font-size: 0.87rem; }
.test-row__action { display: flex; align-items: center; gap: 0.55rem; flex: 0 0 auto; }

.test-list__empty { padding: 1.2rem; text-align: center; }

@media (max-width: 640px) {
  .test-row { flex-direction: column; align-items: stretch; }
  .test-row__action { justify-content: flex-end; }
  .test-list__rows { max-height: none; }  /* let the page scroll on small screens */
}

/* --- Proficiency band chip (changes 113/112/114) --------------------------
   Five sequential hues, one per band ordinal. Triple-encoded (colour + glyph +
   ordinal + label) so meaning never depends on colour alone. Label contrast is
   >= 4.5:1 on each fill in both themes. */
:root {
  --band-1-bg: #ede9fe; --band-1-fg: #5b21b6;  /* Awareness  — purple  */
  --band-2-bg: #cffafe; --band-2-fg: #0e7490;  /* Beginner   — teal    */
  --band-3-bg: #dcfce7; --band-3-fg: #166534;  /* Experienced— green   */
  --band-4-bg: #fef3c7; --band-4-fg: #92400e;  /* Advanced   — amber   */
  --band-5-bg: #dbeafe; --band-5-fg: #1e3a8a;  /* Expert     — navy    */
}
/* Dark band tokens. themes.css defines no band palette, so bands follow the
 * effective surface, not the theme's own tokens. Two triggers:
 *  1. OS prefers dark AND the root is unthemed/neutral (the readiness path).
 *  2. A named DARK theme is active (its surface is dark regardless of OS),
 *     so its band chips need dark backgrounds for contrast.
 * Light named themes fall through to the default light bands above. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]), :root[data-theme=""], :root[data-theme="dark"] {
    --band-1-bg: #3b2a63; --band-1-fg: #ddd6fe;
    --band-2-bg: #164e5b; --band-2-fg: #a5f3fc;
    --band-3-bg: #14532d; --band-3-fg: #bbf7d0;
    --band-4-bg: #4a2f0a; --band-4-fg: #fde68a;
    --band-5-bg: #1e2f5a; --band-5-fg: #bfdbfe;
  }
}
:root[data-theme="dark"],
:root[data-theme="evolve-aurora"], [data-theme="evolve-aurora"]:not(:root),
:root[data-theme="evolve26-nova"], [data-theme="evolve26-nova"]:not(:root),
:root[data-theme="cloudera-deep"], [data-theme="cloudera-deep"]:not(:root),
:root[data-theme="cloudera-midnight"], [data-theme="cloudera-midnight"]:not(:root),
:root[data-theme="classic-dark"], [data-theme="classic-dark"]:not(:root),
:root[data-theme="terminal-amber"], [data-theme="terminal-amber"]:not(:root) {
  --band-1-bg: #3b2a63; --band-1-fg: #ddd6fe;
  --band-2-bg: #164e5b; --band-2-fg: #a5f3fc;
  --band-3-bg: #14532d; --band-3-fg: #bbf7d0;
  --band-4-bg: #4a2f0a; --band-4-fg: #fde68a;
  --band-5-bg: #1e2f5a; --band-5-fg: #bfdbfe;
}
.band-chip {
  display: inline-flex; align-items: center; gap: 0.4rem;
  padding-block: 0.2rem; padding-inline: 0.55rem;
  border-radius: 999px; font-weight: 650; font-size: 0.85rem; line-height: 1.3;
  border: 1px solid transparent; white-space: nowrap;
}
.band-chip--compact { gap: 0.3rem; padding-inline: 0.4rem; font-size: 0.78rem; }
.band-chip--compact .band-chip__label { max-width: 8rem; overflow: hidden; text-overflow: ellipsis; }
.band-chip__glyph { font-size: 0.9em; }
.band-chip__ordinal { font-variant-numeric: tabular-nums; opacity: 0.75; }
.band-chip__flag { font-weight: 800; }
.band-chip--o1 { background: var(--band-1-bg); color: var(--band-1-fg); }
.band-chip--o2 { background: var(--band-2-bg); color: var(--band-2-fg); }
.band-chip--o3 { background: var(--band-3-bg); color: var(--band-3-fg); }
.band-chip--o4 { background: var(--band-4-bg); color: var(--band-4-fg); }
.band-chip--o5 { background: var(--band-5-bg); color: var(--band-5-fg); }
.band-chip--target { border-color: currentColor; border-style: dashed; }
.band-chip--nodata, .band-chip--suppressed {
  background: var(--surface-muted); color: var(--muted); border-color: var(--border);
  font-weight: 550; font-size: 0.8rem;
}

/* --- Per-chapter levels on the CAT result page (change 112) --------------- */
.chapter-levels { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.85rem; }
.chapter-level { padding-block: 0.7rem; border-block-end: 1px solid var(--border); }
.chapter-level:last-child { border-block-end: 0; }
.chapter-level__head { display: flex; justify-content: space-between; gap: 0.75rem; align-items: baseline; }
.chapter-level__name { font-weight: 650; }
.chapter-level__band { display: flex; flex-wrap: wrap; align-items: center; gap: 0.55rem; margin-block-start: 0.35rem; }
.chapter-level__range { font-size: 0.85rem; }
.chapter-level__advice { margin-block-start: 0.35rem; font-size: 0.9rem; }
.chapter-level__note { margin-block-start: 0.3rem; }
.chapter-levels__how { margin-block-start: 0.9rem; }
.chapter-levels__how summary { cursor: pointer; color: var(--primary); font-weight: 600; }
.result-next__text { font-size: 1.02rem; }
.skill-levels { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.5rem; }
.skill-level { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
.skill-level__name { font-weight: 600; min-inline-size: 12rem; }
.skill-level__meta { font-size: 0.85rem; }

/* --- HR course skills dashboard (change 114) ----------------------------- */
.skills-header { margin-block-end: 0.5rem; }
.skills-main { display: grid; grid-template-columns: 3fr 2fr; gap: 1rem; align-items: start; }
.skills-side { display: grid; gap: 0.75rem; }
.skills-time { display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: end; }
.skills-time__hint { margin-block-start: 0.4rem; }
.skills-radar { max-width: 320px; margin-inline: auto; display: block; }
.legend-swatch { display: inline-block; inline-size: 0.9rem; block-size: 0.55rem; border-radius: 2px; vertical-align: middle; margin-inline-end: 0.2rem; }
.legend-swatch--now { background: var(--primary); }
.legend-swatch--past { background: repeating-linear-gradient(90deg, var(--secondary) 0 4px, transparent 4px 7px); }
.legend-swatch--member { background: repeating-linear-gradient(90deg, var(--warning) 0 1px, transparent 1px 4px); }
.skills-box ul { margin: 0.3rem 0 0; padding-inline-start: 0; list-style: none; display: grid; gap: 0.25rem; }
.skills-grid-wrap { overflow-x: auto; }
.skills-grid th, .skills-grid td { white-space: nowrap; padding: 0.4rem 0.5rem; }
.skills-grid__member { position: sticky; inset-inline-start: 0; background: var(--surface); text-align: start; z-index: 1; }
.skills-grid__teamrow { background: var(--surface-muted); font-weight: 650; }
.skills-grid__teamrow .skills-grid__member { background: var(--surface-muted); }
.skills-grid__selected { outline: 2px solid var(--primary); outline-offset: -2px; }
.skills-grid__selected .skills-grid__member { background: var(--info-bg); }
@media (max-width: 820px) {
  .skills-main { grid-template-columns: 1fr; }
}

/* Level Quiz (change 100) — manager UI and student player */
.lq-fieldset {
  border: 1px solid var(--border);
  border-radius: 0.7rem;
  padding: 1rem 1.1rem;
  margin: 0 0 1.1rem;
  background: var(--surface);
}
.lq-fieldset > legend {
  padding: 0 0.4rem;
  font-weight: 700;
  color: var(--muted);
  letter-spacing: 0.03em;
  text-transform: uppercase;
  font-size: 0.85rem;
}
.lq-fieldset > p.muted:first-of-type { margin-block-start: 0; }
.lq-checkbox {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  cursor: pointer;
  margin-block: 0.3rem;
}
.lq-checkbox input {
  width: auto;
  margin: 0;
}
.grid--pass-marks {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 0.9rem 1.1rem;
}
.grid--pass-marks label { display: block; margin-bottom: 0.3rem; }
.lq-pools {
  width: 100%;
  border-collapse: collapse;
}
.lq-pools th, .lq-pools td {
  padding: 0.55rem 0.7rem;
  border-bottom: 1px solid var(--border);
  text-align: start;
}
.lq-pools thead th { font-weight: 650; color: var(--muted); }

/* Student-facing outcome screen — level pass / level fail / final congrats */
.lq-outcome {
  text-align: center;
  padding: 2.4rem 1.6rem;
  max-width: 640px;
  margin: 1.4rem auto;
}
.lq-outcome h1 {
  font-size: 2.1rem;
  margin: 0.4rem 0 0.8rem;
  letter-spacing: -0.02em;
}
.lq-outcome__lede {
  font-size: 1.1rem;
  color: var(--muted);
  margin: 0 0 1.6rem;
}
.lq-outcome--congrats { border-color: #86efac; background: var(--success-bg); }
.lq-outcome--congrats h1 { color: var(--success); }
.lq-outcome--fail { border-color: #fca5a5; background: var(--danger-bg); }
.lq-outcome--fail h1 { color: var(--danger); }
.lq-outcome--pass { background: var(--surface); }
.lq-outcome__stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1rem;
  margin: 1.6rem 0;
}
/* Per-level recap under the whole-quiz totals on a terminal screen (BUG-0011),
   so the roll-up does not hide how each individual level went. */
.lq-outcome__per-level { margin: -0.9rem 0 1.4rem; font-size: 0.9rem; }
.lq-outcome__stat {
  padding: 1rem 0.6rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 0.7rem;
}
.lq-outcome__stat-value {
  display: block;
  font-size: 1.7rem;
  font-weight: 800;
  letter-spacing: -0.03em;
}
.lq-outcome__stat-label {
  display: block;
  color: var(--muted);
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-top: 0.2rem;
}

/* ---- Theme chooser gallery (change 123) --------------------------------
   A responsive comparison grid where each card previews its OWN theme via a
   nested data-theme scope (substrate from change 125). Logical properties
   throughout so the ✓ badge and swatches mirror under dir="rtl". Token-based
   so the selected state is visible in every theme. Written from scratch. */
.theme-picker {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 1rem;
  margin: 1rem 0;
}
@media (max-width: 360px) {
  .theme-picker { grid-template-columns: 1fr; }
}
.theme-card {
  position: relative;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;
  margin: 0;
  cursor: pointer;
}
/* The previewing panel renders in the card's own theme (data-theme scope):
   background from the theme's --bg (via the change-125 legacy bridge). */
.theme-card__preview {
  padding: 1rem;
  background: var(--bg);
  color: var(--text);
  border-block-end: 1px solid var(--border);
}
.theme-card__bar {
  font-weight: 800;
  font-size: 0.8rem;
  letter-spacing: 0.02em;
  color: var(--muted);
  margin-block-end: 0.5rem;
}
.theme-card__heading { margin: 0 0 0.35rem; font-family: var(--font-display, inherit); color: var(--heading, inherit); font-size: 1.1rem; }
.theme-card__text { margin: 0 0 0.5rem; font-size: 0.92rem; }
.theme-card__link { color: var(--primary); }
.theme-card__cta {
  display: inline-block;
  margin-block-start: 0.4rem;
  pointer-events: none; /* decorative preview */
}
.theme-card__swatches { display: inline-flex; gap: 0.3rem; margin-inline-start: 0.6rem; vertical-align: middle; }
.theme-card__swatch { inline-size: 1rem; block-size: 1rem; border-radius: 0.25rem; border: 1px solid var(--border); }
.theme-card__swatch--surface { background: var(--surface); }
.theme-card__swatch--accent { background: var(--accent, var(--primary)); }
.theme-card__swatch--text { background: var(--text); }
.theme-card__meta { padding: 0.85rem 1rem; }
.theme-card__meta h3 { margin: 0 0 0.2rem; font-size: 1.02rem; }
.theme-card__actions { padding: 0 1rem 1rem; margin: 0; }

/* Selected / active state — a token ring + a ✓ badge, visible in every
   theme. User page: driven by the checked radio; admin page: .is-active. */
.theme-card__radio:focus-visible + .theme-card__preview,
.theme-card:focus-within {
  outline: 3px solid var(--focus-ring, #93c5fd);
  outline-offset: 2px;
}
.theme-card.is-selected,
.theme-card.is-active,
.theme-card__radio:checked ~ .theme-card__preview ~ .theme-card__meta,
.theme-card:has(.theme-card__radio:checked) {
  border-color: var(--accent, var(--primary));
  box-shadow: inset 0 0 0 2px var(--accent, var(--primary));
}
.theme-card.is-selected::after,
.theme-card.is-active::after,
.theme-card:has(.theme-card__radio:checked)::after {
  content: "✓";
  position: absolute;
  inset-block-start: 0.5rem;
  inset-inline-end: 0.5rem;
  inline-size: 1.6rem;
  block-size: 1.6rem;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--accent, var(--primary));
  color: var(--accent-contrast, #fff);
  font-weight: 800;
}


/* ---- Change 155: rules for the classes BUG-0013 found bare. -----------------
 * Six classes were referenced across ~70 template usages with zero rules, so
 * validation errors rendered as plain body text and filter bars stacked
 * vertically. Composed from existing tokens; visual weight matches each
 * class's closest styled sibling so nothing here invents a second design
 * vocabulary. Logical properties only (60d).
 */

/* Same treatment as .form-errors/.errorlist — an error must look like one. */
.form-error { color: var(--danger); font-weight: 650; margin: 0.35rem 0; }

/* Sibling of .messages li: a standalone callout paragraph. `notice error` /
 * `notice warning` reuse the message palette. */
.notice { padding: 0.82rem 0.95rem; border-radius: 0.65rem; margin: 0 0 1rem; background: var(--info-bg); border: 1px solid var(--border); color: var(--info-fg, #075985); }
.notice.error { background: var(--danger-bg); border-color: var(--danger); color: var(--danger); }
.notice.warning { background: var(--warning-bg); border-color: var(--warning); color: var(--warning); }

/* The muted helper line under a form control. */
.form-hint { color: var(--muted); font-size: 0.9rem; margin: 0.35rem 0 0; }

/* A simple top-to-bottom form: fields stack with the shared rhythm. */
.stacked-form { display: flex; flex-direction: column; gap: var(--space-stack); max-width: 34rem; }

/* The filter/search row above a table: controls flow inline and wrap. */
.filter-bar { display: flex; flex-wrap: wrap; gap: var(--space-inline); align-items: end; margin-bottom: var(--space-section); }

/* Singular alias of the .breadcrumbs component (do NOT fork a second
 * breadcrumb system): templates that wrote class="breadcrumb" get the same
 * container treatment. */
.breadcrumb { max-width: 1280px; margin: 0 auto; padding: 0.75rem 1.25rem 0; font-size: 0.9rem; color: var(--muted); }

/* ---- Change 155: tutoring modifier classes. ---------------------------------
 * These sit ON already-styled elements (.alert.warning / .muted.small) and
 * previously added no meaning of their own. Modifier rules only — the base
 * styling stays where it is.
 */

/* The teaching moment the learn-first flow centres on: an accent edge gives
 * it a hair more presence than a generic warning body, whether the class
 * sits on the alert itself (dignity) or on a block inside one (reframe). */
.wrong-answer-moment { border-inline-start: 4px solid var(--warning); padding-inline-start: 0.9rem; }
.wrong-answer-moment p { margin-block: 0.5rem; }

/* The "something wrong with this question?" disclosure: separated from the
 * teaching content above it so reporting never competes with learning. */
.challenge-disclosure { margin-block-start: var(--space-section); font-size: 0.9rem; }
.challenge-disclosure summary { cursor: pointer; color: var(--muted); }

/* The unlocalised-content notice: present but deliberately quiet. */
.fallback-notice { font-style: italic; opacity: 0.85; }

/* ---- Change 157: readiness gauge + per-area meters. -------------------------
 * The platform already computed a verified percentage per student and per area
 * and rendered four dots instead, so progress inside a stage was invisible.
 * Built from existing tokens only, so all 16 themes keep their contrast, and
 * with logical properties throughout so RTL is unaffected (60d).
 *
 * Meaning never rests on colour alone: every gauge carries its number as text
 * and an aria-label naming measure, value and target.
 */
/* The semicircular readiness dial. The bands are the readiness engine's own
   stages, so the needle and the stage badge can never disagree; the colours run
   cool-to-warm in stage order and are ALWAYS accompanied by the stage name in
   text, because roughly one in twelve men cannot separate the red from the
   green. Sized in `em` so a compact per-area dial scales from one font-size. */
.readiness-dial {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  margin: 0;
  font-size: 1rem;
}
.readiness-dial__svg {
  display: block;
  inline-size: 100%;
  max-inline-size: 15em;   /* without this the SVG stretches to the card width */
  block-size: auto;
}
/* The four bands need four DISTINGUISHABLE hues, which the semantic tokens
   cannot guarantee: `--link` is undefined in the base :root (so it would draw
   nothing where a theme omits it), and in at least one theme --link and
   --success are the same hex, which would merge "approaching" into "on track".
   So the dial owns its own band tokens, defaulting to the semantic colour where
   one fits and falling back per band otherwise. A theme that wants a different
   dial ramp overrides these four and nothing else. */
:root {
  --dial-early: var(--danger);
  --dial-building: var(--warning);
  --dial-approaching: #3b5bdb;
  --dial-on-track: var(--success);
}
/* Dark grounds need a lighter blue to hold 3:1. Same theme list the banded
   result colours above use, so a new dark theme picks both up together. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]), :root[data-theme=""], :root[data-theme="dark"] {
    --dial-approaching: #8ab4ff;
  }
}
:root[data-theme="dark"],
:root[data-theme="evolve-aurora"], [data-theme="evolve-aurora"]:not(:root),
:root[data-theme="evolve26-nova"], [data-theme="evolve26-nova"]:not(:root),
:root[data-theme="cloudera-deep"], [data-theme="cloudera-deep"]:not(:root),
:root[data-theme="cloudera-midnight"], [data-theme="cloudera-midnight"]:not(:root),
:root[data-theme="classic-dark"], [data-theme="classic-dark"]:not(:root),
:root[data-theme="terminal-amber"], [data-theme="terminal-amber"]:not(:root) {
  --dial-approaching: #8ab4ff;
}
.readiness-dial__band { opacity: 0.9; }
.readiness-dial__band--early { stroke: var(--dial-early); }
.readiness-dial__band--building { stroke: var(--dial-building); }
.readiness-dial__band--approaching { stroke: var(--dial-approaching); }
.readiness-dial__band--on-track { stroke: var(--dial-on-track); }
.readiness-dial__needle { stroke: var(--text); }
.readiness-dial__hub { fill: var(--text); }
/* The target notch: same weight as the needle but quieter, so "where I am"
   stays louder than "where I'm going". */
.readiness-dial__target { stroke: var(--text); opacity: 0.5; }
.readiness-dial__caption {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.15rem;
  text-align: center;
}
.readiness-dial__value { font-size: 1.75em; font-weight: 700; line-height: 1.1; }
.readiness-dial__stage { font-weight: 600; }
/* The caption's target line. Emitted since change 157 with no rule of its own —
   it inherited `.muted.small` and happened to look right; naming it makes the
   dial's three caption parts independently styleable and stops the class from
   being a silent no-op (change 161). */
.readiness-dial__target-label { display: block; }
.readiness-dial--compact { font-size: 0.8rem; }
.readiness-dial--compact .readiness-dial__svg { max-inline-size: 9em; }
.readiness-dial--empty { margin: 0; font-style: italic; }

.progress-gauge { display: flex; flex-direction: column; gap: 0.25rem; }
.progress-gauge__track {
  position: relative;
  block-size: 0.7rem;
  min-inline-size: 8rem;
  border-radius: 999px;
  background: var(--surface-muted);
  border: 1px solid var(--border);
  overflow: hidden;
}
.progress-gauge__fill {
  block-size: 100%;
  background: var(--success);
  border-radius: 999px;
  transition: inline-size 200ms ease;
}
/* The target marker. A percentage means little on its own; this is what turns
   "63%" into "63%, and 80% is the goal". */
.progress-gauge__floor {
  position: absolute;
  inset-block: -2px;
  inline-size: 2px;
  background: var(--text);
  opacity: 0.55;
}
.progress-gauge__legend { display: flex; gap: 0.4rem; align-items: baseline; margin: 0; }
.progress-gauge--empty { margin: 0; font-style: italic; }

.area-meters { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-stack); }
/* Change 184 UX polish: bare list items floated as an orphaned 4+1 grid with
   no visual boundaries. Wrap each meter in a light surface tile so the tile
   itself carries the "one topic" boundary — the orphan on the second row now
   reads as a card, not as loose text. Framing stays lightweight: surface tint
   + 1px border, no shadow, no elevation. Density that change 163 asked for is
   preserved (`padding` is smaller than a `.card`'s). */
.area-meter {
  display: grid;
  gap: 0.35rem;
  padding: 0.7rem 0.85rem;
  background: var(--surface-muted);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.area-meter__head { display: flex; gap: 0.5rem; align-items: baseline; flex-wrap: wrap; margin: 0; }

@media (prefers-reduced-motion: reduce) {
  .progress-gauge__fill { transition: none; }
}

/* ---------------------------------------------------------------------------
 * My Tutor hierarchy (change 161, findings F1/F5/F9 — BUG-0026).
 *
 * Both classes were emitted by my_tutor_home.html from the day it shipped and
 * neither had a rule anywhere, so the page's intended hierarchy was silently
 * discarded by the cascade: the recommended next step rendered as an ordinary
 * card, and the AI-authored note was indistinguishable from any other panel.
 *
 * Built from existing theme tokens only (no new custom properties) so all 17
 * theme blocks keep their contrast, and with logical properties throughout so
 * RTL is unaffected (60d). Meaning never rests on colour: the hero is also
 * first in document order and carries the page's only primary button, and the
 * coach note is identified by its heading, not by its tint.
 */
.hero-card {
  border-color: var(--primary);
  border-inline-start-width: 4px;
  box-shadow: var(--shadow);
}
.hero-card > h2 {
  font-size: 1.35rem;
}
.hero-card > p {
  font-size: 1.05rem;
  margin-block-end: 1rem;
}
.hero-card .button.primary {
  font-size: 1.05rem;
  padding-block: 0.6rem;
  padding-inline: 1.25rem;
}

.coach-teaser {
  background: var(--surface-muted);
  border-inline-start: 4px solid var(--border-strong);
}
.coach-teaser > h2 {
  font-size: 1.05rem;
}
/* The provenance glyph is decorative; the heading text ("AI Coach's notes")
   carries the fact, so a screen reader never depends on the mark. Tinted with
   the theme accent so it reads as a sparkle rather than punctuation, and kept
   at the heading's size so it stays a mark, not an icon button. */
.coach-teaser__mark {
  margin-inline-end: 0.35rem;
  color: var(--accent, var(--primary));
}

/* Exam facts as labelled facts (change 161, F7) — the vendor's guides present
   these as a definition list, and the student usually has that guide open in
   another tab. Logical properties only, so RTL is unaffected. */
.exam-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1.5rem;
  margin-block: 0.5rem 1rem;
}
.exam-facts__item {
  display: flex;
  flex-direction: column;
}
.exam-facts dt {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
}
.exam-facts dd {
  margin: 0;
  font-weight: 600;
}

/* ---------------------------------------------------------------------------
 * The round-result moment (change 166).
 *
 * The screen a practice round ends on. Deliberately quiet: the content is
 * the reward, so nothing here competes with the words. Built from existing
 * theme tokens with logical properties only (RTL-safe, 60d).
 */
.verified-banner {
  border-color: var(--success);
  border-inline-start-width: 4px;
}
.verified-banner > h2 {
  font-size: 1.1rem;
  margin-block-end: 0.5rem;
}
.plain-list {
  list-style: none;
  margin: 0 0 0.5rem;
  padding: 0;
}
.movement-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.6rem;
}
.movement {
  display: flex;
  gap: 0.6rem;
  align-items: baseline;
}
/* Colour is never the only encoding: each mark has an sr-only label beside
   it naming what kind of movement this is. */
.movement--missed .movement__mark { color: var(--warn); }
.movement--banked .movement__mark,
.movement--transfer .movement__mark,
.movement--practised .movement__mark { color: var(--success); }
.movement__body { flex: 1; }
.movement__detail { color: var(--muted); }
.movement__due { display: block; }
.next-session {
  margin-block: 1rem;
  font-size: 1.05rem;
}

/* ---------------------------------------------------------------------------
 * Campaign roster: attention first (change 170).
 *
 * The roster answers "who needs me?" before it lists everyone, so the flagged
 * students get a section of their own and the rest collapse. Theme tokens and
 * logical properties only.
 */
.programme-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1rem;
  margin-block: 0 1rem;
  color: var(--muted);
  font-size: 0.9rem;
}
.programme-strip__stage { white-space: nowrap; }
.roster-section-heading {
  font-size: 0.95rem;
  margin-block: 1rem 0.5rem;
}
.roster-all-clear {
  margin-block: 0 1rem;
  padding: 0.75rem 1rem;
  border-inline-start: 4px solid var(--success);
  background: var(--surface-muted);
  border-radius: var(--radius);
}
.roster-on-plan > summary {
  cursor: pointer;
  font-size: 0.95rem;
  font-weight: 600;
  margin-block: 1rem 0.5rem;
}
.page-actions-disclosure { margin-block-end: 1rem; }
.page-actions-disclosure > summary { cursor: pointer; }

/* The hero's three-slot grammar and the plan's ownership line (change 168). */
.hero-what { font-weight: 600; margin-block-end: 0.25rem; }
.hero-why { margin-block: 0 0.5rem; }
.hero-anchor { margin-block: 0 1rem; font-size: 0.95rem; }
.plan-ownership {
  margin-block: 0 1rem;
  padding-inline-start: 0.75rem;
  border-inline-start: 3px solid var(--border-strong);
}
.warm-start-note {
  margin-block: 0.25rem 0;
  color: var(--muted);
}

/* The multi-certification arrival strip (change 169). One row per family:
   name, stage word, next planned step. No percentages — a second readiness
   number would be a second definition of readiness. */
.family-strip { margin-block-end: 1rem; }
.family-strip ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.35rem; }
.family-strip__row a {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 0.75rem;
  align-items: baseline;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  text-decoration: none;
  color: inherit;
}
.family-strip__row a:hover { border-color: var(--border-strong); }
.family-strip__row--active a {
  border-color: var(--primary);
  border-inline-start-width: 4px;
}
.family-strip__title { font-weight: 600; flex: 1; min-inline-size: 12ch; }
.family-strip__stage { color: var(--muted); }

/* Dial band names and the target caption (change 167). The arcs were
   colour-only; these are the text that makes them mean something. Sized in
   SVG user units, so they scale with the dial's own em-based sizing. */
.readiness-dial__band-label {
  font-size: 9px;
  fill: var(--text);
  opacity: 0.75;
}
.readiness-dial__target-caption {
  font-size: 8px;
  fill: var(--muted);
}

/* Two classes emitted by changes 167/169 that had no rule — caught by the
   class-coverage guard (test_161_my_tutor_styles), which exists precisely
   because .hero-card and .coach-teaser shipped unstyled for months. */
.readiness-strapline {
  margin-block: 0 0.75rem;
  font-size: 0.95rem;
}
.family-strip__next { white-space: nowrap; }

/* The manager's helping loop (change 171). */
.ways-to-help { border-inline-start: 4px solid var(--primary); }
.ways-to-help .privacy-note { margin-block-end: 0; }
.attention-trigger {
  margin-block: 0 1rem;
  padding: 0.6rem 0.9rem;
  border-inline-start: 4px solid var(--warn);
  background: var(--surface-muted);
  border-radius: var(--radius);
}
.compose-trigger {
  margin-block: 0 1rem;
  padding: 0.6rem 0.9rem;
  background: var(--surface-muted);
  border-radius: var(--radius);
}
.stack { display: grid; gap: 0.5rem; }
.secondary-actions { margin-block-start: 1rem; }
.secondary-actions > summary { cursor: pointer; }
.support-case > summary { cursor: pointer; }

/* Trend cell and its stated definition (change 172). */
.trend-evidence { display: block; }
.trend-definition { margin-block: 0.5rem 0; }
.attention-signals { margin-block: 1rem; border: 1px solid var(--border); border-radius: var(--radius); padding: 0.75rem 1rem; }
.attention-signals .choice { display: block; margin-block: 0.25rem; }


/* ---------------------------------------------------------------------------
 * My Tutor restructure (change 163): the meter list and the utility row.
 */
.area-meters {
  list-style: none;
  margin: 0 0 0.75rem;
  padding: 0;
  display: grid;
  /* Change 184 UX polish: 16rem min forced 4 columns at ~1000px, orphaning the
     5th area on its own row (the "lonely Deployments tile" the student sees).
     14rem lets 5 fit on a typical desktop width and still reflows to 3/2/1 at
     smaller viewports — auto-fill keeps the reflow symmetrical. */
  grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
  gap: 0.75rem;
}
.topic-links { margin-block-end: 1.5rem; }
/* Settings, records and disclosures: reachable, not offered. A row of plain
   links reads as navigation; seven identical tiles read as seven offers. */
.utility-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1.25rem;
  margin-block: 1rem 0.75rem;
  font-size: 0.9rem;
}
.utility-row a {
  color: var(--muted);
  display: inline-flex;
  align-items: center;
}
.utility-row a:hover { color: var(--text); }

/* Change 163: on a phone the three study tiles stack full-width, so their
   grid gap and padding dominate. Tighten the stack — same content, less
   chrome. Desktop unchanged. */
@media (max-width: 640px) {
  .option-grid { gap: 0.5rem; }
  .option-grid__item { padding: 0.65rem 0.75rem; gap: 0.25rem; }
  .option-grid__item .muted.small { line-height: 1.35; }
}

/* Area-scoped queue entry (change 164). */
.queue-scope { margin-block: 0 1rem; }
.area-meter__learn { display: inline-block; margin-block-start: 0.15rem; }

/* ---------------------------------------------------------------------------
 * Subtopic list (change 184) — visual polish only.
 *
 * The partial was shipped without a CSS block: `<ul class="subtopic-list muted
 * small">` fell back to browser default `<ul>` (disc bullets + 1em block
 * margins), and combined with the muted-grey text it read like an unfinished
 * placeholder. Rules live here (not inside `_subtopic_list.html`) because the
 * partial is shared across queue.html / _area_meter.html / mastery_map.html /
 * exam_requirements.html and change 184 D4 explicitly says the styling has
 * to work in all four surfaces at once. Token-only (no new colours). Logical
 * properties throughout so the RTL suite stays green.
 */
.subtopic-list {
  list-style: none;
  margin: 0.15rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  line-height: 1.35;
}
.subtopic-list > li {
  position: relative;
  padding-inline-start: 0.9rem;
}
.subtopic-list > li::before {
  /* Tight dot the eye can group by, in a border token rather than the muted
     text colour — so the marker recedes without vanishing on dark themes. */
  content: "";
  position: absolute;
  inset-inline-start: 0.15rem;
  inset-block-start: 0.55em;
  inline-size: 0.32rem;
  block-size: 0.32rem;
  border-radius: 50%;
  background: var(--border-strong);
}
.subtopic-list__count {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  margin-inline-start: 0.15rem;
}
.subtopic-list__more { font-style: italic; }

/* The chosen distractor's rationale (change 176). */
.wrong-answer-moment .chosen-rationale { font-style: italic; margin-block: 0.25rem 0.5rem; }

/* ---------------------------------------------------------------------------
 * Shared pause / leave / cancel chrome (BUG-0058).
 *
 * These four classes were emitted by shipped templates from changes 183, 185
 * and 186 (placement pause modal, placement resume CTA, simulation resume
 * CTA and cancel modal) but had no CSS rules — a BUG-0026-class latent
 * defect that the change-161 guard tripped once change 186 re-emitted the
 * classes from `my_tutor_home.html`. Token-only palette, logical properties
 * only so the RTL suite stays green.
 */
.banner-row {
  /* Horizontal flow inside the `.alert` container so the copy, the resume
     link, and the Cancel-test trigger sit on one line. Wraps on narrow
     viewports rather than overflowing. */
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.85rem;
}
.banner-row > p { margin: 0; }
.button-primary {
  /* The modal's affirmative default ("Keep going"). Same visual weight as
     the platform `.button` so autofocus lands somewhere legible; explicit
     token palette here rather than composing with `.button` because the
     class is used stand-alone in placement_player.html. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  border: 1px solid transparent;
  border-radius: 0.6rem;
  padding: 0.48rem 0.85rem;
  background: var(--accent, var(--primary));
  color: var(--accent-contrast, #fff);
  font-weight: 700;
  font-size: 0.93rem;
  cursor: pointer;
  min-height: 2.3rem;
}
.button-primary:hover {
  background: var(--accent-hover, var(--primary-hover));
  color: var(--accent-contrast, #fff);
}
.button-primary[disabled],
.button-primary[aria-disabled="true"] { opacity: 0.55; cursor: not-allowed; }
/* Alpine.js `x-cloak` — the runtime removes this attribute on init, and
   Alpine itself doesn't ship a default rule for it. Without this rule
   an `x-cloak`-marked element renders visible for the paint gap before
   Alpine boots, and — more importantly for our tests — if Alpine ever
   fails to load, every `x-show="false"` element stays fully rendered
   because `x-show` only takes effect once Alpine evaluates it. The
   `!important` matches the pattern the Alpine docs recommend so it
   overrides more-specific layout rules like `.modal-overlay`'s own
   `display: grid` below. */
[x-cloak] { display: none !important; }
.modal-overlay {
  /* Dim layer that carries the `role="dialog"` and `aria-modal="true"`. The
     card centres above it; the overlay itself is a click surface that
     dismisses. */
  position: fixed;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 1rem;
  background: rgb(0 0 0 / 45%);
  z-index: 60;
}
.modal-card {
  /* The raised card. Uses --surface for theme awareness (light/dark share
     the class); a soft border rather than a heavy shadow so the modal
     reads as a document, not a system alert. */
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border-strong);
  border-radius: 0.8rem;
  padding: 1.1rem 1.15rem;
  max-inline-size: 32rem;
  inline-size: min(32rem, 100%);
  box-shadow: 0 10px 30px rgb(0 0 0 / 20%);
}
.modal-card > h2 { margin-block: 0 0.35rem; }
.modal-card > p { margin-block: 0 0.85rem; }
