/* ClassCloud — deliberately plain. Large tap targets and high contrast matter
   more than styling here: this is read on a projected smart-board and on old
   student laptops. */

/* Theme: follows the device by default, overridden by an explicit choice.
   The [data-theme] rules come after the media query so a deliberate pick
   always wins over the OS setting, in both directions. */

:root {
  --radius: 8px;

  --bg: #f6f7f9;
  --surface: #ffffff;
  --border: #d8dce2;
  --text: #1c2024;
  --muted: #5f6b76;
  --accent: #1b5e9e;
  --error: #a32020;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #16191d;
    --surface: #1f242a;
    --border: #333a42;
    --text: #e8ebee;
    --muted: #9aa5b1;
    --accent: #6aa9e0;
    --error: #e46a6a;
  }
}

:root[data-theme="light"] {
  --bg: #f6f7f9;
  --surface: #ffffff;
  --border: #d8dce2;
  --text: #1c2024;
  --muted: #5f6b76;
  --accent: #1b5e9e;
  --error: #a32020;
}

:root[data-theme="dark"] {
  --bg: #16191d;
  --surface: #1f242a;
  --border: #333a42;
  --text: #e8ebee;
  --muted: #9aa5b1;
  --accent: #6aa9e0;
  --error: #e46a6a;
}

/* Theme toggle: shows the icon for the theme you would switch *to*. */
.theme-toggle {
  background: none;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
  padding: 0.35rem 0.55rem;
}
.theme-toggle:hover { border-color: var(--accent); }

.theme-toggle .icon-dark { display: none; }
:root[data-theme="dark"] .theme-toggle .icon-dark { display: inline; }
:root[data-theme="dark"] .theme-toggle .icon-light { display: none; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .theme-toggle .icon-dark { display: inline; }
  :root:not([data-theme]) .theme-toggle .icon-light { display: none; }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 16px/1.5 system-ui, "Segoe UI", Roboto, sans-serif;
}

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.75rem 1.25rem;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  flex-wrap: wrap;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 700;
  font-size: 1.15rem;
  text-decoration: none;
  color: var(--text);
}
.brand-mark { display: block; flex: none; }
.topbar nav { display: flex; align-items: center; gap: 1rem; }
.topbar a { color: var(--accent); text-decoration: none; }
.topbar a:hover { text-decoration: underline; }
.inline { display: inline; margin: 0; }

/* Two classes deep on purpose: this is a submit button, and
   `button[type="submit"]` (0,1,1) would otherwise outrank a lone class and
   paint the logout control as a full-size blue primary button. */
.topbar .linkish {
  background: none;
  border: 0;
  padding: 0;
  color: var(--accent);
  cursor: pointer;
  font: inherit;
  font-weight: inherit;
  border-radius: 0;
}
.topbar .linkish:hover { text-decoration: underline; filter: none; }

main { max-width: 900px; margin: 0 auto; padding: 1.5rem 1.25rem 4rem; }

h1 { font-size: 1.6rem; margin: 0.5rem 0 1rem; }
h2 { font-size: 1.1rem; margin: 2rem 0 0.75rem; color: var(--muted); font-weight: 600; }
h3 { font-size: 1rem; margin: 0 0 0.75rem; }

.crumbs { font-size: 0.9rem; color: var(--muted); margin-bottom: 0.5rem; }
.crumbs a { color: var(--accent); text-decoration: none; }
.crumbs a:hover { text-decoration: underline; }
.crumbs .sep { margin: 0 0.4rem; opacity: 0.5; }

.description { color: var(--muted); margin: 0 0 1rem; }
.empty { color: var(--muted); font-style: italic; padding: 2rem 0; }
.error {
  background: var(--error);
  color: #fff;
  padding: 0.6rem 0.85rem;
  border-radius: var(--radius);
}

.listing { list-style: none; margin: 0; padding: 0; }
.listing li + li { margin-top: 0.4rem; }

.row {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  padding: 0.85rem 1rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  text-decoration: none;
  color: var(--text);
}
.row:hover { border-color: var(--accent); }

.icon { font-size: 1.35rem; line-height: 1; flex: none; }
.label {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
  flex: 1;
  /* break-word, not anywhere: `anywhere` also shrinks the intrinsic
     min-content width to a single character, which lets the column collapse
     and wrap the name one letter per line. */
  overflow-wrap: break-word;
}
.label small { color: var(--muted); font-size: 0.82rem; min-width: 0; }

/* Descriptions are clamped to two lines in a listing so one verbose folder
   cannot dominate the page. The full text is in the details panel. */
.label small.desc {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Admin badges (visibility, upload level, size) sit below the description and
   are dimmer than it — the teacher's own words matter more than the flags. */
.label small.meta { opacity: 0.75; font-size: 0.78rem; }
.action { flex: none; color: var(--accent); font-size: 0.9rem; }

.panels { display: grid; gap: 1.25rem; margin-top: 2.5rem; }
@media (min-width: 720px) { .panels { grid-template-columns: 1fr 1fr; } }

.panel, .login form {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.15rem;
}

/* ---- Folder toolbar ----------------------------------------------------
   Everything that acts on the current folder lives on one line: creating on
   the left, rare and destructive actions pushed to the right. Forms open as
   dropdowns so they cost nothing until used. */

/* Sticky, because a folder of forty files scrolls the ＋ off the screen and
   the answer to that should not be "scroll back up". */
.toolbar {
  position: sticky;
  top: 0;
  z-index: 25;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  margin: 0 0 1.5rem;
  padding: 0.5rem 0;
  background: var(--bg);
}
.toolbar-gap { flex: 1 1 auto; }

.tool {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.45rem 0.7rem;
  font-size: 0.85rem;
  font-weight: 500;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 6px;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  list-style: none;
}
.tool::-webkit-details-marker { display: none; }
.tool:hover { border-color: var(--accent); color: var(--accent); }
.tool .ico { display: flex; font-size: 0.95rem; line-height: 1; }

.tool.primary { color: var(--accent); }
.tool.danger { color: var(--error); }
.tool.danger:hover { border-color: var(--error); color: var(--error); }

.tool-menu { position: relative; }
.tool-menu[open] > .tool { border-color: var(--accent); }

.tool-menu > .menu {
  position: absolute;
  z-index: 20;
  top: calc(100% + 0.35rem);
  left: 0;
  min-width: 20rem;
  max-width: min(24rem, 90vw);
  padding: 1rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgb(0 0 0 / 0.18);
  /* This hangs off a sticky bar that sits ~17rem down a page with nothing
     below it to scroll — so an overlong menu had no way to be reached at all:
     the page would not scroll, and the menu moved with the bar that pinned it.
     The tallest form here — a new folder, five fields, a checkbox and a hint —
     put Create off the bottom of a laptop screen with no way back to it.
     So the menu carries its own scrollbar, sized to the room that is actually
     under the bar rather than to the form that wants the space. The rem term
     covers the header and toolbar above; the cap keeps a tall screen from
     growing a dropdown the length of the window. */
  max-height: min(26rem, calc(100dvh - 22rem));
  overflow-y: auto;
  overscroll-behavior: contain;
}
.tool-menu.align-end > .menu { left: auto; right: 0; }
/* Wider for the menu that holds a text editor rather than a few fields. */
.tool-menu.wide > .menu { min-width: 28rem; max-width: min(38rem, 92vw); }
.tool-menu > .menu label:last-of-type { margin-bottom: 1rem; }

/* A row says only what differs from the default, and says it as a chip rather
   than as more grey text — the point is that a draft among forty visible files
   is found by looking, not by reading. */
.flag {
  display: inline-block;
  margin-left: 0.4rem;
  padding: 0.05rem 0.4rem;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--error);
  background: color-mix(in srgb, var(--error) 12%, transparent);
  border-radius: 999px;
  white-space: nowrap;
}
/* No color-mix in older engines; the border keeps it legible there. */
@supports not (background: color-mix(in srgb, red 10%, transparent)) {
  .flag { background: none; border: 1px solid var(--error); }
}

/* Text shown by us rather than by the browser. Measured for reading: a
   student's answer is prose, and prose in a full-width column is hard work. */
.preview-text {
  max-width: 44rem;
  margin: 0 auto;
  padding: 1.25rem 1.4rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  text-align: left;
}
/* Plain text keeps its own line breaks and wraps rather than scrolling, since
   a handed-in answer is not code. */
.preview-text.plain {
  font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 0.92rem;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* ---- Writing Markdown ---------------------------------------------------

   Buttons above the field, preview under it. A toggle rather than a second
   column: on a school laptop a side-by-side preview halves the width of the
   thing being written, and what is being written is a paragraph. */

.md-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin: 0.3rem 0 0.35rem;
}

.md-btn {
  min-width: 2rem;
  height: 2rem;
  padding: 0 0.5rem;
  margin: 0;
  font: inherit;
  font-size: 0.85rem;
  line-height: 1;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  cursor: pointer;
}
.md-btn:hover { border-color: var(--accent); color: var(--accent); filter: none; }
/* The preview toggle sits apart from the writing buttons: it changes what is
   on screen rather than what is in the field. */
.md-toggle { margin-left: auto; font-size: 0.8rem; color: var(--muted); }
.md-toggle.on { color: var(--accent); border-color: var(--accent); }

.md-preview {
  margin: 0.5rem 0 0.85rem;
  padding: 0.85rem 1rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  /* Bounded, so a long answer does not push the Send button off the screen. */
  max-height: 24rem;
  overflow-y: auto;
}
.md-preview:empty { display: none; }

@media (max-width: 560px) {
  /* Thumb-sized, like every other control at this width. */
  .md-btn { min-width: 2.75rem; height: 2.75rem; }
}

/* ---- Icons --------------------------------------------------------------

   One sprite, one box, one stroke weight. The previous set mixed emoji with
   text glyphs, which come from different fonts with different metrics — the
   emoji rendered in colour, larger, and on a different baseline. No amount of
   centring fixes that, because the glyphs disagree about where their centre
   is. These are all 24×24 and inherit currentColor, so an icon in a danger
   button is red because the button is red. */

.icon-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

