/* Design system for the AI Assessor CEFR interface.
   =================================================

   Nothing is pulled from an external CDN. The university lab machines may have
   outside network access blocked, and a demo in front of the committee where
   the fonts fail to load would ruin the session. Every token below is defined
   here; no page invents its own numbers.

   Layer order:
     1. tokens          - the whole palette, spacing, radius, shadow, type
     2. reset + base    - element defaults derived from the tokens
     3. layout          - header, nav, main, footer, grids
     4. components      - card, button, field, badge, table, notice, toast...
     5. screens         - the handful of rules that belong to one screen only
*/

/* ------------------------------------------------------------------ 1. tokens
   Academic and restrained (D026): ink navy for everything that acts, a claret
   accent used sparingly for what marks a place (the current page, a section
   label), paper-white surfaces.

   The LIGHT palette lives on bare :root so that every colour has a definition
   that does not depend on a media query. The dark blocks further down only
   REDEFINE these variables; no new colour is introduced there.

   One typeface, Roboto (D031), for headings and text alike: weight and size
   carry the hierarchy. It is served by the application itself from
   static/fonts -- never from a font CDN -- so the CSP stays `font-src 'self'`
   and a lab with no internet still renders the same page. One variable file per
   subset and style holds every weight from 100 to 900; the browser fetches a
   subset only when the page has a character in its range, and Vietnamese needs
   two of them (latin for a, á, â...; vietnamese for ă, đ, ơ, ư and the stacked
   marks). Files from @fontsource-variable/roboto 5.3.0; licence SIL OFL 1.1,
   fonts/ROBOTO-OFL.txt. */
@font-face {
  font-family: "Roboto";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("fonts/roboto-vietnamese-wght-normal.woff2") format("woff2");
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
    U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9,
    U+20AB;
}
@font-face {
  font-family: "Roboto";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("fonts/roboto-latin-ext-wght-normal.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Roboto";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("fonts/roboto-latin-wght-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
/* The true italic, same three subsets: a sentence frame or an <em> set in a
   slanted copy of the upright face looks like a rendering fault. */
@font-face {
  font-family: "Roboto";
  font-style: italic;
  font-weight: 100 900;
  font-display: swap;
  src: url("fonts/roboto-vietnamese-wght-italic.woff2") format("woff2");
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
    U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9,
    U+20AB;
}
@font-face {
  font-family: "Roboto";
  font-style: italic;
  font-weight: 100 900;
  font-display: swap;
  src: url("fonts/roboto-latin-ext-wght-italic.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Roboto";
  font-style: italic;
  font-weight: 100 900;
  font-display: swap;
  src: url("fonts/roboto-latin-wght-italic.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
/* Until Roboto arrives, Arial stretched to Roboto's metrics stands in, so the
   text does not jump when the real face swaps in. */
@font-face {
  font-family: "Roboto Fallback";
  src: local("Arial");
  ascent-override: 92.49%;
  descent-override: 24.34%;
  line-gap-override: 0%;
  size-adjust: 100.3%;
}

:root {
  color-scheme: light;

  /* --- brand + semantic colour ------------------------------------------ */
  --brand: #1e3a5f;            /* ink navy: links, buttons, focus of action */
  --brand-strong: #132944;
  --brand-soft: #e9eef5;
  --brand-fg: #ffffff;
  --accent: #8c2f45;           /* claret: current place, section labels */
  --accent-soft: #f6eaee;

  --danger: #a8322a;
  --danger-soft: #f9e9e7;
  --warn: #8a5800;
  --warn-soft: #faf0dd;
  --ok: #2c6a4c;
  --ok-soft: #e6f2eb;
  --info: #1e3a5f;
  --info-soft: #e9eef5;

  /* --- surfaces + text --------------------------------------------------- */
  --bg: #f5f6f8;
  --bg-elevated: #ffffff;
  --bg-sunken: #eff1f5;
  --bg-hover: #e8ecf2;
  --fg: #1a2232;
  --fg-muted: #586276;         /* 5.6:1 on --bg-elevated, passes WCAG AA */
  --fg-on-brand: #ffffff;
  --border: #dadee6;
  --border-strong: #b8bfcc;
  --focus: #2b5d9b;

  /* --- spacing scale (4px base) ------------------------------------------ */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  --sp-6: 32px;
  --sp-7: 48px;
  --sp-8: 64px;

  /* --- radius: small, almost square; printed-page rather than app-bubble -- */
  --r-sm: 4px;
  --r-md: 6px;
  --r-lg: 10px;
  --r-pill: 999px;

  /* --- elevation: hairline borders do most of the work --------------------- */
  --shadow-sm: 0 1px 2px rgba(20, 30, 50, 0.05);
  --shadow-md: 0 2px 10px rgba(20, 30, 50, 0.07);
  --shadow-lg: 0 14px 36px rgba(20, 30, 50, 0.16);

  /* --- type ---------------------------------------------------------------- */
  --font: "Roboto", "Roboto Fallback", system-ui, -apple-system, "Segoe UI",
          "Helvetica Neue", Arial, "Noto Sans", sans-serif;
  /* Headings, prompts and figures. The same family since D031; the token stays
     so that a screen can still be given a face of its own in one place. */
  --font-display: var(--font);
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  --t-xs: 0.75rem;    /* 12px */
  --t-sm: 0.8125rem;  /* 13px */
  --t-md: 0.875rem;   /* 14px */
  --t-base: 1rem;     /* 16px */
  --t-lg: 1.125rem;   /* 18px */
  --t-xl: 1.375rem;   /* 22px */
  --t-2xl: 1.75rem;   /* 28px */
  --t-3xl: 2.5rem;    /* 40px */
  --lh-tight: 1.25;
  --lh-normal: 1.6;

  /* --- motion ------------------------------------------------------------ */
  --ease: cubic-bezier(0.2, 0, 0, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);   /* things arriving: fast, then settle */
  --dur-fast: 120ms;
  --dur-med: 200ms;
  --dur-slow: 260ms;
  --scrim: rgba(10, 18, 30, 0.5);              /* behind a dialog */
  --glass: rgba(255, 255, 255, 0.72);          /* a pane the scene shows through */
  --glass-line: rgba(20, 30, 50, 0.08);
  --shine: rgba(255, 255, 255, 0.6);           /* light caught by a tilted card */
  --shadow-card: 0 1px 2px rgba(20, 30, 50, 0.04), 0 6px 20px -12px rgba(20, 30, 50, 0.14);
  --cube-ink: var(--brand-strong);            /* the label on a pale block, in either theme */

  /* --- layout ------------------------------------------------------------ */
  --page-max: 1100px;
  --header-h: 60px;
}

/* Dark palette. Two selectors, on purpose:
   - the media query covers the browser default ("system"), but must lose to an
     explicit light choice, hence :not([data-theme="light"]);
   - the attribute selector covers the explicit dark choice and wins in both
     directions because it comes later. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --brand: #a9c3e6;
    --brand-strong: #cddcf2;
    --brand-soft: #1a2a40;
    --brand-fg: #0e1724;
    --accent: #e3a0b0;
    --accent-soft: #3a1f28;

    --danger: #f0a097;
    --danger-soft: #3b201d;
    --warn: #e5bb73;
    --warn-soft: #382b14;
    --ok: #8fcfaa;
    --ok-soft: #173026;
    --info: #a9c3e6;
    --info-soft: #1a2a40;

    --bg: #0f1520;
    --bg-elevated: #171f2c;
    --bg-sunken: #1d2635;
    --bg-hover: #243043;
    --fg: #e7ebf2;
    --fg-muted: #a4afc0;
    --fg-on-brand: #0e1724;
    --border: #2b3546;
    --border-strong: #435069;
    --focus: #8fb4ec;

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow-md: 0 2px 10px rgba(0, 0, 0, 0.45);
    --shadow-lg: 0 14px 36px rgba(0, 0, 0, 0.55);
    --scrim: rgba(0, 0, 0, 0.62);
    --glass: rgba(23, 31, 44, 0.66);
    --glass-line: rgba(255, 255, 255, 0.08);
    --shine: rgba(255, 255, 255, 0.07);
    --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.3), 0 8px 24px -14px rgba(0, 0, 0, 0.6);
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --brand: #a9c3e6;
  --brand-strong: #cddcf2;
  --brand-soft: #1a2a40;
  --brand-fg: #0e1724;
  --accent: #e3a0b0;
  --accent-soft: #3a1f28;

  --danger: #f0a097;
  --danger-soft: #3b201d;
  --warn: #e5bb73;
  --warn-soft: #382b14;
  --ok: #8fcfaa;
  --ok-soft: #173026;
  --info: #a9c3e6;
  --info-soft: #1a2a40;

  --bg: #0f1520;
  --bg-elevated: #171f2c;
  --bg-sunken: #1d2635;
  --bg-hover: #243043;
  --fg: #e7ebf2;
  --fg-muted: #a4afc0;
  --fg-on-brand: #0e1724;
  --border: #2b3546;
  --border-strong: #435069;
  --focus: #8fb4ec;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-md: 0 2px 10px rgba(0, 0, 0, 0.45);
  --shadow-lg: 0 14px 36px rgba(0, 0, 0, 0.55);
  --scrim: rgba(0, 0, 0, 0.62);
  --glass: rgba(23, 31, 44, 0.66);
  --glass-line: rgba(255, 255, 255, 0.08);
  --shine: rgba(255, 255, 255, 0.07);
  --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.3), 0 8px 24px -14px rgba(0, 0, 0, 0.6);
}

/* ----------------------------------------------------------- 2. reset + base */
*,
*::before,
*::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

/* `hidden` must win over any component's own `display`: a `.button-row` or a
   `.notice` marked hidden was still shown, because an author `display: flex`
   beats the browser's default `[hidden] { display: none }`. */
[hidden] { display: none !important; }

body {
  margin: 0;
  /* The page must never scroll sideways; wide content scrolls inside its own
     box (see .table-scroll). `clip`, not `hidden`: `overflow-x: hidden` turns
     the body into a scroll container, and that silently breaks every
     `position: sticky` descendant -- the review screen's player stopped
     sticking. `clip` cuts the overflow without creating a scrollport. */
  overflow-x: clip;
  font-family: var(--font);
  font-size: var(--t-base);
  line-height: var(--lh-normal);
  background: var(--bg);
  color: var(--fg);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

::selection { background: color-mix(in srgb, var(--brand) 22%, transparent); }

h1, h2, h3 { line-height: var(--lh-tight); text-wrap: balance; }

/* Headings are Roboto set heavier and a touch tighter than the text: at 22px
   and up its default spacing looks loose. */
h1, h2 {
  font-family: var(--font-display);
  font-weight: 650;
  letter-spacing: -0.015em;
  color: var(--fg);
}

h2 {
  margin: 0 0 var(--sp-4);
  font-size: var(--t-xl);
}

h3 { margin: 0 0 var(--sp-1); font-size: var(--t-base); font-weight: 650; }

p { margin: 0 0 var(--sp-3); }
p:last-child { margin-bottom: 0; }

a { color: var(--brand); text-underline-offset: 2px; }
a:hover { color: var(--brand-strong); }

/* One visible focus ring everywhere. :focus-visible keeps it off mouse clicks
   but always on for keyboard users. */
:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

code, kbd, samp {
  font-family: var(--font-mono);
  font-size: 0.92em;
}

code {
  background: var(--bg-sunken);
  border-radius: var(--r-sm);
  padding: 1px var(--sp-1);
  overflow-wrap: anywhere;
}

kbd {
  display: inline-block;
  background: var(--bg-sunken);
  border: 1px solid var(--border);
  border-bottom-width: 2px;
  border-radius: var(--r-sm);
  padding: 0 var(--sp-1);
  font-size: var(--t-xs);
  line-height: 1.5;
}

img, audio, video { max-width: 100%; }
audio { width: 100%; }

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

/* Moving between pages (D031). The browser cross-fades the old page into the
   new one instead of blanking the screen; the sidebar or the guest header,
   the same on both pages, is swapped at once rather than faded, so it reads as
   standing still. Same-origin page loads only. A browser without
   cross-document view transitions simply loads the page, as before. */
@view-transition { navigation: auto; }
@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
}
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 160ms;
  animation-timing-function: var(--ease);
}
/* Two names, not one: after signing in, the guest header must not be morphed
   into the sidebar. */
::view-transition-old(app-chrome),
::view-transition-old(guest-chrome) { display: none; }
::view-transition-new(app-chrome),
::view-transition-new(guest-chrome) { animation: none; }
.site-header { view-transition-name: guest-chrome; }
/* Wide screens only: on a phone the sidebar holds the fixed tab bar, and that
   is left to fade with the page. */
@media (min-width: 901px) {
  .app-sidebar { view-transition-name: app-chrome; }
}

/* --------------------------------------------------------------- 3. layout */

/* Keyboard users land here first and can jump past the whole navigation. */
.skip-link {
  position: absolute;
  left: var(--sp-3);
  top: calc(-1 * var(--sp-8));
  z-index: 100;
  background: var(--bg-elevated);
  color: var(--brand);
  border: 2px solid var(--focus);
  border-radius: var(--r-sm);
  padding: var(--sp-2) var(--sp-4);
  font-weight: 650;
  text-decoration: none;
  transition: top var(--dur-fast) var(--ease);
}
.skip-link:focus { top: var(--sp-3); }

/* One compact bar that stays on screen: brand, the navigation for the
   signed-in role, and the account controls. It used to be two rows with a
   long system name and a tagline that the footer already says; on a laptop
   that cost a sixth of the screen before any content. */
.site-header {
  background: var(--bg-elevated);
  color: var(--fg);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 50;
}

.header-inner {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 0 var(--sp-5);
  min-height: var(--header-h);
  display: flex;
  align-items: center;
  gap: var(--sp-2) var(--sp-6);
}

/* The mark and the wordmark (D031). The mark is a speech bubble -- the product
   listens to students speak -- holding three bars that climb like the levels
   it reports, A2, B1, B2. It is drawn inline (_icons.html, logo()) and coloured
   from the tokens, so it turns over with the theme; favicon.svg is the same
   drawing in fixed colours. The name is two-tone: the product in ink, the
   framework it measures against in claret. */
.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  color: var(--fg);
  text-decoration: none;
  white-space: nowrap;
}
.brand:hover { color: var(--fg); }
.brand-mark {
  width: 34px;
  height: 34px;
  flex: none;
  display: block;
  transition: transform var(--dur-slow) var(--ease-out);
}
/* Prefixed class names: a bare `.bar` already styles the progress bars, and
   SVG2 lets CSS set a rect's height, so it squashed the mark's bars to dots. */
.logo-bubble { fill: var(--brand); }
.logo-bar { fill: var(--brand-fg); }
.logo-bar-1 { opacity: 0.6; }
.logo-bar-2 { opacity: 0.8; }
.brand:hover .brand-mark { transform: rotate(-4deg) scale(1.04); }
.brand-name {
  font-family: var(--font-display);
  font-size: 1.06rem;
  font-weight: 700;
  letter-spacing: -0.012em;
}
.brand-name .brand-cefr { color: var(--accent); }
.brand-sub {
  font-size: var(--t-xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
  border-left: 1px solid var(--border);
  padding-left: var(--sp-3);
}

.header-actions {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--t-sm);
}

/* Small outlined control on the header. */
.header-button {
  appearance: none;
  margin: 0;
  background: var(--bg-elevated);
  color: var(--brand);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  padding: 5px var(--sp-3);
  font: inherit;
  font-size: var(--t-sm);
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.header-button .icon { width: 16px; height: 16px; }
.header-button:hover { background: var(--bg-hover); color: var(--brand-strong); }
.header-button.primary {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--brand-fg);
}
.header-button.primary:hover { background: var(--brand-strong); color: var(--brand-fg); }

/* The account menu: who is signed in, terms, theme, sign out. A <details>
   element, so it opens with Enter or a click and needs no script to work;
   common.js only closes it on a click outside or Escape. */
.account-menu { position: relative; }
.account-menu > summary {
  list-style: none;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  max-width: 240px;
}
.account-menu > summary::-webkit-details-marker { display: none; }
.account-menu > summary::after { content: "▾"; font-size: var(--t-xs); opacity: 0.7; }
.who-name { overflow: hidden; text-overflow: ellipsis; }
.who-role {
  font-weight: 500;
  font-size: var(--t-xs);
  color: var(--fg-muted);
  border-left: 1px solid var(--border);
  padding-left: var(--sp-2);
}
.account-panel {
  position: absolute;
  right: 0;
  top: calc(100% + 8px);
  z-index: 70;
  min-width: 240px;
  display: grid;
  gap: 2px;
  padding: var(--sp-2);
  background: var(--bg-elevated);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
}
/* It grows out of the button that opened it (D031): from below in the sidebar,
   where it opens upwards, from the top right on a phone. */
.account-menu[open] > .account-panel {
  animation: pop-in var(--dur-med) var(--ease-out);
  transform-origin: top right;
}
.app-sidebar .account-menu[open] > .account-panel { transform-origin: bottom center; }
@keyframes pop-in { from { opacity: 0; transform: scale(0.96); } }
.account-head {
  margin: 0 0 var(--sp-1);
  padding: var(--sp-2) var(--sp-3) var(--sp-3);
  font-size: var(--t-sm);
  color: var(--fg-muted);
  border-bottom: 1px solid var(--border);
}
.account-head b { color: var(--fg); }
.account-panel a,
.account-panel button {
  display: block;
  width: 100%;
  margin: 0;
  padding: var(--sp-2) var(--sp-3);
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  color: var(--fg);
  font: inherit;
  font-size: var(--t-md);
  font-weight: 500;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.account-panel a:hover,
.account-panel button:hover { background: var(--bg-hover); color: var(--fg); }
.account-panel a[aria-current="page"] { font-weight: 700; color: var(--accent); }

.site-nav {
  display: flex;
  gap: var(--sp-1);
  overflow-x: auto;
  scrollbar-width: none;
  align-self: stretch;
}
.site-nav::-webkit-scrollbar { display: none; }

.site-nav a {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  color: var(--fg-muted);
  text-decoration: none;
  font-size: var(--t-md);
  font-weight: 600;
  padding: 0 var(--sp-3);
  border-bottom: 2px solid transparent;
  border-top: 2px solid transparent;
  white-space: nowrap;
}
.site-nav a:hover { color: var(--brand); border-bottom-color: var(--border-strong); }
.site-nav a[aria-current="page"] {
  color: var(--fg);
  border-bottom-color: var(--accent);
}
.nav-count {
  min-width: 20px;
  padding: 0 6px;
  border-radius: var(--r-pill);
  background: var(--accent);
  color: #fff;
  font-size: var(--t-xs);
  line-height: 20px;
  text-align: center;
}

main {
  max-width: var(--page-max);
  margin: var(--sp-6) auto var(--sp-7);
  padding: 0 var(--sp-5);
}

.site-footer {
  border-top: 1px solid var(--border);
  background: var(--bg-elevated);
  color: var(--fg-muted);
  font-size: var(--t-sm);
}
.site-footer .footer-inner {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: var(--sp-5);
}
.site-footer p { margin: 0 0 var(--sp-1); }

/* Page heading block above the cards. */
.page-head { margin-bottom: var(--sp-5); }
.page-head h1 {
  margin: 0;
  font-size: var(--t-2xl);
  font-weight: 700;
  letter-spacing: -0.02em;
}
.page-head .lede {
  margin: var(--sp-2) 0 0;
  color: var(--fg-muted);
  font-size: var(--t-md);
  max-width: 68ch;
}

.two-col {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  gap: var(--sp-5);
  align-items: start;
}
@media (max-width: 900px) {
  .two-col { grid-template-columns: minmax(0, 1fr); }
}

.stack > * + * { margin-top: var(--sp-4); }

/* ----------------------------------------------------------- 4. components */

.card {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-sm);
  padding: var(--sp-5);
  margin-bottom: var(--sp-5);
}
.card:last-child { margin-bottom: 0; }
.card > :last-child { margin-bottom: 0; }

.card-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-2) var(--sp-4);
  margin-bottom: var(--sp-4);
}
.card-head h2 { margin: 0; }

/* A numbered step heading on the submit screen. */
.step-number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  margin-right: var(--sp-2);
  border-radius: var(--r-pill);
  background: var(--brand-soft);
  color: var(--brand);
  font-size: var(--t-sm);
  font-weight: 700;
  flex: none;
}

/* --- form fields ------------------------------------------------------- */
label {
  display: block;
  font-size: var(--t-sm);
  font-weight: 650;
  margin: var(--sp-4) 0 var(--sp-2);
}
label:first-child { margin-top: 0; }

input, select, textarea {
  width: 100%;
  max-width: 100%;
  padding: var(--sp-3);
  background: var(--bg-elevated);
  color: var(--fg);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  font-family: inherit;
  font-size: var(--t-md);
  transition: border-color var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}
input:hover, select:hover, textarea:hover { border-color: var(--brand); }
input:focus, select:focus, textarea:focus {
  border-color: var(--focus);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--focus) 24%, transparent);
  outline: none;
}
input[type="file"] { padding: var(--sp-2); }
textarea { resize: vertical; min-height: 90px; }

.field-hint {
  margin: var(--sp-2) 0 0;
  color: var(--fg-muted);
  font-size: var(--t-sm);
}

/* --- buttons ----------------------------------------------------------- */
button, .button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  background: var(--brand);
  color: var(--brand-fg);
  border: 1px solid transparent;
  border-radius: var(--r-md);
  padding: var(--sp-3) var(--sp-5);
  font-family: inherit;
  font-size: var(--t-md);
  font-weight: 650;
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
  margin-top: var(--sp-4);
  transition: background-color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease),
              opacity var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}
button:hover, .button:hover { background: var(--brand-strong); color: var(--brand-fg); }
button:active, .button:active { transform: translateY(1px); }
button:disabled, .button[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
  transform: none;
}

button.secondary, .button.secondary {
  background: var(--bg-sunken);
  color: var(--brand);
  border-color: var(--border-strong);
}
button.secondary:hover, .button.secondary:hover {
  background: var(--bg-hover);
  color: var(--brand-strong);
}

button.danger { background: var(--danger); color: #fff; }

/* Một nút trông như liên kết, dùng bên trong ô bảng. Không phải nút đặc: một
   hàng bảng có sáu ô dữ liệu mà ô thứ bảy là một khối màu đỏ thì mắt đọc hàng
   đó thành "nút xoá", chứ không còn đọc thành một bài đã nộp. Vẫn là <button>
   vì nó thực hiện một hành động chứ không dẫn đi đâu — bàn phím và trình đọc
   màn hình phải nghe đúng như thế. */
.link-button {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
  color: var(--brand);
}
.link-button.danger { color: var(--danger); background: none; }
/* Không cho nhãn một từ bị bẻ thành ba dòng trong ô bảng hẹp. */
td .link-button { white-space: nowrap; }
.link-button:hover { text-decoration-thickness: 2px; }
.link-button[disabled] { opacity: 0.5; cursor: progress; }
:root[data-theme="dark"] button.danger { color: var(--brand-fg); }

button.small, .button.small { padding: var(--sp-1) var(--sp-3); font-size: var(--t-sm); margin-top: 0; }

.button-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  align-items: center;
  margin-top: var(--sp-4);
}
.button-row > button, .button-row > .button { margin-top: 0; }

/* --- badges + pills ---------------------------------------------------- */
.badge {
  display: inline-block;
  padding: var(--sp-1) var(--sp-3);
  border-radius: var(--r-pill);
  font-size: var(--t-xs);
  font-weight: 700;
  line-height: 1.4;
  white-space: nowrap;
  background: var(--bg-sunken);
  color: var(--fg-muted);
}
.COMPLETED { background: var(--ok-soft); color: var(--ok); }
.REVIEW_REQUIRED { background: var(--warn-soft); color: var(--warn); }
.REJECTED, .FAILED { background: var(--danger-soft); color: var(--danger); }
.QUEUED, .RUNNING { background: var(--brand-soft); color: var(--brand); }

/* --- notices ----------------------------------------------------------- */
.notice {
  background: var(--warn-soft);
  border: 1px solid color-mix(in srgb, var(--warn) 30%, transparent);
  border-left: 4px solid var(--warn);
  padding: var(--sp-3) var(--sp-4);
  font-size: var(--t-md);
  border-radius: 0 var(--r-md) var(--r-md) 0;
  margin-bottom: var(--sp-5);
}
.notice:last-child { margin-bottom: 0; }
.notice > :last-child { margin-bottom: 0; }
.notice-danger {
  background: var(--danger-soft);
  border-color: color-mix(in srgb, var(--danger) 30%, transparent);
  border-left-color: var(--danger);
}
.notice-info {
  background: var(--info-soft);
  border-color: color-mix(in srgb, var(--info) 30%, transparent);
  border-left-color: var(--info);
}
.notice-warn {
  background: var(--warn-soft);
  border-color: color-mix(in srgb, var(--warn) 30%, transparent);
  border-left-color: var(--warn);
}
.notice-ok {
  background: var(--ok-soft);
  border-color: color-mix(in srgb, var(--ok) 30%, transparent);
  border-left-color: var(--ok);
}

