/* ── Moving a figure inside a question ─────────────────────────────────────
   An imported diagram lands where the paper printed it, which is right most
   of the time and wrong often enough to need an answer. Selecting an image in
   a contenteditable and cutting it is fiddly and easy to get wrong; these are
   the same up/down arrows the question list already uses, plus three sizes.
   Deliberately not drag-and-drop: dragging inside contenteditable drops the
   image into the middle of a word as often as between two paragraphs. */
.rte-editor img { max-width: 100%; height: auto; display: block; margin: 8px 0; cursor: pointer; }
.rte-editor img.selected { outline: 2.5px solid var(--brown-ink); outline-offset: 2px; }
/* A drag has to say it will be caught. Without this the editor looked exactly
   the same whether it would accept the picture or navigate away to it. */
.rte-editor.is-dropping { outline: 2px dashed var(--brown-ink); outline-offset: -2px; background: var(--sand-soft); }
/* The same affordance for a whole file field, so a PDF can be dropped on the
   block rather than aimed at an input a few pixels tall. */
.file-drop { border: 1px dashed var(--border-card); border-radius: var(--radius-md); padding: 12px; }
.file-drop.is-dropping { border-color: var(--brown-ink); border-style: solid; background: var(--sand-soft); }
.file-drop-hint { margin: 6px 0 0; }
.rte-editor img.img-small { max-width: 40%; }
.rte-editor img.img-medium { max-width: 70%; }
.img-tools {
  position: absolute; z-index: 200; display: flex; align-items: center; gap: 4px;
  background: var(--warm-white); border: 1px solid var(--border-card);
  border-radius: var(--radius-sm); padding: 5px 6px;
  box-shadow: 0 4px 14px rgba(46, 35, 24, 0.16);
}
.img-tools .btn-icon { padding: 4px 9px; font-size: 12px; }
/* Alt text, beside Size rather than in a dialog before you can carry on —
   see insertUploadedImage. Narrow on purpose: it is a phrase, not a sentence,
   and a wide field invites one. */
.img-tools .img-alt-input {
  width: 150px; margin: 0; padding: 3px 7px; font: inherit; font-size: 12px;
  border: 1px solid var(--border-card); border-radius: var(--radius-sm);
  background: #fff; color: var(--text-primary);
}
.img-tools .img-alt-input:focus { border-color: var(--brown-ink); outline: none; }
.img-tools-label { font-size: var(--text-xs); font-weight: 700; color: var(--text-muted); padding: 0 3px; }
.img-tools .btn-icon.active { background: var(--deep-ink); color: var(--warm-white); border-color: var(--deep-ink); }

/* A plain checkbox row in the settings cards — same shape as the report
   field checkboxes, which is where the pattern already reads well. */
.check-row { display: flex; align-items: flex-start; gap: 9px; font-size: 14px; color: var(--text-primary); cursor: pointer; }
.check-row input { margin-top: 3px; }

/* "they saw this 3rd" beside the written number, on a shuffled sitting. Quiet
   — the number that matters everywhere else is the one it sits next to. */
.review-q-shown {
  margin-left: 8px; font-size: 11.5px; font-weight: 600; color: var(--text-muted);
  background: var(--sand); border-radius: var(--radius-sm); padding: 2px 7px; cursor: help;
}

/* "This page is running an older version." Bottom of the screen, above
   everything, and dismissable — it is information, not a modal, and somebody
   mid-edit must be able to finish the sentence they are typing. */
