/* ============================================================================
   Swabhyas — Design System  (Prism: navy ink, indigo accent, one hue per exam track)
   Single source of truth. Replaces the accreted v1.2–v1.8.4 stylesheet.
   Restyle-in-place: preserves the existing class contract used by templates,
   fixes the broken dark-theme leftovers, and adds tokens + accessibility.
   ============================================================================ */

:root{
  /* ---- palette (Prism) --------------------------------------------------
     Navy ink on a near-white ground, with one bright hue per exam track.
     Colour here encodes CATEGORY, not mood: a candidate learns "cyan is CFA"
     on the landing page and reads the same signal in the catalogue and the
     maps. The ground keeps a whisper of blue rather than pure white, which is
     easier across a three-hour sitting without going warm.

     ONE accent for actions, and semantic colours that never double as it — a
     score must never be mistaken for a brand colour. Every pair below was
     measured against its own background before it shipped. */
  --ground:#f6f7fb; --surface:#ffffff; --surface-2:#eef0f7; --surface-3:#e3e7f2;
  --ink:#101935; --ink-2:#2b3357; --muted:#4a5170; --faint:#5c6383;   /* faint clears AA on ground */
  --hairline:#a8afc7;                                                  /* borders/icons only, never text */
  --brand:#101935; --brand-2:#1b2547; --brand-3:#333d63; --on-brand:#ffffff;

  /* Indigo. Deliberately NOT one of the track hues below: if the action colour
     were also a category colour, a violet chip would mean two things at once.
     --accent-soft is a cool lavender, ~200 degrees of hue from --danger-soft,
     so a selected option and a wrong answer can never read as the same thing. */
  --accent:#2f3aa8; --accent-ink:#232c85; --accent-soft:#e7e9fa; --accent-line:#c3c8f0;
  --border:#dfe3ef; --border-strong:#c3c9dc;

  /* ---- semantic (AA on the ground and on white) ---- */
  --ok:#116a45; --ok-soft:#e4f2ea;
  --warn:#8a5a12; --warn-soft:#f7eedb;
  --danger:#b3261e; --danger-soft:#fbe9e7;
  --info:#0e5a80; --info-soft:#e2eef5;

  /* ---- global chrome ----------------------------------------------------
     Fixed, not per-theme. The topbar and footer are the brand, so they do not
     change with the reader's study-surface preference. */
  --nav-bg:#101935; --nav-fg:#eef1fa; --nav-dim:#a8b0cd;
  --nav-line:#333d63; --nav-acc:#8fa0ff;

  /* ---- exam tracks ------------------------------------------------------
     The signature of this palette. Each track owns a hue.

     TWO FORMS, and the distinction is not cosmetic. The bright value is for
     FILL — a chip background, a rule, a bar. It CANNOT carry text: the orange
     is 2.84:1 on white and the cyan 2.55:1, both far below the floor. Every
     -ink value clears 4.5:1 on both the ground and white, and is the only one
     that may be used for words. Using a fill hue as a colour is the mistake
     this pairing exists to prevent. */
  --track-uceed:#ff6b35;  --track-uceed-ink:#b23c08;
  --track-nift:#d41a80;   --track-nift-ink:#a8125f;
  --track-cfa:#00b2ca;    --track-cfa-ink:#0e7490;
  --track-ai:#6f52f7;     --track-ai-ink:#5b3ae0;
  --track-tech:#0f7b6c;   --track-tech-ink:#0b6357;

  /* Text that sits ON a bright track fill. White fails on the orange and the
     cyan, so those two take the navy instead — one blanket rule would have to
     sacrifice either the hues or the legibility. */
  --on-track-light:#101935;   /* for uceed, cfa   */
  --on-track-dark:#ffffff;    /* for nift, ai, tech */

  /* ---- categorical ramp -------------------------------------------------
     For encoding *category*, never state. These are the track inks, so a
     chart, a mind-map branch and an exam badge all speak the same language.
     Distinct in hue AND luminance, so they stay separable in greyscale and
     for the most common colour-vision deficiencies. */
  --cat-1:#b23c08; --cat-2:#0e7490; --cat-3:#a8125f; --cat-4:#5b3ae0;

  /* ---- effects ---- */
  --ring:0 0 0 3px rgba(47,58,168,.30);
  --shadow:0 1px 2px rgba(16,25,53,.06), 0 10px 28px rgba(16,25,53,.09);
  --shadow-sm:0 1px 2px rgba(16,25,53,.07);
  --radius:3px; --radius-sm:2px; --radius-pill:999px;   /* near-square: printed, not app-like */

  /* ---- spacing scale: use these, never arbitrary values ---- */
  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:24px; --s6:32px; --s7:48px; --s8:64px;

  /* ---- type ------------------------------------------------------------
     A serif display voice for headings and a workhorse sans for interface
     text. Both resolve from the system, so nothing is fetched at runtime -
     this matters for proctored and offline sittings. */
  --font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-display: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, ui-serif, serif;
  --font-mono: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;

  /* modular scale, ratio 1.25 */
  --t-display:2.75rem; --t-h1:2.05rem; --t-h2:1.5rem; --t-h3:1.15rem; --t-h4:1rem;
  --t-body:1rem; --t-meta:.875rem; --t-label:.72rem;

  --measure:70ch;          /* comfortable reading width for prose */
  --shell:1240px;          /* page container */
}


*{ box-sizing:border-box; }
/* the hidden attribute must always win over display-setting utility classes */
[hidden]{ display:none !important; }
html{ scroll-behavior:smooth; }
body{
  margin:0;
  font-family:var(--font-sans);
  color:var(--ink);
  line-height:1.5;
  -webkit-font-smoothing:antialiased;
  background:
    /* a whisper of the accent in the top corner; color-mix keeps it tied to the
       token rather than a hardcoded rgba that survives a palette change */
    radial-gradient(1100px 380px at 100% -5%,
                    color-mix(in srgb, var(--accent) 7%, transparent), transparent 60%),
    linear-gradient(180deg, color-mix(in srgb, var(--ground) 40%, #fff) 0%,
                var(--ground) 42%, var(--surface-3) 100%);
  min-height:100dvh;
  display:flex; flex-direction:column;      /* pins the footer on short pages */
}
h1,h2,h3,h4{ color:var(--brand); margin:0 0 var(--s3); text-wrap:balance; }
h1,h2{ font-family:var(--font-display); font-weight:600; letter-spacing:-.012em; line-height:1.14; }
h3,h4{ font-family:var(--font-sans); font-weight:650; letter-spacing:-.006em; line-height:1.28; }
h1{ font-size:var(--t-h1); } h2{ font-size:var(--t-h2); } h3{ font-size:var(--t-h3); } h4{ font-size:var(--t-h4); }
.display{ font-family:var(--font-display); font-size:var(--t-display); font-weight:600; line-height:1.08;
  letter-spacing:-.018em; color:var(--brand); margin:0 0 var(--s3); }
.lede{ font-size:1.06rem; color:var(--muted); max-width:var(--measure); }
.prose{ max-width:var(--measure); }
p{ margin:0 0 var(--s3); } p:last-child{ margin-bottom:0; }
a{ color:var(--accent-ink); text-decoration:none; }
a:hover{ text-decoration:underline; }
small{ font-size:.82em; }
::selection{ background:var(--accent-soft); }
:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; border-radius:6px; }
.tnum,table td,.metric,.price,.stat span,.timer{ font-variant-numeric:tabular-nums; }

/* skip link for keyboard users */
.skip-link{ position:absolute; left:-9999px; top:8px; z-index:100; background:var(--brand); color:var(--on-brand);
  padding:10px 16px; border-radius:var(--radius-sm); font-weight:600; }
.skip-link:focus{ left:16px; text-decoration:none; }

/* ---------------------------------------------------------------- top bar */
.topbar{
  position:sticky; top:0; z-index:40;
  display:flex; align-items:center; gap:var(--s4);
  padding:0 var(--s5);
  min-height:78px;          /* sized to the 52px mark plus breathing room */
  background:rgba(255,255,255,.92); backdrop-filter:blur(14px);
  border-bottom:1px solid var(--border);
}
.brand{ display:flex; align-items:center; gap:10px; color:var(--brand); text-decoration:none; flex:none; }
.brand:hover{ text-decoration:none; }
/* The mark is supplied artwork, used unmodified — no tint, no overlay, no
   generated tile behind it. object-fit guards against a non-square replacement. */
.brand-mark{ display:block; width:42px; height:42px; flex:none; border-radius:var(--radius-sm);
  object-fit:contain; }

/* The mark is the illustration only. The full lockup was tried here and failed
   twice over: its wordmark is dark navy and the topbar is navy, so the name
   vanished into the bar, and at 56px the whole composition was a smudge. The
   name and tagline are live text beside it instead - crisp at any size, and
   readable by a screen reader, which an image of words is not. */
.brand-mark{ width:52px; height:52px; }
@media (max-width:560px){ .brand-mark{ width:44px; height:44px; } }
.console-brand .brand-mark{ width:46px; height:46px; }
.brand-copy{ display:flex; flex-direction:column; line-height:1.05; }
.brand-copy strong{ font-family:var(--font-display); font-size:1.4rem;
  font-weight:650; letter-spacing:-.008em; }
.brand-copy small{ text-transform:uppercase; letter-spacing:.15em; color:var(--muted); font-weight:700;
  font-size:.57rem; margin-top:2px; }
/* The student shell carries a tagline rather than a category label. Letterspaced
   caps suit "ADMIN"; they make a sentence sprawl — 28 characters of tracking put
   the sub-label at 3.2x the width of the wordmark above it — and they turn the
   full stops into noise at 9px. Sentence case, no tracking. */
.brand-copy small.brand-tagline{ text-transform:none; letter-spacing:0; font-weight:800;
  font-size:.74rem; margin-top:4px; }

/* primary navigation: a small, stable set of destinations */
.nav-links{ display:flex; gap:var(--s1); align-items:center; margin-left:auto; }
/* `font:inherit` FIRST, then size/weight. The shorthand resets both, so listing
   it after font-weight/font-size silently undoes them (same order as .btn). */
.nav-links a,.linkbutton,.nav-trigger{ color:var(--ink-2); text-decoration:none;
  background:none; border:0; font:inherit; font-weight:600; font-size:.9rem;
  cursor:pointer; padding:8px 12px; border-radius:var(--radius-sm); white-space:nowrap; }
.nav-links a:hover,.linkbutton:hover,.nav-trigger:hover{ color:var(--brand); background:var(--surface-2); text-decoration:none; }
.nav-links a[aria-current="page"]{ color:var(--brand); background:var(--surface-2); box-shadow:inset 0 -2px 0 var(--accent); }

/* grouped menus (Manage, account) */
.nav-menu{ position:relative; }
.nav-menu > summary{ display:inline-flex; align-items:center; gap:6px; cursor:pointer;
  list-style:none; user-select:none; }
/* the default disclosure triangle would sit beside our own chevron */
.nav-menu > summary::-webkit-details-marker{ display:none; }
.nav-menu > summary::marker{ content:""; }
.nav-menu > summary::after{ content:""; width:6px; height:6px; border-right:1.5px solid currentColor;
  border-bottom:1.5px solid currentColor; transform:rotate(45deg) translate(-1px,-1px); opacity:.7; }
.nav-menu[open] > summary{ color:var(--brand); background:var(--surface-2); }
.nav-panel{ position:absolute; right:0; top:calc(100% + 6px); min-width:216px; z-index:50;
  background:var(--surface); border:1px solid var(--border); border-radius:var(--radius);
  box-shadow:var(--shadow); padding:var(--s2); display:flex; flex-direction:column; gap:2px; }