.i {
  display: block;
  flex: none;
  width: 1.05rem;
  height: 1.05rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* ---- The one ＋ ---------------------------------------------------------

   A chooser, not a toolbar. Each row names a thing and says in one line what
   it is, so the difference between a link and a note is answered where it is
   asked rather than in a manual. */

.add-list { list-style: none; margin: 0; padding: 0; }
.add-list > li + li { border-top: 1px solid var(--border); }

.add-row {
  display: grid;
  grid-template-columns: 1.6rem 1fr;
  align-items: baseline;
  gap: 0.1rem 0.6rem;
  padding: 0.6rem 0.4rem;
  cursor: pointer;
  color: var(--text);
  text-decoration: none;
  border-radius: 6px;
  list-style: none;
}
.add-row::-webkit-details-marker { display: none; }
.add-row:hover { background: var(--bg); }
.add-row .ico { grid-row: span 2; font-size: 1.1rem; line-height: 1.2; }
.add-name { font-weight: 600; }
/* The line that does the actual explaining, so it is not an afterthought in
   the tooltip nobody hovers. */
.add-hint { grid-column: 2; color: var(--muted); font-size: 0.85rem; line-height: 1.35; }

/* Open rows get a rule down the left, so a long form still reads as belonging
   to the row that opened it. */
.add-item[open] > .add-row { background: var(--bg); }
.add-item[open] > form {
  margin: 0.25rem 0 0.75rem;
  padding: 0.75rem 0 0.25rem 0.9rem;
  border-left: 2px solid var(--accent);
}

/* The one row that navigates instead of expanding says so. */
.add-go::after {
  content: "→";
  grid-column: 2;
  margin-top: -1.35rem;
  justify-self: end;
  color: var(--muted);
}

@media (max-width: 640px) {
  /* Dropdowns become inline blocks rather than overlays on a narrow screen,
     where an absolutely positioned menu would fall off the edge. Inline, the
     page scrolls the form into reach on its own, so the height bound that the
     overlay needs would only add a second scrollbar inside the first. */
  .tool-menu > .menu {
    position: static;
    min-width: 0;
    box-shadow: none;
    max-height: none;
    overflow-y: visible;
  }
}

/* ---- Preview viewer ----------------------------------------------------- */

.preview-head { margin-bottom: 0.75rem; }
.preview-head h1 { margin-bottom: 0.25rem; word-break: break-word; }
.preview-meta { margin: 0; color: var(--muted); font-size: 0.9rem; }

.preview-stage {
  margin: 1.25rem 0;
  padding: 0.75rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  text-align: center;
}

.preview-stage img {
  max-width: 100%;
  /* Bounded by the viewport so a large scan does not push the download button
     off the bottom of the screen. */
  max-height: 75vh;
  height: auto;
  border-radius: 4px;
}

.preview-pdf {
  width: 100%;
  height: 75vh;
  border: 0;
  border-radius: 4px;
  background: #fff;
}

.preview-actions { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; }

.btn-primary {
  display: inline-block;
  padding: 0.6rem 1.2rem;
  font-weight: 600;
  color: #fff;
  background: var(--accent);
  border-radius: 6px;
  text-decoration: none;
}
.btn-primary:hover { filter: brightness(1.1); }

@media (max-width: 560px) {
  .preview-stage { padding: 0.4rem; }
  .preview-stage img, .preview-pdf { max-height: 60vh; }
  .preview-pdf { height: 60vh; }
}

/* ---- Rendered descriptions --------------------------------------------- */

.markdown > :first-child { margin-top: 0; }
.markdown > :last-child { margin-bottom: 0; }
.markdown p { margin: 0 0 0.7rem; }
.markdown ul, .markdown ol { margin: 0 0 0.7rem; padding-left: 1.4rem; }
.markdown li { margin-bottom: 0.2rem; }
.markdown h1, .markdown h2, .markdown h3,
.markdown h4, .markdown h5, .markdown h6 {
  margin: 1rem 0 0.5rem;
  font-size: 1rem;
  color: var(--text);
}
.markdown a { color: var(--accent); }
.markdown hr { border: 0; border-top: 1px solid var(--border); margin: 1rem 0; }

.markdown code {
  padding: 0.1rem 0.3rem;
  font-size: 0.9em;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 4px;
}
.markdown pre {
  margin: 0 0 0.7rem;
  padding: 0.75rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow-x: auto;
}
.markdown pre code { padding: 0; background: none; border: 0; }

/* Tables scroll inside their own box. A wide one in a description would
   otherwise push the whole page sideways on a student's phone. */
.markdown table {
  display: block;
  width: max-content;
  max-width: 100%;
  overflow-x: auto;
  border-collapse: collapse;
  margin: 0 0 0.7rem;
  font-size: 0.9em;
}
.markdown th, .markdown td {
  padding: 0.35rem 0.6rem;
  border: 1px solid var(--border);
  text-align: left;
}
.markdown th { background: var(--bg); font-weight: 600; }

.markdown blockquote {
  margin: 0 0 0.7rem;
  padding: 0.5rem 0.85rem;
  border-left: 3px solid var(--border);
  color: var(--muted);
}

/* Callouts carry intent, not a colour. Each is defined against the theme
   tokens, so it stays readable in light and dark — which an author-chosen
   colour could not. */
.markdown blockquote.callout {
  border-left-width: 4px;
  border-radius: 0 6px 6px 0;
  background: var(--bg);
  color: var(--text);
}
.markdown blockquote.callout-note { border-left-color: var(--accent); }
.markdown blockquote.callout-tip { border-left-color: #1f7a4d; }
.markdown blockquote.callout-warning { border-left-color: var(--error); }

/* ---- Notes in the listing ----------------------------------------------
   A note is text placed among the rows, not a row to open. It gets the same
   card so the list still reads as one column, but no hover highlight and no
   pointer: nothing here responds to a click. */

.listing li.note {
  padding: 0.9rem 1.1rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.note-title { margin: 0 0 0.5rem; line-height: 1.3; }

/* The admin row is the same card with a handle and controls added, so the
   note lines up with every other row in the listing. Controls sit at the top
   rather than centred — a long note would otherwise leave them floating in
   the middle of a paragraph. */
.with-tools.note-row {
  align-items: flex-start;
  padding: 0.9rem 1.1rem 0.9rem 0;
}
.with-tools.note-row .handle { align-self: flex-start; padding-top: 0.15rem; }
.note-card { flex: 1; min-width: 0; }

.note-meta {
  display: block;
  margin-top: 0.6rem;
  color: var(--muted);
  font-size: 0.78rem;
  opacity: 0.75;
}

/* Every other row hands Edit and Delete to the details panel once selection
   is enabled. A note is never selected — it has no anchor to click — so its
   own links have to stay. A quiz has an anchor but no details template, so it
   is never selected either: without this, its Delete has nowhere left to live. */
.select-enabled .note-row .tools .named,
.select-enabled .quiz-row .tools .named { display: flex; }

/* ---- Markdown syntax crib ---------------------------------------------- */

.format-hint { margin: -0.35rem 0 0.9rem; font-size: 0.82rem; color: var(--muted); }
.format-hint summary { cursor: pointer; color: var(--accent); }
.format-hint ul { margin: 0.5rem 0 0; padding-left: 1.1rem; }
.format-hint li { margin-bottom: 0.2rem; }
.format-hint code { font-size: 0.95em; }

/* ---- Copy link --------------------------------------------------------- */

.sharebtn {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: 1.9rem;
  height: 1.9rem;
  margin: 0;
  padding: 0;
  font-size: 0.85rem;
  line-height: 1;
  color: var(--muted);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  cursor: pointer;
  flex: none;
}
.sharebtn .i { width: 1rem; height: 1rem; }
.sharebtn:hover { color: var(--accent); border-color: var(--accent); }

/* Revealed with the other row controls, but always present on touch, where
   there is no hover to reveal anything. */
.with-tools .sharebtn { opacity: 0; transition: opacity 0.12s ease; }
.with-tools:hover .sharebtn,
.with-tools:focus-within .sharebtn,
.with-tools.selected .sharebtn { opacity: 1; }
@media (hover: none) { .with-tools .sharebtn { opacity: 1; } }

.copy-toast {
  position: absolute;
  right: 0;
  bottom: calc(100% + 0.3rem);
  z-index: 30;
  padding: 0.25rem 0.5rem;
  font-size: 0.72rem;
  white-space: nowrap;
  color: #fff;
  background: #1f7a4d;
  border-radius: 4px;
  pointer-events: none;
}
.copy-toast.failed { background: var(--error); }

.details-copy.copied { color: #1f7a4d; border-color: #1f7a4d; }
.details-copy.copy-failed { color: var(--error); border-color: var(--error); }

/* ---- Handing work in ---------------------------------------------------- */

/* The teacher's half: the hand-in settings on a file's edit form. Same frame
   as the feedback fieldset, because it is the same kind of thing — a block of
   optional settings inside a form about something else. */
.assign {
  margin: 1.25rem 0 1rem;
  padding: 0.9rem 1rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
}
.assign legend { padding: 0 0.4rem; font-weight: 600; font-size: 0.95rem; }
.assign .notice { margin: 0 0 0.9rem; border: 0; padding: 0; background: none; }

/* The student's half: what they have handed in so far. A receipt, so it is a
   plain list — the point is being able to open it and check, not decoration. */
.handed-list { margin: 0; padding: 0; list-style: none; }
.handed-list li {
  padding: 0.45rem 0;
  border-bottom: 1px solid var(--border);
}
.handed-list li:last-child { border-bottom: 0; }
.handed-list .meta { display: block; color: var(--muted); font-size: 0.85rem; }

/* ---- Lesson feedback ---------------------------------------------------- */

.feedback {
  margin: 1.25rem 0 1rem;
  padding: 0.9rem 1rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
}
.feedback legend { padding: 0 0.4rem; font-weight: 600; font-size: 0.95rem; }
.feedback .notice { margin: 0 0 0.9rem; border: 0; padding: 0; background: none; }

.scale + .scale { margin-top: 1rem; }
.scale-q { margin: 0 0 0.45rem; font-size: 0.9rem; font-weight: 500; }

.scale-row { display: flex; gap: 0.4rem; }

/* The whole box is the target, not just the dot — this is used with a finger
   on the board and on phones. */
.scale-opt { flex: 1; margin: 0; cursor: pointer; }
.scale-opt input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}
.scale-opt span {
  display: block;
  padding: 0.55rem 0;
  text-align: center;
  font-weight: 600;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 6px;
}
.scale-opt:hover span { border-color: var(--accent); }
.scale-opt input:checked + span {
  color: #fff;
  background: var(--accent);
  border-color: var(--accent);
}
/* Keyboard focus must stay visible even though the input itself is hidden. */
.scale-opt input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }

.scale-ends {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 0.35rem;
  font-size: 0.78rem;
  color: var(--muted);
}

.feedback-summary { margin: 0 0 1.5rem; max-width: 30rem; }
.feedback-summary h3 { margin-top: 0; }
.feedback-summary .stats dd small { color: var(--muted); font-weight: 400; }

.checkbox { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.35rem; }
.checkbox input { width: auto; margin: 0; flex: none; }
.checkbox span { color: var(--text); }

/* Upload progress */
.upload-status { margin-top: 1rem; }

.progress {
  height: 0.5rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 999px;
  overflow: hidden;
}
.progress .bar {
  height: 100%;
  width: 0;
  background: var(--accent);
  transition: width 0.15s linear;
}
.progress-text { margin: 0.45rem 0 0; font-size: 0.85rem; color: var(--muted); }

.upload-status.failed .progress { display: none; }
.upload-status.failed .progress-text { color: var(--error); font-weight: 600; }

button[type="submit"]:disabled { opacity: 0.55; cursor: default; filter: none; }

/* Narrow screens: students are often on a phone, and the classroom board is
   used in portrait more often than a desktop would suggest. */
@media (max-width: 560px) {
  main { padding: 1rem 0.85rem 3rem; }
  .topbar { padding: 0.6rem 0.85rem; gap: 0.5rem; }
  .topbar nav { gap: 0.75rem; font-size: 0.9rem; flex-wrap: wrap; }
  h1 { font-size: 1.3rem; }

  /* Row controls wrap under the content rather than squeezing it. */
  .with-tools { flex-wrap: wrap; padding-bottom: 0.5rem; }
  .with-tools > .row { flex: 1 1 100%; }
  .tools { margin-left: auto; }

  .toolbar .tool { font-size: 0.8rem; padding: 0.4rem 0.55rem; }
  .searchbar { flex-wrap: wrap; }

  /* The named action takes the width; the icons stay a row of thumb-sized
     squares beside it rather than four stacked full-width bars. */
  .details-actions .btn { flex: 1 1 100%; }
  .details-open { flex: 1 1 100%; padding: 0.7rem; }

  /* 2.75rem is 44px at the default root size, which is the smallest target a
     thumb hits reliably. The desktop sizes are pointer-sized and too small
     here — and on a touch screen there is no hover to enlarge them with. */
  .iconaction { width: 2.75rem; height: 2.75rem; font-size: 1.1rem; }
  .sharebtn { width: 2.75rem; height: 2.75rem; }
  .tools { gap: 0.35rem; }
  .tools .iconbtn { width: 2.75rem; height: 1.3rem; font-size: 0.7rem; }

  /* A sticky bar costs a third of a short screen once it wraps to two lines. */
  .toolbar { position: static; padding: 0; }
}

/* Settings */
.settings-grid { display: grid; gap: 1.25rem; }
@media (min-width: 760px) { .settings-grid { grid-template-columns: 1fr 1fr; } }
.settings-grid .panel { align-self: start; }
.settings-grid h3 { margin-top: 0; }

.stats {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.4rem 1rem;
  margin: 0 0 1rem;
  font-size: 0.9rem;
}
.stats dt { color: var(--muted); }
.stats dd { margin: 0; font-variant-numeric: tabular-nums; }
.stats dd.path { word-break: break-all; font-size: 0.82rem; }

/* ---- The technical page ------------------------------------------------- */

/* The verdict answers the only question most readers came with, so it is the
   size of a heading and sits above everything it summarises. Colour is never
   the only signal — the words differ too, for the reader who cannot see the
   difference between the two greens. */
.verdict {
  margin: 0 0 0.5rem;
  padding: 0.6rem 0.9rem;
  border-left: 4px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  font-size: 1.05rem;
  font-weight: 600;
}
/* The same green .success uses, rather than a new token: it would need a value
   in every theme block including the high-contrast one, for one border. */
.verdict-good { border-left-color: #1f7a4d; }
.verdict-warn { border-left-color: var(--error); }

/* A bar for the two figures that are shares of something. Everything else on
   this page is a number, but "14 GB free" means nothing without the total it
   came out of, and a bar says the ratio faster than the subtraction does. */
.meter {
  height: 0.55rem;
  margin: 0 0 0.9rem;
  border-radius: 999px;
  background: var(--bg);
  border: 1px solid var(--border);
  overflow: hidden;
}
.meter-fill {
  display: block;
  height: 100%;
  background: var(--accent);
  border-radius: inherit;
}
.meter-tight { background: var(--error); }

.health-foot {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-top: 1.5rem;
}
/* When the page was read, so two screenshots can be told apart. */
.health-stamp {
  color: var(--muted);
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
}

/* Search */
.searchbar { display: flex; gap: 0.5rem; margin: 0 0 1.5rem; }
.searchbar input { flex: 1; margin-top: 0; }
.searchbar button { flex: none; }

.topsearch { font-size: 1.05rem; text-decoration: none; margin-left: 0.5rem; }

/* ---- The admin bar, and the hub it leads to ----------------------------- */

/* What is left in the bar after the hub took the rest: things reached for
   without looking, so they are icons rather than words. Sized to a thumb —
   the board is a touch screen, and these are the only controls on it that a
   teacher uses while standing up. */
.topicon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  position: relative;
  width: 2.25rem;
  height: 2.25rem;
  font-size: 1.05rem;
  line-height: 1;
  text-decoration: none;
  border-radius: 8px;
}
.topicon:hover { background: var(--bg); }

/* The count of work waiting. Never the only signal: the aria-label carries the
   same number, because a red circle says nothing to a screen reader. */
.badge {
  display: inline-block;
  min-width: 1.15rem;
  padding: 0 0.28rem;
  border-radius: 999px;
  background: var(--error);
  color: #fff;
  font-size: 0.7rem;
  font-weight: 600;
  line-height: 1.15rem;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.bell .badge {
  position: absolute;
  top: 0.05rem;
  right: 0;
}

/* The hub. Auto-fill rather than a fixed column count: the same grid is three
   across on the board, two on a laptop and one on a phone without a
   breakpoint for each. */
.tiles {
  list-style: none;
  margin: 1.5rem 0 0;
  padding: 0;
  display: grid;
  gap: 0.85rem;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
}
.tile {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-rows: auto auto;
  column-gap: 0.75rem;
  height: 100%;
  padding: 1rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  text-decoration: none;
  color: inherit;
}
.tile:hover {
  border-color: var(--accent);
  /* Lifted rather than recoloured, so the hover reads the same in both themes
     and on a projector that has flattened every subtle background. */
  box-shadow: 0 2px 8px rgb(0 0 0 / 0.10);
}
.tile-icon {
  grid-row: span 2;
  align-self: start;
  font-size: 1.5rem;
  line-height: 1.2;
}
.tile-name {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-weight: 600;
  color: var(--accent);
}
.tile-hint {
  color: var(--muted);
  font-size: 0.85rem;
  line-height: 1.35;
}
/* A tile that earns the full width of the grid. */
.tile-wide { grid-column: 1 / -1; }

.export-links { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.5rem; }
.export-links a {
  display: block;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--accent);
  text-decoration: none;
  font-size: 0.9rem;
}
.export-links a {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
}
.export-links a:hover { border-color: var(--accent); }
.export-links .count { color: var(--muted); font-size: 0.8rem; white-space: nowrap; }

/* An option that would produce an empty archive stays clickable, but says so
   rather than handing over a baffling empty zip. */
.export-links a.empty-export { color: var(--muted); opacity: 0.6; }

/* Panel actions are real buttons: this is where actions live once the panel
   is available, so they get room to be labelled and hit accurately. */
.details-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0.9rem 0 0;
}
/* The one action worth spelling out leads the row; everything else is an
   icon with its name in the tooltip. Four labelled buttons wrapped onto three
   lines in a 20rem panel and made every row look like a form to fill in. */
.details-open {
  flex: 1 1 auto;
  padding: 0.45rem 0.7rem;
  font-size: 0.82rem;
  font-weight: 600;
  text-align: center;
  color: #fff;
  background: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 6px;
  text-decoration: none;
  white-space: nowrap;
}
.details-open:hover { filter: brightness(1.08); }

.iconaction {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 2.1rem;
  height: 2.1rem;
  font-size: 0.95rem;
  line-height: 1;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  text-decoration: none;
}
.iconaction:hover { border-color: var(--accent); color: var(--accent); }
.iconaction.danger { color: var(--error); }
.iconaction.danger:hover { border-color: var(--error); color: var(--error); }

.details-actions .btn {
  flex: 1 1 auto;
  padding: 0.45rem 0.6rem;
  text-align: center;
  font-size: 0.82rem;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  text-decoration: none;
  white-space: nowrap;
}
.details-actions .btn:hover { border-color: var(--accent); color: var(--accent); }
.details-actions .btn.danger { color: var(--error); }
.details-actions .btn.danger:hover { border-color: var(--error); }

.login { max-width: 380px; margin: 3rem auto; }

label { display: block; margin-bottom: 0.85rem; font-size: 0.9rem; color: var(--muted); }
.req { color: var(--error); }

input[type="text"], input[type="password"], textarea, select, input[type="file"] {
  display: block;
  width: 100%;
  margin-top: 0.3rem;
  padding: 0.55rem 0.65rem;
  font: inherit;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
}
textarea { resize: vertical; }

button[type="submit"] {
  padding: 0.6rem 1.2rem;
  font: inherit;
  font-weight: 600;
  color: #fff;
  background: var(--accent);
  border: 0;
  border-radius: 6px;
  cursor: pointer;
}
button[type="submit"]:hover { filter: brightness(1.1); }
button[type="submit"].danger { background: var(--error); }

/* Admin rows: the <li> is the card, so the handle, the content and the
   controls all sit inside one bordered box and line up with each other. The
   controls stay outside the anchor — a stray click should land on "open", not
   "delete" — but inside the card visually. */
.with-tools {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding-right: 0.85rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.with-tools:hover { border-color: var(--accent); }

.with-tools > .row {
  flex: 1;
  min-width: 0;
  background: none;
  border: 0;
  border-radius: 0;
  padding-left: 0;
}
.with-tools > .row:hover { border: 0; }

.tools {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.85rem;
  flex: none; /* content must never squeeze the controls out of the row */
}
.tools a { color: var(--accent); text-decoration: none; white-space: nowrap; }
.tools a:hover { text-decoration: underline; }
.danger-link { color: var(--error) !important; }

/* Move controls: small, quiet, revealed on hover or focus.
   The selector is deliberately two classes deep. These are submit buttons, and
   `button[type="submit"]` (0,1,1) outranks a lone class (0,1,0) — which is why
   they previously rendered as full-size blue primary buttons. Every property
   that rule sets is reset here explicitly rather than relying on inheritance. */
.tools .move {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0;
}

.tools .iconbtn {
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: 1.55rem;
  height: 1.15rem;
  margin: 0;
  padding: 0;
  font: inherit;
  font-size: 0.55rem;
  font-weight: 400;
  /* Flex centring rather than line-height: a line box taller than the content
     box was clipping the glyphs. */
  line-height: 1;
  color: var(--muted);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 3px;
  cursor: pointer;
}
.tools .iconbtn .i { width: 0.8rem; height: 0.8rem; stroke-width: 2.2; }
.tools .iconbtn:hover {
  color: var(--accent);
  border-color: var(--accent);
  filter: none;
}

/* Reveal on hover or keyboard focus. Pointer-coarse devices get them always,
   since there is no hover on the classroom board's touchscreen. */
.with-tools .move { opacity: 0; transition: opacity 0.12s ease; }
.with-tools:hover .move,
.with-tools:focus-within .move,
.with-tools.selected .move { opacity: 1; }

@media (hover: none) {
  .with-tools .move { opacity: 1; }
}

/* The named Edit/Delete links are the no-JavaScript path. With the details
   panel active they are redundant and cost a lot of row width, so they go. */
.select-enabled .tools .named { display: none; }
.tools .named { display: flex; gap: 0.75rem; }

.form-actions { display: flex; align-items: center; gap: 1rem; }
.secondary { color: var(--muted); text-decoration: none; }
.secondary:hover { text-decoration: underline; }

.error ul { margin: 0.4rem 0 0; padding-left: 1.2rem; }

/* Reordering. The handle only appears once the drag script has enhanced the
   list, so a browser without it shows the up/down buttons and nothing that
   promises an interaction it cannot deliver. */
.handle { display: none; }

.drag-enabled .handle .i { width: 0.95rem; height: 0.95rem; }
.drag-enabled .handle {
  display: flex;
  align-items: center;
  align-self: stretch;
  padding: 0 0.15rem 0 0.6rem;
  color: var(--muted);
  cursor: grab;
  font-size: 1.05rem;
  user-select: none;
  /* Stops touch-dragging from scrolling the page instead. */
  touch-action: none;
}
.drag-enabled .handle:hover { color: var(--accent); }
.drag-enabled .handle:active { cursor: grabbing; }

.listing li.dragging {
  opacity: 0.65;
  outline: 2px dashed var(--accent);
  border-radius: var(--radius);
}
.listing.saving { opacity: 0.7; }


/* Browser layout: listing on the left, details panel on the right.
   The panel is only useful once JavaScript has something to put in it, so it
   stays hidden until .select-enabled is applied. */
.browser { display: block; }
.details-panel { display: none; }

.browser.select-enabled { display: grid; gap: 1.5rem; align-items: start; }
.browser.select-enabled .details-panel { display: block; }

@media (min-width: 860px) {
  .browser.select-enabled { grid-template-columns: minmax(0, 1fr) 20rem; }
  .browser.select-enabled .details-panel { position: sticky; top: 1.5rem; }
}

.details-panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.15rem;
}
.details-empty { margin: 0; color: var(--muted); font-size: 0.9rem; }

.details-body h3 {
  margin: 0 0 0.75rem;
  font-size: 1rem;
  line-height: 1.4;
  word-break: break-word;
}
.details-description {
  margin: 0 0 0.9rem;
  color: var(--muted);
  font-size: 0.88rem;
  word-break: break-word;
}

.details-body dl {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.3rem 0.75rem;
  margin: 0 0 1.1rem;
  font-size: 0.85rem;
}
.details-body dt { color: var(--muted); }
.details-body dd { margin: 0; word-break: break-word; }

.details-open {
  display: block;
  padding: 0.6rem 1rem;
  text-align: center;
  font-weight: 600;
  color: #fff;
  background: var(--accent);
  border-radius: 6px;
  text-decoration: none;
}
.details-open:hover { filter: brightness(1.1); }

/* Selection. Rows keep their normal cursor because a single click no longer
   navigates — it selects. */
.select-enabled .selectable .row { cursor: default; }

/* Selection outlines the card: the <li> for admin rows, the anchor for public
   ones, where the anchor is the card. */
.select-enabled li.selected > .row,
.select-enabled li.with-tools.selected {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}

/* Connect screen — designed to be projected on the board and read from the
   back of the room, so everything here is deliberately oversized. */
.connect { text-align: center; padding: 1rem 0 3rem; }
.connect-hint { color: var(--muted); margin-bottom: 2rem; }

.connect-main {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 1.25rem;
  padding: 2rem;
  background: #fff; /* QR codes need a light quiet zone in either theme */
  border-radius: var(--radius);
  border: 1px solid var(--border);
}

.qr { width: min(60vw, 340px); height: auto; image-rendering: pixelated; }
.qr-small { width: 64px; height: 64px; image-rendering: pixelated; }

.connect-url {
  margin: 0;
  font-size: clamp(1.4rem, 4vw, 2.4rem);
  font-weight: 700;
  letter-spacing: 0.02em;
  color: #1c2024;
  word-break: break-all;
}

.connect-alt { margin: 2.5rem auto 0; max-width: 520px; text-align: left; }
.connect-alt summary { cursor: pointer; color: var(--accent); }
.connect-alt .row { gap: 1rem; }

/* Student upload feedback */
.success {
  background: #1f7a4d;
  color: #fff;
  padding: 0.6rem 0.85rem;
  border-radius: var(--radius);
}

/* Tells a student whether what they hand in will be seen by the class or
   only by the teacher. Worth being unmissable. */
.notice {
  margin: 0 0 1rem;
  padding: 0.55rem 0.75rem;
  border-left: 3px solid var(--accent);
  background: var(--bg);
  border-radius: 0 6px 6px 0;
  color: var(--muted);
  font-size: 0.9rem;
}

/* ---- Quizzes ------------------------------------------------------------

   Four surfaces, and only one of them is read by more than one person at a
   time. The student's paper has to work on an old laptop held at an angle, so
   it gets the large targets and the high contrast; the authoring screens can be
   denser, because one person reads them, sitting down, on purpose. */

/* ---- The editor --------------------------------------------------------- */

.quiz-editor textarea {
  /* A quiz is written as code even though it reads as prose: the alignment of
     the "- [x]" markers is what makes a mistake visible while typing. */
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.9rem;
  line-height: 1.5;
  min-height: 22rem;
}

.quiz-manifest ol { list-style: none; margin: 0; padding: 0; counter-reset: q; }
.quiz-manifest li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem 0.75rem;
  padding: 0.6rem 0.85rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  counter-increment: q;
}
.quiz-manifest li + li { margin-top: 0.4rem; }
.quiz-manifest li::before {
  content: counter(q) ".";
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  min-width: 1.6rem;
}

