/* ── Grouped action menus ──────────────────────────────────────────────────
   <details> rather than a hand-rolled popover: it opens, closes and takes
   keyboard focus without any of that being reimplemented. The only script is
   closing the others when one opens, and closing on an outside click. */
.view-header-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.menu { position: relative; }
.menu > summary { list-style: none; cursor: pointer; display: inline-block; }
.menu > summary::-webkit-details-marker { display: none; }
.menu[open] > summary { background: var(--sand); }
.menu-panel {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 30; width: 320px;
  background: var(--warm-white); border: 1px solid var(--border-card);
  border-radius: var(--radius-md); box-shadow: 0 8px 24px rgba(46, 35, 24, 0.16);
  padding: 6px; display: flex; flex-direction: column;
}
.menu-item {
  display: block; width: 100%; text-align: left; background: none; border: none;
  border-radius: var(--radius-sm); padding: 9px 10px; font: inherit; font-weight: 700;
  color: var(--text-primary); cursor: pointer;
}
.menu-item:hover { background: var(--parchment); }
.menu-item > span {
  display: block; margin-top: 2px; font-weight: 400; font-size: 12px;
  line-height: 1.45; color: var(--text-secondary);
}
.menu-item.hidden { display: none; }
/* A heading INSIDE a menu, so a run of five options reads as one question
   with five answers rather than five unrelated commands. */
.menu-heading {
  padding: 8px 12px 4px; font-size: var(--text-xs); font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-secondary);
}
/* The one already chosen. Marked rather than hidden: a menu that removes the
   current answer makes you remember what it was to know whether you changed
   anything. */
.menu-select { display: block; padding: 2px 10px 8px; }
.menu-select select {
  width: 100%; font: inherit; font-size: var(--text-md); padding: 7px 8px;
  border: 1px solid var(--border-card); border-radius: var(--radius-sm);
  background: var(--warm-white);
}
/* Print items are a different kind of thing from Settings/Results/Access. */
.menu-solo #print-questions-btn { border-top: 1px solid var(--border); margin-top: 4px; padding-top: 10px; }

/* Category filter. A <select> instead of a chip row: free-text tags only ever
   accumulate, so the row was a wall waiting to happen — and one list makes a
   stray "horizon" beside "Selective horizon" visible, which wrapped chips
   never did. Status stays chips: four of them, and you want the counts. */
.filter-select { width: auto; min-width: 190px; max-width: 320px; padding-right: 28px; }

/* Test lifecycle pill — the state of the paper, read before its name. A
   half-built test used to be indistinguishable from a finished one. */