/* --- tables ------------------------------------------------------------ */
/* A table is wrapped by common.js in .table-scroll so that a wide table
   scrolls inside its own box and never pushes the page sideways. */
.table-scroll {
  /* Positioned, so a visually hidden header label (position: absolute) is
     placed and clipped inside the box instead of widening the page. */
  position: relative;
  overflow-x: auto;
  margin: var(--sp-3) 0;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  -webkit-overflow-scrolling: touch;
}
.table-scroll > table { margin: 0; border: 0; }

table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--t-md);
}
th, td {
  padding: var(--sp-3);
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--border);
}
th {
  background: var(--bg-sunken);
  font-size: var(--t-sm);
  font-weight: 650;
  color: var(--fg-muted);
  white-space: nowrap;
}
tbody tr:last-child td, tr:last-child td { border-bottom: 0; }
tr:hover > td { background: var(--bg-sunken); }
/* A key/value table (provenance, audio quality) keeps its label column narrow. */
table.kv th { width: 40%; white-space: normal; }

/* --- empty states ------------------------------------------------------ */
.empty-state {
  text-align: center;
  padding: var(--sp-6) var(--sp-4);
  border: 1px dashed var(--border-strong);
  border-radius: var(--r-md);
  background: var(--bg-sunken);
}
.empty-state .empty-mark {
  font-size: var(--t-2xl);
  line-height: 1;
  color: var(--brand);
}
/* h2, not h3. An empty state is usually the FIRST thing under the page's h1,
   and an h3 there leaves a hole in the heading outline: a screen-reader user
   moving by headings hears level 1 then level 3 and has to wonder what they
   skipped. The visual size stays where it was -- the level is about structure,
   not about how big the text looks. */
.empty-state h2 { margin: var(--sp-3) 0 var(--sp-2); font-size: var(--t-base); }
.empty-state p { color: var(--fg-muted); font-size: var(--t-md); max-width: 52ch; margin: 0 auto; }
.empty-state .button-row { justify-content: center; }

/* --- skeleton loading -------------------------------------------------- */
/* Shown INSTEAD of the words "loading...": the shape of the coming content is
   already visible, so the page does not jump when the data lands. */
.skeleton {
  --skeleton-base: var(--bg-sunken);
  --skeleton-shine: var(--bg-hover);
  background: linear-gradient(
    90deg,
    var(--skeleton-base) 25%,
    var(--skeleton-shine) 37%,
    var(--skeleton-base) 63%
  );
  background-size: 400% 100%;
  animation: skeleton-sweep 1.4s var(--ease) infinite;
  border-radius: var(--r-sm);
  height: 1em;
  margin-bottom: var(--sp-3);
}
.skeleton:last-child { margin-bottom: 0; }
.skeleton-title { height: 1.5em; width: 40%; }
.skeleton-line { width: 100%; }
.skeleton-line.short { width: 55%; }
.skeleton-line.medium { width: 78%; }
.skeleton-block { height: 96px; }
.skeleton-row { height: 2.4em; }

@keyframes skeleton-sweep {
  0% { background-position: 100% 50%; }
  100% { background-position: 0 50%; }
}

/* --- toasts ------------------------------------------------------------ */
.toast-region {
  position: fixed;
  right: var(--sp-4);
  bottom: var(--sp-4);
  z-index: 200;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  width: min(380px, calc(100vw - var(--sp-6)));
  pointer-events: none;
}
.toast {
  pointer-events: auto;
  display: flex;
  gap: var(--sp-3);
  align-items: flex-start;
  background: var(--bg-elevated);
  color: var(--fg);
  border: 1px solid var(--border);
  border-left: 4px solid var(--brand);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lg);
  padding: var(--sp-3) var(--sp-4);
  font-size: var(--t-md);
  animation: toast-in var(--dur-slow) var(--ease-out);
}
.toast.toast-ok { border-left-color: var(--ok); }
.toast.toast-error { border-left-color: var(--danger); }
.toast.toast-warn { border-left-color: var(--warn); }
.toast .toast-body { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.toast .toast-close {
  margin: 0;
  padding: 0 var(--sp-1);
  background: none;
  border: 0;
  color: var(--fg-muted);
  font-size: var(--t-lg);
  line-height: 1;
  cursor: pointer;
  flex: none;
}
.toast .toast-close:hover { background: none; color: var(--fg); }
.toast.leaving { animation: toast-out var(--dur-slow) var(--ease) forwards; }

@keyframes toast-in {
  from { opacity: 0; transform: translateY(var(--sp-4)); }
  to { opacity: 1; transform: none; }
}
@keyframes toast-out {
  to { opacity: 0; transform: translateY(var(--sp-3)); }
}

/* --- inline messages --------------------------------------------------- */
.muted { color: var(--fg-muted); font-size: var(--t-sm); }
.error { color: var(--danger); font-weight: 600; }
.success { color: var(--ok); font-weight: 600; }

/* --------------------------------------------------------------- 5. screens */

/* --- submit: task picker ------------------------------------------------ */
.task-grid { display: grid; gap: var(--sp-3); }

/* A real radio input sits behind each card, so the group is keyboard
   navigable with the arrow keys and announced as a radio group. */
.task {
  position: relative;
  display: block;
  border: 2px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-4);
  margin: 0;
  background: var(--bg-elevated);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}
.task:hover { border-color: var(--brand); box-shadow: var(--shadow-sm); }
.task input[type="radio"] {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
  margin: 0;
}
.task:has(input:focus-visible) { outline: 3px solid var(--focus); outline-offset: 2px; }
.task.chosen {
  border-color: var(--brand);
  background: var(--brand-soft);
  box-shadow: var(--shadow-sm);
}
.task h3 { color: var(--brand); font-size: var(--t-base); margin: 0 0 var(--sp-1); }
.task.chosen h3 { padding-right: 76px; }
.task p { font-size: var(--t-md); margin-bottom: var(--sp-2); }
/* The full prompt is repeated above the record button; here two lines are
   enough to choose by. */
.task .task-prompt {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  color: var(--fg-muted);
}
.task.chosen .task-prompt { color: var(--fg); }

/* The chosen prompt, above the record button. */
.prompt-card {
  margin: 0 0 var(--sp-4);
  padding: var(--sp-4);
  border-left: 4px solid var(--brand);
  border-radius: var(--r-sm);
  background: var(--bg-sunken);
}
.prompt-card:empty { display: none; }
.prompt-card .prompt-text { margin: 0 0 var(--sp-2); font-size: var(--t-lg); line-height: 1.55; font-weight: 600; }
.prompt-card .muted { margin: 0; font-size: var(--t-sm); }
.task .task-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-4);
  color: var(--fg-muted);
  font-size: var(--t-sm);
  margin: 0;
}
/* Ghi chú riêng của đề dài. Dùng màu cảnh báo chứ không phải màu thương hiệu:
   đây là điều kiện kèm theo ("điểm tạm, luôn qua giảng viên"), không phải một
   tính năng để mời gọi. */
.task .task-note {
  margin: var(--sp-2) 0 0;
  padding: var(--sp-2) var(--sp-3);
  border-left: 3px solid var(--warn);
  background: var(--warn-soft);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  font-size: var(--t-sm);
  color: var(--fg);
}

.task .chosen-mark {
  position: absolute;
  top: var(--sp-3);
  right: var(--sp-3);
  color: var(--brand);
  font-weight: 700;
  font-size: var(--t-sm);
  opacity: 0;
}
.task.chosen .chosen-mark { opacity: 1; }

/* --- recorder: drop zone ----------------------------------------------- */
.dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  border: 2px dashed var(--border-strong);
  border-radius: var(--r-md);
  background: var(--bg-sunken);
  padding: var(--sp-5) var(--sp-4);
  text-align: center;
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease);
}
.dropzone:hover { border-color: var(--brand); }
.dropzone.dragging {
  border-color: var(--brand);
  border-style: solid;
  background: var(--brand-soft);
  box-shadow: inset 0 0 0 3px color-mix(in srgb, var(--brand) 25%, transparent);
}
.dropzone.disabled { opacity: 0.55; cursor: not-allowed; }
.dropzone .drop-mark { font-size: var(--t-xl); line-height: 1; color: var(--brand); }
.dropzone .drop-title { font-weight: 650; margin: var(--sp-2) 0 var(--sp-1); }
.dropzone input[type="file"] {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}
.dropzone-wrap { position: relative; }
.dropzone-wrap:has(input:focus-visible) .dropzone {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}

/* --- recorder: clock, level meter, duration bar -------------------------- */
.rec-clock {
  font-variant-numeric: tabular-nums;
  font-size: var(--t-2xl);
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--fg);
  min-width: 4ch;
}
.meter {
  display: block;
  height: 10px;
  background: var(--bg-hover);
  border-radius: var(--r-pill);
  overflow: hidden;
}
.meter-bar {
  display: block;
  height: 100%;
  width: 0;
  background: var(--ok);
  transition: width 80ms linear;
}

/* The duration bar: under the minimum, inside the window, over the maximum.
   Colour alone never carries the message -- the hint under it says the same
   thing in words. */
.duration-guide { margin-top: var(--sp-3); }
.duration-track {
  position: relative;
  height: 8px;
  border-radius: var(--r-pill);
  background: var(--bg-hover);
  overflow: hidden;
}
.duration-fill {
  display: block;
  height: 100%;
  width: 0;
  background: var(--fg-muted);
  transition: width 200ms linear;
}
.duration-guide.in-range .duration-fill { background: var(--ok); }
.duration-guide.over .duration-fill { background: var(--danger); }
.duration-window {
  position: absolute;
  top: 0;
  bottom: 0;
  border-left: 2px solid var(--border-strong);
  border-right: 2px solid var(--border-strong);
}

/* --- progress screen ---------------------------------------------------- */
.progress-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-bottom: var(--sp-4);
}
.elapsed {
  font-variant-numeric: tabular-nums;
  font-size: var(--t-md);
  color: var(--fg-muted);
}

.progress-bar {
  height: 8px;
  border-radius: var(--r-pill);
  background: var(--bg-hover);
  overflow: hidden;
  margin-bottom: var(--sp-5);
}
.progress-bar > span {
  display: block;
  height: 100%;
  width: 0;
  background: var(--brand);
  border-radius: var(--r-pill);
  transition: width var(--dur-slow) var(--ease);
}
.progress-bar.failed > span { background: var(--danger); }

.steps { list-style: none; padding: 0; margin: 0; }
.steps li {
  position: relative;
  padding: 0 0 var(--sp-5) var(--sp-6);
  color: var(--fg-muted);
  font-size: var(--t-md);
}
/* The connector between the dots. */
.steps li::after {
  content: "";
  position: absolute;
  left: 11px;
  top: 22px;
  bottom: 0;
  width: 2px;
  background: var(--border);
}
.steps li:last-child { padding-bottom: 0; }
.steps li:last-child::after { display: none; }
.steps li::before {
  content: "";
  position: absolute;
  left: 4px;
  top: 5px;
  width: 16px;
  height: 16px;
  border-radius: var(--r-pill);
  border: 2px solid var(--border-strong);
  background: var(--bg-elevated);
  box-sizing: border-box;
}
.steps li .step-label { font-weight: 600; }
.steps li .step-note { display: block; font-size: var(--t-sm); }

.steps li.done { color: var(--fg); }
.steps li.done::before { background: var(--ok); border-color: var(--ok); }
.steps li.done::after { background: var(--ok); }

.steps li.active { color: var(--fg); }
.steps li.active .step-label { color: var(--brand); }
.steps li.active::before {
  border-color: var(--brand);
  background: var(--bg-elevated);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--brand) 22%, transparent);
  animation: step-pulse 1.6s var(--ease) infinite;
}
@keyframes step-pulse {
  50% { box-shadow: 0 0 0 8px color-mix(in srgb, var(--brand) 8%, transparent); }
}

.steps li.failed { color: var(--danger); }
.steps li.failed::before { background: var(--danger); border-color: var(--danger); }

/* --- report: the headline score ---------------------------------------- */
.score-panel {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-5);
  padding: var(--sp-5);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--brand-soft);
}
.score-figure { text-align: center; flex: none; }
.score {
  display: block;
  font-size: var(--t-3xl);
  font-weight: 750;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--brand);
  font-variant-numeric: tabular-nums;
}
.score-scale { display: block; margin-top: var(--sp-1); color: var(--fg-muted); font-size: var(--t-sm); }
.score-meta { flex: 1 1 240px; min-width: 0; }
.score-band {
  display: inline-block;
  font-size: var(--t-xl);
  font-weight: 700;
  color: var(--brand);
}
.score-caption { margin: var(--sp-2) 0 0; font-size: var(--t-md); color: var(--fg-muted); }

/* How much of the input could actually be measured. NOT a confidence. */
.coverage {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-top: var(--sp-3);
  font-size: var(--t-sm);
}
.coverage-track {
  flex: 1 1 120px;
  height: 8px;
  border-radius: var(--r-pill);
  background: var(--bg-hover);
  overflow: hidden;
}
.coverage-fill { display: block; height: 100%; background: var(--brand); border-radius: var(--r-pill); }

/* --- report: evidence cards -------------------------------------------- */
.evidence-grid { display: grid; gap: var(--sp-3); grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }

.evidence {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  border: 1px solid var(--border);
  border-left: 3px solid var(--brand);
  border-radius: var(--r-md);
  background: var(--bg-elevated);
  padding: var(--sp-4);
  font-size: var(--t-md);
  text-align: left;
  margin: 0;
}
/* An evidence card that seeks the player is a real <button>, so it is
   reachable by keyboard and announced as an action. */
button.evidence {
  width: 100%;
  font-family: inherit;
  color: var(--fg);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
button.evidence:hover {
  background: var(--bg-elevated);
  border-color: var(--brand);
  box-shadow: var(--shadow-md);
  color: var(--fg);
}
.evidence .evidence-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
}
.evidence .evidence-kind {
  font-size: var(--t-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--brand);
}
.evidence .evidence-time {
  font-variant-numeric: tabular-nums;
  font-size: var(--t-xs);
  font-weight: 650;
  color: var(--fg-muted);
  background: var(--bg-sunken);
  border-radius: var(--r-pill);
  padding: 2px var(--sp-2);
}
.evidence .evidence-note { margin: 0; }
.evidence .evidence-snippet {
  margin: 0;
  color: var(--fg-muted);
  font-size: var(--t-sm);
  font-style: italic;
}
.evidence .evidence-action { color: var(--brand); font-size: var(--t-sm); font-weight: 650; }
.evidence b { color: var(--brand); }

/* The older inline seek button, still used by the review screen. */
.evidence button.seek {
  align-self: flex-start;
  margin: 0;
  padding: var(--sp-1) var(--sp-3);
  font-size: var(--t-sm);
  background: var(--bg-sunken);
  color: var(--brand);
  border: 1px solid var(--border);
}
.evidence button.seek:hover { background: var(--bg-hover); color: var(--brand-strong); }

/* --- transcript --------------------------------------------------------- */
.transcript {
  max-height: 360px;
  overflow-y: auto;
  padding: var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--bg-sunken);
  font-size: var(--t-base);
  line-height: 2;
}
.transcript .word {
  cursor: pointer;
  padding: 2px 3px;
  border-radius: var(--r-sm);
  transition: background var(--dur-fast) var(--ease);
}
.transcript .word:hover { background: var(--bg-hover); }
/* Class name kept as "ngo": the review screen writes it too. */
.transcript .word.ngo, .transcript .word.uncertain {
  background: color-mix(in srgb, var(--warn) 22%, transparent);
  border-bottom: 2px dotted var(--warn);
}
/* The word being spoken right now, updated from the audio timeupdate event. */
.transcript .word.speaking {
  background: var(--brand);
  color: var(--brand-fg);
  font-weight: 650;
}

.transcript-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2) var(--sp-3);
  margin: var(--sp-3) 0 var(--sp-2);
  font-size: var(--t-sm);
  color: var(--fg-muted);
}
.legend { display: inline-flex; align-items: center; gap: var(--sp-2); }
.legend .swatch {
  width: 14px;
  height: 14px;
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--warn) 22%, transparent);
  border: 1px solid var(--warn);
  flex: none;
}

.shortcuts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-4);
  margin: var(--sp-4) 0 0;
  padding: var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--bg-sunken);
  font-size: var(--t-sm);
  color: var(--fg-muted);
  list-style: none;
}
.shortcuts li { display: inline-flex; align-items: center; gap: var(--sp-2); }

/* --- review screen ------------------------------------------------------ */
/* The player follows the teacher down the page: SDD R-10 targets under three
   minutes per review, and scrolling back up to press play burns that budget. */
.sticky-player {
  position: sticky;
  /* Below the sticky site header, not under it. */
  top: var(--header-h);
  z-index: 20;
  margin: calc(-1 * var(--sp-5)) calc(-1 * var(--sp-5)) var(--sp-4);
  padding: var(--sp-4) var(--sp-5);
  background: var(--bg-elevated);
  border-bottom: 1px solid var(--border);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
}
.sticky-player h2 { margin-bottom: var(--sp-3); }

/* --- tabs (admin) ------------------------------------------------------- */
.tabs {
  display: flex;
  gap: var(--sp-1);
  overflow-x: auto;
  border-bottom: 2px solid var(--border);
  margin-bottom: var(--sp-5);
  scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }
/* The underline of the chosen tab is drawn by ::after, not a border, so that it
   can grow out from the middle when the tab is chosen (D031). */
.tabs button {
  position: relative;
  margin: 0 0 -2px;
  background: none;
  color: var(--fg-muted);
  border: 0;
  border-radius: var(--r-sm) var(--r-sm) 0 0;
  padding: var(--sp-3) var(--sp-4) calc(var(--sp-3) + 3px);
  font-size: var(--t-md);
  font-weight: 650;
  white-space: nowrap;
}
.tabs button::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 3px;
  border-radius: 3px 3px 0 0;
  background: var(--brand);
  transform: scaleX(0);
  transition: transform var(--dur-slow) var(--ease-out);
}
.tabs button:hover { background: var(--bg-sunken); color: var(--brand); }
.tabs button.chosen { color: var(--brand); background: none; }
.tabs button.chosen::after { transform: scaleX(1); }
/* A panel that comes into view fades in rather than snapping. Opacity only:
   a transform here would turn the panel into the containing block of any
   fixed-position bar inside it for the length of the animation. */
[role="tabpanel"]:not([hidden]) { animation: fade-in var(--dur-med) var(--ease); }
@keyframes fade-in { from { opacity: 0; } }

.row-active { background: var(--brand-soft); }
.row-broken { background: var(--danger-soft); }
.row-active:hover > td, .row-broken:hover > td { background: transparent; }

/* --- filter bar (review queue) ------------------------------------------ */
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
  align-items: flex-end;
  padding: var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--bg-sunken);
  margin-bottom: var(--sp-4);
}
.filter-bar .filter { flex: 1 1 180px; min-width: 0; }
.filter-bar label { margin: 0 0 var(--sp-2); }
.filter-bar .result-count {
  flex: 0 0 auto;
  font-size: var(--t-sm);
  color: var(--fg-muted);
  padding-bottom: var(--sp-3);
}

/* --- auth --------------------------------------------------------------- */
.auth-shell {
  max-width: 440px;
  margin: 0 auto;
}

/* --- practice mode (D023) ----------------------------------------------
   Practice screens. No score and no band anywhere here: a mission is either
   done, not yet done, or not measurable, and each carries the place in the
   recording it rests on. State is shown by shape AND colour (tick, cross,
   dash), never by colour alone. */
.crumb { margin: 0 0 var(--sp-3); font-size: var(--t-md); }
.crumb a { text-decoration: none; }
.eyebrow {
  margin: 0 0 var(--sp-1);
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.task-tags { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin: 0 0 var(--sp-2); }

.practice-top {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  gap: var(--sp-5);
  align-items: stretch;
  margin-bottom: var(--sp-5);
}
.practice-top > .card { margin-bottom: 0; }
.practice-next { border-color: var(--brand); border-width: 2px; }
.practice-next h2 { font-size: var(--t-xl); margin: 0 0 var(--sp-2); }

.practice-today { display: grid; justify-items: center; text-align: center; gap: var(--sp-2); }
.practice-today h2 { margin: 0; justify-self: start; }
.goal-ring {
  --pct: 0;
  width: 112px;
  height: 112px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: conic-gradient(var(--ok) calc(var(--pct) * 1%), var(--bg-sunken) 0);
}
.goal-ring span {
  width: 84px;
  height: 84px;
  border-radius: 50%;
  display: flex;
  align-items: baseline;
  justify-content: center;
  padding-top: 26px;
  background: var(--bg-elevated);
  font-size: var(--t-md);
  color: var(--fg-muted);
}
.goal-ring b { font-size: var(--t-2xl); color: var(--fg); }
.goal-text, .streak { margin: 0; }
.week-dots {
  list-style: none;
  display: flex;
  gap: var(--sp-2);
  padding: 0;
  margin: var(--sp-1) 0 0;
  font-size: var(--t-xs);
  color: var(--fg-muted);
}
.week-dots li { display: grid; justify-items: center; gap: 2px; }
.week-dots .dot {
  width: 14px;
  height: 14px;
  border-radius: var(--r-sm);
  border: 1px solid var(--border-strong);
  background: var(--bg-sunken);
}
.week-dots li.on .dot { background: var(--ok); border-color: var(--ok); }

.path { display: grid; gap: var(--sp-4); }
.path-row h3 { margin: 0 0 var(--sp-2); font-size: var(--t-md); }
.path-nodes {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-3);
}
.path-node a {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-rows: auto auto;
  column-gap: var(--sp-3);
  align-items: center;
  padding: var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--bg-elevated);
  color: var(--fg);
  text-decoration: none;
  height: 100%;
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.path-node a:hover { border-color: var(--brand); background: var(--bg-hover); }
.node-dot {
  grid-row: 1 / span 2;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-weight: 700;
  border: 2px solid var(--border-strong);
  color: var(--fg-muted);
}
.node-band { font-size: var(--t-xs); font-weight: 700; color: var(--fg-muted); }
.node-title { font-size: var(--t-md); line-height: var(--lh-tight); }
.path-node.completed .node-dot { background: var(--ok); border-color: var(--ok); color: var(--bg-elevated); }
.path-node.tried .node-dot { border-color: var(--warn); color: var(--warn); }
.path-key {
  display: inline-grid;
  place-items: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 2px solid var(--border-strong);
  font-size: var(--t-xs);
  margin-left: var(--sp-3);
}
.path-key.completed { background: var(--ok); border-color: var(--ok); color: var(--bg-elevated); }
.path-key.tried { border-color: var(--warn); color: var(--warn); }

.review-list { margin: 0; padding-left: var(--sp-5); display: grid; gap: var(--sp-1); }

.mission-list { list-style: none; padding: 0; margin: 0 0 var(--sp-3); display: grid; gap: var(--sp-3); }
.mission {
  display: grid;
  grid-template-columns: 32px minmax(0, 1fr);
  gap: var(--sp-3);
  align-items: start;
  padding: var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--bg-elevated);
}
.mission-icon {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-weight: 700;
  background: var(--bg-sunken);
  color: var(--fg-muted);
}
.mission.met { border-color: var(--ok); }
.mission.met .mission-icon { background: var(--ok); color: var(--bg-elevated); }
.mission.unmet { border-color: var(--warn); }
.mission.unmet .mission-icon { background: var(--warn-soft); color: var(--warn); }
.mission-label { margin: 0; font-weight: 650; }
.mission-measure { margin: var(--sp-1) 0 0; }
.word-chips, .hits {
  list-style: none;
  padding: 0;
  margin: var(--sp-2) 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}
.word-chips li, .hit {
  padding: 2px var(--sp-3);
  border-radius: var(--r-pill);
  background: var(--brand-soft);
  color: var(--fg);
  font-size: var(--t-sm);
}
.hit { padding: 0; background: none; }
.hit:not(:has(button)) { padding: 2px var(--sp-3); background: var(--warn-soft); }
.hit-seek {
  margin: 0;
  padding: 2px var(--sp-3);
  border-radius: var(--r-pill);
  background: var(--brand-soft);
  color: var(--fg);
  font-size: var(--t-sm);
  font-weight: 500;
}
.hit-seek:hover { background: var(--bg-hover); color: var(--fg); }
.hit-time { font-family: var(--font-mono); color: var(--fg-muted); }
.hits-label { margin: var(--sp-2) 0 0; font-size: var(--t-sm); color: var(--fg-muted); }
.reference-text {
  margin: 0;
  padding: var(--sp-4);
  border-left: 4px solid var(--brand);
  background: var(--bg-sunken);
  border-radius: var(--r-sm);
  font-size: var(--t-lg);
  line-height: 1.8;
  max-width: 70ch;
}
.takeaway {
  margin: 0 0 var(--sp-5);
  padding: var(--sp-4) var(--sp-5);
  border-radius: var(--r-lg);
  background: var(--brand-soft);
  border: 1px solid var(--brand);
}
.takeaway p:last-child { margin: 0; font-size: var(--t-lg); }
.practice-banner { font-size: var(--t-base); }
.practice-day { margin: var(--sp-4) 0 0; }
.practice-rules summary { cursor: pointer; }
.practice-rules ol { margin: var(--sp-3) 0; }
#player { width: 100%; }

