/* The study room.
 *
 * The exam clone (exam.css, marrow.css) keeps the institutional blue chrome,
 * because imitating the NBE screen is the point of it. Everything else — the
 * lobby, the doors, the confirm screen, the scorecard, the ledger — is this
 * file, and it is deliberately a different place: quiet ground, near-black
 * text, one accent used only on things you can act on.
 *
 * The rule for the accent: if it is blue, you can click it. Nothing decorative
 * is ever the accent colour, so scanning for blue is the same as scanning for
 * what to do next.
 */

:root {
  --ink:        #1a1d21;   /* body text */
  --ink-soft:   #5b6670;   /* secondary text */
  --ink-faint:  #8b959e;   /* thirds: counts, timestamps */
  --ground:     #fbfaf8;   /* page */
  --surface:    #ffffff;   /* panels */
  --line:       #e5e2dc;   /* hairlines */
  --line-firm:  #cfcac2;   /* input borders */
  --accent:     #1b5e8e;   /* the one accent */
  --accent-ink: #ffffff;
  --accent-wash:#eef4f9;
  --good:       #2e7d54;
  --bad:        #b3402e;
  --warn-bg:    #fdf6e7;
  --warn-line:  #e6d3a8;
  --warn-ink:   #6e5518;
  --radius:     8px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font: 15px/1.55 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
        Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
}

/* ---- chrome ---------------------------------------------------------- */
.topbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; padding: 14px 22px;
  background: var(--surface); border-bottom: 1px solid var(--line);
}
.brand { font-weight: 600; letter-spacing: -0.01em; }
.topbar nav { display: flex; gap: 18px; align-items: center; font-size: 14px; }
.topbar a { color: var(--ink-soft); text-decoration: none; }
.topbar a:hover { color: var(--accent); }
.linklike {
  background: none; border: none; padding: 0; cursor: pointer;
  color: var(--ink-soft); font: inherit; font-size: 14px;
}
.linklike:hover { color: var(--accent); }
.inline { display: inline; }

main { max-width: 720px; margin: 0 auto; padding: 40px 20px 80px; }
h1 { font-size: 26px; font-weight: 600; letter-spacing: -0.02em; margin: 0 0 6px; }
h2 { font-size: 17px; font-weight: 600; margin: 0 0 8px; }
.lede { color: var(--ink-soft); margin: 0 0 26px; max-width: 60ch; }
.muted { color: var(--ink-soft); }
.small { font-size: 13px; }
.error { color: var(--bad); }
.right { text-align: right; }
.count { font-weight: 400; font-size: 16px; color: var(--ink-faint); }
.eyebrow {
  font-size: 11px; font-weight: 600; letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--ink-faint); margin: 0 0 10px;
}
.back {
  display: inline-block; margin-bottom: 18px; font-size: 13px;
  color: var(--ink-soft); text-decoration: none;
}
.back:hover { color: var(--accent); }

/* ---- buttons --------------------------------------------------------- */
.btn {
  display: inline-block; padding: 9px 16px; border-radius: 6px; cursor: pointer;
  border: 1px solid var(--line-firm); background: var(--surface); color: var(--ink);
  font: inherit; font-size: 14px; text-decoration: none; text-align: center;
}
.btn:hover { border-color: var(--accent); color: var(--accent); }
.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.btn.primary:hover { filter: brightness(1.08); color: var(--accent-ink); }
.btn.big { padding: 12px 26px; font-size: 15px; width: 100%; }
.btn.small { padding: 5px 12px; font-size: 13px; }
.btn.ghost { color: var(--ink-soft); }
.btn:disabled { opacity: 0.45; cursor: not-allowed; filter: none; }
.btn.marrow { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }

/* ---- home: the doors -------------------------------------------------- */
.resume {
  background: var(--surface); border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius); padding: 16px 18px; margin-bottom: 28px;
}
.resume-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
}
.resume-row + .resume-row { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line); }
.resume-name { display: block; font-weight: 600; }
.resume-meta { display: block; font-size: 13px; color: var(--ink-soft); margin-top: 2px; }

.door {
  display: grid; grid-template-columns: 1fr auto; align-items: center;
  gap: 4px 16px; padding: 22px 24px; margin-bottom: 12px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); text-decoration: none; color: inherit;
}
.door:hover { border-color: var(--accent); background: var(--accent-wash); }
/* All three need a definite column. Grid places items with a definite row
   before the free-floating ones, so an unplaced .door-go claimed row 1 /
   column 1 and knocked the title into the arrow's column. */
.door-title { grid-row: 1; grid-column: 1; font-size: 18px; font-weight: 600; letter-spacing: -0.01em; }
.door-blurb { grid-row: 2; grid-column: 1; font-size: 14px; color: var(--ink-soft); }
.door-go { grid-row: 1 / span 2; grid-column: 2; font-size: 20px; color: var(--accent); }
.door-empty { opacity: 0.55; cursor: default; }
.door-empty:hover { border-color: var(--line); background: var(--surface); }

.quiet-links { display: flex; gap: 20px; flex-wrap: wrap; margin-top: 30px; font-size: 14px; }
.quiet-links a { color: var(--ink-soft); text-decoration: none; }
.quiet-links a:hover { color: var(--accent); }

.pyq-toggle {
  display: flex; align-items: center; gap: 8px; margin: -8px 0 18px;
  font-size: 14px; color: var(--ink-soft); cursor: pointer;
}