.update-bar {
  position: fixed; left: 50%; transform: translateX(-50%); bottom: 18px; z-index: 400;
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  max-width: min(720px, calc(100vw - 32px));
  background: var(--deep-ink); color: var(--parchment);
  border-radius: var(--radius-md); padding: 12px 16px; font-size: var(--text-md);
  box-shadow: 0 8px 26px rgba(46, 35, 24, 0.28);
}
.update-bar strong { color: #fff; }
.update-bar .btn-icon { border-color: rgba(250, 246, 238, 0.4); color: var(--parchment); }
.update-bar .btn-icon:hover { background: rgba(250, 246, 238, 0.12); border-color: var(--parchment); }

/* The three overlays that ASK something and wait for an answer must sit above
   the ones that are merely a screen in a box.
   
   Every overlay shared z-index: 100, so stacking fell to DOM order — and the
   import dialog is declared after the confirm dialog. Ticking a held-back
   question and pressing Import opened the confirmation BEHIND the import
   dialog: nothing appeared to happen, and the app sat waiting for a click on a
   button that could not be seen or reached. Untick it, no confirmation needed,
   and it worked — which made it look like the question was being refused. */
.login-overlay.overlay-asking { z-index: 300; }

/* The page as printed, beside a question the importer flagged. Collapsed by
   default: it is a reference for the questions that need one, not something to
   scroll past on every row. */
/* An answer option that is a picture. Bounded, or one diagram makes the review
   list unscrollable. */
.import-option-img {
  display: block; max-width: 260px; max-height: 150px; width: auto; height: auto;
  border: 1px solid var(--border); border-radius: 3px; background: #fff;
}

/* The set a paper belongs to, shown inside the paper. Quiet — it is context,
   not a control, and it sits directly under a heading it must not compete
   with. */
.set-strip {
  font-size: 0.82rem;
  color: var(--text-muted);
  margin: 2px 0 0;
  display: flex; flex-wrap: wrap; gap: 6px; align-items: baseline;
}
.set-strip a { color: var(--brown-ink); text-decoration: none; }
.set-strip a:hover { text-decoration: underline; }
.set-strip strong { color: var(--deep-ink); font-weight: 600; }
.set-strip-label { text-transform: uppercase; letter-spacing: 0.04em; font-size: 0.72rem; }
.set-strip-sep { opacity: 0.5; }

/* A view that has been revealed but not yet filled. Held invisible rather than
   display:none — it keeps its layout, so anything measuring during render
   still reads real sizes, and only the stale content is kept off screen.
   Cleared by the router when the handler finishes, with a timer behind it so a
   thrown handler can never leave a screen blank. */
.view.is-settling { visibility: hidden; }
/* The still copy of the screen you were on, shown while the next one is built.
   It is a picture: its buttons are not wired to anything, so clicks must not
   land on them and look ignored. */
.view-frozen { pointer-events: none; }

/* The header row a sheet has to start with. It is one long line by nature —
   forty questions is forty columns — so it SCROLLS rather than running out of
   its card, which is what it did: the text carried on past the border and off
   the side of the page, and the columns past Q30 could not be read at all.
   white-space: pre keeps it one line, because a wrapped header row read as
   several rows and invited somebody to paste it as several. */
/* One paper's tab, in the whole-week list. Separated by a rule rather than a
   card each: four cards inside a card reads as four things to decide between,
   and these are four things to copy in turn. */
.week-sheet-paper { padding: 12px 0; border-top: 1px solid var(--hairline); }
.week-sheet-paper:first-of-type { border-top: none; padding-top: 4px; }
.week-sheet-paper .roster-header-row { margin-top: 6px; }

.roster-header-row {
  display: block;
  max-width: 100%;
  overflow-x: auto;
  white-space: pre;
  padding: 10px 12px;
  background: var(--parchment);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-sm);
  /* A size and a stack, rather than the browser's default monospace at 13px:
     this is text to be recognised and copied, not read, so it wants to be
     legible and quiet rather than to look like code. */
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: var(--text-sm);
  line-height: 1.7;
  color: var(--text-primary);
}

/* The count inside a state chip. Tabular figures so 9 and 64 do not shuffle
   the chip's width as the list is worked through — a control that resizes
   under you while you use it is one you stop trusting to stay where it was. */
.filter-chip .chip-count { font-weight: 700;
  opacity: 0.75; margin-left: 2px;
}
.filter-chip.active .chip-count { opacity: 0.9; }

/* ── The first reading, in the review queue ──────────────────────────────────
   Above the score box, because it is what is being confirmed. Quiet: this is
   a colleague's judgement being read, not a warning. */
.first-mark {
  background: var(--sand-soft); border: 1px solid var(--hairline);
  border-radius: var(--radius-sm); padding: 10px 12px; margin-bottom: 14px;
}
.first-mark-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.first-mark-score {
  font-family: var(--font-display); font-weight: 700; font-size: 17px;
  color: var(--deep-ink);
}
.first-mark-comment {
  margin: 6px 0 0; font-size: var(--text-md); line-height: 1.55; color: var(--text-secondary);
}

/* The line that says how much of the paper is sound, before the list of what
   is not. Reads as a finding rather than a caption, so it earns being read
   first — which is the whole point of putting it there. */
.item-analysis-summary {
  margin: 10px 0 14px; font-size: 14px; line-height: 1.55;
  color: var(--text-primary);
  border-left: 3px solid var(--border-card); padding-left: 12px;
}