/* Speaking screens (submit, practice task): what to say on the left, the
   recorder on the right. The recorder card stays in view while the learner
   scrolls through a long brief or task list. */
.work-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  gap: var(--sp-5);
  align-items: start;
  margin-bottom: var(--sp-5);
}
.work-split > .card { margin-bottom: 0; }
.recorder-card { position: sticky; top: calc(var(--header-h) + var(--sp-3)); }
.recorder-card h2 { margin-top: 0; }
.reference-card { margin-bottom: var(--sp-4); }
.reference-card .reference-text { font-size: var(--t-base); line-height: 1.7; }
.tips summary { cursor: pointer; color: var(--brand); font-weight: 600; width: fit-content; }
.tips ul { margin: var(--sp-2) 0 0; }

@media (max-width: 900px) {
  .practice-top, .work-split { grid-template-columns: minmax(0, 1fr); }
  .recorder-card { position: static; }
}
@media (max-width: 640px) {
  .path-nodes { grid-template-columns: minmax(0, 1fr); }
  .hit-seek { width: auto; }
}

/* --- shared recorder (submit + practice) ----------------------------------
   One big button, a clock against the task window, a level meter. After
   stopping: listen back, then submit or record again. Upload is folded away. */
.rec { display: grid; gap: var(--sp-3); }
.rec-stage {
  display: flex;
  align-items: center;
  gap: var(--sp-4) var(--sp-5);
  flex-wrap: wrap;
}
.rec-button {
  margin: 0;
  min-height: 60px;
  padding: 0 var(--sp-6) 0 var(--sp-5);
  border-radius: var(--r-pill);
  font-size: var(--t-base);
  gap: var(--sp-3);
  box-shadow: var(--shadow-md);
  flex: none;
}
.rec-dot {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--danger);
  box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.9);
}
.rec-button.recording { background: var(--danger); color: #fff; }
.rec-button.recording .rec-dot {
  border-radius: 3px;
  background: #fff;
  box-shadow: none;
  animation: rec-pulse 1.2s var(--ease) infinite;
}
@keyframes rec-pulse { 50% { opacity: 0.45; } }
@media (prefers-reduced-motion: reduce) {
  .rec-button.recording .rec-dot { animation: none; }
}
.rec-readout { display: grid; gap: var(--sp-2); min-width: 200px; flex: 1; }
.rec-time { margin: 0; display: flex; align-items: baseline; gap: var(--sp-2); flex-wrap: wrap; }
.rec-readout .rec-clock { font-size: var(--t-2xl); line-height: 1; }
.rec-window { font-size: var(--t-sm); }
.rec-readout .duration-guide { margin-top: 0; }
/* The level meter only means something while the microphone is open. */
.rec:not([data-state="recording"]) .meter { visibility: hidden; height: 6px; }
.rec .meter { height: 6px; }
.rec-hint { margin: 0; color: var(--fg-muted); }
.rec[data-state="recording"] .rec-hint { color: var(--fg); font-weight: 600; }

/* Listen back, then decide. Takes the place of the big button. */
.rec-review {
  display: grid;
  gap: var(--sp-3);
  padding: var(--sp-4);
  border: 1px solid var(--brand);
  border-radius: var(--r-md);
  background: var(--brand-soft);
}
.rec-review audio { width: 100%; }
.rec-review .button-row { margin-top: 0; }
.rec-review .button-row > * { margin-top: 0; }
.rec-take { margin: 0; font-weight: 650; overflow-wrap: anywhere; }
.rec-verdict { margin: 0; }
.rec-verdict.warn, .rec-note { color: var(--warn); font-weight: 600; }
.rec-note { margin: 0; }
/* In the listen-back step the verdict inside the box replaces the hint. */
.rec:not([data-state="idle"]):not([data-state="recording"]) .rec-hint { display: none; }
.rec:not([data-state="idle"]):not([data-state="recording"]) .rec-stage { display: none; }
.rec[data-state="recording"] .rec-upload,
.rec[data-state="submitting"] .rec-upload,
.rec[data-state="submitted"] .rec-upload { display: none; }
.rec-upload summary { cursor: pointer; color: var(--brand); font-weight: 600; width: fit-content; }
.rec-upload .dropzone-wrap { margin-top: var(--sp-3); }

/* --- role-based shell ------------------------------------------------------ */
/* Page heading with the page's main action on the right. */
.page-head-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-3) var(--sp-5);
  flex-wrap: wrap;
}
.page-head-row > div { flex: 1 1 420px; }
.page-head-row > .button, .page-head-row > .button-row > * { margin-top: var(--sp-2); flex: none; }
.page-head-row > .button-row { margin-top: 0; }

/* --- report: a run of cards ------------------------------------------------ */
.page-tools { margin-top: var(--sp-2); }
.page-tools > * { margin-top: 0; }
.block-title { margin: var(--sp-5) 0 var(--sp-2); font-size: var(--t-base); }
.next-step {
  margin-top: var(--sp-4);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--border);
}
.next-step-line { margin: 0; }
.next-step .button-row { margin-top: var(--sp-3); }
.report .section-nav { margin-bottom: var(--sp-5); }
.report .sticky-player h2 { margin: 0 0 var(--sp-3); }
.report .sticky-player audio { width: 100%; display: block; }
/* Folded detail: the measurement tables and the provenance. */
.detail-card > summary,
.fold > summary {
  cursor: pointer;
  font-weight: 650;
  color: var(--brand);
  width: fit-content;
}
.detail-card > summary { font-size: var(--t-base); }
.detail-card[open] > summary { margin-bottom: var(--sp-2); }
.fold { margin-top: var(--sp-4); }
.fold[open] > summary { margin-bottom: var(--sp-2); }

/* --- checkbox rows: one box per choice, each with its own explanation -------- */
.check {
  margin: 0;
  font-size: var(--t-md);
  font-weight: 400;
  color: var(--fg);
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--sp-3);
  align-items: start;
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--bg-elevated);
  cursor: pointer;
}
.check:has(input:checked) { border-color: var(--brand); background: var(--brand-soft); }
.check input[type="checkbox"] {
  width: 20px;
  height: 20px;
  margin: 2px 0 0;
  padding: 0;
  accent-color: var(--brand);
}
.terms-points { display: grid; gap: var(--sp-2); }

/* --- word marks ---------------------------------------------------------------
   Heard, unsure, missed: the colours a learner reads after speaking. Shared by
   the lesson feedback (lesson.js), the class view (teach_insights.js) and the
   product page below. */
.w { padding: 1px 3px; border-radius: var(--r-sm); }
.w.hint { font-weight: 700; }
.w.heard { box-shadow: inset 0 -2px 0 color-mix(in srgb, var(--ok) 55%, transparent); }
.w.unsure { background: var(--warn-soft); box-shadow: inset 0 -2px 0 var(--warn); }
.w.missed { background: var(--bg-sunken); color: var(--fg-muted); text-decoration: underline dotted; }
.specimen-legend {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-4);
  font-size: var(--t-sm);
  color: var(--fg-muted);
}
.specimen-legend li { display: inline-flex; align-items: center; gap: var(--sp-2); }
.dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; }
.dot.heard { background: var(--ok); }
.dot.unsure { background: var(--warn); }
.dot.missed { background: var(--border-strong); }
.stage-chip {
  justify-self: start;
  font-size: var(--t-xs);
  font-weight: 650;
  color: var(--brand);
  background: var(--brand-soft);
  border-radius: var(--r-pill);
  padding: 2px var(--sp-3);
}

/* --- product page (D026, redrawn in D031) ------------------------------------
   Few words and one picture of the product. The hero is a small 3D scene: the
   three bars of the mark stand up as blocks, A2, B1, B2, and glass cards taken
   from the real screens float around them -- a passage read aloud with its
   words marked, a recording, an estimate, a teacher's tick. landing.js turns
   the scene toward the pointer (--px, --py, from -1 to 1) and tilts the cards
   marked data-tilt; with reduced motion or a touch screen nothing follows the
   pointer and the scene holds its resting angle.

   A block (.cube) is three faces of a box -- front, right side, top -- the
   only ones the resting angle and the tilt range ever show. It is shared:
   the report, the class statistics, the band page and the sign-in page draw
   the same blocks, each inside its own rotated, perspective box. */
.lp-hero {
  position: relative;
  isolation: isolate;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: var(--sp-5);
  align-items: center;
  margin-top: calc(-1 * var(--sp-6));
  padding: var(--sp-7) 0;
}
/* The ground runs the full width of the window, behind the content column. */
.lp-hero::before,
.lp-hero::after {
  content: "";
  position: absolute;
  inset: 0 calc(50% - 50vw);
  pointer-events: none;
}
.lp-hero::before {
  z-index: -2;
  background:
    radial-gradient(48% 62% at 76% 46%, color-mix(in srgb, var(--brand) 22%, transparent), transparent 72%),
    radial-gradient(30% 40% at 94% 88%, color-mix(in srgb, var(--accent) 18%, transparent), transparent 70%),
    radial-gradient(40% 50% at 6% 0%, color-mix(in srgb, var(--brand) 9%, transparent), transparent 70%),
    linear-gradient(180deg, var(--bg-elevated), var(--bg));
  border-bottom: 1px solid var(--border);
}
.lp-hero::after {
  z-index: -1;
  background-image: radial-gradient(color-mix(in srgb, var(--fg) 13%, transparent) 1px, transparent 1.6px);
  background-size: 22px 22px;
  -webkit-mask-image: radial-gradient(56% 64% at 74% 48%, #000 25%, transparent 75%);
  mask-image: radial-gradient(56% 64% at 74% 48%, #000 25%, transparent 75%);
}

.lp-kicker {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  margin: 0 0 var(--sp-5);
  padding: 4px var(--sp-3) 4px 5px;
  border: 1px solid var(--glass-line);
  border-radius: var(--r-pill);
  background: var(--glass);
  box-shadow: var(--shadow-sm);
  font-size: var(--t-sm);
  font-weight: 600;
  color: var(--fg-muted);
}
.lp-kicker .brand-mark { width: 24px; height: 24px; }
.lp-title {
  margin: 0 0 var(--sp-4);
  font-size: clamp(2rem, 3.6vw, 2.9rem);
  font-weight: 800;
  line-height: 1.12;
  letter-spacing: -0.04em;
}
.lp-line { display: block; }
.lp-grad { color: var(--accent); }
.lp-lede { max-width: 44ch; margin: 0; font-size: var(--t-lg); color: var(--fg-muted); }
.lp-actions { margin-top: var(--sp-5); }
.lp-actions .button { padding: 14px 24px; font-size: var(--t-base); }
.lp-facts {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-5);
  margin: var(--sp-5) 0 0;
  padding: 0;
  font-size: var(--t-sm);
  color: var(--fg-muted);
}
.lp-facts li { display: inline-flex; align-items: center; gap: 6px; }
.lp-facts .icon { width: 16px; height: 16px; color: var(--brand); }

/* The stage holds a fixed-size scene, scaled down on small screens, so the
   positions inside it never depend on the width of the window. */
.lp-stage {
  position: relative;
  height: 470px;
  perspective: 1400px;
  perspective-origin: 50% 30%;
}
.lp-sway,
.lp-scene { position: absolute; transform-style: preserve-3d; }
.lp-sway { inset: 0; animation: lp-sway 16s ease-in-out infinite alternate; }
.lp-scene {
  left: 50%;
  top: 50%;
  width: 520px;
  height: 460px;
  margin: -230px 0 0 -260px;
  transform: scale(var(--lp-scale, 1))
             rotateX(calc(-14deg + var(--py, 0) * 5deg))
             rotateY(calc(-26deg - var(--px, 0) * 9deg));
}
@keyframes lp-sway { from { rotate: y -3deg; } to { rotate: y 3deg; } }

.lp-floor {
  position: absolute;
  left: 0;
  bottom: calc(70px - 260px);
  width: 520px;
  height: 520px;
  border-radius: 50%;
  transform: rotateX(90deg);
  background: radial-gradient(closest-side, color-mix(in srgb, var(--brand) 26%, transparent), transparent 75%);
}
.lp-floor::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background-image:
    linear-gradient(color-mix(in srgb, var(--brand) 22%, transparent) 1px, transparent 1px),
    linear-gradient(90deg, color-mix(in srgb, var(--brand) 22%, transparent) 1px, transparent 1px);
  background-size: 40px 40px;
  background-position: center;
  -webkit-mask-image: radial-gradient(closest-side, #000 30%, transparent 85%);
  mask-image: radial-gradient(closest-side, #000 30%, transparent 85%);
}
.lp-pillars { position: absolute; left: 50%; bottom: 70px; transform-style: preserve-3d; }
.lp-pillars .cube { position: absolute; bottom: 0; }

/* Matte, like a model in card: pale faces told apart by a few points of
   shade and a hairline edge, and at most one block of a row in full colour
   (.is-solid, .is-band). Gloss -- highlights, deep gradients, coloured
   shadows -- made the first version look like plastic. */
.cube {
  --w: 84px;
  --d: 84px;
  --h: 120px;
  --mix: 30%;
  --face: color-mix(in srgb, var(--brand) var(--mix), var(--bg-elevated));
  --ink: var(--cube-ink);
  position: relative;
  flex: none;
  width: var(--w);
  height: var(--h);
  transform-style: preserve-3d;
}
.cube > span { position: absolute; }
.cube .f-front {
  inset: 0;
  display: grid;
  justify-items: center;
  align-content: start;
  padding-top: 14px;
  transform: translateZ(calc(var(--d) / 2));
  background: var(--face);
  color: var(--ink);
  font-size: 1.45rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fg) 7%, transparent);
}
.cube .f-side {
  top: 0;
  bottom: 0;
  left: calc((var(--w) - var(--d)) / 2);
  width: var(--d);
  transform: rotateY(90deg) translateZ(calc(var(--w) / 2));
  background: color-mix(in srgb, var(--face), #000 9%);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent);
}
.cube .f-top {
  left: 0;
  right: 0;
  top: calc((var(--h) - var(--d)) / 2);
  height: var(--d);
  transform: rotateX(90deg) translateZ(calc(var(--h) / 2));
  background: color-mix(in srgb, var(--face), #fff 22%);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fg) 5%, transparent);
}
.cube.is-solid { --mix: 100%; --ink: var(--brand-fg); }
/* The mark's three bars: two pale, the last in full colour. */
.cube.p-a2 { --h: 118px; --mix: 14%; left: -158px; }
.cube.p-b1 { --h: 178px; --mix: 28%; left: -42px; }
.cube.p-b2 { --h: 246px; --mix: 100%; --ink: var(--brand-fg); left: 74px; }

/* Glass cards: frosted panes that turn most of the way back toward the viewer,
   so the words on them stay readable while the scene around them is tilted. */
.lp-card {
  position: absolute;
  display: grid;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--glass-line);
  border-radius: 14px;
  background: var(--glass);
  -webkit-backdrop-filter: blur(14px) saturate(1.4);
  backdrop-filter: blur(14px) saturate(1.4);
  box-shadow: 0 10px 28px -16px rgba(16, 32, 52, 0.28);
  font-size: var(--t-sm);
  line-height: 1.4;
  color: var(--fg);
  transform: translateZ(var(--z, 60px)) rotateY(19deg) rotateX(8deg);
  animation: lp-bob 7s ease-in-out infinite alternate;
  animation-delay: var(--delay, 0s);
}
@keyframes lp-bob { to { translate: 0 -4px; } }
.lp-card-head {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.lp-live { width: 8px; height: 8px; border-radius: 50%; background: var(--ok); box-shadow: 0 0 0 3px color-mix(in srgb, var(--ok) 25%, transparent); }

.lp-card-read { --z: 70px; left: -6px; top: 10px; width: 292px; }
.lp-specimen { margin: 0; font-size: 1.05rem; line-height: 1.85; }
.lp-legend { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 6px; font-size: var(--t-xs); color: var(--fg-muted); }
.lp-legend .dot { width: 8px; height: 8px; margin-left: 4px; }
.lp-legend .dot:first-child { margin-left: 0; }

.lp-card-wave {
  --z: 150px;
  --delay: -2s;
  left: 18px;
  top: 300px;
  grid-auto-flow: column;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-4) var(--sp-2) var(--sp-2);
  border-radius: var(--r-pill);
}
.lp-card-wave b, .lp-mock-float b { font-variant-numeric: tabular-nums; }
.lp-rec {
  position: relative;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--danger);
  color: #fff;
}
.lp-rec .icon { width: 18px; height: 18px; }
.lp-rec::after {
  content: "";
  position: absolute;
  inset: -4px;
  border-radius: 50%;
  border: 2px solid var(--danger);
  animation: lp-ping 1.8s var(--ease-out) infinite;
}
.lp-rec.small { width: 30px; height: 30px; background: var(--brand); color: var(--brand-fg); }
.lp-rec.small::after { content: none; }
@keyframes lp-ping { from { opacity: 0.7; transform: scale(0.9); } to { opacity: 0; transform: scale(1.45); } }
.lp-eq { display: flex; align-items: center; gap: 3px; height: 26px; }
.lp-eq i {
  width: 3px;
  height: 100%;
  border-radius: 2px;
  background: var(--brand);
  transform: scaleY(0.3);
  animation: lp-eq 1s ease-in-out infinite alternate;
}
.lp-eq i:nth-child(3n) { animation-duration: 0.7s; }
.lp-eq i:nth-child(3n + 1) { animation-duration: 1.2s; animation-delay: -0.4s; }
.lp-eq i:nth-child(4n) { animation-delay: -0.9s; }
.lp-eq i:nth-child(5n + 2) { animation-duration: 0.85s; }
@keyframes lp-eq { to { transform: scaleY(1); } }

.lp-card-band { --z: 40px; --delay: -3.5s; right: -34px; top: -26px; width: 158px; gap: var(--sp-1); }
.lp-band { font-size: 2.3rem; font-weight: 800; line-height: 1.05; letter-spacing: -0.03em; color: var(--brand); }
.lp-range { position: relative; height: 6px; margin-top: var(--sp-1); border-radius: var(--r-pill); background: var(--bg-sunken); }
.lp-range span {
  position: absolute;
  left: 36%;
  width: 34%;
  top: 0;
  bottom: 0;
  border-radius: inherit;
  background: linear-gradient(90deg, var(--brand), var(--accent));
}
.lp-range-scale { display: flex; justify-content: space-between; font-size: 0.68rem; font-weight: 600; color: var(--fg-muted); }

.lp-card-check {
  --z: 150px;
  --delay: -1s;
  right: 6px;
  top: 300px;
  grid-auto-flow: column;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4) var(--sp-3) var(--sp-3);
}
.lp-card-check b { display: block; font-size: var(--t-sm); }
.lp-card-check small { color: var(--fg-muted); font-size: var(--t-xs); }
.lp-tick {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--ok);
  color: var(--bg-elevated);
}
.lp-tick .icon { width: 18px; height: 18px; }

/* --- sections ------------------------------------------------------------- */
.lp-section { padding: var(--sp-8) 0 0; }
.lp-head { max-width: 640px; margin: 0 0 var(--sp-6); }
.lp-head h2 {
  margin: 0;
  font-size: clamp(1.75rem, 3.4vw, 2.35rem);
  font-weight: 750;
  letter-spacing: -0.03em;
}

/* Cards that lean toward the pointer, with a glare where it points; the icon
   and the words sit at different depths inside, so they shift against each
   other as the card turns. */
.lp-tilt {
  position: relative;
  transform-style: preserve-3d;
  transform: perspective(900px) rotateX(var(--tilt-x, 0deg)) rotateY(var(--tilt-y, 0deg));
  transition: transform 600ms var(--ease-out), box-shadow var(--dur-slow) var(--ease);
}
/* The layers inside a card: flat at rest, standing off it while it tilts. */
.lp-tilt .lp-orb,
.lp-tilt h3,
.lp-tilt p,
.lp-tilt .lp-step-no { transition: transform 600ms var(--ease-out); }
.lp-tilt.is-tilting .lp-orb { transform: translateZ(24px); }
.lp-tilt.is-tilting h3 { transform: translateZ(12px); }
.lp-tilt.is-tilting p { transform: translateZ(6px); }
.lp-tilt.is-tilting .lp-step-no { transform: translateZ(4px); }
.lp-tilt.is-tilting { transition-duration: 120ms, var(--dur-slow); }
.lp-tilt::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: radial-gradient(circle at var(--glare-x, 50%) var(--glare-y, 0%), color-mix(in srgb, var(--shine) 45%, transparent), transparent 60%);
  opacity: 0;
  transition: opacity var(--dur-slow) var(--ease);
}
.lp-tilt.is-tilting::after { opacity: 1; }

.lp-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-5);
}
.lp-step {
  padding: var(--sp-6) var(--sp-5) var(--sp-5);
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--bg-elevated);
  box-shadow: var(--shadow-sm);
}
.lp-step.is-tilting { box-shadow: var(--shadow-card); }
.lp-step h3 { margin: var(--sp-5) 0 var(--sp-2); font-size: var(--t-lg); font-weight: 700; }
.lp-step p { margin: 0; color: var(--fg-muted); }
.lp-step-no {
  position: absolute;
  top: var(--sp-4);
  right: var(--sp-5);
  font-size: 3.2rem;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.04em;
  color: color-mix(in srgb, var(--fg) 7%, transparent);
}
/* An icon tile: tinted, matte, a hairline edge. */
.lp-orb {
  width: 52px;
  height: 52px;
  border-radius: 14px;
  display: grid;
  place-items: center;
  color: var(--brand);
  background: var(--brand-soft);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--brand) 12%, transparent);
}
.lp-orb .icon { width: 26px; height: 26px; }

.lp-rungs {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sp-4);
}
.lp-rungs > li { display: grid; gap: var(--sp-1); align-content: start; }
.lp-rungs b { font-size: var(--t-lg); font-weight: 700; letter-spacing: -0.01em; }
.lp-rungs li > span:not(.lp-rung-block):not(.lp-hints) { color: var(--fg-muted); font-size: var(--t-md); }
.lp-rung-block {
  position: relative;
  height: 184px;
  margin-bottom: var(--sp-3);
  perspective: 900px;
  perspective-origin: 50% 0%;
}
.lp-rung-block .cube {
  --w: 96px;
  --d: 96px;
  position: absolute;
  left: calc(50% - 48px);
  bottom: 36px;
  transform: rotateX(-16deg) rotateY(-24deg);
}
/* A rung can be shorter than its label: the number sits in the middle. */
.lp-rung-block .f-front { align-content: center; padding-top: 0; font-size: 1.3rem; }
.cube.r1 { --h: 46px; --mix: 12%; }
.cube.r2 { --h: 78px; --mix: 22%; }
.cube.r3 { --h: 110px; --mix: 34%; }
.cube.r4 { --h: 142px; --face: var(--accent); --ink: var(--fg-on-brand); }
.lp-rung-block::after {
  content: "";
  position: absolute;
  left: 10%;
  right: 10%;
  bottom: 18px;
  height: 30px;
  border-radius: 50%;
  background: radial-gradient(closest-side, color-mix(in srgb, var(--brand) 22%, transparent), transparent);
  z-index: -1;
}
.lp-hints { display: flex; gap: 5px; margin-top: var(--sp-2); }
.lp-hints i { width: 22px; height: 6px; border-radius: var(--r-pill); background: var(--border); }
.lp-hints i.on { background: var(--brand); }