.nav-panel p{ margin:var(--s2) var(--s2) var(--s1); font-size:var(--t-label); text-transform:uppercase;
  letter-spacing:.12em; color:var(--muted); font-weight:700; }
.nav-panel a,.nav-panel .linkbutton{ display:block; padding:8px 10px; border-radius:var(--radius-sm);
  font-size:.9rem; font-weight:600; color:var(--ink-2); text-align:left; width:100%; }
.nav-panel a:hover{ background:var(--surface-2); color:var(--brand); }
.nav-panel hr{ border:0; border-top:1px solid var(--border); margin:var(--s2) 0; }

/* mobile disclosure */
.nav-toggle{ display:none; margin-left:auto; align-items:center; gap:8px; font-weight:650; font-size:.9rem;
  color:var(--ink-2); background:none; border:1px solid var(--border); border-radius:var(--radius-sm);
  padding:7px 12px; cursor:pointer; }
.nav-toggle:hover{ background:var(--surface-2); color:var(--brand); }
@media (max-width:1080px){
  .nav-toggle{ display:inline-flex; }
  .nav-links{ display:none; position:absolute; left:0; right:0; top:100%; margin:0;
    flex-direction:column; align-items:stretch; gap:2px; padding:var(--s3);
    background:var(--surface); border-bottom:1px solid var(--border); box-shadow:var(--shadow); }
  .nav-links[data-open="1"]{ display:flex; }
  .nav-links a,.linkbutton,.nav-trigger{ padding:11px 12px; }
  .nav-menu{ position:static; }
  .nav-panel{ position:static; box-shadow:none; border:0; padding:0 0 0 var(--s3); }
}

/* ---------------------------------------------------------------- layout */
.container{ width:100%; max-width:var(--shell); margin:0 auto; padding:var(--s6) var(--s5); flex:1 0 auto; }
/* .page-head is defined once, in the component layer below. */
.eyebrow{ text-transform:uppercase; letter-spacing:.14em; color:var(--accent-ink); font-weight:700; font-size:.72rem; margin:0 0 var(--s2); }
.lead{ font-size:1.08rem; color:var(--muted); max-width:70ch; }
.muted{ color:var(--muted); }
.inline{ display:inline; }
.actions{ display:flex; gap:var(--s3); flex-wrap:wrap; }

.grid{ display:grid; gap:var(--s5); }
.grid.two{ grid-template-columns:repeat(2,minmax(0,1fr)); }
.grid.three{ grid-template-columns:repeat(3,minmax(0,1fr)); }
.grid.four{ grid-template-columns:repeat(4,minmax(0,1fr)); }
.grid.six{ grid-template-columns:repeat(6,minmax(0,1fr)); }
.three-col{ display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:var(--s4); }
.stack{ display:grid; gap:var(--s4); } .stack.compact{ gap:var(--s2); }

/* hero (marketing) */
.hero{ display:grid; grid-template-columns:1.4fr .9fr; gap:var(--s6); align-items:center; padding:40px 0; }
.executive-hero h1{ font-size:3rem; line-height:1.04; letter-spacing:-.03em; margin-bottom:var(--s4); }

/* ---------------------------------------------------------------- cards */
.card{ background:var(--surface); border:1px solid var(--border); border-radius:var(--radius);
  padding:var(--s5); margin-bottom:var(--s5); box-shadow:var(--shadow-sm); }
.card:last-child{ margin-bottom:0; }
/* cards that are grid items must not carry a bottom margin, or the last
   card in a row stretches taller than its siblings (uneven card bottoms) */
.grid > .card,.three-col > .card{ margin-bottom:0; }
.executive-card{ border-top:3px solid var(--accent); box-shadow:var(--shadow); }
.soft-card{ background:var(--surface); border:1px solid var(--border); border-radius:var(--radius); padding:var(--s5); margin:var(--s4) 0; }
.metrics-card hr{ border:0; border-top:1px solid var(--border); margin:var(--s4) 0; }
.metrics-row{ display:flex; gap:var(--s5); align-items:center; justify-content:space-between; flex-wrap:wrap; }

/* ---------------------------------------------------------------- buttons */
.btn{ display:inline-flex; align-items:center; justify-content:center; gap:8px; font:inherit; font-weight:600; font-size:.95rem;
  border:1px solid transparent; border-radius:var(--radius-sm); padding:10px 18px; cursor:pointer;
  background:linear-gradient(140deg,var(--brand),var(--brand-2)); color:var(--on-brand); text-decoration:none;
  box-shadow:0 8px 18px rgba(11,31,58,.16); transition:transform .12s ease, box-shadow .12s ease; }