/* ── The roster, grouped by what may be done to a row ────────────────────────
   A student sitting the paper right now had the same three buttons as everyone
   else, and two of them end the sitting she has open. The group heading is the
   warning; the row carries the reason. */
.roster-group {
  margin: 22px 0 2px; font-size: var(--text-lg); font-weight: 700;
  display: flex; align-items: baseline; gap: 8px;
}
.roster-group:first-child { margin-top: 4px; }
.roster-group .chip-count {
  font-size: var(--text-sm); font-weight: 700; color: var(--text-muted);
}
.roster-group-note { margin: 0 0 10px; }
/* Marked, not alarming. Nothing is wrong — somebody is working. */
.roster-row-live { border-left: 3px solid var(--partial-border); }
.roster-live-note {
  margin: 6px 0 0; font-size: var(--text-sm); line-height: 1.5; color: var(--text-secondary);
  max-width: 46em;
}

/* One missing file, said once. Sits above the warning list because it explains
   most of what is in it. */
.import-key-note {
  margin: 0 0 12px; padding: 10px 12px; font-size: var(--text-md); line-height: 1.55;
  background: var(--sand-soft); border: 1px solid var(--border-card);
  border-radius: var(--radius-sm); color: var(--text-primary);
}

/* A skill with too few marks behind it to mean anything.
   THE BAR GOES, it is not merely dimmed. A bar is a claim about a PROPORTION
   and there is no proportion in one mark: a full-width bar at 0% for Space
   (0/1) says this student cannot do Space, on the evidence of one question.
   Dimming it says the same thing more quietly, which is not the same as not
   saying it. The count stays, because the count is the honest part — the same
   rule the Horizon band reports follow when they refuse to band a cohort of
   five. */
.review-skill-thin .review-skill-bar { visibility: hidden; }
.review-skill-note {
  display: block; font-size: 11.5px; line-height: 1.4;
  color: var(--text-muted); font-weight: 400;
}
/* Said once for the whole panel, so it sits beside the heading rather than
   under every bar. */
.review-skills-note {
  margin-left: 8px; font-size: 11.5px; font-weight: 400;
  text-transform: none; letter-spacing: 0; color: var(--text-muted);
}

/* ── A programme's weeks, read down ────────────────────────────────────────
   Every question asked of a ten-week programme is a down-question: which week
   is nobody sitting, which one is stuck in marking. Same lesson as the results
   table — those answers are unreadable when each row lays itself out.

   Two columns, because two are what the platform records. "Have you looked at
   this week's results" and "have the Plus reports gone" are real jobs and are
   written down nowhere, and a column of zeros meaning "not tracked" reads as a
   column of zeros meaning "nothing done". */
#horizon-weeks { display: grid; grid-template-columns: auto minmax(200px, 1fr) 130px 130px auto; gap: 8px; }
#horizon-weeks > .pw-head,
#horizon-weeks > .pw-row,
#horizon-weeks > .pw-note { grid-column: 1 / -1; display: grid; grid-template-columns: subgrid; align-items: center; }
.pw-head {
  padding: 0 16px 2px; font-size: 11.5px; font-weight: 700; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--text-secondary);
}
.pw-cell { display: grid; gap: 4px; }
.pw-bar { height: 7px; border-radius: 999px; background: var(--sand-soft); overflow: hidden; }
.pw-bar i { display: block; height: 100%; background: var(--correct-border); }
.pw-bar i.is-part { background: var(--warm-tan); }
.pw-n { font-size: var(--text-sm); color: var(--text-secondary); }
.pw-n b { color: var(--text-primary); font-weight: 700; }
#horizon-weeks > .pw-note {
  display: block; margin: 6px 0 0; padding-top: 9px;
  border-top: 1px dashed var(--hairline);
  font-size: var(--text-sm); line-height: 1.55; color: var(--text-secondary);
}
@media (max-width: 900px) {
  #horizon-weeks { display: flex; flex-direction: column; gap: 8px; }
  .pw-head { display: none; }
  #horizon-weeks > .pw-row { display: flex; flex-wrap: wrap; }
  .pw-cell { flex: 1 1 130px; }
}

/* A short list of guarantees, at the point they matter. Reads as prose rather
   than as a feature list — each line is something a tutor would otherwise have
   to take on trust. */
.hint-list {
  margin: 10px 0 0; padding-left: 18px;
  font-size: 13px; line-height: 1.6; color: var(--text-secondary);
}
.hint-list li { margin-bottom: 5px; }
.hint-list strong { color: var(--text-primary); }