.lp-teach {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
  gap: var(--sp-7);
  align-items: center;
}
.lp-teach-copy h2 { margin: 0 0 var(--sp-3); font-size: clamp(1.75rem, 3.4vw, 2.35rem); font-weight: 750; letter-spacing: -0.03em; }
.lp-teach-copy > p:not(.eyebrow) { color: var(--fg-muted); font-size: var(--t-lg); max-width: 46ch; }
.lp-chips { list-style: none; margin: var(--sp-5) 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.lp-chips li {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 7px var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  background: var(--bg-elevated);
  font-size: var(--t-sm);
  font-weight: 600;
}
.lp-chips .icon { width: 16px; height: 16px; color: var(--accent); }

/* A screen held up at an angle. */
.lp-mock-wrap { padding: var(--sp-5) 0; }
.lp-mock {
  --tilt-x: 4deg;
  --tilt-y: -10deg;
  padding: var(--sp-3) var(--sp-4) var(--sp-5);
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--bg-elevated);
  box-shadow: 0 24px 56px -32px rgba(16, 32, 52, 0.32), var(--shadow-sm);
  font-size: var(--t-sm);
}
.lp-mock-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: var(--sp-3);
  padding-bottom: var(--sp-3);
  border-bottom: 1px solid var(--border);
  color: var(--fg-muted);
  font-weight: 600;
}
.lp-mock-bar i { width: 10px; height: 10px; border-radius: 50%; background: var(--border-strong); }
.lp-mock-bar span { margin-left: var(--sp-2); }
.lp-mock-row {
  display: grid;
  grid-template-columns: 70px minmax(0, 1fr) auto auto;
  align-items: center;
  gap: var(--sp-3);
  padding: 10px var(--sp-2);
  border-radius: var(--r-md);
}
.lp-mock-row + .lp-mock-row { border-top: 1px solid var(--border); }
.lp-mock-row:first-of-type { background: var(--brand-soft); }
.lp-mock-row span:not(.lp-pill) { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--fg-muted); }
.lp-pill {
  padding: 2px 10px;
  border-radius: var(--r-pill);
  background: var(--bg-sunken);
  font-size: var(--t-xs);
  font-weight: 700;
  white-space: nowrap;
}
.lp-pill.warn { background: var(--warn-soft); color: var(--warn); }
.lp-pill.ok { background: var(--ok-soft); color: var(--ok); }
.lp-mock-float {
  position: absolute;
  right: -22px;
  bottom: -26px;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-4) var(--sp-2) var(--sp-2);
  border: 1px solid var(--glass-line);
  border-radius: var(--r-pill);
  background: var(--glass);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  box-shadow: 0 10px 24px -14px rgba(16, 32, 52, 0.3);
  transform: translateZ(40px);
}

.lp-principles {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sp-4);
}
.lp-principles li {
  padding: var(--sp-5);
  border: 1px solid var(--border);
  border-radius: 16px;
  background: var(--bg-elevated);
}
.lp-principles .icon { width: 26px; height: 26px; color: var(--accent); }
.lp-principles h3 { margin: var(--sp-3) 0 var(--sp-1); font-size: var(--t-base); font-weight: 700; }
.lp-principles p { margin: 0; color: var(--fg-muted); font-size: var(--t-md); }

.lp-faq-list { display: grid; gap: var(--sp-2); max-width: 760px; }
.lp-faq details {
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--bg-elevated);
  padding: 0 var(--sp-5);
  transition: box-shadow var(--dur-slow) var(--ease);
}
.lp-faq details[open] { box-shadow: var(--shadow-md); }
.lp-faq summary {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-4) 0;
  font-weight: 650;
  cursor: pointer;
  list-style: none;
}
.lp-faq summary::-webkit-details-marker { display: none; }
.lp-faq summary::after {
  content: "+";
  flex: none;
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--bg-sunken);
  color: var(--brand);
  font-size: 1.2rem;
  font-weight: 500;
  transition: transform var(--dur-slow) var(--ease-out);
}
.lp-faq details[open] summary::after { transform: rotate(45deg); }
.lp-faq details p { margin: 0 0 var(--sp-4); color: var(--fg-muted); }

/* The last call: the brand band, with the mark standing off it in 3D. */
.lp-cta {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--sp-6);
  margin: var(--sp-8) 0 var(--sp-4);
  padding: var(--sp-6) var(--sp-7);
  border-radius: 18px;
  background:
    radial-gradient(80% 140% at 100% 0%, rgb(255 255 255 / 0.08), transparent 60%),
    var(--brand);
  color: var(--brand-fg);
  perspective: 800px;
}
.lp-cta h2 { margin: 0 0 var(--sp-1); color: inherit; font-size: clamp(1.6rem, 3vw, 2.1rem); font-weight: 750; letter-spacing: -0.03em; }
.lp-cta p { margin: 0; opacity: 0.85; }
.lp-cta .button { margin: 0; background: var(--bg-elevated); color: var(--brand); padding: 14px 24px; font-size: var(--t-base); }
.lp-cta .button:hover { background: var(--bg-elevated); color: var(--brand-strong); }
.lp-cta-mark { display: block; transform-style: preserve-3d; animation: lp-turn 9s ease-in-out infinite alternate; }
.lp-cta-logo { width: 88px; height: 88px; }
.lp-cta .logo-bubble { fill: var(--brand-fg); }
.lp-cta .logo-bar { fill: var(--brand); }
@keyframes lp-turn {
  from { transform: rotateY(-10deg) rotateX(4deg); }
  to { transform: rotateY(8deg) rotateX(-3deg); }
}

/* Sections rise into place as they scroll in: position only, never opacity,
   so the words are readable whatever the scroll position. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .lp-rise {
      animation: lp-rise linear both;
      animation-timeline: view();
      animation-range: entry 0% cover 30%;
    }
    @keyframes lp-rise { from { translate: 0 48px; scale: 0.96; } }
  }
}

@media (max-width: 900px) {
  .lp-hero { grid-template-columns: minmax(0, 1fr); gap: 0; padding: var(--sp-6) 0 var(--sp-4); }
  .lp-stage { height: 420px; --lp-scale: 0.86; }
  .lp-steps, .lp-teach { grid-template-columns: minmax(0, 1fr); }
  .lp-rungs, .lp-principles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .lp-teach { gap: var(--sp-5); }
  .lp-cta { grid-template-columns: minmax(0, 1fr); justify-items: start; padding: var(--sp-6) var(--sp-5); }
  .lp-section { padding-top: var(--sp-7); }
  .lp-rungs { row-gap: var(--sp-5); }
  .lp-rung-block { height: 136px; }
  .lp-rung-block .cube { --w: 80px; --d: 80px; left: calc(50% - 40px); bottom: 26px; }
  .cube.r1 { --h: 30px; }
  .cube.r2 { --h: 52px; }
  .cube.r3 { --h: 74px; }
  .cube.r4 { --h: 96px; }
}
@media (max-width: 560px) {
  .lp-stage { height: 330px; --lp-scale: 0.62; }
  .lp-card-band { display: none; }
  .lp-principles { grid-template-columns: minmax(0, 1fr); }
  .lp-mock-row { grid-template-columns: 60px minmax(0, 1fr) auto; }
  .lp-mock-row .lp-pill:not(.warn):not(.ok) { display: none; }
  .lp-mock-float { right: 8px; }
}

/* --- sign-in (D026) ---------------------------------------------------------- */
.auth-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 440px);
  gap: var(--sp-7);
  align-items: center;
  max-width: 960px;
  margin: var(--sp-5) auto 0;
}
.auth-intro h2 { font-size: var(--t-2xl); line-height: 1.2; }
.auth-points { margin: var(--sp-4) 0 0; padding-left: 1.1em; display: grid; gap: var(--sp-2); color: var(--fg-muted); }
.auth-card h1 { font-size: var(--t-2xl); margin: 0 0 var(--sp-2); }
.auth-card .lede { color: var(--fg-muted); }
.auth-foot { margin-top: var(--sp-4); font-size: var(--t-sm); }

@media (max-width: 900px) {
  .auth-split { grid-template-columns: minmax(0, 1fr); }
  .auth-intro { display: none; }
}

/* --- review queue ------------------------------------------------------------ */
.card-head > h2 { margin-right: auto; }
.card-head > .small { align-self: center; }
.queue-table td { vertical-align: top; }
.cell-sub { display: block; margin-top: 2px; font-size: var(--t-sm); color: var(--fg-muted); }
.action-cell { white-space: nowrap; }
.action-cell .button { margin: 0; }
.lock-note { display: block; margin-top: var(--sp-1); font-size: var(--t-xs); white-space: normal; }
.queue-table tr.is-locked td { background: var(--bg-sunken); }
a.button[aria-disabled="true"] { pointer-events: auto; }
/* On a phone each queued response becomes a block: who and which task, the
   machine's result, the flags, then a full-width button. A nine-, then
   four-column table still pushed the button off the right edge. */
@media (max-width: 640px) {
  .queue-table, .queue-table tbody, .queue-table tr, .queue-table td { display: block; width: 100%; }
  .queue-table thead { display: none; }
  .queue-table tr { padding: var(--sp-3); border-bottom: 1px solid var(--border); }
  .queue-table tr:last-child { border-bottom: 0; }
  .queue-table td { padding: var(--sp-1) 0; border: 0; }
  .queue-table .action-cell { white-space: normal; padding-top: var(--sp-2); }
  .queue-table .action-cell .button { width: 100%; }
}

/* --- review screen: a player pinned for the whole page --------------------- */
.player-bar {
  position: sticky;
  top: var(--header-h);
  z-index: 20;
  display: flex;
  gap: var(--sp-3);
  align-items: center;
  margin: 0 0 var(--sp-5);
  padding: var(--sp-3) var(--sp-4);
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-md);
}
.player-bar audio { flex: 1; min-width: 0; display: block; }
.player-bar .jump-decision { flex: none; }
.player-bar ~ .two-col .anchor-target { scroll-margin-top: calc(var(--header-h) + 96px); }
@media (min-width: 901px) {
  .player-bar .jump-decision { display: none; }
}
.reason-line { margin-top: var(--sp-3); }
.reason-chips { margin: var(--sp-2) 0 0; }
.reason-chips button { font-weight: 500; text-align: left; }

/* --- admin ----------------------------------------------------------------- */
.models-table td { vertical-align: top; }
.config-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
  gap: var(--sp-3) var(--sp-4);
  align-items: center;
}
.config-grid label { margin: 0; font-weight: 600; }
.config-grid input { margin: 0; }
.config-grid .config-actions, .config-grid #config-message { grid-column: 1 / -1; }
@media (max-width: 640px) {
  .config-grid { grid-template-columns: minmax(0, 1fr); }
  .config-grid input { margin-bottom: var(--sp-2); }
}

/* --- my responses: one row per submission --------------------------------- */
.response-list { list-style: none; margin: 0; padding: 0; }
.response-item {
  display: flex;
  gap: var(--sp-4);
  align-items: flex-start;
  justify-content: space-between;
  padding: var(--sp-3) 0;
  border-top: 1px solid var(--border);
}
.response-item:first-child { border-top: 0; padding-top: 0; }
.response-main { min-width: 0; }
.response-title { font-weight: 650; font-size: var(--t-base); text-decoration: none; }
.response-title:hover { text-decoration: underline; }
.response-meta {
  margin: var(--sp-1) 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-3);
  align-items: center;
  font-size: var(--t-sm);
  color: var(--fg-muted);
}
.response-meta b { color: var(--fg); }
.response-id { margin: var(--sp-1) 0 0; font-size: var(--t-xs); }
.response-side { flex: none; font-size: var(--t-sm); padding-top: 2px; }
.account-links { display: inline-flex; gap: var(--sp-2); }
.page-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  align-items: center;
  margin: 0 0 var(--sp-5);
}
.page-actions > * { margin-top: 0; }
.page-actions .spacer { flex: 1; }

/* Action bar that stays at the bottom of the viewport on long result pages. */
.action-bar {
  position: sticky;
  bottom: 0;
  z-index: 30;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  align-items: center;
  margin: var(--sp-5) calc(-1 * var(--sp-5)) 0;
  padding: var(--sp-3) var(--sp-5);
  background: var(--bg-elevated);
  border-top: 1px solid var(--border);
  box-shadow: 0 -4px 16px rgba(16, 32, 52, 0.08);
}
.action-bar > * { margin-top: 0; }
.action-bar .spacer { flex: 1; }

/* In-page section links for the long report and review screens. */
.section-nav {
  display: flex;
  gap: var(--sp-2);
  flex-wrap: wrap;
  margin: 0 0 var(--sp-4);
}
.section-nav a {
  padding: var(--sp-1) var(--sp-3);
  border-radius: var(--r-pill);
  background: var(--bg-sunken);
  border: 1px solid var(--border);
  text-decoration: none;
  font-size: var(--t-sm);
  font-weight: 600;
}
.section-nav a:hover { border-color: var(--brand); }
[id].anchor-target { scroll-margin-top: calc(var(--header-h) + var(--sp-4)); }

/* Filter chips above a list ("Tất cả", "Đánh giá", "Luyện tập"). */
.chips { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin: 0 0 var(--sp-3); }
.chips button {
  margin: 0;
  padding: var(--sp-1) var(--sp-3);
  border-radius: var(--r-pill);
  background: var(--bg-elevated);
  color: var(--fg);
  border: 1px solid var(--border-strong);
  font-weight: 600;
  font-size: var(--t-sm);
}
.chips button[aria-pressed="true"] {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--brand-fg);
}

/* --- narrow screens ----------------------------------------------------- */
@media (max-width: 640px) {
  :root { --sp-5: 16px; }
  main { margin: var(--sp-5) auto var(--sp-6); padding: 0 var(--sp-4); }
  .header-inner, .site-nav, .site-footer .footer-inner { padding-left: var(--sp-4); padding-right: var(--sp-4); }
  .card { padding: var(--sp-4); border-radius: var(--r-md); }
  .page-head h1 { font-size: var(--t-xl); }
  .score { font-size: var(--t-2xl); }
  .score-panel { padding: var(--sp-4); gap: var(--sp-4); }
  .evidence-grid { grid-template-columns: minmax(0, 1fr); }
  .sticky-player { margin-left: calc(-1 * var(--sp-4)); margin-right: calc(-1 * var(--sp-4)); padding: var(--sp-3) var(--sp-4); }
  .toast-region { left: var(--sp-3); right: var(--sp-3); width: auto; }
  button, .button { width: 100%; }
  button.small, .button.small, .tabs button, .toast .toast-close, .header-button, .evidence button.seek { width: auto; }
  .button-row > button, .button-row > .button { width: 100%; }
}

/* Anything that must exist for a screen reader but not on screen. */
.visually-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* Nhận xét (FR-25) và ưu tiên luyện tập (FR-26).
   Danh sách cố ý thưa và rộng: đây là phần người học đọc kỹ nhất, không phải
   phần liếc qua như bảng số. */
.comment-list,
.priority-list {
  display: grid;
  gap: var(--sp-3);
  margin: var(--sp-3) 0 0;
  padding-left: 0;
}
/* A marker placed "inside" sat on a line of its own above each paragraph.
   Comments need no marker; priorities keep their number, outside the box. */
.comment-list { list-style: none; }
.priority-list { padding-left: var(--sp-5); }
.comment,
.priority {
  background: var(--bg-sunken);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-3) var(--sp-4);
}
.comment p,
.priority p {
  margin: 0;
}
.comment-refs {
  margin-top: var(--sp-2) !important;
  font-size: 0.85em;
}


/* ------------------------------------------------------ dải cửa sổ phân tích
   A long recording is scored in windows; this strip draws them TO SCALE so the
   reader can see they tile the whole recording. Equal-width boxes would hide
   exactly the property that matters -- that no audio was dropped at a boundary
   and none was counted twice. */
.window-strip {
  display: flex;
  gap: 3px;
  margin: var(--sp-3) 0;
  overflow-x: auto;
}
.window-chunk {
  min-width: 92px;
  display: grid;
  gap: 2px;
  align-content: center;
  justify-items: center;
  padding: var(--sp-2) var(--sp-1);
  border-radius: var(--r-sm);
  background: var(--brand-soft);
  border: 1px solid color-mix(in srgb, var(--brand) 28%, transparent);
  color: var(--brand);
  font-size: var(--t-sm);
  font-variant-numeric: tabular-nums;
  text-align: center;
}
.window-chunk .muted { font-size: var(--t-xs); }
/* Trên màn hình duyệt, mỗi ô là một nút tua tới đúng đoạn đó. Reset lại mặc
   định của <button> để nút trông giống hệt ô tĩnh ở màn hình báo cáo — cùng
   một thứ, chỉ khác là bấm được. */
button.window-chunk {
  font: inherit;
  cursor: pointer;
  appearance: none;
}
button.window-chunk:hover { background: var(--bg-hover); }
.window-chunk.unscored {
  background: var(--bg-sunken);
  border-color: var(--border);
  color: var(--fg-muted);
}

/* ------------------------------------------------------------ ô số tổng hợp
   Used on the two statistics screens. Deliberately not a card: a card says
   "separate object you can act on", and these are readings. */
.stat-grid {
  display: grid;
  gap: var(--sp-3);
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  margin: var(--sp-3) 0;
}
.stat {
  background: var(--bg-sunken);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-3) var(--sp-4);
  display: grid;
  gap: 2px;
}
.stat b {
  font-size: var(--t-xl);
  font-variant-numeric: tabular-nums;
  line-height: var(--lh-tight);
}
.stat span { font-size: var(--t-sm); color: var(--fg-muted); }
.stat.attention { background: var(--warn-soft); border-color: color-mix(in srgb, var(--warn) 30%, transparent); }
.stat.attention b { color: var(--warn); }

/* -------------------------------------------------------------- biểu đồ tiến độ
   A plain SVG, drawn from one scale shared by the marks, the ticks and the
   labels. Verified results and machine estimates are drawn differently on
   purpose: blurring the two would let an unconfirmed number look like a
   finding, which FR-21 forbids in the report and which is no more acceptable
   in a chart. */
.trend-chart { width: 100%; height: auto; display: block; }
.trend-chart .grid { stroke: var(--border); stroke-width: 1; }
.trend-chart .axis-label { fill: var(--fg-muted); font-size: 11px; }
.trend-chart .line { fill: none; stroke: var(--brand); stroke-width: 2; }
.trend-chart .dot { fill: var(--bg-elevated); stroke: var(--brand); stroke-width: 2; }
.trend-chart .dot.verified { fill: var(--brand); }
.trend-legend {
  display: flex;
  gap: var(--sp-4);
  flex-wrap: wrap;
  font-size: var(--t-sm);
  color: var(--fg-muted);
}
.trend-legend span { display: inline-flex; align-items: center; gap: var(--sp-2); }
.trend-legend i {
  width: 10px; height: 10px; border-radius: 50%;
  border: 2px solid var(--brand); background: var(--bg-elevated);
}
.trend-legend i.verified { background: var(--brand); }

/* ------------------------------------------------------------------ thanh công cụ */
.page-toolbar {
  display: flex;
  gap: var(--sp-2);
  flex-wrap: wrap;
  align-items: center;
  margin-bottom: var(--sp-4);
}
.page-toolbar .spacer { flex: 1 1 auto; }

/* --------------------------------------------------------------------- in ấn
   A student handing a report to a teacher, or filing it with a portfolio,
   prints it or saves it as PDF from the browser. That path deserves the same
   care as the screen, because a report that prints with the navigation intact
   and the evidence cut in half reads as an accident.

   No server-side PDF renderer: it would add a large dependency, a font
   pipeline and a second layout to keep in step with this one, to produce a
   file the browser already produces from the markup that is right here. */
/* --- phones: the guest bar wraps; tasks fold their prompts ---------------------
   (Signed-in users have the sidebar of D027, which turns into a top bar and a
   bottom tab bar on its own; see "app sidebar" at the end of this file.) */
@media (max-width: 640px) {
  /* One row: the mark and name, sign-in, and the theme as an icon. The links
     of the guest bar only jump within the product page, which the name
     already leads to, so a phone does without them (D031). */
  .header-inner { padding: var(--sp-2) var(--sp-4); column-gap: var(--sp-3); }
  .site-header .brand-sub,
  .site-header .site-nav { display: none; }
  .site-header #theme-toggle { padding: 6px 8px; }
  .site-header #theme-toggle-label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .rec-button { width: 100%; }
  /* The chosen prompt is shown in full above the record button; on a phone the
     list only needs titles to choose by, so the recorder is not pushed below
     three screens of prompts. */
  .task .task-prompt { display: none; }
  .task { padding: var(--sp-3) var(--sp-4); }
  .rec-readout { min-width: 0; }
  .action-bar { margin-left: calc(-1 * var(--sp-4)); margin-right: calc(-1 * var(--sp-4)); }
  .action-bar > .button, .action-bar > button { flex: 1; }
}
@media (max-width: 1100px) {
  .site-header .brand-sub { display: none; }
}