.btn:hover{ transform:translateY(-1px); box-shadow:0 12px 22px rgba(11,31,58,.2); text-decoration:none; }
.btn.secondary{ background:var(--surface); color:var(--brand); border-color:var(--border-strong); box-shadow:none; }
.btn.secondary:hover{ background:var(--surface-2); }
.btn.danger{ background:linear-gradient(140deg,var(--danger),
    color-mix(in srgb, var(--danger) 72%, #000)); color:var(--on-brand); }
.btn.small,.btn .small{ padding:7px 12px; font-size:.86rem; }
.small-btn{ font:inherit; font-weight:600; font-size:.85rem; border:1px solid var(--border-strong);
  background:var(--surface); color:var(--brand); border-radius:8px; padding:6px 11px; cursor:pointer; }
.small-btn:hover{ background:var(--surface-2); }
.muted-btn{ font:inherit; font-weight:600; background:var(--surface-2); border:1px solid var(--border); color:var(--ink-2);
  border-radius:8px; padding:7px 12px; cursor:pointer; }
.muted-btn:hover{ background:var(--surface-3); }

/* ---------------------------------------------------------------- forms */
.form{ display:grid; gap:var(--s4); }
.form label,.stacked label{ font-weight:600; color:var(--ink-2); font-size:.92rem; display:grid; gap:6px; }
.form input,.form textarea,.form select,select,textarea,input:not([type]),input[type=text],input[type=email],input[type=password],input[type=number]{
  width:100%; font:inherit; font-size:.95rem; line-height:1.4; padding:11px 13px; color:var(--ink);
  border:1px solid var(--border-strong); border-radius:var(--radius-sm); background:var(--surface); outline:none;
  /* Stated rather than inherited from content: a select's intrinsic height comes
     from its option metrics, not its line-height, so it still landed a pixel
     short of the inputs beside it. One box for every field, in em so it follows
     the font size wherever the control is used. */
  box-sizing:border-box; min-height:calc(1.4em + 24px); }
.form input:focus,.form textarea:focus,.form select:focus,select:focus,textarea:focus,input:focus{
  border-color:var(--accent); box-shadow:var(--ring); }
/* A native select keeps its own metrics: same padding and font as the inputs
   beside it still rendered 13px taller, so any row mixing a select with text
   fields sat visibly out of line - most obviously "Role" next to Full name and
   Email on the create-user form. Dropping the platform appearance and stating
   the line-height puts them on the same baseline, and the chevron has to be
   drawn back in because appearance:none removes it. */
.form select,select{
  appearance:none; -webkit-appearance:none; -moz-appearance:none;
  padding-right:38px;
  background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%23667085' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right 14px center; }
.form select::-ms-expand,select::-ms-expand{ display:none; }
/* Multi-row selects are lists, not dropdowns - no chevron, no reserved gutter. */
.form select[multiple],select[multiple],.form select[size],select[size]{
  background-image:none; padding-right:13px; }
.form select:disabled,select:disabled{ color:var(--muted); background-color:var(--surface-2); }
/* A checkbox is not a text field. The rule above matches `.form input` and bare
   `input[type=text|email|password|number]`, and the first of those catches
   checkboxes and radios too - so every tick box inside a form was being given
   width:100%, 11px of padding and a text-field border. The native control then
   sat in the middle of a wide bordered rectangle with its label pushed outside,
   which is what broke the practice topic chips and fattened the boxes on the
   admin and registration forms. Reset them to their own size everywhere. */
.form input[type=checkbox],.form input[type=radio],
input[type=checkbox],input[type=radio]{
  width:auto; padding:0; border:0; background:none; box-shadow:none;
  border-radius:0;
  /* The shared field rule states a min-height so a select cannot come out
     shorter than the inputs beside it. A tick box is not a field: left in, it
     inherited a 45px box and pushed every check-card open by 32px. */
  min-height:0; background-image:none;
  /* justify-self, because `.form label` is display:grid and a grid item with
     width:auto still stretches to its column - a bare tick box came out 945px
     wide, so the whole row was a click target. No effect outside a grid. */
  justify-self:start; }
.form input[type=checkbox]:focus,.form input[type=radio]:focus,
input[type=checkbox]:focus,input[type=radio]:focus{
  box-shadow:none; outline:2px solid var(--accent); outline-offset:2px; }
.two-col-form{ display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:var(--s4); }
.two-col-form .span-2{ grid-column:1 / -1; }
.stacked{ display:flex; flex-direction:column; gap:var(--s4); }
.toolbar-form{ display:flex; gap:var(--s3); align-items:center; flex-wrap:wrap; margin-bottom:var(--s4); }
.toolbar-form select,.toolbar-form input{ min-width:160px; width:auto; }
.mini-form{ display:flex; gap:var(--s2); align-items:center; flex-wrap:wrap; margin:var(--s2) 0;
  padding:var(--s2) 0; border-top:1px solid var(--border); }
.mini-form input,.mini-form select{ max-width:180px; width:auto; }
/* Compact controls for the inline forms in admin tables and toolbars. The shared
   field box is sized for a form you fill in; these sit in a table cell next to a
   .small-btn at 34px, so at 45px they both mismatched the button and pushed the
   row to wrap - a Manage panel came out 585px tall in a 286px cell. */
.mini-form input,.mini-form select,.mini-form textarea,
.toolbar-form input,.toolbar-form select{
  min-height:34px; padding:6px 10px; font-size:.88rem; }
.mini-form select,.toolbar-form select{ padding-right:28px; background-position:right 9px center; }
/* These rows live in a table cell around 280px wide. flex-wrap moves an item to
   the next line BEFORE it shrinks it, so a 180px select and its button - 286px
   together - wrapped every time however shrinkable they were; the width has to
   be small enough to fit in the first place. A control that runs out of room
   still shrinks rather than wrapping, which is what min-width:0 permits. */
.mini-form input,.mini-form select{ flex:1 1 auto; min-width:0; }
.mini-form select{ max-width:120px; }
.mini-form input[type=number]{ max-width:88px; }
/* A free-text box in one of these rows keeps its neighbour company rather than
   taking a line of its own: 88 + 180 did not fit 247, so the amount, its reason
   and the button each ended up on a line. */
.mini-form input[type=text],.mini-form input:not([type]){ max-width:120px; }
/* .muted-btn re-declares font:inherit after .small-btn has set .85rem, so a
   Revoke button came out two pixels taller than the row it shares. */
.mini-form .small-btn,.mini-form button{ flex:0 0 auto; min-height:34px; padding:6px 11px;
  font-size:.85rem; line-height:1.2; }
.chips{ display:flex; gap:var(--s4); align-items:center; flex-wrap:wrap; }
.chips label{ display:inline-flex; align-items:center; gap:6px; font-weight:600; }
/* ---- admin forms -----------------------------------------------------------
   One label position, grouped option sets, and a primary action at a button's
   size. These are shared rather than page-local: an admin screen is a screen a
   client may be shown, and every one of them should look like the same product.
*/
.form-row{ display:grid; gap:var(--s4); }
.form-row.two{ grid-template-columns:repeat(2,minmax(0,1fr)); }
.form-row.three{ grid-template-columns:repeat(3,minmax(0,1fr)); }
@media (max-width:980px){ .form-row.two,.form-row.three{ grid-template-columns:1fr; } }

.opt-set{ border:1px solid var(--border); border-radius:var(--radius-sm);
  padding:var(--s4); margin:0; background:var(--surface-2); }
.opt-set legend{ padding:0 var(--s2); font-weight:700; font-size:.95rem; color:var(--ink); }
.opt-note{ margin:0 0 var(--s3); font-size:.86rem; color:var(--muted); }
.opt-heading{ margin:var(--s4) 0 var(--s2); font-size:.78rem; font-weight:650;
  letter-spacing:.08em; text-transform:uppercase; color:var(--ink-2); }
.opt-heading:first-of-type{ margin-top:0; }

.form-actions{ display:flex; flex-wrap:wrap; gap:var(--s4); align-items:center;
  justify-content:space-between; border-top:1px solid var(--border);
  padding-top:var(--s4); }
.form-actions p{ margin:0; max-width:60ch; }
.form-actions .btn{ flex:none; }

/* Numeric columns line up on the decimal, which is what makes a table scannable. */
table td.num,table th.num{ text-align:right; font-variant-numeric:tabular-nums; }
/* The Actions column is sized by its content, so narrowing these controls
   narrowed the column with them and the rows wrapped anyway - the layout was
   chasing itself. A floor on the panel gives the column something to hold to;
   the table already scrolls inside .tablewrap, so asking for the width is safe. */
.row-actions{ min-width:300px; }
.row-actions summary{ cursor:pointer; font-weight:600; font-size:.88rem; color:var(--brand); }
.row-actions[open] summary{ margin-bottom:var(--s2); }

.check-grid,.question-picker{ display:grid; gap:var(--s3); }
.check-grid.cols-2{ grid-template-columns:repeat(2,minmax(0,1fr)); }
.check-grid.cols-3{ grid-template-columns:repeat(3,minmax(0,1fr)); }
/* equal rows, so a card with a long description does not tower over its neighbours */
.check-grid.cols-2,.check-grid.cols-3{ grid-auto-rows:1fr; }
@media (max-width:980px){ .check-grid.cols-3{ grid-template-columns:repeat(2,minmax(0,1fr)); } }
@media (max-width:640px){ .check-grid.cols-2,.check-grid.cols-3{ grid-template-columns:1fr; } }
/* Scoped through .check-grid so this is (0,2,0) and actually beats
   `.form label{display:grid}` at (0,1,1). Unscoped it lost, so every card in
   every form was a two-row grid: tick box on one row, title on the next, with
   the gap between them growing with the card. The row layout below only ever
   applied outside a .form. */
.check-grid .check-card,.question-picker .check-card,.check-card{
  display:flex; align-items:flex-start; gap:10px; border:1px solid var(--border);
  border-radius:var(--radius-sm); padding:var(--s3); background:var(--surface); cursor:pointer;
  transition:border-color .15s ease, background .15s ease; }
/* These cards sit on .opt-set, which is --surface-2. Both states resolved to
   that same colour - hover explicitly, and checked because --brand-soft is not
   a token that exists, so the fallback took over - which left a selected card
   filled with its own background and told apart only by a border. --accent-soft
   is the token the palette defines for a chosen option, and it is kept a long
   way from --danger-soft on purpose so a selection can never read as an error. */
.check-card:hover{ border-color:var(--accent-line); background:var(--surface);
  box-shadow:0 1px 3px rgba(16,25,53,.08); }
.check-card:has(input:checked){ border-color:var(--accent); background:var(--accent-soft); }
.check-card:has(input:checked) .check-card-name{ color:var(--accent-ink); }
.check-card:focus-within{ outline:2px solid var(--accent); outline-offset:2px; }
.check-card input{ margin:2px 0 0; flex:none; accent-color:var(--brand); }
.check-card .check-card-body{ display:block; min-width:0; }
.check-card .check-card-name{ display:block; font-weight:650; font-size:.9rem; line-height:1.35;
  color:var(--ink); }
.check-card span{ font-weight:600; margin-left:6px; }
.check-card small{ display:block; color:var(--muted); margin:4px 0 0; font-size:.78rem; line-height:1.45; }
/* What a bundle covers is a list, and it is the reason to tick the bundle
   rather than the exams under it - so it is labelled rather than left as a
   run-on of names under the title. */
.check-card .covers{ display:block; margin:4px 0 0; font-size:.74rem; line-height:1.45;
  color:var(--muted); }
.check-card .covers b{ display:block; font-size:.66rem; font-weight:700;
  text-transform:uppercase; letter-spacing:.07em; color:var(--faint); margin-bottom:1px; }
.check-line,.check{ display:flex; align-items:center; gap:8px; }

/* auth pages */
.narrow{ max-width:500px; margin:44px auto; }
.auth-shell{ display:flex; justify-content:center; }
.auth-card{ max-width:520px; width:100%; }
/* ---- sign-in, the Prism way -------------------------------------------------
   The card itself is the navy panel and the form sits ON it; the earlier build
   had this inverted (white card, navy aside). Every value below was measured
   against the navy rather than picked to look right:
     eyebrow  --track-cfa  6.78:1     heading --on-brand  17.32:1
     labels   --nav-dim    8.05:1     input text --nav-fg 12.71:1 (on the 7% fill)
   The top rule is the four bright track hues - pure fill, carrying no text,
   which is the one job those hues are safe for. */
.login-card{ background:var(--brand); border-color:var(--brand-3);
  box-shadow:0 18px 44px rgba(16,25,53,.28); border-top:0;
  position:relative; overflow:hidden; }
.login-card::before{ content:""; position:absolute; inset:0 0 auto; height:3px;
  background:linear-gradient(90deg, var(--track-uceed), var(--track-nift),
                             var(--track-cfa), var(--track-ai)); }
.login-card .eyebrow{ color:var(--track-cfa); }
.login-card h1{ color:var(--on-brand); }
.login-card .muted{ color:var(--nav-dim); }
.login-card a{ color:var(--track-cfa); }
.login-card .field-label{ color:var(--nav-dim); }
/* --danger is 2.65:1 on the navy - the required mark has to lighten to stay visible */
.login-card .field-label .req{ color:#ff8080; }
.login-card .form input{ background:rgba(255,255,255,.07); border-color:var(--brand-3);
  color:var(--nav-fg); }
.login-card .form input::placeholder{ color:var(--nav-dim); opacity:1; }
.login-card .form input:focus{ border-color:var(--track-cfa);
  box-shadow:0 0 0 3px rgba(0,178,202,.30); }
/* Chrome paints its own near-white fill on autofilled inputs, which would drop
   dark text onto the navy card. The inset shadow is the only way to repaint it. */
.login-card .form input:-webkit-autofill,
.login-card .form input:-webkit-autofill:hover,
.login-card .form input:-webkit-autofill:focus{
  -webkit-text-fill-color:var(--nav-fg); caret-color:var(--nav-fg);
  -webkit-box-shadow:0 0 0 1000px #212943 inset; }
/* The prism sweep, darkened from the concept art. The bright stops cannot carry
   white text - #ff6b35 measures 2.84:1 - so each is taken to a depth that clears
   AA: 5.18, 6.04 and 6.72:1 left to right. */
.login-card .form .btn{ width:100%; border:0; color:#ffffff;
  background:linear-gradient(90deg,#c2410c 0%,#be185d 52%,#5b3ae0 100%);
  box-shadow:0 10px 22px rgba(91,58,224,.30); }
.login-card .form .btn:hover{ box-shadow:0 14px 26px rgba(91,58,224,.38); }
/* The exam cards had no dark-panel treatment, so they fell back to the global
   light --surface and sat on the navy card as white boxes. Laid out as a row -
   control first, then the label - rather than an inline checkbox with margins
   guessing where the text starts. */
/* grid-auto-rows:1fr, or the row holding the long exam title is taller than
   the row below it and the four cards read as two different sizes. */
.login-card .check-grid-2{ grid-template-columns:repeat(2,minmax(0,1fr));
  grid-auto-rows:1fr; gap:10px; }
.login-card .check-card{ display:flex; align-items:flex-start; gap:10px;
  background:rgba(255,255,255,.06); border-color:var(--brand-3); padding:13px 14px;
  cursor:pointer; transition:border-color .15s ease, background .15s ease; }
.login-card .check-card:hover{ background:rgba(255,255,255,.10);
  border-color:var(--nav-dim); }
.login-card .check-card:has(input:checked){ border-color:var(--track-cfa);
  background:rgba(0,178,202,.12); }
.login-card .check-card:focus-within{ outline:2px solid var(--track-cfa);
  outline-offset:2px; }
.login-card .check-card input{ margin:2px 0 0; accent-color:var(--track-cfa);
  width:16px; height:16px; flex:none; }
.login-card .check-card .check-card-body{ display:block; margin:0; min-width:0; }
.login-card .check-card .check-card-name{ display:block; margin:0; font-weight:600;
  font-size:.9rem; line-height:1.35; color:var(--nav-fg); }
.login-card .check-card small{ display:block; margin:3px 0 0;
  font-family:var(--font-mono, ui-monospace, monospace); font-size:.72rem;
  letter-spacing:.06em; color:var(--nav-dim); }
.login-card .optional{ font-weight:500; font-size:.76rem; color:var(--nav-dim);
  text-transform:none; letter-spacing:0; margin-left:6px; }
@media (max-width:560px){
  .login-card .check-grid-2{ grid-template-columns:minmax(0,1fr); }
}
/* Trial papers. Each is a choice, so each gets the panel's own full-width CTA
   rather than a small button floating in a card - the page exists to be acted
   on, and the only question is which paper. */
.login-card .trial-paper{ margin-top:var(--s4); padding:var(--s4);
  border:1px solid var(--brand-3); border-radius:var(--radius-sm);
  background:rgba(255,255,255,.06); }
.login-card .trial-title{ margin:0; font-size:1.02rem; font-weight:650; line-height:1.35;
  color:var(--nav-fg); font-family:inherit; letter-spacing:0; }
.login-card .trial-meta{ display:flex; flex-wrap:wrap; gap:var(--s3);
  margin:6px 0 var(--s4); font-size:.84rem; color:var(--nav-dim); }
.login-card .trial-meta span + span::before{ content:"·"; margin-right:var(--s3); }
.login-card .trial-note{ margin-top:var(--s5); font-size:.86rem; }
.login-card .demo-panel{ background:rgba(255,255,255,.06); border-color:var(--brand-3);
  color:var(--nav-dim); }
.login-card .demo-panel strong{ color:var(--nav-fg); }
.demo-panel{ margin-top:var(--s4); padding:var(--s3) var(--s4); border-radius:var(--radius-sm);
  background:var(--surface-2); border:1px solid var(--border); font-size:.9rem; }
.demo-panel p{ margin:2px 0; }

/* ---------------------------------------------------------------- tables */
table,.table{ width:100%; border-collapse:collapse; background:var(--surface); font-size:.92rem;
  border-radius:var(--radius-sm); overflow:hidden; }
th,td{ text-align:left; padding:11px 12px; border-bottom:1px solid var(--border); vertical-align:top; color:var(--ink-2); }
th{ font-size:.72rem; text-transform:uppercase; letter-spacing:.07em; color:var(--muted); background:var(--surface-2); font-weight:700; }
tbody tr:last-child td{ border-bottom:0; }
tbody tr:hover{ background:var(--surface-2); }

.list-row{ display:flex; justify-content:space-between; gap:var(--s4); align-items:center;
  border-top:1px solid var(--border); padding:var(--s3) 0; }
.list-row:first-of-type{ border-top:0; }
/* A list item is a title and a line of meta, not a title with a <br> and a run
   of spans - so the meta gets its own row and can carry chips without the
   baseline wandering. */
.list-main{ min-width:0; }

/* ---- topic chips (practice studio) -----------------------------------------
   A checkbox and its label on ONE line. These lived in a <style> block on the
   page at `.topic-chip` (0,1,0), which loses to `.form label{display:grid}`
   (0,1,1) in styles.css - so inside the practice form every chip stacked its
   box above its text and the whole picker read as a rendering fault. Scoped
   through .topic-chips to win on class count, and shared so the next page that
   wants a chip picker does not repeat the same discovery.
*/
/* align-items:flex-start, or the container stretches every chip to the height
   of the tallest - one long topic name wraps to two lines and drags the whole
   row to 67px. */
.topic-chips{ display:flex; flex-wrap:wrap; align-items:flex-start; gap:8px;
  margin:10px 0 4px; }
.topic-chips .topic-chip{ display:inline-flex; align-items:center; gap:8px;
  /* flex:none and nowrap: a chip is a label, and a flex item shrinks by default -
     without these the longest topic name is squeezed onto three lines inside its
     own pill and drags the whole row taller. */
  flex:none; white-space:nowrap;
  padding:7px 14px; border:1px solid var(--border); border-radius:var(--radius-pill);
  background:var(--surface); color:var(--ink-2);
  font-size:.85rem; font-weight:500; line-height:1.25; cursor:pointer; user-select:none;
  transition:border-color .15s ease, background .15s ease, color .15s ease; }
.topic-chips .topic-chip:hover{ border-color:var(--brand); background:var(--surface-2); }
.topic-chips .topic-chip:has(input:checked){ border-color:var(--brand);
  background:var(--surface-2); color:var(--ink); font-weight:650; }
.topic-chips .topic-chip:focus-within{ outline:2px solid var(--brand); outline-offset:2px; }
.topic-chips .topic-chip input{ margin:0; flex:none; accent-color:var(--brand); }
.topic-chips .topic-chip .n{ color:var(--muted); font-weight:500;
  font-variant-numeric:tabular-nums; }

/* The summary follows the form: the topic picker runs to two dozen chips, and a
   count you cannot see while choosing is not a summary. */
.grid.two > .practice-preview{ align-self:start; position:sticky; top:var(--s4); }
.practice-preview .preview-actions{ margin-top:var(--s5); }
.practice-preview .preview-actions .btn{ width:100%; }
.list-main strong{ display:block; line-height:1.35; }
.list-meta{ display:flex; align-items:center; flex-wrap:wrap; gap:8px;
  margin:5px 0 0; font-size:.84rem; }
.list-meta .pill{ font-size:.72rem; }
.lock{ color:var(--danger); font-weight:700; }

/* stats + metrics */
/* .stat is defined once, in the component layer below. */
.small-stat b,.small-stat > span:first-child{ font-size:1.5rem; }
.metric{ display:block; font-size:3rem; font-weight:800; color:var(--brand); letter-spacing:-.04em; }
.price{ font-size:2.3rem; font-weight:800; color:var(--brand); letter-spacing:-.04em; }
.invoice{ max-width:800px; margin:0 auto; }

/* ---------------------------------------------------------------- badges + pills */
.badge{ display:inline-flex; align-items:center; gap:6px; background:var(--accent-soft); color:var(--accent-ink);
  border:1px solid var(--accent-line); font-weight:700; border-radius:var(--radius-pill);
  padding:4px 11px; font-size:.72rem; text-transform:uppercase; letter-spacing:.05em; }
.pill,.pill-row .pill{ display:inline-flex; align-items:center; gap:6px; border-radius:var(--radius-pill);
  padding:3px 11px; font-size:.76rem; font-weight:700; text-transform:uppercase; letter-spacing:.04em;
  background:var(--surface-2); color:var(--ink-2); border:1px solid var(--border); }
.pill-row{ display:flex; gap:var(--s2); flex-wrap:wrap; }
/* semantic status variants (fixed from unreadable dark-theme colours) */
.pill.ok,.pill.configured,.pill.paid,.pill.active,.pill.completed,.pill.resolved{ background:var(--ok-soft); color:var(--ok); border-color:transparent; }
/* A form used as a row action sits inline with the buttons beside it: the
   default block margin would push it onto its own line and break the row. */
.inline-form{ margin:0; flex:0 0 auto; display:inline-flex; }
.pill.pending,.pill.assigned,.pill.in_review,.pill.warning,.pill.open{ background:var(--warn-soft); color:var(--warn); border-color:transparent; }
.pill.failed,.pill.cancelled,.pill.misconfigured,.pill.revoked,.pill.suspended{ background:var(--danger-soft); color:var(--danger); border-color:transparent; }
.pill.partial{ background:var(--info-soft); color:var(--info); border-color:transparent; }
/* attempt-history breakdown chips (correct / partial / incorrect / unattempted) */
/* Timeline tables: a date should occupy one line, not four. */
.nowrap{ white-space:nowrap; }
table.timeline td{ vertical-align:middle; }
table.timeline tbody tr:hover{ background:var(--surface-2); }
table.timeline td strong{ font-weight:650; }

.hb{ display:inline-flex; gap:8px; flex-wrap:wrap; font-size:.8rem; font-weight:700; }
.hb-c{ color:var(--ok); } .hb-p{ color:var(--info); } .hb-i{ color:var(--danger); } .hb-u{ color:var(--ink-2); font-weight:600; }

/* tabs */
.tabs{ display:flex; gap:var(--s2); margin:var(--s2) 0 var(--s5); flex-wrap:wrap; }
.tab{ padding:8px 15px; border:1px solid var(--border-strong); border-radius:var(--radius-pill);
  text-decoration:none; color:var(--ink-2); font-weight:600; background:var(--surface); font-size:.9rem; }
.tab:hover{ text-decoration:none; background:var(--surface-2); }
.tab.active{ background:var(--brand); color:var(--on-brand); border-color:var(--brand); }

/* ============================================================================
   Exam runtime — focused, distraction-free layout with question navigator
   ============================================================================ */
.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; }
.exam-page{ margin:0; background:var(--ground); min-height:100vh; }
.exam-top{ position:sticky; top:0; z-index:20; display:flex; align-items:center; justify-content:space-between; gap:16px;
  padding:12px 24px; background:linear-gradient(140deg,var(--brand),var(--brand-2)); color:var(--on-brand); }
.exam-ident{ display:flex; align-items:center; gap:12px; min-width:0; }
.exam-brand{ display:flex; align-items:center; gap:9px; font-weight:750;
  font-size:.95rem; letter-spacing:-.01em;
  color:var(--on-brand); white-space:nowrap; padding-right:12px;
  border-right:1px solid rgba(255,255,255,.22); }
.exam-brand img{ display:block; width:26px; height:26px; object-fit:contain; flex:none; }
/* Closes the page without offering a way off it - see the note in attempt.html.
   The --nav-* tokens, so this is the same brand chrome as the site footer: they
   are :root-level and fixed rather than per-theme, which matters here because
   the exam runtime is a standalone document with no data-theme on <html>, so
   the themed `:root[data-theme] .footer` rule never reaches it. Styling it with
   --surface instead left a light bar under a navy page. */
.exam-foot{ position:sticky; bottom:0; z-index:20;
  display:flex; flex-wrap:wrap; justify-content:space-between; gap:8px 24px;
  margin-top:var(--s6); padding:16px 24px;
  background:var(--nav-bg); border-top:1px solid var(--nav-line);
  font-size:.78rem; color:var(--nav-dim); }
@media (max-width:760px){ .exam-foot{ padding:14px 16px; } }
.exam-ident strong{ display:block; font-size:1rem; color:var(--on-brand); line-height:1.2; }
.exam-ident small{ color:rgba(255,255,255,.62); font-size:.8rem; }
.timer{ display:flex; flex-direction:column; align-items:flex-end; line-height:1.05;
  background:rgba(255,255,255,.1); border:1px solid rgba(255,255,255,.18); padding:7px 16px; border-radius:var(--radius-sm); }
.timer small{ font-size:.58rem; text-transform:uppercase; letter-spacing:.12em; color:rgba(255,255,255,.65); }
.timer b{ font-size:1.5rem; font-weight:750; font-variant-numeric:tabular-nums; letter-spacing:.02em; }
.timer.warn{ background:rgba(180,35,24,.3); border-color:rgba(255,180,170,.5); }
.timer.warn b{ color:color-mix(in srgb, var(--danger-soft) 88%, #fff); }

.exam-layout{ display:grid; grid-template-columns:minmax(0,1fr) 260px; gap:var(--s5);
  max-width:1180px; margin:0 auto; padding:var(--s5); align-items:start; }
.exam-main{ display:flex; flex-direction:column; gap:var(--s5); }
.exam-section{ display:flex; flex-direction:column; gap:var(--s4); }
.exam-section-head{ font-size:.9rem; text-transform:uppercase; letter-spacing:.08em; color:var(--muted); margin:0; }

.q-card{ background:var(--surface); border:1px solid var(--border); border-radius:var(--radius);
  padding:var(--s5); box-shadow:var(--shadow-sm); scroll-margin-top:88px; }
.q-bar{ display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:var(--s3); }
.q-index{ font-size:.85rem; color:var(--muted); } .q-index b{ color:var(--brand); font-size:1rem; }
.q-tags{ display:flex; gap:8px; align-items:center; }
.q-prompt{ font-size:1.08rem; font-weight:600; color:var(--ink); line-height:1.45; margin:0 0 var(--s4); }
.q-figures{ display:flex; flex-wrap:wrap; gap:var(--s3); margin:0 0 var(--s4); }
.q-figure{ margin:0; }
/* The exam SVGs carry a viewBox but no width/height, so they have NO intrinsic
   size. max-* alone has nothing to constrain and the image collapsed to a few
   pixels - loaded, visible, and far too small to see. An explicit width gives
   the box its size; the viewBox supplies the aspect ratio, so height:auto keeps
   the proportions and object-fit guards the clamp. */
.q-figure img{ display:block; width:260px; max-width:100%; height:auto; max-height:340px;
  border:1px solid var(--border); border-radius:var(--radius-sm);
  background:#fff; padding:6px; object-fit:contain; }
/* The same box for a figure written into the page instead of fetched. An inline
   <svg> takes its width from the attribute it carries, so it is overridden here
   for the same reason the <img> is: the drawing must fit the column, not the
   size the generator happened to emit. object-fit does nothing to an SVG - the
   viewBox and preserveAspectRatio do that job - so it is left off. */
.q-figure > .fig-svg{ display:block; width:260px; max-width:100%; height:auto; max-height:340px;
  border:1px solid var(--border); border-radius:var(--radius-sm);
  background:#fff; padding:6px; }
.asset-thumb{ width:56px; height:56px; object-fit:contain; background:#fff; border:1px solid var(--border); border-radius:6px; padding:2px; }

.opts{ display:grid; gap:10px; }
.opt{ display:flex; align-items:center; gap:12px; padding:12px 15px; border:1px solid var(--border-strong);
  border-radius:var(--radius-sm); cursor:pointer; background:var(--surface); font-size:.98rem; color:var(--ink-2);
  position:relative; transition:border-color .12s, background .12s; }
.opt:hover{ border-color:var(--accent); background:var(--accent-soft); }
.opt.sel{ border-color:var(--accent); background:var(--accent-soft); color:var(--ink); font-weight:600; }
.opt input{ position:absolute; opacity:0; width:1px; height:1px; }
.opt-key{ flex:0 0 auto; width:26px; height:26px; border-radius:50%; border:2px solid var(--border-strong);
  display:grid; place-items:center; font-weight:700; font-size:.8rem; color:var(--muted); }
.opt.sel .opt-key{ border-color:var(--accent); background:var(--accent); color:var(--on-brand); }
.opt input:focus-visible ~ .opt-key{ outline:2px solid var(--accent); outline-offset:2px; }
.opt-body{ display:flex; align-items:center; gap:12px; flex:1; min-width:0; }
/* Figure canvases stay literally white: the exam SVGs are black line art with no
   background of their own, so a themed surface would bleed through them. */
.opt-figure{ display:block; width:190px; max-width:100%; height:auto; max-height:180px;
  object-fit:contain; background:#fff;
  border:1px solid var(--border); border-radius:8px; padding:4px; }
/* An inlined option figure is a span carrying the box, with the drawing inside
   it filling the width - so the box keeps its own size rather than inheriting
   whatever the SVG declares. */
span.opt-figure{ box-sizing:border-box; }
.opt-figure > .fig-svg{ display:block; width:100%; height:auto; max-height:170px; }
.opt-text{ min-width:0; }
/* when every option carries a figure, show the choices as a 2-col picture grid */
.opts:has(.opt-figure){ grid-template-columns:repeat(2,minmax(0,1fr)); align-items:stretch; }
.opts:has(.opt-figure) .opt{ flex-direction:column; align-items:flex-start; gap:10px; }
@media (max-width:560px){ .opts:has(.opt-figure){ grid-template-columns:1fr; } }
.nat{ display:flex; flex-direction:column; gap:6px; font-weight:600; color:var(--ink-2); max-width:280px; }
.nat input{ font-weight:400; }
.passage{ background:var(--surface-2); border-left:3px solid var(--accent); padding:12px 14px;
  border-radius:var(--radius-sm); color:var(--ink-2); margin-bottom:var(--s4); }

.q-foot{ display:flex; align-items:center; justify-content:space-between; margin-top:var(--s4);
  padding-top:var(--s3); border-top:1px solid var(--border); }
.mark-btn{ font:inherit; font-size:.85rem; font-weight:600; color:var(--muted); background:none;
  border:1px solid var(--border-strong); border-radius:var(--radius-pill); padding:6px 13px; cursor:pointer; }
.mark-btn:hover{ color:var(--warn); border-color:var(--warn); }
.mark-btn.on{ background:var(--warn-soft); color:var(--warn); border-color:transparent; }
.save-state{ font-size:.82rem; color:var(--muted); }
.save-state.saved{ color:var(--ok); font-weight:600; }
.save-state.failed{ color:var(--danger); font-weight:600; }

.exam-nav{ position:sticky; top:88px; background:var(--surface); border:1px solid var(--border);
  border-radius:var(--radius); padding:var(--s4); box-shadow:var(--shadow-sm); }
.exam-nav h4{ font-size:.72rem; text-transform:uppercase; letter-spacing:.08em; color:var(--muted); margin-bottom:var(--s3); }
.qgrid{ display:grid; grid-template-columns:repeat(5,1fr); gap:8px; }
.qbtn{ aspect-ratio:1; display:grid; place-items:center; border-radius:8px; border:1px solid var(--border);
  background:var(--surface-2); font-size:.85rem; font-weight:700; color:var(--ink-2); text-decoration:none; font-variant-numeric:tabular-nums; }
.qbtn:hover{ text-decoration:none; border-color:var(--accent); }
.qbtn.done{ background:var(--ok); color:var(--on-brand); border-color:var(--ok); }
.qbtn.mark{ background:var(--warn); color:var(--on-brand); border-color:var(--warn); }
.qbtn.cur{ outline:2px solid var(--accent); outline-offset:1px; }
.legend{ display:flex; flex-direction:column; gap:8px; margin-top:var(--s4); font-size:.78rem; color:var(--muted); }
.legend span{ display:flex; align-items:center; gap:8px; }
.legend i{ width:13px; height:13px; border-radius:4px; display:inline-block; }
.lg-done{ background:var(--ok); } .lg-mark{ background:var(--warn); } .lg-none{ background:var(--surface-2); border:1px solid var(--border); }
.meter{ height:8px; border-radius:var(--radius-pill); background:var(--surface-3); overflow:hidden; }
.meter i{ display:block; height:100%; background:var(--accent); border-radius:var(--radius-pill); transition:width .2s ease; }
.exam-progress{ margin:var(--s4) 0; display:flex; flex-direction:column; gap:6px; }
.exam-submit{ width:100%; margin-top:var(--s2); }
.exam-note{ font-size:.76rem; margin-top:var(--s3); }

@media (max-width:900px){
  .exam-layout{ grid-template-columns:1fr; padding:var(--s4) var(--s3); }
  .exam-nav{ position:static; order:-1; }
  .qgrid{ grid-template-columns:repeat(8,1fr); }
  .exam-top{ padding:10px 16px; }
}

/* ---------------------------------------------------------------- feedback */
.alert{ padding:12px 16px; border-radius:var(--radius-sm); font-size:.95rem;
  background:var(--info-soft); color:var(--info); border:1px solid rgba(31,95,139,.28); }
.alert.error{ background:var(--danger-soft); color:var(--danger); border-color:rgba(180,35,24,.28); }
.alert.success{ background:var(--ok-soft); color:var(--ok); border-color:rgba(31,122,77,.28); }
.alert.warning{ background:var(--warn-soft); color:var(--warn); border-color:rgba(154,91,0,.28); }
.notice{ background:var(--accent-soft); border:1px solid var(--accent-line); color:var(--accent-ink);
  padding:14px 16px; border-radius:var(--radius-sm); margin:var(--s4) 0; }

code{ font-family:var(--font-mono); font-size:.86em; background:var(--surface-2); color:var(--ink);
  padding:.15em .4em; border-radius:6px; border:1px solid var(--border); }
details summary{ cursor:pointer; color:var(--accent-ink); font-weight:600; }
/* The bot challenge. Usually invisible, so the wrapper holds its own space
   rather than letting a challenge that does appear shove the submit button
   down under someone's cursor as they click it. */
.challenge{ min-height:66px; display:flex; flex-direction:column; justify-content:center; }
.challenge:empty{ min-height:0; }
.challenge-noscript{ margin:0; font-size:.9rem; color:var(--warn); }
/* The mark scheme on a subjective paper: available before the candidate starts,
   folded away so it does not sit between the brief and the upload control. */
.rubric-detail{ background:var(--surface-2); border:1px solid var(--border);
  border-radius:var(--radius-sm); padding:12px 14px; margin:var(--s4) 0; }
.rubric-detail[open] summary{ margin-bottom:var(--s3); }
.rubric-detail table{ margin:0; font-size:.92rem; }
.rubric-detail td:last-child, .rubric-detail th:last-child{ text-align:right; width:7rem;
  font-variant-numeric:tabular-nums; }
.plan{ border-top:3px solid var(--accent); }
.footer{ flex:none; background:var(--surface); border-top:1px solid var(--border); color:var(--muted);
  font-size:var(--t-meta); padding:var(--s6) var(--s5) var(--s5); margin-top:var(--s7); }
.footer-inner{ max-width:var(--shell); margin:0 auto; display:grid; gap:var(--s5);
  grid-template-columns:1.4fr 1fr 1fr; align-items:start; }
/* The public pages carry a fourth column. Without this the track list is three
   wide and the last column drops onto a row of its own under the brand blurb.
   Deliberately one class and placed above the 760px override, so that override
   still wins and the whole thing stacks on a phone. */
.footer-inner-4{ grid-template-columns:1.5fr 1fr 1fr 1fr; }
.footer-col strong{ display:block; color:var(--ink); font-size:.9rem; margin-bottom:var(--s2); }
.footer-col a{ display:block; color:var(--muted); padding:3px 0; }
.footer-col a:hover{ color:var(--brand); }
.footer-note{ max-width:var(--shell); margin:var(--s5) auto 0; padding-top:var(--s4);
  border-top:1px solid var(--border); display:flex; justify-content:space-between; gap:var(--s4);
  flex-wrap:wrap; font-size:.82rem; color:var(--faint); }
@media (max-width:760px){ .footer-inner{ grid-template-columns:1fr; gap:var(--s4); } }
.footer a{ color:var(--muted); }

/* ---------------------------------------------------------------- responsive */
@media (max-width:900px){
  .hero,.grid.two,.grid.three,.grid.four,.grid.six,.three-col{ grid-template-columns:1fr; }
  .two-col-form{ grid-template-columns:1fr; }
  .topbar{ padding:10px 16px; }
  .container{ padding:24px 16px; }
  .executive-hero h1{ font-size:2.15rem; }
  .runtime-header{ display:block; } .timer{ display:inline-block; margin-top:10px; }
  .page-head{ align-items:flex-start; }
  /* let wide tables scroll inside their card instead of breaking the layout */
  .card,.soft-card{ overflow-x:auto; }
}

@media (prefers-reduced-motion: reduce){ *{ transition:none !important; animation:none !important; scroll-behavior:auto !important; } }

/* ============================================================================
   Console shell (admin + evaluator) — persistent sidebar + slim top strip
   ============================================================================ */
.console-body{ margin:0; background:var(--ground); }
.console-shell{ display:grid; grid-template-columns:250px minmax(0,1fr); min-height:100vh; }

.console-side{ background:linear-gradient(190deg,var(--brand),var(--brand-2)); color:var(--on-brand);
  padding:16px 14px; display:flex; flex-direction:column; gap:6px;
  position:sticky; top:0; height:100vh; overflow-y:auto; }
.console-brand{ display:flex; align-items:center; gap:12px; padding:6px 8px 16px; text-decoration:none; }
.console-brand .brand-copy strong{ color:var(--nav-fg); }
.console-brand .brand-copy small{ color:var(--nav-dim); }
.console-brand:hover{ text-decoration:none; }

.console-nav{ display:flex; flex-direction:column; gap:12px; }
.nav-group{ display:flex; flex-direction:column; gap:2px; }
.nav-group-foot{ margin-top:auto; padding-top:12px; border-top:1px solid rgba(255,255,255,.1); }
/* The group labels were rgba(255,255,255,.4), which composites to #707586 on the
   console's --brand-2 and lands at 3.26:1 - under the 4.5:1 floor for 10px caps.
   --nav-dim is the chrome's own dim value and measures 6.96:1 on the same ground. */
.nav-group > p{ font-size:.64rem; letter-spacing:.15em; text-transform:uppercase;
  color:var(--nav-dim); padding:0 10px; margin:6px 0 3px; }
.nav-item{ display:flex; align-items:center; gap:10px; width:100%; text-align:left; cursor:pointer;
  font:inherit; font-size:.9rem; font-weight:500; color:rgba(255,255,255,.82);
  background:none; border:0; padding:9px 10px; border-radius:8px; text-decoration:none; }
.nav-item:hover{ background:rgba(255,255,255,.09); color:var(--on-brand); text-decoration:none; }
.nav-item.on{ background:rgba(255,255,255,.12); color:var(--on-brand); box-shadow:inset 3px 0 0 var(--accent); font-weight:600; }
.nav-logout{ color:rgba(255,255,255,.66); }
.nav-logout:hover{ color:var(--on-brand); }

.console-wrap{ display:flex; flex-direction:column; min-width:0; }
.console-top{ display:flex; align-items:center; justify-content:space-between; gap:var(--s4);
  padding:12px 28px; background:rgba(255,255,255,.9); backdrop-filter:blur(14px);
  border-bottom:1px solid var(--border); position:sticky; top:0; z-index:10; }
.console-env{ font-size:.78rem; font-weight:600; color:var(--muted); letter-spacing:.02em; }
.console-top-user{ display:flex; align-items:center; gap:12px; font-size:.9rem; }
.avatar{ width:34px; height:34px; border-radius:50%; background:var(--accent-soft); color:var(--accent-ink);
  display:grid; place-items:center; font-weight:700; font-size:.8rem; border:1px solid var(--accent-line); }
.console-main{ padding:28px; width:100%; max-width:1240px; }

@media (max-width:900px){
  .console-shell{ grid-template-columns:1fr; }
  .console-side{ position:static; height:auto; flex-direction:column; gap:4px; }
  .console-nav{ flex-direction:row; flex-wrap:nowrap; overflow-x:auto; gap:4px; padding-bottom:4px; }
  .nav-group{ flex-direction:row; align-items:center; gap:4px; }
  .nav-group > p{ display:none; }
  .nav-group-foot{ margin-top:0; border-top:0; border-left:1px solid rgba(255,255,255,.12); padding-left:6px; }
  .nav-item{ white-space:nowrap; padding:8px 12px; }
  .nav-item.on{ box-shadow:inset 0 -3px 0 var(--accent); }
  .console-main{ padding:20px 16px; }
  .console-top{ padding:10px 16px; }
}

/* ---------------------------------------------------------------- dashboard readiness + empty states */
.readiness-card{ border-top:3px solid var(--accent); }
.readiness{ display:flex; align-items:center; gap:var(--s6); flex-wrap:wrap; }
.ring{ position:relative; width:104px; height:104px; flex:0 0 auto; }
.ring b{ position:absolute; inset:0; display:grid; place-items:center; font-size:1.7rem; font-weight:800; color:var(--brand); }
.readiness-body{ flex:1; min-width:260px; }
.readiness-body h2{ margin-bottom:6px; }
.topic-meters{ display:grid; gap:var(--s3); margin-top:var(--s4); }
.topic-meter-head{ display:flex; justify-content:space-between; font-size:.88rem; margin-bottom:5px; color:var(--ink-2); }
/* .empty is defined once, in the component layer below. */

/* ---------------------------------------------------------------- figure zoom viewer */
.zoomable{ cursor:zoom-in; }
.zoom-overlay{ position:fixed; inset:0; z-index:100; display:none; flex-direction:column;
  background:rgba(9,17,28,.92); }
.zoom-overlay.open{ display:flex; }
.zoom-bar{ display:flex; gap:8px; justify-content:flex-end; align-items:center; padding:12px 16px; }
.zoom-bar button{ font:inherit; font-weight:600; font-size:.95rem; color:var(--on-brand); background:rgba(255,255,255,.12);
  border:1px solid rgba(255,255,255,.24); border-radius:8px; padding:8px 14px; cursor:pointer; min-width:44px; }
.zoom-bar button:hover{ background:rgba(255,255,255,.22); }
.zoom-bar button:focus-visible{ outline:2px solid var(--on-brand); outline-offset:2px; }
.zoom-close{ margin-left:8px; }
.zoom-stage{ flex:1; display:grid; place-items:center; overflow:hidden; padding:8px; }
.zoom-stage img{ max-width:94vw; max-height:78vh; object-fit:contain; background:#fff; border-radius:6px;
  transform-origin:center center; transition:transform .12s ease; touch-action:none; user-select:none; -webkit-user-drag:none; }
.zoom-caption{ color:rgba(255,255,255,.85); text-align:center; padding:8px 16px 20px; font-size:.9rem; }
@media (prefers-reduced-motion: reduce){ .zoom-stage img{ transition:none; } }

/* ============================================================================
   Student analytics — readiness dashboard (donut · KPIs · sparkline · mastery)
   ============================================================================ */
.exec-band{ display:grid; grid-template-columns:1.05fr 1.35fr; gap:var(--s5); margin-bottom:var(--s5); align-items:stretch; }
.exec-band > .card{ margin-bottom:0; }

.donut-wrap{ display:flex; align-items:center; gap:var(--s5); flex-wrap:wrap; }
.donut{ width:150px; height:150px; flex:0 0 auto; }
.donut-track{ fill:none; stroke:var(--surface-3); stroke-width:13; }
.donut-val{ fill:none; stroke-width:13; stroke-linecap:round; transition:stroke-dasharray .7s cubic-bezier(.4,0,.2,1); }
.donut-val.band-strong{ stroke:var(--ok); } .donut-val.band-developing{ stroke:var(--warn); } .donut-val.band-focus{ stroke:var(--danger); }
.donut-num{ text-anchor:middle; font-size:27px; font-weight:800; fill:var(--brand); font-variant-numeric:tabular-nums; }
.donut-lbl{ text-anchor:middle; font-size:10px; letter-spacing:.14em; text-transform:uppercase; fill:var(--muted); }
.readiness-side{ min-width:170px; flex:1; }
.readiness-side .ins{ display:flex; align-items:center; gap:8px; margin:7px 0 0; color:var(--ink-2); font-size:.9rem; }
.readiness-side .ins strong{ color:var(--ink); }
.dot{ width:9px; height:9px; border-radius:50%; display:inline-block; flex:0 0 auto; }
.dot.ok{ background:var(--ok); } .dot.danger{ background:var(--danger); }

.pill.band-strong{ background:var(--ok-soft); color:var(--ok); }
.pill.band-developing{ background:var(--warn-soft); color:var(--warn); }
.pill.band-focus{ background:var(--danger-soft); color:var(--danger); }

.kpi-grid{ display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:var(--s4); }
.kpi{ margin-bottom:0; padding:var(--s4) var(--s5); display:flex; flex-direction:column; justify-content:center; }
.kpi-lbl{ text-transform:uppercase; letter-spacing:.09em; font-size:.68rem; font-weight:700; color:var(--muted); margin:0; }
.kpi-num{ font-size:1.85rem; font-weight:750; color:var(--brand); letter-spacing:-.03em; margin:5px 0 3px;
  display:flex; align-items:baseline; gap:9px; font-variant-numeric:tabular-nums; }
.kpi-sub{ margin:0; font-size:.8rem; }
/* a delta is a separate figure from the value it follows — it needs its own air,
   and "pp" because a change in a percentage is percentage POINTS, not percent */
.delta{ display:inline-flex; align-items:center; gap:3px; margin-left:8px;
  vertical-align:middle; font-size:.72rem; font-weight:700; padding:2px 8px;
  border-radius:var(--radius-pill); letter-spacing:.01em; font-variant-numeric:tabular-nums; }
.delta.up{ background:var(--ok-soft); color:var(--ok); } .delta.down{ background:var(--danger-soft); color:var(--danger); }

.chart-head{ display:flex; justify-content:space-between; align-items:baseline; gap:var(--s4); flex-wrap:wrap; margin-bottom:var(--s4); }
.chart-head h2{ margin:0; }
.small{ font-size:.82rem; }

.spark-wrap{ width:100%; }
.spark{ width:100%; height:auto; display:block; overflow:visible; }
.grid-line{ stroke:var(--border); stroke-width:1; stroke-dasharray:3 4; }
.grid-line.axis{ stroke:var(--border-strong); stroke-dasharray:none; }
.spark-area{ fill:color-mix(in srgb, var(--accent) 12%, transparent); stroke:none; }
.spark-line{ fill:none; stroke:var(--accent); stroke-width:2.5; stroke-linejoin:round; stroke-linecap:round; }
.spark-dot{ fill:var(--surface); stroke:var(--accent); stroke-width:2.5; }
.spark-val{ text-anchor:middle; font-size:11px; font-weight:700; fill:var(--brand); font-variant-numeric:tabular-nums; }
.spark-xlbl{ text-anchor:middle; font-size:10px; fill:var(--muted); }

.mastery{ display:flex; flex-direction:column; gap:var(--s5); }
.mastery-top{ display:flex; justify-content:space-between; align-items:center; gap:var(--s3); margin-bottom:7px; }
.mastery-name{ font-weight:650; color:var(--ink); }
.acc-tag{ font-weight:700; font-size:.82rem; padding:2px 10px; border-radius:var(--radius-pill); flex:0 0 auto; font-variant-numeric:tabular-nums; }
.acc-tag.band-strong{ background:var(--ok-soft); color:var(--ok); }
.acc-tag.band-developing{ background:var(--warn-soft); color:var(--warn); }
.acc-tag.band-focus{ background:var(--danger-soft); color:var(--danger); }
.hbar{ display:flex; height:14px; border-radius:var(--radius-pill); overflow:hidden; background:var(--surface-3); }
.seg{ height:100%; transition:width .6s cubic-bezier(.4,0,.2,1); }
.seg.correct{ background:var(--ok); } .seg.partial{ background:var(--info); } .seg.incorrect{ background:var(--danger); } .seg.unattempted{ background:var(--border-strong); }
.mastery-legend{ display:flex; gap:var(--s4); flex-wrap:wrap; margin-top:8px; align-items:center; }
.mastery-legend .marks{ margin-left:auto; font-weight:700; color:var(--ink-2); }
.sw{ width:10px; height:10px; border-radius:3px; display:inline-block; margin-right:5px; vertical-align:-1px; }
.sw.correct{ background:var(--ok); } .sw.partial{ background:var(--info); } .sw.incorrect{ background:var(--danger); } .sw.unattempted{ background:var(--border-strong); }

.empty-note{ padding:var(--s5); text-align:center; background:var(--surface-2); border-radius:var(--radius-sm); }
.empty-hero{ text-align:center; padding:var(--s7) var(--s5); }
.empty-hero .btn{ margin-top:var(--s3); }

@media (max-width:860px){
  .exec-band{ grid-template-columns:1fr; }
}

/* ---------------------------------------------------------------- practice studio */
.practice-preview{ background:linear-gradient(180deg,var(--surface),var(--surface-2)); }
.preview-total{ font-size:2.6rem; font-weight:800; color:var(--brand); letter-spacing:-.04em; margin:2px 0 2px; font-variant-numeric:tabular-nums; }
.preview-total .muted{ font-size:1rem; font-weight:600; letter-spacing:0; }
.split-chips{ display:flex; flex-wrap:wrap; gap:8px; margin-top:var(--s3); }
.split-chip{ display:inline-flex; align-items:center; gap:6px; background:var(--surface); border:1px solid var(--border-strong);
  border-radius:var(--radius-pill); padding:6px 13px; font-size:.88rem; color:var(--ink-2); }
.split-chip strong{ color:var(--brand); font-variant-numeric:tabular-nums; }
input[type=range]{ width:100%; accent-color:var(--accent); margin:2px 0 0; }

/* ---------------------------------------------------------------- code tasks (Pyodide) */
.code-task{ display:flex; flex-direction:column; gap:var(--s3); }
.code-examples{ font-size:.9rem; color:var(--ink-2); }
.code-examples ul{ margin:6px 0 0; padding-left:18px; }
.code-examples code{ font-size:.86em; }
.code-editor{ width:100%; min-height:220px; font-family:var(--font-mono); font-size:.9rem; line-height:1.5;
  tab-size:4; white-space:pre; overflow:auto; color:#e6edf3; background:#0d1626; border:1px solid var(--border-strong);
  border-radius:var(--radius-sm); padding:12px 14px; resize:vertical; }
.code-editor:focus{ outline:2px solid var(--accent); outline-offset:1px; }
.code-actions{ display:flex; align-items:center; gap:var(--s4); }
.code-status{ font-size:.9rem; font-weight:600; color:var(--muted); font-variant-numeric:tabular-nums; }
.code-status.pass{ color:var(--ok); }
.code-status.fail{ color:var(--danger); }
.code-output{ font-family:var(--font-mono); font-size:.82rem; line-height:1.5; white-space:pre-wrap; word-break:break-word;
  background:var(--surface-2); border:1px solid var(--border); border-radius:var(--radius-sm); padding:10px 12px; margin:0;
  max-height:220px; overflow:auto; color:var(--ink-2); }

/* ---------------------------------------------------------------- flashcards (spaced repetition) */
.deck-card{ display:flex; flex-direction:column; gap:var(--s3); }
.deck-head{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
.deck-head h3{ margin:0; }
.deck-stats{ display:flex; align-items:center; gap:var(--s4); flex-wrap:wrap; margin-top:auto; }
.deck-due{ color:var(--muted); font-size:.95rem; } .deck-due strong{ color:var(--ink); font-variant-numeric:tabular-nums; }
.deck-due.hot{ color:var(--danger); } .deck-due.hot strong{ color:var(--danger); }
.deck-new{ color:var(--muted); font-size:.95rem; } .deck-new strong{ color:var(--info); font-variant-numeric:tabular-nums; }

.fc-builder{ margin-bottom:var(--s5); }
.fc-builder-row{ display:flex; flex-wrap:wrap; align-items:flex-end; gap:var(--s3); }
.fc-builder-row label{ display:grid; gap:5px; font-weight:600; color:var(--ink-2); font-size:.9rem; }
.fc-builder-row select{ min-width:150px; }
.fc-builder-row .btn{ margin-left:auto; }
.deck-heading{ font-size:1.1rem; margin:0 0 var(--s3); }
.mycard-list{ display:flex; flex-direction:column; gap:var(--s3); }
.mycard{ border:1px solid var(--border); border-radius:var(--radius-sm); padding:var(--s3) var(--s4); }
.mycard-meta{ display:flex; align-items:center; gap:8px; margin-bottom:6px; }
.mycard-front{ font-weight:600; }
.mycard-back{ font-size:.92rem; margin-top:2px; }
.mycard form{ margin-top:6px; }
.study-shell{ max-width:720px; margin:0 auto; }
.study-top{ display:flex; align-items:center; gap:var(--s3); margin-bottom:var(--s4); }
.study-top .study-progress{ margin-left:auto; font-variant-numeric:tabular-nums; }
.flashcard{ min-height:220px; display:flex; flex-direction:column; gap:var(--s3); font-size:1.08rem; }
.flashcard[hidden], .fc-ratings[hidden]{ display:none; }
.fc-topic{ text-transform:uppercase; letter-spacing:.08em; margin:0; }
.fc-face code{ font-size:.9em; }
.fc-face pre{ background:var(--surface-2); border:1px solid var(--border); border-radius:var(--radius-sm); padding:12px 14px; overflow:auto; }
.fc-back hr{ border:0; border-top:1px solid var(--border); margin:var(--s2) 0 var(--s3); }
.fc-extra{ font-size:.92rem; margin-top:var(--s3); }
.study-controls{ margin-top:var(--s4); display:flex; flex-direction:column; align-items:center; gap:var(--s3); }
.fc-show{ min-width:220px; }
.fc-ratings{ display:grid; grid-template-columns:repeat(4,1fr); gap:10px; width:100%; }
.rate{ font:inherit; font-weight:650; cursor:pointer; border:1px solid var(--border-strong); border-radius:var(--radius-sm);
  padding:12px 8px; background:var(--surface); color:var(--ink); display:flex; flex-direction:column; align-items:center; gap:4px; transition:transform .1s ease; }
.rate:hover{ transform:translateY(-1px); }
.rate .kbd{ font-size:.7rem; }
.rate.again{ border-color:rgba(180,35,24,.4); color:var(--danger); }
.rate.hard{ border-color:rgba(154,91,0,.4); color:var(--warn); }
.rate.good{ border-color:rgba(31,95,139,.4); color:var(--info); }
.rate.easy{ border-color:rgba(31,122,77,.4); color:var(--ok); }
.rate.again:hover{ background:var(--danger-soft); } .rate.hard:hover{ background:var(--warn-soft); }
.rate.good:hover{ background:var(--info-soft); } .rate.easy:hover{ background:var(--ok-soft); }
.kbd{ display:inline-block; border:1px solid var(--border-strong); border-bottom-width:2px; border-radius:5px; padding:0 5px;
  font-family:var(--font-mono); font-size:.72rem; color:var(--muted); background:var(--surface-2); }
.study-done{ text-align:center; margin-top:var(--s5); }
.study-done .actions{ justify-content:center; margin-top:var(--s3); }
@media (max-width:560px){ .fc-ratings{ grid-template-columns:repeat(2,1fr); } }

/* ---------------------------------------------------------------- print (invoices / results) */
@media print{
  /* ---- the page becomes a document ---- */
  @page{ margin:14mm 12mm; }
  html,body{ background:#fff !important; color:#000; }
  body{ font-size:10.5pt; }

  /* interface chrome has no meaning on paper */
  .topbar,.footer,.skip-link,.nav-links,.nav-toggle,.actions,.btn,.small-btn,
  .pager,.toolbar-form,.zoom-bar,.mark-btn,.exam-nav,.print-hide{ display:none !important; }

  .container{ padding:0; max-width:none; width:auto; }
  .card,.tablewrap{ box-shadow:none !important; border:1px solid #999; break-inside:avoid; }
  .page-head{ border-bottom:1.5pt solid #000; margin-bottom:8mm; }

  /* keep a heading with the content it introduces */
  h1,h2,h3,h4{ break-after:avoid; color:#000; }
  tr,li,.rowitem,.stat{ break-inside:avoid; }
  thead{ display:table-header-group; }     /* repeat headers across pages */

  /* a link's destination is invisible on paper unless it is spelled out; skip
     in-page and javascript hrefs, which would be noise */
  a[href^="http"]::after{ content:" (" attr(href) ")"; font-size:8.5pt; color:#555; word-break:break-all; }

  /* semantic fills often drop out of a print pipeline — keep them */
  .pill,.badge,.tone-ok,.tone-warn,.tone-danger,.tone-info,.tone-neutral,
  .band,.donut-val{ -webkit-print-color-adjust:exact; print-color-adjust:exact; }

  /* ---- the result report specifically ---- */
  .rr{ max-width:none; }
  .rr-head{ break-after:avoid; }
  .rr .solution,.rr details{ break-inside:avoid; }
  .rr details[open] > summary ~ *{ display:block; }   /* print what is open */
  .print-only{ display:block !important; }
}
.print-only{ display:none; }

/* ============================================================================
   ENTERPRISE COMPONENT LAYER
   Added in the interface refresh. Every rule below binds to the tokens above -
   no raw colour values - so a palette change reaches the whole product.
   ============================================================================ */

/* ---- page header ---------------------------------------------------------
   .page-head already existed and 20 templates use it; these rules finish it
   rather than introduce a rival class. */
.page-head{ display:flex; justify-content:space-between; align-items:flex-end; gap:var(--s4);
  flex-wrap:wrap; margin-bottom:var(--s5); padding-bottom:var(--s4); border-bottom:1px solid var(--border); }
.page-head > div:first-child{ min-width:min(100%,32ch); }
.page-head h1{ margin:0; }
.page-head .muted{ margin:var(--s2) 0 0; max-width:var(--measure); }
.page-head .actions{ display:flex; gap:var(--s2); align-items:center; flex-wrap:wrap; }
.crumbs{ display:flex; align-items:center; gap:var(--s2); flex-wrap:wrap; margin:0 0 var(--s2);
  font-size:var(--t-meta); color:var(--muted); }
.crumbs a{ color:var(--muted); }
.crumbs a:hover{ color:var(--brand); }
.crumbs li{ list-style:none; display:flex; align-items:center; gap:var(--s2); }
.crumbs ol{ display:flex; align-items:center; gap:var(--s2); flex-wrap:wrap; margin:0; padding:0; }
.crumbs li + li::before{ content:"/"; color:var(--hairline); }
.crumbs [aria-current="page"]{ color:var(--ink-2); font-weight:600; }

/* .eyebrow is defined once, with the page-head rules above. */

/* ---- statistics: populated, empty and loading are different things ------ */
.statgrid{ display:grid; gap:var(--s3); grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); }
.stat{ background:var(--surface); border:1px solid var(--border); border-radius:var(--radius);
  padding:var(--s4) var(--s4) var(--s3); box-shadow:var(--shadow-sm); }
.stat b,.stat > span:first-child{ display:block; font-family:var(--font-display); font-size:1.9rem;
  font-weight:600; line-height:1; color:var(--brand); font-variant-numeric:tabular-nums; }
.stat p,.stat small{ display:block; margin:var(--s2) 0 0; color:var(--muted); font-size:var(--t-meta); }
.stat .stat-note{ margin-top:var(--s1); font-size:.78rem; color:var(--faint); }
.stat.is-empty{ background:var(--surface-2); border-style:dashed; }
.stat.is-empty b,.stat.is-empty > span:first-child{ color:var(--faint); }
.stat.is-loading b{ color:transparent; background:linear-gradient(90deg,var(--surface-2),var(--surface-3),var(--surface-2));
  background-size:200% 100%; border-radius:var(--radius-sm); animation:shimmer 1.4s infinite; }
@keyframes shimmer{ from{ background-position:200% 0; } to{ background-position:-200% 0; } }

/* ---- empty state: say what is missing and what fixes it ----------------- */
.empty{ text-align:center; padding:var(--s7) var(--s5); border:1px dashed var(--border-strong);
  border-radius:var(--radius); background:var(--surface-2); color:var(--muted); }
.empty h3{ color:var(--ink); margin:0 0 var(--s2); }
.empty p{ margin:0 auto; max-width:52ch; font-size:.95rem; }
.empty .actions{ margin-top:var(--s4); justify-content:center; }

/* ---- list row: the repeated "thing with an action" --------------------- */
.rowlist{ display:flex; flex-direction:column; gap:var(--s2); }
.rowitem{ display:flex; align-items:center; gap:var(--s4); flex-wrap:wrap;
  background:var(--surface); border:1px solid var(--border); border-radius:var(--radius);
  padding:var(--s3) var(--s4); }
.rowitem:hover{ border-color:var(--border-strong); }
.rowitem .ri-main{ flex:1; min-width:min(100%,24ch); }
.rowitem .ri-title{ font-weight:650; color:var(--ink); }
.rowitem .ri-meta{ color:var(--muted); font-size:var(--t-meta); margin-top:2px;
  font-variant-numeric:tabular-nums; }

/* ---- tables built for scanning ----------------------------------------- */
.tablewrap{ overflow-x:auto; border:1px solid var(--border); border-radius:var(--radius);
  background:var(--surface); box-shadow:var(--shadow-sm); }
.tablewrap table{ margin:0; border:0; }
table thead th{ position:sticky; top:0; z-index:1; background:var(--surface-2);
  font-size:var(--t-label); text-transform:uppercase; letter-spacing:.08em; color:var(--muted);
  font-weight:700; white-space:nowrap; }
table tbody tr:hover{ background:var(--surface-2); }
td.num,th.num{ text-align:right; font-variant-numeric:tabular-nums; }

/* ---- section banner: scope notes, cautions, guidance -------------------- */
.note{ background:var(--surface); border:1px solid var(--border); border-left:3px solid var(--accent);
  border-radius:var(--radius-sm); padding:var(--s4) var(--s4); color:var(--ink-2); font-size:.93rem; }
.note strong{ color:var(--ink); }
.note + .note{ margin-top:var(--s3); }
.note.info{ border-left-color:var(--info); }
.note.ok{ border-left-color:var(--ok); }
.note.warn{ border-left-color:var(--warn); }
.note.danger{ border-left-color:var(--danger); }

/* ---- two-column authentication layout ---------------------------------- */
/* start, not center: centring works while both columns are short, but the
   register form is twice the height of the sign-in card, and its aside then
   floats halfway down the page against a border that starts nowhere. */
.authwrap{ display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr); gap:var(--s7);
  align-items:start; min-height:min(72vh,640px); }
/* The aside stays quiet - one navy panel per screen, and on this screen it is
   the sign-in card. Its only colour is the track hues on the bullets. */
.authwrap .auth-aside{ border-left:1px solid var(--border); padding-left:var(--s6); }
/* The lockup stands in for the old text eyebrow. Width-capped rather than
   height-set: this is the wide artwork, and on a narrow column its height is
   the dependent dimension. max-width keeps it from ever outgrowing the column. */
.authwrap .auth-aside .auth-logo{ display:block; width:min(260px,70%); height:auto;
  margin:0 auto var(--s4); }
.authwrap .auth-aside ul{ margin:var(--s4) 0 0; padding:0; list-style:none;
  display:flex; flex-direction:column; gap:var(--s3); }
.authwrap .auth-aside li{ display:flex; gap:var(--s3); color:var(--ink-2); font-size:.93rem; }
.authwrap .auth-aside li::before{ content:""; flex:none; width:6px; height:6px;
  margin-top:8px; border-radius:2px; background:var(--track-cfa-ink); }
.authwrap .auth-aside li:nth-child(1)::before{ background:var(--track-uceed-ink); }
.authwrap .auth-aside li:nth-child(2)::before{ background:var(--track-nift-ink); }
.authwrap .auth-aside li:nth-child(3)::before{ background:var(--track-cfa-ink); }
.authwrap .auth-aside li:nth-child(4)::before{ background:var(--track-ai-ink); }
@media (max-width:900px){
  .authwrap{ grid-template-columns:1fr; gap:var(--s5); min-height:0; }
  .authwrap .auth-aside{ border-left:0; border-top:1px solid var(--border);
    padding-left:0; padding-top:var(--s5); }
}

/* ---- wide content never pushes the page sideways --------------------------
   27 templates put a table straight into a .card. When the table is wider than
   the card the whole document scrolled horizontally, which on a laptop shows up
   as a 2px jitter and on a phone as a page you can drag off-screen. The card
   itself takes the scroll instead. .tablewrap does the same job explicitly for
   new markup. */
.card:has(> table),
.card:has(> div > table),
.console-card:has(> table){ overflow-x:auto; }

/* ---- pager -----------------------------------------------------------------
   Every list view is bounded. The count line matters as much as the links: an
   operator needs to know how much is behind the filter, not just that there is
   "more". */
.pager{ display:flex; align-items:center; justify-content:space-between; gap:var(--s4);
  flex-wrap:wrap; margin-top:var(--s4); padding-top:var(--s4); border-top:1px solid var(--border); }
.pager p{ margin:0; font-size:var(--t-meta); color:var(--muted); }
.pager b{ color:var(--ink); font-variant-numeric:tabular-nums; font-weight:650; }
.pager-pages{ display:flex; align-items:center; gap:var(--s1); list-style:none; margin:0; padding:0; }
.pager-pages a,.pager-pages span{ display:inline-flex; align-items:center; justify-content:center;
  min-width:34px; height:34px; padding:0 9px; border-radius:var(--radius-sm); font-size:var(--t-meta);
  font-weight:600; font-variant-numeric:tabular-nums; text-decoration:none; color:var(--ink-2);
  border:1px solid transparent; }
.pager-pages a:hover{ background:var(--surface-2); border-color:var(--border); color:var(--brand); text-decoration:none; }
.pager-pages [aria-current="page"]{ background:var(--brand); color:var(--on-brand); }
.pager-pages [aria-disabled="true"]{ color:var(--faint); cursor:default; }
.pager-gap{ min-width:18px; color:var(--hairline); }
.pager-size{ display:flex; align-items:center; gap:var(--s1); }
.pager-size a,.pager-size span{ padding:3px 8px; border-radius:var(--radius-sm); font-weight:600;
  text-decoration:none; color:var(--ink-2); }
.pager-size a:hover{ background:var(--surface-2); color:var(--brand); text-decoration:none; }
.pager-size [aria-current="true"]{ background:var(--surface-3); color:var(--ink); }

/* sortable column headers */
.th-sort{ display:inline-flex; align-items:center; gap:5px; color:inherit; text-decoration:none; }
.th-sort:hover{ color:var(--brand); text-decoration:none; }
th[aria-sort] .th-sort{ color:var(--brand); }
th[aria-sort] .th-sort::after{ content:"▾"; font-size:.7em; }
th[aria-sort="ascending"] .th-sort::after{ content:"▴"; }

/* a prompt cell is a preview, not the content — keep rows one line tall */
.cell-prompt{ max-width:44ch; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:var(--muted); }

/* inline filter fields sit on one toolbar row */
.field-inline{ display:inline-flex; align-items:center; }
.field-inline input[type="search"]{ min-width:min(100%,28ch); }

/* ---- bounded question picker (manual test building) ---- */
.picker-search{ display:flex; gap:var(--s2); flex-wrap:wrap; align-items:center;
  padding:var(--s3); background:var(--surface-2); border:1px solid var(--border);
  border-radius:var(--radius-sm); margin-bottom:var(--s3); }
.picker-search input[type="search"]{ flex:1 1 22ch; min-width:18ch; }
.question-picker .check-card{ display:block; }

/* ---- form fields with anchored validation ----------------------------------
   An error belongs beside the input that caused it, not only in a banner at the
   top of the page. aria-invalid drives the visual state so the two can never
   disagree. */
.field{ display:flex; flex-direction:column; gap:6px; margin-bottom:var(--s4); }
.field-label{ font-weight:600; font-size:.92rem; color:var(--ink); }
.field-label .req{ color:var(--danger); text-decoration:none; border:0; cursor:help; }
.field-help{ font-size:var(--t-meta); color:var(--muted); }
.field-error{ font-size:var(--t-meta); font-weight:600; color:var(--danger);
  display:flex; align-items:flex-start; gap:6px; }
.field-error::before{ content:"!"; flex:none; display:inline-grid; place-items:center;
  width:15px; height:15px; margin-top:1px; border-radius:50%;
  background:var(--danger); color:var(--on-brand); font-size:.66rem; font-weight:800; }

input[aria-invalid="true"],select[aria-invalid="true"],textarea[aria-invalid="true"]{
  border-color:var(--danger); background:var(--danger-soft); }
input[aria-invalid="true"]:focus,select[aria-invalid="true"]:focus,
textarea[aria-invalid="true"]:focus{
  border-color:var(--danger); box-shadow:0 0 0 3px rgba(180,35,24,.22); }

/* page-level summary that links each problem back to its field */
.form-summary ul{ margin:var(--s2) 0 0; padding-left:1.1em; }
.form-summary li{ margin-bottom:4px; font-size:.93rem; }

/* ---- semantic tones -------------------------------------------------------
   These were inline styles repeated across templates. A tone is *state*, so it
   gets a name: change what "warning" looks like once, everywhere. Pair with
   .pill (or any chip) — the tone only supplies colour. */
.tone-ok{ background:var(--ok-soft); color:var(--ok); }
.tone-warn{ background:var(--warn-soft); color:var(--warn); }
.tone-danger{ background:var(--danger-soft); color:var(--danger); }
.tone-info{ background:var(--info-soft); color:var(--info); }
.tone-neutral{ background:var(--surface-2); color:var(--muted); }

/* ---- in-table empty state --------------------------------------------------
   A single cell spanning the table. Without this it reads as a broken row; with
   it, it reads as an answer to "where are my rows?". */
.empty-row td{ text-align:center; padding:var(--s6) var(--s4); color:var(--muted);
  font-size:.94rem; background:var(--surface-2); }
.empty-row td b{ display:block; color:var(--ink); font-weight:650; margin-bottom:4px; }
.empty-row td a{ font-weight:600; }

/* ---- busy state on submit --------------------------------------------------
   Generating a practice set, building a test and importing a CSV all take real
   seconds on the server. Without feedback the page looks frozen and people click
   again — which on a create endpoint means a duplicate. */
.btn[aria-busy="true"],.small-btn[aria-busy="true"]{ pointer-events:none; opacity:.75; }
.btn[aria-busy="true"]::before,.small-btn[aria-busy="true"]::before{
  content:""; width:1em; height:1em; margin-right:.5em; border-radius:50%;
  border:2px solid currentColor; border-right-color:transparent;
  animation:spin .7s linear infinite; }
@keyframes spin{ to{ transform:rotate(360deg); } }
@media (prefers-reduced-motion: reduce){
  .btn[aria-busy="true"]::before,.small-btn[aria-busy="true"]::before{ animation:none; }
}

/* ---- table density --------------------------------------------------------
   An operator scans these top to bottom, so the eye needs a stable left edge for
   labels and a stable right edge for figures. Numerals line up column-wise;
   identifiers and timestamps never wrap mid-value. */
table{ font-variant-numeric:tabular-nums; }
table th,table td{ padding:9px var(--s4); }
td.num,th.num,td.tnum,th.tnum{ text-align:right; font-variant-numeric:tabular-nums; white-space:nowrap; }
table td time,table td .stamp{ white-space:nowrap; color:var(--muted); font-size:var(--t-meta); }
/* zebra only where rows are dense enough to need it */
.tablewrap table tbody tr:nth-child(even){ background:color-mix(in srgb, var(--surface-2) 45%, transparent); }
.tablewrap table tbody tr:hover{ background:var(--surface-2); }

/* ---- error pages ----------------------------------------------------------
   A dead end should still look like the product, and should give the person a
   way onward. The code is decoration — the sentence above it does the work. */
.errorwrap{ max-width:56ch; margin:var(--s8) auto; text-align:center; }
.error-code{ font-family:var(--font-display); font-size:clamp(4rem,14vw,7rem); line-height:1;
  font-weight:600; letter-spacing:-.03em; margin:0 0 var(--s3);
  color:var(--border-strong); font-variant-numeric:lining-nums; }
.errorwrap h1{ margin:0 0 var(--s3); }
.errorwrap .lede{ margin:0 auto var(--s4); color:var(--muted); }
.error-detail{ display:inline-block; margin:0 auto var(--s4); padding:var(--s3) var(--s4);
  background:var(--surface); border:1px solid var(--border); border-left:3px solid var(--warn);
  border-radius:var(--radius-sm); color:var(--ink-2); font-size:.94rem; text-align:left; }
.error-ref{ font-size:var(--t-meta); color:var(--muted); margin-bottom:var(--s5); }
.error-ref code{ font-weight:700; color:var(--ink); }
.errorwrap .actions{ justify-content:center; flex-wrap:wrap; }