/* ── The Plus tag ──────────────────────────────────────────────────────────
   A billing marker sitting next to a child's results, so it has to read as a
   note rather than as a grade. Lilac: it is the one accent in the palette that
   carries no verdict — the marking colours are green/gold/red and borrowing
   any of them would make "this family paid" look like a judgement of the
   student. Untagged shows as an outline with a question mark, so the control
   is visible before it is used rather than appearing out of nowhere. */
.plus-toggle {
  cursor: pointer; font-weight: 700;
  background: none; color: var(--text-muted);
  border: 1px dashed var(--border-card);
}
.plus-toggle:hover { border-color: var(--lilac); color: var(--text-primary); }
.plus-toggle.is-on {
  background: var(--partial-bg); border: 1px solid var(--partial-border);
  color: #3A4A73; border-style: solid;
}
.plus-toggle:disabled { opacity: 0.5; cursor: default; }
.plus-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.plus-head .btn-primary { margin-left: auto; }
/* A tagged row, marked quietly on the week list. Enough to pick out at a
   glance, not enough to look like a score. */
.card-row.is-plus { box-shadow: inset 3px 0 0 var(--lilac); }
/* Blocked leads, and looks like the job it is. */
.card-row.plus-blocked { background: var(--state-attention-bg); }
/* Ready, and short a paper. Marked distinctly from "nothing to send" because
   the two need different actions: one waits, the other is a judgement call. */
.card-row.plus-partial { background: var(--state-attention-bg); box-shadow: inset 3px 0 0 var(--flag-gold); }

/* ══════════════════════════════════════════════════════════════════════════
   IMPORT REVIEW — the parse on the left, the page on the right
   ══════════════════════════════════════════════════════════════════════════
   The screen's whole job is a comparison: what the parser built, against what
   the paper actually prints. It used to be a narrow column with the page
   folded into a closed <details> underneath each question — so the comparison
   was two clicks and a scroll away, and a dropped line (which leaves every
   count intact and every question looking whole) could not be caught at all.

   Ported from the design's import-review.html, which was refitted against our
   real ids. Two of its controls are deliberately not here: the paper dropdown
   (we already know the paper — the modal is opened from inside it) and the
   in-review key upload (needs an endpoint that takes a key against an existing
   draft; until that exists the button says it re-reads the file). */
.imp-card {
  max-width: 1360px; width: 100%; padding: 0; overflow: hidden;
  display: flex; flex-direction: column; max-height: calc(100vh - 56px);
  background: var(--parchment);
}
.imp-card-head {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 16px 24px; background: var(--warm-white);
  border-bottom: 1px solid var(--border-card); flex: 0 0 auto;
}
.imp-card-head h1 { font-size: 19px; letter-spacing: -0.01em; margin: 0; }
.imp-card-head .hint-text { font-size: 13px; color: var(--text-secondary); }
.imp-card-head .btn-ghost { margin-left: auto; }
.imp-card-body { padding: 20px 24px; overflow-y: auto; flex: 1 1 auto; }
.imp-card-foot {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 14px 24px; background: var(--warm-white);
  border-top: 1px solid var(--border-card); flex: 0 0 auto;
}
.imp-card-foot .imp-foot-note { flex: 1 1 100%; font-size: 13px; color: var(--text-secondary); }

/* The paper-level verdict, recounted by the work each group implies. Four
   numbers that can be compared at a glance, where there was one run-on
   sentence that had to be read to the end before any of it meant anything. */
.imp-verdict {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  background: var(--warm-white); border: 1px solid var(--border-card);
  border-radius: var(--radius-lg); overflow: hidden; margin-bottom: 16px;
}
.imp-count { padding: 15px 20px; box-shadow: inset -1px 0 0 var(--hairline); }
.imp-count:last-child { box-shadow: none; }
.imp-count-n {
  display: block; font-weight: 700; font-size: 27px;
  line-height: 1.1;
}
.imp-count-l { display: block; font-size: 13px; color: var(--text-secondary); margin-top: 3px; }
.imp-count.is-blocked { background: var(--sand-soft); box-shadow: inset 3px 0 0 var(--deep-ink), inset -1px 0 0 var(--hairline); }
.imp-count.is-blocked .imp-count-n { color: var(--brown-ink); }
.imp-count.is-clean .imp-count-n { color: var(--correct-text); }

/* The missing key, stated once, with the way out. Every tick-only chip
   downstream is a consequence of this one line. */