/* A poll and an open question are not errors, but each is something a teacher
   meant to do on purpose — so they are marked, not hidden. */
.quiz-manifest li.warn { border-left: 3px solid var(--accent); }

.q-heading { flex: 1 1 14rem; min-width: 0; }
.q-kind { color: var(--accent); font-size: 0.85rem; }
.q-detail { color: var(--muted); font-size: 0.85rem; }

.q-points {
  font-variant-numeric: tabular-nums;
  font-size: 0.85rem;
  padding: 0.1rem 0.45rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--muted);
  white-space: nowrap;
}
.q-tag { color: var(--muted); font-size: 0.85rem; }

.quiz-totals { margin-top: 0.75rem; font-weight: 600; }

.quiz-problems { border-left: 3px solid var(--error); padding-left: 0.85rem; }
.quiz-problems ul { margin: 0; padding-left: 1.1rem; }
.quiz-problems li { margin-bottom: 0.35rem; }
.quiz-problems .line {
  font-variant-numeric: tabular-nums;
  color: var(--error);
  font-weight: 600;
  margin-right: 0.4rem;
}

/* ---- Images: the empty slots, and what has been filled ------------------ */

.quiz-slots ul, .quiz-images ul { list-style: none; margin: 0; padding: 0; }

.quiz-slot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.85rem;
  padding: 0.7rem 0.85rem;
  background: var(--surface);
  /* Dashed, because the frame is empty and meant to be filled. */
  border: 1px dashed var(--border);
  border-radius: var(--radius);
}
.quiz-slot + .quiz-slot { margin-top: 0.5rem; }