@media print {
  :root { color-scheme: light; }
  /* Hide the CONTROLS, not every <button>.
     `button { display: none }` was the obvious rule and a bug: an evidence card
     and a window box are both <button> elements, because clicking one seeks the
     audio. That rule deleted the evidence -- the most useful part of the report
     -- from every printed copy. What must go is the chrome nobody can operate
     on paper. */
  .app-sidebar, .site-nav, .header-actions, .toast-region, .skip-link, .page-toolbar,
  .site-header .site-tagline, audio, .evidence-action, .link-button,
  .button-row, button[type="submit"], #logout-link, #theme-toggle,
  .section-nav, .action-bar, .player-bar, .chips, .rec {
    display: none !important;
  }
  /* Sticky bars would repeat on every printed page. */
  .site-header, .sticky-player { position: static; }

  /* The ones that stay are read, not pressed, so strip the affordances. */
  .evidence, .window-chunk {
    text-align: left;
    cursor: default;
  }

  body { background: #fff; color: #000; font-size: 11pt; }
  main { padding: 0; }
  .site-header { border: 0; padding: 0 0 8pt; }
  .site-title { font-size: 12pt; }

  /* Blocks the reader is meant to follow as a unit must not be split across a
     page break: an evidence card whose timestamp sits on page 2 and whose note
     sits on page 3 has stopped being evidence. */
  .card, .evidence, .comment, .priority, .window-chunk, .stat, table {
    box-shadow: none;
    break-inside: avoid;
  }
  .card { border: 1px solid #bbb; margin-bottom: 10pt; }
  h1, h2, h3 { break-after: avoid; }

  /* A link whose destination is invisible is useless on paper. */
  .transcript .word { background: none !important; }
  .transcript .word.ngo { text-decoration: underline dotted; }

  /* The disclaimer is the one thing that must survive printing, because a
     printed page travels further than a screen and with less context. */
  .site-footer { border-top: 1px solid #bbb; font-size: 9pt; }
}

/* Trust Layer (D022): dong khoang tin cay ngay duoi khung diem. */
.interval-line {
  margin: var(--sp-3) 0 0;
}

/* --- lesson path: learner side (D025) -----------------------------------------
   The path, one lesson step by step, today's review. The passage a learner
   reads aloud is set in the serif at reading size: it is the one thing on the
   screen that is read rather than scanned, so it gets the room and the type,
   and everything around it stays small and quiet. State is carried by a mark
   (tick, dot, number) as well as by colour. */

/* The four rungs, in the same four tones everywhere they appear: the product
   page, a lesson card on the path, the step rail. */
:root {
  --rung-model: var(--brand);
  --rung-controlled: color-mix(in srgb, var(--brand) 68%, var(--bg-elevated));
  --rung-guided: color-mix(in srgb, var(--brand) 42%, var(--bg-elevated));
  --rung-free: var(--accent);
}

/* --- the path ------------------------------------------------------------- */
.path-head { margin-bottom: var(--sp-5); display: grid; gap: var(--sp-2); }
.path-head h1 { margin: 0; font-size: var(--t-2xl); }
.path-head .lede { margin: 0; color: var(--fg-muted); max-width: 70ch; }
.path-head-row { display: flex; align-items: center; gap: var(--sp-3) var(--sp-5); flex-wrap: wrap; }
.path-switch { display: flex; align-items: center; gap: var(--sp-2); }
.path-switch label { margin: 0; white-space: nowrap; }
.path-switch select { width: auto; max-width: 320px; padding: var(--sp-2) var(--sp-3); }
.sample-badge { background: var(--accent-soft); color: var(--accent); }

.overall { display: flex; align-items: center; gap: var(--sp-3); max-width: 560px; }
.overall-track, .lesson-progress-track {
  flex: 1;
  height: 6px;
  border-radius: var(--r-pill);
  background: var(--bg-sunken);
  overflow: hidden;
}
.overall-track span, .lesson-progress-track span {
  display: block;
  height: 100%;
  width: 0;
  background: var(--ok);
  border-radius: var(--r-pill);
  transition: width var(--dur-slow) var(--ease);
}
.overall .muted, .lesson-progress .muted { white-space: nowrap; }

.unit-tags { display: flex; gap: var(--sp-2); flex-wrap: wrap; }
.band-badge { background: var(--brand); color: var(--brand-fg); }

.mini-ladder { display: inline-flex; gap: 2px; align-items: center; }
.mini-ladder i { width: 10px; height: 6px; border-radius: 1px; background: var(--rung-model); }
.mini-ladder i.controlled { background: var(--rung-controlled); }
.mini-ladder i.guided { background: var(--rung-guided); }
.mini-ladder i.free { background: var(--rung-free); width: 16px; }

/* --- one lesson ------------------------------------------------------------- */
.lesson-head { margin-bottom: var(--sp-5); display: grid; gap: var(--sp-2); }
.lesson-head h1 { margin: 0; font-size: var(--t-2xl); }
.lesson-head .lede { margin: 0; color: var(--fg-muted); max-width: 68ch; }
.lesson-progress { display: flex; align-items: center; gap: var(--sp-3); max-width: 520px; margin-top: var(--sp-2); }

.lesson-layout {
  display: grid;
  grid-template-columns: 248px minmax(0, 1fr);
  gap: var(--sp-5);
  align-items: start;
}
.step-rail { position: sticky; top: calc(var(--header-h) + var(--sp-4)); }
.step-rail ol { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; position: relative; }
.step-rail ol::before {
  content: "";
  position: absolute;
  left: 20px;
  top: 20px;
  bottom: 20px;
  width: 2px;
  background: var(--border-strong);
}
.rail-step {
  position: relative;
  display: grid;
  grid-template-columns: 26px minmax(0, 1fr);
  gap: var(--sp-3);
  align-items: center;
  width: 100%;
  margin: 0;
  padding: var(--sp-2);
  background: none;
  color: var(--fg);
  border: 0;
  border-radius: var(--r-md);
  text-align: left;
  font-weight: 500;
}
.rail-step:hover { background: var(--bg-hover); color: var(--fg); }
.rail-mark {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: var(--t-xs);
  font-weight: 700;
  background: var(--bg-elevated);
  border: 2px solid var(--border-strong);
  color: var(--fg-muted);
  z-index: 1;
}
.rail-text { display: grid; min-width: 0; }
.rail-stage { font-size: var(--t-xs); font-weight: 650; letter-spacing: 0.04em; text-transform: uppercase; color: var(--fg-muted); }
.rail-title { font-size: var(--t-md); line-height: var(--lh-tight); overflow-wrap: anywhere; }
.step-rail li.now .rail-step { background: var(--brand-soft); }
.step-rail li.now .rail-mark { border-color: var(--brand); color: var(--brand); }
.step-rail li.passed .rail-mark { background: var(--ok); border-color: var(--ok); color: var(--bg-elevated); }
.step-rail li.tried .rail-mark { border-color: var(--warn); color: var(--warn); }

.step-card { padding: var(--sp-6); margin-bottom: 0; }
.step-top { display: flex; justify-content: space-between; align-items: center; gap: var(--sp-3); flex-wrap: wrap; margin-bottom: var(--sp-3); }
.step-card h2 { margin: 0 0 var(--sp-2); font-size: var(--t-2xl); }
.step-card h2:focus { outline: none; }
.instruction { max-width: 68ch; }
.stage-chip.stage-model { color: var(--brand); }
.stage-chip.stage-free { color: var(--accent); background: var(--accent-soft); }

.step-content { margin: var(--sp-5) 0; }
.step-content > * + * { margin-top: var(--sp-3); }
.step-content > .eyebrow { margin-top: var(--sp-4); }
.step-content > .eyebrow + * { margin-top: var(--sp-1); }
.read-text {
  margin: 0;
  padding: var(--sp-4) var(--sp-5);
  max-width: 68ch;
  font-family: var(--font-display);
  font-size: 1.3rem;
  line-height: 1.85;
  color: var(--fg);
  background: var(--bg-sunken);
  border-left: 3px solid var(--brand);
  border-radius: 0 var(--r-md) var(--r-md) 0;
  overflow-wrap: anywhere;
}
.read-text.veiled { filter: blur(7px); user-select: none; }
.read-text.marked { background: var(--bg-elevated); border: 1px solid var(--border); border-left: 3px solid var(--brand); }
.hint-word {
  font-weight: 700;
  text-decoration: underline;
  text-decoration-color: var(--accent);
  text-decoration-thickness: 2px;
  text-underline-offset: 5px;
}
.model-audio { display: grid; gap: var(--sp-1); max-width: 520px; }
.model-audio .eyebrow { margin: 0; }
.look-up { display: grid; gap: var(--sp-3); justify-items: start; }
.look-up button { margin-top: 0; }
.look-hidden {
  margin: 0;
  width: 100%;
  max-width: 68ch;
  padding: var(--sp-5);
  text-align: center;
  font-style: italic;
  color: var(--fg-muted);
  border: 1px dashed var(--border-strong);
  border-radius: var(--r-md);
}
.look-countdown, .prep-clock { font-family: var(--font-mono); font-weight: 700; color: var(--accent); }
.blank {
  display: inline-block;
  min-width: 4.5em;
  margin: 0 2px;
  border-bottom: 2px solid var(--brand);
  text-align: center;
  font-family: var(--font);
  font-size: var(--t-sm);
  font-weight: 700;
  line-height: 1.5;
  color: var(--fg-muted);
}
.word-chips.bank li { background: var(--bg-elevated); border: 1px solid var(--border-strong); }
.word-chips.big li { padding: var(--sp-1) var(--sp-4); font-family: var(--font-display); font-size: var(--t-lg); }
.word-chips.found li { background: var(--ok-soft); }
.word-chips.missing li { background: var(--bg-sunken); color: var(--fg-muted); text-decoration: underline dotted; }
.question, .prompt { margin: 0; max-width: 62ch; font-family: var(--font-display); font-size: var(--t-xl); line-height: 1.45; }
.frame {
  margin: 0;
  max-width: 62ch;
  padding: var(--sp-3) var(--sp-4);
  font-family: var(--font-display);
  font-size: var(--t-lg);
  font-style: italic;
  background: var(--accent-soft);
  border-radius: var(--r-md);
}
.step-image { display: block; width: auto; max-width: 100%; max-height: 360px; border: 1px solid var(--border); border-radius: var(--r-md); }
.outline-points { margin: 0; padding-left: var(--sp-5); display: grid; gap: var(--sp-1); font-size: var(--t-lg); }
.prep { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.prep button { margin-top: 0; }
.round-pills { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.round-pills li {
  padding: 2px var(--sp-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-pill);
  font-size: var(--t-sm);
  color: var(--fg-muted);
}
.round-pills li.now { background: var(--brand); border-color: var(--brand); color: var(--brand-fg); font-weight: 650; }
.round-pills li.done { border-color: var(--ok); color: var(--ok); }
.mission-mini { margin: 0; padding-left: var(--sp-5); display: grid; gap: var(--sp-1); }
.step-tip { margin: 0 0 var(--sp-4); padding-top: var(--sp-3); border-top: 1px solid var(--border); color: var(--fg-muted); font-size: var(--t-md); }
.step-recorder { padding-top: var(--sp-4); border-top: 1px solid var(--border); }
.last-try {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
  margin: 0 0 var(--sp-4);
  padding: var(--sp-2) var(--sp-3);
  background: var(--bg-sunken);
  border-radius: var(--r-md);
  font-size: var(--t-sm);
}

/* Feedback: the verdict, one thing to do, then the evidence. */
.step-feedback { display: grid; gap: var(--sp-3); padding-top: var(--sp-4); border-top: 1px solid var(--border); }
.step-feedback:focus { outline: none; }
.step-feedback > .eyebrow { margin: var(--sp-2) 0 0; }
.verdict { padding: var(--sp-3) var(--sp-4); border-left: 4px solid var(--border-strong); border-radius: 0 var(--r-md) var(--r-md) 0; font-size: var(--t-lg); background: var(--bg-sunken); }
.verdict.ok { background: var(--ok-soft); border-color: var(--ok); color: var(--ok); }
.verdict.todo { background: var(--warn-soft); border-color: var(--warn); }
.step-feedback .tip { margin: 0; padding: var(--sp-3) var(--sp-4); background: var(--brand-soft); border-radius: var(--r-md); }
.change-list, .signal-list, .gap-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-1); }
.change-list li::before { content: "→ "; color: var(--brand); font-weight: 700; }
.signal-list li > span { display: inline-block; width: 1.2em; font-weight: 700; color: var(--fg-muted); }
.signal-list li.met > span { color: var(--ok); }
.signal-list li.unmet > span { color: var(--warn); }
.gap-list li { padding: var(--sp-1) var(--sp-3); border-radius: var(--r-sm); background: var(--bg-sunken); }
.gap-list li.correct { background: var(--ok-soft); }
.gap-list li.distractor, .gap-list li.other { background: var(--warn-soft); }
.replay { display: grid; gap: var(--sp-1); }
.replay .eyebrow { margin: 0; }
.feedback-actions { margin-top: var(--sp-2); }
.seekable { cursor: pointer; }
.w.seekable:hover, .word-chips li.seekable:hover { outline: 1px solid var(--brand); outline-offset: 1px; }
.w.teacher { outline: 1px dashed var(--accent); outline-offset: 1px; }

.lesson-summary { padding: var(--sp-6); }
.lesson-summary h2 { font-size: var(--t-2xl); }
.lesson-summary:focus { outline: none; }

@media (max-width: 900px) {
  .lesson-layout { grid-template-columns: minmax(0, 1fr); gap: var(--sp-3); }
  .step-rail { position: static; overflow-x: auto; padding-bottom: var(--sp-1); }
  .step-rail ol { display: flex; gap: var(--sp-1); }
  .step-rail ol::before { display: none; }
  .rail-step { width: auto; grid-template-columns: 26px; padding: var(--sp-1); }
  .rail-text { display: none; }
  .step-rail li.now .rail-step { grid-template-columns: 26px auto; padding-right: var(--sp-3); }
  .step-rail li.now .rail-text { display: grid; }
  .step-rail li.now .rail-title { white-space: nowrap; }
}
@media (max-width: 640px) {
  .step-card, .lesson-summary { padding: var(--sp-4); }
  .step-card h2, .lesson-summary h2 { font-size: var(--t-xl); }
  .read-text { font-size: 1.15rem; padding: var(--sp-3) var(--sp-4); }
  .path-switch select { max-width: 100%; }
}

/* --- lesson path: teacher side (D025) -----------------------------------------
   Authoring screens are tools, not documents: dense where a teacher scans
   (rows of lessons, per-step rates), roomy where a teacher writes (the step
   editor). Every row says what students currently see. */
.teach-head { display: flex; justify-content: space-between; align-items: flex-end; gap: var(--sp-4); flex-wrap: wrap; }
.teach-head > div:first-child { flex: 1 1 420px; }
.teach-tools { margin-top: 0; }
.state-on { background: var(--ok-soft); color: var(--ok); }
.state-off { background: var(--bg-sunken); color: var(--fg-muted); }
.state-warn { background: var(--warn-soft); color: var(--warn); }
button.icon-only, .button.icon-only { min-width: 34px; padding-left: var(--sp-2); padding-right: var(--sp-2); }
.t-actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; }
.t-actions > button, .t-actions > .button { margin-top: 0; }

.t-path { padding: 0; overflow: hidden; }
.t-path-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--sp-4);
  flex-wrap: wrap;
  padding: var(--sp-5);
  border-bottom: 1px solid var(--border);
}
.t-path-title { display: grid; gap: var(--sp-2); flex: 1 1 360px; min-width: 0; }
.t-path-title h2 { margin: 0; }
.t-path-title p { margin: 0; max-width: 70ch; }
.t-unit { padding: var(--sp-4) var(--sp-5) var(--sp-5); }
.t-unit + .t-unit { border-top: 1px solid var(--border); }
.t-unit-head { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--sp-3); flex-wrap: wrap; margin-bottom: var(--sp-3); }
.t-unit-title { display: grid; gap: var(--sp-1); flex: 1 1 320px; min-width: 0; }
.t-unit-title h3 { margin: 0; font-family: var(--font-display); font-size: var(--t-lg); font-weight: 600; }
.t-unit-title p { margin: 0; }
/* Fixed columns, so the columns of every unit line up down the page. */
.t-lessons { table-layout: fixed; min-width: 760px; }
.t-lessons th:nth-child(2) { width: 150px; }
.t-lessons th:nth-child(3) { width: 210px; }
.t-lessons th:nth-child(4) { width: 330px; }
.t-lessons td { vertical-align: middle; overflow-wrap: anywhere; }
.t-lessons tbody tr:hover { background: var(--bg-hover); }
.t-lesson-link { font-weight: 650; text-decoration: none; }
.t-lesson-link:hover { text-decoration: underline; }
.t-steps { white-space: nowrap; }
.t-row-actions { display: flex; flex-wrap: wrap; gap: var(--sp-1); justify-content: flex-end; }
.t-row-actions > * { margin-top: 0; }
.t-empty { margin: 0; padding: var(--sp-3) var(--sp-4); border: 1px dashed var(--border-strong); border-radius: var(--r-md); }
.t-path > .t-empty { margin: var(--sp-5); }

/* Dialogs: one sheet style for every form that opens over a list. */
dialog.sheet {
  width: min(560px, calc(100vw - 32px));
  max-height: calc(100vh - 48px);
  overflow: auto;
  padding: var(--sp-6);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--bg-elevated);
  color: var(--fg);
  box-shadow: var(--shadow-lg);
}
dialog.sheet.wide { width: min(800px, calc(100vw - 32px)); }
dialog.sheet::backdrop { background: var(--scrim); }
dialog.sheet h2 { margin-top: 0; }

/* Opening and closing (D031): the sheet fades in while it grows from 96% to
   full size, the scrim fades in behind it, and both reverse on close.
   `display` and `overlay` are transitioned with allow-discrete so the dialog
   stays on screen, and on top, until its closing fade has finished.
   @starting-style gives the values it animates FROM when it opens. A browser
   without these simply shows and hides the dialog at once, as before. */
dialog.sheet {
  opacity: 0;
  transform: translateY(6px) scale(0.96);
  transition: opacity var(--dur-med) var(--ease-out),
              transform var(--dur-med) var(--ease-out),
              overlay var(--dur-med) allow-discrete,
              display var(--dur-med) allow-discrete;
}
dialog.sheet[open] { opacity: 1; transform: none; }
dialog.sheet::backdrop {
  opacity: 0;
  transition: opacity var(--dur-med) var(--ease),
              overlay var(--dur-med) allow-discrete,
              display var(--dur-med) allow-discrete;
}
dialog.sheet[open]::backdrop { opacity: 1; }
@starting-style {
  dialog.sheet[open] { opacity: 0; transform: translateY(6px) scale(0.96); }
  dialog.sheet[open]::backdrop { opacity: 0; }
}
.sheet-head { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--sp-3); margin-bottom: var(--sp-4); }
.sheet-head h2 { margin: 0 0 var(--sp-1); }
.sheet-head p { margin: 0; }
.sheet-head button { margin-top: 0; flex: none; }
.field-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--sp-4); }
.field-row.three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.field-row.numbers { grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); align-items: end; }
.inline-field { display: flex; gap: var(--sp-2); align-items: center; }
.inline-field > select, .inline-field > input { flex: 1; min-width: 0; }
.inline-field > button { margin-top: 0; flex: none; }

/* --- the lesson builder ---------------------------------------------------- */
.builder-head { display: grid; gap: var(--sp-2); margin-bottom: var(--sp-3); }
.builder-head h1 { margin: 0; font-size: var(--t-2xl); }
.builder-bar {
  position: sticky;
  top: var(--header-h);
  z-index: 20;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
  margin: 0 0 var(--sp-5);
  padding: var(--sp-3) 0;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}
.builder-bar .button-row { margin-top: 0; }
.save-state { font-size: var(--t-sm); color: var(--fg-muted); }
.save-state.pending { color: var(--warn); }
.save-state.ok { color: var(--ok); }
.save-state.error { color: var(--danger); font-weight: 650; }

.builder-layout { display: grid; grid-template-columns: 290px minmax(0, 1fr); gap: var(--sp-5); align-items: start; }
.builder-side { display: grid; gap: var(--sp-4); position: sticky; top: calc(var(--header-h) + 72px); }
.builder-side > .card { margin-bottom: 0; padding: var(--sp-4); }
.builder-side h2 { font-size: var(--t-lg); margin-bottom: var(--sp-3); }
.builder-rail { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; position: relative; }
.builder-rail::before { content: ""; position: absolute; left: 20px; top: 20px; bottom: 20px; width: 2px; background: var(--border-strong); }
.builder-rail .rail-step { grid-template-columns: 26px minmax(0, 1fr) auto; }
.builder-rail li.now .rail-step { background: var(--brand-soft); }
.builder-rail li.now .rail-mark { border-color: var(--brand); color: var(--brand); }
.builder-rail li.free .rail-mark { border-color: var(--accent); color: var(--accent); }
.lint-dot {
  min-width: 20px;
  padding: 0 6px;
  border-radius: var(--r-pill);
  background: var(--warn-soft);
  color: var(--warn);
  font-size: var(--t-xs);
  font-weight: 700;
  line-height: 20px;
  text-align: center;
}
.add-step { margin-top: var(--sp-4); padding-top: var(--sp-3); border-top: 1px solid var(--border); }
.add-step label { margin-top: 0; }
.lint-panel > p { margin: 0 0 var(--sp-3); }
.lint-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-1); max-height: 40vh; overflow: auto; }
.lint-item {
  display: block;
  width: 100%;
  margin: 0;
  padding: var(--sp-2) var(--sp-3);
  border: 0;
  border-left: 3px solid var(--warn);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  background: var(--warn-soft);
  color: var(--fg);
  font-size: var(--t-sm);
  font-weight: 400;
  line-height: 1.45;
  text-align: left;
}
.lint-item:hover { background: var(--bg-hover); color: var(--fg); }
.lint-info .lint-item { border-left-color: var(--border-strong); background: var(--bg-sunken); }
.lint-where { font-weight: 700; }
.lint-clear { color: var(--ok); font-size: var(--t-sm); }

.builder-main { display: grid; gap: var(--sp-4); min-width: 0; }
.builder-main > .card { margin-bottom: 0; }
.meta-more, .editor-more { margin-top: var(--sp-4); }
.meta-more summary, .editor-more summary { cursor: pointer; color: var(--brand); font-weight: 600; width: fit-content; }
.editor-top { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--sp-3); flex-wrap: wrap; margin-bottom: var(--sp-2); }
.editor-top h2 { margin: 0; font-size: var(--t-xl); }
.editor-top h2:focus { outline: none; }
.editor-field label { margin-top: var(--sp-4); }
.editor-field textarea[lang="en"] { font-family: var(--font-display); font-size: var(--t-base); line-height: 1.6; }
.media-panel { margin-top: var(--sp-5); padding-top: var(--sp-4); border-top: 1px solid var(--border); display: grid; gap: var(--sp-3); }
.media-panel h3 { margin: 0; font-family: var(--font-display); font-size: var(--t-lg); font-weight: 600; }
.media-panel > p { margin: 0; }
.media-current { display: grid; gap: var(--sp-2); justify-items: start; }
.media-current audio { width: 100%; max-width: 520px; }
.media-current button, .media-panel .step-image + button { margin-top: 0; }
.media-panel .step-image { margin-bottom: var(--sp-2); }
.model-recorder { padding: var(--sp-4); background: var(--bg-sunken); border-radius: var(--r-md); }

/* --- class insights -------------------------------------------------------- */
.ins-lessons { display: grid; gap: var(--sp-2); }
.ins-lesson { border: 1px solid var(--border); border-radius: var(--r-md); background: var(--bg-elevated); }
.ins-lesson[open] { border-color: var(--border-strong); }
.ins-lesson > summary {
  display: grid;
  grid-template-columns: minmax(0, 2fr) 110px minmax(150px, 1.1fr) 90px minmax(0, 1.4fr);
  gap: var(--sp-3);
  align-items: center;
  padding: var(--sp-3) var(--sp-4);
  cursor: pointer;
  list-style: none;
  font-size: var(--t-md);
}
.ins-lesson > summary::-webkit-details-marker { display: none; }
.ins-lesson > summary:hover { background: var(--bg-hover); }
.ins-title { display: grid; min-width: 0; }
.ins-title b { font-family: var(--font-display); font-size: var(--t-base); font-weight: 600; }
.ins-num { font-variant-numeric: tabular-nums; color: var(--fg-muted); }
.ins-num b { color: var(--fg); }
.ins-done, .rate { display: flex; align-items: center; gap: var(--sp-2); font-variant-numeric: tabular-nums; font-size: var(--t-sm); }
.ins-stall { font-size: var(--t-sm); color: var(--warn); }
.ins-stall b { color: var(--fg); }
.ins-idle { grid-column: span 3; font-size: var(--t-sm); color: var(--fg-muted); }
.bar { flex: 1; min-width: 60px; height: 6px; border-radius: var(--r-pill); background: var(--bg-sunken); overflow: hidden; }
.bar > span { display: block; height: 100%; background: var(--ok); border-radius: var(--r-pill); }
.bar.low > span { background: var(--warn); }
.ins-steps { margin: 0; }
.ins-steps th:first-child, .ins-steps td:first-child { padding-left: var(--sp-4); }
.ins-steps .num, .ins-recent .num { text-align: right; font-variant-numeric: tabular-nums; }
.ins-steps .rate { max-width: 260px; }
.nowrap { white-space: nowrap; }
.word-picks { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.word-pick {
  margin: 0;
  padding: var(--sp-1) var(--sp-2) var(--sp-1) var(--sp-3);
  gap: var(--sp-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-pill);
  background: var(--bg-elevated);
  color: var(--fg);
  font-weight: 500;
}
.word-pick:hover { background: var(--bg-hover); color: var(--fg); }
.word-pick[aria-pressed="true"] { background: var(--brand); border-color: var(--brand); color: var(--brand-fg); }
.word-pick .count {
  min-width: 22px;
  padding: 0 6px;
  border-radius: var(--r-pill);
  background: var(--bg-sunken);
  color: var(--fg-muted);
  font-size: var(--t-xs);
  font-weight: 700;
  line-height: 20px;
}
.word-pick[aria-pressed="true"] .count { background: var(--brand-strong); color: var(--brand-fg); }
.word-chips-line .chip { display: inline-block; margin: 0 var(--sp-1) var(--sp-1) 0; padding: 2px var(--sp-3); border-radius: var(--r-pill); background: var(--brand-soft); font-size: var(--t-sm); }

/* An attempt opened by a teacher: the words of the text are buttons. */
.override-words { line-height: 2.1; }
.override-words .w {
  display: inline;
  margin: 0;
  padding: 1px 3px;
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  color: var(--fg);
  font: inherit;
  cursor: pointer;
}
.override-words .w:hover { outline: 1px solid var(--brand); color: var(--fg); background: var(--bg-hover); }
.override-words .w.unsure { background: var(--warn-soft); }
.override-words .w.missed { background: var(--bg-sunken); color: var(--fg-muted); }
.teacher-key { width: 14px; height: 10px; display: inline-block; border: 1px dashed var(--accent); border-radius: 2px; }
.signal-list.plain li { padding-left: 0; }
.raw-transcript summary { cursor: pointer; color: var(--brand); font-weight: 600; }
.raw-transcript p { margin-top: var(--sp-2); font-size: var(--t-md); color: var(--fg-muted); }
.verdict-pick { margin: var(--sp-4) 0 0; padding: var(--sp-3) var(--sp-4); border: 1px solid var(--border); border-radius: var(--r-md); }
.verdict-pick legend { padding: 0 var(--sp-1); font-size: var(--t-sm); font-weight: 650; }
.verdict-pick label { display: flex; align-items: center; gap: var(--sp-2); margin: var(--sp-1) 0; font-weight: 500; font-size: var(--t-md); }
.verdict-pick input { width: auto; }

@media (max-width: 900px) {
  .builder-layout { grid-template-columns: minmax(0, 1fr); }
  .builder-side { position: static; }
  .ins-lesson > summary { grid-template-columns: minmax(0, 1fr) auto; }
  .ins-lesson > summary .ins-done, .ins-lesson > summary .ins-stall { grid-column: 1 / -1; }
}
@media (max-width: 640px) {
  .field-row, .field-row.three { grid-template-columns: minmax(0, 1fr); }
  .builder-bar { position: static; }
  .builder-bar .button-row > button { width: auto; flex: 1; }
  .t-path-head, .t-unit { padding: var(--sp-4); }
  .t-row-actions { justify-content: flex-start; }
  dialog.sheet { padding: var(--sp-4); }
  .teach-head .button-row > button, .teach-head .button-row > .button { width: 100%; }
}
/* On a phone every <button> fills the row (see "narrow screens"). Words and
   chips are buttons too, and must stay the size of their text. */
@media (max-width: 640px) {
  .last-try .link-button, .word-pick, .override-words .w { width: auto; }
}

/* --- icons, topics and the app sidebar (D027) -----------------------------------
   Icons come from one same-origin sprite (static/icons.svg) and take the colour
   of the text around them. Each topic has a hue of its own, muted to sit with
   the ink navy, and a lighter one for the dark theme; a unit, its nodes and its
   banner draw from that one hue. */
.icon {
  width: 1.25em;
  height: 1.25em;
  flex: none;
  display: inline-block;
  vertical-align: -0.25em;
}

:root {
  --sidebar-w: 252px;
  --t-daily: #2f7d79;
  --t-education: #2a4f80;
  --t-travel: #2d6fa8;
  --t-career: #6b4c8a;
  --t-food: #a8620c;
  --t-health: #a33a55;
  --t-technology: #4351a8;
  --t-environment: #2e7d4f;
  --t-people: #8a5a44;
  --t-society: #4a5a70;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --t-daily: #74c6bf;
    --t-education: #8fb4ec;
    --t-travel: #7fb8e8;
    --t-career: #b9a0d8;
    --t-food: #e8b066;
    --t-health: #e895a8;
    --t-technology: #9aa6ec;
    --t-environment: #84cf9f;
    --t-people: #d4a58c;
    --t-society: #a9b6c9;
  }
}
:root[data-theme="dark"] {
  --t-daily: #74c6bf;
  --t-education: #8fb4ec;
  --t-travel: #7fb8e8;
  --t-career: #b9a0d8;
  --t-food: #e8b066;
  --t-health: #e895a8;
  --t-technology: #9aa6ec;
  --t-environment: #84cf9f;
  --t-people: #d4a58c;
  --t-society: #a9b6c9;
}
.topic-daily_life { --topic: var(--t-daily); }
.topic-education { --topic: var(--t-education); }
.topic-travel { --topic: var(--t-travel); }
.topic-career { --topic: var(--t-career); }
.topic-food { --topic: var(--t-food); }
.topic-health { --topic: var(--t-health); }
.topic-technology { --topic: var(--t-technology); }
.topic-environment { --topic: var(--t-environment); }
.topic-people { --topic: var(--t-people); }
.topic-society { --topic: var(--t-society); }
.topic-none { --topic: var(--brand); }