.status-pill {
  display: inline-block; margin-right: 8px; padding: 2px 9px; border-radius: 50px;
  font-size: 10.5px; font-weight: 800; letter-spacing: 0.05em; text-transform: uppercase;
  vertical-align: middle; border: 1.5px solid;
}
.status-draft   { color: #7A5A30; border-color: #D4C4A0; background: #FBF6EA; }
.status-open    { color: var(--correct-text); border-color: var(--correct-border); background: var(--correct-bg); }
.status-scheduled { color: #5A4020; border-color: #C9B48A; background: #FFFDF7; }
.status-closed  { color: #6A5A48; border-color: #DDD0B8; background: #F5F1E8; }
.status-ended   { color: #6A5A48; border-color: #BFB09A; background: #EFE9DC; }

/* Readiness panel — what stands between this paper and a student sitting it */
.readiness { margin: 0 0 18px; padding: 14px 16px; border-radius: var(--radius-md); border: 1px solid; }
.readiness-ready { background: var(--correct-bg); border-color: var(--correct-border); }
.readiness-blocked { background: #FBF6EA; border-color: #D4C4A0; }
/* Ready, but with something worth fixing. Its own colour on purpose: green
   would say "all clear" over a list of things that aren't, and the blocked
   parchment would say "you can't" over a button that works. */
.readiness-warned { background: #FFF8E8; border-color: var(--flag-gold); }
.readiness-warnings { margin: 6px 0 0 18px; font-size: 13px; line-height: 1.5; }
.readiness-warnings li { margin-bottom: 3px; }
.readiness h4 { margin: 0 0 6px; font-size: 14px; }
.readiness ul { margin: 6px 0 0 18px; font-size: 13px; line-height: 1.6; color: var(--text-secondary); }
.readiness-actions { margin-top: 12px; display: flex; gap: 8px; flex-wrap: wrap; }
.locked-banner {
  margin: 0 0 18px; padding: 12px 16px; border-radius: var(--radius-md);
  background: #FBF6EA; border: 1px solid #D4C4A0; font-size: var(--text-md); line-height: 1.6;
}
.locked-banner strong { display: block; margin-bottom: 2px; }

/* A change made in the closing seconds — the one worth finding. */
.review-flag.flag-late { background: #FBE1DD; border-color: #C0392B; color: #8E2418; }
.change-late { color: #8E2418; font-weight: 700; }

/* ── The student's own link (student profile) ──────────────────────────────
   Above the history because it's what you come here to FETCH when a parent
   messages asking where the next test is; the history is what you come here
   to read. */
.portal-link-card {
  background: var(--warm-white); border: 1px solid var(--border-card);
  border-radius: var(--radius-md); padding: 14px 16px; margin: 12px 0 18px;
}
.portal-link-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.portal-link-head strong { font-size: 14px; }
.portal-link-head .hint-text { margin: 0; flex: 1 1 260px; }
.portal-link-row { display: flex; align-items: center; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
.portal-link-row code {
  flex: 1 1 260px; background: var(--parchment); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 7px 10px; font-size: 13px;
  overflow-wrap: anywhere;
}
#portal-link-create-btn { margin-top: 10px; }

/* ── Bulk skill tagging (question list) ───────────────────────────────────
   The bar only exists while something is ticked, so it's a strip that
   appears where you're already looking rather than a permanent toolbar. */
.q-pick { display: flex; align-items: center; padding: 0 4px 0 0; cursor: pointer; }
.q-pick input { width: 16px; height: 16px; cursor: pointer; }
/* PINNED to the bottom, not inserted above the list.
   In the flow it pushed every question down 100px the moment you ticked one
   — so the row you had just clicked slid out from under the cursor, and
   ticking a second meant re-aiming. A bulk-action bar is the standard place
   for this: it appears over the content, and nothing you are pointing at
   moves. The list gets bottom padding while it's up so the last row is never
   hidden underneath it. */
.tag-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) { .tag-bar { left: 0; } }
body.tag-bar-open #question-list,
body.tag-bar-open #bank-list { padding-bottom: 120px; }
.tag-bar-count { font-weight: 700; font-size: var(--text-md); color: var(--deep-ink); }
.tag-bar .text-input { flex: 1 1 220px; max-width: 320px; margin: 0; }
/* Says the absence out loud. A blank space where a tag should be reads as
   "nothing to see"; this reads as the job it is. */
.badge-untagged {
  background: transparent; border: 1px dashed var(--warm-tan);
  color: var(--text-muted); font-style: italic;
}
.tag-bar-note { flex: 1 0 100%; margin: 2px 0 0; }

/* Named after the Tests list it was written for, and now the only thing left
   of it: the Horizon programme list, the week list and the Plus roster are
   .card-row too and all three lean on this to take the free space beside their
   action buttons. Renaming it is a separate tidy-up, not a change to make
   while the Tests list is moving. */
.test-row-main { flex: 1; min-width: 0; }

/* ── Tests: ONE TABLE, sorted by last activity ────────────────────────────
   This replaced four stage groups (Live now / Scheduled / Being built /
   Finished), and the reason is worth keeping: a group heading answers a
   question nobody opens this screen with. The two things looked for here are
   "the set I was last working on" and "which sets are live and who has sat
   them" — and grouping hid the set that needed marking three headings down
   while the headings themselves were often two rows tall and one row long.

   So: no grouping, state as a WORD in its own column, the state chips back to
   do the filtering without splitting the table, and the row you touched last
   named rather than hunted for.

   THE TRACKS ARE THE GRAMMAR. Set / Category / State / Last activity /
   actions, fixed, with the header naming each one once. Rows are role="row"
   over a grid rather than a <table> because the set cell holds two lines and
   the actions cell holds a <details> menu, both of which fight table layout —
   the roles are what make the header cells announce as headers.  */
.tr-head, .tr-row { grid-template-columns: minmax(0, 1fr) 132px 190px 128px 176px; }
/* START, not centre: a row that grows a second line would otherwise float its
   last column into the middle of the extra space. Same trap as .gp-row. */
.tr-row.is-lasthere { background: var(--state-attention-bg); box-shadow: inset 3px 0 0 var(--correct-border); }
.tr-row.is-lasthere:hover { background: var(--state-attention-bg); }

.tr-name-line { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
/* A real link with the destination in it, so Enter does what the click does.
   A click handler on the row is not reachable from a keyboard. */
.tr-name {
  font-family: var(--font-display); font-weight: 700; font-size: var(--text-lg);
  color: var(--text-primary); text-decoration: none; overflow-wrap: anywhere;
}
.tr-name:hover { text-decoration: underline; }
.tr-lasthere { color: var(--correct-text); background: var(--correct-bg); border: 1px solid var(--correct-border); }
.tr-shape { font-size: var(--text-sm); color: var(--text-secondary); margin-top: 4px; }
.tr-cat, .tr-when { font-size: 13px; color: var(--brown-ink); min-width: 0; overflow-wrap: anywhere; }

/* The state cell: a word, the job beside it, and under both a figure that says
   what it counted. Never a bare percentage of the roster — see .tr-sat. */
.tr-state-line { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.tr-state-word { font-size: var(--text-md); font-weight: 700; color: var(--text-primary); }
.tr-state-word.is-past { color: var(--text-muted); font-weight: 600; }
.tr-sat { font-size: 12px; color: var(--text-secondary); margin-top: 3px; }
.job-chip.is-warn { color: var(--state-warn-text); background: var(--state-warn-bg); border: 1px solid var(--flag-gold); }
.job-chip.is-bad { color: var(--state-bad-text); background: var(--state-bad-bg); border: 1px solid var(--incorrect-border); }
.job-chip.is-quiet { color: var(--text-secondary); background: var(--sand-soft); border: 1px solid var(--border-card); }

/* ONE LOUD ACTION PER ROW. The dark button appears only where there is a job
   to do, so ten rows carry one instruction rather than ten equal offers.

   WRAP, because the column is 176px and a row can carry three controls:
   "Keep building", "Results" and the menu come to about 270px of nowrap
   buttons. A flex row that overflows with justify-content: flex-end
   overflows to the LEFT, so on the first live Tests screen (20 Sep 2026)
   the buttons sat on top of "Edited yesterday" in the column beside them.
   Wrapping keeps every row's columns aligned (each row is its own grid, so
   a max-content column would give every row a different width) and puts
   the third control on a second line instead of over the neighbour. */
.tr-actions { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; align-items: flex-start; }
.tr-go {
  font-family: var(--font-ui); font-size: var(--text-sm); font-weight: 700; white-space: nowrap;
  color: var(--text-primary); background: var(--sand);
  border: 1px solid var(--border-card); border-radius: 8px; padding: 7px 14px; cursor: pointer;
}
.tr-go:hover { border-color: var(--brown-ink); }
.tr-go.is-ghost { background: transparent; color: var(--brown-ink); }
.tr-go.is-loud { background: var(--deep-ink); color: var(--parchment); border-color: var(--deep-ink); }
.tr-go.is-loud:hover { background: var(--ink-hover); border-color: var(--ink-hover); }
.tr-actions .row-menu > summary { padding: 7px 11px; }
.tr-foot { font-size: var(--text-sm); line-height: 1.6; color: var(--text-muted); margin: 10px 18px 0; }

/* The sort control, at the right of the filter bar where it is a property of
   the list rather than another filter on it. */
.test-sort { display: flex; align-items: center; gap: 8px; margin-left: auto; }
.test-sort > span { font-size: 12px; color: var(--text-muted); }
.test-sort .filter-select { min-width: 0; }
.tag-chip.chip-warn {
  color: var(--state-warn-text); background: var(--state-warn-bg); border-color: var(--flag-gold);
}
.tag-chip.chip-warn.active { color: var(--warm-white); background: var(--deep-ink); border-color: var(--deep-ink); }



/* ── Students: the same table, with the jobs sorted to the top ────────────
   Five facts became two columns and a meta line. The old row carried a run of
   chips — duplicate name, no gender, N to mark, sitting now, N to do — which
   levelled two genuinely different things: what I OWE this student, and what
   SHE owes me. "I have her essay to mark" and "she has three papers to sit"
   were the same shade of amber, side by side, in no order.

   So: Needs you is one job in words with how long it has waited, Their to-do
   is kept apart from it, and Papers sat is a total rather than a task. The
   rows that have a job sort first and carry the gold rule — not a separate
   block above the table, which would be a second list of the same people,
   read twice and stale the moment one cleared. */
.roster-head, .roster-row { grid-template-columns: minmax(0, 1fr) 210px 132px 104px 152px; }
/* Gold, not green: this is work waiting, where the Tests list's tint marks the
   row you were last on. Same tint family, different rule colour, and the word
   in the column is what actually carries the meaning. */
.roster-row.is-job { background: var(--state-attention-bg); box-shadow: inset 3px 0 0 var(--flag-gold); }
.roster-row.is-job:hover { background: var(--state-attention-bg); }

.roster-name {
  font-family: var(--font-display); font-weight: 700; font-size: var(--text-lg);
  color: var(--text-primary); text-decoration: none; overflow-wrap: anywhere;
}
.roster-name:hover { text-decoration: underline; }
.roster-meta { font-size: var(--text-sm); color: var(--text-secondary); margin-top: 4px; }
.roster-what { font-size: var(--text-md); font-weight: 700; color: var(--text-primary); }
.roster-what.is-warn { color: var(--state-warn-text); }
.roster-what.is-live { color: var(--correct-text); }
/* LATE, and it is a different colour from warn on purpose. Amber is "this
   wants doing"; red is "we told a family three days and it has been longer".
   A queue where the overdue row looks like the rest cannot tell you which one
   to do first, which is the only thing a queue is for. */
.roster-what.is-late { color: var(--state-bad-text); font-weight: 800; }
/* "Nothing" is the good state and must not read as a fault or as a gap — it is
   quiet, and it is still a word rather than an empty cell. */
.roster-what.is-none { color: var(--text-muted); font-weight: 400; }
.roster-since { font-size: 12px; color: var(--text-secondary); margin-top: 3px; }
.roster-todo, .roster-sat { font-size: 13px; color: var(--brown-ink); }
.roster-actions { display: flex; gap: 8px; justify-content: flex-end; align-items: flex-start; }
.roster-actions .row-menu > summary { padding: 7px 11px; }
/* An <a> in a menu of <button>s — mailto: is a link, and making it a button
   that fakes a navigation would be worse than matching the padding by hand. */
.menu-item[href] { display: block; text-decoration: none; }

/* The two data-quality notes, under the roster rather than over it. Both are
   about NAMES, not about work: nothing is waiting, something is simply not
   known. As panels above the table they were two boxes about three students
   standing over a list of six — outweighing the thing they annotate. */
.roster-note {
  font-size: var(--text-sm); line-height: 1.7; color: var(--text-secondary);
  padding: 10px 18px 0;
}
.roster-note strong { color: var(--brown-ink); }
.roster-note a { color: var(--brown-ink); font-weight: 700; }



/* Now used by exactly one thing: the Horizon week summary, which is a genuine
   warning box — a week whose reports cannot go out. The two Students panels
   and the Tests list's "Waiting on you" strip that also used it have retired
   into the rows and the .roster-note lines above; a footnote about three
   names does not want a gold border, and a blocked week does.
   Its .needs-you-row/.needs-you-what children went with them. */
.needs-you {
  background: #FFF8E8; border: 1px solid var(--flag-gold); border-radius: var(--radius-md);
  padding: 12px 16px; margin-top: 14px;
}
.needs-you > strong { font-size: 13px; display: block; margin-bottom: 6px; }

/* The row's overflow menu. Right-aligned because it sits at the right edge —
   a left-aligned panel there would open off the card. */
.row-menu > summary { list-style: none; padding: 0 10px; font-size: var(--text-xl); }
.row-menu > summary::-webkit-details-marker { display: none; }
.menu-panel-right { right: 0; left: auto; }
/* THE ROSTER'S ROW MENU OPENS RIGHTWARDS, because its button is on the LEFT
   of a wide card. Right-aligned, a 320px panel hung off the card's left edge
   and under the sidebar, which drew over it: every label was hidden and only
   the descriptions showed. The controls were all there and none of them could
   be read. Found by opening one. */
.roster-actions .menu-panel { left: 0; right: auto; }
.menu-item-danger { color: #B02E1D; }

/* ── The journey: what your student sees, in order ────────────────────────
   The settings above these are RULES — who, when, how it's watched. These are
   the student's actual screens in sequence, because that is how you check
   them. Grouped by setting type, step 2 was invisible and steps 3 and 5 sat
   in different halves of one card. */
.journey-band { margin-top: 30px; padding: 0 4px; }
.journey-band h3 { font-size: var(--text-lg); font-weight: 800; margin-bottom: 2px; }
.journey-band .hint-text { margin-top: 0; }
.journey-step h3 { display: flex; align-items: center; gap: 10px; }
.journey-num {
  flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%;
  background: var(--brown-ink); color: var(--warm-white);
  font-size: 12px; font-weight: 700; display: inline-flex;
  align-items: center; justify-content: center;
}

/* Step 1 — the access doorway, drawn flat. Nothing to set, so it is shown
   small: it is here to complete the sequence, not to be worked on. */
.access-preview { display: flex; justify-content: center; margin-top: 12px; }
.access-preview-card {
  background: var(--warm-white); border: 1px solid var(--border-card);
  border-radius: 16px; padding: 22px 24px; max-width: 380px; width: 100%;
  font-family: var(--font-body);
}
.access-preview-title { font-family: var(--font-display); font-weight: 600; font-size: 17px; }
.access-preview-sub { font-weight: 600; font-size: 14px; margin-top: 4px; }
.access-preview-window { color: var(--text-secondary); font-size: 13px; margin-top: 6px; }
.access-preview-actions { display: flex; justify-content: space-between; align-items: center; margin-top: 18px; }
.access-preview-ghost {
  border: 1.5px solid var(--border-card); border-radius: 50px;
  padding: 7px 16px; font-size: 13px; color: var(--brown-ink);
}
.access-preview-go {
  background: var(--deep-ink); color: var(--warm-white);
  border-radius: 50px; padding: 8px 22px; font-size: 14px; font-weight: 600;
}

/* Step 2 — the automatic pages, each in its own frame. */
.auto-pages { display: flex; flex-direction: column; gap: 14px; margin-top: 12px; }
.auto-page-label {
  font-size: var(--text-xs); font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--text-muted); margin-bottom: 5px;
}
.auto-page-frame {
  width: 100%; border: 1.5px solid var(--border); border-radius: var(--radius-md);
  background: #fff; display: block; min-height: 120px;
}

/* ── The question rail ────────────────────────────────────────────────────
   The whole paper above the editor. Filled = ready, amber = a student
   couldn't answer it, hollow = works but has no skill tag. */
.q-rail { display: flex; flex-wrap: wrap; gap: 5px; flex: 1; justify-content: center; }
.q-dot {
  width: 26px; height: 26px; border-radius: 50%; font-size: var(--text-xs); font-weight: 700;
  border: 1.5px solid transparent; cursor: pointer; font-family: inherit;
  display: inline-flex; align-items: center; justify-content: center;
}
.q-dot-ready { background: var(--sand); color: var(--brown-ink); }
.q-dot-broken { background: #FBE6E1; color: #B02E1D; border-color: #E7B6AC; }
.q-dot-untagged { background: transparent; color: var(--text-muted); border: 1.5px dashed var(--warm-tan); }
.q-dot-here { outline: 2px solid var(--deep-ink); outline-offset: 1px; }
.q-dot:hover { border-color: var(--brown-ink); }

/* What this question still needs, while you're writing it — rather than at
   activation, which is the expensive moment to find out. */
.editor-readiness {
  border-radius: var(--radius-sm); padding: 10px 14px; margin-bottom: 12px; font-size: var(--text-md);
}
.editor-readiness.is-blocked { background: #FBE6E1; border: 1px solid #E7B6AC; }
.editor-readiness.is-warned { background: #FFF8E8; border: 1px solid var(--flag-gold); }
.editor-readiness strong { display: block; margin-bottom: 2px; }
.editor-readiness ul { margin: 4px 0 0 18px; }

/* Skill tags you already use, one click each — clicking can't misspell. */
.tag-chips { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
.tag-chip {
  border: 1.5px solid var(--border); background: var(--parchment); color: var(--brown-ink);
  border-radius: 50px; padding: 4px 11px; font-size: var(--text-sm); font-family: inherit; cursor: pointer;
}
.tag-chip:hover { border-color: var(--brown-ink); }
.tag-chip.active { background: var(--deep-ink); color: var(--warm-white); border-color: var(--deep-ink); }

/* A tag this paper already uses, versus one borrowed from elsewhere in your
   library. The distinction is the whole point of the ordering, so it should
   be visible without reading the tooltip. */
.tag-chip-paper { border-color: var(--warm-tan); background: var(--sand); }
.tag-chip-more { border-style: dashed; color: var(--text-muted); }
/* The editor's overflow is a word now, not a glyph — it sits beside worded
   buttons, and at 26px it wrapped under Save and opened over the rail. */
#editor-menu > summary { list-style: none; }
#editor-menu > summary::-webkit-details-marker { display: none; }

/* ── Marking queue ────────────────────────────────────────────────────────
   Prompt and answer on the left, the mark on the right, and nothing else on
   screen. You are here for twenty essays in a row. */
.marking-grid { display: grid; grid-template-columns: 1fr 320px; gap: 18px; align-items: start; }
@media (max-width: 900px) { .marking-grid { grid-template-columns: 1fr; } }
.marking-prompt {
  background: var(--parchment); border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 12px 14px; margin-top: 10px; font-family: var(--exam-font); font-size: 14px;
}
/* Their words, at reading size and measure — marking prose in a cramped
   preview box is how you miss what it actually says. */
.marking-answer {
  margin-top: 14px; padding: 16px 18px; background: var(--warm-white);
  border: 1px solid var(--border-card); border-radius: var(--radius-md);
  font-family: var(--exam-font); font-size: var(--text-lg); line-height: 1.65;
  max-width: 70ch; white-space: pre-wrap;
}
.marking-answer p { margin-bottom: 10px; }
.marking-answer ul, .marking-answer ol { margin: 0 0 10px 22px; }
.marking-side { position: sticky; top: 16px; }
.marking-side textarea { resize: vertical; font-family: inherit; }

/* The student's portal, mirrored on their profile. */
.mirror-group { margin-top: 12px; }
.mirror-group h4 {
  font-size: var(--text-xs); font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-muted); margin-bottom: 4px;
}
.mirror-row {
  display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap;
  padding: 7px 0; border-bottom: 1px solid var(--border); font-size: 14px;
}
.mirror-row:last-child { border-bottom: none; }
.chip-go { border-style: solid; background: var(--deep-ink); color: var(--warm-white); border-color: var(--deep-ink); }

/* The student-profile header can hold five actions; let it breathe. */
#view-student-detail .view-header-actions { gap: 8px; }
#portal-mirror-card { margin-top: 18px; }

/* ── Question library ─────────────────────────────────────────────────────
   A row you can open. Closed it's a line; open it is the whole question with
   its options and the correct one marked — the bank stored those all along
   and never showed them, so checking an answer meant inserting the question
   into a real test and opening it. */
.bank-row { flex-wrap: wrap; }
.bank-preview {
  flex: 1 1 100%; margin-top: 12px; padding: 14px 16px;
  background: var(--parchment); border: 1px solid var(--border);
  border-radius: var(--radius-sm); font-family: var(--exam-font);
}
.bank-used-in { color: var(--text-muted); font-size: var(--text-sm); }
#bank-tabs { margin-bottom: 10px; }

/* A banked reading set, opened: the extracts, then every question with its
   answers. The decision here is "is this the right PAPER", so the whole
   thing has to be readable in place. */
.set-extract-label, .set-question-label {
  font-size: var(--text-xs); font-weight: 700; letter-spacing: 0.07em; text-transform: uppercase;
  color: var(--text-muted); margin: 14px 0 4px;
}
.set-extract {
  background: var(--warm-white); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 12px 14px;
  font-size: 14px; line-height: 1.6; max-height: 240px; overflow-y: auto;
}
.set-question-label { border-top: 1px solid var(--border); padding-top: 12px; }

/* Restore: both sides of the trade, side by side. "Are you sure?" is not a
   confirmation when the thing being confirmed is invisible. */
.restore-table { font-size: var(--text-md); }
.restore-table td, .restore-table th { padding: 7px 12px; }

/* Who awarded the mark. Unanswerable while everyone shared one login, and it
   is the question a parent actually asks. */
.marked-by-note { font-size: 12px; color: var(--text-muted); white-space: nowrap; }

/* ── Staff accounts ──────────────────────────────────────────────────────── */
/* A switched-off account still has to be readable — its name stays attached to
   everything that person wrote or marked — so it dims rather than disappears. */
.staff-row-off { opacity: 0.55; }
.staff-row-off .card-row-title { text-decoration: line-through; }

/* The "insert the standard instructions" row above the start-page preview.
   Button and its caveat on one line, so the sentence about square brackets is
   read as part of pressing it rather than as unrelated hint text below. */
.startpage-template-row {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin: 2px 0 10px;
}
.startpage-template-row .hint-text { margin: 0; flex: 1 1 260px; }

/* ── Report presets ────────────────────────────────────────────────────────
   Who the document is for, asked before what goes in it. Full-width rows
   rather than a segmented control: each one carries a sentence explaining the
   trade, and that sentence is the reason somebody picks correctly. */
.report-presets { display: flex; flex-direction: column; gap: 6px; margin: 10px 0 4px; }
.report-preset {
  display: block; width: 100%; text-align: left; cursor: pointer;
  background: #fff; border: 1.5px solid var(--border); border-radius: var(--radius-sm);
  padding: 9px 12px; font-family: inherit;
}
.report-preset:hover { border-color: var(--deep-ink); }
.report-preset.active { border-color: var(--deep-ink); border-width: 2px; background: var(--parchment); }
.report-preset-label { display: block; font-weight: 700; font-size: 14px; color: var(--text-primary); }
.report-preset-hint { display: block; font-size: var(--text-sm); color: var(--text-muted); margin-top: 1px; }
.report-preset-custom {
  margin-top: 2px; font-size: var(--text-sm); font-weight: 700; color: var(--text-muted);
}
.report-customise > summary {
  cursor: pointer; font-size: var(--text-md); font-weight: 600; color: var(--text-primary);
  padding: 8px 0 2px;
}
.report-field-section { margin-top: 10px; }
.report-field-section:first-child { margin-top: 4px; }
.report-field-section h4 {
  font-size: var(--text-xs); font-weight: 800; letter-spacing: 0.07em; text-transform: uppercase;
  color: var(--text-muted); margin-bottom: 4px;
}
/* Each field says what it MEANS in the document. A label alone makes a tutor
   guess whether "skills" is a table or a sentence, and guessing is how a
   cohort figure ends up in a report for a family. */
.report-check-row { padding: 5px 0; }
.report-check-name { display: block; font-weight: 600; }
.report-check-why {
  display: block; margin-top: 2px; font-size: var(--text-sm); line-height: 1.5;
  color: var(--text-muted);
}
/* Locked: still visible, still saying what it is. A control that vanishes
   reads as a bug; one that explains itself reads as a rule. */
.report-check-row.is-locked { cursor: default; }
.report-check-row.is-locked .report-check-name { color: var(--text-muted); }
.report-check-lock {
  font-size: var(--text-xs); font-weight: 700; color: var(--brown-ink);
  background: var(--state-warn-bg); border: 1px solid var(--flag-gold);
  border-radius: 999px; padding: 1px 8px; margin-left: 7px; white-space: nowrap;
}
.report-field-count { float: right; font-weight: 400; color: var(--text-muted); }
/* What the lock has taken off the chosen preset, said before the download
   rather than discovered in the file. */
.report-preset-trim {
  margin-top: 6px; font-size: var(--text-sm); line-height: 1.5;
  color: var(--brown-ink); background: var(--state-warn-bg);
  border: 1px solid var(--flag-gold); border-radius: var(--radius-sm);
  padding: 6px 10px;
}

/* ── Horizon ───────────────────────────────────────────────────────────────
   Almost nothing new: the programme, week and student lists are .card-row and
   .category-badge like every other list in this app, so they inherit its
   spacing, hover and mobile stacking for free. What IS here is the sheet
   importer, which has no equivalent anywhere else — a column-mapping form and
   a preview of the rows as they were actually read. */
.ext-field-row { display: flex; gap: 12px; flex-wrap: wrap; margin: 10px 0; }
.ext-field-row .field { flex: 1 1 170px; margin: 0; }
.external-import textarea.text-input { font-family: ui-monospace, monospace; font-size: var(--text-sm); }
/* The preview is the whole point of parsing before committing, so it scrolls
   rather than wraps: a sheet with eleven columns has to still LOOK like the
   sheet, or checking the mapping against it is guesswork. */
.ext-preview { overflow-x: auto; margin: 12px 0; border: 1px solid var(--border-card); border-radius: var(--radius-sm); }
.ext-preview table { border-collapse: collapse; font-size: var(--text-sm); width: 100%; }
.ext-preview th, .ext-preview td {
  padding: 5px 10px; text-align: left; white-space: nowrap;
  border-bottom: 1px solid var(--border-card);
}
.ext-preview th { background: var(--sand); font-weight: 700; color: var(--brown-ink); }
.ext-preview tr:last-child td { border-bottom: none; }
/* A badge that is also a button — the missing-gender chip on a student row
   goes straight to the record that fixes it, rather than telling you off. */
button.category-badge { cursor: pointer; border: none; font-family: inherit; }
button.category-badge:hover { filter: brightness(0.95); }

/* The empty-questions state offers the two real routes as buttons. Prose
   naming a menu was the only pointer to the importer, and the biggest control
   on screen said "+ New question" — which for somebody holding a PDF of the
   paper is the slowest way in. */
.empty-state-actions {
  display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; margin-top: 14px;
}

/* A status line that says which kind of thing it is.
   "That file couldn't be opened" used to render in the same grey hint text as
   "Reading Math Test 9.pdf…" — the one line you must not miss, wearing the
   format of the line you can ignore. Progress stays quiet; a failure gets a
   block of its own, in the danger red the row actions already use. */
.form-status { margin: 12px 0 0; }
.form-status:empty { display: none; }
.form-status.is-working {
  font-size: 13px; color: var(--text-secondary);
}
.form-status.is-error {
  display: flex; gap: 9px; align-items: flex-start;
  background: #FBF0EE; border: 1px solid #E8C4BC; border-left: 4px solid #C0392B;
  border-radius: var(--radius-sm); padding: 11px 14px;
  font-size: 14px; font-weight: 600; line-height: 1.45; color: #8C2A1E;
}
/* Drawn rather than an emoji or an icon font: the brand's personality comes
   from SVG doodles, and neither of the other two would match at this size. */
.form-status.is-error::before {
  content: '!';
  flex: 0 0 auto; width: 17px; height: 17px; margin-top: 1px;
  border-radius: 50%; background: #C0392B; color: #FBF0EE;
  font-size: 12px; font-weight: 800; line-height: 17px; text-align: center;
}

/* An imported stem keeps the paper's paragraphs in the review list. Blocks
   rather than <p> so the existing .import-q-stem spacing still applies, with
   a gap between paragraphs and none inside one. */
.import-q-para { display: block; }
.import-q-para + .import-q-para { margin-top: 0.6em; }

/* ── Notes the importer left on a question ─────────────────────────────────
   A third kind of message, deliberately not folded into .editor-readiness:
   readiness asks whether a student could sit this question, these ask whether
   it matches the paper it came from. A question can be perfectly ready and
   still be missing the diagram its whole point rests on.

   Sand rather than the attention gold — this is work to do, not something
   wrong, and it sits directly under a box that may already be gold. */
.import-notes {
  background: var(--sand); border: 1px solid var(--border-card);
  border-radius: var(--radius-sm); padding: 10px 14px; margin-bottom: 12px;
  font-size: var(--text-md); color: var(--text-primary);
}
.import-notes-head {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
}
.import-notes ul { margin: 6px 0 0 18px; }
.import-notes li { margin: 2px 0; }
.category-badge.badge-import { background: #EFE7D6; color: var(--brown-ink); cursor: help; }

/* ── The open link card (hub Access tab) ──────────────────────────────────
   One test — the deliberately-free one — carries a public /try/<slug> door;
   this card is where it is set, shown and removed. */
.open-link-card {
  margin-bottom: 16px; padding: 14px 16px;
  background: var(--sand-soft);
  border: 1px solid var(--border); border-radius: 12px;
}
.open-link-card h3 { font-size: 15px; margin-bottom: 4px; }
.open-link-row { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
.open-link-row input {
  flex: 1 1 220px; padding: 8px 10px; font-size: 14px;
  border: 1.5px solid var(--border-card); border-radius: 8px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.open-link-url {
  margin-top: 8px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px; color: var(--text-secondary); word-break: break-all;
}

/* ── Free sittings ─────────────────────────────────────────────────────────
   The free tier's own screen: who came through the open doors, on which link.
   A list screen, so it lives here beside the other lists. */
.free-channels { display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.free-channel {
  background: var(--warm-white); border: 1px solid var(--border-card);
  border-radius: var(--radius-md); padding: 12px 14px;
}
.free-channel-name {
  font-size: var(--text-sm); font-weight: 700; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--text-secondary);
}
/* Started and finished side by side: a channel that sends fifty people who
   all quit at the instructions is not working, however good the first
   figure looks. */
.free-channel-n {
  font-size: 28px; font-weight: 800; font-variant-numeric: tabular-nums;
  line-height: 1.1; margin-top: 2px;
}
.free-channel-sub { font-size: var(--text-sm); color: var(--text-muted); }

.free-search {
  width: 100%; max-width: 340px; box-sizing: border-box;
  padding: 9px 12px; font: inherit; font-size: var(--text-base);
  border: 1.5px solid var(--border-card); border-radius: 8px; background: #fff;
}
.free-search:focus { outline: 2px solid var(--brown-ink); outline-offset: 1px; }

.free-person-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.free-person-head strong { font-size: var(--text-lg); }
/* The copy button sits at the far end, away from the name, so a mis-click
   cannot send somebody else's page link. */
.free-person-head .free-copy { margin-left: auto; }
.free-sittings-list { list-style: none; margin: 8px 0 0; display: grid; gap: 4px; }
.free-sittings-list li { display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap; }
.free-sittings-list a { color: var(--text-primary); }