/* The alt text leads. When a language model wrote the quiz this is the only
   description of the picture that will ever exist, and it is what the teacher
   reads to know what to go and find. */
.slot-alt { flex: 1 1 12rem; font-weight: 600; }
.slot-ref { font-size: 0.85rem; color: var(--muted); word-break: break-all; }
.slot-where { font-size: 0.85rem; color: var(--muted); white-space: nowrap; }
.quiz-slot form { display: flex; align-items: center; gap: 0.5rem; flex: 1 1 100%; }
.quiz-slot input[type="file"] { margin-top: 0; flex: 1 1 auto; width: auto; }

.quiz-images ul {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  gap: 0.75rem;
}
.quiz-image {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 0.6rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.quiz-image img {
  width: 100%;
  height: 7rem;
  object-fit: contain;
  background: var(--bg);
  border-radius: 6px;
}
.image-meta { display: flex; flex-wrap: wrap; gap: 0.35rem; font-size: 0.8rem; }
.image-meta code { word-break: break-all; }
.image-meta small { color: var(--muted); }

/* ---- The paper, as the teacher proof-reads it --------------------------- */

.paper-questions { list-style: none; margin: 0; padding: 0; counter-reset: pq; }
.paper-question {
  padding: 1rem 1.1rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  counter-increment: pq;
}
.paper-question + .paper-question { margin-top: 0.75rem; }
.paper-question .q-text::before {
  content: counter(pq) ". ";
  color: var(--muted);
  font-weight: 600;
}

.q-text { font-weight: 600; }
.q-text > p, .q-prompt > p { margin: 0 0 0.5rem; }
.q-prompt { color: var(--muted); }

/* A picture in a question is the reason stage 2 exists; it must not overflow
   the card on a narrow screen. */
.q-text img, .q-prompt img, .take-intro img, .quiz-intro img {
  max-width: 100%;
  height: auto;
  border-radius: 6px;
}

.q-meta { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: baseline; margin: 0.5rem 0; }

.q-options, .q-pairs, .q-answers { list-style: none; margin: 0.5rem 0 0; padding: 0; }
.q-options li, .q-pairs li, .q-answers li { margin-bottom: 0.3rem; }
.q-options .tick { margin-right: 0.4rem; }
.q-answers code { font-size: 0.9rem; }
.spare { color: var(--muted); }

/* ---- Sitting a test ----------------------------------------------------- */

.take-join, .take-closed { max-width: 26rem; }
.take-join input[type="text"] { font-size: 1.15rem; padding: 0.7rem; }
.take-join button[type="submit"] { width: 100%; padding: 0.8rem; font-size: 1.05rem; }

/* The header follows the paper down. The clock is the one thing a student
   needs at every moment, and ten questions is several screens. */
.take-header {
  position: sticky;
  top: 0;
  z-index: 5;
  margin: 0 -1.25rem 1rem;
  padding: 0.75rem 1.25rem;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}
.take-header h1 { margin: 0; font-size: 1.25rem; }
.take-who { margin: 0.15rem 0 0; color: var(--muted); font-size: 0.9rem; }

.take-clock {
  margin: 0.35rem 0 0;
  font-variant-numeric: tabular-nums;
  font-size: 1.1rem;
  font-weight: 600;
}
/* The last minute. Marked rather than announced: a dialog would take the
   keyboard away from someone still typing. */
.take-clock.urgent { color: var(--error); }

/* Whether the last answer reached the server. A student who has lost the
   network has to find out during the lesson, not after it. */
.take-status { margin: 0.25rem 0 0; min-height: 1.2rem; font-size: 0.85rem; color: var(--muted); }
.take-status.ok { color: #1f7a4d; }
.take-status.failed { color: var(--error); font-weight: 600; }

.take-questions, .take-review { list-style: none; margin: 0; padding: 0; counter-reset: tq; }
.take-question {
  padding: 1rem 1.1rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  counter-increment: tq;
}
.take-question + .take-question { margin-top: 0.85rem; }
.take-question .q-text::before {
  content: counter(tq) ". ";
  color: var(--muted);
  font-weight: 600;
}

.q-hint { margin: 0.35rem 0 0.75rem; color: var(--muted); font-size: 0.85rem; }

/* An option is a target, not a line of text. The whole row is clickable and
   tall enough to hit on a trackpad in a hurry. */
.take-question .q-options li { margin-bottom: 0.4rem; }
.take-question .q-options label {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  margin: 0;
  padding: 0.6rem 0.75rem;
  color: var(--text);
  font-size: 1rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  cursor: pointer;
}
.take-question .q-options label:hover { border-color: var(--accent); }
.take-question .q-options input { width: auto; margin: 0.15rem 0 0; flex: none; }

/* A ticked row stands out. :has() is an enhancement — where it is missing the
   native control still shows the state, which is the part that matters. */
.take-question .q-options label:has(input:checked) {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}

.take-question .q-pairs li { margin-bottom: 0.5rem; }
.take-question .q-pairs label {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
  color: var(--text);
}
.pair-left { flex: 1 1 9rem; font-weight: 600; }
.take-question .q-pairs select { flex: 1 1 11rem; width: auto; margin-top: 0; }

.q-answer { font-size: 1rem; }
input.q-answer { max-width: 22rem; }

.take-actions { margin: 1.5rem 0; }
.take-actions button[type="submit"] { padding: 0.8rem 1.6rem; font-size: 1.05rem; }

/* ---- Handed in ---------------------------------------------------------- */

.take-mark { font-size: 1.2rem; margin: 0.5rem 0 1rem; }
.take-mark strong { font-size: 1.6rem; color: var(--accent); }
.take-mark small { color: var(--muted); margin-left: 0.4rem; }

/* The verdict is carried by a word as well as by a colour, so it still reads
   for a student who cannot tell the two greens from the red. */
.take-review .take-question { border-left-width: 4px; }
.take-review .take-question.right { border-left-color: #1f7a4d; }
.take-review .take-question.wrong { border-left-color: var(--error); }
.take-review .take-question.by-hand { border-left-color: var(--accent); }

.q-verdict { margin: 0.5rem 0; font-weight: 600; }
.take-question.right .q-verdict { color: #1f7a4d; }
.take-question.wrong .q-verdict { color: var(--error); }
.take-question.by-hand .q-verdict { color: var(--accent); }
.q-verdict .q-points { font-weight: 400; margin-left: 0.4rem; }

.take-review .q-options li.chosen { font-weight: 600; }
.take-review .q-options li.correct { color: #1f7a4d; }
.key { color: #1f7a4d; font-weight: 700; margin-left: 0.35rem; }
.right-answer { color: var(--muted); margin-left: 0.5rem; }
.q-written { margin: 0.5rem 0 0; }

/* ---- Watching a sitting ------------------------------------------------- */

.sitting-state { margin: 0 0 1rem; font-weight: 600; }
.sitting-state .running { color: #1f7a4d; }
.sitting-state .closed { color: var(--muted); }

/* Read out to a room, so it is set large and selects in one click. */
.sitting-link { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.sitting-link code {
  font-size: 1.3rem;
  font-weight: 600;
  padding: 0.2rem 0.6rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 6px;
  user-select: all;
}

.sitting-counts { margin: 1.5rem 0 0.75rem; font-size: 1.05rem; }
.sitting-counts strong { font-size: 1.3rem; font-variant-numeric: tabular-nums; }
.sitting-counts small { color: var(--muted); }

/* Glanced at from across the room while a lesson runs, which is why the rows
   are large and the state is a word rather than an icon. */
.sitting-rows { width: 100%; border-collapse: collapse; }
.sitting-rows th, .sitting-rows td {
  text-align: left;
  padding: 0.55rem 0.6rem;
  border-bottom: 1px solid var(--border);
}
.sitting-rows th { color: var(--muted); font-size: 0.85rem; font-weight: 600; }
.sitting-rows td { font-size: 1rem; }
.sitting-rows td:last-child { font-variant-numeric: tabular-nums; white-space: nowrap; }
.sitting-rows tr.done td { color: #1f7a4d; }
.sitting-rows tr.late td { color: var(--error); }
.sitting-rows small { color: var(--muted); font-variant-numeric: tabular-nums; }

/* ---- Narrow screens ----------------------------------------------------- */

@media (max-width: 560px) {
  .take-header { margin: 0 -1.25rem 0.75rem; }
  .take-question { padding: 0.85rem; }
  .take-question .q-options label { padding: 0.7rem; }
  .quiz-slot form { flex-direction: column; align-items: stretch; }
  .sitting-link code { font-size: 1.1rem; }
}

/* ---- Three classes the templates have always used and the stylesheet never
        defined -------------------------------------------------------------

   Found by checking every class in the quiz templates against this file: .hint
   appears in seventeen places across eight templates, .actions in thirteen, and
   both rendered as ordinary body text. They predate quizzes; they are fixed
   here because that is where the gap was noticed. */

/* Guidance under a field or a heading. Quieter than the thing it explains,
   which is the entire job. */
.hint { margin: 0.3rem 0 1rem; color: var(--muted); font-size: 0.85rem; }

/* A row of buttons and links closing a form or a page. */
.actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  margin: 1.25rem 0 0;
}

/* A notice that reports a restriction rather than a state — a quiz that can no
   longer be edited, say. Without this it was indistinguishable from an
   ordinary notice, which is the one thing it must not be. */
.notice.warning {
  border-left-color: #b5820a;
  color: var(--text);
}
.notice.error { border-left-color: var(--error); background: var(--bg); color: var(--text); }
.notice.success { border-left-color: #1f7a4d; background: var(--bg); color: var(--text); }

/* The format reference, rendered in-app from the same Markdown the repository
   ships. It is a document to read rather than a page to scan, and 900px is
   wide enough that the eye loses the line between one row and the next. */
.quiz-help { max-width: 42rem; }
.quiz-help table { width: 100%; border-collapse: collapse; margin: 1rem 0; }
.quiz-help th, .quiz-help td {
  text-align: left;
  padding: 0.4rem 0.6rem;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}
.quiz-help th { color: var(--muted); font-size: 0.85rem; }
.quiz-help pre {
  padding: 0.85rem;
  overflow-x: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

/* .take, .take-done, .quiz-paper and .sitting carry no rules of their own.
   They are structural hooks whose children are styled; giving them empty
   declarations to satisfy a coverage check would be worse than the gap. */

/* Shown when a browser will not render a PDF inline. It is an aside about the
   viewer, not part of the document, and should read as one. */
.preview-fallback { color: var(--muted); font-size: 0.9rem; }

/* ---- Marking by hand ---------------------------------------------------- */

.marking-questions { list-style: none; margin: 0; padding: 0; counter-reset: mq; }
.marking-question {
  padding: 0.85rem 1rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  counter-increment: mq;
}
.marking-question + .marking-question { margin-top: 0.6rem; }
.marking-question .q-text::before {
  content: counter(mq) ". ";
  color: var(--muted);
  font-weight: 600;
}
.marking-question .q-text { margin: 0 0 0.4rem; }

/* The questions that need a person are the point of the screen; the rest are
   context, and should read as context. */
.marking-question.by-hand { border-left: 4px solid var(--accent); }
.marking-question:not(.by-hand) { opacity: 0.75; }

.q-answer-given { margin: 0.35rem 0; color: var(--muted); font-size: 0.9rem; }
/* What the student actually wrote, set apart from the label in front of it —
   it is the thing being read, and several sentences of it. */
.q-answer-given .given {
  display: block;
  margin-top: 0.25rem;
  padding: 0.5rem 0.65rem;
  color: var(--text);
  background: var(--bg);
  border-radius: 6px;
  white-space: pre-wrap;
}

.mark-input { display: flex; align-items: center; gap: 0.5rem; margin: 0.6rem 0 0; }
.mark-input small { color: var(--muted); }
.mark-input input {
  width: 5rem;
  margin-top: 0;
  font-size: 1.05rem;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* ---- The breakdown ------------------------------------------------------

   Read the afternoon of the lesson, to decide what tomorrow starts with. The
   counts carry it, so they are the only thing set large. */

.breakdown-summary { margin: 0 0 1rem; font-size: 1.05rem; }
.breakdown-summary strong { font-size: 1.3rem; font-variant-numeric: tabular-nums; }
.breakdown-summary small { color: var(--muted); }

.breakdown-questions { list-style: none; margin: 0; padding: 0; counter-reset: bq; }
.breakdown-question {
  padding: 0.85rem 1rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  counter-increment: bq;
}
.breakdown-question + .breakdown-question { margin-top: 0.6rem; }
.breakdown-question .q-text::before {
  content: counter(bq) ". ";
  color: var(--muted);
  font-weight: 600;
}
.breakdown-question .q-text { margin: 0 0 0.35rem; }

.counts, .tally { list-style: none; display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0.5rem 0 0; padding: 0; }
.counts li, .tally li {
  padding: 0.3rem 0.6rem;
  font-size: 0.9rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 999px;
}
.counts strong, .tally-count { font-variant-numeric: tabular-nums; }

/* Coloured by what the number means, but every one of them is also labelled in
   words — the colour is a second reading, never the only one. */
.counts .full { border-color: #1f7a4d; color: #1f7a4d; }
.counts .partial { border-color: var(--accent); color: var(--accent); }
.counts .nothing { border-color: var(--error); color: var(--error); }
.counts .unanswered { color: var(--muted); }
.counts .waiting { border-style: dashed; color: var(--muted); }

.tally li { display: flex; gap: 0.5rem; align-items: baseline; }
.tally-label { color: var(--text); }
.tally-count { font-weight: 600; }

/* Says what a screen is, under the name of what it is about. A sitting, its
   breakdown and its marking sheets otherwise share one heading. */
.page-kind { margin: -0.5rem 0 0.5rem; color: var(--accent); font-weight: 600; }

/* ---- Sheets to hand back ------------------------------------------------ */

.sheet {
  padding: 1.25rem;
  margin-bottom: 1rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.sheet-head { border-bottom: 1px solid var(--border); padding-bottom: 0.6rem; margin-bottom: 0.85rem; }
.sheet-name { margin: 0; font-size: 1.3rem; }
.sheet-quiz { margin: 0.15rem 0; color: var(--muted); }
.sheet-mark { margin: 0.35rem 0 0; font-size: 1.1rem; }
.sheet-mark strong { font-size: 1.5rem; }
.sheet-mark small { color: var(--muted); }
.sheet-provisional { margin: 0.35rem 0 0; color: var(--error); font-size: 0.9rem; }

.sheet-questions { list-style: none; margin: 0; padding: 0; counter-reset: sq; }
.sheet-question {
  padding: 0.5rem 0 0.5rem 0.85rem;
  border-left: 3px solid var(--border);
  counter-increment: sq;
}
.sheet-question + .sheet-question { margin-top: 0.5rem; }
.sheet-question .q-text { margin: 0 0 0.25rem; }
.sheet-question .q-text::before {
  content: counter(sq) ". ";
  color: var(--muted);
  font-weight: 600;
}
.sheet-question.right { border-left-color: #1f7a4d; }
.sheet-question.partial { border-left-color: var(--accent); }
.sheet-question.unread { border-left-style: dashed; }

.sheet-given, .sheet-expected, .sheet-points { margin: 0.15rem 0; font-size: 0.92rem; }
.sheet-expected { color: #1f7a4d; }
.sheet-points { color: var(--muted); font-variant-numeric: tabular-nums; }
.sheet-foot { margin-top: 0.85rem; color: var(--muted); font-size: 0.8rem; }

.printbtn {
  padding: 0.6rem 1.2rem;
  font: inherit;
  font-weight: 600;
  color: #fff;
  background: var(--accent);
  border: 0;
  border-radius: 6px;
  cursor: pointer;
}

/* ---- On paper -----------------------------------------------------------

   The requirement is one Ctrl+P and fourteen pages, one student each, with no
   navigation and nothing dark. The last part is not a nicety: a teacher working
   in the dark theme would otherwise print fourteen pages of near-black, which
   is unreadable, and empties a school printer's toner doing it. */

@media print {
  /* Colours are stated outright rather than inherited, because the variables
     may be carrying the dark palette at the moment Print is pressed. */
  :root, :root[data-theme="dark"], :root[data-theme="light"] {
    --bg: #ffffff;
    --surface: #ffffff;
    --border: #999999;
    --text: #000000;
    --muted: #444444;
    --accent: #000000;
    --error: #000000;
  }

  body { background: #fff; color: #000; font-size: 11pt; }
  main { max-width: none; padding: 0; }

  /* Everything that is a control rather than content. */
  .topbar, .sheets-toolbar, .crumbs, .actions, .printbtn { display: none !important; }

  .sheet {
    /* One student per page. break-after is the current property and
       page-break-after the one older engines still read; both are given
       because a wrong page break here means re-printing the whole class. */
    break-after: page;
    page-break-after: always;
    border: 0;
    border-radius: 0;
    padding: 0;
    margin: 0;
    background: #fff;
  }
  /* No trailing blank page after the last student. */
  .sheet:last-child { break-after: auto; page-break-after: auto; }

  /* A question must not be split across two pages: half an answer at the
     bottom of one sheet and half at the top of the next is worse than a
     shorter page. */
  .sheet-question { break-inside: avoid; page-break-inside: avoid; }
  .sheet-head { break-inside: avoid; page-break-inside: avoid; }

  .sheet-question.right { border-left-color: #000; }
  .sheet-question.partial { border-left-color: #666; }
  .sheet-expected { color: #000; font-weight: 600; }
  .sheet-provisional { color: #000; font-weight: 600; }
}

/* ---- Correcting a key, and setting a mark aside ------------------------- */

.regrade textarea {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.9rem;
  line-height: 1.5;
}

.regrade-preview { margin-top: 1.5rem; }
.regrade-rows { width: 100%; border-collapse: collapse; margin: 0.75rem 0; }
.regrade-rows th, .regrade-rows td {
  text-align: left;
  padding: 0.45rem 0.6rem;
  border-bottom: 1px solid var(--border);
  font-variant-numeric: tabular-nums;
}
.regrade-rows th { color: var(--muted); font-size: 0.85rem; }

/* Only the rows that move are worth the eye. A paper whose mark is unchanged
   is on the table so the teacher can see it was considered, not to be read. */
.regrade-rows tr.moved td { font-weight: 600; }
.regrade-rows tr.held td { color: var(--muted); font-style: italic; }
.regrade-rows .delta { color: var(--accent); margin-left: 0.35rem; font-weight: 400; }

.override-computed { font-size: 1.05rem; margin: 1rem 0; }
.override-computed strong { font-size: 1.4rem; }
.override-computed small { color: var(--muted); }
.override input[type="text"] { max-width: 22rem; }

/* A mark set by hand says so wherever it appears, or an override is a number
   with no history behind it. */
.by-hand-badge {
  margin-left: 0.35rem;
  padding: 0.05rem 0.4rem;
  font-size: 0.75rem;
  color: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 999px;
}

/* ---- Running the room --------------------------------------------------- */

/* A frozen paper. The overlay is a courtesy — the server refuses every write
   from a frozen student whatever this page is doing — so it is allowed to be
   simply an overlay, with nothing depending on it. */
.frozen-overlay {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 2rem;
  text-align: center;
  background: var(--bg);
}
.frozen-message { font-size: 1.4rem; font-weight: 600; margin: 0; }
.take.is-frozen { filter: blur(4px); pointer-events: none; user-select: none; }

/* Uncopiable question text. Friction, not prevention: an extension that
   answers questions reads the DOM and is untouched by this. It stops the
   student who would have highlighted a question and pasted it into a search
   box, which is a real and common thing, and it stops nothing else.

   The inputs stay selectable, or a student could not correct their own typing. */
.no-copy .q-text, .no-copy .q-prompt, .no-copy .q-options, .no-copy .q-pairs {
  user-select: none;
  -webkit-user-select: none;
}
.no-copy input, .no-copy textarea, .no-copy select { user-select: text; }

.state-badge {
  margin-left: 0.35rem;
  padding: 0.05rem 0.4rem;
  font-size: 0.75rem;
  border-radius: 999px;
  border: 1px solid currentColor;
}
.state-badge.frozen { color: var(--accent); }
.state-badge.voided { color: var(--error); }
.state-badge.away { color: var(--muted); }

/* Presence, on the invigilation screen.
   The dot is the fast read down a column of thirty names — but it is never the
   only one: a student who has dropped also carries the words, and the dot has
   a title, because "the grey one" is not a thing a teacher should have to see
   to do their job. Hollow rather than a second colour, so it still reads as
   absent on a projector that has given up on subtle greys. */
.dot {
  display: inline-block;
  width: 0.5rem;
  height: 0.5rem;
  margin-right: 0.35rem;
  border-radius: 50%;
  vertical-align: middle;
}
.dot-on { background: #1f7a4d; }
.dot-off { background: transparent; border: 1px solid var(--muted); }
.sitting-rows tr.voided td { opacity: 0.55; text-decoration: line-through; }
.sitting-rows tr.voided td:first-child { text-decoration: none; }

/* Signals are numbers to walk over about. Set quietly on purpose: nothing here
   is a verdict, and styling them like an alarm would invite treating them as
   one. */
.signals { display: flex; flex-wrap: wrap; gap: 0.4rem; font-size: 0.8rem; color: var(--muted); }
.signals .gaps {
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.05em;
  color: var(--text);
}

/* Starting a test, from the screen where the teacher is already standing. */
.quiz-sittings { margin: 2rem 0; }
.quiz-sittings form { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 0.75rem; }
.quiz-sittings form .hint { flex: 1 1 100%; margin: 0; }
.quiz-sittings label { margin: 0; }
.quiz-sittings input[type="number"] { width: 7rem; }

.sitting-history { list-style: none; margin: 0.75rem 0 0; padding: 0; }
.sitting-history li {
  display: flex;
  gap: 0.75rem;
  align-items: baseline;
  padding: 0.35rem 0;
  border-bottom: 1px solid var(--border);
  font-size: 0.9rem;
}
.sitting-history .running { color: #1f7a4d; font-weight: 600; }
.sitting-history .closed { color: var(--muted); }

/* The teacher's reason, beside the mark while marking and beside the question
   when the student reads it back. */
.mark-comment { display: block; margin: 0.5rem 0 0; }
.mark-comment input { margin-top: 0.2rem; }

.q-comment {
  margin: 0.4rem 0 0;
  padding: 0.4rem 0.6rem;
  border-left: 3px solid var(--accent);
  background: var(--bg);
  border-radius: 0 6px 6px 0;
  font-size: 0.9rem;
}




/* ---- A locked folder --------------------------------------------------- */

/* Narrow and centred: this page has one job and one field, and a student
   meeting it should not have to look for either. */
.unlock { max-width: 24rem; margin: 3rem auto; text-align: center; }
.unlock h1 { margin: 0 0 0.4rem; font-size: 1.4rem; }
.unlock .hint { margin: 0 0 1.25rem; }

.unlock-form { display: grid; gap: 0.5rem; text-align: left; }
.unlock-form label { font-weight: 600; }
.unlock-form .primary {
  margin-top: 0.4rem;
  padding: 0.7rem 1.2rem;
  font: inherit;
  font-weight: 600;
  color: #fff;
  background: var(--accent);
  border: 0;
  border-radius: 6px;
  cursor: pointer;
}
/* The promise the feature makes, said where it is being relied on. */
.unlock-note { margin-top: 1.5rem; font-size: 0.85rem; }

/* Moving to a new sitting of a quiz already sat. Set apart from the result
   above it, because it is a decision and not a continuation of reading. */
.take-resit {
  margin: 1.5rem 0;
  padding: 0.9rem 1.1rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.take-resit .hint { margin: 0 0 0.6rem; }

/* Shutting the folders again. Quiet and to one side: it is for the moment a
   laptop changes hands, not something to invite a click at other times. */
.lock-again { display: inline; margin-left: auto; }
.lock-again button {
  padding: 0.2rem 0.6rem;
  font: inherit;
  font-size: 0.85rem;
  color: var(--muted);
  background: none;
  border: 1px solid var(--border);
  border-radius: 999px;
  cursor: pointer;
}
.lock-again button:hover { color: var(--accent); border-color: var(--accent); }

/* A locked folder in a listing still shows its name — the name was never the
   secret — with enough of a mark that nobody wonders why it will not open. */
.folder-locked { color: var(--muted); font-size: 0.85rem; white-space: nowrap; }

/* ---- Archive and purge ------------------------------------------------- */

/* The only screen in the app that destroys work, so it is built to be read
   rather than clicked through: the archive first, then the names, then the
   tickbox. */
.purge { max-width: 46rem; }
.purge section { margin: 1.75rem 0; }
.purge h2 { margin: 0 0 0.25rem; font-size: 1.1rem; }

.purge-archive {
  padding: 1rem 1.1rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.purge-archive .btn { margin-top: 0.6rem; }

/* Named, not counted. "14 students" is a number to click past; a list of
   people is a decision. */
.purge-names {
  list-style: none;
  margin: 0.5rem 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}
.purge-names li {
  padding: 0.3rem 0.6rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 0.9rem;
}

.purge-what { border-left: 3px solid var(--error); padding-left: 1rem; }

.purge-form {
  margin-top: 1.5rem;
  padding: 1rem 1.1rem;
  border: 1px solid var(--error);
  border-radius: var(--radius);
}
.purge-tick { display: flex; align-items: flex-start; gap: 0.6rem; margin-bottom: 0.9rem; }
.purge-tick input { width: auto; margin: 0.2rem 0 0; flex: none; }

/* ---- The breakdown's three other arrangements -------------------------- */

/* "Повторити разом" is the answer to the question this page was opened with,
   so it reads as a conclusion rather than as another table. */
.revisit, .topics, .hardest { margin: 2rem 0; }
.revisit h2, .topics h2, .hardest h2 { margin: 0 0 0.25rem; font-size: 1.15rem; }

.revisit-list, .hardest-list { list-style: none; margin: 0.75rem 0 0; padding: 0; display: grid; gap: 0.6rem; }
.revisit-list li, .hardest-list li {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: baseline;
  gap: 0.3rem 0.75rem;
  padding: 0.7rem 0.9rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.revisit-list li { border-left: 3px solid var(--error); }
.revisit-no { color: var(--muted); font-size: 0.85rem; white-space: nowrap; }
.revisit-text { font-weight: 600; }
.revisit-count { color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
/* The popular mistake sits under the question it belongs to, spanning the row. */
.revisit-popular { grid-column: 1 / -1; color: var(--error); font-size: 0.9rem; }

.topic-list { list-style: none; margin: 0.75rem 0 0; padding: 0; display: grid; gap: 0.5rem; }
.topic-list li { display: grid; grid-template-columns: 10rem 1fr auto auto; align-items: center; gap: 0.75rem; }
.topic-tag { font-family: var(--mono, monospace); font-size: 0.9rem; color: var(--muted); }
.topic-bar { height: 0.9rem; background: var(--border); border-radius: 4px; overflow: hidden; }
.topic-fill { display: block; height: 100%; background: var(--accent); }
.topic-share { font-variant-numeric: tabular-nums; font-weight: 600; min-width: 3.5ch; text-align: right; }
.topic-count { color: var(--muted); font-size: 0.85rem; white-space: nowrap; }

/* The same list on a student's own result page, narrower and without the
   question count — one person does not need to be told a topic rests on
   three questions. */
.take-topics { margin: 1.5rem 0; }
.take-topics h2 { margin: 0 0 0.5rem; font-size: 1.05rem; }
.take-topics .topic-list li { grid-template-columns: 8rem 1fr auto; }

/* A question the class did not reach, said in the colour of a caution rather
   than of an error: nobody did anything wrong. */
.warning-text { color: var(--error); }

/* The correct option in a distractor tally, so the eye finds the mistakes by
   finding what is not marked. */
.tally li.right .tally-label { font-weight: 700; color: #1f7a4d; }

@media (max-width: 600px) {
  .revisit-list li, .hardest-list li { grid-template-columns: 1fr auto; }
  .revisit-no { grid-column: 1 / -1; }
  .topic-list li { grid-template-columns: 1fr auto; }
  .topic-bar { grid-column: 1 / -1; }
}

/* ---- Live mode ----------------------------------------------------------

   Two surfaces from one stylesheet. The board is projected and read from the
   back of a room, so everything on it is enormous and there is almost nothing
   of it. The student's page is a phone or a laptop at arm's length and carries
   the same information at ordinary size.

   Several of the classes below are created by live.js rather than by a
   template, which is why they are not in the template-class guard: the page is
   drawn from the stream, because deciding in markup what a student may see
   would put the answer key in the HTML during a question nobody has answered. */

.board, .live { max-width: 100%; }

.board-head, .live-head { margin-bottom: 1rem; }
.board-head h1 { margin: 0; font-size: 1.4rem; color: var(--muted); font-weight: 600; }
.board-progress {
  margin: 0.2rem 0 0;
  font-size: 1.2rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.board-status, .live-status { margin: 0.25rem 0 0; min-height: 1.2rem; font-size: 0.9rem; color: var(--muted); }
.board-status.failed, .live-status.failed { color: var(--error); font-weight: 600; }
.live-status.ok { color: #1f7a4d; }

/* ---- What the room reads ---- */

.board .live-question { font-size: clamp(1.8rem, 5vw, 3.5rem); line-height: 1.15; margin: 0 0 0.5rem; }
.board .live-prompt { font-size: clamp(1.1rem, 2.5vw, 1.8rem); color: var(--muted); }
.live .live-question { font-size: 1.35rem; margin: 0 0 0.5rem; }
.live .live-prompt { color: var(--muted); }

.live-big { font-size: clamp(1.5rem, 4vw, 2.5rem); margin: 0; }
.live-count { font-size: clamp(3rem, 12vw, 8rem); font-weight: 700; margin: 0; line-height: 1; }

/* The clock. On the board it is the largest thing on the screen after the
   question, because it is what the room is watching. */
.live-clock {
  font-size: clamp(2rem, 8vw, 5rem);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  margin: 0.5rem 0;
  line-height: 1;
}
.live .live-clock { font-size: 2rem; }
.live-clock.urgent { color: var(--error); }

.board .live-options { list-style: none; margin: 1rem 0 0; padding: 0; display: grid; gap: 0.6rem; }
.board .live-options li {
  padding: 0.7rem 1rem;
  font-size: clamp(1.1rem, 3vw, 2rem);
  background: var(--surface);
  border: 2px solid var(--border);
  border-radius: var(--radius);
}
@media (min-width: 700px) { .board .live-options { grid-template-columns: 1fr 1fr; } }

/* Something to read while the class arrives. Secondary to the join count on
   purpose — the teacher is watching the number, the room is reading this. */
.live-fact {
  max-width: 40ch;
  margin: 1.5rem auto 0;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
  font-size: clamp(1rem, 2.2vw, 1.6rem);
  line-height: 1.4;
  color: var(--muted);
  text-align: center;
}
.live .live-fact { font-size: 1rem; margin-top: 1.25rem; }
.live-fact.fresh { animation: fact-in 0.5s ease; }

@keyframes fact-in {
  from { opacity: 0; transform: translateY(0.4rem); }
  to   { opacity: 1; transform: none; }
}

/* A projector in a bright room and a phone at arm's length both settle down
   when the movement stops mattering. */
@media (prefers-reduced-motion: reduce) {
  .live-fact.fresh { animation: none; }
}

/* A count, never a list of names: "eleven of fourteen" tells a teacher when to
   move on, and "Олена has not answered" tells a room something about Олена. */
.live-answered {
  margin-top: 1rem;
  font-size: clamp(1.2rem, 3vw, 2rem);
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}

/* ---- Answering ---- */

.live-answer { display: grid; gap: 0.6rem; margin-top: 1rem; }
.live-option {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  margin: 0;
  padding: 0.85rem 1rem;
  font-size: 1.1rem;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
}
.live-option:has(input:checked) { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.live-option input { width: auto; margin: 0; flex: none; }
.live-pair { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; margin: 0; color: var(--text); }
.live-pair select { width: auto; flex: 1 1 10rem; margin-top: 0; }
.live-text { font-size: 1.1rem; }
.live-answer .primary {
  padding: 0.8rem 1.6rem;
  font: inherit;
  font-weight: 600;
  font-size: 1.05rem;
  color: #fff;
  background: var(--accent);
  border: 0;
  border-radius: 6px;
  cursor: pointer;
}
.live-accepted { color: #1f7a4d; font-weight: 600; margin: 0 0 0.5rem; }

/* ---- The reveal ---- */

.live-correct {
  font-size: clamp(1.2rem, 3vw, 2.2rem);
  font-weight: 600;
  color: #1f7a4d;
  margin: 0.5rem 0 1rem;
}

/* The tally is the reason to run a quiz this way: the class sees where
   everyone went, together, before anybody is told a mark. */
.live-tally { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.5rem; }
.live-tally li { display: flex; align-items: center; gap: 0.75rem; font-size: clamp(1rem, 2.5vw, 1.5rem); }
.live-tally .tally-label { flex: 0 0 8rem; }
.live-tally .tally-bar {
  height: 1.6rem;
  min-width: 2px;
  border-radius: 4px;
  background: var(--border);
  transition: width 0.4s ease;
}
.live-tally li.correct .tally-bar { background: #1f7a4d; }
.live-tally li.correct .tally-label { font-weight: 700; color: #1f7a4d; }
.live-tally .tally-count { font-variant-numeric: tabular-nums; color: var(--muted); }

.live-verdict { margin-top: 1rem; font-size: 1.3rem; font-weight: 700; }
.live-verdict.right { color: #1f7a4d; }
.live-verdict.wrong { color: var(--error); }

/* ---- The board between questions ---- */

.live-standings { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.5rem; }
.live-standings li {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  padding: 0.6rem 1rem;
  font-size: clamp(1rem, 2.5vw, 1.8rem);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

/* The place is a real number from the server, not a count of the rows on
   screen: a trimmed board runs 1..5 and then jumps to wherever the reader
   actually is. */
.standing-place {
  min-width: 2ch;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* The jump, drawn rather than closed up. */
.live-standings li.gap { margin-top: 1.2rem; position: relative; }
.live-standings li.gap::before {
  content: "···";
  position: absolute;
  top: -1.15rem;
  left: 1rem;
  color: var(--muted);
  letter-spacing: 0.2em;
}
.live-standings li.me { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.standing-name { flex: 1; }
.standing-streak { font-size: 0.7em; color: var(--accent); }
.standing-points { font-weight: 700; font-variant-numeric: tabular-nums; }

/* ---- Own pace ---- */

/* The student's own "next". Given room, because it is the only control on the
   page and a thumb on a laptop trackpad should not have to aim. */
.live-advance { margin-top: 1.25rem; }
.live-advance .primary {
  padding: 0.8rem 2rem;
  font: inherit;
  font-weight: 600;
  font-size: 1.05rem;
  color: #fff;
  background: var(--accent);
  border: 0;
  border-radius: 6px;
  cursor: pointer;
}
.live-advance .primary:disabled { opacity: 0.5; cursor: default; }

/* The board while a class works separately: two numbers a teacher can read
   across a room and decide from. */
.live-progress { display: flex; gap: 2rem; margin-bottom: 1.5rem; }
.live-tile { display: flex; flex-direction: column; }
.tile-value {
  font-size: clamp(2.5rem, 9vw, 6rem);
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.tile-label { font-size: clamp(0.9rem, 2vw, 1.3rem); color: var(--muted); }

.board-mode { color: var(--muted); font-size: 0.95rem; }

/* ---- The teacher's controls ---- */

.board-controls {
  position: sticky;
  bottom: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  margin-top: 2rem;
  padding: 0.85rem 0;
  background: var(--bg);
  border-top: 1px solid var(--border);
}
.board-controls form { margin: 0; }

/* One button means "next", whatever next is, so a teacher holding a clicker in
   front of a class is not choosing between four verbs. */
.board-controls .big { padding: 0.9rem 2.5rem; font-size: 1.15rem; }
.board-controls .ghost, .btn.ghost {
  padding: 0.6rem 1rem;
  font: inherit;
  color: var(--muted);
  background: none;
  border: 1px solid var(--border);
  border-radius: 6px;
  cursor: pointer;
  text-decoration: none;
}
.board-controls .ghost:hover, .btn.ghost:hover { border-color: var(--accent); color: var(--accent); }
.board-controls .primary {
  font-weight: 600;
  color: #fff;
  background: var(--accent);
  border: 0;
  border-radius: 6px;
  cursor: pointer;
}

/* Games — the noughts-and-crosses workbench. See docs/games.md.

   Selectors are compounded (.game-board .game-square) rather than single
   classes because button[type="submit"] is styled globally as a filled accent
   button, and an attribute selector outranks a lone class. A board of nine
   accent-blue rectangles is what happens without this. */
.game { max-width: 32rem; }

.game-levels {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin: 0 0 1.25rem;
}
.game-levels-label { color: var(--muted); }
.game-levels .game-level {
  padding: 0.4rem 0.9rem;
  font-weight: 500;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
}
.game-levels .game-level:hover { border-color: var(--accent); filter: none; }
.game-levels .game-current {
  border-color: var(--accent);
  color: var(--accent);
  font-weight: 700;
}

.game-rules { color: var(--muted); margin: 0 0 1rem; }

.game-board {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.5rem;
  width: min(20rem, 100%);
  margin: 0 0 1rem;
}
.game-board .game-square {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1;
  padding: 0;
  font-size: clamp(2rem, 11vw, 3rem);
  font-weight: 700;
  line-height: 1;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.game-board .game-square:hover { border-color: var(--accent); filter: none; }
.game-board .game-taken { cursor: default; }
.game-board .game-won { border-color: var(--accent); color: var(--accent); }

.game-status { font-weight: 600; margin: 0 0 0.35rem; }
.game-final { color: var(--accent); }
.game-score { color: var(--muted); margin: 0 0 0.75rem; }
.game-score small { margin-left: 0.5rem; opacity: 0.8; }

.game-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  list-style: none;
  padding: 0;
  margin: 0 0 1.5rem;
}
.game-chip {
  padding: 0.15rem 0.6rem;
  font-size: 0.85rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--muted);
}
.game-win { border-color: var(--accent); color: var(--accent); }
.game-loss { border-color: var(--error); color: var(--error); }
.game-draw { color: var(--muted); }

/* Which glyph the student holds this game. Sides are drawn per game, so this
   is not decoration — see game.go. */
.game-side { margin: 0 0 0.75rem; color: var(--muted); }
.game-glyph {
  display: inline-block;
  min-width: 1.4em;
  padding: 0 0.3em;
  font-size: 1.15rem;
  text-align: center;
  color: var(--accent);
  border: 1px solid var(--border);
  border-radius: 6px;
}

/* Binary lights. Bulbs are submit buttons, so the selectors are compounded for
   the same reason the board's are: button[type="submit"] outranks a lone class. */
.bits-target { margin: 0 0 0.75rem; color: var(--muted); }
.bits-number { font-size: 1.6rem; color: var(--text); }

.bits-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0 0 1rem;
}
.bits-row .bits-bulb {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.15rem;
  min-width: 3rem;
  padding: 0.5rem 0.3rem;
  color: var(--muted);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.bits-row .bits-bulb:hover { border-color: var(--accent); filter: none; }
.bits-row .bits-on {
  color: var(--accent);
  border-color: var(--accent);
  font-weight: 700;
}
.bits-lamp { font-size: 1.5rem; line-height: 1; }
.bits-weight { font-size: 0.75rem; opacity: 0.85; }

.bits-budget { margin: 0 0 0.5rem; color: var(--muted); }
.bits-budget small { margin-left: 0.4rem; opacity: 0.8; }

.game-other { margin: 2rem 0 0; font-size: 0.9rem; }

/* Hex colours. The swatches carry their value as an inline style, which is the
   one place in this stylesheet that cannot say what a colour will be. */
.hex-ask { margin: 0 0 1rem; }
.hex-question { margin: 0 0 0.6rem; font-weight: 600; }
.hex-code {
  margin: 0;
  font-family: ui-monospace, "Cascadia Code", Consolas, monospace;
  font-size: 1.5rem;
  letter-spacing: 0.04em;
}
.hex-subject {
  width: min(14rem, 60vw);
  height: 5rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.hex-options {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0 0 1rem;
}
.hex-options .hex-option {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  min-width: 6rem;
  padding: 0.6rem;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.hex-options .hex-option:hover { border-color: var(--accent); filter: none; }
.hex-letter { font-size: 0.8rem; color: var(--muted); }
.hex-swatch {
  display: block;
  width: 4.5rem;
  height: 2.5rem;
  border: 1px solid var(--border);
  border-radius: 4px;
}
.hex-label {
  font-family: ui-monospace, "Cascadia Code", Consolas, monospace;
  font-size: 1.05rem;
}

/* Nim. Each counter is a button that takes it and everything to its right, so a
   row reads as "leave it this long" rather than needing a number typed in. */
.nim-took { margin: 0 0 0.75rem; color: var(--muted); }

.nim-rows { margin: 0 0 1rem; }
.nim-row {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  margin-bottom: 0.5rem;
}
.nim-number {
  min-width: 1.5rem;
  color: var(--muted);
  font-size: 0.85rem;
}
.nim-cleared { color: var(--muted); }
.nim-rows .nim-counter {
  padding: 0.15rem 0.35rem;
  line-height: 1;
  color: var(--accent);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
}
.nim-rows .nim-counter:hover { border-color: var(--accent); filter: none; }
.nim-stone { font-size: 1.3rem; }