.imp-keybar {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 14px 18px; background: var(--state-warn-bg); border: 1px solid var(--flag-gold);
  border-radius: var(--radius-md); margin-bottom: 16px;
  font-size: var(--text-md); line-height: 1.55; color: var(--brown-ink);
}
.imp-keybar strong { color: var(--deep-ink); }
.imp-keybar > span:first-child { flex: 1 1 100%; }

/* Paper-level, and the reason it has to be read: these describe things no
   per-question flag can catch. */
#import-warnings:not(:empty) {
  background: var(--warm-white); border: 1px solid var(--border-card);
  border-radius: var(--radius-md); padding: 14px 18px; margin-bottom: 16px;
}
.imp-warn-title {
  font-size: var(--text-xs); font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--text-secondary); margin: 0 0 8px;
}
#import-warnings .imp-warn {
  font-size: var(--text-md); line-height: 1.6; color: var(--text-primary); margin: 0;
  padding: 7px 0 7px 16px; border-bottom: 1px solid var(--hairline); position: relative;
}
#import-warnings .imp-warn:last-child { border-bottom: 0; padding-bottom: 0; }
#import-warnings .imp-warn::before { content: "•"; position: absolute; left: 2px; color: var(--ink-faint); }

.imp-listbar {
  display: flex; justify-content: space-between; align-items: center;
  gap: 10px; flex-wrap: wrap; margin-top: 16px;
}
#import-summary { font-size: var(--text-md); color: var(--text-secondary); }
/* The list IS the screen. No 46vh cap of its own — the modal body scrolls. */
#import-list { margin-top: 14px; display: flex; flex-direction: column; gap: 14px; }

.import-q {
  background: var(--warm-white); border: 1px solid var(--border-card);
  border-radius: var(--radius-lg); overflow: hidden;
}
/* Two states, two weights. A question needing a tick is not in the same
   condition as one whose options came through as pictures. Ink, never red —
   a paper the parser could not read is not an error anybody made. */
.import-q.is-blocked { border-color: var(--warm-tan); box-shadow: inset 3px 0 0 var(--warm-tan); }
.import-q-head {
  display: grid; grid-template-columns: auto auto minmax(0, 1fr); gap: 12px;
  align-items: baseline; padding: 14px 18px;
  border-bottom: 1px solid var(--hairline); cursor: pointer;
}
.import-q-head > strong { font-weight: 700; font-size: 14px; color: var(--text-secondary); }
.import-include { width: 17px; height: 17px; accent-color: var(--deep-ink); align-self: center; }
.import-q-stem { font-size: 15.5px; line-height: 1.5; font-family: var(--exam-font); }

/* Parse on the left, page on the right. The comparison IS the review. */
.imp-body { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
.imp-body > * { min-width: 0; padding: 16px 18px; }
.imp-body > .imp-page-col { border-left: 1px solid var(--hairline); background: var(--sand-soft); }
.imp-page-label {
  font-size: var(--text-xs); font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-secondary); margin: 0 0 8px;
}
.imp-page-shot {
  display: block; max-width: 100%; height: auto;
  border: 1px solid var(--border-card); border-radius: 6px; background: #fff;
}
@media (max-width: 1080px) {
  .imp-body { grid-template-columns: minmax(0, 1fr); }
  .imp-body > .imp-page-col { border-left: 0; border-top: 1px solid var(--hairline); }
}

.import-options { display: flex; flex-direction: column; gap: 7px; }
.import-option {
  display: grid; grid-template-columns: auto auto minmax(0, 1fr); gap: 10px;
  align-items: baseline; padding: 9px 12px;
  border: 1px solid var(--pane-border); border-radius: 8px; background: #fff;
  font-family: var(--exam-font); font-size: 14.5px; line-height: 1.45; cursor: pointer;
}
.import-option:hover { border-color: var(--brown-ink); }
.import-option input { accent-color: var(--navy); align-self: center; }
.import-option-letter { font-weight: 700; color: var(--navy); min-width: 16px; }
/* Where the tick is missing, the instruction sits on the thing to be ticked. */
.imp-tick-note {
  display: block; font-size: var(--text-sm); font-weight: 600; color: var(--brown-ink);
  margin-bottom: 8px;
}
.import-issues { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
/* Scoped to the list on purpose — .category-badge is used on a dozen other
   screens and restyling it globally to suit this one is how a change to the
   import dialog moves the roster's chips. */
#import-list .category-badge {
  display: inline-flex; align-items: center; font-size: 12px; font-weight: 600;
  border-radius: 999px; padding: 4px 11px; border: 1px solid transparent; line-height: 1.4;
}
#import-list .category-badge.badge-attention {
  background: var(--sand-soft); border-color: var(--border-card); color: var(--brown-ink);
}
#import-list .category-badge.badge-blocker {
  background: var(--warm-white); border-color: var(--warm-tan);
  color: var(--deep-ink); font-weight: 700;
}
#import-list .category-badge.badge-blocker::before { content: "!"; font-weight: 800; margin-right: 6px; }

