/* ── Results database table ─────────────────────────────────────────────── */
/* A real table (not card rows) because this view's job is scanning many
   sittings at once and comparing down a column — the same reason the
   reference tool uses one here and cards elsewhere. */
.results-table-wrap { overflow-x: auto; background: var(--warm-white); border: 1px solid var(--border-card); border-radius: var(--radius-lg); }
.results-table { width: 100%; border-collapse: collapse; font-size: var(--text-md); }
.results-table th {
  text-align: left; padding: 11px 14px; background: var(--sand); color: var(--text-primary);
  font-weight: 700; font-size: 12px; letter-spacing: 0.03em; text-transform: uppercase; white-space: nowrap;
}
.results-table td { padding: 11px 14px; border-top: 1px solid var(--border); vertical-align: middle; }
.results-table tbody tr { cursor: pointer; }
.results-table tbody tr:hover { background: var(--parchment); }
.results-sub { color: var(--text-muted); font-size: 12px; }
/* Fixed width, so a column of them has ONE left edge. Inline-block pills of
   varying content were why every row's score started at a different x and the
   table read as crooked. */
.score-pill {
  display: inline-block; min-width: 72px; text-align: center;
  padding: 2px 9px; border-radius: 10px; background: var(--sand); font-weight: 700;
}
.score-pill.score-good { background: var(--correct-bg); color: var(--correct-text); }
.score-pill.score-mid { background: #FBEFD5; color: var(--state-warn-text); }
.score-pill.score-low { background: #FBE1DD; color: #A5321F; }
/* Blank paper. Still negative — leaving it blank is bad, and it still counts
   zero everywhere — but a DIFFERENT bad from getting things wrong, so the
   first thing seen before a parent conversation isn't a red 0% that
   misdescribes what happened. Deeper red and outlined, so it reads as worse
   than a low score rather than milder. */
.score-pill.score-none {
  background: #F6D9D3; color: #8C2716; border: 1.5px solid #C0654F;
  padding: 1px 8px;
}
.score-pill.score-pending { background: var(--sand); color: var(--brown-ink); font-weight: 600; }
.score-pill.score-live { background: #E4EAF5; color: #2F4A7A; }

/* ── Attempt review ─────────────────────────────────────────────────────── */
.review-cards { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 20px; }
@media (max-width: 720px) { .review-cards { grid-template-columns: 1fr; } }
.review-card { background: var(--warm-white); border: 1px solid var(--border-card); border-radius: var(--radius-lg); padding: 16px 18px; }
.review-card-score { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.review-card-label { font-size: var(--text-xs); font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-muted); }
.review-card-name { font-size: 19px; font-weight: 600; font-family: var(--font-display); margin-top: 2px; }
.review-card-sub { font-size: 13px; color: var(--text-secondary); }
.score-ring { width: 88px; height: 88px; flex-shrink: 0; }

.review-section { background: var(--warm-white); border: 1px solid var(--border-card); border-radius: var(--radius-lg); margin-bottom: 18px; overflow: hidden; }
.review-section-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; padding: 16px 18px; flex-wrap: wrap; }
.review-section-head h3 { font-size: var(--text-xl); }
.review-timer { min-width: 150px; }
.review-timer-bar { height: 6px; border-radius: 3px; background: var(--sand); overflow: hidden; margin-top: 5px; }
.review-timer-bar span { display: block; height: 100%; background: var(--brown-ink); }
.review-qhead {
  display: flex; justify-content: space-between; align-items: center;
  padding: 9px 18px; background: var(--parchment); border-top: 1px solid var(--border);
  font-size: 12px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-muted);
}
.review-q { border-top: 1px solid var(--border); }
.review-q-row { display: flex; align-items: center; gap: 12px; padding: 11px 18px; cursor: pointer; }
.review-q-row:hover { background: var(--parchment); }
.review-q-num { font-weight: 800; font-size: 13px; flex-shrink: 0; min-width: 34px; }
.review-q-stem {
  flex: 1; min-width: 0; font-size: var(--text-md); color: var(--text-secondary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.review-q-stem :is(p, div) { margin: 0; display: inline; }
/* Same collapse, same missing space as .stem-title-content — two lists that
   flatten a multi-paragraph stem onto one row, and both ran the paragraphs
   together ("…open before.You have 20 minutes."). */
.review-q-stem :is(p, div) + :is(p, div)::before { content: " "; }
/* Maths in a collapsed row: KaTeX's default display sizing makes stacked
   fractions taller than the row, which is what made these look cramped and
   mis-aligned. Pin to the row's own size and keep it on the baseline. */
.review-q-stem .katex { font-size: 0.95em; vertical-align: -0.15em; }
.review-q-stem img { display: none; }
/* Expanded: the body below carries the full stem (with real maths/images),
   so the truncated preview here would just repeat it. Keep the space so the
   number stays left and the time/marks stay right — only the text goes. */
.review-q-row.open .review-q-stem { visibility: hidden; }

/* Analytical layers, toggled from the "Show" chips. CSS-only so switching is
   instant and survives an expanded accordion — re-rendering would collapse
   every row the tutor had just opened. */
/* Its own row now, not squeezed between the heading and Expand all. Left
   aligned and wrapping normally, so five chips read as a list rather than as a
   staggered stack around whatever space was left. */
.review-show-toggles {
  display: flex; align-items: center; gap: 5px; flex-wrap: wrap;
  padding: 8px 18px 10px; background: var(--parchment);
  border-top: 1px solid var(--border);
}
/* Pushed to the end of the row, so the chips stay a single uninterrupted run
   and the least-used control is not sitting in the middle of them. */
.review-show-toggles .btn-icon { margin-left: auto; }
/* A hair apart from the layers it commands, and unmistakably not one of them:
   it is the only chip that changes what the others say. */
.review-layer-all { margin-right: 5px; font-weight: 700; }
.review-show-label { font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-secondary); }
.review-layer-chip { font-size: 11.5px; padding: 2px 9px; }
#review-sections.hide-time .review-time { display: none; }
#review-sections.hide-rushed .flag-rushed { display: none; }
#review-sections.hide-changes .flag-change,
#review-sections.hide-changes .review-change-note { display: none; }
/* The three analyses that used to live only in the downloaded report. Sits
   between the section header and the question list, because it describes the
   whole paper rather than any one row. */
.review-how-worked {
  padding: 10px 14px;
  border-top: 1px solid var(--border);
  background: var(--parchment);
  font-size: 0.86rem;
  line-height: 1.5;
}
.review-how-worked .review-card-label { display: block; margin-bottom: 2px; }
/* All three go together. They are one story — flagged, and whether they came
   back — so hiding the flag and leaving "never went back" behind would strand
   a mark explaining something no longer on the row. */
#review-sections.hide-flagged .flag-flagged,
#review-sections.hide-flagged .flag-returned,
#review-sections.hide-flagged .flag-not-returned { display: none; }
#review-sections.hide-monitoring .flag-monitor { display: none; }
.stem-img-chip {
  display: inline-block; padding: 0 6px; margin: 0 3px; border-radius: 4px;
  background: var(--sand); color: var(--brown-ink);
  font-size: var(--text-xs); font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
  vertical-align: middle;
}
.review-timer-sub { font-size: 11.5px; color: var(--text-muted); margin-top: 3px; }
/* Expanded body: the question as the student actually saw it, images and
   all, with room to breathe. */
.review-full-stem { font-size: 14.5px; line-height: 1.6; margin-bottom: 12px; }
.review-full-stem img { max-width: 100%; height: auto; border-radius: 6px; margin: 8px 0; display: block; }
.review-full-stem .katex { font-size: 1.05em; }
.review-full-stem :is(p, div) { margin: 0 0 6px; }
.review-q-meta { display: flex; align-items: center; gap: 10px; flex-shrink: 0; font-size: 13px; }
.review-time { color: var(--text-muted); }
.review-marks { font-weight: 700; }
.review-marks.good { color: var(--correct-text); }
.review-marks.bad { color: #A5321F; }
.review-chevron { color: var(--text-muted); width: 12px; }
/* Each marker says what it is, in words. A glyph alone is a private code —
   it needed a legend on the same screen ("What do these mean?") to be read at
   all, and reached a screen reader through a title attribute, which is to say
   not reliably. The glyph stays as decoration beside the word. */
.review-flag {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 11.5px; font-weight: 700; padding: 1px 7px; border-radius: 8px;
  white-space: nowrap;
}
.review-flag [aria-hidden="true"] { font-weight: 400; }
/* Deliberately the loudest flag on the row: a warned-and-repeated breach is
   the one thing here you'd actually raise with a student or parent. Hover
   gives the breakdown of which signals made up the count. */
.flag-monitor { background: #A5321F; color: #fff; cursor: help; }
.flag-rushed { background: #FBEFD5; color: var(--state-warn-text); }
/* Quiet, both of them. Neither is a verdict: going back is the method working
   and not going back is usually running out of time, so neither belongs in the
   warn or breach colours. They sit next to the flag they explain, and the row
   already carries the loud marks for anything that needs one. */
.flag-returned { background: var(--sand); color: var(--brown-ink); }
.flag-not-returned {
  background: transparent; color: var(--text-secondary);
  border: 1px dashed var(--border-card);
}
.flag-helped { background: var(--correct-bg); color: var(--correct-text); }
.flag-hurt { background: #FBE1DD; color: #A5321F; }
.flag-neutral { background: var(--sand); color: var(--brown-ink); }

.review-q-body { padding: 4px 18px 16px; }
.review-options { display: flex; flex-direction: column; gap: 6px; }
.review-option {
  display: flex; align-items: baseline; gap: 9px; padding: 8px 12px;
  border: 1px solid var(--border); border-radius: var(--radius-sm); font-size: 14px;
}
.review-option.is-correct { border-color: var(--correct-border); background: #F3F8F3; }
.review-option.is-wrong { border-color: #C0392B; background: #FDF3F1; }
.review-option-letter { font-weight: 800; min-width: 14px; }
.review-option :is(p, div) { margin: 0; display: inline; }
.review-option-tag { margin-left: auto; font-size: var(--text-xs); font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted); white-space: nowrap; }
.review-option-tag.good { color: var(--correct-text); }
.review-answer-pair { display: flex; gap: 28px; flex-wrap: wrap; font-size: 14px; }
.review-answer-pair .bad { color: #A5321F; }
.review-comment { margin-top: 10px; font-size: 13px; color: var(--text-secondary); }

.results-bulk-bar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 10px 14px; margin-bottom: 12px;
  background: var(--sand); border-radius: var(--radius-md); font-size: var(--text-md); font-weight: 600;
}
.results-table .col-check { width: 34px; }
.results-table .col-check input { cursor: pointer; }

/* Numbers right-aligned and tabular, so digits line up down the column
   instead of drifting with their own widths. */
.results-table .col-num { text-align: right; white-space: nowrap; }
/* The focus-monitoring flag had been living INSIDE the date cell, which made
   one row's date column wider than every other row's — the visible kink. */
.results-table .col-flag { width: 44px; text-align: center; }
.results-table th.col-num .col-sort { justify-content: flex-end; width: 100%; }
.col-sort {
  background: none; border: none; padding: 0; font: inherit; color: inherit;
  cursor: pointer; display: inline-flex; align-items: center; gap: 3px;
  text-transform: inherit; letter-spacing: inherit;
}
.col-sort:hover { color: var(--brown-ink); }
.results-table th.sorted { color: var(--brown-ink); }
.sort-caret { font-size: 10px; }
.cell-link { color: inherit; font-weight: 700; text-decoration: none; }
.cell-link:hover { text-decoration: underline; }
/* A sitting happening right now. Left border rather than a full tint: it must
   read as live without competing with the flag column for alarm. */
.results-table tr.row-live { background: #F7F9FD; }
.results-table tr.row-live td:first-child { box-shadow: inset 3px 0 0 #2F4A7A; }
/* Pinned, like the tag bar — it used to render below the table where you
   couldn't see it answer the tick you just made. */
#results-bulk-bar {
  position: fixed; bottom: 0; left: var(--sidebar-width); right: 0; z-index: 60;
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  background: var(--sand); border-top: 1px solid var(--border-card);
  padding: 12px 24px; box-shadow: 0 -4px 16px rgba(46, 35, 24, 0.10);
}
@media (max-width: 700px) { #results-bulk-bar { left: 0; } }
body.results-bulk-open .results-table-wrap { margin-bottom: 96px; }
/* Cohort line on the review page — analytical extra, so visually quieter
   than the answer itself. */
.review-cohort { margin-top: 10px; font-size: var(--text-sm); color: var(--text-muted); }
/* Inline plain maths inside a collapsed row (see humanizeLatex) — a hair
   of letter-spacing keeps "3/4 + 1/8" legible at row size. */
.stem-maths-inline { font-family: var(--exam-font); letter-spacing: 0.01em; }

/* ── Download-report modal (review page) ──────────────────────────────────
   Same control shapes as the answer-sheet version in player.css, but using
   the authoring palette variables rather than that file's literals. */
.dl-card { max-width: 520px; text-align: left; }
/* Switching to CSV removes the three PDF-only shape tabs and their hint, so
   the card does get shorter. That one is a real change of what's on offer,
   not incidental jitter, and it happens below the control you just clicked —
   the format tabs and everything above them hold still. Reserving 156px of
   empty band for a control that isn't there would read as a hole, and a
   min-height on the card would be taller than most laptop viewports. */
.format-tabs { display: flex; gap: 6px; margin-top: 10px; }
.format-tab, .style-tab {
  flex: 1; padding: 9px 12px; border: 1.5px solid var(--border); border-radius: 8px;
  background: var(--parchment); color: var(--brown-ink);
  font-weight: 600; font-size: 13px; line-height: 1.3; cursor: pointer;
}
.format-tab.active { background: var(--deep-ink); border-color: var(--deep-ink); color: var(--parchment); }
.style-tab.active { background: var(--brown-ink); border-color: var(--brown-ink); color: var(--parchment); }
/* No inner max-height: clipped to ~85px on a laptop it hid 6 of 9 options
   with no scrollbar to hint at them. The card itself already scrolls. */
/* Every region that changes size gets a floor at its own tallest state, so
   the Download button never moves while you compare the three shapes.
   Measured, not guessed — the tickbox list runs 218/238/267px across the
   three document shapes, and the preset hint 48/63/105px across Custom,
   Tutor/Practice and Parent. If you lengthen one of those strings past its
   floor the jumping comes back, so raise the floor with the text. */
.report-field-checks { display: flex; flex-direction: column; gap: 8px; min-height: 270px; }
/* Two lines now for all four states, rather than reserving for one outlier
   that left a visible hole under the shorter ones. Levelling the strings is
   the better half of the fix; the floor is only the safety net. */
#dl-preset-hint { min-height: 3.4em; }
#dl-style-hint { min-height: 63px; }
.report-check-row { display: flex; align-items: flex-start; gap: 8px; font-size: var(--text-md); color: var(--text-primary); cursor: pointer; }
.report-check-row input { margin-top: 3px; }

/* Results filter selects. Sit on one line with the state chips; wrap as a
   block on narrow screens rather than squeezing the labels. */
.results-selects { display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap; margin: 10px 0 2px; }
.results-select-wrap { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.results-select-wrap > span {
  font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--text-secondary); font-weight: 700;
}
.results-select {
  padding: 7px 10px; border: 1.5px solid var(--border); border-radius: var(--radius-sm);
  background: var(--warm-white); color: var(--text-primary); font-size: var(--text-md); font-weight: 600;
  max-width: 320px;
}
.results-select:focus { outline: none; border-color: var(--brown-ink); }
@media (max-width: 560px) { .results-select { max-width: 100%; width: 100%; } .results-select-wrap { width: 100%; } }

/* KaTeX's `font:` shorthand hard-sets font-weight:normal, so maths stayed
   light inside a bold heading while the words either side of it went bold —
   "What is <b>2/5 + 1/5</b> as a fraction?" rendered half-bold. Inheriting
   instead makes maths follow whatever weight its context has. KaTeX's own
   deliberate bolds (\mathbf, \textbf) declare font-weight:700 on their own
   descendant spans, so they still win over this. */
.katex { font-weight: inherit; }

/* ── Import review ───────────────────────────────────────────────────────── */

/* ── Editor side column: the author's copy of the student's left pane ──────
   Same extracts, same tabs, beside the question form. Writing "In Extract B,
   why does…" should happen with Extract B on screen — the dropdown only ever
   named it, which is why questions were written from memory. */
.editor-side-tabs { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 12px; }
.editor-side-tab {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--parchment); color: var(--text-secondary);
  font-size: var(--text-sm); font-weight: 700; padding: 6px 11px; cursor: pointer;
}
.editor-side-tab:hover { color: var(--text-primary); }
.editor-side-tab.active {
  background: var(--sand); color: var(--text-primary); border-color: var(--border-card);
}
.editor-reading-pane { max-height: calc(100vh - 220px); overflow-y: auto; }
.editor-reading-body { font-size: 14.5px; line-height: 1.7; color: var(--text-primary); }
.editor-reading-body p { margin: 0 0 10px; }
.editor-reading-body img { max-width: 100%; height: auto; border-radius: 6px; margin: 6px 0; }

/* ── Reading-text group headers in the question list ───────────────────────
   The list takes the shape of the paper: each text heads the run of questions
   that read it, so "these 23 questions use Extract A/B" is visible instead of
   being reconstructed from 23 identical lines of grey metadata. Grouped by
   consecutive RUN, so the headers never imply a different order. */
.reading-group {
  display: flex; align-items: center; gap: 12px;
  margin: 22px 0 6px; padding: 12px 16px;
  background: var(--sand); border: 1px solid var(--border-card);
  border-radius: var(--radius-md);
}
.reading-group:first-child { margin-top: 0; }
.reading-group-caret {
  background: none; border: none; padding: 0; width: 16px; flex-shrink: 0;
  font-size: 13px; color: var(--brown-ink);
}
.reading-group-main { flex: 1; min-width: 0; }
.reading-group-title { font-family: var(--font-display); font-weight: 600; font-size: 14.5px; }
.reading-group-meta { font-size: var(--text-sm); color: var(--text-secondary); margin-top: 2px; }
/* Questions with no text get a quieter divider — it's a boundary marker, not
   a thing you act on, so it shouldn't compete with the real headers. */
.reading-group-plain {
  background: none; border: none; border-top: 1px dashed var(--border);
  border-radius: 0; padding: 14px 4px 4px; margin-bottom: 2px;
  font-size: var(--text-sm); color: var(--text-muted);
}
.reading-group-plain strong { font-weight: 700; }
/* ── The group is a container, not a heading ───────────────────────────────
   A passage and its questions are ONE object — delivery shuffles whole units
   for exactly that reason. As a header followed by six siblings the list said
   the opposite everywhere except in the reader's head, and an indent is not a
   boundary: it gives the run a start and no end. */
.qs-list { display: grid; gap: 8px; }
.qs-group {
  border: 1px solid var(--border-card); border-radius: var(--radius-md);
  background: var(--warm-white); overflow: hidden; margin: 0;
}
.qs-group:first-child { margin-top: 0; }
/* Inside a group the header is the group's own title bar: a sand bar across
   the top of the container, not a free-standing card with its own border. */
.qs-group-head {
  margin: 0; border: 0; border-radius: 0; padding: 11px 14px;
  background: var(--sand-soft); border-bottom: 1px solid var(--hairline);
}
.qs-group > .reading-group-plain { border-top: 0; }
.qs-group-name { font-size: 14.5px; font-weight: 700; }

/* ── The question row ──────────────────────────────────────────────────────
   Fixed columns so the eye runs down ONE column — which questions have no
   skill, which are flagged — instead of re-reading a paragraph per question.
   The row is not a card: a card has a border and a hover lift, which says
   "separate object", and a question inside a reading group is not one. */
.qs-q {
  display: grid;
  /* The flags column is fixed, not auto. As `auto` it took its max-content
     width — three chips on a broken question — and the stem, the one thing
     you actually read, was squeezed to 166px beside a 439px strip of chips.
     Fixed width, wrapping chips: the stem keeps the room. */
  grid-template-columns: 24px 26px 40px minmax(0, 1fr) 140px 150px auto;
  gap: 12px; align-items: center;
  padding: 9px 14px; border-bottom: 1px solid var(--hairline);
  background: var(--warm-white);
}
.qs-q:last-child { border-bottom: 0; }
.qs-q:hover { background: var(--parchment); }
/* A loose question — no reading text — is its own row on the page rather than
   inside a container, so it carries the container's edges itself. */
.qs-list > .qs-q { border: 1px solid var(--border-card); border-radius: var(--radius-md); }
.qs-grip { min-height: 1px; }
.qs-n { font-size: var(--text-sm); font-weight: 700; color: var(--text-secondary); }
/* The position label IS the move control — type a number, press Enter. It
   reads as the label it replaced until you touch it: a row of 35 boxed input
   fields would turn a list you scan into a form you fill in. The affordance
   arrives on hover, where a person looking for it will find it. */
input.qs-n {
  width: 3.4em; padding: 2px 4px; text-align: center;
  font-family: inherit; font-variant-numeric: tabular-nums;
  background: transparent; border: 1px solid transparent;
  border-radius: var(--radius-sm); cursor: text;
}
input.qs-n:hover { border-color: var(--border); background: var(--warm-white); }
input.qs-n:focus {
  border-color: var(--brown-ink); background: var(--warm-white);
  outline: none; color: var(--text-primary);
}
input.qs-n:disabled { opacity: 0.5; }
.qs-stem { min-width: 0; }
/* Two lines, not one. The design clamps the stem to a single line, which is
   right for prose; ours can hold typeset maths, and a half-clipped fraction
   is worse than a second line. */
.qs-stem .stem-title-row {
  font-weight: 500; font-size: 14px; line-height: 1.4;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.qs-sub { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
.qs-skill { font-size: var(--text-sm); color: var(--text-secondary); }
.qs-skill.is-missing { color: var(--brown-ink); font-weight: 600; }
.qs-flags { display: flex; gap: 5px; flex-wrap: wrap; justify-content: flex-end; }
.qs-lock {
  display: inline-flex; align-items: center; gap: 5px; font-size: 11.5px; font-weight: 600;
  color: var(--brown-ink); background: var(--state-warn-bg); border: 1px solid var(--flag-gold);
  border-radius: 999px; padding: 2px 9px;
}
/* Two tones only: something a student would hit, and something to check. */
.qs-lock.is-bad { color: var(--state-bad-text); background: var(--state-bad-bg); border-color: var(--state-bad-border); }
.qs-q.is-flagged { background: var(--state-attention-bg); }
.qs-q.is-broken { background: var(--state-bad-bg); }
/* Narrow: the columns stop being columns, and the row goes back to stacking.
   A 150px skill column at 700px wide leaves the stem no room at all. */
@media (max-width: 1000px) {
  .qs-q { grid-template-columns: 24px 26px 40px minmax(0, 1fr) auto; }
  .qs-skill, .qs-flags { grid-column: 4 / -1; justify-content: flex-start; }
  .band-actions { grid-column: 1 / -1; justify-content: flex-end; }
}
@media (max-width: 640px) {
  .reading-group { flex-wrap: wrap; }
  .qs-group { padding: 8px; }
}

/* Step through questions from inside the editor — writing a paper is a
   sequence, and bouncing out to the list between every question isn't. */
.editor-nav { display: flex; align-items: center; gap: 12px; margin: 4px 0 2px; }
.editor-nav .editor-position { margin: 0; }
.editor-nav .btn-icon:disabled { opacity: 0.35; cursor: not-allowed; }

/* ── Start-page editor (test settings) ─────────────────────────────────────
   The instructions field IS a page the student reads, so it's edited inside a
   frame shaped like that page rather than in a box beside another field. The
   editor sits in the flow at the real measure and the real font; the header,
   the minutes/marks line and the timer warning are the player's own furniture,
   drawn flat so the authored middle is seen between the fixed top and bottom.

   Two destinations, two frames — the field goes to a different screen
   depending on the test. A standalone paper's instructions are the last page
   before Begin, in the neutral CBT chrome at 960px. A set's instructions are a
   box on the branded picker, ~500px wide in the brand body face. Showing both
   at the same
   size would make one of them a lie, and the wide one is the lie that costs:
   an author would format to a measure twice what the student gets. */
.startpage-frame {
  border: 1.5px solid var(--border); border-radius: var(--radius-md); overflow: hidden;
  margin-top: 10px;
}
.startpage-chrome {
  display: flex; align-items: center; justify-content: space-between;
  padding: 8px 14px; background: #fff; border-bottom: 2px solid #d7dae0;
  font-family: var(--exam-font); font-size: var(--text-sm); color: #555;
}
.startpage-chrome-title { font-weight: 700; color: #1a1a1a; }
.startpage-chrome-brand { font-weight: 800; letter-spacing: 0.02em; }
.startpage-chrome-foot { border-bottom: none; border-top: 2px solid #d7dae0; }
.startpage-chrome-back { color: #8b9099; }
.startpage-begin {
  background: var(--correct-border); color: #fff; border-radius: 4px; padding: 4px 14px; font-weight: 700;
}
.startpage-page {
  background: #fff; padding: 22px 24px 26px; font-family: var(--exam-font);
}
/* Headings too — the app-wide `h1,h2,h3 { var(--font-display) }` at the top of
   this file was reaching inside the preview and setting the paper's title, and
   any heading the author typed with the H2/H3 buttons, in the BRAND display
   face. The player never does that: .instr-page h2 declares no family, so it
   inherits the CBT system stack like everything else on that screen.
   So the preview was showing a font the exam will not use — on the one screen
   whose entire job is showing what the exam will look like, and on the one
   element an author reads first. Everything inside this frame follows the
   student's font, headings included. */
.startpage-page h1,
.startpage-page h2,
.startpage-page h3 { font-family: var(--exam-font); }
/* The measure the student reads at (player.css .instr-body), so a line that
   wraps here wraps there. */
.startpage-page > * { max-width: 912px; margin-left: auto; margin-right: auto; }
.startpage-fixed { color: #1a1a1a; }
.startpage-head h2 { font-size: 26px; font-weight: 800; margin-bottom: 4px; }
.startpage-meta { font-size: var(--text-lg); font-weight: 600; color: #555; margin-bottom: 14px; }
.startpage-tail { margin-top: 14px; }
.startpage-warning { font-size: var(--text-lg); font-weight: 700; }
/* The integrity notice the player inserts between the authored text and the
   timer warning — present only when focus monitoring is on, same as there. */
.startpage-integrity {
  background: #FFF8E8; border: 1px solid var(--flag-gold); border-radius: 8px;
  padding: 10px 14px; margin-bottom: 12px; font-size: 13px; line-height: 1.55;
}
.startpage-integrity.hidden { display: none; }
.startpage-integrity strong { display: block; margin-bottom: 2px; }
/* The editable middle. No border of its own — it's part of the page, not a
   field sitting on it — so the toolbar is what marks it as editable, and a
   focus ring confirms it. */
.startpage-rte { border: none; border-radius: 0; background: transparent; overflow: visible; }
.startpage-rte .rte-toolbar {
  border: 1px solid var(--border); border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  background: var(--parchment);
}
.startpage-rte .rte-editor {
  border: 1px solid var(--border); border-top: none; border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  min-height: 300px; max-height: 52vh; font-size: var(--text-lg); line-height: 1.6; padding: 14px 16px;
}
.startpage-rte .rte-editor:focus { border-color: var(--brown-ink); }
/* Set-level: a box on the branded picker, not a page in the exam chrome. */
.startpage-frame[data-surface="picker"] .startpage-chrome { display: none; }
.startpage-frame[data-surface="picker"] .startpage-page {
  background: #FAF6EE; font-family: var(--font-body); padding: 24px;
}
.startpage-frame[data-surface="picker"] .startpage-page > * { max-width: 500px; }
.startpage-frame[data-surface="picker"] .startpage-head h2 { font-size: var(--text-2xl); color: #2E2318; }
.startpage-frame[data-surface="picker"] .startpage-meta { color: #6A5A48; }
.startpage-frame[data-surface="picker"] .startpage-tail { display: none; }
.startpage-frame[data-surface="picker"] .startpage-rte .rte-editor {
  font-family: var(--font-body); font-size: 14px; background: #FFFEF9;
  border-color: #D4C4A0; min-height: 220px;
}
.startpage-frame[data-surface="picker"] .startpage-rte .rte-toolbar { border-color: #D4C4A0; }
@media (max-width: 900px) {
  .startpage-page { padding: 16px; }
  .startpage-head h2 { font-size: 21px; }
}

/* ── Finish-screen preview (test settings) ─────────────────────────────────
   A scaled-down stand-in for the student's finished panel. Everything except
   the message is fixed by the player, so it's rendered flat as context — the
   point is to see the authored words in the space they'll actually occupy,
   not to reproduce the player pixel for pixel. */
.finish-preview { margin-top: 16px; }
.finish-preview-label { font-size: var(--text-xs); font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-muted); margin-bottom: 8px; }
.finish-panel {
  background: var(--parchment); border: 1px solid var(--border-card); border-radius: var(--radius-md);
  /* The brand body face, not the exam font: this is the branded picker. */
  padding: 18px; font-family: var(--font-body);
}
.finish-panel-title { font-family: var(--font-display); font-weight: 600; font-size: 17px; color: var(--deep-ink); }
.finish-panel-line { margin: 4px 0 12px; font-size: 13px; color: var(--text-secondary); }
.finish-panel-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  border: 1px solid var(--border-card); border-radius: var(--radius-sm); background: var(--warm-white);
  padding: 10px 12px; font-size: 13px; font-weight: 700; color: var(--deep-ink);
}
.finish-panel-chip { background: var(--correct-bg); color: var(--correct-text); border-radius: 50px; padding: 3px 10px; font-size: var(--text-xs); }
/* Same green message box the student gets (player.css .end-message-box). */
.finish-panel-msg {
  margin: 12px 0; padding: 10px 12px; background: var(--correct-bg); border: 1px solid var(--correct-border);
  border-radius: 6px; font-size: var(--text-sm); line-height: 1.6; color: var(--text-primary);
}
.finish-panel-msg p { margin: 0 0 8px; }
.finish-panel-msg p:last-child { margin-bottom: 0; }
.finish-panel-empty { color: var(--text-muted); font-style: italic; }
.finish-panel-cta {
  display: inline-block; margin-top: 14px; padding: 7px 16px; border-radius: 50px;
  background: var(--deep-ink); color: var(--parchment); font-size: 12px; font-weight: 700;
}

/* Gap-fill drafts in the import review: the paragraph with its gaps drawn in,
   so the author can see that gap 6 sits where gap 6 belongs before importing. */
.import-gap-text {
  margin: 0 0 10px; padding: 10px 12px; background: var(--parchment);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  font-family: var(--exam-font); font-size: 13px; line-height: 1.8;
  max-height: 220px; overflow-y: auto;
}
.import-gap {
  display: inline-block; min-width: 22px; padding: 1px 6px; margin: 0 2px;
  border: 1.5px solid var(--warm-tan); border-radius: 4px; background: var(--warm-white);
  font-weight: 700; font-size: 12px; color: var(--brown-ink); text-align: center;
}
