/* Examo admin console — design tokens and components.
 *
 * Follows the shadcn/ui convention (the Zenith template's idiom):
 *
 *   - separation by **hairline border**, not by shadow. Cards are flat.
 *   - a near-white canvas, not a grey one. Contrast comes from borders.
 *   - 14px base type, tight line-height, restrained weights.
 *   - small radii (8px cards, 6px controls) — soft, not pill-like.
 *   - muted chrome so the data is the loudest thing on the page.
 *
 * Written from scratch. No template CSS or assets are used; the convention is
 * the thing being followed, and it is nobody's property.
 *
 * ALL colour lives in :root. A rebrand, a dark mode, or a school-specific
 * variant is an edit to this block and nothing else.
 */

:root {
  /* ---- Brand ------------------------------------------------------------ */
  /* "Ink — the question" and "Flow Green — the answer". Used literally: green
   * marks answers, confirmations and live things. Nothing decorative is green. */
  --ink: #0c1f33;
  --flow: #08a45c;
  --lift: #3edc8c;   /* dark backgrounds only — fails contrast on white */
  --chalk: #f4f6f4;
  --muted-brand: #5a6b7a;

  /* ---- Surfaces --------------------------------------------------------- */
  --background: #ffffff;
  --card: #ffffff;
  --canvas: #fcfcfd;
  --subtle: #f7f8f9;      /* table hover, inset panels */
  --border: #e6e8eb;
  --border-strong: #d3d8dd;

  /* ---- Text ------------------------------------------------------------- */
  --foreground: var(--ink);
  --foreground-muted: var(--muted-brand);
  --foreground-subtle: #8a97a3;

  /* ---- Semantic --------------------------------------------------------- */
  /* Success is the brand green: an answer and a confirmation are one gesture.
   * Warning and danger are additions — the brand has no red or amber, and a
   * console that cannot say "this failed" in red is a worse console. Both are
   * muted so they sit beside Flow Green rather than shouting over it. */
  --success: var(--flow);
  --success-bg: #e9f7f0;
  --success-fg: #056b3c;
  --warning: #b26b00;
  --warning-bg: #fdf4e6;
  --warning-fg: #7d4b00;
  --danger: #b53a2c;
  --danger-bg: #fceeec;
  --danger-fg: #86281d;
  --info: #2b6a94;
  --info-bg: #ebf3f8;
  --info-fg: #1d4c6c;

  /* ---- Type ------------------------------------------------------------- */
  --font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  --font-mono: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;

  /* ---- Shape ------------------------------------------------------------ */
  --radius: 8px;
  --radius-sm: 6px;
  --radius-pill: 999px;
  --sidebar-width: 240px;
}

/* --------------------------------------------------------------------------
 * Reset
 * ----------------------------------------------------------------------- */

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