/* ── Fixing a question in the review, beside the page ──────────────────────
   The alternative was importing it broken and mending it in the editor, or
   dropping it and typing it in by hand — and in both of those the page is no
   longer on screen, so "what did the paper actually say" becomes a PDF hunt. */
.imp-q-actions {
  display: flex; gap: 8px; flex-wrap: wrap; align-items: center;
  margin-top: 12px; padding-top: 10px; border-top: 1px dashed var(--hairline);
}
.import-option { position: relative; }
.import-option-body { min-width: 0; }
/* An option with neither words nor a picture. Named rather than left blank —
   an empty row reads as a rendering fault, which is how four picture options
   were once imported as four empty answers. */
.import-option.is-bare { border-style: dashed; border-color: var(--flag-gold); background: var(--state-attention-bg); }
.imp-bare-note { color: var(--text-muted); font-style: italic; font-size: 13px; }
/* The per-option tools sit on their own line under the option's text, so a
   long option is never squeezed by them. */
.imp-opt-tools { grid-column: 3; display: flex; gap: 6px; flex-wrap: wrap; margin-top: 6px; }
.imp-opt-tools .btn-icon { font-size: 12px; padding: 2px 8px; }
.imp-edit { display: block; }
.imp-edit .field-label { display: block; margin-bottom: 4px; }
.imp-edit textarea.text-input { width: 100%; font-family: var(--exam-font); font-size: 14px; }
.imp-edit-opt {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 8px;
  align-items: start; margin-bottom: 6px;
}
.imp-edit-opt .import-option-letter { padding-top: 8px; }
.imp-edit-opt .btn-icon { align-self: start; margin-top: 4px; }
/* The words the importer took out of an option. On screen, and copyable —
   "add it there by hand" without saying which text meant hunting a 67-page
   PDF for a sentence the importer had just been holding. */
.imp-cut {
  background: var(--state-attention-bg); border: 1px solid var(--flag-gold);
  border-radius: var(--radius-sm); padding: 10px 12px; margin-bottom: 12px;
}
.imp-cut-head {
  margin: 0 0 6px; font-size: 12px; font-weight: 700;
  color: var(--brown-ink);
}
.imp-cut-line {
  margin: 3px 0; font-family: var(--exam-font); font-size: var(--text-md);
  line-height: 1.5; color: var(--text-primary);
}
.imp-cut-line::before { content: "“"; }
.imp-cut-line::after { content: "”"; }
.imp-cut .btn-icon { margin-top: 8px; font-size: 12px; padding: 2px 8px; }

/* ── Defining a rubric ─────────────────────────────────────────────────────
   Laid out the way a rubric is printed and read: each set with what it judges
   and what it is out of, then its bands highest first, each with the range it
   covers and the paragraph a marker recognises a response by. */
.rb-set {
  border: 1px solid var(--border-card); border-radius: var(--radius-md);
  padding: 12px 14px; margin-bottom: 10px; background: var(--warm-white);
}
.rb-set-head { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-bottom: 8px; }
.rb-set-head .text-input { flex: 1 1 140px; min-width: 0; }
.rb-set-key { flex: 0 0 56px !important; text-align: center; font-weight: 700; }
.rb-set-out { display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--text-secondary); white-space: nowrap; }
.rb-set-out .text-input { width: 74px; flex: 0 0 74px; }
.rb-set-head .btn-icon { margin-left: auto; }
.rb-band {
  display: grid; grid-template-columns: minmax(0, 1fr) auto auto;
  gap: 8px; align-items: start; margin-top: 8px;
  padding-top: 8px; border-top: 1px dashed var(--hairline);
}
.rb-band-label { font-weight: 600; }
.rb-band-range { display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--text-secondary); }
.rb-band-range .text-input { width: 66px; }
/* PLACED EXPLICITLY, all three. Left to flow, the descriptor's `1 / -1` push
   the Remove button onto a row of its own where it stretched the full width —
   a destructive control made the largest thing in the band. */