/* The sidebar: brand, the role's work as icon + label, the account at the foot.
   On the left, where the eye starts reading (the project owner's choice,
   07/10/2026, after first trying it on the right). */
body.has-sidebar { padding-left: var(--sidebar-w); --header-h: 0px; }
.app-sidebar {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  width: var(--sidebar-w);
  z-index: 50;
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
  padding: var(--sp-5) var(--sp-4);
  background: var(--bg-elevated);
  border-right: 1px solid var(--border);
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--border-strong) transparent;
}
.app-sidebar .brand { padding: 0 var(--sp-2); }
.brand-text { display: grid; line-height: 1.25; }
.app-sidebar .brand-sub { border-left: 0; padding-left: 0; font-size: 0.66rem; }
.app-sidebar .site-nav {
  flex-direction: column;
  gap: var(--sp-1);
  overflow: visible;
  align-self: auto;
}
.app-sidebar .site-nav a {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3);
  border: 0;
  border-radius: var(--r-md);
  color: var(--fg-muted);
  font-size: var(--t-base);
  font-weight: 600;
  transition: background-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}
.app-sidebar .site-nav a .icon { width: 24px; height: 24px; }
.app-sidebar .site-nav a:hover { background: var(--bg-hover); color: var(--fg); }
.app-sidebar .site-nav a[aria-current="page"] {
  background: var(--brand-soft);
  color: var(--brand);
  box-shadow: inset 3px 0 0 var(--accent);
}
.sidebar-foot { margin-top: auto; }
.app-sidebar .account-menu > summary { max-width: none; }
.app-sidebar .account-menu > summary::after { content: none; }
.account-button {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--bg-elevated);
  color: var(--fg);
  cursor: pointer;
}
.account-button:hover { background: var(--bg-hover); }
.avatar {
  width: 34px;
  height: 34px;
  flex: none;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--brand-soft);
  color: var(--brand);
}
.who { display: grid; min-width: 0; text-align: left; line-height: 1.3; }
.who .who-name { font-weight: 700; font-size: var(--t-md); }
.who .who-role { border: 0; padding: 0; }
.account-button .chevron { margin-left: auto; color: var(--fg-muted); transition: transform var(--dur-fast) var(--ease); }
.account-menu[open] .account-button .chevron { transform: rotate(180deg); }
.app-sidebar .account-panel { top: auto; bottom: calc(100% + 8px); left: 0; right: 0; min-width: 0; }
.account-panel a,
.account-panel button { display: flex; align-items: center; gap: var(--sp-3); }
.account-panel .icon { color: var(--fg-muted); }
/* The theme button -- in the account menu, or on the guest bar -- shows the
   theme it switches TO. */
.theme-light { display: none; }
:root[data-theme="dark"] .theme-dark { display: none; }
:root[data-theme="dark"] .theme-light { display: inline-block; }

/* Narrow screens: the sidebar becomes a slim bar on top (brand, account) and its
   navigation a tab bar at the bottom, for every role. */
@media (max-width: 900px) {
  body.has-sidebar { padding-left: 0; --header-h: 57px; }
  .app-sidebar {
    position: sticky;
    top: 0;
    bottom: auto;
    width: auto;
    height: auto;
    flex-direction: row;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-2) var(--sp-4);
    border-right: 0;
    border-bottom: 1px solid var(--border);
    overflow: visible;
  }
  .app-sidebar .brand-sub { display: none; }
  .app-sidebar .site-nav {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 60;
    flex-direction: row;
    justify-content: space-around;
    gap: 0;
    background: var(--bg-elevated);
    border-top: 1px solid var(--border);
    box-shadow: 0 -4px 16px rgba(16, 32, 52, 0.1);
    padding: 0 0 env(safe-area-inset-bottom);
  }
  .app-sidebar .site-nav a {
    flex: 1;
    flex-direction: column;
    justify-content: center;
    gap: 2px;
    padding: var(--sp-2) var(--sp-1);
    border-radius: 0;
    font-size: var(--t-xs);
    text-align: center;
  }
  .app-sidebar .site-nav a[aria-current="page"] {
    background: none;
    color: var(--brand);
    box-shadow: inset 0 3px 0 var(--accent);
  }
  .sidebar-foot { margin: 0 0 0 auto; }
  .account-button { width: auto; padding: var(--sp-1); border: 0; }
  .account-button .who, .account-button .chevron { display: none; }
  .app-sidebar .account-panel { top: calc(100% + 8px); bottom: auto; left: auto; right: 0; min-width: 240px; }
  .app-sidebar .account-menu[open] > .account-panel { transform-origin: top right; }
  body.has-sidebar main { padding-bottom: 80px; }
  body.has-sidebar .site-footer { padding-bottom: 72px; }
  body.has-sidebar .action-bar { bottom: 62px; }
}
@media print {
  body.has-sidebar { padding-left: 0; }
}

/* --- the path as a trail (D027) -------------------------------------------------
   Parts by level, units by topic, lessons as coins on a winding line. A coin
   is pressed like a button (a darker lower rim), a tick when complete, the
   topic's colour while in progress, a faded outline before it is reached. */
.path-head { margin-bottom: var(--sp-5); }
.path-title { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.path-title h1 { margin: 0; font-size: var(--t-2xl); }
.overall { gap: var(--sp-2); }
.overall > .icon { color: var(--warn); }
.path-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: var(--sp-6);
  align-items: start;
}
.path-side { position: sticky; top: calc(var(--header-h) + var(--sp-5)); display: grid; gap: var(--sp-4); }
.side-card { margin-bottom: 0; padding: var(--sp-4) var(--sp-5); display: grid; gap: var(--sp-3); }
.side-card h2 { margin: 0; font-size: var(--t-lg); display: flex; align-items: center; gap: var(--sp-2); }
.side-card h2 .icon { color: var(--accent); }
.side-card .eyebrow { display: flex; align-items: center; gap: var(--sp-2); margin: 0; }
.side-card p { margin: 0; }
.side-card .button { margin-top: 0; width: 100%; }

.next-card { border: 2px solid var(--topic, var(--brand)); }
.next-main { display: flex; align-items: center; gap: var(--sp-3); }
.next-main h2 { font-size: var(--t-lg); }
.next-meta { font-size: var(--t-sm); color: var(--fg-muted); }
.next-reason { font-size: var(--t-md); }
.today-row { display: flex; align-items: center; gap: var(--sp-4); }
.today-card .goal-ring { width: 92px; height: 92px; flex: none; }
.today-card .goal-ring span {
  width: 70px;
  height: 70px;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding-top: 0;
  font-size: var(--t-xs);
  line-height: 1.15;
}
.today-card .goal-ring b { font-size: var(--t-xl); }
.today-facts { display: grid; gap: var(--sp-1); font-size: var(--t-md); }
.streak { display: flex; align-items: center; gap: var(--sp-1); }
.streak .flame { color: #d9822b; width: 22px; height: 22px; }
.streak b { font-size: var(--t-lg); }
.today-card .week-dots { margin: 0; justify-content: space-between; }
.review-count { margin: 0; font-family: var(--font-display); font-size: var(--t-3xl); line-height: 1; color: var(--fg); }
.review-count small { font-family: var(--font); font-size: var(--t-md); color: var(--fg-muted); }

/* A part: one level. */
.path-part { margin-bottom: var(--sp-7); }
.part-head {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  margin-bottom: var(--sp-4);
  padding-bottom: var(--sp-3);
  border-bottom: 2px solid var(--border);
}
.part-head h2 { margin: 0; font-size: var(--t-2xl); }
.part-head .eyebrow { margin: 0; }
.part-badge {
  width: 52px;
  height: 52px;
  flex: none;
  border-radius: var(--r-lg);
  display: grid;
  place-items: center;
  background: var(--brand);
  color: var(--brand-fg);
  font-family: var(--font-display);
  font-size: var(--t-lg);
  font-weight: 700;
}
.band-b1 .part-badge { background: var(--accent); }
.band-b2 .part-badge,
.band-c1 .part-badge,
.band-c2 .part-badge { background: var(--fg); color: var(--bg-elevated); }
.part-count { margin-left: auto; font-size: var(--t-sm); color: var(--fg-muted); white-space: nowrap; }

/* A unit: one topic, a banner and its trail. */
.unit { margin-bottom: var(--sp-5); }
.unit-banner {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-4) var(--sp-5);
  border-radius: var(--r-lg);
  background: color-mix(in srgb, var(--topic) 12%, var(--bg-elevated));
  border: 1px solid color-mix(in srgb, var(--topic) 35%, var(--border));
}
.unit-icon {
  width: 48px;
  height: 48px;
  flex: none;
  border-radius: 14px;
  display: grid;
  place-items: center;
  background: var(--topic, var(--brand));
  color: var(--bg-elevated);
}
.unit-icon .icon { width: 26px; height: 26px; }
.unit-text { display: grid; gap: 2px; min-width: 0; }
.unit-topic {
  margin: 0;
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--topic, var(--fg-muted));
}
.unit-banner h3 { margin: 0; font-family: var(--font-display); font-size: var(--t-xl); font-weight: 600; }
.unit-fn { margin: 0; font-size: var(--t-sm); color: var(--fg-muted); }
.unit-count {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  font-size: var(--t-sm);
  font-weight: 700;
  color: var(--topic);
  white-space: nowrap;
}
.unit-empty { margin: var(--sp-3) 0 0; }

.unit-trail {
  --row: 124px;
  --coin: 64px;
  position: relative;
  height: calc(var(--n) * var(--row) - var(--row) + var(--coin) + 48px);
  margin: var(--sp-5) 0 var(--sp-3);
}
.trail-line { position: absolute; top: 0; left: 50%; transform: translateX(-50%); overflow: visible; }
.trail-line path { fill: none; stroke-width: 6; stroke-linecap: round; }
.trail-line path.ahead { stroke: var(--border-strong); stroke-dasharray: 1 12; }
.trail-line path.done { stroke: color-mix(in srgb, var(--ok) 70%, var(--bg-elevated)); }
.trail { list-style: none; margin: 0; padding: 0; }
.lesson-node {
  position: absolute;
  top: calc(var(--i) * var(--row));
  left: calc(50% + var(--x) - var(--coin) / 2);
  width: var(--coin);
  height: var(--coin);
}
.node-coin {
  position: relative;
  width: var(--coin);
  height: var(--coin);
  margin: 0;
  padding: 0;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--bg-elevated);
  color: color-mix(in srgb, var(--topic) 70%, var(--fg-muted));
  border: 3px solid color-mix(in srgb, var(--topic) 30%, var(--border));
  box-shadow: 0 5px 0 color-mix(in srgb, var(--topic) 25%, var(--border-strong));
  transition: transform var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
.node-coin:hover { background: var(--bg-elevated); transform: translateY(-2px); box-shadow: 0 7px 0 color-mix(in srgb, var(--topic) 30%, var(--border-strong)); }
.node-coin:active { transform: translateY(3px); box-shadow: 0 2px 0 color-mix(in srgb, var(--topic) 30%, var(--border-strong)); }
.node-coin[aria-expanded="true"] { outline: 3px solid color-mix(in srgb, var(--topic) 45%, transparent); outline-offset: 4px; }
.coin-icon { width: 28px; height: 28px; }
.lesson-node.started .node-coin {
  background: var(--topic);
  border-color: var(--topic);
  color: var(--bg-elevated);
  box-shadow: 0 5px 0 color-mix(in srgb, var(--topic) 60%, #000);
}
/* Steps passed so far, as a ring around a coin in progress. */
.lesson-node.started .node-coin::after {
  content: "";
  position: absolute;
  inset: -9px;
  border-radius: 50%;
  background: conic-gradient(var(--ok) calc(var(--done) * 1%), transparent 0);
  -webkit-mask: radial-gradient(circle, transparent 37px, #000 38px);
  mask: radial-gradient(circle, transparent 37px, #000 38px);
}
.lesson-node.completed .node-coin {
  background: var(--ok);
  border-color: var(--ok);
  color: var(--bg-elevated);
  box-shadow: 0 5px 0 color-mix(in srgb, var(--ok) 60%, #000);
}
.lesson-node.new:not(.next) .node-coin { opacity: 0.8; }
.lesson-node.next .node-coin {
  background: var(--topic);
  border-color: var(--topic);
  color: var(--bg-elevated);
  box-shadow: 0 5px 0 color-mix(in srgb, var(--topic) 60%, #000), 0 0 0 8px color-mix(in srgb, var(--topic) 18%, transparent);
}
.node-flag {
  position: absolute;
  bottom: calc(100% + 12px);
  left: 50%;
  transform: translateX(-50%);
  padding: var(--sp-1) var(--sp-3);
  border-radius: var(--r-md);
  background: var(--bg-elevated);
  color: var(--topic);
  border: 2px solid var(--topic);
  font-size: var(--t-xs);
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
  animation: flag-bob 1.6s var(--ease) infinite alternate;
}
.node-flag::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  margin-left: -6px;
  border: 6px solid transparent;
  border-top-color: var(--topic);
}
@keyframes flag-bob { to { transform: translate(-50%, -4px); } }
.lesson-node .lesson-title {
  position: absolute;
  top: calc(var(--coin) + 12px);
  left: 50%;
  transform: translateX(-50%);
  width: 168px;
  text-align: center;
  font-size: var(--t-sm);
  font-weight: 600;
  line-height: 1.3;
  color: var(--fg);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.lesson-node.new:not(.next) .lesson-title { color: var(--fg-muted); }

/* The card a coin opens. */
.node-card {
  position: absolute;
  z-index: 5;
  display: grid;
  gap: var(--sp-2);
  padding: var(--sp-4);
  background: var(--bg-elevated);
  border: 2px solid var(--topic, var(--brand));
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
}
.node-card::before {
  content: "";
  position: absolute;
  bottom: 100%;
  left: calc(var(--arrow) - 8px);
  border: 8px solid transparent;
  border-bottom-color: var(--topic, var(--brand));
}
.node-card h3 { margin: 0; font-family: var(--font-display); font-size: var(--t-lg); font-weight: 600; }
.card-summary { margin: 0; font-size: var(--t-sm); color: var(--fg-muted); }
.card-facts { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-3); font-size: var(--t-sm); color: var(--fg-muted); }
.card-facts li { display: inline-flex; align-items: center; gap: var(--sp-1); }
.node-card .button { margin-top: var(--sp-1); background: var(--topic, var(--brand)); color: var(--bg-elevated); }
.node-card .button:hover { filter: brightness(1.08); }
.practice-rules summary { display: flex; align-items: center; gap: var(--sp-2); }
.practice-rules summary .icon { color: var(--brand); }

@media (prefers-reduced-motion: reduce) {
  .node-flag { animation: none; }
}
@media (max-width: 1100px) {
  .path-grid { grid-template-columns: minmax(0, 1fr) 260px; gap: var(--sp-5); }
}
@media (max-width: 900px) {
  .path-grid { grid-template-columns: minmax(0, 1fr); }
  .path-side { position: static; grid-row: 1; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .path-side .next-card { grid-column: 1 / -1; }
}
@media (max-width: 640px) {
  .path-side { grid-template-columns: minmax(0, 1fr); }
  .unit-banner { padding: var(--sp-3) var(--sp-4); gap: var(--sp-3); }
  .unit-icon { width: 40px; height: 40px; border-radius: 12px; }
  .unit-banner h3 { font-size: var(--t-lg); }
  .unit-fn { display: none; }
  .lesson-node .lesson-title { width: 140px; }
  .part-head h2 { font-size: var(--t-xl); }
}

/* --- icons in the lesson player and on buttons (D027) ---------------------------- */
button .icon, .button .icon { width: 1.15em; height: 1.15em; }
.rail-mark .rail-icon { width: 15px; height: 15px; }
.step-rail li.passed .rail-mark .rail-icon { width: 14px; height: 14px; }
.stage-chip { display: inline-flex; align-items: center; gap: var(--sp-1); }
.stage-chip .icon { width: 14px; height: 14px; }
.verdict { display: flex; align-items: flex-start; gap: var(--sp-3); }
.verdict > .icon { width: 24px; height: 24px; margin-top: 1px; }
.step-feedback .tip { display: flex; align-items: flex-start; gap: var(--sp-2); }
.step-feedback .tip > .icon { color: var(--accent); margin-top: 2px; }
.model-audio .eyebrow, .replay .eyebrow { display: inline-flex; align-items: center; gap: var(--sp-1); }
.last-try > span { display: inline-flex; align-items: center; gap: var(--sp-1); }
.summary-mark {
  width: 64px;
  height: 64px;
  margin-bottom: var(--sp-3);
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--brand-soft);
  color: var(--brand);
}
.summary-mark.done { background: var(--ok-soft); color: var(--ok); }
.summary-mark .icon { width: 32px; height: 32px; }
.empty-mark .icon { width: 22px; height: 22px; }

/* --- teacher pages: topics and icons (D027) --------------------------------------- */
.t-unit-head { align-items: center; }
.t-unit-head > .unit-icon { width: 44px; height: 44px; border-radius: 12px; }
.topic-badge { background: color-mix(in srgb, var(--topic) 14%, var(--bg-elevated)); color: var(--topic); }
.builder-side h2, .media-panel h3, #editor-h { display: flex; align-items: center; gap: var(--sp-2); }
.builder-side h2 .icon { color: var(--accent); }
.editor-kind {
  width: 36px;
  height: 36px;
  border-radius: 10px;
  display: grid;
  place-items: center;
  background: var(--brand-soft);
  color: var(--brand);
}
.builder-rail .rail-mark .rail-icon { width: 15px; height: 15px; }

/* --- page headings carry the icon of their place in the sidebar (D027) ---------- */
h1:has(> .head-icon) { display: flex; align-items: center; gap: var(--sp-3); }
.head-icon {
  width: 42px;
  height: 42px;
  flex: none;
  border-radius: 12px;
  display: grid;
  place-items: center;
  background: var(--brand-soft);
  color: var(--brand);
}
.head-icon .icon { width: 24px; height: 24px; }

/* Trail refinements: the coin icon at full size (button .icon would shrink it),
   titles beside the coin on wide screens, under it with a ground on phones. */
.node-coin .coin-icon { width: 28px; height: 28px; }
.lesson-node .lesson-title { top: 50%; transform: translateY(-50%); width: var(--room, 176px); text-align: left; }
.lesson-node.label-right .lesson-title { left: calc(var(--coin) + 14px); }
.lesson-node.label-left .lesson-title { left: auto; right: calc(var(--coin) + 14px); text-align: right; }
.lesson-node.label-below .lesson-title {
  top: calc(var(--coin) + 10px);
  left: 50%;
  right: auto;
  transform: translateX(-50%);
  width: 140px;
  text-align: center;
  padding: 1px var(--sp-2);
  border-radius: var(--r-sm);
  background: var(--bg);
}
.unit-trail { height: calc(var(--n) * var(--row) - var(--row) + var(--coin) + 16px); }
.unit-trail:has(.label-below) { height: calc(var(--n) * var(--row) - var(--row) + var(--coin) + 48px); }

/* Phones: what to study next, then today and the reviews side by side, short. */
@media (max-width: 640px) {
  .path-side { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--sp-3); }
  .path-side .next-card { grid-column: 1 / -1; }
  .next-card .muted { display: none; }
  .side-card { padding: var(--sp-3) var(--sp-4); }
  .today-row { flex-direction: column; align-items: flex-start; gap: var(--sp-2); }
  .today-card .goal-ring { width: 72px; height: 72px; }
  .today-card .goal-ring span { width: 54px; height: 54px; }
  .today-card .goal-ring b { font-size: var(--t-lg); }
  .today-card .week-dots, .today-card .goal-text, .review-card .muted { display: none; }
  .review-count { font-size: var(--t-2xl); }
}

/* --- D028: accounts, counts in the sidebar, preparation, the band page ------- */

/* How many things wait behind a sidebar item. Claret, the colour that already
   marks "your place" in this design, so it reads as personal, not as an alarm. */
.nav-badge {
  margin-left: auto;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  display: inline-grid;
  place-items: center;
  border-radius: var(--r-pill);
  background: var(--accent);
  color: var(--bg-elevated);
  font-size: var(--t-xs);
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.nav-badge[hidden] { display: none; }
@media (max-width: 900px) {
  .app-sidebar .site-nav a { position: relative; }
  .app-sidebar .nav-badge {
    position: absolute;
    top: 3px;
    left: calc(50% + 5px);
    margin: 0;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
  }
}

/* "New since you last looked", on a row of the student's own list. */
.new-chip {
  display: inline-block;
  padding: 1px var(--sp-2);
  border-radius: var(--r-pill);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: var(--t-xs);
  font-weight: 700;
}

.small-note { font-size: var(--t-sm); }

/* The account page: the password form wider, the facts beside it. */
/* Two columns only when each can be at least 330px wide: the sidebar takes its
   share first, so the viewport width alone says little about the room left. */
.account-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 330px), 1fr));
  gap: var(--sp-5);
  align-items: start;
}
.account-grid .card { margin: 0; }
.account-grid h2,
.list-toolbar h2,
.slips-head h2 { display: flex; align-items: center; gap: var(--sp-2); }
.account-grid h2 .icon,
.list-toolbar h2 .icon,
.slips-head h2 .icon { color: var(--fg-muted); }

.pw-rules {
  list-style: none;
  margin: var(--sp-2) 0 0;
  padding: 0;
  display: grid;
  gap: 2px;
  font-size: var(--t-sm);
  color: var(--fg-muted);
}
.pw-rules li { display: flex; gap: var(--sp-2); align-items: baseline; }
.pw-rules li::before { content: "○"; flex: none; width: 1em; text-align: center; }
.pw-rules li.met { color: var(--ok); }
.pw-rules li.met::before { content: "✓"; }

label.check-row {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-weight: 400;
  font-size: var(--t-md);
}
.check-row input { width: auto; }

.facts { display: grid; margin: 0; }
.facts > div {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-2) 0;
  border-bottom: 1px solid var(--border);
}
.facts > div:last-child { border-bottom: 0; }
.facts dt { color: var(--fg-muted); }
.facts dd { margin: 0; font-weight: 600; text-align: right; }

/* Administrator: a compact creation form and a list with its tools. */
.inline-form {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3) var(--sp-4);
  align-items: flex-end;
}
.inline-form .field { display: grid; min-width: 150px; }
.inline-form .field[hidden] { display: none; }
.inline-form label { margin: 0 0 var(--sp-2); }
.inline-form .button-row { flex-basis: 100%; margin-top: var(--sp-1); }

.list-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-bottom: var(--sp-3);
}
.list-toolbar h2 { margin: 0; }
.toolbar-controls { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.toolbar-controls input,
.toolbar-controls select { width: auto; min-width: 170px; padding: var(--sp-2) var(--sp-3); }

.row-actions { display: flex; gap: var(--sp-1); justify-content: flex-end; }
.row-actions button {
  margin: 0;
  background: var(--bg-sunken);
  color: var(--brand);
  border-color: var(--border);
}
.row-actions button:hover { background: var(--bg-hover); color: var(--brand-strong); }
.row-actions button.danger { background: var(--danger-soft); color: var(--danger); }
tr.row-muted td { color: var(--fg-muted); }

/* One-time passwords on slips, cut along the dashes. */
.slips-head {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: var(--sp-3);
  margin-bottom: var(--sp-3);
}
.slips-head h2 { margin: 0; }
.slips-head .button-row { margin: 0; }
.slip-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
  gap: var(--sp-3);
}
.slip {
  border: 1px dashed var(--border-strong);
  border-radius: var(--r-md);
  padding: var(--sp-4);
  background: var(--bg-elevated);
  break-inside: avoid;
}
.slip-brand {
  margin: 0 0 var(--sp-3);
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent);
}
/* The two codes stacked, each on one line: a password broken over two lines
   is a password typed wrong. */