/* ---- panels: door picker, confirm ------------------------------------ */
.panel {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); margin-bottom: 18px; overflow: hidden;
}
.panel-head, .pick-row {
  display: grid; grid-template-columns: 22px 1fr 5.5rem 5rem 4rem 5rem;
  gap: 10px; align-items: center; padding: 9px 16px;
}
.panel-head {
  grid-template-columns: 1fr 5.5rem 5rem 4rem 5rem;
  padding-left: 48px;
  font-size: 11px; letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--ink-faint); background: var(--ground); border-bottom: 1px solid var(--line);
}
.pick-row { border-bottom: 1px solid var(--line); cursor: pointer; }
.pick-row:last-child { border-bottom: none; }
.pick-row:hover { background: var(--accent-wash); }
.pick-name { font-weight: 500; }
.alloc { color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.alloc.has { color: var(--accent); font-weight: 600; }
.pick-row .right { font-variant-numeric: tabular-nums; }

.controls { padding: 18px 20px; }
.field { margin-bottom: 14px; }
.field-label {
  display: block; font-size: 13px; color: var(--ink-soft); margin-bottom: 7px;
}
.segmented { display: inline-flex; border: 1px solid var(--line-firm); border-radius: 6px; overflow: hidden; }
.seg {
  padding: 8px 18px; border: none; border-right: 1px solid var(--line-firm);
  background: var(--surface); color: var(--ink-soft); font: inherit; font-size: 14px;
  cursor: pointer;
}
.seg:last-child { border-right: none; }
.seg:hover { background: var(--accent-wash); }
.seg.on { background: var(--accent); color: var(--accent-ink); }
.summary { font-weight: 600; margin: 4px 0 10px; }
.warning {
  background: var(--warn-bg); border: 1px solid var(--warn-line); color: var(--warn-ink);
  border-radius: 6px; padding: 9px 12px; font-size: 13px; margin: 0 0 14px;
}

/* ---- confirm --------------------------------------------------------- */
.confirm { padding: 24px; }
.confirm-name { font-size: 19px; font-weight: 600; margin: 0; letter-spacing: -0.01em; }
.confirm-size { color: var(--ink-soft); margin: 2px 0 16px; }
.consequences { list-style: none; padding: 0; margin: 0 0 22px; }
.consequences li {
  display: grid; grid-template-columns: 26px 1fr; gap: 10px; align-items: start;
  padding: 11px 0; border-top: 1px solid var(--line); font-size: 14px;
}
.consequences .icon { font-size: 15px; line-height: 1.5; }

.switch { margin-top: 16px; }
.switch > summary { cursor: pointer; font-size: 13px; color: var(--ink-soft); }
.switch > summary:hover { color: var(--accent); }
.switch-body { margin-top: 10px; }
.switch-mode {
  display: block; padding: 11px 14px; margin-bottom: 8px; text-decoration: none;
  border: 1px solid var(--line); border-radius: 6px; color: inherit;
}
.switch-mode:hover { border-color: var(--accent); background: var(--accent-wash); }
.switch-mode.on { border-color: var(--accent); background: var(--accent-wash); }
.switch-mode b { display: block; font-size: 14px; }
.switch-mode span { font-size: 12.5px; color: var(--ink-soft); }

/* ---- archive --------------------------------------------------------- */
.shelf-head { display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap; }
.shelf-head h1 { flex: 1; }
.filter {
  flex: 1; min-width: 190px; padding: 8px 12px; font: inherit; font-size: 14px;
  border: 1px solid var(--line-firm); border-radius: 6px; background: var(--surface);
}
.filter:focus { outline: 2px solid var(--accent-wash); border-color: var(--accent); }

.shelf-group { margin-bottom: 10px; }
.shelf-group > summary {
  cursor: pointer; padding: 10px 14px; font-weight: 600; font-size: 14px;
  background: var(--surface); border: 1px solid var(--line); border-radius: 6px;
}
.shelf-group > summary:hover { border-color: var(--accent); color: var(--accent); }
.shelf-group[open] > summary { border-radius: 6px 6px 0 0; border-bottom: none; }
.shelf {
  background: var(--surface); border: 1px solid var(--line);
  border-top: none; border-radius: 0 0 6px 6px;
}
.panel.shelf { border-top: 1px solid var(--line); border-radius: var(--radius); }

.prow {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 11px 16px; border-bottom: 1px solid var(--line);
}
.prow:last-child { border-bottom: none; }
.prow-main { flex: 1; min-width: 210px; }
.prow-name { font-weight: 500; }
.prow-meta { display: block; font-size: 12.5px; color: var(--ink-soft); margin-top: 2px; }
.prow-actions { display: flex; gap: 8px; align-items: center; }
.prow-more { width: 100%; }
.prow-more > summary { cursor: pointer; font-size: 12px; color: var(--ink-faint); list-style: none; }
.prow-more > summary::before { content: "▸ "; }
.prow-more[open] > summary::before { content: "▾ "; }
.prow-more > summary:hover { color: var(--accent); }
.prow-detail { padding: 8px 0 4px; }

.chip {
  font-size: 11px; padding: 1px 8px; border-radius: 999px; margin-left: 6px;
  border: 1px solid var(--line-firm); background: var(--ground); color: var(--ink-soft);
}
.chip.gt { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.chip.mine { background: var(--accent-wash); border-color: var(--accent); color: var(--accent); }
.chip.done { background: #eaf4ee; border-color: #bcd9c7; color: var(--good); }
.mix .sub { display: inline-block; margin-right: 12px; font-size: 13px; color: var(--ink-soft); }
.mix .sub b { color: var(--ink); }

/* ---- tables ---------------------------------------------------------- */
table.board { border-collapse: collapse; width: 100%; margin-top: 6px; font-size: 13.5px; }
.board th, .board td { border-bottom: 1px solid var(--line); padding: 7px 10px; text-align: left; }
.board th { color: var(--ink-faint); font-weight: 500; font-size: 12px;
  text-transform: uppercase; letter-spacing: 0.04em; }
.board a { color: var(--accent); text-decoration: none; }
.board-label { margin: 14px 0 2px; font-size: 13px; font-weight: 600; }

/* ---- login / the portal ------------------------------------------------
 *
 * The one fact this whole system runs against -- days to 31 October -- said
 * before you have even signed in, the same number and the same "line rising
 * toward a threshold" shape home.html's own projection chart uses once you
 * are inside. Not stock decoration: a preview of the actual chart.
 *
 * Breaks out of main's 720px column (a wide two-panel card reads as a
 * considered front door, not a form squeezed into the ordinary content
 * width) but stays a contained, rounded panel rather than true edge-to-edge
 * bleed -- and only above the phone breakpoint, where main's own width
 * already does the job and rounded corners flush to the screen edge would
 * look like an accident rather than a choice.
 */
:root {
  --portal-bg:        #102030;
  --portal-ink:        #eef3f6;
  --portal-ink-soft:   #8fa6b8;
  --portal-line:       rgba(255,255,255,0.16);
  --portal-good:       #5cc794;
}

.portal { margin: 8px 0 24px; }
.portal-inner {
  max-width: 1180px; margin: 0 auto; min-height: 520px;
  display: flex; border-radius: 16px; overflow: hidden;
  box-shadow: 0 1px 3px rgba(16,32,48,.08), 0 14px 34px rgba(16,32,48,.12);
}

.portal-hero {
  flex: 1 1 0; background: var(--portal-bg); color: var(--portal-ink);
  padding: 56px 44px; display: flex; flex-direction: column;
  justify-content: center; gap: 26px;
}
.portal-clock { display: flex; flex-direction: column; }
.portal-days {
  font-size: 84px; font-weight: 700; letter-spacing: -0.03em; line-height: 1;
  font-variant-numeric: tabular-nums;
}
.portal-days-label { color: var(--portal-ink-soft); font-size: 15px; margin-top: 6px; }

.portal-trend { width: 100%; height: 64px; display: block; }
.portal-threshold { stroke: var(--portal-line); stroke-width: 1; stroke-dasharray: 3 4; }
.portal-trend-line { fill: none; stroke: var(--portal-good); stroke-width: 2; }
.portal-trend-mark { fill: var(--portal-good); }

.portal-blurb {
  color: var(--portal-ink-soft); font-size: 14.5px; line-height: 1.6;
  max-width: 30ch; margin: 0;
}
/* One serif glyph, not a second typeface for content: a sans quotation mark
   at this size reads as a comma, not a quote, so the mark alone borrows a
   serif's shape while every actual word around it stays in the site's own
   system stack. */
.portal-quote-mark {
  font-family: Georgia, "Times New Roman", serif;
  font-size: 2.1em; line-height: 0; vertical-align: -0.36em;
  color: var(--portal-good); margin-right: 1px;
}

/* The blueprint list -- NBEMS's own published split, heaviest subject
   first, the same order material.py's subject grid already uses. Sits in
   the site's ordinary light palette rather than the hero's, and reads as
   one continuous light side with the form (a hairline divides them, not a
   third colour) so the page stays a two-tone composition: the dark hero,
   and everything else. A fixed width, not a share of whatever's left --
   "Obstetrics & Gynaecology" needs a real column regardless of how wide the
   other two panels end up, so it does not get to be the one that bends.
   Off below 900px, in the same rule that lets .portal-hero and .portal-form
   claim its share back -- 19 rows is real scrolling on a screen too narrow
   to give it the room it needs anyway. */
.portal-subjects {
  flex: 0 0 320px; background: var(--surface); padding: 40px 26px;
  border-left: 1px solid var(--line);
}
.portal-subjects-cap { margin: 0 0 14px; font-size: 13px; color: var(--ink-soft); }
.portal-subjects-list { list-style: none; margin: 0; padding: 0; }
.portal-subjects-list li {
  display: flex; align-items: center; gap: 10px; padding: 4px 0;
}
.ps-name { flex: 1 1 auto; font-size: 12.5px; color: var(--ink); white-space: nowrap; }
.ps-bar {
  flex: 0 0 48px; display: block; height: 4px; border-radius: 999px;
  background: var(--line); overflow: hidden;
}
.ps-bar i { display: block; height: 100%; background: var(--accent); border-radius: 999px; }
.ps-marks {
  flex: 0 0 18px; text-align: right; font-size: 12px; color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}

.portal-form {
  flex: 1 1 0; display: flex; align-items: center; justify-content: center;
  background: var(--surface); padding: 40px 20px;
}

/* `.portal-form .login-card`, not `.login-card` alone: the base `.card` rule
   (below, later in the cascade) sets its own padding, border, background and
   margin-bottom at equal specificity, so a same-specificity override placed
   earlier in the file loses to source order regardless of intent. The
   ancestor-qualified selector outranks it outright instead of depending on
   where either rule happens to sit. Flattened rather than bordered: it
   already sits inside .portal-inner's own bordered, shadowed panel, and a
   card-shaped box nested inside that panel would just be a card on a card. */
.portal-form .login-card {
  width: 100%; max-width: 300px; padding: 0; margin-bottom: 0;
  border: none; background: transparent; border-radius: 0;
}
.login-card h1 { margin: 0 0 4px; }
.login-card label { display: block; margin: 14px 0; font-size: 14px; color: var(--ink-soft); }
.login-card input {
  width: 100%; padding: 9px 11px; margin-top: 5px; font: inherit;
  border: 1px solid var(--line-firm); border-radius: 6px; background: var(--ground);
}
.login-card input:focus { outline: 2px solid var(--accent-wash); border-color: var(--accent); }
.login-card .btn { width: 100%; margin-top: 10px; }

/* Three columns need real width. The blueprint list holds its own 320px
   regardless -- "Obstetrics & Gynaecology" never gets to be the one that
   bends -- but below this a fixed 320px column leaves the hero and the form
   squeezed instead. Drop to the two-panel layout early rather than let
   either of those get cramped; the list disappears again at the phone
   breakpoint below along with everything else that doesn't fit a fast,
   one-purpose screen. */
@media (max-width: 900px) {
  .portal-subjects { display: none; }
}

@media (max-width: 620px) {
  .portal { margin: 0; }
  .portal-inner { display: block; border-radius: 0; box-shadow: none; min-height: 0; }
  .portal-hero { padding: 32px 20px 26px; gap: 18px; }
  .portal-days { font-size: 56px; }
  .portal-trend { height: 44px; }
  .portal-form { padding: 28px 4px 8px; background: transparent; }
}

/* ---- admin: right now -------------------------------------------------
   Presence is the one place a colour means a state rather than an action, so
   the dot carries it and nothing else does: green at the screen, amber idle.
   The progress bar reuses the scorecard's four-outcome segments deliberately —
   a live paper and a finished one should read the same way. */
.live h2 { display: flex; align-items: center; gap: 8px; }
.pulse {
  width: 7px; height: 7px; border-radius: 50%; background: var(--good);
  animation: pulse 2.4s ease-in-out infinite;
}
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }
@media (prefers-reduced-motion: reduce) { .pulse { animation: none; } }

.who { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0 4px; }
.chip {
  display: inline-flex; align-items: center; gap: 6px; font-size: 13px;
  padding: 3px 10px 3px 8px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--ground); white-space: nowrap;
}
.chip em { font-style: normal; color: var(--ink-faint); font-size: 12px; }
.chip .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--line-firm); }
.chip.online .dot { background: var(--good); }
.chip.idle .dot { background: #c99a3d; }
.chip.online { border-color: #cfe4d8; }

.live-board td { vertical-align: middle; }
.live-board .num { font-variant-numeric: tabular-nums; }
.live-board .num small { color: var(--ink-faint); margin-left: 4px; }
.live-board .num.low { color: var(--bad); font-weight: 600; }
.progress-cell { min-width: 160px; }
.progress-cell .outcome-bar { height: 8px; }
.progress-cell small { display: block; margin-top: 4px; color: var(--ink-faint); font-size: 12px; }
.flag { color: var(--warn-ink); background: var(--warn-bg);
  border: 1px solid var(--warn-line); border-radius: 4px; padding: 0 5px; font-size: 12px; }

/* ---- results --------------------------------------------------------- */
.card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 18px 20px; margin-bottom: 16px;
}
.headline { padding: 24px; }
.headline-score { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.score-big { font-size: 52px; font-weight: 600; letter-spacing: -0.035em; line-height: 1; }
.score-big.pass { color: var(--good); } .score-big.fail { color: var(--bad); }
.score-of { font-size: 22px; color: var(--ink-faint); }
.verdict {
  margin-left: 8px; font-size: 12px; font-weight: 600; letter-spacing: 0.09em;
  border-radius: 999px; padding: 4px 12px; align-self: center;
}
.verdict.pass { background: #eaf4ee; color: var(--good); }
.verdict.fail { background: #fbecea; color: var(--bad); }
.headline-line { margin: 10px 0 18px; color: var(--ink-soft); max-width: 58ch; }

/* One bar, four outcomes. Right and wrong alone hide the two failures that
   have different fixes, so all four get width in proportion. */
.outcome-bar { display: flex; height: 10px; border-radius: 999px; overflow: hidden; gap: 2px; }
.outcome-bar > span, .stack > span { display: block; }
.seg-correct   { background: var(--good); }
.seg-wrong     { background: var(--bad); }
.seg-skip      { background: #c99a3d; }
.seg-unreached { background: var(--line-firm); }

.legend { display: flex; gap: 16px; flex-wrap: wrap; list-style: none;
  padding: 0; margin: 10px 0 0; font-size: 13px; color: var(--ink-soft); }
.legend i { display: inline-block; width: 9px; height: 9px; border-radius: 3px; margin-right: 6px; }
.k-correct { background: var(--good); } .k-wrong { background: var(--bad); }
.k-skip { background: #c99a3d; } .k-unreached { background: var(--line-firm); }

.note { font-size: 13.5px; margin: 14px 0 0; }
.note.warning { background: var(--warn-bg); border: 1px solid var(--warn-line);
  color: var(--warn-ink); border-radius: 6px; padding: 10px 12px; }

.projection { padding: 20px 22px; }
.projection-line { font-size: 17px; margin: 0 0 12px; }
.projection-line b { font-size: 22px; letter-spacing: -0.02em; }
.proj-bar { display: flex; height: 12px; background: var(--line);
  border-radius: 999px; overflow: hidden; }
.proj-fill { background: var(--accent); }
.proj-rest { flex: 1; }

.subjects td { vertical-align: middle; }
.subjects .subj { font-weight: 500; }
.subjects .chip { font-size: 10px; }
.stack { display: flex; height: 9px; border-radius: 999px; overflow: hidden;
  gap: 2px; width: 100%; vertical-align: middle; }
th.w-bar, td.w-bar { width: 42%; }

.up { color: var(--good); } .down { color: var(--bad); }

.review-q { scroll-margin-top: 20px; }
.review-q img { max-width: 100%; height: auto; }
.review-head { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-bottom: 10px; }
.tag { font-size: 11px; padding: 2px 9px; border-radius: 999px; color: #fff; }
.tag.right { background: var(--good); } .tag.wrong { background: var(--bad); }
.tag.skip { background: var(--ink-faint); }
.tag.repeat { background: #7a4fa3; }
.review-choices { list-style: upper-alpha; padding-left: 22px; }
.review-choices li { margin: 5px 0; padding: 7px 10px; border-radius: 6px; }
.review-choices .is-correct { background: #eaf4ee; }
.review-choices .is-chosen:not(.is-correct) { background: #fbecea; }
.review-choices .mark { font-size: 12px; color: var(--ink-soft); white-space: nowrap; }
.solution { margin-top: 10px; }
.solution summary { cursor: pointer; color: var(--accent); font-weight: 500; font-size: 14px; }
.solution div { margin-top: 8px; line-height: 1.6; }
.solution img { max-width: 100%; height: auto; }

/* ---- the scorecard's four pictures ------------------------------------
 *
 * All four are drawn with the colours already defined above: green is banked,
 * red is lost, amber is left blank, grey is never reached. Nothing here invents
 * a palette, because the same four outcomes are being shown as the bar at the
 * top of the page and a reader should not have to learn them twice.
 *
 * There is no chart library. Three of the four are boxes with a percentage
 * width, which CSS does natively and correctly at every screen size; only the
 * pace curve needs real geometry, and that is one inline <svg>.
 */

/* The paper in one picture: one square per question, in the order you sat it.
   Squares are small on purpose — 300 of them have to fit on one screen for the
   shape to be visible, and the shape is the whole point. */
.qmap { display: flex; flex-wrap: wrap; gap: 3px; }
.qcell {
  width: 15px; height: 15px; border-radius: 3px; display: block;
  background: var(--line-firm); position: relative;
}
.qcell.is-correct { background: var(--good); }
.qcell.is-wrong { background: var(--bad); }
.qcell.is-skip { background: #c99a3d; }
.qcell.is-unreached { background: var(--line-firm); }
/* Missed here and missed before. It is a fact about the question, not a fifth
   outcome, so it is a mark on top of the colour rather than another colour. */
.qcell.is-repeat::after {
  content: ""; position: absolute; inset: auto 2px 2px auto;
  width: 4px; height: 4px; border-radius: 50%; background: #7a4fa3;
  box-shadow: 0 0 0 1px #fff;
}
.qcell:hover { outline: 2px solid var(--accent); outline-offset: 1px; }
.k-repeat { background: #7a4fa3; }
.vh {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

/* Marks at risk in October, subject by subject. The bar's WIDTH is what the
   blueprint gives the subject, so a long bar is a heavy subject; the split
   inside it is what you would bank against what you would lose. Two encodings
   in one row, and neither can be read as the other. */
.stakes { display: flex; flex-direction: column; gap: 7px; }
.stake-row {
  display: grid; gap: 2px 12px; align-items: center;
  grid-template-columns:
    minmax(140px, 1.5fr) minmax(110px, 3fr) minmax(86px, auto) minmax(74px, auto);
}
.stake-name { font-weight: 500; }
/* Two right answers out of thirty-three marks draws the greenest bar on the
   page, and it is the least trustworthy row on it. Thin rows are faded so the
   eye does not read the strongest signal off the weakest evidence. */
.stake-row.is-thin { opacity: 0.55; }
.stake-track { min-width: 0; }
.stake-bar { display: flex; height: 12px; border-radius: 3px; overflow: hidden; min-width: 2px; }
.stake-banked { background: var(--good); }
.stake-risk { background: var(--bad); opacity: 0.55; }
.stake-none { flex: 1; background: repeating-linear-gradient(
  45deg, var(--line) 0 4px, var(--surface) 4px 8px); }
.stake-num { font-size: 13px; text-align: right; white-space: nowrap; }
.stake-num b { font-variant-numeric: tabular-nums; }
.stake-here { font-size: 12px; color: var(--ink-faint); text-align: right; white-space: nowrap; }
@media (max-width: 620px) {
  /* Four columns do not fit a phone, and the bar is the one that must not
     shrink: the name wraps above it and the two numbers share the line below. */
  .stake-row { grid-template-columns: 1fr auto auto; }
  .stake-name { grid-column: 1 / -1; }
  .stake-track { grid-column: 1 / -1; }
}

/* Then against now, per subject, on one 0-100% track. A slope, not two bars:
   the question is which way it moved, and a pair of bars makes you subtract. */
.shifts { display: flex; flex-direction: column; gap: 10px; }
.shift-row {
  display: grid; gap: 12px; align-items: center;
  grid-template-columns: minmax(110px, 1.2fr) minmax(140px, 3fr) minmax(120px, auto);
}
.shift-row.is-thin { opacity: 0.62; }
.shift-name { font-weight: 500; }
.shift-track {
  position: relative; height: 14px; border-radius: 999px;
  background: linear-gradient(to right, var(--line), var(--line));
  background-size: 100% 2px; background-position: 0 center;
  background-repeat: no-repeat;
}
.shift-link { position: absolute; top: 6px; height: 2px; background: var(--ink-faint); }
.shift-link.up { background: var(--good); }
.shift-link.down { background: var(--bad); }
.shift-dot {
  position: absolute; top: 3px; width: 8px; height: 8px; margin-left: -4px;
  border-radius: 50%; box-sizing: content-box;
}
.shift-dot.before { background: var(--surface); border: 2px solid var(--ink-faint); top: 1px; }
.shift-dot.now { background: var(--ink); border: 2px solid var(--surface); top: 1px; }
.shift-num { font-size: 13px; text-align: right; white-space: nowrap; }
.shift-num b { margin-right: 6px; font-variant-numeric: tabular-nums; }

/* Score across your attempts, with the pass line drawn where it actually is.
   A column per attempt: there are rarely more than a handful, and columns make
   the gap between two of them readable in a way a sparkline does not. */
.trend {
  position: relative; display: flex; align-items: flex-end; gap: 6px;
  height: 130px; padding: 0 2px; border-bottom: 1px solid var(--line-firm);
}
.trend-col {
  flex: 1; height: 100%; max-width: 64px;
  display: flex; flex-direction: column; justify-content: flex-end;
}
.trend-value { font-size: 12px; color: var(--ink-soft); text-align: center; margin-bottom: 3px; }
.trend-bar { width: 100%; border-radius: 3px 3px 0 0; background: var(--line-firm); min-height: 2px; }
.trend-bar.pass { background: var(--good); }
.trend-bar.fail { background: var(--bad); opacity: 0.55; }
.trend-bar.is-current { outline: 2px solid var(--accent); outline-offset: 1px; opacity: 1; }
.trend-line {
  position: absolute; left: 0; right: 0; height: 0;
  border-top: 1px dashed var(--ink-faint);
}
.trend-line::after {
  content: attr(data-label); position: absolute; right: 0; top: -16px;
  font-size: 11px; color: var(--ink-faint);
}

/* The only chart here with real geometry. preserveAspectRatio="none" stretches
   the plot to the panel, which distorts shapes -- so nothing inside is a shape:
   two strokes, both kept true width by vector-effect, and every label in HTML
   underneath. */
.curve { display: block; width: 100%; height: 170px; }
.curve-line { fill: none; stroke: var(--accent); stroke-width: 2;
  stroke-linejoin: round; stroke-linecap: round; }
.curve-even { stroke: var(--ink-faint); stroke-width: 1; stroke-dasharray: 4 4; }
.curve-axis {
  display: flex; justify-content: space-between; font-size: 12px;
  color: var(--ink-faint); margin-top: 6px;
}
.curve-y { font-size: 12px; color: var(--ink-faint); margin: 0 0 2px; }

/* The projected mark and what it is out of, in one tile without two font
   sizes fighting each other. */
.tile b .of { font-size: 15px; font-weight: 500; color: var(--ink-faint); }

.next-list { margin: 0; padding-left: 20px; }
.next-list li { margin: 8px 0; }
.head-aside { margin: 0; font-size: 14px; }

/* ---- the review screen ------------------------------------------------ */
/* The scorecard's map on one line. It does not wrap: 300 squares in a grid at
   the top of a review screen would push the question itself below the fold,
   which is the problem this screen exists to fix. */
.qstrip-wrap {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 8px; margin: 14px 0;
  overflow-x: auto; overscroll-behavior-x: contain;
}
.qmap.qstrip { flex-wrap: nowrap; gap: 3px; width: max-content; }
.qstrip .qcell { width: 13px; height: 13px; }
/* Outside the current filter: still reachable, visibly not part of the walk. */
.qstrip .qcell.is-out { opacity: 0.34; }
.qstrip .qcell.is-here {
  outline: 2px solid var(--accent); outline-offset: 1px; opacity: 1;
}
.review-nav { display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-top: 16px; }
.review-count { font-size: 13px; color: var(--ink-soft); }
.btn.is-off { opacity: 0.4; pointer-events: none; }
.segmented a.seg { text-decoration: none; }

/* ---- weak areas ------------------------------------------------------ */
.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
  gap: 10px; margin: 16px 0; }
.tile {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 14px; text-align: center;
}
.tile b { display: block; font-size: 26px; font-weight: 600; letter-spacing: -0.02em; line-height: 1.15; }
.tile span { font-size: 12px; color: var(--ink-soft); }
.tile.accent b { color: var(--accent); }
.subjects td { vertical-align: middle; }
.subjects .subj { font-weight: 500; }
th.w-bar, td.w-bar { width: 46%; }
.bar {
  display: inline-block; width: calc(100% - 46px); height: 8px;
  background: var(--line); border-radius: 999px; overflow: hidden; vertical-align: middle;
}
.bar .fill { display: block; height: 100%; border-radius: 999px; }
.fill.low { background: var(--bad); } .fill.mid { background: #c99a3d; }
.fill.high { background: var(--good); }
.pct { display: inline-block; width: 40px; text-align: right; font-variant-numeric: tabular-nums; }
.thin { margin-top: 14px; }

/* ---- admin ----------------------------------------------------------- */
.row-actions form { margin-right: 6px; }
.add-user { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.add-user input {
  padding: 7px 10px; font: inherit; border: 1px solid var(--line-firm); border-radius: 6px;
}

/* ---- touch + small screens -------------------------------------------
 *
 * Two separate concerns, deliberately keyed off two different things:
 *
 *   `pointer: coarse` — the input is a finger. Nothing to do with width: an
 *   iPad in landscape is 1024px wide and still wants 44px hit targets and
 *   16px inputs (Safari zooms the whole page when you focus anything
 *   smaller, and never zooms back out).
 *
 *   `max-width` — the sheet is narrow. This is where columns reflow.
 *
 * A desktop at 500px gets the reflow and not the fat targets; an iPad at
 * 1024px gets the targets and not the reflow. Keying both off width would
 * get both cases wrong.
 */

@media (pointer: coarse) {
  /* 16px is the threshold below which iOS Safari zooms on focus. Every
     control that sets its own size has to be named: `.login-card input`
     and `.add-user input` say `font: inherit` at a higher specificity than
     a bare `input`, so the generic rule alone leaves both at 15px and both
     still zoom. */
  input, select, textarea,
  .filter, .login-card input, .add-user input { font-size: 16px; }

  .btn, .seg, .linklike, .btn-nbe { min-height: 44px; }
  .btn.small { min-height: 38px; }
  .topbar nav { gap: 20px; }
  .topbar a, .quiet-links a { padding: 4px 0; }
  .pick-row, .prow, .switch-mode { min-height: 48px; }
}

/* Hover is a lie on a touchscreen: the state sticks to the last thing
   tapped until you tap elsewhere. Leave the colour cues to :active. */
@media (hover: none) {
  .door:hover { border-color: var(--line); background: var(--surface); }
  .pick-row:hover, .switch-mode:hover { background: transparent; }
  .switch-mode:hover { border-color: var(--line); }
  .door:active { border-color: var(--accent); background: var(--accent-wash); }
  .pick-row:active { background: var(--accent-wash); }
}

/* Tables carry more columns than a phone has room for. Let them keep their
   real widths and scroll inside their own box, rather than squashing the
   numbers into unreadable slivers or pushing the whole page sideways.
   Scrolling inside a box only works if it's discoverable, though -- reported
   on Android as the page looking "distorted": a table cut off flush against
   the screen edge, with nothing saying there is more of it to the right and
   no visible scrollbar (touch Chrome hides overlay scrollbars almost
   immediately). The label says so in words instead. */
.board-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.board-wrap > .board { min-width: 460px; }
.board-wrap > .subjects { min-width: 520px; }
.board-wrap > .live-board { min-width: 760px; }

@media (max-width: 620px) {
  main { padding: 24px 14px 60px; }
  .topbar { flex-wrap: wrap; gap: 8px 16px; padding: 12px 14px; }
  /* Wrapping eight links to a second line was the other half of the Android
     report: nav's own row-gap plus the topbar's wrap-gap stacked, reading as
     a big, uneven jump before "Admin". One scrollable row removes the second
     line -- and the jump -- entirely, the same move .board-wrap already
     makes for a table that does not fit. */
  .topbar nav {
    flex-wrap: nowrap; overflow-x: auto; -webkit-overflow-scrolling: touch;
    gap: 14px; justify-content: flex-start;
  }
  .topbar nav a, .topbar nav form { flex: none; }

  .board-wrap { position: relative; }
  .board-wrap::before {
    content: "swipe for more →";
    display: block; text-align: right;
    font-size: 11px; color: var(--ink-faint); margin: 0 0 3px;
  }
  h1 { font-size: 22px; }
  .score-big { font-size: 42px; }

  .door { padding: 18px 16px; }
  .door-title { font-size: 17px; }

  /* The picker's six columns become two lines: the decision (tick, subject,
     how many this paper gets) on top, the evidence for it underneath. The
     header row goes away, so each number carries its own label. */
  .panel-head { display: none; }
  .pick-row {
    grid-template-columns: 22px auto auto auto 1fr auto;
    gap: 3px 12px;
    padding: 12px 14px;
  }
  .pick-row .pick  { grid-row: 1 / span 2; grid-column: 1; align-self: center; }
  .pick-name       { grid-row: 1; grid-column: 2 / 6; }
  .pick-row .alloc { grid-row: 1; grid-column: 6; }
  .pick-marks { grid-row: 2; grid-column: 2; }
  .pick-avail { grid-row: 2; grid-column: 3; }
  .pick-seen  { grid-row: 2; grid-column: 4; }
  .pick-marks, .pick-avail, .pick-seen {
    text-align: left; font-size: 12px; color: var(--ink-faint);
  }
  .pick-marks::before, .pick-avail::before, .pick-seen::before {
    content: attr(data-label) " ";
  }

  .controls { padding: 14px; }
  .segmented { display: flex; width: 100%; }
  .seg { flex: 1; padding: 10px 6px; }

  .confirm { padding: 18px 16px; }
  .headline, .projection { padding: 18px 16px; }
  .card { padding: 14px 16px; }
  .login-card { margin: 40px auto; padding: 22px; }
  .tiles { grid-template-columns: repeat(auto-fit, minmax(88px, 1fr)); }
  .review-choices { padding-left: 20px; }
}

/* ══════════ the material: PYQ and Q-Bank front doors, and a Subject ══════════

   These two screens are the same screen twice, parameterised by kind, so the
   styling is deliberately one block rather than two. `.sub` is the unit: a
   subject, what it is worth in October, and how you do on it -- ranked by the
   first, coloured by the second.                                             */

.page-h1 { font-size: 26px; font-weight: 600; letter-spacing: -0.02em; margin: 4px 0 0; }

/* What the pool is made of, stated as facts rather than offered as controls:
   nothing here filters yet, and a chip that looks clickable and is not is worse
   than a plain sentence. */
.facets { display: flex; flex-wrap: wrap; gap: 6px; margin: 16px 0 4px; }
.facet {
  font-size: 12px; padding: 3px 10px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--surface); color: var(--ink-soft);
}
.facet b { color: var(--ink); font-weight: 600; margin-left: 4px; }

.sgrid {
  display: grid; gap: 9px; margin: 18px 0;
  grid-template-columns: repeat(auto-fill, minmax(215px, 1fr));
}
.sub {
  display: block; padding: 13px 14px; text-decoration: none; color: inherit;
  background: var(--surface); border: 1px solid var(--line); border-radius: 10px;
}
.sub:hover { border-color: var(--accent); background: var(--accent-wash); }
.sub-t { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
.sub-t b { font-weight: 600; }
.sub-n { font-size: 12px; color: var(--ink-faint); }
.sub small { display: block; font-size: 11.5px; color: var(--ink-faint); margin-top: 6px; }

/* The bar carries the accuracy, and its colour carries the judgement. A subject
   never attempted gets a stub in the faint colour rather than a full-width bar
   in the good one -- "no evidence" must not read as "fine". */
.meter { display: block; height: 5px; margin-top: 9px; border-radius: 999px;
         background: #eceae5; overflow: hidden; }
.meter i { display: block; height: 100%; background: var(--good); }
.meter i.low { background: var(--bad); }
.meter i.mid { background: #c98a2e; }
.meter i.none { background: var(--line-firm); }

/* A paper inside a subject: one sitting, or one module. */
.mrow {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 14px; border-top: 1px solid var(--line);
}
.mrow-name { flex: 1; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.mrow-name b { font-weight: 600; }
.mrow .right { flex: none; text-align: right; }

.sizes { display: flex; flex-wrap: wrap; gap: 8px; padding: 12px 14px; }
.sizes .chip { cursor: pointer; }
.sizes .chip.on { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }

.refine { border-top: 1px solid var(--line); }
.refine > summary {
  padding: 10px 14px; cursor: pointer; font-size: 13px; color: var(--ink-soft);
}
.refine > summary:hover { color: var(--accent); }
.refine-body { padding: 0 14px 12px; }
.refine-body .field-label { padding: 4px 0 0; }

.tag.good { background: var(--good); }
.tag.warn { background: #c98a2e; }
.tag.bad  { background: var(--bad); }

/* The two front doors on home, under the intents. */
.entrances { display: grid; gap: 9px; grid-template-columns: 1fr 1fr; margin: 14px 0 4px; }
.entrance {
  display: grid; grid-template-columns: 1fr auto; gap: 2px 10px; align-items: center;
  padding: 14px 16px; text-decoration: none; color: inherit;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
}
.entrance:hover { border-color: var(--accent); background: var(--accent-wash); }
.entrance-title { grid-row: 1; grid-column: 1; font-size: 15px; font-weight: 600; }
.entrance-blurb { grid-row: 2; grid-column: 1; font-size: 13px; color: var(--ink-soft); }
.entrance .door-go { grid-row: 1 / span 2; grid-column: 2; }

.empty { color: var(--ink-soft); padding: 18px 0; }

@media (max-width: 560px) { .entrances { grid-template-columns: 1fr; } }

/* ══════════ the dashboard ══════════════════════════════════════════════════

   The chart is the screen. It is deliberately larger than the suggestion under
   it: the app tells you the truth before it tells you what to do (standing
   principle 4).                                                              */

.dash { display: grid; grid-template-columns: minmax(0, 1fr) 260px; gap: 26px; align-items: start; }
.dash-rail { display: grid; gap: 12px; }

/* Days to 31 October, as a fact rather than a sum. */
.runway { display: flex; align-items: baseline; gap: 8px; margin-bottom: 18px; }
.runway-days { font-size: 34px; font-weight: 600; letter-spacing: -0.03em; }
.runway-label { color: var(--ink-soft); font-size: 14px; }

.standing { padding: 16px 18px; }
.standing-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; }
.standing-cov { text-align: right; }
.standing-note { margin: 12px 0 0; }

/* One series, one axis, no legend. The dashed line is the pass mark -- a
   threshold, not data, so it never reads as a second series. */
.proj-chart { display: block; width: 100%; height: 90px; margin-top: 14px; overflow: visible; }
.proj-line { fill: none; stroke: var(--accent); stroke-width: 2; vector-effect: non-scaling-stroke; }
.proj-threshold { stroke: var(--line-firm); stroke-width: 1; stroke-dasharray: 4 4;
                  vector-effect: non-scaling-stroke; }
/* On the line, at the point it names. `vector-effect` keeps the dot round
   under the chart's non-uniform scaling. */
.proj-mark { fill: var(--accent); opacity: 0.5; cursor: help;
             vector-effect: non-scaling-size; }
.proj-mark:hover { opacity: 1; }

/* Present, not presiding. */
.suggest {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin: 14px 0 22px; padding: 13px 16px;
  background: var(--accent-wash); border: 1px solid #cfe0ee; border-radius: var(--radius);
}
.suggest-body { flex: 1; min-width: 220px; display: grid; gap: 3px; }

.rcard { background: var(--surface); border: 1px solid var(--line);
         border-radius: var(--radius); padding: 13px 14px; }
.rcard .btn.small { margin-top: 10px; }
.lrow { display: grid; gap: 3px; padding: 9px 0; border-top: 1px solid var(--line); }
.lrow:first-of-type { border-top: 0; }
.lrow .btn { margin-top: 6px; justify-self: start; }
.wrow { display: grid; gap: 5px; padding: 7px 0; }
.wrow-head { display: flex; justify-content: space-between; font-size: 13px; }

.up { color: var(--good); }

@media (max-width: 860px) {
  .dash { grid-template-columns: 1fr; }
  .dash-rail { order: 2; }
}

/* A PYQ sitting is a paper you can sit, so its chip is a button. A source is
   not, so those stay plain spans -- the shape says which is which. */
button.facet { cursor: pointer; font: inherit; font-size: 12px; }
button.facet:hover { border-color: var(--accent); background: var(--accent-wash); color: var(--ink); }