.rb-band-label { grid-column: 1; grid-row: 1; }
.rb-band-range { grid-column: 2; grid-row: 1; }
.rb-band > .btn-icon { grid-column: 3; grid-row: 1; justify-self: end; white-space: nowrap; }
/* The band's CONTENT is the rubric — the headline a marker recognises a
   response by, and under it the criteria that decide the mark. Both get the
   full width beneath the row rather than being squeezed beside two number
   boxes, and both are placed EXPLICITLY for the reason written above: left to
   flow, a `1 / -1` item pushes Remove onto a row of its own and a destructive
   control becomes the largest thing in the band. */
.rb-band-headline { grid-column: 1 / -1; grid-row: 2; font-weight: 600; }
.rb-band-desc { grid-column: 1 / -1; grid-row: 3; font-size: var(--text-md); }

/* ── Marking against a rubric ──────────────────────────────────────────────
   THE BAND IS THE JUDGEMENT; THE MARK IS THE ADJUSTMENT. Nobody chooses "11
   out of 15" — they read the piece, recognise it as a Band 5, and then pick a
   mark inside it. A row of sixteen numbers asks for the answer and hides the
   question, so the bands are the control and the numbers appear only inside
   the band once it is chosen. */
.mk-set {
  border: 1px solid var(--border-card); border-radius: var(--radius-md);
  padding: 14px 16px; margin-bottom: 14px; background: var(--warm-white);
}
.mk-set-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
.mk-set-name { font-weight: 700; font-size: var(--text-lg); }
.mk-set-out { font-size: 13px; color: var(--text-secondary); }
.mk-set-what { flex: 1 1 100%; font-size: var(--text-sm); color: var(--text-secondary); }
.mk-band-list { display: flex; flex-direction: column; gap: 6px; }
.mk-band-opt { border: 1px solid var(--border-card); border-radius: 10px; overflow: hidden; background: #fff; }
.mk-band-opt.is-chosen { border-color: var(--deep-ink); box-shadow: inset 3px 0 0 var(--deep-ink); }
.mk-band-pick {
  display: flex; align-items: baseline; gap: 10px; width: 100%; text-align: left;
  border: 0; background: none; font: inherit; color: inherit; cursor: pointer;
  padding: 10px 12px;
}
.mk-band-pick:hover { background: var(--sand-soft); }
.mk-band-n { font-weight: 700; font-size: 14px; }
.mk-band-marks { margin-left: auto; font-size: var(--text-sm); color: var(--text-secondary); }
/* The descriptor only for the chosen band. All six at once is a page of prose
   nobody reads; the one you have chosen is the one to check yourself against. */
.mk-band-body {
  margin: 0; padding: 0 12px 10px 30px; font-size: 13px; line-height: 1.55;
  color: var(--text-secondary);
}
.mk-band-body li { margin: 4px 0; }
.mk-within {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding: 10px 12px; border-top: 1px solid var(--hairline); background: var(--sand-soft);
}
.mk-within-label { font-size: 12px; font-weight: 600; color: var(--text-secondary); margin-right: 4px; }
.mk-num {
  min-width: 34px; padding: 5px 9px; border: 1px solid var(--border-card);
  border-radius: 8px; background: #fff; font: inherit; font-weight: 700; cursor: pointer;
}
.mk-num:hover { border-color: var(--brown-ink); }
.mk-num.is-on { background: var(--deep-ink); border-color: var(--deep-ink); color: var(--warm-white); }

/* THE KEYS, DRAWN ON THE THING THEY DRIVE.
   Picking a band was six clicks per set, two sets per piece, every piece. The
   digit is the band and the arrows place the mark inside it — but a digit is
   shown only on the set the keys are pointed at, because that is the only set
   where pressing it does anything. A digit drawn on a set it does not drive is
   worse than no digit at all.
   Two nested left rules, and they mean different things: brown is the set the
   keys are on, deep ink is the band chosen inside it. */
.mk-set.is-current {
  border-color: var(--brown-ink); box-shadow: inset 3px 0 0 var(--brown-ink);
}
.mk-set-keys {
  margin-left: auto; display: inline-flex; align-items: center; gap: 4px;
  font-size: 12px; color: var(--text-secondary); white-space: nowrap;
}
.mk-set-keys.is-away { opacity: 0.7; }
.mk-set-keys kbd, .mk-band-key {
  font: inherit; font-size: 11px; font-weight: 700;
  min-width: 17px; padding: 1px 5px; text-align: center;
  border: 1px solid var(--border-card); border-bottom-width: 2px;
  border-radius: 5px; background: var(--warm-white); color: var(--text-secondary);
}
.mk-band-key { flex: 0 0 auto; }
.mk-band-opt.is-chosen .mk-band-key { border-color: var(--deep-ink); color: var(--deep-ink); }

.mk-fb { margin-top: 12px; }
.mk-improve { margin-top: 10px; }
.mk-improve-list { display: flex; flex-direction: column; gap: 5px; }
/* A delivery switch that is ON, on the group it belongs to. Lilac like the
   Plus tag: it is a setting, not a verdict, and must not borrow the marking
   colours. */
.btn-icon.is-on {
  background: var(--partial-bg); border-color: var(--partial-border); color: #3A4A73;
}

/* ── The shuffle, dealt before anyone sits it ──────────────────────────────
   Every chip is a question's number AS WRITTEN, tinted by its reading group,
   so the blocks are visible without reading a single number: you see the group
   held together, and where it moved to. */
.shuffle-preview {
  margin-top: 12px; padding: 14px 16px;
  background: var(--warm-white); border: 1px solid var(--border-card);
  border-radius: var(--radius-md);
}
.sp-section + .sp-section { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--hairline); }
.sp-head { font-weight: 700; font-size: 14.5px; margin-bottom: 6px; }
.sp-legend { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.sp-key {
  font-size: 11.5px; padding: 2px 9px; border-radius: 999px;
  border: 1px solid var(--border-card); color: var(--text-secondary);
}
.sp-key-loose { background: transparent; border-style: dashed; }
.sp-run { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 5px 0; }
.sp-run-n {
  flex: 0 0 68px; font-size: 12px; font-weight: 700; color: var(--text-secondary);
}
.sp-chips { display: flex; flex-wrap: wrap; gap: 3px; }
.sp-chip {
  min-width: 24px; text-align: center; font-size: 12px; font-weight: 600; padding: 2px 5px; border-radius: 5px;
  border: 1px solid var(--hairline);
}
.sp-opts { font-size: 12px; color: var(--text-secondary); }
/* The working behind a verdict. Prose, not a chip: it is a sentence somebody
   reads and acts on, and it only appears on the rows that are flagged — on a
   sound question it would be true, uninteresting, and thirty-five of them
   would bury the six that matter. */
.item-why {
  margin: 8px 0 0; padding: 9px 12px;
  background: var(--sand-soft); border-left: 3px solid var(--warm-tan);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  font-size: 13px; line-height: 1.6; color: var(--text-secondary);
}
.item-why strong { color: var(--text-primary); }
/* Somewhere to go, on the rows that need it. */
.item-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }

/* The resolution note on the image tools bar. Warn tint, not bad: nothing is
   broken — a decision is waiting on a person while recapturing is cheap. */
.img-tools .img-res {
  font-size: var(--text-xs); font-weight: 700; padding: 3px 8px;
  background: var(--state-warn-bg); border: 1px solid var(--state-warn-border);
  border-radius: var(--radius-sm); color: var(--state-warn-text);
  max-width: 250px;
}

/* The table size picker — sweep across squares, the table you would get
   lights up. Word's grid, in our clothes. */
.table-picker {
  position: absolute; z-index: 60; padding: 10px;
  background: var(--warm-white); border: 1px solid var(--border);
  border-radius: var(--radius-sm); box-shadow: 0 6px 18px rgba(60, 45, 20, 0.18);
}
.table-picker-grid {
  display: grid; grid-template-columns: repeat(8, 17px); gap: 3px; margin: 8px 0;
}
/* Real buttons — the tick-box grammar forbids inert bordered squares, and
   these are pressed, so they carry a control's element as well as its shape. */
.table-picker-grid button {
  width: 17px; height: 17px; border: 1px solid var(--border);
  border-radius: 2px; background: #fff; cursor: pointer; padding: 0;
}
.table-picker-grid button.lit { background: var(--sand-soft); border-color: var(--brown-ink); }
.table-picker-label { font-size: var(--text-xs); font-weight: 700; color: var(--text-muted); }

/* The alt-text mark. 138 pictures in the real store carry no alt text at
   all — the field was always here and nothing ever pointed at it. A dot,
   because the insert-time PROMPT that came before was answered the way
   prompts are: dismissed. */
.img-tools .img-tools-label.needs-alt { color: var(--state-warn-text); }
.img-tools .img-tools-label.needs-alt::after {
  content: '•'; margin-left: 3px; color: var(--state-warn-border); font-size: 15px;
}