.slip-codes { display: grid; gap: var(--sp-2); }
.slip-codes > div { display: grid; gap: 2px; }
.slip-label {
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.slip-code {
  font-family: var(--font-mono);
  font-size: var(--t-xl);
  font-weight: 700;
  letter-spacing: 0.04em;
  white-space: nowrap;
}
.slip-note, .slip-name { margin: var(--sp-3) 0 0; font-size: var(--t-sm); overflow-wrap: anywhere; }
.slip-name { color: var(--fg-muted); }

@media print {
  body.printing-slips main > :not(#panel-accounts),
  body.printing-slips #panel-accounts > :not(#slips),
  body.printing-slips .slips-head,
  body.printing-slips .slips-warning,
  body.printing-slips .site-footer { display: none !important; }
  body.printing-slips #slips { border: 0; box-shadow: none; padding: 0; margin: 0; }
  body.printing-slips .slip-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10pt; }
  body.printing-slips .slip { border: 1px dashed #000; }
}

/* Preparation before an assessed answer: a ring that fills as time runs out. */
.prep {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3) var(--sp-4);
  margin: var(--sp-4) 0;
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--bg-sunken);
}
.prep[hidden] { display: none; }
.prep-ring {
  --p: 0;
  width: 58px;
  height: 58px;
  flex: none;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: conic-gradient(var(--brand) calc(var(--p) * 1turn), var(--border) 0);
}
.prep-ring > span {
  width: 46px;
  height: 46px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--bg-elevated);
  font-weight: 700;
  font-size: var(--t-lg);
  font-variant-numeric: tabular-nums;
}
.prep[data-state="running"] .prep-ring { background: conic-gradient(var(--accent) calc(var(--p) * 1turn), var(--border) 0); }
.prep-text { flex: 1 1 200px; min-width: 0; }
.prep-text p { margin: 0; }
.prep-title { font-weight: 700; }
.prep-actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; }
.prep-actions button { margin: 0; }
@media (prefers-reduced-motion: no-preference) {
  .prep-ring { transition: background var(--dur-slow) linear; }
}

/* The band page: thresholds as a ladder, one card per aspect with its label. */
.band-ladder {
  list-style: none;
  padding: 0;
  margin: var(--sp-3) 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-2);
}
.band-ladder li {
  display: grid;
  gap: 2px;
  padding: var(--sp-3);
  border-radius: var(--r-md);
  background: var(--brand-soft);
  font-size: var(--t-sm);
}
.band-ladder .band-name { font-family: var(--font-display); font-size: var(--t-xl); font-weight: 700; color: var(--brand); }
@media (max-width: 560px) { .band-ladder { grid-template-columns: 1fr; } }

.prov-legend { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin: 0 0 var(--sp-4); }
.prov {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  padding: 2px var(--sp-3);
  border-radius: var(--r-pill);
  font-size: var(--t-xs);
  font-weight: 700;
  white-space: nowrap;
}
.prov .icon { width: 16px; height: 16px; }
.prov-indirect { background: var(--ok-soft); color: var(--ok); }
.prov-not_measured { background: var(--warn-soft); color: var(--warn); }

.aspect-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
}
.aspect-head h2 { margin: 0; }
.aspect-machine { display: grid; gap: var(--sp-1); margin: var(--sp-3) 0 0; font-size: var(--t-md); }
.aspect-machine > div { display: grid; grid-template-columns: 9em minmax(0, 1fr); gap: var(--sp-2); }
.aspect-machine dt { color: var(--fg-muted); }
.aspect-machine dd { margin: 0; }
@media (max-width: 560px) { .aspect-machine > div { grid-template-columns: 1fr; gap: 0; } }
.band-columns {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-3);
  margin-top: var(--sp-4);
}
.band-col { padding: var(--sp-3); background: var(--bg-sunken); border-radius: var(--r-md); }
.band-col p { margin: 0; font-size: var(--t-md); }
.band-col .band-label { font-weight: 700; color: var(--brand); margin-bottom: var(--sp-1); }
@media (max-width: 760px) { .band-columns { grid-template-columns: 1fr; } }
.limit-list { margin: 0; padding-left: 1.2em; display: grid; gap: var(--sp-2); }
.band-help { display: inline-block; margin-top: var(--sp-2); font-size: var(--t-sm); }

/* --- D029: profile, Google, sessions, history ---------------------------------- */

/* Larger text for whoever asks for it on the account page. rem-based type
   scales with it; spacing in px stays put, so layouts do not jump. */
:root[data-font="large"] { font-size: 112.5%; }

/* An avatar is an icon on a soft wash of one topic colour. The default sits
   at the same specificity as the colour classes and before them, so a colour
   class always wins. */
.avatar,
.avatar-lg,
.swatch { --avatar: var(--brand); }
.avatar-daily { --avatar: var(--t-daily); }
.avatar-education { --avatar: var(--t-education); }
.avatar-travel { --avatar: var(--t-travel); }
.avatar-career { --avatar: var(--t-career); }
.avatar-food { --avatar: var(--t-food); }
.avatar-health { --avatar: var(--t-health); }
.avatar-technology { --avatar: var(--t-technology); }
.avatar-environment { --avatar: var(--t-environment); }
.avatar-people { --avatar: var(--t-people); }
.avatar-society { --avatar: var(--t-society); }
.avatar[class*="avatar-"] {
  background: color-mix(in srgb, var(--avatar) 16%, var(--bg-elevated));
  color: var(--avatar);
}
.avatar-lg {
  width: 64px;
  height: 64px;
  flex: none;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: color-mix(in srgb, var(--avatar) 16%, var(--bg-elevated));
  color: var(--avatar);
  border: 2px solid color-mix(in srgb, var(--avatar) 35%, transparent);
}
.avatar-lg .icon { width: 32px; height: 32px; }
.profile-head { display: flex; align-items: center; gap: var(--sp-4); }
.profile-head h1 { margin: 0; }

.tabs button .icon { width: 18px; height: 18px; }
.stack-cards { display: grid; gap: var(--sp-5); align-content: start; }
.stack-cards .card { margin: 0; }

/* Pickers: real radio inputs, drawn as tiles. The input stays in the
   accessibility tree and takes the arrow keys; only its circle is hidden. */
fieldset.picker { border: 0; padding: 0; margin: var(--sp-4) 0 0; }
fieldset.picker legend { font-size: var(--t-sm); font-weight: 650; margin-bottom: var(--sp-2); padding: 0; }
.icon-picker,
.color-picker { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.pick { position: relative; margin: 0; cursor: pointer; }
.pick input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
.pick-face {
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  border-radius: var(--r-md);
  border: 1px solid var(--border);
  background: var(--bg-sunken);
  color: var(--fg-muted);
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.pick:hover .pick-face { border-color: var(--border-strong); }
.pick input:checked + .pick-face { border: 2px solid var(--brand); background: var(--brand-soft); color: var(--brand); }
.pick input:focus-visible + .pick-face { outline: 2px solid var(--focus); outline-offset: 2px; }
.swatch .pick-face { border-radius: 50%; background: var(--avatar); border-color: transparent; }
.swatch input:checked + .pick-face { border: 3px solid var(--bg-elevated); box-shadow: 0 0 0 2px var(--avatar); background: var(--avatar); }

.chip-radio { position: relative; margin: 0; font-weight: 600; }
.chip-radio input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
.chip-radio span {
  display: inline-block;
  padding: var(--sp-2) var(--sp-4);
  border-radius: var(--r-pill);
  border: 1px solid var(--border-strong);
  background: var(--bg-elevated);
  color: var(--fg);
}
.chip-radio input:checked + span { background: var(--brand); border-color: var(--brand); color: var(--brand-fg); }
.chip-radio input:focus-visible + span { outline: 2px solid var(--focus); outline-offset: 2px; }
.goal-chips { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-top: var(--sp-3); }

/* Sessions and history: one line each, an icon, what, when. */
.session-list,
.activity-list { list-style: none; margin: var(--sp-3) 0 0; padding: 0; display: grid; }
.session,
.activity {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-3) 0;
  border-bottom: 1px solid var(--border);
}
.session:last-child,
.activity:last-child { border-bottom: 0; }
.session-icon,
.activity-icon {
  width: 32px;
  height: 32px;
  flex: none;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--bg-sunken);
  color: var(--fg-muted);
}
.session.current .session-icon { background: var(--ok-soft); color: var(--ok); }
.activity.warn .activity-icon { background: var(--warn-soft); color: var(--warn); }
.session-main,
.activity-main { flex: 1; min-width: 0; }
.session .link-button { margin-left: auto; flex: none; }

/* Login: a password field with its own show button, Google under a rule. */
.password-field { position: relative; }
.password-field input { padding-right: 64px; }
.password-field .link-button {
  position: absolute;
  right: var(--sp-3);
  top: 50%;
  transform: translateY(-50%);
  margin: 0;
  font-size: var(--t-sm);
}
.auth-or {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin: var(--sp-5) 0 var(--sp-3);
  color: var(--fg-muted);
  font-size: var(--t-sm);
}
.auth-or::before,
.auth-or::after { content: ""; flex: 1; height: 1px; background: var(--border); }
.google-button { width: 100%; margin-top: 0; }
/* The Google mark keeps its own colours (branding guidelines); a white disc
   behind it keeps the yellow and green readable on the dark theme. */
.google-logo {
  flex: none;
  width: 18px;
  height: 18px;
  padding: 2px;
  border-radius: 50%;
  background: #fff;
  box-sizing: content-box;
}
.auth-help { margin-top: var(--sp-4); font-size: var(--t-sm); }
.auth-help summary { cursor: pointer; color: var(--brand); font-weight: 600; }
.auth-help p { margin: var(--sp-2) 0 0; color: var(--fg-muted); }

/* Administrator's account list and detail dialog (D029). */
.account-cell { display: flex; align-items: center; gap: var(--sp-2); }
.account-cell .avatar { width: 28px; height: 28px; }
.account-cell .avatar .icon { width: 16px; height: 16px; }
.account-open { font-weight: 700; text-decoration: none; color: var(--fg); }
.account-open:hover { text-decoration: underline; color: var(--brand); }
.sheet-head .avatar { width: 48px; height: 48px; }
.sheet-head .avatar .icon { width: 26px; height: 26px; }
#account-dialog h3 { margin: var(--sp-5) 0 var(--sp-2); font-size: var(--t-lg); }
#account-dialog .facts { margin-top: var(--sp-2); }
.role-form { margin-top: var(--sp-4); }
.toolbar-controls .button,
.toolbar-controls button { margin-top: 0; }

/* --- D030: blind double rating ---------------------------------------------------- */

/* A rater's sheets as numbered tiles: grey not opened, outlined in progress,
   filled when submitted. The number is the rater's own order, nothing more. */
.rate-batch .progress-bar { margin: var(--sp-3) 0; }
.sheet-grid {
  list-style: none;
  margin: var(--sp-4) 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}
.sheet-chip {
  min-width: 40px;
  height: 36px;
  padding: 0 var(--sp-2);
  display: inline-grid;
  place-items: center;
  border-radius: var(--r-md);
  border: 1px solid var(--border);
  background: var(--bg-sunken);
  color: var(--fg-muted);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  text-decoration: none;
}
.sheet-chip.opened { border-color: var(--brand); color: var(--brand); background: var(--bg-elevated); }
.sheet-chip.done { background: var(--ok-soft); border-color: transparent; color: var(--ok); }
.sheet-chip:hover { border-color: var(--border-strong); }

/* The sheet: five criteria as a column of label + number, then overall and band. */
fieldset.criteria-scores,
fieldset.band-choice,
fieldset.unscorable-reasons { border: 0; padding: 0; margin: var(--sp-4) 0 0; }
fieldset.criteria-scores legend,
fieldset.band-choice legend,
fieldset.unscorable-reasons legend { font-size: var(--t-sm); font-weight: 650; padding: 0; margin-bottom: var(--sp-2); }
.score-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 96px;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-1) 0;
}
.score-row label { margin: 0; font-weight: 600; font-size: var(--t-md); }
.score-row input { text-align: center; font-variant-numeric: tabular-nums; }
.overall-row { margin-top: var(--sp-3); padding-top: var(--sp-3); border-top: 1px solid var(--border); }
.overall-row label { font-weight: 700; }
.band-choice .chips { display: flex; gap: var(--sp-2); }
#sheet-audio { width: 100%; margin-top: var(--sp-2); }

/* Administrator: raters to pick, progress per rater, the report's tables. */
.rater-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: var(--sp-1) var(--sp-4); }
.rater-progress {
  display: grid;
  grid-template-columns: 7em minmax(60px, 1fr) auto;
  align-items: center;
  gap: var(--sp-2);
  margin-top: var(--sp-1);
  font-size: var(--t-sm);
}
.progress-bar.small { height: 6px; }
.adjudicate-form { display: flex; flex-wrap: wrap; gap: var(--sp-1); margin-top: var(--sp-2); }
.adjudicate-form select { width: auto; padding: var(--sp-1) var(--sp-2); }
.adjudicate-form button { margin: 0; }
#report-dialog h3 { margin: var(--sp-5) 0 var(--sp-2); font-size: var(--t-lg); }
table.confusion { width: auto; margin: var(--sp-3) 0; }
table.confusion th, table.confusion td { text-align: center; min-width: 52px; }
.rater-progress .progress-bar { margin: 0; }
.sheet-chip.seen { border-style: dashed; color: var(--fg-muted); text-decoration: line-through; }

/* --- D032: achievements, the record, the week -------------------------------------- */

/* Today: the record under the streak, the rest days on the dots (dashed: no
   practice, yet the streak held), and the week against the one before. */
.streak-best { margin: 0; font-size: var(--t-sm); color: var(--fg-muted); }
.streak-best b { color: var(--fg); font-variant-numeric: tabular-nums; }
.week-dots li.rest .dot { background: transparent; border: 1.5px dashed var(--ok); }
.week-sum { margin: 0; font-size: var(--t-sm); color: var(--fg-muted); font-variant-numeric: tabular-nums; }
.week-sum b { color: var(--fg); font-weight: 600; }

/* The practice page's card: a tally, the last few medals, the nearest one. */
.ach-tally { margin-left: auto; font-size: var(--t-md); font-weight: 500; color: var(--fg-muted); font-variant-numeric: tabular-nums; }
.ach-tally b { color: var(--fg); font-size: var(--t-lg); font-weight: 700; }
.ach-row { display: flex; align-items: center; gap: var(--sp-3); }
.ach-medals { list-style: none; margin: 0; padding: 0; display: flex; gap: var(--sp-2); }
.ach-medals li {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--brand);
  color: var(--brand-fg);
}
.ach-medals .icon { width: 16px; height: 16px; }
.ach-fresh {
  display: inline-block;
  padding: 0 var(--sp-2);
  border-radius: var(--r-pill);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: var(--t-xs);
  font-weight: 700;
  line-height: 20px;
  vertical-align: 1px;
  white-space: nowrap;
}
.ach-next { font-size: var(--t-md); }
.ach-next span { color: var(--fg-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
.ach-card .bar, .ach .bar { flex: none; width: 100%; min-width: 0; }
.ach-all { display: inline-flex; align-items: center; gap: 2px; justify-self: start; font-size: var(--t-md); font-weight: 600; }
.ach-all .icon { width: 16px; height: 16px; }

/* The progress page's list: one tile per achievement, grouped. A tile reached
   is a filled medal; one not reached yet keeps its words readable and shows
   how far it is -- dimmed medals, never dimmed text. */
.ach-panel { scroll-margin-top: calc(var(--header-h) + var(--sp-4)); }
.ach-lede { margin: 0 0 var(--sp-2); max-width: 68ch; }
.ach-group { margin: var(--sp-5) 0 var(--sp-3); font-size: var(--t-base); }
.ach-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: var(--sp-3);
}
.ach {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--bg-elevated);
}
.ach.is-new { border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); }
.ach-medal {
  flex: none;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  border: 1px dashed var(--border-strong);
  background: var(--bg-sunken);
  color: var(--fg-muted);
}
.ach-medal .icon { width: 22px; height: 22px; }
.ach.is-unlocked .ach-medal { border: 1px solid var(--brand); background: var(--brand); color: var(--brand-fg); }
.ach-body { display: grid; gap: var(--sp-1); min-width: 0; flex: 1; }
.ach-title { font-size: var(--t-md); font-weight: 600; color: var(--fg); }
.ach-desc { font-size: var(--t-sm); color: var(--fg-muted); line-height: 1.45; }
.ach-state { font-size: var(--t-xs); color: var(--fg-muted); font-variant-numeric: tabular-nums; }
.ach.is-unlocked .ach-state { color: var(--ok); font-weight: 600; }
@media (prefers-reduced-motion: no-preference) {
  .ach.is-new .ach-medal { animation: medal-in 520ms var(--ease-out) both; }
}
@keyframes medal-in { from { scale: 0.6; opacity: 0; } }

/* The side column stops following the page when it is taller than the
   window (practice_path.js, fitSide): its last cards would stay hidden. */
.path-side.is-tall { position: static; }

/* Two columns of side cards: this one under the two short ones, across. */
@media (max-width: 900px) {
  .path-side .ach-card { grid-column: 1 / -1; }
}
@media (max-width: 640px) {
  .today-card .week-sum, .today-card .streak-best { display: none; }
}

/* --- D033: service plans, payment, receipts ------------------------------------------- */

/* The administrator's tab: the plan in force beside what is used, then buying. */
.bill-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: var(--sp-4); align-items: start; }
.bill-grid > .card { margin-bottom: 0; }
#billing-body > .card, #billing-body > .bill-grid { margin-bottom: var(--sp-4); }
.bill-plan-name { margin: 0; font-size: var(--t-2xl); font-weight: 700; line-height: 1.1; color: var(--fg); }
.bill-facts { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: var(--sp-1) var(--sp-4); margin: var(--sp-3) 0; font-size: var(--t-md); }
.bill-facts dt { color: var(--fg-muted); }
.bill-facts dd { margin: 0; font-variant-numeric: tabular-nums; }
.bill-total { font-size: var(--t-lg); font-weight: 700; color: var(--fg); }
.bill-features { list-style: none; margin: var(--sp-2) 0 0; padding: 0; display: grid; gap: var(--sp-1); font-size: var(--t-md); }
.bill-features li { display: flex; align-items: center; gap: var(--sp-2); }
.bill-features .icon { width: 16px; height: 16px; flex: none; }
.bill-features li.on .icon { color: var(--ok); }
.bill-features li.off { color: var(--fg-muted); }
.bill-next { display: flex; align-items: center; gap: var(--sp-2); margin: var(--sp-2) 0; font-size: var(--t-md); }
.bill-next .icon { width: 16px; height: 16px; color: var(--accent); }
.bill-meter { margin: var(--sp-3) 0; }
.bill-meter-head { display: flex; justify-content: space-between; gap: var(--sp-3); margin-bottom: var(--sp-1); font-size: var(--t-md); }
.bill-meter-head b { font-variant-numeric: tabular-nums; }
.bill-meter .progress-bar { margin: 0; }
.bill-form { display: flex; flex-wrap: wrap; gap: var(--sp-3) var(--sp-4); align-items: flex-end; }
.bill-form .field { display: grid; min-width: 140px; }
.bill-form .field label { margin: 0 0 var(--sp-2); }
.bill-options { display: flex; flex-wrap: wrap; gap: var(--sp-3); border: 0; margin: 0; padding: 0; flex-basis: 100%; }
.bill-options legend { padding: 0; margin-bottom: var(--sp-2); font-weight: 600; }
.bill-option { position: relative; margin: 0; cursor: pointer; }
.bill-option input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
.bill-option span {
  display: grid;
  gap: 2px;
  min-width: 190px;
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-lg);
  background: var(--bg-elevated);
}
.bill-option small { color: var(--fg-muted); font-size: var(--t-sm); }
.bill-option input:checked + span { border-color: var(--brand); box-shadow: inset 0 0 0 1px var(--brand); background: var(--brand-soft); }
.bill-option input:focus-visible + span { outline: 2px solid var(--focus); outline-offset: 2px; }
/* D036. The Google sign-in card on the configuration tab. */
#google-setup .card-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); }
.google-kv th { width: 34%; }
.google-kv code { overflow-wrap: anywhere; }
.google-kv td .button, .google-kv td button { margin-left: var(--sp-2); vertical-align: middle; }
/* D035. The gateway to pay through: its name, how the payer pays, a test note. */
.bill-gateway span { min-width: 220px; max-width: 340px; }
.bill-gateway .badge { margin-left: var(--sp-1); font-style: normal; vertical-align: 1px; }
.bill-gateway-note { line-height: 1.4; }
.bill-problems { margin: var(--sp-2) 0 0; padding-left: var(--sp-5); }
.bill-quote { margin: var(--sp-4) 0; padding: var(--sp-3) var(--sp-4); border-radius: var(--r-md); background: var(--bg-sunken); min-height: 3rem; }
.bill-quote:empty { display: none; }
.bill-quote .bill-facts { margin: 0; }
#bill-pay { display: inline-flex; align-items: center; gap: var(--sp-2); }
.bill-tiers { list-style: none; margin: 0 0 var(--sp-4); padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--sp-3); }
.bill-tier { padding: var(--sp-4); border: 1px solid var(--border); border-radius: var(--r-lg); background: var(--bg-elevated); }
.bill-tier p { margin: 0 0 var(--sp-1); }
.bill-tier-name { font-size: var(--t-lg); font-weight: 700; }
.bill-tier-price b { font-size: var(--t-lg); }
.bill-always { margin: var(--sp-1) 0 var(--sp-3); padding-left: var(--sp-5); font-size: var(--t-md); }
#bill-org-form .field { flex: 1 1 360px; }
.bill-key { margin-top: var(--sp-4); }
.bill-key summary { cursor: pointer; font-weight: 600; }
.bill-key textarea { width: 100%; font-family: var(--font-mono); font-size: var(--t-sm); margin: var(--sp-2) 0; }

/* The comparison of plans: what every plan has, then what each step adds. */
.ladder-lede { max-width: 72ch; }
.ladder { width: 100%; min-width: 640px; table-layout: fixed; border-collapse: separate; border-spacing: 0; }
.ladder th, .ladder td { padding: var(--sp-2) var(--sp-3); text-align: center; vertical-align: middle; border-bottom: 1px solid var(--border); white-space: normal; }
.ladder thead th:first-child { width: 40%; }
.ladder th { background: transparent; }
.ladder tbody th { text-align: left; font-weight: 500; color: var(--fg); font-size: var(--t-md); line-height: 1.4; }
.ladder thead th { vertical-align: bottom; padding-top: var(--sp-4); }
.ladder tr:hover > td { background: transparent; }
.ladder-col { width: 20%; position: relative; }
.ladder-col.is-current { background: var(--brand-soft); border-radius: var(--r-md) var(--r-md) 0 0; }
.ladder-name { display: block; font-size: var(--t-lg); font-weight: 700; color: var(--fg); }
.ladder-price { display: grid; gap: 2px; margin: var(--sp-1) 0; font-weight: 400; }
.ladder-price b { font-size: var(--t-base); font-variant-numeric: tabular-nums; }
.ladder-price small { color: var(--fg-muted); font-size: var(--t-xs); }
.ladder-pick {
  display: inline-block;
  margin-bottom: var(--sp-1);
  padding: 0 var(--sp-2);
  border-radius: var(--r-pill);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: var(--t-xs);
  font-weight: 700;
  line-height: 20px;
}
.ladder-yes .icon { width: 18px; height: 18px; color: var(--ok); vertical-align: -3px; }
.ladder-no { color: var(--fg-muted); }
.ladder-gain th { font-weight: 600; }
.ladder-actions td, .ladder-actions th { border-bottom: 0; padding-top: var(--sp-3); }
.ladder-actions button { margin: 0; }
.bill-hint { margin: var(--sp-2) 0 0; }
.bill-features li.off .icon { color: var(--fg-muted); }

/* The payment pages: one card in the middle, the amount first. */
.pay-page { max-width: 640px; margin: 0 auto; display: grid; gap: var(--sp-4); }
.pay-banner { margin: 0; }
.pay-card { margin: 0; }
.pay-card .eyebrow { display: flex; align-items: center; gap: var(--sp-2); }
.pay-label { margin: var(--sp-2) 0 0; color: var(--fg-muted); font-size: var(--t-md); }
.pay-amount { margin: 0 0 var(--sp-3); font-size: var(--t-3xl); line-height: 1.1; font-variant-numeric: tabular-nums; }
.pay-facts { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: var(--sp-2) var(--sp-4); margin: var(--sp-3) 0; font-size: var(--t-md); }
.pay-facts dt { color: var(--fg-muted); }
.pay-facts dd { margin: 0; overflow-wrap: anywhere; }
.pay-actions { display: grid; gap: var(--sp-2); margin-top: var(--sp-4); }
.pay-actions button { width: 100%; margin: 0; justify-content: center; display: inline-flex; align-items: center; gap: var(--sp-2); }
.pay-lede { font-size: var(--t-base); }
.pay-mark { width: 56px; height: 56px; border-radius: 50%; display: grid; place-items: center; background: var(--bg-sunken); color: var(--fg-muted); }
.pay-mark .icon { width: 30px; height: 30px; }
.pay-mark.ok { background: var(--ok-soft); color: var(--ok); }
.pay-mark.bad { background: var(--danger-soft); color: var(--danger); }