body {
  margin: 0;
  font-family: var(--font);
  font-size: 14px;
  line-height: 1.5;
  color: var(--foreground);
  background: var(--canvas);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a { color: var(--flow); text-decoration: none; }
a:hover { text-decoration: underline; }

h1, h2, h3 { margin: 0 0 0.35rem; font-weight: 600; letter-spacing: -0.014em; }
h1 { font-size: 1.35rem; }
h2 { font-size: 1rem; }
h3 { font-size: 0.9rem; }

/* --------------------------------------------------------------------------
 * Shell
 * ----------------------------------------------------------------------- */

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

/* Dark sidebar, full logo lock-up. The mark and wordmark are drawn white-on-Ink
 * — this is the ground they were designed for, so no tile is needed and the
 * brand reads at full strength. The near-white content area does the shadcn
 * work; the navigation is where Examo gets to look like itself. */
.sidebar {
  width: var(--sidebar-width);
  flex: 0 0 var(--sidebar-width);
  background: var(--ink);
  padding: 0.85rem 0.6rem;
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
}

.brand {
  display: flex; flex-direction: column; align-items: center;
  gap: 0.5rem;
  padding: 0.6rem 0.4rem 1.1rem;
}

/* The lock-up is transparent, so it sits flush on the sidebar with no tile and
 * no radius — the background is --ink, drawn once, and the artwork floats on
 * it. The asset previously shipped its own navy a shade off --ink, which meant
 * two blues on one surface and a visible square. One source of truth for the
 * brand colour is the stylesheet, not an image.
 *
 * `max-width` as well as `width` on purpose. The asset is intrinsically 512px
 * in a 240px sidebar, so if this rule is ever missed — an old stylesheet still
 * cached, a selector renamed — it overflows rather than degrading quietly. The
 * cap means the worst case is "too small", never "broken". */
.brand img {
  width: 104px;
  max-width: 100%;
  height: auto;
  display: block;
}
.brand .sub {
  font-size: 0.58rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: rgb(255 255 255 / 40%);
}

.nav { display: flex; flex-direction: column; gap: 1px; }

.nav-label {
  padding: 0.75rem 0.5rem 0.25rem;
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgb(255 255 255 / 32%);
}

.nav a {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.45rem 0.55rem;
  border-radius: var(--radius-sm);
  color: rgb(255 255 255 / 68%);
  font-size: 0.82rem;
  text-decoration: none;
  transition: background 110ms ease, color 110ms ease;
}
.nav a:hover { background: rgb(255 255 255 / 7%); color: #fff; text-decoration: none; }
.nav a.active { background: rgb(255 255 255 / 10%); color: #fff; font-weight: 500; }
/* Lift marks the current page. Ink is the only ground it is legible on, so
 * this is the one place in the console it appears. */
.nav a.active::before {
  content: "";
  position: absolute;
  left: -0.6rem;
  width: 3px; height: 18px;
  border-radius: 0 2px 2px 0;
  background: var(--lift);
}
.nav svg { width: 16px; height: 16px; flex: 0 0 16px; opacity: 0.9; }
.nav .count {
  margin-left: auto;
  font-size: 0.68rem; font-weight: 500;
  padding: 0.05rem 0.35rem;
  border-radius: 5px;
  background: rgb(255 255 255 / 12%); color: #fff;
}

/* A route that exists in the menu but is not built yet. Shown rather than
 * hidden: a missing item reads as "my role cannot see this", which is the one
 * thing it must not be confused with. */
.nav a.soon {
  color: rgb(255 255 255 / 34%);
  cursor: default;
}
.nav a.soon:hover { background: none; color: rgb(255 255 255 / 34%); }
.nav a.soon svg { opacity: 0.45; }
.nav a.soon .count {
  background: rgb(255 255 255 / 7%);
  color: rgb(255 255 255 / 50%);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.sidebar-foot {
  margin-top: auto;
  border-top: 1px solid rgb(255 255 255 / 10%);
  padding-top: 0.65rem;
  display: flex; align-items: center; gap: 0.5rem;
}
.sidebar-foot .who { font-size: 0.78rem; font-weight: 500; color: #fff;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sidebar-foot .role {
  color: var(--lift);
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  font-weight: 600;
}
.sidebar-foot .avatar { background: rgb(255 255 255 / 12%); }
.sidebar-foot .btn-ghost { color: rgb(255 255 255 / 55%); }
.sidebar-foot .btn-ghost:hover { background: rgb(255 255 255 / 8%); color: #fff; }

.avatar {
  width: 30px; height: 30px; flex: 0 0 30px;
  border-radius: 50%;
  background: var(--ink); color: #fff;
  display: grid; place-items: center;
  font-size: 0.7rem; font-weight: 500;
}
.avatar-sm { width: 26px; height: 26px; flex: 0 0 26px; font-size: 0.62rem; }

.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }

.topbar {
  background: var(--background);
  border-bottom: 1px solid var(--border);
  padding: 0.8rem 1.5rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}
.topbar h1 { margin: 0; font-size: 1.05rem; }

.content { padding: 1.5rem; max-width: 1360px; width: 100%; }

/* --------------------------------------------------------------------------
 * Cards — bordered, never shadowed
 * ----------------------------------------------------------------------- */

.card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.1rem 1.25rem;
}
.card + .card { margin-top: 0.85rem; }
.card-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; margin-bottom: 0.9rem;
}
.card-head h2 { margin: 0; }
.card-head .sub { color: var(--foreground-muted); font-size: 0.82rem; margin: 0.1rem 0 0; }
.card-flush { padding: 0; }
.card-flush .card-head { padding: 1.1rem 1.25rem 0; margin-bottom: 0.75rem; }

.grid { display: grid; gap: 0.85rem; }
.grid-4 { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }

/* Grid and flex items default to `min-width: auto`, which means "at least as
 * wide as my content". A 1600px rendered exam page therefore pushed its column
 * wider than the track and out over its neighbour — and `width: 100%` on the
 * image could not help, because the column itself had grown to fit it.
 *
 * `min-width: 0` is the fix and it belongs on the children rather than in the
 * track definition: the same rule then covers every grid on every screen,
 * including ones not written yet. */
.grid > * { min-width: 0; }

/* Stat card: label and tinted icon on one row, number, trend, then a sparkline
 * bleeding to the card's edges. The trend line is what makes it worth more
 * than a number — a count with no direction tells you nothing. */
.stat { padding-bottom: 0; overflow: hidden; }
.stat .top { display: flex; align-items: flex-start; justify-content: space-between; }
.stat .label { color: var(--foreground-muted); font-size: 0.78rem; font-weight: 500; }
.stat .icon {
  width: 28px; height: 28px; flex: 0 0 28px;
  border-radius: var(--radius-sm);
  display: grid; place-items: center;
}
.stat .icon svg { width: 15px; height: 15px; }
.icon-success { background: var(--success-bg); color: var(--success-fg); }
.icon-info    { background: var(--info-bg);    color: var(--info-fg); }
.icon-warning { background: var(--warning-bg); color: var(--warning-fg); }
.icon-muted   { background: var(--subtle);     color: var(--foreground-muted); }

.stat .value {
  font-size: 1.5rem; font-weight: 600; letter-spacing: -0.03em; line-height: 1.2;
  font-variant-numeric: tabular-nums; margin-top: 0.1rem;
}
.stat .trend {
  display: flex; align-items: center; gap: 0.2rem;
  font-size: 0.72rem; margin-bottom: 0.5rem;
}
.stat .trend svg { width: 12px; height: 12px; }
.trend-up { color: var(--success-fg); }
.trend-down { color: var(--success-fg); }
.trend-flat { color: var(--foreground-subtle); }
.trend-warn { color: var(--warning-fg); }
.stat .trend .vs { color: var(--foreground-subtle); }
.stat .spark {
  display: block; width: calc(100% + 2.5rem); height: 36px; margin: 0 -1.25rem;
}

/* --------------------------------------------------------------------------
 * Segmented control — the Revenue / Orders / Profit switcher
 * ----------------------------------------------------------------------- */

.segment { display: inline-flex; background: #f2f4f5; border-radius: var(--radius-sm);
  padding: 2px; }
.segment button {
  border: 0; background: transparent; font: inherit; font-size: 0.75rem;
  padding: 0.22rem 0.6rem; border-radius: 5px; cursor: pointer;
  color: var(--foreground-muted);
}
.segment button.on { background: var(--background); color: var(--foreground);
  font-weight: 500; }

/* --------------------------------------------------------------------------
 * Progress
 * ----------------------------------------------------------------------- */

.progress + .progress { margin-top: 0.7rem; }
.progress .head { display: flex; justify-content: space-between; font-size: 0.8rem;
  margin-bottom: 0.28rem; }
.progress .track { height: 5px; background: #f2f4f5; border-radius: 3px;
  overflow: hidden; }
.progress .fill { height: 100%; border-radius: 3px; background: var(--flow); }
.progress .fill.warn { background: var(--warning); }
.progress .fill.bad { background: var(--danger); }
.progress .why { font-size: 0.72rem; color: var(--foreground-subtle);
  margin-top: 0.2rem; }

/* --------------------------------------------------------------------------
 * Legend, for donuts and multi-series charts
 * ----------------------------------------------------------------------- */

.legend { font-size: 0.8rem; }
.legend .item { display: flex; align-items: center; gap: 0.42rem; padding: 0.18rem 0; }
.legend .swatch { width: 8px; height: 8px; border-radius: 2px; flex: 0 0 8px; }
.legend .n { margin-left: auto; color: var(--foreground-muted); font-weight: 500;
  font-variant-numeric: tabular-nums; }

/* --------------------------------------------------------------------------
 * Search
 * ----------------------------------------------------------------------- */

.search {
  display: flex; align-items: center; gap: 0.45rem;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 0.35rem 0.6rem; min-width: 260px;
  color: var(--foreground-subtle); font-size: 0.82rem;
}
.search svg { width: 15px; height: 15px; }
.search kbd {
  margin-left: auto; font: inherit; font-size: 0.68rem;
  border: 1px solid var(--border); border-radius: 4px; padding: 0 0.25rem;
}

/* --------------------------------------------------------------------------
 * Badges
 * ----------------------------------------------------------------------- */

.badge {
  display: inline-flex; align-items: center; gap: 0.28rem;
  padding: 0.1rem 0.5rem;
  border-radius: var(--radius-sm);
  font-size: 0.73rem;
  font-weight: 500;
  border: 1px solid transparent;
}
.badge-success { background: var(--success-bg); color: var(--success-fg); }
.badge-warning { background: var(--warning-bg); color: var(--warning-fg); }
.badge-danger  { background: var(--danger-bg);  color: var(--danger-fg); }
.badge-info    { background: var(--info-bg);    color: var(--info-fg); }
.badge-muted   { background: var(--subtle); color: var(--foreground-muted);
  border-color: var(--border); }

/* --------------------------------------------------------------------------
 * Buttons
 * ----------------------------------------------------------------------- */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.4rem;
  height: 34px;
  padding: 0 0.85rem;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  font: inherit;
  font-size: 0.85rem;
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  transition: background 110ms ease, border-color 110ms ease;
}
.btn:hover { text-decoration: none; }
.btn-primary { background: var(--flow); color: #fff; }
.btn-primary:hover { background: #07914f; }
.btn-secondary { background: var(--background); color: var(--foreground);
  border-color: var(--border-strong); }
.btn-secondary:hover { background: var(--subtle); }
.btn-ghost { background: transparent; color: var(--foreground-muted); }
.btn-ghost:hover { background: var(--subtle); color: var(--foreground); }
.btn-danger { background: var(--danger); color: #fff; }
.btn-danger:hover { background: #9c3125; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }

/* Tinted variants, for actions that carry a *direction* rather than weight.
 *
 * Solid colour stays for the paper-level decisions — Activate is primary green,
 * Withdraw is solid red — because those are rare and consequential. Per-item
 * actions happen fifty times a paper, and fifty solid buttons on one screen
 * shout equally and therefore say nothing. A tint reads as "this is the good
 * outcome / this is a correction / this is the last resort" while staying
 * secondary to the page's real decision. */
.btn-soft-success {
  background: var(--success-bg); color: var(--success-fg);
  border-color: #cbe7d8;
}
.btn-soft-success:hover { background: #d8f0e4; }
.btn-soft-warning {
  background: var(--warning-bg); color: var(--warning-fg);
  border-color: #f0e0bf;
}
.btn-soft-warning:hover { background: #fbeed3; }
.btn-soft-danger {
  background: var(--danger-bg); color: var(--danger-fg);
  border-color: #f0d3ce;
}
.btn-soft-danger:hover { background: #fbe0da; }
.btn-sm { height: 28px; padding: 0 0.6rem; font-size: 0.8rem; }
.btn-block { width: 100%; }

/* --------------------------------------------------------------------------
 * Forms
 * ----------------------------------------------------------------------- */

.field { margin-bottom: 0.9rem; }
.field label {
  display: block; margin-bottom: 0.3rem;
  font-size: 0.82rem; font-weight: 500; color: var(--foreground);
}
/* `.setting` rows carry bare inputs rather than labelled `.field` blocks — the
 * label is the setting's name in the left column — so they opt into the same
 * control styling here rather than duplicating it. */
.field input, .field select, .field textarea,
.setting input, .setting select {
  width: 100%;
  height: 36px;
  padding: 0 0.7rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  font: inherit;
  font-size: 0.875rem;
  color: var(--foreground);
  background: var(--background);
  transition: border-color 110ms ease, box-shadow 110ms ease;
}
.field textarea { height: auto; padding: 0.55rem 0.7rem; }
.field input:focus, .field select:focus, .field textarea:focus,
.setting input:focus, .setting select:focus {
  outline: none;
  border-color: var(--flow);
  box-shadow: 0 0 0 3px rgb(8 164 92 / 12%);
}
.field .hint { margin-top: 0.28rem; font-size: 0.78rem; color: var(--foreground-muted); }

/* --------------------------------------------------------------------------
 * Tables
 * ----------------------------------------------------------------------- */

.table { width: 100%; border-collapse: collapse; font-size: 0.86rem; }
.table th {
  text-align: left;
  padding: 0.55rem 1.25rem;
  font-size: 0.75rem;
  font-weight: 500;
  color: var(--foreground-muted);
  border-bottom: 1px solid var(--border);
  background: var(--subtle);
  white-space: nowrap;
}
.table td { padding: 0.65rem 1.25rem; border-bottom: 1px solid var(--border); }
.table tbody tr:hover { background: var(--subtle); }
.table tbody tr:last-child td { border-bottom: none; }
.table .num { text-align: right; font-variant-numeric: tabular-nums; }
.mono { font-family: var(--font-mono); font-size: 0.85em; }

/* --------------------------------------------------------------------------
 * Notices
 * ----------------------------------------------------------------------- */

.notice {
  padding: 0.65rem 0.85rem;
  border: 1px solid;
  border-radius: var(--radius-sm);
  font-size: 0.86rem;
  margin-bottom: 0.9rem;
}
.notice-error   { background: var(--danger-bg);  border-color: #f0d3ce;
  color: var(--danger-fg); }
.notice-success { background: var(--success-bg); border-color: #c9e9d9;
  color: var(--success-fg); }
.notice-warning { background: var(--warning-bg); border-color: #f2dfbe;
  color: var(--warning-fg); }
.notice-info    { background: var(--info-bg);    border-color: #cfe0ec;
  color: var(--info-fg); }

.empty { text-align: center; padding: 2.5rem 1rem; color: var(--foreground-muted); }
.empty h3 { color: var(--foreground); }

/* --------------------------------------------------------------------------
 * Login — the one page with no shell
 * ----------------------------------------------------------------------- */

.login-page {
  min-height: 100vh;
  display: grid;
  place-items: center;
  background: var(--ink);
  padding: 1.5rem;
}
.login-card {
  width: 100%;
  max-width: 380px;
  background: var(--card);
  border-radius: var(--radius);
  padding: 1.75rem;
}
/* The front door, so the lock-up leads rather than decorates. Centred and
 * given room: this is the one console page a teacher sees before they have
 * any context, and the brand is the context. */
.login-card { text-align: center; }
/* The card is white and the artwork is white, so here the Ink panel is drawn
 * rather than baked into the asset — same colour as the sidebar, from the same
 * variable. Change --ink once and both surfaces follow. */
.login-card .mark {
  background: var(--ink);
  border-radius: var(--radius);
  padding: 0.9rem 1.1rem;
  margin: 0 auto 1.25rem;
  width: fit-content;
  max-width: 100%;
  line-height: 0;
}
.login-card .mark img {
  width: 148px;
  max-width: 100%;
  height: auto;
  display: block;
}
/* The lock-up already says "Examo", so the heading names the surface. */
.login-card h1 { font-size: 1.15rem; margin-bottom: 0.15rem; }
/* Centring is for the brand and the headings, not the form: a centred label
 * above a left-aligned input is how a login page starts looking amateur. */
.login-card form, .login-card .notice { text-align: left; }
.login-card .tagline {
  color: var(--foreground-muted); font-size: 0.85rem; margin: 0 0 1.4rem;
}

/* --------------------------------------------------------------------------
 * Upload
 * ----------------------------------------------------------------------- */

.dropzone {
  border: 1.5px dashed var(--border-strong);
  border-radius: var(--radius);
  background: var(--subtle);
  padding: 1.4rem 1rem;
  text-align: center;
  cursor: pointer;
  transition: border-color 120ms ease, background 120ms ease;
}
.dropzone:hover { border-color: var(--flow); background: var(--success-bg); }
.dropzone svg { width: 22px; height: 22px; color: var(--foreground-subtle); }
.dropzone .title { font-weight: 500; font-size: 0.85rem; margin-top: 0.4rem; }
.dropzone .hint { color: var(--foreground-muted); font-size: 0.78rem; }
.dropzone.filled {
  border-style: solid; border-color: var(--success);
  background: var(--success-bg); text-align: left;
  display: flex; align-items: center; gap: 0.65rem;
}
.dropzone.filled svg { color: var(--success-fg); }
.dropzone.required-missing { border-color: var(--danger); background: var(--danger-bg); }

/* --------------------------------------------------------------------------
 * Review — the question/memo mapping, shown rather than described
 * ----------------------------------------------------------------------- */

.steps { display: flex; align-items: center; gap: 0.5rem; font-size: 0.8rem;
  margin-bottom: 1.1rem; }
.steps .step { display: flex; align-items: center; gap: 0.4rem;
  color: var(--foreground-subtle); }
.steps .step .n {
  width: 20px; height: 20px; border-radius: 50%;
  display: grid; place-items: center;
  font-size: 0.7rem; font-weight: 600;
  background: var(--subtle); color: var(--foreground-muted);
  border: 1px solid var(--border);
}
.steps .step.done .n { background: var(--success); color: #fff; border-color: var(--success); }
.steps .step.done { color: var(--foreground-muted); }
.steps .step.on .n { background: var(--ink); color: #fff; border-color: var(--ink); }
.steps .step.on { color: var(--foreground); font-weight: 500; }
.steps .sep { width: 22px; height: 1px; background: var(--border); }

.kv { display: flex; flex-wrap: wrap; gap: 0 1.6rem; font-size: 0.82rem; }
.kv div { padding: 0.15rem 0; }
.kv .k { color: var(--foreground-muted); }
.kv .v { color: var(--foreground); font-weight: 500; }

/* One question: its number and marks, the rendered question, the linked
 * answer. Side by side because the mapping is the thing being checked, and a
 * mapping you have to imagine is a mapping nobody checks. */
.mapping { border-bottom: 1px solid var(--border); padding: 0.9rem 1.25rem; }
.mapping:last-child { border-bottom: none; }
.mapping.flagged { background: var(--warning-bg); }
.mapping .head { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.6rem; }
.mapping .qno {
  font-weight: 600; font-size: 0.9rem;
  font-variant-numeric: tabular-nums;
}
.mapping .pair { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; }
.mapping .pane .cap {
  font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--foreground-muted); font-weight: 500; margin-bottom: 0.3rem;
}

.thumb {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: #fff;
  padding: 0.6rem 0.7rem;
  font-size: 0.78rem;
  color: var(--foreground-muted);
  min-height: 84px;
  position: relative;
  overflow: hidden;
}
.thumb .lines span {
  display: block; height: 6px; border-radius: 2px;
  background: var(--border); margin-bottom: 5px;
}
.thumb.answer { background: #fbfdfc; border-color: #d6ece1; }
.thumb.missing {
  border-style: dashed; border-color: var(--danger);
  background: var(--danger-bg); color: var(--danger-fg);
  display: grid; place-items: center; font-weight: 500;
}
.thumb .tag {
  position: absolute; top: 6px; right: 6px;
  font-size: 0.62rem; letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--foreground-subtle);
}

/* --------------------------------------------------------------------------
 * Review panes
 *
 * Question above, answer below, both the same fixed height. Side by side left
 * each too narrow to read — a rendered exam page is portrait and wide — and a
 * reviewer is comparing one against the other rather than scanning both at
 * once. Equal heights mean the cards line up down the page and nothing
 * reflows as images of different shapes finish loading.
 * ----------------------------------------------------------------------- */

.pane {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: #fff;
  overflow: hidden;
  position: relative;
}
.pane + .pane { margin-top: 0.75rem; }

.pane-label {
  font-size: 0.62rem; letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--foreground-subtle);
  padding: 0.5rem 0.7rem 0;
}

.pane a { display: block; }

/* `contain` rather than `cover`: cropping an exam question to fit a box is
 * how a reviewer approves something they have not seen all of. */
.pane img {
  display: block;
  width: 100%;
  height: 320px;
  object-fit: contain;
  object-position: top left;
  padding: 0.5rem 0.7rem 0.7rem;
}

/* Text sizes to its content; only images get the fixed height.
 *
 * A multiple-choice answer is one letter. Boxing it at 320px to match the
 * image pane left most of the card empty, which reads as something failing to
 * load rather than as a short answer. Equal heights were there to stop the
 * page reflowing as images load — text has no such problem. */
.pane .empty, .pane .answer-text {
  padding: 0.7rem;
  min-height: 3.5rem;
  display: grid;
  align-content: center;
}
/* A one-letter answer is the whole thing being checked, so it is set large
 * rather than tucked into a caption. Prose guidance runs to a few lines and
 * grows naturally.
 *
 * A long marking scheme scrolls rather than being cut. The template used to
 * truncate at 600 characters, which removed the end of a memo with nothing on
 * screen to say so — and "Approve this answer" sits directly beneath it. A
 * `max-height` keeps a one-letter answer one line tall and stops a fifty-item
 * paper becoming an unscrollable page; the scrollbar is the disclosure that
 * truncation never gave. */
.pane .answer-text {
  font-size: 1rem; line-height: 1.55; color: var(--foreground);
  align-content: start;
  white-space: pre-wrap;
  max-height: 320px;
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* --------------------------------------------------------------------------
 * System mode
 *
 * Which rules are in force, pinned above the account footer where it never
 * scrolls away. Green for Live, red for anything else — a debug mode is meant
 * to be temporary, and the way a temporary safeguard-off becomes permanent is
 * that nothing reminds anyone it is on.
 *
 * Manager-only, matching the setting's capability: a teacher cannot change it,
 * and a warning nobody can act on is just noise.
 * ----------------------------------------------------------------------- */

.mode {
  display: flex; align-items: center; gap: 0.5rem;
  margin: 0 0.15rem 0.4rem;
  padding: 0.5rem 0.6rem;
  border-radius: var(--radius-sm);
  text-decoration: none;
  font-size: 0.7rem; line-height: 1.25;
}
.mode i {
  flex: 0 0 auto;
  width: 8px; height: 8px; border-radius: 50%;
}
.mode-label { display: block; font-weight: 600; letter-spacing: 0.01em; }
.mode-note { display: block; opacity: 0.75; }

.mode-live {
  background: rgb(255 255 255 / 4%);
  color: rgb(255 255 255 / 55%);
}
.mode-live i { background: var(--flow); }

/* Deliberately loud. This is the state that must not be forgotten. */
.mode-debug {
  background: rgb(181 58 44 / 22%);
  color: #ffd9d4;
}
.mode-debug i {
  background: #ff5f4a;
  box-shadow: 0 0 0 3px rgb(255 95 74 / 22%);
}
.mode-debug:hover { background: rgb(181 58 44 / 32%); }

/* --------------------------------------------------------------------------
 * Review progress track
 *
 * One segment per item, in paper order. A count says how much is left; the
 * track says *where* — which matters on a fifty-two item paper where the
 * remaining work is rarely evenly spread. Each segment is an anchor, so it is
 * navigation as well as a report.
 * ----------------------------------------------------------------------- */

.track {
  display: flex; gap: 2px;
  padding: 0 1.25rem 0.75rem;
}
.track .seg {
  flex: 1 1 0; min-width: 3px; height: 10px;
  border-radius: 2px;
  background: var(--border);
  transition: transform 0.08s ease;
}
.track .seg:hover { transform: scaleY(1.5); }

/* Green checked, amber parked, red unservable, grey still to do. Red is a
 * *fact* — an item missing a half — not a decision: nothing in the console
 * declines a single answer (§11 #39 parks, §11 #33 withdraws a paper).
 *
 * Scoped `.track .seg-*`, not bare `.seg-*`. The base rule above is two
 * classes deep, so an unscoped modifier loses on specificity and every segment
 * renders the default grey — with the legend still showing the right colours,
 * which makes it look like a data problem rather than a CSS one. */
.track .seg-checked { background: var(--success); }
.track .seg-parked  { background: var(--warning); }
.track .seg-blocked { background: var(--danger); }
.track .seg-pending { background: var(--border-strong); }

.track-key {
  display: flex; flex-wrap: wrap; gap: 0.9rem;
  padding: 0 1.25rem 1rem;
  font-size: 0.75rem; color: var(--foreground-muted);
}
.track-key i {
  display: inline-block; width: 9px; height: 9px;
  border-radius: 2px; margin-right: 0.3rem;
}
.dot-checked { background: var(--success); }
.dot-pending { background: var(--border-strong); }
.dot-parked  { background: var(--warning); }
.dot-blocked { background: var(--danger); }

/* --------------------------------------------------------------------------
 * Collapsing a reviewed item
 *
 * A checked item folds, so the next undecided one is at the top of the screen
 * rather than below the reviewer's own completed work. `<details>` because it
 * needs no JavaScript (§11 #30) and re-opening is one click — nothing is
 * hidden, only folded.
 * ----------------------------------------------------------------------- */

details.card > summary {
  cursor: pointer;
  list-style: none;          /* the default triangle sits badly in a card head */
  user-select: none;
}
details.card > summary::-webkit-details-marker { display: none; }
details.card > summary:hover { background: var(--subtle); }

/* A closed item still shows its number, topic and badge — enough to find it
 * again without opening it. Muting the whole row would make a fifty-item paper
 * read as greyed out rather than as done. */
details.card:not([open]) > summary { border-bottom: none; }
details.card:not([open]) > summary h2 { font-size: 0.95rem; }

/* A thin coloured edge, so state is legible while scrolling past a run of
 * folded items — the badge is at the far right and the eye is at the left. */
details.item-checked { border-left: 3px solid var(--success); }
details.item-parked  { border-left: 3px solid var(--warning); }
details.item-blocked { border-left: 3px solid var(--danger); }
details.item-pending { border-left: 3px solid var(--border-strong); }

/* --------------------------------------------------------------------------
 * Item actions
 *
 * Full-size controls on a tinted bar. These were `btn-sm` inside a muted row
 * and read as footnotes — but parking an item is the decision that lets a
 * paper reach learners, and it should not look like a caption.
 * ----------------------------------------------------------------------- */

.item-actions {
  display: flex; flex-wrap: wrap; gap: 0.6rem;
  align-items: center;
  padding: 0.85rem 1.25rem;
  border-top: 1px solid var(--border);
  background: var(--subtle);
}
.item-actions form { display: contents; }
.item-actions .action-group {
  display: flex; gap: 0.4rem; align-items: center;
  flex: 1 1 340px; min-width: 0;
}
.item-actions .action-group input[type="text"],
.item-actions .action-group select {
  flex: 1 1 auto; min-width: 0;
  height: 36px; padding: 0 0.7rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  font: inherit; font-size: 0.875rem;
  background: var(--background);
}
.item-actions .btn { white-space: nowrap; }
/* Names the group, so "Replace" and "Park it" cannot be read as the same kind
 * of action as "Approve this answer" — one approves what is there, the others
 * change it. */
.item-actions .action-label {
  font-size: 0.78rem; color: var(--foreground-subtle); white-space: nowrap;
}
.item-actions .repair-note {
  flex: 1 1 340px; font-size: 0.86rem; color: var(--foreground-muted);
}

.checklist { font-size: 0.84rem; }
.checklist li {
  display: flex; align-items: flex-start; gap: 0.5rem;
  padding: 0.3rem 0; list-style: none;
}
.checklist ul { margin: 0; padding: 0; }
.checklist svg { width: 15px; height: 15px; flex: 0 0 15px; margin-top: 2px; }
.checklist .ok svg { color: var(--success); }
.checklist .warn svg { color: var(--warning); }
.checklist .bad svg { color: var(--danger); }

/* --------------------------------------------------------------------------
 * Settings
 *
 * One row per setting: what it is and why on the left, the control on the
 * right. Each row is its own form — a whole-page save would record every
 * setting as changed whenever one of them was, and make "who raised the cap"
 * unanswerable.
 * ----------------------------------------------------------------------- */

.setting {
  display: grid;
  grid-template-columns: 1fr 260px;
  gap: 1.25rem;
  padding: 1rem 1.25rem;
  border-bottom: 1px solid var(--border);
  align-items: start;
}
.setting:last-child { border-bottom: none; }
.setting .name { font-weight: 500; font-size: 0.9rem; }
.setting .help { color: var(--foreground-muted); font-size: 0.82rem; margin-top: 0.15rem; }
.setting .caveat {
  font-size: 0.78rem; margin-top: 0.4rem; padding: 0.4rem 0.6rem;
  border-radius: var(--radius-sm);
  background: var(--warning-bg); color: var(--warning-fg);
}
.setting .problem {
  font-size: 0.78rem; margin-top: 0.4rem; padding: 0.4rem 0.6rem;
  border-radius: var(--radius-sm);
  background: var(--danger-bg); color: var(--danger-fg);
}
.setting .control { display: flex; align-items: center; gap: 0.4rem; }
.setting .control input[type="number"],
.setting .control input[type="text"] { text-align: right; }
.setting .unit { color: var(--foreground-subtle); font-size: 0.78rem; white-space: nowrap; }
.setting .default {
  color: var(--foreground-subtle); font-size: 0.74rem;
  margin-top: 0.3rem; text-align: right;
}
.setting .guard { margin-top: 0.4rem; }

/* Integration status. A dot and a state — never a value. */
.probe {
  display: grid; grid-template-columns: auto 1fr auto; gap: 0.75rem 1rem;
  align-items: center;
  padding: 0.7rem 1.25rem; border-bottom: 1px solid var(--border);
  font-size: 0.86rem;
}
.probe:last-child { border-bottom: none; }
.probe .env {
  font-family: var(--font-mono); font-size: 0.74rem; color: var(--foreground-subtle);
}
.dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 8px; }
.dot.ok { background: var(--success); }
.dot.warn { background: var(--warning); }
.dot.down { background: var(--danger); }

/* A footnote fixed to the bottom of a card: why the card is the way it is,
 * rather than a value it is reporting. */
.cardnote {
  padding: 0.85rem 1.25rem; border-top: 1px solid var(--border);
  display: flex; align-items: center; gap: 0.6rem; font-size: 0.86rem;
  background: var(--subtle); color: var(--foreground-muted);
}
.cardnote svg { width: 16px; height: 16px; flex: 0 0 16px; }

/* --------------------------------------------------------------------------
 * Money (§11 #201)
 *
 * Six classes the payments page needs and nothing else has wanted yet. They
 * live here rather than in a <style> block on the template for the reason
 * every other rule does: a second stylesheet is a second place a colour
 * drifts.
 * ----------------------------------------------------------------------- */

/* A name and the thing that qualifies it, stacked — "Card" over
 * "R49/month, hosted checkout". Two facts about one row that must not read
 * as two columns. */
.plan-line { display: flex; flex-direction: column; gap: 0.12rem; }
.plan-line .when { color: var(--foreground-subtle); font-size: 0.76rem; }

/* A row that wants a person. The stripe rather than a fill, because a filled
 * row competes with the badge that says *why* it needs one. */
.attention { border-left: 3px solid var(--warning); }

/* Money lines up or it cannot be compared down a column. */
.money { font-variant-numeric: tabular-nums; }

/* --------------------------------------------------------------------------
 * Utilities
 * ----------------------------------------------------------------------- */

.row { display: flex; align-items: center; gap: 0.6rem; }
.spread { justify-content: space-between; }
.muted { color: var(--foreground-muted); }
.small { font-size: 0.82rem; }
.mt-0 { margin-top: 0; }
.mb-0 { margin-bottom: 0; }

@media (max-width: 820px) {
  .shell { flex-direction: column; }
  .sidebar { width: 100%; flex: none; height: auto; position: static; }
  .content { padding: 1rem; }
}