/* The receipt: a sheet that prints on its own. */
.receipt { max-width: 760px; }
.receipt-tools { justify-content: space-between; margin: 0; }
.receipt-tools button { display: inline-flex; align-items: center; gap: var(--sp-2); margin: 0; }
.receipt-head { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--sp-4); border-bottom: 1px solid var(--border); padding-bottom: var(--sp-3); }
.receipt-head h1 { margin: 0; font-family: var(--font-mono); font-size: var(--t-xl); }
.receipt-head .eyebrow { margin: 0; }
.receipt-paid { margin: 0; text-align: right; color: var(--ok); font-weight: 700; }
.receipt-paid .icon { width: 18px; height: 18px; vertical-align: -3px; }
.receipt-paid small { color: var(--fg-muted); font-weight: 400; }
.receipt-lines { margin: var(--sp-3) 0; }
.receipt-lines .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.receipt-lines tfoot th { border-top: 2px solid var(--border-strong); }
.receipt-note { font-size: var(--t-sm); }
@media print {
  .receipt { max-width: none; }
  .receipt-sheet { border: 0; box-shadow: none; padding: 0; }
}

/* ===================================================== 6. depth and motion (D031)
   The product page's world, carried into every screen: one quiet glow of the
   brand colours behind the work, matte surfaces separated by hairlines and
   soft neutral shadows, and motion that is short and settles quietly. No
   gloss: highlights, bevels and coloured glows made the first version look
   like plastic. All of it is decoration or easing -- no information lives only
   here -- and the reduced motion rule of section 2 stops every animation. */

/* --- the ground. A fixed layer rather than a fixed background: it is
   composited, so a long page scrolls without repainting it. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(1000px 560px at 100% -8%, color-mix(in srgb, var(--brand) 10%, transparent), transparent 70%),
    radial-gradient(760px 460px at 15% -18%, color-mix(in srgb, var(--accent) 6%, transparent), transparent 72%);
}

/* --- frosted chrome. Wide screens only for the sidebar: on a phone it holds
   the fixed tab bar, and a backdrop filter would become that bar's
   containing block and pull it off the bottom of the screen. */
.site-header {
  background: color-mix(in srgb, var(--bg-elevated) 84%, transparent);
  -webkit-backdrop-filter: blur(14px) saturate(1.3);
  backdrop-filter: blur(14px) saturate(1.3);
}
@media (min-width: 901px) {
  .app-sidebar {
    background: color-mix(in srgb, var(--bg-elevated) 86%, transparent);
    -webkit-backdrop-filter: blur(18px) saturate(1.3);
    backdrop-filter: blur(18px) saturate(1.3);
  }
}
.app-sidebar .site-nav a[aria-current="page"] {
  background: linear-gradient(90deg, var(--brand-soft), color-mix(in srgb, var(--brand-soft) 35%, transparent));
}
.app-sidebar .site-nav a .icon { transition: transform var(--dur-med) var(--ease-out); }
.app-sidebar .site-nav a:hover .icon { transform: translateX(2px); }
.avatar { box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--brand) 18%, transparent); }

/* --- icon tiles: the page's icon and the empty state's mark, tinted and
   matte with a hairline edge, like the product page's. */
.head-icon,
.empty-state .empty-mark {
  color: var(--brand);
  background: var(--brand-soft);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--brand) 12%, transparent);
}
.empty-state {
  border: 1px dashed color-mix(in srgb, var(--border-strong) 80%, transparent);
  border-radius: 14px;
  background:
    radial-gradient(120% 90% at 50% 0%, color-mix(in srgb, var(--brand) 7%, transparent), transparent 70%),
    var(--bg-sunken);
}
.empty-state .empty-mark {
  width: 60px;
  height: 60px;
  margin: 0 auto;
  border-radius: 18px;
  display: grid;
  place-items: center;
  font-size: 1.6rem;
}
.empty-state .empty-mark .icon { width: 28px; height: 28px; }

/* --- surfaces */
.card {
  border-radius: 14px;
  border-color: color-mix(in srgb, var(--border) 85%, transparent);
  box-shadow: var(--shadow-card);
}
.table-scroll { border-radius: 12px; background: var(--bg-elevated); box-shadow: var(--shadow-sm); }
th { background: var(--bg-sunken); }
td { transition: background-color var(--dur-fast) var(--ease); }
.stat { border-radius: 12px; background: var(--bg-elevated); }
.stat b { font-size: var(--t-2xl); font-weight: 700; letter-spacing: -0.02em; }
.stat.attention { background: var(--warn-soft); }
.task { transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-med) var(--ease); }
.task:hover { box-shadow: var(--shadow-card); }
.toast {
  border-radius: 12px;
  background: var(--glass);
  -webkit-backdrop-filter: blur(16px) saturate(1.4);
  backdrop-filter: blur(16px) saturate(1.4);
}
dialog.sheet { border-radius: 14px; }
dialog.sheet::backdrop {
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
}

/* --- a row of blocks on one floor: the report's band podium, the class's band
   columns, the band page, the sign-in page. The blocks are the product page's
   (.cube); each sets its own height (--h) and how lit it is. */
.cube-stage {
  position: relative;
  isolation: isolate;
  display: flex;
  justify-content: center;
  padding: var(--sp-5) 0 var(--sp-3);
  perspective: 900px;
  perspective-origin: 50% 0%;
}
.cube-stage::after {
  content: "";
  position: absolute;
  z-index: -1;
  left: 12%;
  right: 12%;
  bottom: 4px;
  height: 26px;
  border-radius: 50%;
  background: radial-gradient(closest-side, color-mix(in srgb, var(--brand) 24%, transparent), transparent);
}
.cube-row {
  display: flex;
  align-items: flex-end;
  gap: var(--cube-gap, 22px);
  transform-style: preserve-3d;
  transform: rotateX(-14deg) rotateY(-24deg);
}
.cube-row .cube { --w: 62px; --d: 62px; }
.cube-row .cube .f-front { font-size: 1.05rem; padding-top: 10px; }
.cube .f-front small { display: block; margin-top: 2px; font-size: 0.72em; font-weight: 650; opacity: 0.85; }
.cube.tone-1 { --mix: 14%; }
.cube.tone-2 { --mix: 28%; }
.cube.is-dim { --mix: 16%; }
.cube.is-possible { --mix: 32%; }
.cube.is-band { --face: var(--accent); --ink: var(--fg-on-brand); }

/* The report: beside the score, where the estimate stands among the bands. */
.band-podium { flex: none; padding: var(--sp-2) var(--sp-3) 0; }
.band-podium .cube { --w: 46px; --d: 46px; }
.band-podium .cube-row { --cube-gap: 18px; }
.band-podium .cube .f-front { font-size: 0.9rem; padding-top: 7px; }

/* The sign-in page: the form on frosted glass, the blocks beside it. */
.auth-card {
  background: var(--glass);
  -webkit-backdrop-filter: blur(16px) saturate(1.3);
  backdrop-filter: blur(16px) saturate(1.3);
}
.auth-cubes { justify-content: flex-start; padding-left: var(--sp-6); margin: var(--sp-2) 0 var(--sp-6); }
.auth-cubes .cube-row { --cube-gap: 26px; }
.auth-cubes .cube { --w: 72px; --d: 72px; }
.band-columns { margin-bottom: var(--sp-2); }
.band-columns .cube-row { --cube-gap: 30px; }

/* --- a status says itself with a dot, and a running one breathes */
.badge:is(.COMPLETED, .REVIEW_REQUIRED, .REJECTED, .FAILED, .QUEUED, .RUNNING)::before {
  content: "";
  display: inline-block;
  width: 6px;
  height: 6px;
  margin: 0 6px 1px 0;
  border-radius: 50%;
  background: currentColor;
  vertical-align: middle;
}
.badge:is(.QUEUED, .RUNNING)::before { animation: breathe 1.4s ease-in-out infinite alternate; }
@keyframes breathe { to { opacity: 0.25; } }

/* --- progress fills with the brand gradient and grows in when drawn: the
   width is set inline, so the starting point needs !important to win. */
.progress-bar { background: var(--bg-hover); }
.progress-bar > span {
  background: linear-gradient(90deg, var(--brand), color-mix(in srgb, var(--brand), var(--accent) 22%));
  transition: width 700ms var(--ease-out);
}
.progress-bar.failed > span { background: var(--danger); }
@starting-style {
  .progress-bar > span { width: 0 !important; }
}

/* --- buttons: matte, with a hairline of shadow that grows a little on hover;
   the press of section 4 (one pixel down) is the only movement. Controls that
   only borrow the <button> element -- tabs, links in tables, chips, the menus
   -- stay flat (the list below). */
button,
.button {
  box-shadow: 0 1px 2px rgb(20 30 50 / 0.12);
}
button:not(:disabled):hover,
.button:not([aria-disabled="true"]):hover {
  box-shadow: 0 2px 8px -2px rgb(20 30 50 / 0.22);
}
:is(
  .tabs button, .link-button, .toast .toast-close, .header-button, .account-panel button,
  .account-button, .chips button, .evidence button.seek, .row-actions button,
  .reason-chips button, button.window-chunk, button.evidence, .rec-button
) {
  background-image: none;
  box-shadow: none;
}
:is(
  .tabs button, .link-button, .toast .toast-close, .header-button, .account-panel button,
  .account-button, .chips button, .evidence button.seek, .row-actions button,
  .reason-chips button, button.window-chunk, button.evidence
):hover,
:is(
  .tabs button, .link-button, .toast .toast-close, .header-button, .account-panel button,
  .account-button, .chips button, .evidence button.seek, .row-actions button,
  .reason-chips button, button.window-chunk, button.evidence
):active {
  transform: none;
  box-shadow: none;
}

/* --- folds open and close by sliding instead of jumping, where the browser
   can animate to an intrinsic height. The account menu is a <details> too,
   but its panel floats; it keeps its own pop-in. */
@supports selector(::details-content) {
  :root { interpolate-size: allow-keywords; }
  details:not(.account-menu)::details-content {
    block-size: 0;
    overflow-y: clip;
    transition: block-size var(--dur-slow) var(--ease-out),
                content-visibility var(--dur-slow) allow-discrete;
  }
  details:not(.account-menu)[open]::details-content { block-size: auto; }
}

/* --- arriving: anchors glide, and a page's blocks rise into place one after
   another as it opens, from a readable start rather than from nothing. */
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
  main > * { animation: rise-in 380ms var(--ease-out) both; }
  main > :nth-child(2) { animation-delay: 40ms; }
  main > :nth-child(3) { animation-delay: 80ms; }
  main > :nth-child(4) { animation-delay: 120ms; }
  main > :nth-child(n + 5) { animation-delay: 160ms; }
}
@keyframes rise-in { from { translate: 0 6px; opacity: 0.5; } }

@media print {
  body::before { display: none; }
  .card, .table-scroll { box-shadow: none; }
}

/* ===================================================== 7. the ground's art (D037)
   "Terraces of speech": terraced hills -- the climb from A2 to B2, a step at a
   time, as on the rice terraces every student here has seen -- whose ridges
   are the loudness envelopes of spoken phrases, a ripple per syllable. Drawn
   by tools/make_art.py from one geometry; every piece is decoration (W-NFR-3),
   hidden from screen readers, behind the work, off when printing, and never
   under text at a strength that would take it below AA (W-NFR-4).

   Two places carry it:
   - every page: a fixed ground -- contour lines high on the right, the hills
     low on the right. It holds still and stays put from page to page:
     scenery, not a show. Staff pages, where figures are read, have it quieter;
   - the product and sign-in pages: the hills as a wide scene in four tones,
     each ridge drawing itself in, the nearest one, in the accent, breathing;
     on the product page a low sun (a moon in the dark theme) rises behind
     the far hill.

   The pieces are masks, so the theme colours them: one ink, thinned per band. */
:root {
  --art-ink: var(--brand);       /* hills and lines, thinned by each band's alpha */
  --art-hills: 11%;              /* the ground's nearest band */
  --art-lines: 11%;              /* the ground's contour lines */
  --art-sun: var(--accent);      /* a low sun in the claret */
  --art-sun-mix: 24%;
  --art-strength: 1;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --art-ink: #9fb8dc;          /* moonlit hills */
    --art-hills: 10%;
    --art-lines: 10%;
    --art-sun: #e9c992;          /* the moon */
    --art-sun-mix: 30%;
  }
}
:root[data-theme="dark"] {
  --art-ink: #9fb8dc;
  --art-hills: 10%;
  --art-lines: 10%;
  --art-sun: #e9c992;
  --art-sun-mix: 30%;
}
/* Where figures are read, the scenery steps back. */
.role-teacher,
.role-admin { --art-strength: 0.62; }

/* --- the ground. Fixed and composited like the glow of section 6, so a long
   page scrolls without repainting it; named for view transitions so that it
   stays where it is while the page changes in front of it. */
.ground {
  position: fixed;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
  opacity: var(--art-strength);
  view-transition-name: ground;
}
::view-transition-old(ground) { display: none; }
::view-transition-new(ground) { animation: none; }
.ground > i { position: absolute; display: block; }
.ground-lines {
  top: 0;
  right: 0;
  width: min(900px, 72vw);
  aspect-ratio: 900 / 560;
  background: color-mix(in srgb, var(--art-ink) var(--art-lines), transparent);
  -webkit-mask: url("art/contours.svg") center / 100% 100% no-repeat;
  mask: url("art/contours.svg") center / 100% 100% no-repeat;
}
.ground-hills {
  right: 0;
  bottom: 0;
  width: min(1180px, 86vw);
  aspect-ratio: 1200 / 480;
  background: color-mix(in srgb, var(--art-ink) var(--art-hills), transparent);
  -webkit-mask: url("art/hills.svg") center / 100% 100% no-repeat;
  mask: url("art/hills.svg") center / 100% 100% no-repeat;
}
@media (max-width: 900px) {
  .ground-lines { width: 120vw; }
  .ground-hills { right: -18vw; width: 150vw; }
}

/* --- empty states: a horizon of the same hills along the foot of the box. */
.empty-state { position: relative; isolation: isolate; overflow: hidden; }
.empty-state::after {
  content: "";
  position: absolute;
  inset: auto 0 0;
  height: 72px;
  z-index: -1;
  pointer-events: none;
  background: color-mix(in srgb, var(--art-ink) 8%, transparent);
  -webkit-mask: url("art/hills.svg") center / 100% 100% no-repeat;
  mask: url("art/hills.svg") center / 100% 100% no-repeat;
}

/* --- the scene of the product and sign-in pages. The bands are opaque mixes
   of the ink into the page, so nearer hills hide farther ones; the scene fades
   out toward the left, where the words of the page stand on it, along a
   smoothstep: a straight ramp leaves a visible edge where it starts. */
.lp-terraces,
.auth-terraces {
  --fade-from: 6%;
  --fade-to: 62%;
  --fade-span: calc(var(--fade-to) - var(--fade-from));
  --fade: linear-gradient(90deg,
    transparent var(--fade-from),
    rgb(0 0 0 / 0.03) calc(var(--fade-from) + var(--fade-span) * 0.1),
    rgb(0 0 0 / 0.16) calc(var(--fade-from) + var(--fade-span) * 0.25),
    rgb(0 0 0 / 0.5) calc(var(--fade-from) + var(--fade-span) * 0.5),
    rgb(0 0 0 / 0.84) calc(var(--fade-from) + var(--fade-span) * 0.75),
    rgb(0 0 0 / 0.97) calc(var(--fade-from) + var(--fade-span) * 0.9),
    #000 var(--fade-to));
  position: absolute;
  left: calc(50% - 50vw);
  right: calc(50% - 50vw);
  bottom: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
  -webkit-mask-image: var(--fade);
  mask-image: var(--fade);
}
.lp-terraces {
  /* Taller on a wider screen, so the hills keep their shape as they stretch. */
  height: min(56%, clamp(260px, 25vw, 440px));
  /* landing.js sets --px, --py on the hero: the hills drift against the scene. */
  translate: calc(var(--px, 0) * -10px) calc(var(--py, 0) * -4px);
}
.auth-split { position: relative; isolation: isolate; }
/* On the sign-in page the words take the left and the form the middle: the
   hills come in from behind the form, and no sun peeks out at its edge. */
.auth-terraces {
  --fade-from: 34%;
  --fade-to: 80%;
  bottom: calc(-1 * var(--sp-7));
  height: clamp(240px, 22vw, 400px);
}
.auth-terraces .art-sun { display: none; }
/* Anchored right and never narrower than 960px: on a phone the hills are a
   window onto the right of the scene rather than squeezed into it. */
.art-terraces {
  position: absolute;
  right: 0;
  bottom: 0;
  display: block;
  width: max(100%, 960px);
  height: 100%;
  overflow: visible;
}
/* Sized by the scene's height, as the hills are, with its lower edge behind
   the far hill: rising, not floating. */
.art-sun {
  position: absolute;
  top: 14%;
  right: 9%;
  height: 23%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: color-mix(in srgb, var(--art-sun) var(--art-sun-mix), var(--bg));
}
.art-terraces .art-fill { fill: color-mix(in srgb, var(--art-ink) var(--band), var(--bg)); }
.art-terraces .b0 { --band: 8%; }
.art-terraces .b1 { --band: 13%; }
.art-terraces .b2 { --band: 18%; }
.art-terraces .b3 { --band: 24%; }
.art-terraces .art-wall,
.art-terraces .art-wave {
  fill: none;
  vector-effect: non-scaling-stroke;
  stroke-linecap: round;
}
.art-terraces .art-wall {
  stroke: color-mix(in srgb, var(--art-ink) calc(var(--band) + 5%), var(--bg));
  stroke-width: 1;
}
.art-terraces .art-wave {
  stroke: color-mix(in srgb, var(--art-ink) calc(var(--band) + 22%), var(--bg));
  stroke-width: 1.5;
}
/* The nearest phrase is a voice: in the accent. */
.art-terraces .b3 .art-wave {
  stroke: color-mix(in srgb, var(--accent) 62%, var(--bg));
  stroke-width: 2.2;
}
.art-terraces .art-band { transform-box: fill-box; transform-origin: 50% 100%; }

/* Each band rises into place, far to near; each ridge then draws itself in;
   the sun comes up; the nearest band breathes, slowly. Nothing of it with
   reduced motion: the scene is simply there. */
@media (prefers-reduced-motion: no-preference) {
  .art-terraces .art-band { animation: art-rise 1100ms var(--ease-out) both; }
  .art-terraces .b1 { animation-delay: 90ms; }
  .art-terraces .b2 { animation-delay: 180ms; }
  .art-terraces .b3 {
    animation:
      art-rise 1100ms var(--ease-out) 270ms both,
      art-breathe 7s ease-in-out 1.6s infinite alternate;
  }
  .art-terraces .art-wave {
    stroke-dasharray: 1;
    animation: art-draw 1800ms var(--ease-out) 380ms both;
  }
  .art-terraces .b1 .art-wave { animation-delay: 480ms; }
  .art-terraces .b2 .art-wave { animation-delay: 580ms; }
  .art-terraces .b3 .art-wave { animation-delay: 680ms; }
  .art-sun { animation: art-sunrise 2000ms var(--ease-out) 200ms both; }
}
@keyframes art-rise { from { transform: translateY(14px); opacity: 0; } }
@keyframes art-draw { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
@keyframes art-breathe { to { transform: scaleY(1.035); } }
@keyframes art-sunrise { from { transform: translateY(24px); opacity: 0; } }

@media print {
  .ground,
  .lp-terraces,
  .auth-terraces,
  .empty-state::after { display: none; }
}

/* ===================================================== 8. learning evidence (D039–D042) */
.pron-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-4); }
.pron-item { display: grid; gap: var(--sp-2); padding-bottom: var(--sp-4); border-bottom: 1px solid var(--border); }
.pron-item:last-child { border-bottom: 0; padding-bottom: 0; }
.pron-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); flex-wrap: wrap; }
.pron-examples { margin: 0; padding-left: var(--sp-5); font-size: var(--t-sm); }
.pron-tip { margin: 0; font-size: var(--t-sm); color: var(--fg-muted); }
.badge.warn { background: var(--warn-bg, var(--bg-sunken)); color: var(--warn); }
.kpi-list { margin: var(--sp-4) 0 0; padding-left: var(--sp-5); display: grid; gap: var(--sp-2); }

/* D040: the feedback study -- the learner's survey and the two arms side by side. */
.survey fieldset { border: 0; padding: 0; margin: 0 0 var(--sp-3); }
.survey legend { font-weight: 600; margin-bottom: var(--sp-1); }
.survey-scale { display: flex; flex-wrap: wrap; gap: var(--sp-3); }
.survey-scale label { display: inline-flex; align-items: center; gap: var(--sp-1); }
.kpi-arms { display: grid; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); gap: var(--sp-4); }
.kpi-arm h3 { margin: 0; }

/* ===================================================== 9. composed paths (D046) */
.plan-card { margin-top: var(--sp-5); display: grid; gap: var(--sp-4); }
.plan-head h2 { margin: 0 0 var(--sp-1); }
.plan-head .eyebrow { display: flex; align-items: center; gap: var(--sp-2); margin: 0 0 var(--sp-1); }
.plan-head p { margin: 0; max-width: 70ch; }
.plan-summary { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--sp-3); }
.plan-summary p { margin: 0; }
.plan-warnings p { margin: 0 0 var(--sp-1); }
.plan-unit { margin: var(--sp-4) 0 var(--sp-2); font-size: var(--t-base); }
.plan-list { margin: 0; padding-left: var(--sp-5); display: grid; gap: var(--sp-3); }
.plan-item { padding-left: var(--sp-1); }
.plan-item-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-2); }
.plan-item.status-completed .plan-item-head b { color: var(--fg-muted); }
.plan-meta { margin: var(--sp-1) 0 0; font-size: var(--t-sm); }
.plan-reasons { margin: var(--sp-1) 0 0; padding-left: var(--sp-4); font-size: var(--t-sm); color: var(--fg-muted); }
.plan-model { display: flex; align-items: center; gap: var(--sp-2); font-size: var(--t-sm); margin: var(--sp-3) 0 0; }
.plan-form summary { cursor: pointer; display: flex; align-items: center; gap: var(--sp-2); }
.plan-form form { margin-top: var(--sp-3); }
.plan-keep { display: flex; gap: var(--sp-2); align-items: baseline; margin: 0; font-weight: 400; }
.plan-keep input { width: auto; margin: 0; }
.goal-fieldset { border: 0; padding: 0; margin: var(--sp-3) 0 0; }
.goal-fieldset legend { font-weight: 600; padding: 0; }
.goal-fieldset .goal-chips { margin-top: var(--sp-2); }
#compose-result h3 { margin-top: 0; }

/* ================================================================= my runs (D053) */
.plain-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-2); font-size: var(--t-md); }
.pack-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: var(--sp-3); margin: var(--sp-3) 0; }
.pack { display: grid; gap: var(--sp-1); padding: var(--sp-4); border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--bg-elevated); }
.pack-runs { font-size: var(--t-xl); font-weight: 700; }
.pack-price { font-variant-numeric: tabular-nums; }
.pack .button { justify-self: start; margin-top: var(--sp-2); }

/* ================================================================= classes and assignments (D054) */
.assign-row { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--sp-3); padding: var(--sp-2) 0; border-bottom: 1px solid var(--border); }
.assign-row:last-child { border-bottom: 0; }
.band-spread { display: inline-flex; flex-wrap: wrap; gap: var(--sp-2); }
.band-chip { font-variant-numeric: tabular-nums; }
.join-code { font-size: var(--t-lg); letter-spacing: 0.12em; }

/* ================================================================= reminders (D055) */
.nudges { display: grid; gap: var(--sp-2); margin-bottom: var(--sp-4); }
.nudges[hidden] { display: none; }
.nudge { display: grid; gap: 2px; padding: var(--sp-3) var(--sp-4); border: 1px solid var(--border); border-left: 3px solid var(--accent); border-radius: var(--r-sm); background: var(--bg-elevated); color: inherit; text-decoration: none; }
.nudge:hover { background: var(--bg-hover); }
.nudge:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.nudge-urgent { border-left-color: var(--warn); }
.nudge-kind { font-size: var(--t-xs); font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--fg-muted); }

/* ================================================================= guided listening (D056) */
.replay-list { counter-reset: none; }
.replay-moment { display: grid; gap: var(--sp-2); padding: var(--sp-3) 0; border-bottom: 1px solid var(--border); }
.replay-moment:last-child { border-bottom: 0; }
.replay-head { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-2) var(--sp-3); }
.replay-moment fieldset { border: 0; margin: 0; padding: 0; display: grid; gap: var(--sp-1); }
