/*
| No build step: plain CSS so the app deploys to any PHP host as-is.
|
| One stylesheet serves the three interface languages. Use logical properties
| (margin-inline-start, inset-inline-end, text-align: start) and never
| left/right, so right-to-left pages need no overrides.
|
| Breakpoints: 600px (phones), 860px (two-column layouts collapse).
|
| Colours are written here once, for the light theme. The dark theme is
| public/css/dark.css: the same tokens with other values, and nothing else.
| A rule never names a colour itself, so it needs no dark counterpart.
| The brand tokens are the built-in look: a teacher who chooses a colour of her
| own (Settings) replaces them with a rule in the page (App\Support\Branding).
| Web fonts are self-hosted and declared in fonts.css; see public/fonts/README.md.
*/

/* ---------- Tokens ---------- */

:root {
    /* Form controls and scrollbars drawn by the browser follow the theme too. */
    color-scheme: light;

    /* Surfaces and text */
    --ground: #f3f5f4;
    --surface: #ffffff;
    --surface-subtle: #f6f8f7;
    --emphasis: #15232b;
    --on-emphasis: #ffffff;
    --track: #e3e9e7;
    --scrim: rgba(21, 35, 43, .55);
    --ink: #15232b;
    --ink-soft: #33444d;
    --ink-faint: #44535b;
    --muted: #52626b;

    /* Lines */
    --line: #d9e0de;
    --line-soft: #e6ebe9;
    --control: #8a9995;
    --control-soft: #c3cdca;

    /* Brand */
    --primary: #0d5c63;
    --primary-strong: #094950;
    --primary-soft: #e2f0ef;
    --on-primary: #ffffff;
    --focus: #6fb9bf;
    --panel: #0d5c63;
    --on-panel: #ffffff;
    --on-panel-soft: #d3e7e6;
    /* What an uploaded logo stands on: logos are drawn for a light page. */
    --logo-ground: #ffffff;
    /* What a clip is shown on, and the sign over it before it plays: a film is black in either theme. */
    --video-ground: #000000;
    --on-video: #ffffff;

    /* States: every pair is text on its soft background, at least 4.5:1 */
    --good: #1a6b45;
    --good-soft: #e2f3e9;
    --warn: #7a4e00;
    --warn-soft: #fff0cc;
    --bad: #a3221b;
    --bad-soft: #fce8e6;
    --bad-faint: #fcefed;
    --bad-strong: #7d1a14;
    --bad-line: #e9b8b3;
    --neutral-soft: #eef2f1;
    /* A student's own highlight on the exam's text, and the line under one that carries a note. */
    --highlight: #ffe98a;
    --highlight-line: #8a6400;
    /* The corner of a question flagged to come back to. */
    --flag: #c27803;
    --on-state: #ffffff;
    /* The exam screen (decision 54). Where the student is now: one warm colour beside the brand's, which says
       "answered". What a chosen tab or a word to carry stands on. The ground under the question being answered,
       barely there, and under words to choose from. The keyboard's mark: a soft light, never a hard line. */
    --accent: #c9761c;
    --accent-soft: color-mix(in srgb, var(--accent) 15%, var(--surface));
    --accent-strong: color-mix(in srgb, var(--accent) 60%, var(--ink));
    --plate: #ffffff;
    --wash: color-mix(in srgb, var(--ink) 3.5%, var(--surface));
    --group: color-mix(in srgb, var(--neutral-soft) 75%, var(--surface));
    --halo: 0 0 0 4px color-mix(in srgb, var(--focus) 50%, transparent);

    /* Shape */
    --radius-control: 10px;
    --radius-card: 14px;
    --radius-pill: 99px;
    --shadow-raised: 0 1px 2px rgba(21, 35, 43, .15);
    --shadow-dialog: 0 24px 64px rgba(21, 35, 43, .3);
    --shadow-bar: 0 -6px 20px rgba(21, 35, 43, .08);

    /* Sizes */
    --control-height: 48px;
    --touch: 44px;
    /* A gap in a text: a field the height of a line of writing. In rem, so that it follows the exam's text size. */
    --gap-height: 2.125rem;
    --gutter: 24px;
    --shell: 1200px;

    /* Type */
    --font-ui: "Instrument Sans", "Segoe UI", system-ui, -apple-system, sans-serif;
    --font-fa: "Vazirmatn", "Segoe UI", Tahoma, sans-serif;
    --font-read: "Source Serif 4", Georgia, "Times New Roman", serif;
    --font-code: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace;

    /* Motion: short and soft. Everything stops under prefers-reduced-motion. */
    --motion-fast: 140ms;
    --motion-base: 220ms;
    --motion-enter: 420ms;
    --motion-page: 260ms;
    --ease: cubic-bezier(.4, 0, .2, 1);
    --ease-out: cubic-bezier(.16, 1, .3, 1);
    /* The exam screen: a plate that glides to its new place, softly, and one that arrives with the least spring. */
    --motion-glide: 500ms;
    --ease-soft: cubic-bezier(.22, 1, .36, 1);
    --ease-spring: cubic-bezier(.28, 1.1, .36, 1);
    /* For words and figures that come into their place: it slows without the long last stretch of the soft curve, on
       which writing is seen to creep its last pixel for a third of a second after it looked settled. */
    --ease-settle: cubic-bezier(.3, .7, .4, 1);
    --shadow-lift: 0 6px 18px rgba(21, 35, 43, .08);
    --shadow-pop: 0 12px 32px rgba(21, 35, 43, .16), 0 2px 6px rgba(21, 35, 43, .08);

    /* The crayons of a picture to colour (decision 72): the colours of a box of them, the same on a light page and a
       dark one, as the picture they colour is. The line round a thing still to colour, and the light behind it. */
    --crayon-red: #e53935;
    --crayon-orange: #fb8c00;
    --crayon-yellow: #fdd835;
    --crayon-green: #43a047;
    --crayon-blue: #1e88e5;
    --crayon-purple: #8e24aa;
    --crayon-pink: #f06292;
    --crayon-brown: #8d6e63;
    --crayon-grey: #9e9e9e;
    --crayon-black: #212121;
    --paint-edge: #33444d;
    --paint-halo: #ffffff;

    /* An answer sheet is paper (docs/features/answer-sheets.md): white with black ink on a light page and a dark one,
       on the screen as it will be printed. Its lines are grey enough to write on and dark enough to be seen. */
    --sheet-ground: #ffffff;
    --sheet-ink: #111618;
    --sheet-line: #5f6b67;

    /* A book's cover (decision 116): the colour of each kind of book and the shape drawn on it, white letters over
       them, the shade of its spine. A book is a thing: it looks the same on a light page and a dark one. */
    --kind-students_book: #0d5c63;
    --kind-students_book-shape: #e9a23b;
    --kind-workbook: #a65a14;
    --kind-workbook-shape: #0d5c63;
    --kind-teachers_book: #2c3f4a;
    --kind-teachers_book-shape: #e9a23b;
    --kind-guide: #1a6b45;
    --kind-guide-shape: #f3d27a;
    --kind-tests: #8c3d4f;
    --kind-tests-shape: #f3d27a;
    --kind-resources: #6a4c93;
    --kind-resources-shape: #86d6dc;
    --kind-method: #2f4b8a;
    --kind-method-shape: #86d6dc;
    --kind-other: #3d5866;
    --kind-other-shape: #e9a23b;
    --on-cover: #ffffff;
    --cover-shade: rgba(0, 0, 0, .2);
    --cover-glow: rgba(255, 255, 255, .16);

    /* Pictures inside the stylesheet cannot read a token, so the whole picture is one. */
    --select-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2352626b' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
}

/* ---------- Base ---------- */

*, *::before, *::after { box-sizing: border-box; }

/* The scrollbar's space is kept even on short pages, so pages do not shift sideways. */
html { -webkit-text-size-adjust: 100%; scrollbar-gutter: stable; }

body {
    margin: 0;
    background: var(--ground);
    color: var(--ink);
    font: 16px/1.55 var(--font-ui);
}

/* A student may read the exam in larger text (the button in the exam's bar; exam.js remembers it). */
html[data-text-size="2"] { font-size: 112.5%; }
html[data-text-size="2"] body { font-size: 18px; }
html[data-text-size="3"] { font-size: 125%; }
html[data-text-size="3"] body { font-size: 20px; }

/* Persian needs its own typeface and more room between lines. */
html[lang="fa"] body, [lang="fa"] { font-family: var(--font-fa); }
html[lang="fa"] body { line-height: 1.8; }

h1, h2, h3, h4 { line-height: 1.25; margin: 0 0 .5em; }
h1 { font-size: 2rem; letter-spacing: -.01em; }
h2 { font-size: 1.25rem; }
h3 { font-size: 1.0625rem; }
h4 { font-size: .9375rem; }
html[lang="fa"] h1 { letter-spacing: 0; }
p { margin: 0 0 1em; }
a { color: var(--primary); }
a:hover { color: var(--primary-strong); }
img { max-width: 100%; height: auto; }
[hidden] { display: none !important; }

:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
/* After a page change the keyboard's place is moved to the page's content, so that the next Tab starts there
   (ui.js). The content itself is not something to act on, and a ring around the whole of it reads as a stray
   line under the page. */
main[tabindex="-1"]:focus { outline: none; }

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after { animation: none !important; transition: none !important; scroll-behavior: auto !important; }
}

.visually-hidden {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap;
}

.icon {
    width: 1.125em; height: 1.125em; flex: none;
    fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round;
}
.icon.solid { fill: currentColor; stroke: none; }
/* Arrows and chevrons point the other way on right-to-left pages. */
[dir="rtl"] .icon.flip { transform: scaleX(-1); }

/* ---------- Layout helpers ---------- */

.wrap { max-width: var(--shell); margin: 0 auto; padding: 32px var(--gutter) 64px; }
/*
| Every page is as wide as the header and fills that width: a page with little
| on it arranges its blocks in columns rather than leaving half the screen empty.
| Text that would become too long to read limits itself (.readable).
*/
.readable { max-width: 70ch; }

.row { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.row.between { justify-content: space-between; }
.row.tight { gap: 8px; }
.stack { display: flex; flex-direction: column; gap: 18px; }
.stack.loose { gap: 24px; }
.spacer { flex: 1; }

.muted { color: var(--muted); }
.small { font-size: .875rem; }
.pre { white-space: pre-line; }
.flush { margin: 0; }
.num { font-variant-numeric: tabular-nums; }

/* Two columns that become one on narrow screens: main content and a side column. */
.columns { display: flex; flex-wrap: wrap; gap: 24px; align-items: flex-start; }
.columns > .main { flex: 999 1 560px; min-width: 0; }
.columns > .side { flex: 1 1 320px; min-width: 0; }
@media (min-width: 980px) {
    .columns > .side.sticky { position: sticky; inset-block-start: 16px; }
}

/* ---------- Card ---------- */

.card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-card);
    padding: 24px 28px;
}
.card > :last-child { margin-bottom: 0; }
.card-title { margin: 0 0 4px; font-size: 1.25rem; }
.card.plain { padding: 0; overflow: hidden; }
.card-head {
    display: flex; align-items: center; justify-content: space-between; gap: 12px 24px; flex-wrap: wrap;
    padding: 18px 28px; background: var(--surface-subtle); border-bottom: 1px solid var(--line);
}
.card-head > h2, .card-head > h3 { margin: 0; font-size: 1.25rem; }
.card-body { padding: 20px 28px 24px; }
@media (max-width: 600px) {
    .card { padding: 20px; }
    .card-head { padding: 16px 20px; }
    .card-body { padding: 16px 20px 20px; }
}

/* ---------- Forms ---------- */

.field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.field-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 18px; }

label, .label { font-weight: 600; font-size: .9375rem; }

input[type="text"], input[type="email"], input[type="password"], input[type="number"],
input[type="tel"], input[type="search"], select, textarea {
    width: 100%;
    min-height: var(--control-height);
    font: inherit;
    color: inherit;
    background: var(--surface);
    border: 1.5px solid var(--control);
    border-radius: var(--radius-control);
    padding: 10px 14px;
}
textarea { resize: vertical; min-height: 96px; line-height: 1.6; }
input[readonly] { background: var(--surface-subtle); border-color: var(--control-soft); color: var(--ink-soft); }
input:focus-visible, select:focus-visible, textarea:focus-visible { border-color: var(--primary); }
input[type="checkbox"], input[type="radio"] {
    appearance: none; -webkit-appearance: none;
    display: inline-grid; place-content: center; flex: none;
    width: 22px; height: 22px; margin: 0; cursor: pointer;
    background: var(--surface); border: 1.5px solid var(--control); border-radius: 6px;
}
input[type="radio"] { border-radius: 50%; }
input[type="checkbox"]::before, input[type="radio"]::before {
    content: ""; background: var(--on-primary);
    transform: scale(0); transition: transform var(--motion-base) var(--ease-out);
}
input[type="checkbox"]::before { width: 13px; height: 13px; clip-path: polygon(13% 47%, 0 62%, 38% 100%, 100% 22%, 86% 9%, 37% 70%); }
input[type="radio"]::before { width: 9px; height: 9px; border-radius: 50%; }
input[type="checkbox"]:hover, input[type="radio"]:hover { border-color: var(--primary); }
input[type="checkbox"]:checked, input[type="radio"]:checked { background: var(--primary); border-color: var(--primary); }
input[type="checkbox"]:checked::before, input[type="radio"]:checked::before { transform: scale(1); }
input[type="checkbox"]:disabled, input[type="radio"]:disabled { opacity: .5; cursor: not-allowed; }
@media (forced-colors: active) {
    input[type="checkbox"], input[type="radio"] { appearance: auto; -webkit-appearance: auto; }
    input[type="checkbox"]::before, input[type="radio"]::before { content: none; }
}
/*
| A slider is drawn here, like the checkbox, so it looks the same on every device.
| The script that owns it writes how far it is into --fill; it always runs left
| to right (dir="ltr"), as a volume or a position does in every language.
*/
input[type="range"] {
    appearance: none; -webkit-appearance: none;
    width: 100%; height: 28px; margin: 0; padding: 0; background: transparent; border: 0; cursor: pointer;
    --range-track: linear-gradient(to right, var(--primary) var(--fill, 100%), var(--track) var(--fill, 100%));
}
input[type="range"]::-webkit-slider-runnable-track { height: 6px; border-radius: 3px; background: var(--range-track); }
input[type="range"]::-moz-range-track { height: 6px; border-radius: 3px; background: var(--range-track); }
input[type="range"]::-webkit-slider-thumb {
    -webkit-appearance: none; width: 18px; height: 18px; margin-top: -6px; border-radius: 50%;
    background: var(--surface); border: 2px solid var(--primary); box-shadow: var(--shadow-raised);
}
input[type="range"]::-moz-range-thumb {
    box-sizing: border-box; width: 18px; height: 18px; border-radius: 50%;
    background: var(--surface); border: 2px solid var(--primary); box-shadow: var(--shadow-raised);
}
input[type="range"]:hover::-webkit-slider-thumb, input[type="range"]:active::-webkit-slider-thumb { background: var(--primary-soft); }
input[type="range"]:hover::-moz-range-thumb, input[type="range"]:active::-moz-range-thumb { background: var(--primary-soft); }
input[type="range"]:focus-visible { outline: 0; }
input[type="range"]:focus-visible::-webkit-slider-thumb { outline: 3px solid var(--focus); outline-offset: 2px; }
input[type="range"]:focus-visible::-moz-range-thumb { outline: 3px solid var(--focus); outline-offset: 2px; }
@media (forced-colors: active) {
    input[type="range"] { appearance: auto; -webkit-appearance: auto; }
}
input[type="file"] { font: inherit; font-size: .9375rem; max-width: 100%; color: var(--muted); }
input[type="file"]::file-selector-button {
    min-height: var(--touch); margin-inline-end: 12px; padding: 6px 16px;
    font: inherit; font-weight: 600; color: var(--ink); cursor: pointer;
    background: var(--surface); border: 1.5px solid var(--control); border-radius: var(--radius-control);
}
input[type="file"]::file-selector-button:hover { background: var(--neutral-soft); }

.help { font-size: .875rem; color: var(--muted); font-weight: 400; }
.error { font-size: .875rem; color: var(--bad); font-weight: 600; }
.code-input { font-weight: 600; letter-spacing: .12em; text-transform: uppercase; }

.check { display: flex; align-items: center; gap: 12px; min-height: var(--touch); font-weight: 500; cursor: pointer; }
.check.boxed { padding: 12px 16px; border-radius: var(--radius-control); background: var(--ground); font-weight: 600; }
.check.top { align-items: flex-start; }
.check.top input { margin-block-start: 2px; }
/* What a box means is said under its name, each explanation on a line of its own. */
.check > span > .help { display: block; }

fieldset { margin: 0; padding: 0; border: 0; min-width: 0; }
fieldset.group { padding: 16px; border: 1px solid var(--line); border-radius: 12px; }
fieldset.group > legend { float: inline-start; width: 100%; margin-bottom: 12px; padding: 0; font-size: .875rem; color: var(--muted); }
fieldset.group > legend + * { clear: both; }

.code, code, pre { font-family: var(--font-code); font-size: .875rem; }
textarea.code { line-height: 1.7; background: var(--surface-subtle); }
code { padding: 1px 6px; border-radius: 5px; background: var(--neutral-soft); direction: ltr; unicode-bidi: isolate; }

/* A file input inside a large dashed target. */
.dropzone {
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px;
    min-height: 150px; padding: 20px; text-align: center; cursor: pointer;
    border: 2px dashed var(--control); border-radius: var(--radius-card); background: var(--surface-subtle);
}
.dropzone.tall { min-height: 220px; }
.dropzone > .icon { width: 28px; height: 28px; color: var(--primary); }
.dropzone-title { font-size: 1.0625rem; font-weight: 700; }
.dropzone:focus-within { outline: 3px solid var(--focus); outline-offset: 2px; }

/* ---------- Buttons ---------- */

.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 8px;
    min-height: var(--control-height); padding: 8px 22px;
    font: inherit; font-weight: 700; text-decoration: none; cursor: pointer;
    color: var(--on-primary); background: var(--primary);
    border: 1.5px solid var(--primary); border-radius: var(--radius-control);
}
.btn:hover { background: var(--primary-strong); border-color: var(--primary-strong); color: var(--on-primary); }
.btn:disabled, .btn[aria-disabled="true"] { opacity: .5; cursor: not-allowed; pointer-events: none; }

.btn.secondary { background: var(--surface); color: var(--primary); }
.btn.secondary:hover { background: var(--primary-soft); color: var(--primary-strong); }
.btn.ghost { background: var(--surface); color: var(--ink); border-color: var(--control); font-weight: 600; }
.btn.ghost:hover { background: var(--neutral-soft); color: var(--ink); border-color: var(--control); }
.btn.text { background: transparent; color: var(--primary); border-color: transparent; padding-inline: 12px; font-weight: 600; }
.btn.text:hover { background: var(--primary-soft); color: var(--primary-strong); border-color: transparent; }
.btn.quiet { background: transparent; color: var(--ink-faint); border-color: transparent; padding-inline: 8px; font-weight: 600; }
.btn.quiet:hover { background: var(--neutral-soft); color: var(--ink); border-color: transparent; }
.btn.danger { background: var(--surface); color: var(--bad); border-color: var(--bad); }
.btn.danger:hover { background: var(--bad-soft); color: var(--bad); border-color: var(--bad); }

.btn.small { min-height: var(--touch); padding: 6px 16px; font-size: .9375rem; }
.btn.large { min-height: 56px; padding: 10px 32px; font-size: 1.0625rem; border-radius: 12px; }
.btn.block { width: 100%; }

/* ---------- Badges, notices ---------- */

.badge {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 3px 12px; border-radius: var(--radius-pill);
    font-size: .8125rem; font-weight: 600; white-space: nowrap;
    background: var(--neutral-soft); color: var(--ink-faint);
}
.badge.good { background: var(--good-soft); color: var(--good); }
.badge.bad { background: var(--bad-soft); color: var(--bad); }
.badge.warn { background: var(--warn-soft); color: var(--warn); }
.badge.accent { background: var(--primary-soft); color: var(--primary); }
.badge.large { font-size: 1.0625rem; font-weight: 700; padding: 4px 14px; }
/* Status is never carried by colour alone: a dot or an icon comes with it. */
.badge.dot::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.badge > .icon { width: 13px; height: 13px; stroke-width: 3; }
.tag { font-size: .75rem; font-weight: 600; padding: 2px 8px; border-radius: 6px; background: var(--neutral-soft); color: var(--ink-faint); }

.flash, .notice {
    display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap;
    padding: 14px 18px; border-radius: 12px; margin: 0 0 16px; font-weight: 600;
}
/* A message about what was just done stays in sight wherever the page is scrolled to: the page does not
   jump to its top to show it. It can be closed; a success leaves by itself (see Motion). */
.flash { position: sticky; inset-block-start: 12px; z-index: 20; flex-wrap: nowrap; background: var(--good-soft); color: var(--good); box-shadow: var(--shadow-raised); }
.flash.bad { background: var(--bad-soft); color: var(--bad); }
.flash > .flash-text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.flash-close {
    flex: none; display: inline-grid; place-items: center; width: 28px; height: 28px; margin: -3px; padding: 0;
    border: 0; border-radius: 8px; background: transparent; color: inherit; cursor: pointer;
}
.flash-close:hover { background: color-mix(in srgb, currentColor 14%, transparent); }
.flash-close .icon { width: 16px; height: 16px; margin: 0; }
.notice { background: var(--warn-soft); color: var(--warn); }
.notice.info { background: var(--primary-soft); color: var(--ink); font-weight: 400; }
.notice a { color: inherit; font-weight: 700; }
.notice > .icon, .flash > .icon { width: 20px; height: 20px; margin-block-start: 2px; }
/* A notice that lists what it is about (the Library card's errors, the exercises a change retired): the list in plain weight. */
.library-list { margin: 0; padding-inline-start: 1.25em; font-weight: 400; overflow-wrap: anywhere; }
.notice.info > .icon { color: var(--primary); }
.notice > .grow { flex: 1 1 240px; }
/* In a side column there is no room for the words to go beside the icon at 240 px: they stay beside it. */
.notice.in-side { flex-wrap: nowrap; }
.notice.in-side > .grow { flex: 1 1 0; min-width: 0; }

.empty { padding: 32px 24px; text-align: center; color: var(--muted); }

/* ---------- Tables ---------- */

.table-scroll { overflow-x: auto; }
table.data { width: 100%; border-collapse: collapse; font-size: .9375rem; }
table.data th, table.data td { padding: 14px 12px; border-bottom: 1px solid var(--line-soft); text-align: start; vertical-align: middle; }
table.data th { padding-block: 12px; font-size: .8125rem; font-weight: 600; color: var(--muted); white-space: nowrap; border-bottom-color: var(--line); }
table.data thead tr { background: var(--surface-subtle); }
table.data tbody tr:last-child td { border-bottom: 0; }
table.data th:first-child, table.data td:first-child { padding-inline-start: 20px; }
table.data th:last-child, table.data td:last-child { padding-inline-end: 20px; }
table.data .end { text-align: end; white-space: nowrap; }
table.data .strong { font-weight: 700; }
/* A heading the teacher wrote (the title of a section) may take two lines; a state said in two languages takes one each. */
table.data th.wraps { white-space: normal; min-width: 88px; }
table.data td.stacks { white-space: nowrap; }
table.data td.stacks .tr-alt { display: block; margin: 0; }
table.data td.stacks .result-mark { flex-wrap: wrap; }
table.data td.stacks .result-mark .tr-alt { flex-basis: 100%; }
table.data tr.is-wrong { background: var(--bad-faint); }
table.data.wide { min-width: 1020px; }
/* A wide table scrolls sideways inside its card; the way into a row (.pinned on its last cell) stays in
   sight at the end, and what passes under it fades out instead of being cut. */
table.data .pinned { position: sticky; inset-inline-end: 0; background: var(--surface); }
table.data thead .pinned { background: var(--surface-subtle); }
table.data td.pinned::before {
    content: ""; position: absolute; inset-block: 0; inset-inline-end: 100%; width: 16px; pointer-events: none;
    background: linear-gradient(to left, var(--surface), transparent);
}
[dir="rtl"] table.data td.pinned::before { background: linear-gradient(to right, var(--surface), transparent); }
table.data.mid { min-width: 760px; }
table.data.plain thead tr { background: transparent; }
table.data.plain th:first-child, table.data.plain td:first-child { padding-inline-start: 0; }
table.data.plain th:last-child, table.data.plain td:last-child { padding-inline-end: 0; }
.row-link { display: inline-flex; align-items: center; gap: 4px; min-height: var(--touch); font-weight: 700; text-decoration: none; }

.pagination { margin-top: 16px; }

/* ---------- Site header ---------- */

/* The Privacy page and the way to it (decision 175): a line at the foot of a page, once the administrator shows it. */
.site-foot { max-width: var(--shell); margin: 0 auto; padding: 8px var(--gutter) 32px; font-size: .875rem; color: var(--muted); }
.site-foot a { color: var(--muted); }
.site-foot a:hover { color: var(--ink); }
.privacy h2 { font-size: 1.125rem; }
.privacy-list { margin: 0; padding-inline-start: 1.25rem; display: grid; gap: 6px; }

.site-head { position: relative; z-index: 30; background: var(--surface); border-bottom: 1px solid var(--line); }
.site-head-inner {
    max-width: var(--shell); margin: 0 auto; padding: 0 var(--gutter); min-height: 64px;
    display: flex; align-items: center; gap: 8px 28px; flex-wrap: wrap;
}
.brand { display: inline-flex; align-items: center; gap: 10px; font-size: 1.0625rem; font-weight: 700; color: var(--ink); text-decoration: none; }
.brand:hover { color: var(--ink); }
.brand::before { content: ""; width: 12px; height: 12px; border-radius: 3px; background: var(--primary); flex: none; }
/* The teacher's own logo takes the place of the square (components/brand). */
.brand.has-logo::before { content: none; }
.brand-logo { flex: none; height: 32px; width: auto; max-width: 132px; object-fit: contain; padding: 3px; border-radius: 8px; background: var(--logo-ground); }
.brand-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.main-nav { display: flex; gap: 2px; flex: 1 1 auto; align-self: stretch; flex-wrap: wrap; }
.main-nav a, .tabs a {
    display: flex; align-items: center; gap: 8px; padding: 0 12px;
    font-weight: 600; text-decoration: none; color: var(--ink-faint);
    border-bottom: 3px solid transparent;
}
.main-nav a { min-height: 64px; padding: 0 10px; }
/* Seven links (Books first, decision 96) and the brand stand on one row down to here; below it the panel's tag gives
   them its room. */
@media (max-width: 1240px) { .site-head .brand .tag { display: none; } }
/* The profile's place in the top bar, for a phone whose bar at the bottom would hold seven links (partials/nav). */
.head-profile { display: none; }
.main-nav .icon { width: 18px; height: 18px; stroke-width: 2; }
.head-tools .icon { display: none; }
.main-nav a:hover, .tabs a:hover { color: var(--ink); }
/* The chosen one differs in colour and by its line, never in weight: heavier letters are wider, and its neighbours would move. */
.main-nav a[aria-current="page"], .tabs a[aria-current="page"] { position: relative; color: var(--primary); }
.nav-line { position: absolute; inset-inline: 0; inset-block-end: -3px; height: 3px; border-radius: 3px 3px 0 0; background: var(--primary); }
.head-tools { display: flex; align-items: center; gap: 8px 12px; flex-wrap: wrap; }
/*
| The header is one row on every screen wider than a phone: the brand, the links, Sign out. It used to wrap where the
| row ran out of room (below about 1090 px: a laptop of 1366 px at 125%), and Sign out stood alone on a second line
| under the links — out of its place, as the owner saw it. So it gives room in steps, the wider step first:
|   - up to 1100 px, Sign out is its picture (its name is still said, and shown when pointed at) and the links stand closer;
|   - up to 1000 px, each link is its picture over its word, as on a phone's bar.
| The brand's name gives way last (it is cut with "…"). Measured at 1100, 1024, 900 and 701 px, as the administrator
| (seven links) and as a teacher: one row of 64 px, nothing past the edge.
*/
@media (min-width: 701px) {
    .site-head-inner { flex-wrap: nowrap; }
    .site-head-inner > .brand { flex: 0 1 auto; min-width: 0; }
    .main-nav { flex-wrap: nowrap; }
    .main-nav a { flex: none; white-space: nowrap; }
}
@media (min-width: 701px) and (max-width: 1100px) {
    .site-head-inner { gap: 8px 16px; }
    .main-nav a { padding-inline: 8px; }
    .head-tools .icon { display: block; width: 20px; height: 20px; stroke-width: 2; }
    .head-tools .btn > span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
    .head-tools .btn { min-width: var(--touch); padding: 0; }
}
@media (min-width: 701px) and (max-width: 1000px) {
    .main-nav a { flex-direction: column; justify-content: center; gap: 3px; padding: 6px 6px; font-size: .75rem; line-height: 1.25; }
}

/* Segmented control: a choice between two or three views of the same thing. */
.segmented { display: inline-flex; gap: 2px; padding: 3px; border-radius: 12px; background: var(--track); }
.segmented a {
    display: inline-flex; align-items: center; gap: .35em; min-height: 40px; padding: 0 14px;
    font: inherit; font-size: .875rem; font-weight: 600; line-height: 1.3; text-decoration: none; cursor: pointer;
    border: 0; border-radius: 9px; background: transparent; color: var(--ink-faint);
}
.segmented .current { background: var(--surface); color: var(--ink); box-shadow: var(--shadow-raised); }
/*
| Choices side by side that answer one question are of one width, the widest one's (design-system.md, "Choices of one
| width"): "Students · Classes · Teachers" of three widths read as three kinds of thing. A row that scrolls sideways
| (.scroll) is a list of filters with their counts, and keeps the width of each.
*/
.segmented:not(.scroll) { display: inline-grid; grid-auto-flow: column; grid-auto-columns: 1fr; }
.segmented:not(.scroll) :is(a, label) { justify-content: center; }

/* A white band under the header that carries a page's title and its tabs. */
.band { background: var(--surface); border-bottom: 1px solid var(--line); }
.band-inner { max-width: var(--shell); margin: 0 auto; padding: 24px var(--gutter) 0; display: flex; flex-direction: column; gap: 16px; }
.band + .wrap { padding-top: 28px; }

.tabs { display: flex; gap: 4px; flex-wrap: wrap; }
.tabs a { min-height: var(--control-height); padding: 0 14px; }
.tab-count { font-size: .8125rem; font-weight: 600; padding: 1px 8px; border-radius: var(--radius-pill); background: var(--neutral-soft); color: var(--ink-faint); }
.tab-count.warn { background: var(--warn-soft); color: var(--warn); font-weight: 700; }
[aria-current="page"] > .tab-count:not(.warn) { background: var(--primary-soft); color: var(--primary); }

.page-head { display: flex; align-items: center; justify-content: space-between; gap: 16px 24px; flex-wrap: wrap; margin-bottom: 24px; }
.page-head h1 { margin: 0; }
.page-head.top { align-items: flex-start; }
.page-title { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.page-title h1 { margin: 0; }
.page-meta { display: flex; align-items: center; gap: 10px 14px; flex-wrap: wrap; color: var(--muted); }
.lead { font-size: 1.0625rem; color: var(--muted); }

.back-link { display: inline-flex; align-items: center; gap: 6px; min-height: 28px; font-size: .9375rem; font-weight: 600; text-decoration: none; align-self: flex-start; }
.wrap > .back-link { margin-bottom: 20px; }

/* ---------- Sign-in and front door ---------- */

.auth { min-height: 100vh; display: flex; flex-wrap: wrap; }
.auth-aside {
    flex: 1 1 420px; min-width: 0; padding: 40px 56px 56px;
    display: flex; flex-direction: column; gap: 48px;
    background: var(--panel); color: var(--on-panel);
}
.auth-aside .brand, .auth-aside .brand:hover { color: var(--on-panel); align-self: flex-start; }
.auth-aside .brand::before { background: var(--on-panel); }
.auth-brand { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; min-width: 0; }
.brand-tagline { margin: 0; font-size: .9375rem; color: var(--on-panel-soft); }
.auth-pitch { flex: 1; display: flex; flex-direction: column; justify-content: center; gap: 20px; max-width: 460px; }
.auth-title { margin: 0; font-family: var(--font-read); font-weight: 600; font-size: clamp(2.25rem, 5vw, 3.25rem); line-height: 1.1; letter-spacing: -.01em; }
html[lang="fa"] .auth-title { font-family: var(--font-fa); font-weight: 700; line-height: 1.5; letter-spacing: 0; }
.auth-lead { margin: 0; font-size: 1.1875rem; color: var(--on-panel-soft); }
.auth-steps { list-style: none; margin: 16px 0 0; padding: 0; display: flex; flex-direction: column; gap: 16px; }
.auth-steps li { display: flex; align-items: center; gap: 14px; }
.auth-steps .step-num { border-color: currentColor; color: inherit; }

.auth-main { flex: 1 1 480px; min-width: 0; padding: 24px 32px 56px; display: flex; flex-direction: column; gap: 32px; }
.auth-box { flex: 1; width: 100%; max-width: 460px; margin: 0 auto; display: flex; flex-direction: column; justify-content: center; gap: 24px; }
.auth-box .card { padding: 32px; border-radius: 16px; }
.auth-box .btn.block { min-height: 52px; font-size: 1.0625rem; }
.auth-alt { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; padding: 0 4px; }
.auth-links { display: flex; flex-direction: column; gap: 4px; padding: 0 4px; color: var(--ink-faint); }
.auth-links p { margin: 0; min-height: 32px; }
.auth-links a { font-weight: 700; }
@media (max-width: 600px) {
    .auth-aside { padding: 28px 24px 36px; gap: 24px; }
    .auth-main { padding: 16px 16px 40px; }
    .auth-box .card { padding: 24px 20px; }
}

.step-num {
    flex: none; width: 30px; height: 30px; border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    border: 1.5px solid var(--primary); color: var(--primary); font-weight: 700; font-size: .875rem;
}
.step-num.filled { background: var(--primary); color: var(--on-primary); }

/* ---------- Student pages ---------- */

.code-entry { display: flex; gap: 10px; flex-wrap: wrap; }
.code-entry input { flex: 1 1 220px; min-width: 0; min-height: 56px; font-size: 1.25rem; border-radius: 12px; }
.code-entry .btn { min-height: 56px; padding-inline: 32px; font-size: 1.0625rem; border-radius: 12px; }

.list { display: flex; flex-direction: column; gap: 12px; }
.list-row {
    display: flex; align-items: center; gap: 12px 24px; flex-wrap: wrap;
    padding: 18px 24px; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-card);
    color: var(--ink); text-decoration: none;
}
a.list-row:hover { border-color: var(--primary); color: var(--ink); }
.list-main { flex: 999 1 240px; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.list-title { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: 1.125rem; font-weight: 700; }
.list-cell { flex: 1 1 140px; display: flex; flex-direction: column; }
.list-go { display: inline-flex; align-items: center; gap: 6px; font-weight: 700; color: var(--primary); }
.kicker { font-size: .75rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
html[lang="fa"] .kicker { letter-spacing: 0; font-size: .8125rem; }
.level-heading { margin: 28px 0 10px; }
.level-heading:first-of-type { margin-top: 0; }

/* Before the exam */
.section-list { list-style: none; margin: 0; padding: 0; }
.section-list li { display: flex; align-items: center; gap: 14px; padding: 14px 0; border-bottom: 1px solid var(--line-soft); }
.section-list li:last-child { border-bottom: 0; padding-bottom: 0; }
.section-list .name { flex: 1; font-weight: 600; }
/* Under the list, where the sections share one time: the time of the whole exam. */
.section-total { display: flex; align-items: center; gap: 14px; margin: 14px 0 0; padding-block-start: 14px; border-top: 1px solid var(--line); }
.section-total .name { flex: 1; font-weight: 700; }
.rules { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 16px; }
.rules li { display: flex; align-items: flex-start; gap: 14px; }
.rules .icon { width: 22px; height: 22px; margin-block-start: 1px; color: var(--primary); stroke-width: 2; }
.rules li > span:not(.tile) { flex: 1; min-width: 0; }
.divider { height: 1px; margin: 4px 0; border: 0; background: var(--line-soft); }
.center { text-align: center; }

/* After the exam */
.done-head { display: flex; gap: 16px; align-items: flex-start; }
.done-head h1 { margin: 0 0 4px; font-size: 1.875rem; }
.done-mark { flex: none; width: 48px; height: 48px; border-radius: 50%; background: var(--good-soft); color: var(--good); display: flex; align-items: center; justify-content: center; }
.done-mark .icon { width: 24px; height: 24px; stroke-width: 2.6; }

.timeline { list-style: none; margin: 0; padding: 0; }
.timeline li { position: relative; display: flex; gap: 14px; padding-bottom: 20px; }
.timeline li:last-child { padding-bottom: 0; }
.timeline li:not(:last-child)::before {
    content: ""; position: absolute; inset-block: 30px 2px; inset-inline-start: 13px; width: 2px; background: var(--control-soft);
}
.timeline .mark { flex: none; width: 28px; height: 28px; border-radius: 50%; border: 2px solid var(--control); display: flex; align-items: center; justify-content: center; }
.timeline .done { font-weight: 700; }
.timeline .done .mark { background: var(--good); border-color: var(--good); color: var(--on-state); }
.timeline .done .icon { width: 14px; height: 14px; stroke-width: 3.2; }
.timeline .now { font-weight: 700; color: var(--primary); }
.timeline .now .mark { border-color: var(--primary); background: var(--primary-soft); }
.timeline .later { color: var(--muted); }
.timeline li > span:last-child { padding-block-start: 2px; }

.copy-field { display: flex; gap: 8px; flex-wrap: wrap; }
.copy-field input { flex: 1 1 220px; min-width: 0; font-size: .9375rem; }
/* An address inside a sentence: it breaks where it must, and never pushes its box wider. */
.address { overflow-wrap: anywhere; font-weight: 600; }

.score-row { display: flex; align-items: center; gap: 12px 20px; flex-wrap: wrap; padding: 16px 0; border-bottom: 1px solid var(--line-soft); }
.score-row:last-child { border-bottom: 0; padding-bottom: 0; }
.score-row .name { flex: 1 1 200px; font-weight: 600; }
.score-row .value { min-width: 110px; text-align: end; }
.score-row .value strong { font-size: 1.25rem; }
/*
| A student's result. Beside the page (first on a phone) the total; in it the
| points of every section and part, then the answers one by one.
*/
.result-total { display: flex; flex-direction: column; gap: 10px; }
.result-score { margin: 0; display: flex; align-items: baseline; gap: 8px; line-height: 1; }
.result-score strong { font-size: 3rem; font-weight: 700; letter-spacing: -.02em; }
.result-score span { font-size: 1.25rem; font-weight: 600; color: var(--muted); }
.result-total progress { flex: none; width: 100%; height: 8px; }
.result-percent { font-size: 1.0625rem; font-weight: 700; color: var(--ink-soft); }
.result-open { display: flex; gap: 8px; align-items: flex-start; padding: 10px 12px; border-radius: var(--radius-control); background: var(--warn-soft); color: var(--warn); font-size: .875rem; font-weight: 600; }
.result-open .icon { margin-block-start: 2px; }
.result-facts { list-style: none; margin: 6px 0 0; padding: 12px 0 0; border-top: 1px solid var(--line-soft); display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 8px 16px; font-size: .9375rem; color: var(--ink-soft); }
.result-facts li { display: flex; align-items: center; gap: 8px; }
.result-facts .icon { width: 16px; height: 16px; stroke-width: 2.4; color: var(--muted); }
.result-facts .good .icon { color: var(--good); }
.result-facts .bad .icon { color: var(--bad); }

.score-block { padding: 14px 0; border-bottom: 1px solid var(--line-soft); }
.score-block:last-child { border-bottom: 0; padding-bottom: 0; }
.score-block .score-row { padding: 0; border: 0; }
.score-block .score-row progress { flex: 1 1 100%; }
.part-scores { list-style: none; margin: 10px 0 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 4px 28px; font-size: .9375rem; color: var(--ink-soft); }
.part-scores li { display: flex; justify-content: space-between; gap: 12px; padding: 4px 0; }
.part-scores .num { font-weight: 600; color: var(--ink); }
/* A homework's task that asked for nothing back (decision 126) is done by handing it in: no score, a tick. */
.task-done-mark { display: inline-flex; align-items: center; gap: 4px; font-weight: 600; color: var(--good); }
.task-done-mark .icon { width: 16px; height: 16px; }
.task-note { margin: 0 0 6px; }

.result-review { display: flex; flex-direction: column; gap: 16px; }
.review-head { margin-block-start: 8px; }
/* A choice between two views, made of radio buttons so it works without a script. */
fieldset.segmented { margin: 0; border: 0; }
.segmented label { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; min-height: 38px; padding: 4px 14px; border-radius: 9px; font-size: .9375rem; font-weight: 600; color: var(--muted); cursor: pointer; }
.segmented label:has(input:checked) { background: var(--surface); color: var(--ink); box-shadow: var(--shadow-raised); }
.segmented label:has(input:focus-visible) { outline: 3px solid var(--focus); outline-offset: 2px; }
.segmented input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.result-review:has([name="review-filter"][value="open"]:checked) .review-row.is-settled { display: none; }

.review-section-title { margin: 0 0 4px; font-size: 1.125rem; }
.review-part + .review-part { margin-block-start: 22px; padding-block-start: 18px; border-top: 1px solid var(--line-soft); }
.review-row { display: flex; align-items: flex-start; gap: 12px 14px; flex-wrap: wrap; padding: 12px 0; border-top: 1px solid var(--line-soft); }
.review-row:first-of-type { border-top: 0; }
.review-body { flex: 1 1 260px; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.review-prompt { color: var(--ink-soft); }
.review-prompt > :last-child { margin-bottom: 0; }
.review-answer { margin: 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; }
.review-label { font-size: .8125rem; font-weight: 600; color: var(--muted); }
.review-key { color: var(--good); }
/* Why the correct answer is the answer, and the way to hear it again: under the key, in the reader's own line. */
.review-why { margin: 0; font-size: .9375rem; color: var(--ink-soft); }
/* The button's own padding is taken back, so its sign stands in line with the words above it. */
.review-body > .btn { align-self: flex-start; margin-inline-start: -16px; }
.review-part > .voice { margin-block-end: 14px; }
.review-part > .heard { margin-block-start: 12px; }
.review-row > .result-mark, .review-row > .badge { margin-block-start: 3px; }

/* A gap of a text, marked: the number, what was written, and the correct answer after a wrong one. A box, as a
   record is: the gap of the exam screen is a line to write on, and this is what was written, weighed. */
.blank.marked {
    display: inline-flex; align-items: stretch; vertical-align: baseline; position: relative;
    min-height: var(--gap-height); margin: 3px 2px; padding-inline: 1.75rem 0; line-height: 1.2; white-space: normal; cursor: auto;
    background: var(--surface); border: 1.5px solid var(--control-soft); border-radius: 8px; box-shadow: none;
}
.blank.marked::before { content: none; }
.blank.marked b {
    position: absolute; inset-block: 0; inset-inline-start: 0; width: 1.75rem; margin: 0;
    display: inline-flex; align-items: center; justify-content: center;
    font: 700 .8125rem/1 var(--font-ui); font-variant-numeric: tabular-nums; color: var(--ink-soft);
    background: var(--surface-subtle); border-inline-end: 1px solid var(--control-soft);
    border-start-start-radius: 6.5px; border-end-start-radius: 6.5px;
}
/* A gap whose answers are not shown is its number alone. */
.blank.marked:has(b:last-child) { vertical-align: middle; min-width: 1.75rem; padding: 0; }
.blank.marked b:last-child { border-inline-end: 0; border-radius: 6.5px; }
.blank.marked .given, .blank.marked .key { display: inline-flex; align-items: center; padding: 2px 10px; font-weight: 600; }
.blank.marked > :last-child { border-start-end-radius: 6.5px; border-end-end-radius: 6.5px; }
.blank.marked.good .given { background: var(--good-soft); color: var(--good); }
.blank.marked.bad .given { background: var(--bad-soft); color: var(--bad); text-decoration: line-through; }
.blank.marked.none .given { color: var(--muted); }
.blank.marked .key { background: var(--good-soft); color: var(--good); border-inline-start: 1px solid var(--surface); }

.side-scores > div { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--line-soft); }
.side-scores > div:last-child { border-bottom: 0; }

progress {
    appearance: none; -webkit-appearance: none; flex: 1 1 160px; min-width: 0;
    height: 8px; border: 0; border-radius: 4px; overflow: hidden; background: var(--track); color: var(--primary);
}
progress::-webkit-progress-bar { background: var(--track); }
progress::-webkit-progress-value { background: var(--primary); border-radius: 4px; }
progress::-moz-progress-bar { background: var(--primary); border-radius: 4px; }
progress.ok { color: var(--good); }
progress.ok::-webkit-progress-value { background: var(--good); }
progress.ok::-moz-progress-bar { background: var(--good); }

.answer-text {
    white-space: pre-wrap; padding: 20px 24px;
    border: 1px solid var(--line-soft); border-radius: 12px; background: var(--surface-subtle);
    font-family: var(--font-read); font-size: 1.125rem; line-height: 1.75;
}
.feedback { padding: 16px 20px; border-radius: 12px; background: var(--primary-soft); font-size: 1.0625rem; }
.block-title { margin: 0 0 8px; font-size: .8125rem; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
html[lang="fa"] .block-title { letter-spacing: 0; }

/* ---------- Exam screen ---------- */

/*
| The page is a paper and the student has a pen (decision 54).
|   - What is printed is ink (--ink).
|   - The brand's colour is what is settled: an answer, a choice, a part that is done.
|   - One warm colour (--accent) is where the student is now: the question being
|     answered, on the page and in the footer.
|   - Nothing is marked by a line drawn round it. A state is a fill, and a fill moves:
|     the plate under a chosen part, a chosen option or the question being answered
|     glides from where it was to where it is (public/js/exam.js puts it there).
|   - The keyboard's own mark is a soft light round the thing (--halo), never a hard line.
| Movement here is soft and never sudden: what appears first makes its room, what stands
| beside something that goes is carried, and what does not change does not move.
*/
.exam { min-height: 100vh; display: flex; flex-direction: column; overflow-x: clip; }
.exam :focus-visible { outline: 0; box-shadow: var(--halo); }
/* What is selected with the pointer, to highlight it or to write over it: a wash of the brand's colour. */
.exam ::selection { background: color-mix(in srgb, var(--primary) 20%, transparent); }

.exam-bar { position: sticky; inset-block-start: 0; z-index: 20; background: var(--surface); border-bottom: 1px solid var(--line); }
/*
| Two rows: the exam, the save state and the clock, then the sections on a row of
| their own. Nothing wraps, so a long title is cut short rather than breaking the bar.
*/
.exam-bar-inner {
    max-width: var(--shell); margin: 0 auto; padding: 8px var(--gutter) 0;
    display: grid; grid-template-columns: minmax(0, 1fr) auto auto auto; grid-template-areas: "id save size timer" "steps steps steps steps";
    align-items: center; column-gap: 16px;
}
/* Whose paper this is: the student as the circle the rest of the site shows them as, the exam beside it. */
.exam-id { grid-area: id; display: flex; align-items: center; gap: 12px; min-width: 0; }
.exam-id-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.35; }
.exam-id strong, .exam-id .who, .exam-id .section-now { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.exam-id strong { font-size: 1.0625rem; }
.exam-id .who, .exam-id .section-now { font-size: .8125rem; color: var(--muted); }
/* Which section this is, in words: where the row of sections is not shown (a phone). */
.exam-id .section-now { display: none; }

.steps {
    grid-area: steps; list-style: none; margin: 8px 0 0; padding: 6px 0; min-width: 0;
    display: flex; align-items: center; gap: 10px; font-size: .875rem; border-top: 1px solid var(--line-soft); overflow: hidden;
}
/* Every section keeps its number; the titles share what room is left, the current one last to give way. */
.steps li { display: flex; align-items: center; gap: 8px; flex: 0 100 auto; min-width: 26px; color: var(--muted); }
.steps li + li { min-width: 44px; }
.steps li + li::before { content: ""; flex: 0 1 auto; width: 28px; min-width: 10px; height: 1px; background: var(--control-soft); }
.steps .label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.steps .mark, .part-tabs .mark {
    position: relative; width: 26px; height: 26px; border-radius: 50%; flex: none;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--neutral-soft); color: var(--ink-soft); font-size: .8125rem; font-weight: 600;
}
.steps .current { flex-shrink: 1; color: var(--ink); font-weight: 700; }
.steps .current .mark { background: var(--primary); color: var(--on-primary); }
.steps .done .mark { background: var(--good-soft); color: var(--good); }
.steps .mark .icon { display: none; width: 14px; height: 14px; stroke-width: 3; }
.steps .done .mark .icon { display: block; }
.steps .done .mark .n { display: none; }
/*
| The same row where the student moves between the sections: each section is a way to it, a tab at the
| foot of the bar. No line joins them — they have no order to keep. The one on screen is told by the
| line under it and by its filled number, never by heavier letters, so that choosing one moves none of
| the others. The line is one thing for the row: it slides to the section that is chosen. A section
| whose questions are all answered shows a tick; one whose own time is over a lock, and stays in its place.
*/
.steps.free { position: relative; gap: 4px; padding: 0; overflow-x: auto; scrollbar-width: none; overscroll-behavior-x: contain; }
.steps.free::-webkit-scrollbar { display: none; }
.steps.free li, .steps.free li + li { flex: 0 1 auto; min-width: 0; }
.steps.free li + li::before { content: none; }
.steps.free button {
    display: inline-flex; align-items: center; gap: 8px; min-width: 0; max-width: 100%; min-height: var(--touch); padding-block: 6px; padding-inline: 6px 14px; position: relative;
    font: inherit; font-weight: 600; color: var(--muted); cursor: pointer;
    background: transparent; border: 0; border-radius: var(--radius-control) var(--radius-control) 0 0;
    transition: color var(--motion-base) var(--ease), background-color var(--motion-base) var(--ease), opacity var(--motion-base) var(--ease);
}
.steps.free button:hover:not(:disabled, [aria-disabled="true"]) { background: var(--neutral-soft); color: var(--ink); }
.steps.free button[aria-selected="true"] { color: var(--ink); }
.steps.free:not(.gliding) button[aria-selected="true"]::after { content: ""; position: absolute; inset-inline: 0; inset-block-end: 0; height: 3px; border-radius: 3px 3px 0 0; background: var(--primary); }
.steps.free li.glide { position: absolute; top: auto; bottom: 0; left: 0; min-width: 0; width: 0; height: 3px; border-radius: 3px 3px 0 0; background: var(--primary); opacity: 0; pointer-events: none; transition: transform var(--motion-glide) var(--ease-spring), width var(--motion-glide) var(--ease-spring), opacity var(--motion-fast) var(--ease); }
.steps.free [aria-selected="true"] .mark { background: var(--primary); color: var(--on-primary); }
.steps.free .complete:not([aria-selected="true"]) .mark { background: var(--good-soft); color: var(--good); }
.steps.free .complete .icon-complete, .steps.free .closed .icon-closed { display: block; }
.steps.free .complete .mark .n, .steps.free .closed .mark .n { display: none; }
.steps.free .closed { color: var(--ink-faint); cursor: not-allowed; }
.steps.free .closed .icon-closed { stroke-width: 2; }
.steps.free .closed .icon-complete { display: none; }
/* What a section has left of its own time, where each has its own: the running one beside the large clock, the others standing still. */
.step-time { flex: none; font-size: .8125rem; font-weight: 500; color: var(--muted); }
.steps.free .closed .step-time { color: var(--ink-faint); }
/* A section the server is being asked for: a ring turns round its mark until the answer is here. */
.steps.free button.is-busy { pointer-events: none; }
/*
| While a recording that cannot be paused plays, the ways to another part or section are closed (exam.js,
| heldByRecording(); decision 152): fainter, and a press on one says why in the note beside it. Nothing moves.
*/
:is(.steps.free button, .part-tabs button)[aria-disabled="true"] { opacity: .45; cursor: not-allowed; }
.exam .ways { transition: opacity var(--motion-base) var(--ease); }
.exam.held-by-recording .ways { opacity: .45; }
.steps.free button.is-busy .mark::after { content: ""; position: absolute; inset: -3px; border-radius: 50%; border: 2px solid var(--primary); border-inline-end-color: transparent; animation: spin 900ms linear infinite; }

/*
| Saved, and being saved. The mark is a ring: while an answer is on its way the ring turns; when it
| has arrived the tick is drawn into it. The words change the way the clock's figures do, one going
| as the other comes, in a place as wide as the longest of them, so nothing beside them moves.
*/
.save-status { grid-area: save; position: relative; display: inline-flex; align-items: center; gap: 8px; font-size: .875rem; font-weight: 500; color: var(--muted); white-space: nowrap; transition: color var(--motion-base) var(--ease); }
.save-status .icon { flex: none; width: 20px; height: 20px; }
.save-status .icon-failed { display: none; }
.save-icon .ring { opacity: .28; transform-origin: 12px 12px; transition: opacity var(--motion-base) var(--ease); }
.save-status[data-state="saving"] .ring { opacity: 1; stroke-dasharray: 18 39; animation: turn 800ms linear infinite; }
.save-icon .tick { stroke-dasharray: 15; stroke-dashoffset: 0; transition: stroke-dashoffset 340ms var(--ease-out) 60ms; }
.save-status[data-state="saving"] .tick { stroke-dashoffset: 15; transition: none; }
.save-status[data-state="saved"] { color: var(--good); }
/* Not saved yet, and still being tried: a state to know of, not an alarm. */
.save-status[data-state="failed"] { color: var(--warn); font-weight: 700; }
.save-status[data-state="failed"] .save-icon { display: none; }
.save-status[data-state="failed"] .icon-failed { display: block; }
.save-words { display: inline-grid; overflow: hidden; line-height: 1.5; }
.save-words > span { grid-area: 1 / 1; opacity: 0; transform: translateY(-100%); transition: transform 380ms var(--ease-out), opacity var(--motion-base) var(--ease); }
.save-status[data-state="saved"] [data-save-word="saved"], .save-status[data-state="saving"] [data-save-word="saving"], .save-status[data-state="failed"] [data-save-word="failed"] { opacity: 1; transform: none; }
.save-status[data-state="saving"] [data-save-word="saved"], .save-status[data-state="failed"] [data-save-word="saving"] { transform: translateY(100%); }
/*
| A sentence where there is room and a short form where there is not: on a phone,
| and in bilingual mode, where every sentence is twice as long. The sentence stays
| in the page for screen readers.
*/
.when-tight { display: none; }

/* The clock. Each figure is a window on a wheel: when it changes, the old one goes down out of it and the new one comes in from above, once. */
.timer { grid-area: timer; display: flex; align-items: center; gap: 10px; min-height: 52px; padding: 4px 16px; border-radius: var(--radius-control); background: var(--neutral-soft); }
.timer > .icon { width: 20px; height: 20px; stroke-width: 2; }
.text-size {
    grid-area: size; display: inline-grid; place-content: center; width: var(--touch); height: var(--touch); padding: 0; position: relative;
    background: var(--neutral-soft); color: var(--ink-soft); border: 0; border-radius: var(--radius-control); cursor: pointer;
}
.text-size:hover { background: var(--track); color: var(--ink); }
.text-size .icon { width: 22px; height: 22px; stroke-width: 2; }
html[data-text-size] .text-size { background: var(--primary-soft); color: var(--primary); }
.timer-text { display: flex; flex-direction: column; }
.timer-label { font-size: .6875rem; font-weight: 600; letter-spacing: .05em; text-transform: uppercase; }
html[lang="fa"] .timer-label { letter-spacing: 0; font-size: .75rem; }
.timer-value { display: inline-flex; font-size: 1.375rem; font-weight: 700; line-height: 1.15; font-variant-numeric: tabular-nums; font-family: var(--font-ui); }
.figures { display: inline-flex; }
.dg { position: relative; display: inline-block; height: 1.15em; overflow: hidden; }
.dg > i { display: block; font-style: normal; text-align: center; }
.dg > i + i { position: absolute; inset: 0; transform: translateY(-100%); }
.dg.roll > i:first-child { animation: roll-out 460ms var(--ease-out) forwards; }
.dg.roll > i + i { animation: roll-in 460ms var(--ease-out) forwards; }
.timer .when-low { display: none; }
/* The last five minutes: a calm change of colour. */
.timer.low { background: var(--warn-soft); color: var(--warn); }
.timer.low .when-low { display: inline; }
.timer.low .when-normal { display: none; }
/* The last minute. */
.timer.last { background: var(--bad-soft); color: var(--bad); }

/* Said calmly while the student can go on working; urgent only when the page is about to be left with answers unsent. */
.save-banner { padding: 10px var(--gutter); text-align: center; font-weight: 600; background: var(--warn-soft); color: var(--warn); }
.save-banner.urgent { background: var(--bad); color: var(--on-state); }
/* "5 minutes left": said once under the bar, then gone. */
.time-notice { display: flex; align-items: center; justify-content: center; gap: 10px; padding: 8px var(--gutter); font-weight: 600; background: var(--warn-soft); color: var(--warn); }
.time-notice.last { background: var(--bad-soft); color: var(--bad); }
.time-notice .icon { width: 18px; height: 18px; stroke-width: 2; }

/* As wide as the two bars. Running text limits itself; tables, a passage and an essay use the room. */
.exam-main { flex: 1; width: 100%; max-width: var(--shell); margin: 0 auto; padding: 24px var(--gutter) 40px; }
.exam-main > * + * { margin-top: 20px; }
.part > * + * { margin-top: 20px; }
/* One block per section on the page; only the one on screen is shown, so none of them stands under another. */
.exam-main > .exam-section { margin-top: 0; }
.exam-section > * + * { margin-top: 20px; }

.exam-note { display: flex; gap: 12px; align-items: flex-start; font-size: .9375rem; color: var(--ink-soft); }
.exam-note > .icon { width: 20px; height: 20px; margin-block-start: 2px; color: var(--primary); stroke-width: 2; }
.exam-note > div > :last-child, .instructions > :last-child, .q-prompt > :last-child { margin-bottom: 0; }

/*
| The parts of a section: names on a track, and one plate under the one on screen. The plate is
| one thing: choosing another part does not switch one off and another on, it slides the plate
| there. Until the script has measured where the plate goes, the chosen name carries the plate's
| look itself, so the first paint is already right. A part that is done shows a tick, drawn in.
| More parts than fit: the row is pulled sideways, and simply goes on out of its place.
*/
.part-tabs { position: relative; display: inline-flex; max-width: 100%; gap: 2px; padding: 4px; background: var(--track); border-radius: var(--radius-pill); overflow-x: auto; scrollbar-width: none; overscroll-behavior-x: contain; }
.part-tabs::-webkit-scrollbar { display: none; }
.part-tabs.pulls { cursor: grab; }
.part-tabs.pulling { cursor: grabbing; }
.part-tabs.pulling button { pointer-events: none; }
/* Both rows scroll sideways; the words kept for screen readers must stay inside their button to scroll with it. */
.part-tabs button, .qnav-item { position: relative; }
.part-tabs button {
    z-index: 1; flex: none; display: inline-flex; align-items: center; gap: 10px; min-height: 40px; padding-block: 4px; padding-inline: 7px 16px;
    font: inherit; font-weight: 600; font-size: .9375rem; white-space: nowrap; cursor: pointer;
    background: transparent; color: var(--ink-faint); border: 0; border-radius: var(--radius-pill);
    transition: color var(--motion-base) var(--ease), transform var(--motion-fast) var(--ease), opacity var(--motion-base) var(--ease);
}
.part-tabs button:hover, .part-tabs button[aria-selected="true"] { color: var(--ink); }
.part-tabs .mark { background: var(--plate); transition: background-color var(--motion-base) var(--ease), color var(--motion-base) var(--ease); }
.part-tabs [aria-selected="true"] .mark { background: var(--primary); color: var(--on-primary); }
.part-tabs:not(.gliding) button[aria-selected="true"] { background: var(--plate); box-shadow: var(--shadow-raised); }
.tab-glide {
    position: absolute; top: 4px; bottom: 4px; left: 0; width: 0; border-radius: var(--radius-pill); opacity: 0; pointer-events: none;
    background: var(--plate); box-shadow: var(--shadow-raised);
    transition: transform var(--motion-glide) var(--ease-spring), width var(--motion-glide) var(--ease-spring), opacity var(--motion-fast) var(--ease);
}
.part-tabs .mark .n { transition: opacity var(--motion-base) var(--ease), transform var(--motion-base) var(--ease-soft); }
.part-tabs .mark .icon { position: absolute; inset: 0; margin: auto; width: 14px; height: 14px; stroke-width: 3; stroke-dasharray: 24; stroke-dashoffset: 24; transition: stroke-dashoffset 460ms var(--ease-soft) 80ms; }
.part-tabs .complete .mark .n { opacity: 0; transform: scale(.4); }
.part-tabs .complete .mark .icon { stroke-dashoffset: 0; }
.part-tabs .complete:not([aria-selected="true"]) .mark { background: var(--good-soft); color: var(--good); }
.tab-progress { font-size: .8125rem; font-weight: 500; color: var(--muted); }

/*
| A part's head: what this is (its name, and which questions it holds, on one line), and what
| to do — the task, a heavy line of writing with a rule under it, because it is what must be
| read before anything is answered. No box and no sign: whatever a teacher writes reads right.
*/
.part-top { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 12px; margin: 0 0 14px; }
.part-title { margin: 0; font-size: 1.375rem; }
.q-range { margin: 0; padding: 3px 11px; font-size: .8125rem; font-weight: 600; white-space: nowrap; color: var(--ink-soft); background: var(--neutral-soft); border-radius: var(--radius-pill); font-variant-numeric: tabular-nums; }
/* A homework with no question at all: nothing to count, flag or go back to, in the footer or on handing it in. */
.exam[data-no-questions] :is(.review-map, .review-legend, [data-answered-line], .dialog-missing) { display: none; }
/* The footer keeps its place for the way on, where the questions would have stood. */
.exam[data-no-questions] .qnav { visibility: hidden; }
/* A homework's task that asks for nothing (decision 126): the teacher's words as a text, and a line saying that handing
   the homework in is its "done". */
.task-text { margin: 0 0 16px; font-size: 1.125rem; line-height: 1.6; color: var(--ink); }
.task-text > :last-child { margin-block-end: 0; }
.task-done { display: flex; align-items: flex-start; gap: 8px; margin: 0; padding-block-start: 14px; border-top: 1px solid var(--line-soft); color: var(--muted); }
.task-done .icon { width: 18px; height: 18px; flex: none; translate: 0 2px; }
.instructions { position: relative; margin: 0 0 20px; padding-block-end: 18px; font-size: 1.125rem; font-weight: 600; line-height: 1.45; color: var(--ink); }
/* The rule under the task is a thing of its own, so that it can be drawn in from its start. */
.instructions::after { content: ""; position: absolute; inset-inline: 0; inset-block-end: 0; height: 1px; background: var(--line-soft); transform-origin: 0 50%; }
.instructions:dir(rtl)::after { transform-origin: 100% 50%; }
.instructions strong { font-weight: 700; color: var(--primary-strong); }
.instructions ul { margin: 0 0 .5em; padding-inline-start: 1.2em; }
.instructions table, .instructions blockquote { margin: 0 0 8px; font-weight: 400; }
.instructions th, .instructions td { line-height: 1.4; color: var(--ink); }

.split { position: relative; display: flex; flex-wrap: wrap; gap: 20px; align-items: flex-start; }
.split > .card { flex: 1 1 440px; min-width: 0; }
.pane-bar { display: none; }
.passage { font-family: var(--font-read); font-size: 1.1875rem; line-height: 1.75; padding: 32px 40px; }
.passage h2 { font-size: 1.75rem; font-weight: 600; margin: 0 0 20px; }
.passage p { margin: 0 0 18px; }
.passage .kicker { font-family: var(--font-ui); margin: 0 0 6px; }
html[lang="fa"] .passage .kicker { font-family: var(--font-fa); }
/*
| Wide screens: a text beside its questions, each scrolling by itself. What runs on past the edge of a box fades
| there, so a line is never cut in half by the edge: at the box's top and at its foot stays a line of no height
| that throws a soft light of the box's own colour. At the start and the end of the text those two lie over the
| box's empty margin, and nothing is seen of them.
|
| The browser's own bar is not shown in these boxes. It takes its width out of a box that has something to scroll
| and gives it back to one that has not: the lines of a text were a few pixels shorter in one part than in the
| next, and everything in the box moved by that. In its place each box has a bar of the page's own (.pane-bar,
| drawn and moved by exam.js), which lies over the box's far edge and takes no room: thin, in the colour of a
| line, a little heavier under the pointer. It is one thing from part to part — it goes softly from the length
| and the place it had to the ones the new text gives it (.soft), and is never simply another bar.
*/
@media (min-width: 960px) {
    .split > .card { --pad: 24px; max-height: calc(100vh - 270px); min-height: 320px; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: none; }
    .split > .card::-webkit-scrollbar { display: none; }
    .split > .passage { --pad: 32px; }
    .split > .card::before, .split > .card::after { content: ""; display: block; position: sticky; z-index: 3; height: 0; pointer-events: none; box-shadow: 0 0 14px 12px var(--surface); }
    /* A thing that stays in a box that scrolls stays inside the box's margin; each line is moved out to the edge itself. */
    .split > .card::before { top: 0; transform: translateY(calc(-1 * var(--pad))); }
    .split > .card::after { bottom: 0; transform: translateY(var(--pad)); }
    .pane-bar { display: block; position: absolute; z-index: 4; width: 14px; pointer-events: none; }
    .pane-bar.on { pointer-events: auto; }
    .pane-bar > i {
        position: absolute; inset-block-start: 0; inset-inline-end: 4px; width: 6px; height: 36px; border-radius: 4px; opacity: 0; touch-action: none;
        background: var(--control-soft);
        transition: opacity var(--motion-base) var(--ease), background-color var(--motion-fast) var(--ease), width var(--motion-fast) var(--ease);
    }
    .pane-bar.on > i { opacity: 1; }
    .pane-bar.soft > i { transition: transform 460ms var(--ease-soft), height 460ms var(--ease-soft), opacity var(--motion-base) var(--ease), background-color var(--motion-fast) var(--ease), width var(--motion-fast) var(--ease); }
    .pane-bar.still > i { transition: none; }
    .pane-bar:hover > i, .pane-bar.held > i { width: 8px; background: var(--control); }
    /* The bars of two boxes that are both new leave and come with them. */
    .part.leaving .split:not(:has(> .stays)) > .pane-bar { opacity: 0; transition: opacity var(--motion-fast) var(--ease); }
    .part.entering .split:not(:has(> .stays)) > .pane-bar { animation: fade-in var(--motion-enter) var(--ease) both; }
}
/*
| Where the window is tall enough for it, such a part makes the screen exactly as tall as the window: the two
| bars, the row of parts, and the two boxes taking all that is left. The page then does not scroll at all; only
| what is in the boxes does. (Their height used to be the window's less a guessed number, which was never the
| right one: the whole page scrolled by the difference, under two boxes that scrolled too.)
*/
@media (min-width: 960px) and (min-height: 620px) {
    .exam:has(.exam-section:not([hidden]) > .part:not([hidden]) > .split) { height: 100vh; height: 100dvh; min-height: 0; }
    .exam:has(.exam-section:not([hidden]) > .part:not([hidden]) > .split) > .exam-main { display: flex; flex-direction: column; min-height: 0; padding-block-end: 20px; }
    .exam-section:not([hidden]):has(> .part:not([hidden]) > .split), .part:not([hidden]):has(> .split) { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
    .exam-section:has(> .part:not([hidden]) > .split) > .part-tabs { align-self: flex-start; flex: none; }
    .exam-section:has(> .part:not([hidden]) > .split) > .exam-note, .part:has(> .split) > :not(.split) { flex: none; }
    .part:not([hidden]) > .split { flex: 1 1 auto; min-height: 0; flex-wrap: nowrap; align-items: stretch; }
    .part:not([hidden]) > .split > .card { flex: 1 1 0; max-height: none; min-height: 0; }
}
@media (max-width: 600px) {
    .passage { padding: 24px 20px; font-size: 1.0625rem; }
}

/*
| Going to another part. Each thing of the part comes and goes in the way that suits it, one after another in
| the order they stand (--i, written by exam.js), and none of it sudden:
|   - what both parts have does none of this: it stays, and only what it says changes (.stays, below);
|   - what was there leaves from its top down, a short step to the side it is left by;
|   - the name, the sentences and the questions come in from the side the part is come to; running text, the
|     recording, a table and the task rise a little into place; the rule under the task is drawn from its start;
|   - a question's number arrives a breath after its line, growing to its size; its answers fade in after it;
|   - the words of a word box come one by one.
| The card takes its new height while the page takes its new place, on one curve (exam.js, morph()). On a
| right-to-left page the sides are the other way round. Opening the page, everything rises.
*/
.part.leaving > :is(.player, .voice, .video, .check-bar):not(.stays), .part.leaving :is(.passage:not(.stays), .part-top:not(:has(.stays)), .instructions:not(.stays), .content > *, .option-key, .place-picture, .question, .essay, .card > p, .part-side > p) {
    animation: item-out 140ms var(--ease) both; animation-delay: calc(min(var(--i, 0), 4) * 9ms);
}
.part.leaving.back > :is(.player, .voice, .video, .check-bar):not(.stays), .part.leaving.back :is(.passage:not(.stays), .part-top:not(:has(.stays)), .instructions:not(.stays), .content > *, .option-key, .place-picture, .question, .essay, .card > p, .part-side > p),
[dir="rtl"] .part.leaving:not(.back) > :is(.player, .voice, .video, .check-bar):not(.stays), [dir="rtl"] .part.leaving:not(.back) :is(.passage:not(.stays), .part-top:not(:has(.stays)), .instructions:not(.stays), .content > *, .option-key, .place-picture, .question, .essay, .card > p, .part-side > p) { animation-name: item-out-back; }
[dir="rtl"] .part.leaving.back > :is(.player, .voice, .video, .check-bar):not(.stays), [dir="rtl"] .part.leaving.back :is(.passage:not(.stays), .part-top:not(:has(.stays)), .instructions:not(.stays), .content > *, .option-key, .place-picture, .question, .essay, .card > p, .part-side > p) { animation-name: item-out; }

.part.entering :is(.part-title:not(.stays), .line, .question), .exam-section.entered:not(.row-stays) > .part-tabs { animation: part-in 560ms var(--ease-soft) both; animation-delay: calc(var(--i, 0) * 34ms); }
/*
| Going to another section, what the section says of itself leaves with the part (.leaves): its note, and its
| row of parts. The row of parts from one section to the next is one row (exam.js, swapNow()): it does not leave
| and come in. Only its names go (.row-goes-on), the next section's come in their place (.row-stays), and the row
| takes its new width as one thing. The plate under the chosen name goes and comes with the names.
*/
.exam-section.leaves > .exam-note, .exam-section.leaves:not(.row-goes-on) > .part-tabs, .exam-section.leaves.row-goes-on > .part-tabs > button { animation: soft-out 170ms var(--ease) both; }
.exam-section.leaves.row-goes-on > .part-tabs > .tab-glide { animation: fade-out 170ms var(--ease) both; }
.exam-section.row-stays > .part-tabs > :is(button, .tab-glide) { animation: fade-in 340ms var(--ease) both; }
/* A text that comes in beside a card taking its new width waits for the card's box to leave its place (exam.js, morph()). */
.part.entering.widens > .split > .passage:not(.stays) { animation-delay: 220ms; }
.part.entering.widens > .split > .pane-bar { animation: fade-in 340ms var(--ease) 220ms both; }
.part.entering.back :is(.part-title:not(.stays), .line, .question), [dir="rtl"] .part.entering:not(.back) :is(.part-title:not(.stays), .line, .question) { animation-name: part-in-back; }
[dir="rtl"] .part.entering.back :is(.part-title:not(.stays), .line, .question) { animation-name: part-in; }
.part.entering > :is(.player, .voice, .video, .check-bar):not(.stays), .part.entering :is(.passage:not(.stays), .instructions:not(.stays), .content > :not(.rows), .option-key, .place-picture, .essay, .card > p, .part-side > p),
.exam-section.entered > .exam-note, .exam .part.entering.rise :is(.part-title, .line, .question) { animation: part-rise 560ms var(--ease-soft) both; animation-delay: calc(var(--i, 0) * 34ms); }
.part.entering .q-range:not(.stays) { animation: chip-in 420ms var(--ease-soft) both; animation-delay: calc(var(--i, 0) * 34ms + 120ms); }
.part.entering :is(.line > .qn, .question > .qnum) { animation: chip-in 460ms var(--ease-spring) both; animation-delay: calc(var(--i, 0) * 34ms + 90ms); }
.part.entering :is(.choices, .question .select, .short-answer, .word-order, .order, .recorder, .letters-block) { animation: fade-in 420ms var(--ease) both; animation-delay: calc(var(--i, 0) * 34ms + 140ms); }
.part.entering .box-word { animation: chip-in 420ms var(--ease-spring) both; animation-delay: calc(var(--i, 0) * 34ms + var(--w, 0) * 26ms + 80ms); }
.part.entering .instructions:not(.stays)::after { animation: rule-in 640ms var(--ease-soft) both; animation-delay: calc(var(--i, 0) * 34ms + 120ms); }
/*
| What two parts both have stays where it is when one takes the other's place (.stays, set by exam.js): the box
| of a recording, of a text, of a check; the part's name, which questions it holds, the task and the rule under
| it. They are not taken away and brought back — only what they say changes:
|   - words that will be other words go where they stand, while the rest of the old part leaves (.goes), and
|     the new come in their place (.coming), as words change in the footer; words that are the same do nothing.
|     Of a name, and of the line that says which questions, the words the two begin alike with are the same
|     words: only what comes after them goes and comes (.tail, set apart by exam.js) — from "Question 2" to
|     "Question 3", the figure;
|   - which questions a part holds stands after its name, and goes with the end of that name to its new place;
|   - the rule under a task of another length goes to its new place with the card (--rule-shift);
|   - the text in a box that stays leaves and comes, the box itself does neither.
*/
.q-range > span { display: inline-block; }
.tail.drifts { display: inline-block; }
.part.leaving .tail { animation: soft-out 170ms var(--ease) both; }
.exam .part.entering .tail { animation: fade-in 340ms var(--ease) both, drift-in 420ms var(--ease-settle) both; }
/* (A chip the next part has none of goes too, though the name beside it stays.) */
.part.leaving :is(.part-title.goes, .q-range.goes > span, .q-range:not(.stays), .instructions.goes > *), .part.leaving .passage.stays > * { animation: soft-out 170ms var(--ease) both; }
.exam .part.entering :is(.part-title.coming, .q-range.coming > span, .instructions.coming > *) { animation: fade-in 340ms var(--ease) both, drift-in 420ms var(--ease-settle) both; }
.instructions.rule-moves::after { animation: rule-move 540ms cubic-bezier(.25, 1, .5, 1) both; }
.part.entering .passage.stays > * { animation: part-rise 560ms var(--ease-soft) both; animation-delay: calc(var(--i, 0) * 34ms); }
.card.morph { overflow: hidden; }
/*
| A card that goes across the page to another width (a part beside its text, a part alone) is seen as one box
| that takes its new shape (exam.js, reshape()): the box is drawn under the card (.card-ghost), the card's own
| frame is not drawn meanwhile, and its words are cut to the box. Nothing in the card is laid out again on the way.
*/
.exam-main:has(> .card-ghost) { position: relative; isolation: isolate; }
.card-ghost { position: absolute; inset-block-start: 0; inset-inline-start: 0; margin: 0; z-index: -1; pointer-events: none; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-card); }
.card.reshaping { background: transparent; border-color: transparent; box-shadow: none; }
/* What the card holds is seen once its box is nearly in its place: a line cut by the box's edge on its way reads as a fault. */
.part.entering .card.reshaping { animation: fade-in 300ms var(--ease) 220ms both; }
/*
| Grammarly's own buttons and panels, where writing aids are kept out of the exam screen (decision 152): elements its
| extension adds to the page, by the names it gives them, and any other exam.js finds of it ([data-kept-out]). They
| carry styles of their own on the element, which only !important outweighs.
*/
html:has(#exam[data-aids-closed]) :is(grammarly-extension, grammarly-popups, grammarly-desktop-integration, grammarly-mirror, grammarly-extension-vbars, [data-grammarly-shadow-root], [data-kept-out]) { display: none !important; }
/* The page's own bar, while a change goes to a page that does not scroll (exam.js, barStill()): its room stays. */
html.bar-still { overflow-y: hidden; }

/* Audio player: play only. No pause and no seeking, as in the real exam. */
.player {
    display: flex; align-items: center; gap: 16px 24px; flex-wrap: wrap;
    padding: 18px 20px; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-card);
}
.player-button { min-width: 8.25rem; min-height: 52px; border-radius: 26px; padding-inline: 18px 22px; }
.player[data-state="playing"] .player-button { background: var(--primary-soft); color: var(--primary); opacity: 1; }
/* While it plays, three bars move in the button: the sign that sound is coming, where the picture of "play" was. */
.eq { display: none; align-items: flex-end; gap: 3px; width: 18px; height: 16px; }
.eq i { flex: 1; height: 100%; border-radius: 2px; background: currentColor; transform-origin: bottom; animation: eq 900ms ease-in-out infinite; }
.eq i:nth-child(2) { animation-delay: -300ms; }
.eq i:nth-child(3) { animation-delay: -600ms; }
.player[data-state="playing"] .eq { display: inline-flex; }
.player[data-state="playing"] .player-button .icon, .player[data-state="done"] .player-button .icon { display: none; }
.player-body { flex: 1 1 260px; min-width: 0; display: flex; flex-direction: column; gap: 8px; }
.player-meta { display: flex; justify-content: space-between; align-items: center; gap: 12px; font-size: .875rem; }
.player .plays { display: inline-flex; align-items: center; gap: 8px; font-weight: 600; }
/* A play is a dot: full while it is still to be had, empty once it is spent. */
.plays-dots { display: inline-flex; gap: 4px; }
.plays-dots:empty { display: none; }
.plays-dots i { width: 9px; height: 9px; border-radius: 50%; background: var(--primary); transition: background-color var(--motion-base) var(--ease), transform var(--motion-base) var(--ease-spring); }
.plays-dots i.spent { background: var(--track); transform: scale(.8); }
/* How far it has played and how long it is stand at the two ends of the line they measure, written alike; a figure of the first rolls once when it changes, like the clock's. */
.player-line { display: flex; align-items: center; gap: 12px; }
.player .time { flex: none; display: inline-flex; min-width: 2.25rem; font-size: .9375rem; font-weight: 700; line-height: 1.15; font-variant-numeric: tabular-nums; font-family: var(--font-ui); color: var(--ink); }
.player .time.total { justify-content: flex-end; }
/* The length of the recording as a row of marks, filled as it is heard. It cannot be moved: there is no handle on it. */
.player .track { position: relative; flex: 1; height: 18px; overflow: hidden; background: repeating-linear-gradient(to right, var(--track) 0 3px, transparent 3px 7px); }
.player .track i { position: absolute; top: 0; bottom: 0; left: 0; width: 0; background: repeating-linear-gradient(to right, var(--primary) 0 3px, transparent 3px 7px); }
.player-note { margin: 0; font-size: .8125rem; color: var(--muted); }
/* Where a wrong answer takes marks away (decision 75): said under the task, before anything is answered. */
.penalty-note { display: flex; align-items: flex-start; gap: 8px; max-width: 90ch; margin: -6px 0 18px; font-size: .9375rem; color: var(--ink-soft); }
.penalty-note > .icon { flex: none; width: 18px; height: 18px; margin-block-start: 2px; color: var(--warn); }
/* Loudness: the speaker silences and restores, the slider sets it. No word: the picture says it, with no wave, one, or two. */
.player .volume { display: inline-flex; align-items: center; gap: 6px; padding-inline-end: 6px; }
.player .volume input { width: 112px; }
.volume-button {
    display: inline-grid; place-content: center; flex: none; width: var(--touch); height: var(--touch); padding: 0;
    background: transparent; color: var(--ink-soft); border: 0; border-radius: 50%; cursor: pointer;
    transition: background-color var(--motion-fast) var(--ease), color var(--motion-fast) var(--ease);
}
.volume-button:hover { background: var(--neutral-soft); color: var(--ink); }
.volume-button .icon { width: 22px; height: 22px; stroke-width: 2; }
.volume-button .wave, .volume-button .cross { transition: opacity var(--motion-base) var(--ease); }
.volume-button .cross, .volume[data-level="0"] .wave, .volume[data-level="1"] .wave.far { opacity: 0; }
.volume[data-level="0"] .cross { opacity: 1; }

/*
| A listed question. Its number is a small chip: grey while it waits, the brand's soft colour
| once it is answered, warm while it is the one being answered — the same states as its number
| in the footer, and no line round it in any. Flagged, it has the footer's folded corner.
| Under the question being answered lies a ground barely darker than the card; it is one
| thing for the part and glides from question to question (.here, placed by exam.js).
*/
.qnum {
    display: inline-flex; align-items: center; justify-content: center; flex: none;
    min-width: 34px; height: 34px; padding: 0 6px; border-radius: 9px;
    background: var(--emphasis); color: var(--on-emphasis); font: 700 .9375rem/1 var(--font-ui);
}
.qnum.small { min-width: 28px; height: 28px; border-radius: 7px; font-size: .8125rem; }
.question > .qnum {
    position: relative; min-width: 1.75rem; height: 1.75rem; padding: 0 .25rem; border-radius: 8px; font-size: .8125rem; font-variant-numeric: tabular-nums;
    background: var(--neutral-soft); color: var(--ink-soft);
    transition: background-color var(--motion-base) var(--ease), color var(--motion-base) var(--ease);
}
.question.answered > .qnum { background: var(--primary-soft); color: var(--primary-strong); }
.question.current > .qnum { background: var(--accent-soft); color: var(--accent-strong); }
.question > .qnum::after { content: ""; position: absolute; inset-block-start: 0; inset-inline-end: 0; width: 9px; height: 9px; background: var(--flag); border-start-end-radius: 8px; border-end-start-radius: 7px; opacity: 0; transform: scale(.3); transition: opacity var(--motion-base) var(--ease), transform var(--motion-base) var(--ease-spring); }
.question.flagged > .qnum::after { opacity: 1; transform: none; }

.card:has(> .question, .part-questions > .question) { position: relative; }
.here {
    position: absolute; top: 0; left: 0; width: 0; height: 0; border-radius: var(--radius-control); opacity: 0; pointer-events: none;
    background: var(--wash);
    transition: transform var(--motion-glide) var(--ease-spring), width var(--motion-glide) var(--ease-spring), height var(--motion-glide) var(--ease-spring), opacity var(--motion-base) var(--ease);
}
.question { position: relative; display: flex; align-items: flex-start; gap: .875rem; max-width: 47rem; margin-inline: -.5rem; padding: .5rem; border-radius: var(--radius-control); scroll-margin-block: 110px; }
/* With its answer beside it, the question is one row: its number stands in the middle of that row's height. */
.question:has(> .q-row) { align-items: center; }
/* With its answers under it, it is a block, and stands a little apart from the next. */
.question:has(> .q-main:not(.q-row)) + .question, .question + .question:has(> .q-main:not(.q-row)) { margin-block-start: .5rem; }
.card:not(.gliding) > :is(.question, .part-questions > .question, .part-beside > .part-questions > .question).current { background: var(--wash); }
.q-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: .625rem; }
.q-prompt { margin: 0; font-size: 1.125rem; line-height: 1.6; max-width: 80ch; }
.q-row { flex-direction: row; align-items: center; flex-wrap: wrap; gap: .5rem 1rem; }
.q-row .q-prompt { flex: 1 1 240px; }
.q-row select { width: auto; min-width: 150px; }

/*
| The pictures of an exam (App\Exams\Rendering\Pictures): a part's own, a question's, those written in a text. A
| picture says its width and height itself, already made to fit its size, so the page keeps its room before the
| file has arrived and nothing under it moves when it does. The stylesheet only keeps it inside its place.
*/
.picture { display: inline-block; vertical-align: bottom; max-inline-size: 100%; block-size: auto; border-radius: 6px; }
/* A picture that is not uploaded: what it was said to show, as words. */
.picture-missing { display: inline-block; padding: .25rem .625rem; border: 1px dashed var(--control); border-radius: 6px; color: var(--ink-soft); font: 400 .9375rem/1.5 var(--font-ui); }
.part-picture { margin: 0 0 1.25rem; }
/* A question's own picture stands above its words, on a line of its own also where the answer is beside the question;
   the number then stands at the top, beside the picture, not in the middle of all that height. */
.q-picture { flex: 0 0 100%; }
.question:has(.q-picture) { align-items: flex-start; }
.essay-task .q-picture { margin-block-end: .75rem; }
/*
| Beside its words, where the teacher chose (decision 143): the picture at the start of the question, at most 45% of it,
| and its words and answer in a column beside it. Where the question is too narrow for both (a phone, a column of
| questions), the picture stands above its words as before: the question is the container asked (`question`). The
| picture spans every row of the other column; the last of them is a flexible one, so that a tall picture lengthens that
| row and not the room between the words and the answer.
*/
.question:has(> .q-main > .q-picture.beside) { container: question / inline-size; }
@container question (min-width: 32rem) {
    .q-main:not(.q-row):has(> .q-picture.beside) { display: grid; grid-template-columns: fit-content(45%) minmax(0, 1fr); grid-template-rows: repeat(7, auto) 1fr; gap: 0 1.25rem; align-items: start; }
    .q-main:not(.q-row) > .q-picture.beside { grid-column: 1; grid-row: 1 / -1; }
    .q-main:not(.q-row):has(> .q-picture.beside) > :not(.q-picture) { grid-column: 2; }
    .q-main:not(.q-row):has(> .q-picture.beside) > :not(.q-picture) ~ :not(.q-picture) { margin-block-start: .625rem; }
}
.q-row > .q-picture.beside { flex: none; }

/*
| How a part is laid out where the teacher chose (decision 143; nothing of it where she did not):
| .part-beside > .part-side + .part-questions — the part's picture and text beside its questions, half and half or a
| third and two thirds; one under the other where the card is too narrow for both (on a phone, beside a reading text).
| .part-questions.cols-2 / .cols-3 — its questions in columns, never narrower than a question can be read in: fewer
| columns where the card is narrower. A question stays a child of its card's flow for public/js/exam.js, which lays
| the ground of the question being answered (.here) under it wherever it stands.
*/
.part-beside { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 1.5rem 2.5rem; }
.part-beside > * { min-width: 0; }
.part-beside.half > .part-side, .part-beside.half > .part-questions { flex: 1 1 calc(50% - 1.25rem); min-inline-size: min(100%, 18rem); }
.part-beside.third > .part-side { flex: 1 1 calc(33.333% - 1.667rem); min-inline-size: min(100%, 14rem); }
.part-beside.third > .part-questions { flex: 1 1 calc(66.667% - .833rem); min-inline-size: min(100%, 20rem); }
.part-side > :last-child { margin-block-end: 0; }
.part-side > .part-picture .picture { display: block; }
.part-questions.cols-2, .part-questions.cols-3 { display: grid; align-items: start; gap: .5rem 1.75rem; }
.part-questions.cols-2 { grid-template-columns: repeat(auto-fill, minmax(max(15rem, calc((100% - 1.75rem) / 2)), 1fr)); }
.part-questions.cols-3 { grid-template-columns: repeat(auto-fill, minmax(max(12rem, calc((100% - 3.5rem) / 3)), 1fr)); }
/* What is not a question (the options of a matching exercise) has the whole width, above the columns. */
.part-questions:is(.cols-2, .cols-3) > :not(.question) { grid-column: 1 / -1; }
/* In columns the rows keep one line: the room between questions is the grid's, not each question's own. */
.part-questions:is(.cols-2, .cols-3) > .question { max-width: none; margin-block-start: 0; }

/*
| A block of columns in a text (decision 144, RichText::columns()): two or three columns side by side, each a text of
| its own — words, a table, pictures, gaps. Never narrower than 14rem: fewer columns on a narrow card, one on a phone,
| read first to last as they are written. Its room above and below is a paragraph's.
*/
.text-columns { display: grid; align-items: start; gap: 1rem 2rem; margin-block: 0 1rem; }
.text-columns.cols-2 { grid-template-columns: repeat(auto-fill, minmax(max(14rem, calc((100% - 2rem) / 2)), 1fr)); }
.text-columns.cols-3 { grid-template-columns: repeat(auto-fill, minmax(max(12rem, calc((100% - 4rem) / 3)), 1fr)); }
.text-column { min-width: 0; }
.text-column > :first-child { margin-block-start: 0; }
.text-column > :last-child { margin-block-end: 0; }
.text-column .picture { display: block; margin-inline: auto; }

/*
| A box in a text (decision 145, RichText): a note, a tip, something important — the teacher's words in a frame of
| its kind, under its title where it has one. Its colours are the site's soft grounds with a line of the full colour at
| its start; what it is is said by its title, never by its colour alone. A dividing line is the text's own `---`.
*/
.text-box { margin-block: 0 1rem; padding: .875rem 1.125rem; border-inline-start: 4px solid var(--control); border-radius: var(--radius-control); background: var(--neutral-soft); }
.text-box.tip { border-color: var(--primary); background: var(--primary-soft); }
.text-box.important { border-color: var(--warn); background: var(--warn-soft); }
.text-box-title { margin: 0 0 .375rem; font-weight: 700; }
.text-box > :last-child { margin-block-end: 0; }
/*
| How a part looks where the teacher chose (decision 145, Part::looks()), on its card: its words large (for young
| learners, a picture book) or normal, and the room between its questions, compact, normal or roomy. What a part does
| not say it takes from the look of the whole exam (decision 146, Exam::looks(), on .exam and .booklet): for young
| learners its words are large and its questions roomy, compact its questions close. Sizes in rem, so that the
| student's own text size (Aa) still scales them.
*/
:is(.card.text-large, .look-young .card:not(.text-normal)) :is(.content, .task-text, .instructions, .q-prompt) { font-size: 1.3125rem; }
:is(.card.text-large, .look-young .card:not(.text-normal)) .choices label { font-size: 1.25rem; }
:is(.card.text-large, .look-young .card:not(.text-normal)) :is(.content, .instructions) table { font-size: 1.125rem; }
:is(.card.text-large, .look-young .card:not(.text-normal)) .content p:has(.blank),
:is(.card.text-large, .look-young .card:not(.text-normal)) .content:has(.blank) li:not(.line) { line-height: 2.75rem; }
:is(.card.spacing-compact, .look-compact .card:not(.spacing-normal, .spacing-roomy)) .question { padding-block: .25rem; }
:is(.card.spacing-compact, .look-compact .card:not(.spacing-normal, .spacing-roomy)) .question + .question { margin-block-start: 0; }
:is(.card.spacing-roomy, .look-young .card:not(.spacing-compact, .spacing-normal)) .question + .question { margin-block-start: 1.5rem; }
:is(.card.spacing-roomy, .look-young .card:not(.spacing-compact, .spacing-normal)) .part-questions:is(.cols-2, .cols-3) { row-gap: 1.5rem; }
:is(.card.spacing-roomy, .look-young .card:not(.spacing-compact, .spacing-normal)) .part-questions:is(.cols-2, .cols-3) > .question { margin-block-start: 0; }
/*
| The tables of an exam's texts striped (decision 146, .tables-stripes): no lines between the columns, a heavier line
| under the headings, every other row on the soft ground. Ruled in every line is the screen's own way, above.
*/
.tables-stripes :is(.content, .instructions) table { border-block: 1px solid var(--line); }
.tables-stripes :is(.content, .instructions) :is(th, td) { border-inline: 0; border-block: 0; }
.tables-stripes :is(.content, .instructions) th { background: transparent; border-block-end: 2px solid var(--line); }
.tables-stripes :is(.content, .instructions) tbody tr:nth-child(even) { background: var(--surface-subtle); print-color-adjust: exact; -webkit-print-color-adjust: exact; }
/* On paper the soft ground of the screen is hardly seen: a printed stripe is a light grey. */
@media print { .tables-stripes :is(.content, .instructions) tbody tr:nth-child(even) { background: color-mix(in srgb, var(--ink) 8%, white); } }
:is(.content, .text-column, .text-box, .instructions) > hr { margin-block: 1.25rem; border: 0; border-block-start: 1px solid var(--line); }
/* A picture that can be opened large is a button around it; pressed, it is shown in dialog.lightbox (public/js/ui.js):
   the picture alone over the page, as large as the window lets it be and never larger than it is. */
.picture-open { display: inline-block; vertical-align: bottom; max-inline-size: 100%; padding: 0; background: none; border: 0; border-radius: 6px; line-height: 0; cursor: zoom-in; }
.picture-open:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
dialog.lightbox { width: auto; max-width: none; padding: 0; background: transparent; border-radius: 10px; box-shadow: none; overflow: visible; cursor: zoom-out; }
dialog.lightbox > img { display: block; max-inline-size: calc(100vw - 32px); max-block-size: calc(100dvh - 32px); border-radius: 10px; background: var(--surface); box-shadow: var(--shadow-dialog); }
.lightbox-close { position: fixed; inset-block-start: 12px; inset-inline-end: 12px; display: grid; place-items: center; width: var(--touch); height: var(--touch); padding: 0; color: var(--ink); background: var(--surface); border: 0; border-radius: 50%; box-shadow: var(--shadow-raised); cursor: pointer; }
.lightbox-close .icon { width: 20px; height: 20px; }
/* An answer written under its question: a line to write on, as wide as a phrase. */
.short-answer {
    max-width: 22.5rem; min-height: var(--touch); padding: .25rem .125rem; font-weight: 600; color: var(--primary-strong); caret-color: var(--primary);
    background: transparent; border: 0; border-radius: 0; box-shadow: inset 0 -1.5px 0 var(--control);
}
.short-answer:hover { box-shadow: inset 0 -1.5px 0 var(--ink-faint); }
.exam .short-answer:focus-visible { box-shadow: inset 0 -2px 0 var(--accent); }

/*
| The options of a question. Each stands on the card with its letter in a small circle; the
| chosen one stands on a plate in the brand's soft colour, and the plate is one thing for the
| question: changing the answer slides it to the other option. Pressing the chosen option
| again takes the answer back. Several may be ticked in a question that asks for several:
| there each chosen one has its own ground and its letter a square.
*/
.choices { position: relative; display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 2px 8px; max-width: 47.5rem; margin-inline: -.625rem; }
.choices.stacked { grid-template-columns: 1fr; }
/* In the columns the teacher chose (decision 143); fewer where an option would be narrower than it can be read in. */
.choices.cols-2 { grid-template-columns: repeat(auto-fill, minmax(max(9rem, calc((100% - 8px) / 2)), 1fr)); }
.choices.cols-3 { grid-template-columns: repeat(auto-fill, minmax(max(8rem, calc((100% - 16px) / 3)), 1fr)); }
.choices.cols-4 { grid-template-columns: repeat(auto-fill, minmax(max(7rem, calc((100% - 24px) / 4)), 1fr)); }
.choices label {
    position: relative; z-index: 1; display: flex; align-items: center; gap: .75rem; min-height: var(--touch); padding: .375rem .625rem;
    font-size: 1.0625rem; font-weight: 400; line-height: 1.45; cursor: pointer; border-radius: var(--radius-control);
    transition: background-color var(--motion-base) var(--ease), color var(--motion-base) var(--ease), opacity var(--motion-base) var(--ease);
}
.choices input { position: absolute; inset: 0; width: 100%; height: 100%; min-height: 0; margin: 0; border: 0; opacity: 0; cursor: pointer; }
/* A choice closed (a question settled, a paper to read) stays the card it was: its field, unseen, stays unseen. */
.choices input:disabled { opacity: 0; cursor: default; }
.choices .opt-key {
    flex: none; display: inline-grid; place-content: center; width: 1.75rem; height: 1.75rem; border-radius: 50%;
    font: 700 .8125rem/1 var(--font-ui); background: var(--neutral-soft); color: var(--ink-soft);
    transition: background-color var(--motion-base) var(--ease), color var(--motion-base) var(--ease);
}
/* An option that is its own text has no letter: a small dot holds its place, and fills. */
.choices .opt-key:empty::after { content: ""; width: .5rem; height: .5rem; border-radius: 50%; background: var(--control-soft); transition: background-color var(--motion-base) var(--ease); }
.choices label:has([type="checkbox"]) .opt-key { border-radius: 7px; }
.choices label:not(:has(:checked)):hover { background: var(--surface-subtle); }
.choices label:has(:checked) .opt-key { background: var(--primary); color: var(--on-primary); }
.choices label:has(:checked) .opt-key:empty::after { background: var(--on-primary); }
.choices:not(.gliding) label:has(:checked), .choices label:has([type="checkbox"]:checked) { background: var(--primary-soft); }
.choices:has(:focus-visible) { box-shadow: none; }
.exam .choices label:has(:focus-visible) { box-shadow: var(--halo); }
.exam .choices input:focus-visible { box-shadow: none; }
.plate {
    position: absolute; top: 0; left: 0; width: 0; height: 0; border-radius: var(--radius-control); opacity: 0; pointer-events: none;
    background: var(--primary-soft);
    transition: transform var(--motion-glide) var(--ease-spring), width var(--motion-glide) var(--ease-spring), height var(--motion-glide) var(--ease-spring), opacity var(--motion-base) var(--ease);
}
/* Put where it belongs without being seen to travel there: the first time, and when the page is laid out anew. */
.plate.still, .here.still, .tab-glide.still, .qnav-here.still, .mark-glide.still, .steps.free li.glide.still { transition: none; }
/* First drawn, a stroke is drawn from its start, as a marker's is. */
.plate.fresh { animation: sweep 420ms var(--ease-soft); }
/*
| A few short words that are their own text (True / False), or a row of letters (A–H): one small
| switch beside the question. The chosen one is a plate in the brand's colour, and it is one
| plate: changing the answer slides it across. Taken back, it leaves by the side it stood on.
*/
.choices.labels, .choices.by-letter { display: inline-flex; flex-wrap: wrap; flex: none; gap: 0; max-width: 100%; margin: 0; padding: 2px; background: var(--neutral-soft); border-radius: 9px; }
/* The words of one switch are all as wide as the widest of them. */
.choices.labels { display: inline-grid; grid-template-columns: none; grid-auto-flow: column; grid-auto-columns: 1fr; }
.choices.labels label, .choices.by-letter label {
    justify-content: center; min-width: 3.75rem; min-height: 2rem; padding: 0 .75rem;
    font-size: .9375rem; font-weight: 600; line-height: 1.2; color: var(--ink-soft); border-radius: 7px; background: none;
}
.choices.by-letter label { min-width: 2.25rem; padding: 0 .5rem; }
.choices.labels input, .choices.by-letter input { inset-block: -.375rem; height: auto; }
.choices.labels .opt-key { display: none; }
.choices.labels label:hover, .choices.by-letter label:hover { background: none; color: var(--ink); }
.choices.labels label:has(:checked), .choices.by-letter label:has(:checked) { color: var(--on-primary); }
.choices.labels:not(.gliding) label:has(:checked), .choices.by-letter:not(.gliding) label:has(:checked) { background: var(--primary); }
/* The plate of a switch runs in a lane as large as the switch's inside, so that leaving, it is cut off at the edge. */
.plate-lane { position: absolute; inset: 2px; overflow: hidden; border-radius: 7px; pointer-events: none; }
.plate-lane .plate { border-radius: 7px; background: var(--primary); }
.plate.out { transition: transform 420ms var(--ease), opacity 60ms linear 400ms; }
/* A letter already given to another item: still free to choose, but stepped back, with a dot under it. */
.choices.by-letter label.used:not(:has(:checked)) { color: var(--muted); opacity: .6; }
.choices.by-letter label.used:not(:has(:checked))::after { content: ""; position: absolute; inset-block-end: 3px; inset-inline-start: calc(50% - 2px); width: 4px; height: 4px; border-radius: 50%; background: currentColor; }
/* The letters stand beside their item, in one column down the exercise, not at the far end of the card. */
.q-row:has(.by-letter) .q-prompt { flex: 0 1 300px; }
/*
| Options that are pictures ("choose the right picture", decision 64): a row of cards, each a picture in a frame of
| one shape and its letter under it. The frame keeps the row's room before the files arrive, and lets pictures of
| other shapes stand side by side; the picture is fitted into it, never cut. The chosen card stands on the plate that
| glides, and its letter fills, as every option's does; ticked ones (several to choose) each have their own ground.
| How many stand in a row is the number of options (.n2 … .n6): three in a row, four in a row on a wide screen and
| two by two on a phone, five and six as two rows of three.
*/
.choices.pictures { --cols: 3; grid-template-columns: repeat(var(--cols), minmax(0, 1fr)); gap: 6px 8px; max-width: calc(var(--cols) * 15rem); margin-inline: -.5rem; }
.choices.pictures.n2 { --cols: 2; }
.choices.pictures.n4 { --cols: 4; }
.choices.pictures label { flex-direction: column; align-items: center; gap: .5rem; padding: .5rem .5rem .625rem; border-radius: var(--radius-card); }
/* The frame holds the room; the picture lies in it whole, as large as the frame lets it be, whatever its shape. */
.opt-picture { position: relative; display: grid; place-items: center; align-self: stretch; aspect-ratio: 4 / 3; overflow: hidden; border-radius: 8px; background: var(--surface-subtle); }
.opt-picture .picture { position: absolute; inset: 0; inline-size: 100%; block-size: 100%; max-inline-size: none; object-fit: contain; border-radius: 0; }
.opt-picture .picture-missing { margin: .5rem; text-align: center; }
.choices.pictures .plate { border-radius: var(--radius-card); }
/* Pictures to match: the same cards, listed once above the items, each with its letter under it. */
.option-key.pictures { --cols: 3; grid-template-columns: repeat(var(--cols), minmax(0, 1fr)); gap: 12px; max-width: calc(var(--cols) * 15rem); padding: 12px; }
.option-key.pictures.n2 { --cols: 2; }
.option-key.pictures.n4 { --cols: 4; }
.option-key.pictures li { flex-direction: column; align-items: center; gap: 6px; }
.option-key.pictures .opt-picture { background: var(--surface); }

/* Where a question is looked at again: the items of an ordering question, with their letters, one to a line. */
.option-key.review-items { grid-template-columns: 1fr; gap: 4px; margin-block: 4px 8px; font-size: .9375rem; }

/* Where a question is looked at again (a result, the answers of a class): its pictures small, each with its letter. */
.review-options { list-style: none; display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 .5rem; padding: 0; }
.review-options li { display: flex; flex-direction: column; align-items: center; gap: 4px; inline-size: 7.5rem; font: 700 .8125rem/1 var(--font-ui); }
.review-options .opt-picture { inline-size: 100%; }

/* A list to open, for more options than can be laid out: a soft ground, no line round it. */
.question .select-button { background: var(--neutral-soft); border: 0; }
.question .select-button:hover, .question .select-button[aria-expanded="true"] { background: var(--track); }

/*
| Words to put in order ("you / Are / the USA / from"): a line the sentence is
| built on, and under it the words still to use. A word is pressed to move it
| between the two; on the line it can be dragged to another place.
*/
.word-order { display: flex; flex-direction: column; gap: 10px; max-width: 47.5rem; }
.word-line, .word-bank { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.word-line { min-height: 60px; padding: 8px 2px; box-shadow: inset 0 -1.5px 0 var(--control); }
.word-line:has(.word) .word-hint { display: none; }
.word-hint { padding-inline: 6px; font-size: .9375rem; color: var(--muted); }
.word-bank { padding: .625rem; background: var(--neutral-soft); border-radius: var(--radius-control); }
/* The letters of one word (decision 70): square tiles, close together on the line, so that they read as the word. */
.word-order.spelling .word { min-width: 2.75rem; padding-inline: 10px; text-align: center; font-size: 1.25rem; }
.word-order.spelling .word-line { gap: 3px; }
.word {
    min-height: var(--touch); padding: 6px 14px; font: inherit; font-size: 1.0625rem; font-weight: 600; cursor: pointer;
    background: var(--plate); color: var(--ink); border: 0; border-radius: 8px; box-shadow: var(--shadow-raised);
    transition: background-color var(--motion-base) var(--ease), color var(--motion-base) var(--ease), box-shadow var(--motion-base) var(--ease), transform var(--motion-base) var(--ease-spring);
}
.word:hover:not(:disabled) { transform: translateY(-1px); box-shadow: 0 3px 8px rgba(21, 35, 43, .16); }
/* The empty place of a word that is on the line. */
.word-bank .word.placed { background: var(--track); color: transparent; box-shadow: none; cursor: default; }
/* On the line a word is dragged, so the page must not scroll under the finger. */
.word-line .word { background: var(--primary-soft); color: var(--primary-strong); box-shadow: none; touch-action: none; cursor: grab; animation: land 320ms var(--ease-spring); }
.word.dragging { cursor: grabbing; box-shadow: var(--shadow-pop); opacity: .85; animation: none; }

/*
| Things to put in order (decision 66): the items one under another, each on a plate of the word box's make, with
| its place, its letter, a grip to carry it by and two arrows. A moved item glides to its place, and the others make
| room by gliding (exam.js writes where each was, `translate`, and lets it go). Once the student has moved one, the
| places take the brand's colour: the order is an answer.
*/
/*
| Things joined by lines (decision 97): columns side by side, each thing a plate like an item to put in order, the
| lines on a layer under them (public/js/ui.js draws them from where the things stand). The room between two columns
| is the room a line is drawn in. The thing pressed first stands on the accent's ground — it is where the student
| is — and a thing with a line has a dot of the brand's colour on its side: settled. On a result a line's verdict is
| its colour and its stroke (right: whole; wrong: dashed; one not drawn: dotted), and the rows under the board say
| it in words (exam/review/joins).
*/
.join { display: flex; flex-direction: column; gap: .5rem; max-width: 47.5rem; }
.join-hint { margin: 0; font-size: .9375rem; color: var(--muted); }
.join-board { position: relative; display: grid; align-items: start; column-gap: clamp(2rem, 9vw, 5rem); }
.join-board.cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.join-board.cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.join-board.cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); column-gap: clamp(1.25rem, 5vw, 3.5rem); }
.join-layer { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; pointer-events: none; }
.join-column { position: relative; z-index: 1; display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.join-title { min-height: 1.5rem; font-size: .8125rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
.join-item {
    display: flex; flex-wrap: wrap; align-items: center; gap: .25rem .5rem; min-height: var(--touch); padding: .5rem .75rem;
    font: inherit; font-size: 1.0625rem; line-height: 1.4; text-align: start; color: var(--ink);
    background: var(--plate); border: 0; border-radius: var(--radius-control); box-shadow: var(--shadow-raised);
    cursor: pointer; user-select: none; -webkit-user-select: none;
    transition: background-color var(--motion-fast) var(--ease), box-shadow var(--motion-fast) var(--ease);
}
.join-item:hover:not(:disabled) { box-shadow: var(--shadow-lift); }
.join-item.held { background: var(--accent-soft); }
/* The example's things, as a paper's example is set apart: a soft ground, softer words, its first with the word. */
.join-item.in-example { background: var(--group); color: var(--ink-soft); box-shadow: none; }
.join-item .example-label { margin: 0; }
.join-item:disabled { cursor: default; }
.join-text { overflow-wrap: break-word; hyphens: auto; }
.join-picture { flex-basis: 100%; }
.join-picture .picture { display: block; border-radius: 6px; }
.join-layer .join-line { fill: none; stroke: var(--primary); stroke-width: 2.5; stroke-linecap: round; }
.join-layer .join-line.given { stroke: var(--ink-faint); stroke-width: 2; }
.join-layer .join-line.pulling { stroke: var(--accent); stroke-dasharray: 2 6; }
.join-layer .join-line.good { stroke: var(--good); }
.join-layer .join-line.bad { stroke: var(--bad); stroke-dasharray: 8 6; }
.join-layer .join-line.missed { stroke: var(--ink-faint); stroke-dasharray: 2 5; }
.join-layer .join-end { fill: var(--primary); }
.join-layer .join-end.given, .join-layer .join-end.missed { fill: var(--ink-faint); }
.join-layer .join-end.good { fill: var(--good); }
.join-layer .join-end.bad { fill: var(--bad); }
.join-layer .join-end.pulling { fill: var(--accent); }

/*
| Things put in columns (decision 115): a table of columns, each a card with its heading, what the paper prints in it
| and ruled lines the words stand on, written as a gap is; under it the box of words to put in them. A word in the box
| that is in a column stays where it was, shown used, so that nothing in the box moves. The columns are as many to a
| row as the width holds, a phone showing two.
*/
.sort { display: flex; flex-direction: column; gap: .75rem; max-inline-size: 60rem; }
.sort-hint { margin: 0; font-size: .9375rem; color: var(--muted); }
.sort-table { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 7rem), 1fr)); gap: .5rem; align-items: start; }
.sort-column {
    display: flex; flex-direction: column; min-inline-size: 0; overflow: hidden;
    background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-control);
    transition: border-color var(--motion-base) var(--ease), box-shadow var(--motion-base) var(--ease);
}
.sort-title {
    display: block; inline-size: 100%; padding: .5rem .75rem; margin: 0; font: inherit; font-size: .9375rem; font-weight: 700; line-height: 1.3; text-align: start;
    color: var(--primary-strong); background: var(--primary-soft); border: 0; border-radius: 0; cursor: default;
}
.sort.holding .sort-column { border-color: var(--accent); cursor: pointer; }
.sort.holding .sort-title { cursor: pointer; }
.sort-column.over { box-shadow: var(--halo); }
.sort-cells { margin: 0; padding: .25rem .625rem .5rem; list-style: none; display: flex; flex-direction: column; }
.sort-row { display: flex; align-items: center; gap: .375rem; min-block-size: 2.75rem; box-shadow: inset 0 -1.5px 0 var(--control-soft); overflow-wrap: anywhere; }
/* What the paper prints in the table already is not a line to write on. */
.sort-row.sort-given { font-size: 1.0625rem; line-height: 1.4; color: var(--ink-soft); box-shadow: none; }
/* A word put in a column is written on its line, as an answer is in a gap. */
.sort-row .sort-thing {
    justify-content: flex-start; inline-size: 100%; min-height: 2.5rem; padding-inline: .125rem; font-size: 1.0625rem; font-weight: 600; text-align: start;
    color: var(--primary-strong); background: transparent; border-radius: 6px; box-shadow: none; cursor: pointer; touch-action: manipulation;
}
.sort-row .sort-thing:hover { background: var(--primary-soft); transform: none; box-shadow: none; }
.sort-box { display: flex; flex-wrap: wrap; gap: .5rem; margin: 0; padding: .625rem; list-style: none; inline-size: fit-content; max-inline-size: 46rem; background: var(--neutral-soft); border-radius: var(--radius-control); }
.sort-box:empty { display: none; }
.sort-thing { font-family: inherit; border: 0; touch-action: manipulation; }
.sort-thing::before, .sort-thing.used::before { content: none; }
.sort-thing.held { background: var(--accent-soft); }
.sort-box .sort-thing.used { cursor: pointer; box-shadow: inset 0 0 0 1.5px var(--control-soft); }
.sort-box .sort-thing.used:hover { transform: none; }
/* A table looked at again: the words as they were put, each with its verdict, and what was left out. */
.sort-row.sort-good { color: var(--good); font-weight: 600; }
.sort-row.sort-bad { color: var(--bad); font-weight: 600; text-decoration: line-through; text-decoration-thickness: 1.5px; }
.sort-row.sort-missed { color: var(--muted); font-style: italic; }
.sort-row .icon { flex: none; inline-size: 16px; block-size: 16px; }

/*
| A crossword (decision 115): the grid, a box to a letter, and the clues under it. A box is as wide as the width lets it
| be, up to the size of a finger; the number of the answer that begins in it stands in its corner. Where answers are laid
| one under another (a ladder) the highlighted column carries the hidden word. The answer being written is
| drawn on a plate of the brand's colour, the box in it on the warm one the exam screen uses for "here".
*/
.crossword { display: flex; flex-direction: column; gap: .75rem; max-inline-size: 47.5rem; }
.cross-hint { margin: 0; font-size: .9375rem; color: var(--muted); }
.cross-grid { display: grid; grid-template-columns: repeat(var(--cols), minmax(0, 1fr)); gap: 2px; inline-size: min(100%, calc(var(--cols) * 2.75rem)); }
.cross-void { aspect-ratio: 1; }
.cross-box {
    position: relative; display: block; aspect-ratio: 1; min-inline-size: 0; background: var(--surface); border: 1.5px solid var(--control); border-radius: 4px;
    transition: background-color var(--motion-fast) var(--ease), border-color var(--motion-fast) var(--ease);
}
.cross-box.key { background: var(--primary-soft); border-color: var(--primary); }
.cross-box.on-word { background: var(--accent-soft); }
.cross-box.key.on-word { background: var(--accent-soft); }
.cross-box:focus-within { border-color: var(--accent); box-shadow: var(--halo); }
.cross-box input, .cross-letter {
    display: block; inline-size: 100%; block-size: 100%; padding: 0; margin: 0; font: inherit; font-size: clamp(.875rem, 2.4vw, 1.25rem); font-weight: 700; line-height: 1;
    text-align: center; text-transform: uppercase; color: var(--primary-strong); caret-color: transparent; background: transparent; border: 0; border-radius: 3px; outline: 0;
}
.cross-letter { display: grid; place-items: center; }
.cross-n { position: absolute; inset-block-start: 1px; inset-inline-start: 3px; z-index: 1; font-size: clamp(.5rem, 1.5vw, .6875rem); font-weight: 700; line-height: 1; color: var(--muted); pointer-events: none; }
.cross-clues { display: flex; flex-direction: column; gap: .25rem; }
.cross-clues ol { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; }
.cross-clues li { display: flex; align-items: baseline; gap: .5rem; }
.cross-kind { margin: .25rem 0 0; font-size: .8125rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
.cross-clue {
    display: flex; align-items: baseline; gap: .625rem; min-block-size: var(--touch); padding: .375rem .5rem; margin-inline: -.5rem; font: inherit; font-size: 1.0625rem; line-height: 1.45; text-align: start;
    color: inherit; background: none; border: 0; border-radius: var(--radius-control); cursor: pointer;
    transition: background-color var(--motion-base) var(--ease);
}
.cross-clue b { flex: none; min-inline-size: 1.25rem; font-family: var(--font-ui); color: var(--primary-strong); }
button.cross-clue:hover { background: var(--neutral-soft); }
button.cross-clue.current { background: var(--accent-soft); }
.cross-clues li.cross-good .cross-clue { color: var(--good); }
.cross-clues li.cross-bad .cross-clue { color: var(--bad); }
.cross-clues li .icon { flex: none; inline-size: 16px; block-size: 16px; align-self: center; }
.cross-clues li.cross-good .icon { color: var(--good); }
.cross-clues li.cross-bad .icon { color: var(--bad); }
.cross-answer { font-weight: 700; color: var(--good); text-transform: uppercase; letter-spacing: .04em; font-size: .9375rem; }
/* A grid looked at again: the letter in each box, right in green, wrong in red and struck, and what was left out in the key's letter, faint. */
.cross-box.cross-good .cross-letter { color: var(--good); }
.cross-box.cross-bad { background: var(--bad-soft); border-color: var(--bad-line); }
.cross-box.cross-bad .cross-letter { color: var(--bad); text-decoration: line-through; text-decoration-thickness: 1.5px; }
.cross-box.cross-missed .cross-letter { color: var(--muted); font-style: italic; font-weight: 600; }

/*
| A chat and a flow of prompts, as a text draws them (decision 115; App\Support\RichText): the messages of a phone,
| received on the left and sent on the right on a tint of the brand's colour, the time small after each; and a
| speaking task's prompts, a box for each in the column of the student who says it, an arrow to where it goes next.
*/
.chat { display: flex; flex-direction: column; gap: .5rem; max-inline-size: 30rem; margin-block: .75rem; padding-block-end: .75rem; overflow: hidden; background: var(--neutral-soft); border-radius: var(--radius-card); }
.chat p, .flow p { margin: 0; }
.chat-title { padding: .5rem .75rem; font-size: .9375rem; font-weight: 700; text-align: center; color: var(--on-emphasis); background: var(--emphasis); }
.chat .chat-turn { display: flex; align-items: flex-end; gap: .5rem; max-inline-size: 85%; margin-inline: .75rem; padding: .5rem .75rem; line-height: 1.45; border-radius: 14px; }
.chat .chat-turn.in { align-self: flex-start; color: var(--ink); background: var(--surface); border-end-start-radius: 4px; }
.chat .chat-turn.out { align-self: flex-end; color: var(--ink); background: var(--primary-soft); border-end-end-radius: 4px; }
.chat-said { min-inline-size: 0; overflow-wrap: anywhere; }
.chat-time { flex: none; font-size: .6875rem; line-height: 1.6; color: var(--muted); }
.flow { max-inline-size: 38rem; margin-block: .75rem; }
.flow .flow-names { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; margin-block-end: .5rem; }
.flow-name { padding: .375rem .75rem; font-size: .9375rem; font-weight: 700; text-align: center; border-radius: var(--radius-control); }
.flow-name.first { color: var(--ink); background: var(--track); }
.flow-name.second { color: var(--primary-strong); background: var(--primary-soft); }
.flow-list { display: flex; flex-direction: column; gap: .5rem; margin: 0; padding: 0; list-style: none; }
.flow-turn { display: flex; align-items: center; gap: .5rem; }
.flow-turn.second { flex-direction: row-reverse; }
.flow-box { max-inline-size: 62%; padding: .5rem .75rem; line-height: 1.45; border: 1.5px solid var(--control-soft); border-radius: var(--radius-control); }
.flow-turn.first .flow-box { background: var(--neutral-soft); border-color: var(--control); }
.flow-turn.second .flow-box { background: var(--primary-soft); border-color: var(--primary); }
.flow-arrow { display: grid; flex: none; place-items: center; inline-size: 1.75rem; color: var(--ink-faint); }
.flow-arrow .icon { inline-size: 20px; block-size: 20px; }
/* The arrow of a box in the second column points back toward the first. */
.flow-turn.second .flow-arrow.left .icon { rotate: 180deg; }
.flow-arrow.down .icon { rotate: 90deg; }
.flow-turn.second .flow-arrow.down .icon { rotate: 90deg; }

.order { display: flex; flex-direction: column; gap: .5rem; max-width: 47.5rem; }
.order-hint { margin: 0; font-size: .9375rem; color: var(--muted); }
.order-items { position: relative; list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.order-item {
    position: relative; display: flex; align-items: center; gap: .5rem; min-height: var(--touch); padding: .25rem .25rem .25rem 0;
    background: var(--plate); border-radius: var(--radius-control); box-shadow: var(--shadow-raised);
    transition: translate var(--motion-glide) var(--ease-spring), box-shadow var(--motion-base) var(--ease);
}
.order-item.still, .order-item.dragging { transition: box-shadow var(--motion-base) var(--ease); }
.order-item.dragging { z-index: 2; box-shadow: var(--shadow-pop); }
.order-grip { flex: none; display: grid; place-items: center; align-self: stretch; width: 2.25rem; color: var(--muted); cursor: grab; touch-action: none; }
.order-grip .icon { width: 18px; height: 18px; }
.order-place {
    flex: none; display: inline-grid; place-content: center; min-width: 1.75rem; height: 1.75rem; border-radius: 50%;
    font: 700 .8125rem/1 var(--font-ui); background: var(--neutral-soft); color: var(--ink-soft);
    transition: background-color var(--motion-base) var(--ease), color var(--motion-base) var(--ease);
}
.order.arranged .order-place { background: var(--primary-soft); color: var(--primary-strong); }
.order-text { flex: 1; min-width: 0; padding-block: .25rem; font-size: 1.0625rem; line-height: 1.5; }
.order-text b { margin-inline-end: .375rem; font-family: var(--font-ui); color: var(--primary-strong); }
.order-moves { flex: none; display: flex; gap: 2px; }
.order-move {
    position: relative; display: grid; place-items: center; width: 2.25rem; height: 2.25rem; padding: 0;
    color: var(--ink-soft); background: none; border: 0; border-radius: 8px; cursor: pointer;
    transition: background-color var(--motion-fast) var(--ease), color var(--motion-fast) var(--ease), opacity var(--motion-fast) var(--ease);
}
/* Smaller than a finger: what can be pressed reaches round it, into the room between the items. */
.order-move::after { content: ""; position: absolute; inset: -4px -1px; }
.order-move .icon { width: 18px; height: 18px; }
.order-move:hover:not(:disabled) { background: var(--neutral-soft); color: var(--ink); }
.order-move:disabled { opacity: .3; cursor: default; }
.exam .order-move:focus-visible { box-shadow: var(--halo); }

/*
| Words of a text to press (decision 69): the text as it reads, each word a button that has no look of its own until
| it is pointed at or pressed. A pressed word stands on a soft plate of the brand's colour and is underlined: two
| signs, never colour alone. The lines are far enough apart for a finger, and the text keeps the lines it was written in.
*/
.word-text { margin: 0; max-width: 80ch; font-size: 1.125rem; line-height: 2.2; white-space: pre-line; }
.tap-word {
    display: inline; margin: 0 -.12em; padding: .06em .12em; font: inherit; line-height: 1.5; vertical-align: baseline; color: inherit;
    background: none; border: 0; border-radius: 5px; cursor: pointer; -webkit-box-decoration-break: clone; box-decoration-break: clone;
    transition: background-color var(--motion-base) var(--ease), color var(--motion-base) var(--ease);
}
.tap-word:hover { background: var(--neutral-soft); }
.tap-word[aria-pressed="true"] { color: var(--primary-strong); background: var(--primary-soft); text-decoration: underline 2px; text-underline-offset: .2em; }
.exam .tap-word:focus-visible { box-shadow: var(--halo); }
/* The syllables of a word: side by side, as the word is printed. Where stress is marked (`underline`) a pressed word or
   syllable is underlined as on the paper, and has no plate. */
.syllables { white-space: nowrap; }
.underlined .tap-word[aria-pressed="true"] { background: none; text-decoration-thickness: 3px; text-decoration-color: var(--primary); }
.underlined.review-words .w-pressed { background: none; text-decoration: underline 3px var(--primary); text-underline-offset: .2em; }
.underlined.review-words .w-off { background: none; text-decoration: line-through var(--bad); }
/* The same text where the question is looked at again: what was pressed on its plate, struck through where it was not
   to be pressed; the words to press underlined. */
.review-words { margin-block: 4px 8px; font-size: 1rem; line-height: 1.9; }
.review-words span { padding: .1em .12em; margin: 0 -.12em; border-radius: 5px; -webkit-box-decoration-break: clone; box-decoration-break: clone; }
.review-words .w-pressed { background: var(--primary-soft); color: var(--primary-strong); }
.review-words .w-off { background: var(--bad-soft); color: var(--bad); text-decoration: line-through; }
.review-words .w-key { text-decoration: underline 2px var(--good); text-underline-offset: .2em; }

/*
| Matching ("1–5 with A–E"): the lettered options once, as a list above the
| questions, and beside each question a row of the letters alone.
*/
.option-key { list-style: none; margin: 0 0 8px; padding: 12px 16px; display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 6px 24px;
    background: var(--neutral-soft); border-radius: var(--radius-control); }
.option-key li { display: flex; align-items: baseline; gap: 10px; }
.option-key b { flex: none; min-width: 1.4em; font-family: var(--font-ui); }

/*
| The lettered list the gaps of a text share ("use the words A–I", "sentences A–G": decision 65): once, after the
| text, each option its letter and its words. Where the script runs each is a word to press or carry into a gap
| (.box-word, as in the word box); one that stands in a gap is struck out, and can still be used again.
*/
.option-key.bank { gap: 8px 10px; padding: 10px; margin-block: 4px 12px; }
.option-key.bank.sentences { grid-template-columns: 1fr; }
.option-key.bank li { display: block; }
.option-key.bank li > span { display: flex; align-items: baseline; gap: .625rem; }
.option-key.bank .box-word { inline-size: 100%; padding: .375rem .75rem; font-weight: 400; line-height: 1.45; }
.option-key.bank .box-word::before { content: none; }
.option-key.bank .box-word b { color: var(--primary-strong); }
.option-key.bank .box-word.used > span { text-decoration: line-through; text-decoration-thickness: 1.5px; }
.option-key.bank .box-word.used b { color: var(--muted); }
/* Names to put on the people of a picture, each its own text (decision 71): a word box, the words side by side. */
.option-key.bank.names { display: flex; flex-wrap: wrap; gap: .5rem; inline-size: fit-content; max-width: 46rem; padding: .625rem; }
.option-key.bank.names .box-word { inline-size: auto; padding: .25rem .875rem; font-weight: 600; line-height: 1.3; }
.option-key.bank.names .box-word::before { content: ""; }
.option-key.bank.names .box-word.used > span { text-decoration: none; }

/*
| Missing letters ("w _ m _ _"): the word as a row of cells. A printed letter is
| a filled cell, a missing one a line of the same width for one letter.
*/
.letters-block { display: inline-flex; flex-direction: column; align-items: flex-start; gap: 6px; margin-inline: 2px; vertical-align: top; max-width: 100%; }
.letters { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 4px; }
/* The same word in ordinary writing, as it is being typed. */
.letters-word { font-size: 1.125rem; font-weight: 600; letter-spacing: .06em; color: var(--ink-soft); }
.letters > span, .letters > input {
    flex: none; width: 36px; height: var(--touch); min-height: 0; padding: 0;
    display: inline-grid; place-content: center; text-align: center;
    font: 600 1.25rem/1 var(--font-ui); border-radius: 8px;
}
.letters > span { background: var(--neutral-soft); color: var(--ink); }
.letters > span.joint { width: 14px; background: transparent; }
.letters > input { background: transparent; border: 0; border-radius: 0; color: var(--primary-strong); caret-color: var(--primary); box-shadow: inset 0 -2px 0 var(--control); transition: box-shadow var(--motion-base) var(--ease); }
.letters > input:not(:placeholder-shown) { box-shadow: inset 0 -2px 0 var(--control-soft); }
.exam .letters > input:focus-visible { box-shadow: inset 0 -2px 0 var(--accent); }

/*
| A word begun in a gap of a text ("The inf_ _ _ _ _ _ _ _ was clear": TOEFL's Complete the Words, the C-test;
| decision 67): its printed letters are the text's own writing, and each missing letter a short line of its own, in
| the size of the text. What can be pressed reaches into the room between the lines, as for every gap.
*/
.blank.lettered-word { align-items: baseline; padding-inline: 0; box-shadow: none; cursor: default; }
.blank.lettered-word:hover { box-shadow: none; }
.blank.lettered-word::before { content: none; }
.blank.lettered-word .letters { flex-wrap: nowrap; align-items: baseline; gap: 0; }
.blank.lettered-word .letters > span { display: inline; width: auto; height: auto; padding: 0; font: inherit; background: none; border-radius: 0; }
.blank.lettered-word .letters > span.joint { width: .3em; }
.blank.lettered-word .letters > input {
    width: .95em; height: 2.5rem; margin-block: calc((1.5em - 2.5rem) / 2); margin-inline: 1px; font: inherit; font-weight: 600; line-height: 1.5;
    box-shadow: none; background: linear-gradient(var(--control), var(--control)) center bottom calc((2.5rem - 1.5em) / 2) / 100% 1.5px no-repeat;
}
.blank.lettered-word .letters > input:not(:placeholder-shown) { box-shadow: none; background-image: linear-gradient(var(--control-soft), var(--control-soft)); }
.exam .blank.lettered-word .letters > input:focus-visible { box-shadow: none; background-image: linear-gradient(var(--accent), var(--accent)); background-size: 100% 2px; }
.blank.lettered-word.current > b { color: var(--accent-strong); }

/* A text with gaps: notes, a table, a summary, the sentences of an exercise. */
.content { font-size: 1.125rem; }
/*
| The sentences of an exercise, one to a line, each with its own gap (PartContentRenderer::asLines()): a list
| whose lines have their question's number at their start, as a paper prints them. The number is the same small
| chip as a listed question's, with the same three states, and the ground under the line being answered is the
| same one thing that glides (.here, in .rows). The gap of such a line shows no number of its own.
*/
.rows { position: relative; max-width: 46rem; margin: 0 0 .75em; }
.content .lines { list-style: none; max-width: none; margin: 0; padding: 0; }
.content .line {
    position: relative; display: grid; grid-template-columns: 1.75rem minmax(0, 1fr); column-gap: .875rem; align-items: baseline;
    margin-inline: -.5rem; padding: .25rem .5rem; border-radius: var(--radius-control); line-height: inherit;
}
.content .lines .line > p, .content .lines .line > p:has(.blank) { max-width: none; margin: 0; line-height: 2.1; }
.qn {
    position: relative; display: inline-flex; align-items: center; justify-content: center; flex: none;
    width: 1.75rem; height: 1.75rem; border-radius: 8px;
    font: 700 .8125rem/1 var(--font-ui); font-variant-numeric: tabular-nums;
    background: var(--neutral-soft); color: var(--ink-soft);
    transition: background-color var(--motion-base) var(--ease), color var(--motion-base) var(--ease);
}
.line:has(.blank.answered) > .qn, .line:has(input[type="text"]:not(:placeholder-shown)) > .qn, .line:has(:checked:not([value=""])) > .qn { background: var(--primary-soft); color: var(--primary-strong); }
.line:has(.blank.current) > .qn { background: var(--accent-soft); color: var(--accent-strong); }
.qn::after { content: ""; position: absolute; inset-block-start: 0; inset-inline-end: 0; width: 9px; height: 9px; background: var(--flag); border-start-end-radius: 8px; border-end-start-radius: 7px; opacity: 0; transform: scale(.3); transition: opacity var(--motion-base) var(--ease), transform var(--motion-base) var(--ease-spring); }
.line:has(.blank.flagged) > .qn::after { opacity: 1; transform: none; }
.rows:not(.gliding) .line:has(.blank.current) { background: var(--wash); }
.line .blank > b { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
/* The lines of a paragraph with gaps, and of every item of a list that has some, are set apart alike, so that
   the fields of one line stand clear of those of the next and each can be pressed (2.5rem, as tall as a number
   of the footer on a phone). A paragraph without a gap (a heading, "Dear Sam,") keeps the lines of plain text. */
.content p:has(.blank), .content:has(.blank) li:not(.line) { line-height: 2.5rem; }
/* Such lines carry their own room above and below, so a paragraph of them needs less after it. */
.content p:has(.blank) { margin-block-end: .5em; }
.content > p, .content > ul, .content > ol, .instructions > p { max-width: 90ch; }
.content > :last-child { margin-block-end: 0; }
.content h3 { margin: 0 0 8px; font-size: 1.25rem; }
.content table, .instructions table { width: 100%; border-collapse: collapse; font-size: 1rem; }
.content th, .content td, .instructions th, .instructions td { border: 1px solid var(--line); padding: 10px 12px; text-align: start; vertical-align: top; }
/* A cell is a box of its own: a line with a field in it is as tall as the field, the lines of plain text stay together.
   The first lines of a row stand on one line of writing, so that "Surname" reads across to its gap. */
.content th, .content td { line-height: 1.6; vertical-align: baseline; }
.content th, .instructions th { background: var(--surface-subtle); line-height: 1.4; }
/* Markdown tables always have a header row; one left empty (a conversation, a form) is not shown. */
.content thead:not(:has(th:not(:empty))), .instructions thead:not(:has(th:not(:empty))) { display: none; }
/*
| A quote is the word box of a paper: "> beach · cliff · island". Where the gaps under it are
| written in, each word of it is a small plate to press into the gap being answered, or to pick
| up and carry to any gap (exam.js). A word that is used is struck out, as a student strikes it
| out on the paper, and can still be used again.
*/
.content blockquote, .instructions blockquote {
    margin: 0 0 12px; padding: 10px 14px;
    background: var(--neutral-soft); color: var(--ink); border-radius: var(--radius-control);
}
.content blockquote p, .instructions blockquote p { margin: 0; line-height: 1.7; }
blockquote.word-box p { display: flex; flex-wrap: wrap; gap: .5rem; }
blockquote.word-box { max-width: 46rem; padding: .625rem; }
.word-box .sep { display: none; }
.box-word {
    position: relative; display: inline-flex; align-items: center; min-height: 2.25rem; padding: .25rem .875rem;
    font-size: 1.0625rem; font-weight: 600; line-height: 1.3; cursor: grab; touch-action: none; user-select: none; -webkit-user-select: none;
    color: var(--ink); background: var(--plate); border-radius: 8px; box-shadow: var(--shadow-raised);
    transition: color var(--motion-base) var(--ease), background-color var(--motion-base) var(--ease), box-shadow var(--motion-base) var(--ease), transform var(--motion-base) var(--ease-spring), opacity var(--motion-base) var(--ease);
}
.box-word::before { content: ""; position: absolute; inset-inline: .7rem; inset-block-start: 52%; height: 1.5px; background: currentColor; transform: scaleX(0); transform-origin: 0 50%; transition: transform 280ms var(--ease-out); }
[dir="rtl"] .box-word::before { transform-origin: 100% 50%; }
.box-word:hover { transform: translateY(-1px); box-shadow: 0 3px 8px rgba(21, 35, 43, .16); }
.box-word.used { background: transparent; color: var(--muted); box-shadow: none; }
.box-word.used::before { transform: scaleX(1); }
.box-word.lifted { opacity: .35; transform: none; box-shadow: none; }
.box-word.example { cursor: default; pointer-events: none; }
.slot-word.lifted { opacity: .35; }
/* The word in the hand. Moved to a point measured on the screen (translate, set by exam.js), so its origin is the physical corner. */
.carry {
    position: fixed; z-index: 60; top: 0; left: 0; padding: .25rem .875rem; pointer-events: none;
    font-size: 1.0625rem; font-weight: 600; line-height: 1.3; white-space: nowrap; rotate: -3deg;
    color: var(--on-primary); background: var(--primary); border-radius: 8px; box-shadow: var(--shadow-pop);
}
.exam.carrying, .exam.carrying * { cursor: grabbing !important; user-select: none; -webkit-user-select: none; }
.content u, .instructions u, .q-prompt u, .passage u { text-underline-offset: .18em; }
.content ul, .content ol { padding-inline-start: 1.2em; margin: 0 0 .5em; }
/* A table of a text stands in a box of its own (RichText::boxTables()): one too wide for its place scrolls sideways
   there, and nothing else of the text does. It never scrolls up and down: a box that may shows a bar for as long as
   something in it is on its way into place. */
.table-box { overflow-x: auto; overflow-y: hidden; }
.content .table-box { margin-block-end: 1.25rem; }
.content > * + h3 { margin-block-start: 1.25rem; }

/*
| What a paper sets apart (App\Support\RichText): its examples and its conversations.
|   An example — a paragraph or a cell that begins with "Example", a line 0 — stands on a soft ground of its own
|   with its word (or its 0) as a small label, and its answer, the bold in it, is drawn as a gap already
|   answered: the student sees how an answer looks before giving one. Not bold, not the colour of the task.
|   A conversation — lines that each begin with a speaker's name in bold — sets the names in a column of
|   their own, and what each says beside it; a gap in it is a gap as anywhere. In an exercise (.content) each
|   conversation is a box of its own, its number a small label over it (.dialogue-n: "Conversation 2", never a
|   number a question could be taken for), its speakers' names on two grounds — who begins, who answers — and the
|   paper's example a turn set apart where it stands (.turn.example). A part can ask for its conversations as they
|   are written (App\Enums\DialogueLook): then none of this is drawn.
*/
.example { max-width: 90ch; padding: .375rem .75rem; border-radius: var(--radius-control); background: var(--group); font-weight: 400; color: var(--ink-soft); }
p.example { margin-block: 0 .75rem; }
.example-label {
    display: inline-flex; align-items: center; margin-inline-end: .375rem; padding: .0625rem .5rem; vertical-align: .1em;
    font: 600 .6875rem/1.4 var(--font-ui); letter-spacing: .04em; text-transform: uppercase; color: var(--muted);
    background: var(--surface); border-radius: var(--radius-pill);
}
.example strong, .instructions .example strong { padding-inline: .2em; font-weight: 600; color: var(--primary-strong); box-shadow: inset 0 -1.5px 0 var(--control-soft); }
td.example { padding: .5rem .75rem; border-radius: 0; }
td.example .example-label { display: flex; inline-size: fit-content; margin-block-end: .25rem; }
/* The example of numbered lines is their line 0: its number is a label, dashed, and the line is not to answer. */
.content .line.example { margin-block-end: .25rem; background: var(--group); }
.content .line.example > .qn { background: transparent; color: var(--muted); box-shadow: inset 0 0 0 1.5px var(--control-soft); }
.content .line.example > p { color: var(--ink-soft); }
.content .line.example strong { padding-inline: .2em; font-weight: 600; color: var(--primary-strong); box-shadow: inset 0 -1.5px 0 var(--control-soft); }
.dialogue { display: grid; grid-template-columns: max-content minmax(0, 1fr); align-items: baseline; gap: .25rem 1rem; max-width: 90ch; margin-block: 0 1rem; }
.dialogue .turn { display: contents; }
.dialogue .speaker { font-weight: 700; color: var(--primary-strong); }
.dialogue .speaker, .dialogue .said { line-height: 1.6; }
.dialogue-n { grid-column: 1 / -1; margin: 0; font: 700 .75rem/1.6 var(--font-ui); letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.content .dialogue { gap: .375rem .875rem; padding: .875rem 1.125rem 1rem; border-radius: var(--radius-card); background: var(--surface-subtle); }
.content .dialogue + .dialogue { margin-block-start: .25rem; }
.content .dialogue .speaker {
    display: inline-flex; justify-content: center; justify-self: start; min-inline-size: 1.75rem; padding: .0625rem .5rem;
    font: 700 .8125rem/1.5 var(--font-ui); border-radius: var(--radius-pill); background: var(--primary-soft); color: var(--primary-strong);
}
.content .dialogue .voice-2 > .speaker { background: var(--surface); color: var(--ink-soft); box-shadow: inset 0 0 0 1.5px var(--control-soft); }
.content .dialogue:has(.blank) .said { line-height: 2.5rem; }
/* The paper's example is a turn like the others, set apart where it stands: a soft ground, softer words, its label. */
.content .dialogue .turn.example > .said { margin-inline: -.5rem; padding-inline: .5rem; border-radius: 8px; background: var(--group); color: var(--ink-soft); }

/*
| A gap: a line to write on, as on the paper — square, like a line drawn with a ruler, with
| its small number at its start. Three kinds: a gap to write in (label.blank > b + .gap-field >
| input), a gap whose few options stand in the sentence to be pressed (.blank.pick > b +
| label > radio + span), and a gap with a list to open (.blank > b + select).
|   to do:          a grey line and a small grey number
|   being answered: a warm line grows along it from the middle, and the number is warm
|   answered:       the words and the number in the brand's colour, the line light
| A gap to write in is as long as the longest answer of its own exercise needs, the same for
| every gap of that exercise (--gap-w, from .content.gaps-short / .gaps-long): short answers
| get short gaps, and no gap says anything about its own answer. A longer answer moves along
| inside it; typing never moves the words after it.
*/
.content { --gap-w: 6.5em; }
.content.gaps-short { --gap-w: 4.25em; }
.content.gaps-long { --gap-w: 9.5em; }
/* The words in brackets beside a gap ("(not take)") are one thing to read: a line does not break inside them. */
.content .cue { white-space: nowrap; }
.blank {
    display: inline-flex; align-items: baseline; vertical-align: baseline; position: relative; isolation: isolate;
    max-width: 100%; margin-inline: .125em; padding-inline: .2em;
    font: inherit; line-height: 1.5; white-space: nowrap; cursor: text;
    box-shadow: inset 0 -1.5px 0 var(--control);
    scroll-margin-block: 110px;
    transition: box-shadow var(--motion-base) var(--ease), background-color var(--motion-base) var(--ease);
}
.blank:hover { box-shadow: inset 0 -1.5px 0 var(--ink-faint); }
.blank > b {
    flex: none; margin-inline-end: .3em;
    font: 700 .6875em/1 var(--font-ui); font-variant-numeric: tabular-nums; color: var(--muted);
    transition: color var(--motion-base) var(--ease);
}
.blank::before {
    content: ""; position: absolute; inset-inline: 0; inset-block-end: 0; height: 2px; background: var(--accent);
    transform: scaleX(0); transition: transform 360ms var(--ease-out);
}
.blank:focus-within::before, .blank.current::before { transform: scaleX(1); }
.blank:has(input[type="text"]:not(:placeholder-shown)), .blank:has(:checked:not([value=""])), .blank.answered { box-shadow: inset 0 -1.5px 0 var(--control-soft); }
.blank:has(input[type="text"]:not(:placeholder-shown)) > b, .blank:has(:checked:not([value=""])) > b, .blank.answered > b { color: var(--primary); }
.blank.current > b { color: var(--accent-strong); }
/* A word of the box is being carried over this gap: it opens to take it. */
.blank.over { background: var(--primary-soft); box-shadow: inset 0 -2px 0 var(--primary); }
.blank.flagged > b { text-decoration: underline; text-decoration-color: var(--flag); text-decoration-thickness: 2px; text-underline-offset: 2px; }
.exam .blank :focus-visible { box-shadow: none; }
/* The field is lower than a finger is wide, so what can be pressed reaches into the room between the lines. */
label.blank::after, .blank .select-button::after { content: ""; position: absolute; z-index: -1; inset-inline: 0; inset-block: calc((1.5em - 2.5rem) / 2); }

/* To write in. */
.blank .gap-field { position: relative; display: inline-flex; }
.blank input[type="text"] {
    position: relative; z-index: 1; width: var(--gap-w); min-width: 0; min-height: 0; margin: 0; padding: 0;
    font: inherit; font-weight: 600; text-align: center; color: var(--primary-strong); caret-color: var(--primary);
    background: transparent; border: 0; border-radius: 0;
}
/* In a table the gap gives way to a narrow column before the table has to scroll. */
td .blank input[type="text"] { width: min(var(--gap-w), 6.5em); }
/* What is written, letter by letter: each comes up onto the line as it is typed, and goes down off it when it is
   rubbed out. The field itself holds the cursor; where the answer is longer than the gap, or is not written in
   letters that stand apart, the field shows its own text and this layer steps aside (exam.js, .inked). */
.blank .ink { position: absolute; inset: 0; z-index: 2; display: none; overflow: hidden; white-space: pre; text-align: center; pointer-events: none; font-weight: 600; color: var(--primary-strong); }
.blank.inked .ink { display: block; }
.blank .ink i { display: inline-block; font-style: normal; white-space: pre; }
.blank .ink i.new { animation: letter 260ms var(--ease-out) both; }
.blank .ink i.gone { position: absolute; animation: letter-out 240ms var(--ease) forwards; }
.blank.inked input[type="text"], .blank.inked input[type="text"]::selection { color: transparent; }

/* To choose in: the options of a paper, on one very soft ground that says "one of these", a stroke between them.
   Choosing one draws a marker's stroke over it; the others and the strokes between them step well back, so the
   sentence reads with its answer in it. The stroke is one thing for the gap: changing the answer slides it. */
.blank.pick { flex-wrap: wrap; align-items: center; padding: 3px; margin-inline: .1em; border-radius: 8px; background: var(--group); box-shadow: none; white-space: normal; cursor: default; }
.blank.pick::before { content: none; }
/* Never the line of a gap to write in: not at rest, not under the pointer, not once it is answered. */
.blank.pick:hover, .blank.pick:has(:checked:not([value=""])), .blank.pick.answered { box-shadow: none; }
.blank.pick > b { margin-inline: .3em .1em; }
.blank.pick label {
    position: relative; z-index: 1; display: inline-block; padding: 0 .3em; border-radius: 5px;
    font: inherit; line-height: 1.3; white-space: nowrap; cursor: pointer; color: var(--ink);
    transition: color var(--motion-base) var(--ease), opacity var(--motion-base) var(--ease);
}
.blank.pick input { position: absolute; inset-inline: -.1em; inset-block: -.5rem; width: auto; height: auto; min-height: 0; margin: 0; border: 0; border-radius: 0; opacity: 0; cursor: pointer; }
.blank.pick label + label { margin-inline-start: .8em; }
.blank.pick label + label::before { content: "/"; position: absolute; inset-inline-start: -.8em; width: .8em; text-align: center; font-weight: 400; color: var(--muted); pointer-events: none; transition: opacity var(--motion-base) var(--ease); }
.blank.pick label:has(:checked) { color: var(--primary-strong); }
.blank.pick:not(.gliding) label:has(:checked) { background: var(--primary-soft); }
.blank.pick:has(:checked) label:not(:has(:checked)) { opacity: .36; }
.blank.pick:has(:checked) label:has(:checked)::before { opacity: .36; }
.blank.pick:has(:checked) label:not(:has(:checked)):hover { opacity: .8; }
.blank.pick .plate { border-radius: 5px; }
.exam .blank.pick:has(:focus-visible) { box-shadow: var(--halo); }

/* Where the device draws its own colours, the fill that says "chosen" would be gone: the system's own pair says it. */
@media (forced-colors: active) {
    .blank.pick label:has(:checked), .choices label:has(:checked) { forced-color-adjust: none; background: Highlight; color: HighlightText; }
}

/* With a list to open, for more options than fit a line. */
.blank:has(select) { cursor: default; }
.blank select { width: auto; min-width: 7.5em; min-height: 0; padding-block: 0; border: 0; border-radius: 0; background-color: transparent; }
.exam .blank:has(.select-button:focus-visible) { box-shadow: inset 0 -1.5px 0 var(--control), var(--halo); }

/* Under a word box whose words are the gap's own list (exam.js): the gap is a place a word is put in — pressed
   in, carried in, and pressed again to send it back. The list is still there, unseen, and holds the answer. */
.blank.slot .select, .blank.slot > select { display: none; }
.blank.slot { cursor: pointer; }
.slot-word {
    min-width: var(--gap-w); padding: 0; font: inherit; font-weight: 600; line-height: 1.5; text-align: center; color: var(--primary-strong);
    background: transparent; border: 0; border-radius: 0; cursor: pointer; touch-action: none;
}
.slot-word::before { content: "\200b"; }
.slot-word.landed > span { display: inline-block; animation: land 320ms var(--ease-spring); }
.exam .slot-word:focus-visible { box-shadow: none; }
.exam .blank.slot:has(:focus-visible) { box-shadow: inset 0 -1.5px 0 var(--control), var(--halo); }

/*
| A picture with gaps on it (decision 71; App\Exams\Rendering\PlacePicture, exam/questions/partials/place-picture):
| a diagram whose boxes are to be labelled, a drawing whose people are to be named. Each gap is drawn where the
| picture says (--x, --y: hundredths of its width and height, from its top left corner, so the picture is never
| mirrored: the frame is left to right) and all as wide as the widest needs (--place-w). Two ways of showing it:
|   on the picture: each gap a small raised box centred on its place, moved in where it would stand over the edge;
|   listed:         the picture carries a numbered mark at each place, and the gaps stand under it, one after another.
| On the picture where the picture is drawn wide enough for no box to touch another. That width (--fits, in rem) is
| the server's to find; the stylesheet compares it with the width the picture has. The gauge is a box --fits
| times narrower than its place, so one container query (at 16rem) asks "is the place at least --fits wide?" for
| every picture; inside it, the frame is as wide as the place again. A picture whose boxes never stand clear says
| no width, and is always listed. Nothing is measured on the screen, so the page is drawn once, the right way.
*/
.place-picture { --gap-w: 6.5em; --place-h: 2.5rem; margin: 0 0 1.25rem; }
.place-picture.gaps-short { --gap-w: 4.25em; }
.place-picture.gaps-long { --gap-w: 9.5em; }
.place-gauge.measured { container: places / inline-size; inline-size: calc(100% * 16 / var(--fits)); }
.place-frame { display: grid; inline-size: max-content; max-inline-size: 100%; }
.place-gauge.measured .place-frame { max-inline-size: calc(100% * var(--fits) / 16); }
.place-art, .place-pins { grid-area: 1 / 1; }
.place-art { line-height: 0; }
/* The marks of the places, for the gaps listed under the picture: the gap's number, on the picture's own ground. */
.place-pins { position: relative; pointer-events: none; }
.pin {
    position: absolute; inset-inline-start: var(--x); inset-block-start: var(--y); translate: -50% -50%;
    display: grid; place-items: center; min-inline-size: 1.75rem; block-size: 1.75rem; padding-inline: .375rem;
    font: 700 .8125rem/1 var(--font-ui); font-variant-numeric: tabular-nums; color: var(--ink);
    background: var(--surface); border-radius: var(--radius-pill); box-shadow: 0 0 0 2px var(--primary), var(--shadow-raised);
    pointer-events: auto; cursor: pointer;
    transition: background-color var(--motion-base) var(--ease), color var(--motion-base) var(--ease), box-shadow var(--motion-base) var(--ease);
}
/* Smaller than a finger is wide: what can be pressed reaches round it. */
.pin::after { content: ""; position: absolute; inset: calc((1.75rem - var(--touch)) / 2); border-radius: 50%; }
.pin.answered { color: var(--on-primary); background: var(--primary); }
.pin.current { box-shadow: 0 0 0 2px var(--accent), 0 0 0 5px var(--accent-soft), var(--shadow-raised); }
.pin.flagged { text-decoration: underline; text-decoration-color: var(--flag); text-decoration-thickness: 2px; text-underline-offset: 2px; }
/* On a result each number is marked as its answer was: a tick's colour, a cross's, nothing given. The words under
   the picture say it too. */
.pin.good { color: var(--on-state); background: var(--good); box-shadow: 0 0 0 2px var(--good), var(--shadow-raised); }
.pin.bad { color: var(--on-state); background: var(--bad); box-shadow: 0 0 0 2px var(--bad), var(--shadow-raised); }
.pin.none, .pin.open { color: var(--muted); box-shadow: 0 0 0 2px var(--control), var(--shadow-raised); }
.review-places .pin { cursor: default; }
/* Listed: the gaps under the picture, each with its number, as many to a line as fit. */
.places { grid-row: 2; contain: inline-size; display: flex; flex-wrap: wrap; gap: 0 1.5rem; margin: .5rem 0 0; padding: 0; list-style: none; line-height: 2.5rem; }
.place { min-inline-size: 0; }
/* On the picture: the gaps stand where their marks would, and the marks are not needed. */
@container places (inline-size >= 16rem) {
    .places { grid-area: 1 / 1; position: relative; display: block; margin: 0; contain: none; pointer-events: none; line-height: 1.5; }
    .place {
        position: absolute; z-index: 1;
        inset-inline-start: clamp(0px, calc(var(--x) - var(--place-w) / 2), calc(100% - var(--place-w)));
        inset-block-start: clamp(0px, calc(var(--y) - var(--place-h) / 2), calc(100% - var(--place-h)));
        display: flex; inline-size: var(--place-w); block-size: var(--place-h);
        background: var(--surface); border-radius: 8px; box-shadow: 0 0 0 1px var(--control-soft), var(--shadow-lift);
        pointer-events: auto;
    }
    .place:focus-within, .place:has(.current) { z-index: 2; }
    .place > .blank { flex: 1; min-inline-size: 0; margin: 0; padding-inline: .5em; align-items: center; border-radius: 8px; }
    .place > .blank .gap-field { flex: 1; min-inline-size: 0; }
    .place > .blank input[type="text"] { width: 100%; }
    .place > .blank select, .place > .blank .select-button, .place .slot-word { flex: 1; min-width: 0; }
    .place-pins { display: none; }
}

/*
| Things of a picture to colour (decision 72; exam/questions/colour, .paint in place-picture). On the picture, each
| thing is a round mark at its place: a dashed ring while it is white, the crayon's colour once it is coloured,
| laid over the drawing as a crayon is (multiply: the lines show through). Its colour is said in words too, to a
| screen reader and on the result. Under the picture, the crayons: a colour and its name, raised; the one in the
| hand stands up, ringed. The rubber makes a thing white again.
*/
.paints { grid-area: 1 / 1; position: relative; pointer-events: none; }
.paint {
    position: absolute; inset-inline-start: var(--x); inset-block-start: var(--y); translate: -50% -50%;
    inline-size: clamp(1.75rem, 7%, 3.5rem); aspect-ratio: 1; padding: 0;
    background: color-mix(in srgb, var(--paint-halo) 45%, transparent); border: 2px dashed var(--paint-edge); border-radius: 50%;
    pointer-events: auto; cursor: pointer;
    transition: background-color var(--motion-base) var(--ease), border-color var(--motion-base) var(--ease), scale var(--motion-base) var(--ease-spring);
}
.paint::after { content: ""; position: absolute; inset: min(0px, calc((100% - var(--touch)) / 2)); border-radius: 50%; }
.paint:hover { scale: 1.08; }
.paint[data-colour] { background: var(--paint); border: 2px solid color-mix(in srgb, var(--paint) 70%, var(--crayon-black)); mix-blend-mode: multiply; }
.paint.landed { animation: land 320ms var(--ease-spring); }
.exam .paint:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
[data-crayon="red"], .paint[data-colour="red"] { --paint: var(--crayon-red); }
[data-crayon="orange"], .paint[data-colour="orange"] { --paint: var(--crayon-orange); }
[data-crayon="yellow"], .paint[data-colour="yellow"] { --paint: var(--crayon-yellow); }
[data-crayon="green"], .paint[data-colour="green"] { --paint: var(--crayon-green); }
[data-crayon="blue"], .paint[data-colour="blue"] { --paint: var(--crayon-blue); }
[data-crayon="purple"], .paint[data-colour="purple"] { --paint: var(--crayon-purple); }
[data-crayon="pink"], .paint[data-colour="pink"] { --paint: var(--crayon-pink); }
[data-crayon="brown"], .paint[data-colour="brown"] { --paint: var(--crayon-brown); }
[data-crayon="grey"], .paint[data-colour="grey"] { --paint: var(--crayon-grey); }
[data-crayon="black"], .paint[data-colour="black"] { --paint: var(--crayon-black); }
.crayons { display: flex; flex-wrap: wrap; gap: .5rem; margin: .75rem 0 .25rem; padding: 0; border: 0; }
.crayon {
    position: relative; display: inline-flex; align-items: center; gap: .5rem; min-height: var(--touch); padding: .25rem .875rem .25rem .375rem;
    font: 600 .9375rem/1.2 var(--font-ui); color: var(--ink); background: var(--plate); border-radius: var(--radius-pill); box-shadow: var(--shadow-raised);
    cursor: pointer; transition: box-shadow var(--motion-base) var(--ease), translate var(--motion-base) var(--ease-spring);
}
.crayon input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
.crayon-tip { flex: none; inline-size: 1.75rem; block-size: 1.75rem; border-radius: 50%; background: var(--paint); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--paint) 70%, var(--crayon-black)); transition: scale var(--motion-base) var(--ease-spring); }
.crayon.rubber .crayon-tip { background: var(--paint-halo); box-shadow: inset 0 0 0 2px var(--control); }
.crayon:hover { translate: 0 -1px; }
.crayon:has(:checked) { translate: 0 -3px; box-shadow: 0 0 0 2px var(--primary), var(--shadow-lift); }
.crayon:has(:checked) .crayon-tip { scale: 1.15; }
.exam .crayon:has(:focus-visible) { box-shadow: var(--halo), var(--shadow-raised); }
.crayon-hint { margin: 0; font-size: .9375rem; color: var(--muted); }
/* On a phone the crayons stand in a box of them: each its colour over its name, four or five to a row. */
@media (max-width: 600px) {
    .crayons { display: grid; grid-template-columns: repeat(auto-fill, minmax(3.5rem, 1fr)); gap: .375rem; }
    .crayon { flex-direction: column; justify-content: center; gap: .25rem; padding: .375rem .25rem; font-size: .8125rem; border-radius: var(--radius-control); text-align: center; }
}
/* On a result: each thing as the student coloured it, with a tick or a cross on it, and the right colour as a small
   round on a thing that was to be coloured and is not. */
.review-colours { margin-block: 4px 8px; }
.review-colours .paint { inline-size: clamp(1.5rem, 6%, 3rem); cursor: default; }
.review-colours .paint:hover { scale: none; }
.paint-mark {
    position: absolute; inset-block-start: -6px; inset-inline-end: -6px; display: grid; place-items: center; inline-size: 1.25rem; block-size: 1.25rem;
    color: var(--on-state); border: 2px solid var(--paint-halo); border-radius: 50%;
}
.paint-mark .icon { width: 12px; height: 12px; stroke-width: 3; }
.paint-mark.good { background: var(--good); }
.paint-mark.bad { background: var(--bad); }
.paint-mark.missed { background: var(--paint); }
/* Pressed with no crayon in the hand: the crayons say where to begin. */
.colouring.choose .crayons { animation: nudge 420ms var(--ease); }

/* A gap gone to from the footer says "here" once, and softly: a warm light comes up under it and goes again. It
   never starts lit — a light that is simply there the moment a number is pressed reads as a blink — and it comes
   over the ground the gap has and goes back to it: a gap with options stands on a ground of its own, which a
   light that began and ended at "nothing" took away and dropped back. */
.blank.arrived { animation: arrive 1500ms var(--ease); }

/* Speaking: the answer is recorded in a box under its task. What the box shows follows data-state, set by exam.js. */
.recorder {
    display: flex; flex-direction: column; gap: 14px; max-width: 720px; padding: 16px 18px;
    border: 1px solid var(--line); border-radius: var(--radius-card); background: var(--surface);
    transition: background-color var(--motion-base) var(--ease), border-color var(--motion-base) var(--ease);
}
/* Being recorded: the box takes the colour of the dot, which breathes beside the word "Recording". */
.speaking[data-state="recording"] .recorder { background: var(--bad-faint); border-color: var(--bad-line); }
.recorder-live { display: flex; align-items: center; flex-wrap: wrap; gap: 10px 12px; }
.recorder-now { flex: 1; min-width: 0; display: flex; align-items: center; gap: 10px; }
.recorder-dot { flex: none; width: 12px; height: 12px; border-radius: 50%; background: var(--muted); }
.speaking[data-state="recording"] .recorder-dot { background: var(--bad); }
.recorder-time { margin-inline-start: auto; font-weight: 600; font-variant-numeric: tabular-nums; }
.recorder-live progress { flex: 1 1 100%; height: 6px; }
progress.level { height: 4px; }
.recorder-status { margin: 0; font-size: .9375rem; color: var(--muted); }
.recorder-status:empty { display: none; }
.recorder-status > span, .mic-check [data-mic-status] > span { display: block; }
.speaking.trouble .recorder-status { color: var(--bad); font-weight: 600; }
.recorder-actions { display: flex; flex-wrap: wrap; gap: 10px; }
.recorder-actions:not(:has(button:not([hidden]))) { display: none; }
.speaking .when-again, .speaking[data-taken="true"] .when-first,
.speaking:not([data-state="prep"]) .when-prep, .speaking:not([data-state="recording"]) .when-recording { display: none; }
.speaking[data-taken="true"] .when-again { display: inline; }
.mic-check { display: flex; flex-direction: column; gap: 12px; }
.mic-check progress { flex: none; width: 100%; }
.mic-check [data-mic-status]:empty { display: none; }

/*
| A clip to watch: the site's own player (the video component, decision 91). The picture in the clip's own proportions
| (16:9 until the clip says), a play sign over it while it waits, and under it the voice player's bar — a button, a bar
| to move along, the time — with full screen at its end. Of a stretch, the bar and the time are the stretch's.
| Behaviour in ui.js ([data-video]).
*/
.video { display: flex; flex-direction: column; max-width: 720px; overflow: hidden; border: 1px solid var(--line); border-radius: var(--radius-card); background: var(--surface-subtle); }
.video-frame { position: relative; aspect-ratio: var(--video-ratio, 16 / 9); background: var(--video-ground); cursor: pointer; }
.video-frame video { display: block; width: 100%; height: 100%; object-fit: contain; }
.video-cover {
    position: absolute; inset: 0; margin: auto; display: grid; place-items: center; width: 64px; height: 64px; pointer-events: none;
    border-radius: 50%; background: color-mix(in srgb, var(--video-ground) 55%, transparent); color: var(--on-video);
    transition: opacity var(--motion-fast) var(--ease);
}
.video-cover .icon { width: 28px; height: 28px; }
.video[data-state="playing"] .video-cover { opacity: 0; }
.video-bar { display: flex; align-items: center; gap: 8px 12px; padding-block: 8px; padding-inline: 8px 6px; }
.video-bar input[type="range"] { flex: 1 1 120px; min-width: 0; }
.video .voice-button .icon-pause, .video[data-state="playing"] .voice-button .icon-play { display: none; }
.video[data-state="playing"] .voice-button .icon-pause { display: block; }
.video-full {
    flex: none; display: grid; place-items: center; width: var(--touch); height: var(--touch); padding: 0; cursor: pointer;
    border: 0; border-radius: 50%; background: none; color: var(--ink-soft); transition: color var(--motion-fast) var(--ease);
}
.video-full:hover { color: var(--primary); }
.video-full .icon { width: 20px; height: 20px; }
.video-full .icon-shrink, .video:fullscreen .video-full .icon-expand { display: none; }
.video:fullscreen .video-full .icon-shrink { display: block; }
.video .voice-error { padding: 0 16px 12px; }
/* Full screen: the picture takes what the bar leaves. */
.video:fullscreen { max-width: none; border: 0; border-radius: 0; }
.video:fullscreen .video-frame { flex: 1 1 auto; aspect-ratio: auto; }

/* A recording to listen to: the site's own player (the voice component). Behaviour in ui.js. */
.voice {
    display: flex; align-items: center; flex-wrap: wrap; gap: 8px 12px; max-width: 560px; padding-block: 8px; padding-inline: 8px 16px;
    border: 1px solid var(--line); border-radius: var(--radius-pill); background: var(--surface-subtle);
}
.voice audio { display: none; }
.voice-button {
    flex: none; display: grid; place-items: center; width: var(--touch); height: var(--touch); padding: 0; cursor: pointer;
    border: 0; border-radius: 50%; background: var(--primary); color: var(--on-primary);
    transition: background var(--motion-fast) var(--ease);
}
.voice-button:hover { background: var(--primary-strong); }
.voice-button .icon { width: 20px; height: 20px; }
.voice-button .icon-pause, .voice[data-state="playing"] .voice-button .icon-play { display: none; }
.voice[data-state="playing"] .voice-button .icon-pause { display: block; }
.voice input[type="range"] { flex: 1 1 120px; min-width: 0; }
.voice-time { flex: none; font-size: .875rem; font-variant-numeric: tabular-nums; color: var(--muted); }
.voice-speed {
    flex: none; min-width: 52px; min-height: 36px; padding: 0 8px; cursor: pointer; font: inherit; font-size: .875rem; font-weight: 600;
    border: 1px solid var(--control); border-radius: var(--radius-pill); background: var(--surface); color: var(--ink);
}
.voice-speed:hover { border-color: var(--primary); color: var(--primary); }
.voice-error { flex: 1 1 100%; margin: 0; padding-inline: 8px; font-size: .875rem; font-weight: 600; color: var(--bad); }
/*
| The player of a side column, with its speed button, does not fit one row there or on a phone.
| So it is a box that may have two, and the speed button keeps to the far end of whichever row
| it lands on. The button is written left to right in every language, so its own "start" is
| always the left: on a right-to-left page the gap goes on its other side.
*/
.side > .voice:has(.voice-speed) { padding-block: 10px; padding-inline: 10px 14px; border-radius: var(--radius-card); }
.side > .voice:has(.voice-speed) .voice-time { padding-inline: 4px; }
.side > .voice .voice-speed { margin-inline: auto 0; }
[dir="rtl"] .side > .voice .voice-speed { margin-inline: 0 auto; }
.print-only { display: none; }

/* Essay: the task stays beside the writing area. */
.essay { display: flex; flex-wrap: wrap; gap: 24px; align-items: flex-start; scroll-margin-block: 110px; }
.essay-task { flex: 1 1 300px; min-width: 0; }
.essay-task .q-prompt { margin: 0; }
.essay-task ul { padding-inline-start: 1.3em; }
.essay-editor { flex: 999 1 480px; min-width: 0; display: flex; flex-direction: column; border: 1px solid var(--line); border-radius: 12px; overflow: hidden; background: var(--surface); }
.essay-editor:focus-within { border-color: var(--focus); box-shadow: var(--halo); }
.exam .essay-editor textarea:focus-visible { box-shadow: none; }
.essay-editor label { padding: 14px 24px 0; }
.essay-editor textarea {
    display: block; min-height: 420px; border: 0; border-radius: 0; outline: 0; padding: 10px 24px 20px;
    font-family: var(--font-read); font-size: 1.1875rem; line-height: 1.75;
}
/* A box to write in is the browser's to draw, bar and all. The room of its bar is the box's from the first word
   (scrollbar-gutter): a text that grows past the foot of the box gets a bar there, and its lines are not made
   shorter by it — a word at the end of a line would jump to the next. The writing box, and the box of a note. */
.exam textarea { scrollbar-gutter: stable; scrollbar-width: thin; scrollbar-color: var(--control-soft) transparent; }
.word-meter { display: flex; align-items: center; gap: 10px 20px; flex-wrap: wrap; padding: 12px 24px; border-top: 1px solid var(--line-soft); background: var(--surface-subtle); }
.word-count { display: inline-flex; align-items: center; gap: 8px; font-weight: 600; font-variant-numeric: tabular-nums; }
.word-count .icon { display: none; stroke-width: 2.6; }
.word-meter.ok .word-count { color: var(--good); }
.word-meter.ok .word-count .icon { display: block; }
.word-meter progress { height: 6px; }
.word-meter .word-hint { font-size: .875rem; color: var(--muted); }

/*
| Highlights and notes. A student selects text of the exam and a small bar offers
| to highlight it or to write a note on it; a highlight is pressed to read, change
| or remove it. A highlight with a note is underlined, so it is told apart without colour.
*/
mark[data-hl] { background: var(--highlight); color: inherit; border-radius: 2px; cursor: pointer; -webkit-box-decoration-break: clone; box-decoration-break: clone; }
mark[data-hl].noted { text-decoration: underline dotted var(--highlight-line) 2px; text-underline-offset: .22em; }
/* Both are moved to a point measured on the screen (translate, set by exam.js), so their origin is the physical corner. */
.mark-bar, .mark-pop {
    position: absolute; z-index: 25; top: 0; left: 0;
    background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-control); box-shadow: var(--shadow-pop);
}
.mark-bar { display: flex; padding: 4px; gap: 2px; }
.mark-bar button {
    display: inline-flex; align-items: center; gap: 8px; min-height: 40px; padding: 4px 12px;
    font: inherit; font-size: .9375rem; font-weight: 600; white-space: nowrap; cursor: pointer;
    background: transparent; color: var(--ink); border: 0; border-radius: 7px;
}
/* The ground under the one the pointer is on is one thing for the bar: it glides from Highlight to Note and back
   (.mark-glide, placed by exam.js), and is not switched off under one and on under the other. */
.mark-bar button { position: relative; z-index: 1; }
.mark-bar:not(.gliding) button:hover { background: var(--neutral-soft); }
.mark-glide {
    position: absolute; top: 4px; bottom: 4px; left: 0; width: 0; border-radius: 7px; opacity: 0; pointer-events: none; background: var(--neutral-soft);
    transition: transform var(--motion-base) var(--ease-soft), width var(--motion-base) var(--ease-soft), opacity var(--motion-fast) var(--ease);
}
.mark-bar .icon { width: 18px; height: 18px; stroke-width: 2; color: var(--primary); }
.mark-pop { width: min(320px, calc(100vw - 2 * var(--gutter))); padding: 12px; display: flex; flex-direction: column; gap: 10px; }
.mark-pop textarea { min-height: 84px; padding: 8px 12px; font-size: 1rem; }
.mark-pop-actions { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.mark-pop-actions .btn.text { color: var(--bad); padding-inline: 8px; }

/*
| Why a paste or a copy did nothing (decision 147): a note beside the field or the words it was tried on, for a few
| seconds. It lies over the page and takes no room, so nothing moves when it comes; it is moved to its place by
| exam.js (translate), so its origin is the physical corner, as the bar of highlights'. It is never pressed.
*/
.aid-note {
    position: fixed; z-index: 30; top: 0; left: 0; display: flex; align-items: center; gap: 8px;
    max-width: min(22rem, calc(100vw - 16px)); padding: 8px 12px; font-size: .875rem; font-weight: 600; line-height: 1.35;
    background: var(--surface); color: var(--ink); border: 1px solid var(--line); border-radius: var(--radius-control); box-shadow: var(--shadow-pop);
    pointer-events: none; opacity: 0; transition: opacity var(--motion-base) var(--ease);
}
.aid-note.shown { opacity: 1; }
.aid-note .icon { flex: none; width: 16px; height: 16px; stroke-width: 2; color: var(--warn); }
.mark-pop-actions .btn.text:hover { background: var(--bad-soft); color: var(--bad); }

/*
| Footer: the question numbers of the part on screen, and the way on from it.
| The numbers stay on one row and scroll sideways when a part has many. A number is grey
| while it waits; answered, the brand's colour spreads into it from its middle, like ink.
| Where the student is, one warm mark under the numbers, which slides to the question being
| answered. Going to another part, a number the new part does not have closes in the row and
| one it needs opens there, so what stands after them is carried to its place and never jumps;
| the numbers that stay change their figures.
*/
.exam-footer { position: sticky; inset-block-end: 0; z-index: 20; background: var(--surface); border-top: 1px solid var(--line); }
.exam-footer-inner { max-width: var(--shell); margin: 0 auto; padding: 8px var(--gutter); display: flex; align-items: center; gap: 8px 24px; }
.qnav { display: flex; align-items: center; gap: 12px; flex: 1 1 0; min-width: 0; }
.qnav-group { position: relative; display: flex; gap: 6px; min-width: 0; padding-block: 2px 8px; overflow-x: auto; scrollbar-width: none; overscroll-behavior-x: contain; }
.qnav-group::-webkit-scrollbar { display: none; }
.qnav-item {
    flex: none; display: block; width: 38px; height: 38px; padding: 0;
    font: 700 .875rem/1 var(--font-ui); font-variant-numeric: tabular-nums; cursor: pointer;
    background: var(--neutral-soft); color: var(--ink-soft); border: 0; border-radius: var(--radius-control);
    transition: background-color var(--motion-base) var(--ease), color var(--motion-base) var(--ease), transform var(--motion-fast) var(--ease), width 520ms var(--ease), margin 520ms var(--ease), opacity 320ms var(--ease);
}
.qnav-item:hover { background: var(--track); color: var(--ink); }
.qnav-item:active { transform: scale(.94); }
.qnav-item.answered { color: var(--on-primary); }
.num-win { position: relative; display: grid; place-content: center; width: 100%; height: 100%; overflow: hidden; border-radius: inherit; }
.num-win::before { content: ""; position: absolute; inset: 0; background: var(--primary); clip-path: circle(0% at 50% 50%); transition: clip-path 520ms var(--ease-soft), background-color var(--motion-base) var(--ease); }
.qnav-item.answered .num-win::before { clip-path: circle(75% at 50% 50%); }
.qnav-item.answered:hover .num-win::before { background: var(--primary-strong); }
.num-win > .num { position: relative; z-index: 1; }
/* Flagged to come back to: a folded corner, so it is told apart by shape as well as colour. */
.qnav-item.flagged::after {
    content: ""; position: absolute; z-index: 2; inset-block-start: 0; inset-inline-end: 0; width: 12px; height: 12px;
    background: var(--flag); border-start-end-radius: var(--radius-control); border-end-start-radius: 9px; box-shadow: 0 0 0 1.5px var(--surface);
}
.qnav-here { position: absolute; bottom: 0; left: 0; width: 38px; height: 4px; border-radius: 2px; opacity: 0; pointer-events: none; background: var(--accent); transition: transform var(--motion-glide) var(--ease-spring), width var(--motion-glide) var(--ease-spring), opacity var(--motion-base) var(--ease); }
.qnav-group:not(.gliding) .qnav-item.current::before, .review-items .qnav-item.current::before { content: ""; position: absolute; inset-inline: 0; inset-block-end: -6px; height: 4px; border-radius: 2px; background: var(--accent); }
/* Many may close at once, and what stands after them is carried all that way: so they start gently, not at speed. */
.qnav-item.closing { width: 0; margin-inline-start: -6px; opacity: 0; transform: scale(.6); pointer-events: none; transition: width 520ms var(--ease), margin 520ms var(--ease), opacity 260ms var(--ease), transform 520ms var(--ease); }
.qnav-item.opening { animation: open-tile 520ms var(--ease) both; }
/* Given another look without being seen to take it (exam.js, swapTiles). */
.qnav-item.still, .qnav-item.still .num-win::before { transition: none !important; }
/* Words and figures that change with the part change softly, and stay the size they are: the old sink a hair
   and fade while the new settle from just above; a number rolls through its tile's window. */
.roll-win { position: relative; display: block; white-space: nowrap; }
.roll-old { position: absolute; inset: 0; pointer-events: none; animation: soft-out 190ms var(--ease) both; }
.roll-new { animation: fade-in 380ms var(--ease) both, drift-in 420ms var(--ease-settle) both; }
/* Out of the flow, like every figure that is going: in it, it would push the new one up, to drop when it is gone. */
.num-win > .roll-old { position: absolute; inset: 0; display: grid; place-content: center; z-index: 1; animation-name: num-out; animation-duration: 240ms; }
.num-win > .roll-new { animation-name: fade-in, num-in; animation-duration: 300ms, 340ms; }

/* Flag marks the question being answered. With no question chosen there is nothing for it to mark: it rests, faded, and
   wakes when one is chosen. */
.qnav-flag {
    display: inline-flex; align-items: center; gap: 8px; flex: none; min-height: 38px; margin-block-end: 6px; padding: 4px 12px; position: relative;
    font: inherit; font-size: .875rem; font-weight: 600; white-space: nowrap; cursor: pointer;
    background: var(--neutral-soft); color: var(--ink-soft); border: 0; border-radius: var(--radius-control);
    transition: background-color var(--motion-base) var(--ease), color var(--motion-base) var(--ease), opacity 360ms var(--ease);
}
.qnav-flag:hover { background: var(--track); color: var(--ink); }
.qnav-flag .icon { width: 18px; height: 18px; stroke-width: 2; }
.qnav-flag:disabled { opacity: .4; cursor: default; background: var(--neutral-soft); color: var(--ink-soft); }
.qnav-flag[aria-pressed="true"] { background: var(--warn-soft); color: var(--warn); }
.qnav-flag[aria-pressed="true"] .icon { fill: var(--flag); stroke: var(--flag); }
/*
| Questions: the way to the list of every question, with how much is answered under the word. It is the first
| thing of the footer on every screen — the place that never moves, whatever the numbers and the capsule after
| it become — and of the capsule's own make: a soft pill as tall as the capsule, one at each end of the footer.
| The review grows out of it (dialog.review, below). Its second line is as wide as the sentence will ever be
| (.sizer holds the sentence at its longest, without being seen), so an answer given never moves the numbers.
*/
.qnav-count {
    display: inline-flex; align-items: center; gap: 10px; flex: none; min-height: 52px; margin: 0; padding-block: 4px; padding-inline: 14px 18px; position: relative;
    font: inherit; white-space: nowrap; text-align: start; cursor: pointer;
    background: var(--neutral-soft); color: var(--ink); border: 0; border-radius: 26px;
    transition: background-color var(--motion-base) var(--ease);
}
.qnav-count:hover { background: var(--track); }
.qnav-count > .icon { width: 20px; height: 20px; stroke-width: 2; color: var(--ink-soft); }
.qnav-count-text { display: flex; flex-direction: column; align-items: flex-start; min-width: 0; line-height: 1.2; text-align: start; }
.qnav-count-text > * { max-width: 100%; }
.count-word { font-size: .9375rem; font-weight: 700; }
.qnav-count-text > [role="status"], .qnav-count .count-short, .qnav-count .sizer { font-size: .75rem; font-weight: 400; color: var(--muted); font-variant-numeric: tabular-nums; }
.qnav-count .count-short { display: none; }
.qnav-count .sizer { height: 0; overflow: hidden; visibility: hidden; }
/*
| Between a phone and a wide screen the footer is short of room, and the numbers come first: they are the way
| to a question. So what stands beside them says less. Under "Questions" the count is two numbers, not a
| sentence; Flag is its picture; and the name the way on leads to is cut short sooner.
*/
@media (min-width: 701px) and (max-width: 1199px) {
    .qnav-count-text > [role="status"], .qnav-flag > span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
    .qnav-count .count-short { display: block; }
    .qnav-count .sizer { display: none; }
    .qnav-flag { width: 38px; padding: 0; justify-content: center; }
    .ways .pg-next { max-width: 15rem; }
}
[data-ui-locale="fa-en"] .qnav-count .when-wide { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
[data-ui-locale="fa-en"] .qnav-count .when-tight { display: inline; }
[data-ui-locale="fa-en"] .qnav-flag > span > .tr-alt { display: none; }

/*
| The way back and the way on are one small thing: a capsule with a quiet disc for back and,
| for on, where it leads and a disc in the brand's colour. Where there is no way back, the disc
| is not there and the capsule is shorter by it. Coming, the capsule first grows the room for
| it, and then the disc comes into that room from the side; going, the disc leaves first and
| the room closes after it. At the end of the section the way on is one line, and its disc a tick.
| The capsule is as wide as what stands in it, and takes a new width as one thing: its far end
| stands still, and what is in it — held against that end — is uncovered or covered at its near
| end (exam.js, sizeCapsule(), .sizing). Nothing in it has a width that moves by itself.
*/
.ways { position: relative; flex: none; display: flex; align-items: center; justify-content: flex-end; padding: 4px; background: var(--neutral-soft); border-radius: 30px; }
.ways.sizing { overflow: hidden; transition: width 520ms var(--ease-soft); }
.pg-back, .pg-next { flex: none; display: inline-flex; align-items: center; min-height: 44px; padding: 0; cursor: pointer; font: inherit; text-align: start; border: 0; background: transparent; color: var(--ink); border-radius: 26px; }
.pg-back { width: 48px; height: 44px; position: relative; }
.pg-disc {
    flex: none; display: inline-grid; place-content: center; width: 44px; height: 44px; border-radius: 50%;
    background: var(--plate); color: var(--ink-soft); box-shadow: var(--shadow-raised);
    transition: opacity 420ms var(--ease) 240ms, transform 640ms var(--ease-soft) 240ms, color var(--motion-base) var(--ease);
}
.pg-disc .icon, .nav-disc .icon { width: 20px; height: 20px; stroke-width: 2.4; transition: transform var(--motion-fast) var(--ease); }
.pg-back:hover .pg-disc { color: var(--primary); }
.pg-back:active .pg-disc .icon { transform: scale(.84); }
[dir="rtl"] .pg-back:active .pg-disc .icon { transform: scale(-.84, .84); }
.pg-back.off { width: 0; pointer-events: none; }
.pg-back.off .pg-disc { opacity: 0; transform: translateX(-14px) scale(.92); transition: none; }
[dir="rtl"] .pg-back.off .pg-disc { transform: translateX(14px) scale(.92); }
/* Going, the disc leaves from where it stood, whatever the capsule becomes meanwhile: a copy of it, held against
   the capsule's far end (exam.js, sendDisc()). */
.pg-disc.going { position: absolute; top: 4px; pointer-events: none; transition: none; animation: disc-out 300ms var(--ease) both; }
[dir="rtl"] .pg-disc.going { animation-name: disc-out-back; }
.pg-next { gap: 16px; max-width: 22rem; padding-inline-start: 12px; justify-content: flex-end; }
.nav-disc { position: relative; z-index: 1; flex: none; display: inline-grid; place-content: center; width: 44px; height: 44px; border-radius: 50%; background: var(--primary); color: var(--on-primary); transition: background-color var(--motion-base) var(--ease), transform var(--motion-base) var(--ease-soft); }
.nav-disc .icon { grid-area: 1 / 1; transition: opacity var(--motion-base) var(--ease), transform var(--motion-base) var(--ease-soft); }
.nav-disc .icon-end, .pg-next.ends .icon-go { opacity: 0; transform: scale(.5); }
.pg-next.ends .icon-end { opacity: 1; transform: none; }
.pg-next:hover .nav-disc { background: var(--primary-strong); }
.pg-next:hover .nav-title { color: var(--primary-strong); }
.pg-next:active .nav-disc { transform: scale(.94); }
.nav-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.3; }
.nav-kicker { display: block; font-size: .75rem; font-weight: 500; color: var(--muted); transition: opacity var(--motion-base) var(--ease); }
/* The line keeps its height when it has nothing to say, so that the words under it go to the middle softly and are not dropped there. */
.nav-kicker::before { content: "\200b"; }
.nav-title { display: block; overflow: hidden; text-overflow: ellipsis; font-weight: 700; transition: color var(--motion-base) var(--ease), translate 420ms var(--ease-soft); }
/* One line, where the way on has nothing above it: it stands in the middle of the capsule's height. */
.pg-next.single .nav-kicker:not(.roll-old) { opacity: 0; }
.pg-next.single .nav-title { translate: 0 -.5rem; }
/* What is going keeps the place it had (exam.js writes it), whatever the capsule has become. */
.roll-old { transition: none !important; }

/* ---------- The exam's dialogs ---------- */

dialog {
    width: calc(100% - 32px); max-width: 460px; padding: 28px;
    border: 0; border-radius: 16px; box-shadow: var(--shadow-dialog); background: var(--surface); color: var(--ink);
}
dialog::backdrop { background: var(--scrim); }
dialog h2 { font-size: 1.375rem; margin: 0 0 16px; }
dialog p { margin: 0 0 16px; }
.dialog-missing { padding: 12px 16px; border-radius: var(--radius-control); background: var(--warn-soft); color: var(--warn); font-weight: 600; }
.dialog-actions { display: flex; gap: 10px; flex-wrap: wrap; }
.dialog-actions .btn { flex: 1 1 140px; }

/*
| The installation of an update, step by step (Settings → Updates). ui.js sets each step's
| data-state (waiting, active, done, failed) and fills the bar as the page loader is filled:
| what is measured (the download) is shown as it is, what cannot be (the unpacking) creeps.
*/
dialog.update-run { max-width: 520px; }
dialog.update-run .heading { margin-block-end: 20px; }
dialog.update-run h2 { margin: 0; }
.run-bar { --progress: 0; position: relative; height: 8px; border-radius: 4px; overflow: hidden; background: var(--track); }
.run-bar::after {
    content: ""; position: absolute; inset: 0; border-radius: 4px; background: var(--primary);
    transform: scaleX(var(--progress)); transform-origin: 0 50%; transition: transform var(--motion-enter) linear;
}
[dir="rtl"] .run-bar::after { transform-origin: 100% 50%; }
.run-bar[data-state="waiting"]::after { transition: transform 90s cubic-bezier(.08, .7, .2, 1); }
.run-steps { list-style: none; margin: 20px 0 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }
.run-steps li { display: flex; align-items: center; gap: 12px; color: var(--muted); }
.run-steps .run-text { flex: 1 1 auto; }
.run-mark {
    flex: none; display: inline-grid; place-items: center; width: 24px; height: 24px; border-radius: 50%;
    border: 2px solid var(--control); color: transparent;
}
.run-mark .icon { width: 14px; height: 14px; stroke-width: 3; }
.run-steps [data-state="active"] { color: var(--ink); font-weight: 600; }
.run-steps [data-state="active"] .run-mark { border-color: var(--primary); border-inline-end-color: transparent; animation: spin 900ms linear infinite; }
.run-steps [data-state="done"] { color: var(--ink-soft); }
.run-steps [data-state="done"] .run-mark { border-color: var(--good); background: var(--good); color: var(--on-state); }
.run-steps [data-state="failed"] { color: var(--bad); font-weight: 600; }
.run-steps [data-state="failed"] .run-mark { border-color: var(--bad); }
.run-error { display: flex; flex-direction: column; gap: 4px; margin-block-start: 20px; padding: 12px 16px; border-radius: var(--radius-control); background: var(--bad-soft); color: var(--bad); }
.run-error [data-update-error-text] { overflow-wrap: anywhere; }
dialog.update-run .dialog-actions { margin-block-start: 20px; }

/*
| One person edits an exam at a time (docs/features/editing-together.md, admin/exams/partials/edit-lock). The line
| over a page that is view only says who edits and offers to ask; its forms are in fieldset.lock-area, switched off:
| the fields stay where they are, read as read-only fields do, and the buttons that would send them are not shown.
| The dialog is the site's question (dialog.confirm), with a line that counts down where an answer is awaited.
*/
/* Who else edits an exam, beside it on the list of exams and under its name in its header: a pencil and words. */
.meta.editing-now { color: var(--ink-soft); font-weight: 600; }
.lock-bar { align-items: center; }
.lock-bar > .btn { flex: none; }
fieldset.lock-area:disabled :is(input:not([type="checkbox"], [type="radio"], [type="file"], [type="range"]), textarea) {
    background: var(--surface-subtle); border-color: var(--control-soft); color: var(--ink-soft);
}
fieldset.lock-area:disabled :is(button:not([type="button"]), .save-bar) { display: none; }
fieldset.lock-area:disabled :is(.dropzone, .file-box) { opacity: .5; pointer-events: none; }
fieldset.lock-area:disabled .option-card { cursor: not-allowed; }
fieldset.lock-area:disabled .option-card:hover { border-color: var(--control-soft); }
/* Wide enough for its three answers on one line: the way back, view only, ask. */
dialog.lock-dialog { max-width: 540px; }
dialog.lock-dialog [data-lock-text] { color: var(--ink-soft); }
/* What went wrong with an answer (the save before it, the site not answering): said where the help stood. */
dialog.lock-dialog .trouble { color: var(--warn); font-weight: 600; }
dialog.lock-dialog .lock-time { display: flex; align-items: center; gap: 8px; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
dialog.lock-dialog .lock-time .icon { flex: none; width: 18px; height: 18px; }
dialog.lock-dialog .dialog-actions { justify-content: flex-end; }
dialog.lock-dialog .dialog-actions .btn { flex: 0 1 auto; }
dialog.lock-dialog .dialog-actions.stacked { flex-direction: column-reverse; }
dialog.lock-dialog .dialog-actions.stacked .btn { flex: none; width: 100%; }
/* On a phone the answers are one under another, the one that answers first, all as wide as the dialog. */
@media (max-width: 480px) {
    dialog.lock-dialog .dialog-actions { flex-direction: column-reverse; }
    dialog.lock-dialog .dialog-actions .btn { flex: none; width: 100%; }
}
.danger-zone p.help { display: flex; align-items: flex-start; gap: 8px; margin-block-start: 8px; }
.danger-zone p.help .icon { flex: none; width: 18px; height: 18px; margin-block-start: 2px; }

/* The site's own question before something that cannot be undone, in place of the browser's (ui.js, form[data-confirm]). */
dialog.confirm .heading { margin-block-end: 12px; }
dialog.confirm h2 { margin: 0; }
dialog.confirm [data-confirm-text] { color: var(--ink-soft); }
/* The answer is as wide as its words need; "cancel" stays small beside it. */
dialog.confirm .dialog-actions { justify-content: flex-end; }
dialog.confirm .dialog-actions .btn { flex: 0 1 auto; }
dialog.confirm .dialog-actions [data-confirm-yes] { flex: 1 1 auto; }

/*
| The review of a section: every question as the button it is in the footer, under the name of
| its part. Opened to look (data-mode="review", the footer's Questions button) it only shows;
| opened by the button that ends the section (data-mode="finish") it also asks to end it.
| Either way it is one thing, on every screen: a sheet standing on the foot of the screen,
| round only at its top, and it is the button that opened it, grown — the Questions pill at the
| footer's start, or the capsule at its far end. It stands over that button, at that end of the
| footer, as wide as a list of questions needs; on a phone, from edge to edge.
|
| How it is that button. Closed, the sheet is cut to the button's own outline (exam.js measures
| it and writes --ct, --cr, --cb, --cl and --round; .frozen and .measuring are its tools) and
| has the button's own ground, so that it lies on the button and cannot be told from it.
| Opened to look, the button's picture and two lines are the sheet's heading: they travel there
| and back (.fly; --fx, --fy, --fs), so the sheet is simply there on its button from the first
| moment. The capsule's words are not the sheet's: there the sheet comes over them softly, a
| breath before it grows. Closing, either sheet lets go of its button softly (opacity, over the
| last tenth of a second), when it has all but arrived: what stands in the button is covered
| and uncovered, never switched.
| (The site's ordinary way for a dialog to appear — a step up, a little larger — is switched off
| here by a rule as strong as the one that gives it: left on, the sheet was measured while it
| was still on that step, and closed onto a place beside its button.)
*/
dialog.review {
    inset: auto; inset-block-end: 0; inset-inline-end: calc(max(0px, (100% - var(--shell)) / 2) + var(--gutter));
    width: min(600px, calc(100% - 2 * var(--gutter))); max-width: none; max-height: min(84dvh, 760px); margin: 0; padding: 22px 24px 24px;
    overflow-y: auto; border-radius: 0; box-shadow: none; background: var(--neutral-soft); opacity: 0;
    clip-path: inset(var(--ct, 100%) var(--cr, 0px) var(--cb, 0px) var(--cl, 0px) round var(--round, 26px));
    transition: clip-path 460ms var(--ease-soft), background-color 460ms var(--ease), opacity 120ms linear 340ms;
}
dialog.review[data-mode="review"] { inset-inline: calc(max(0px, (100% - var(--shell)) / 2) + var(--gutter)) auto; }
dialog.review[open] { animation: none; }
dialog.review.open { opacity: 1; clip-path: inset(0px 0px 0px 0px round 26px 26px 0px 0px); background: var(--surface); transition: clip-path 560ms var(--ease-soft), background-color 420ms var(--ease), opacity 0s; }
dialog.review[data-mode="finish"].open { transition: clip-path 560ms var(--ease-soft) 70ms, background-color 420ms var(--ease) 70ms, opacity 90ms linear; }
dialog.review::backdrop, dialog.review[open]::backdrop { animation: none; opacity: 0; transition: opacity 360ms var(--ease); }
dialog.review.open::backdrop { opacity: 1; transition: opacity 480ms var(--ease); }
dialog.review.frozen, dialog.review.frozen * { transition: none !important; }
/* What is in it comes when it has opened, and is gone before it has closed. */
dialog.review > *, dialog.review .review-close { opacity: 0; transition: opacity 140ms var(--ease); }
dialog.review.open > *, dialog.review.open .review-close { opacity: 1; transition: opacity 420ms var(--ease) 200ms; }
dialog[data-mode="review"] [data-when="finish"], dialog[data-mode="finish"] [data-when="review"] { display: none; }
/* Opened to look, the heading is the button's own picture and two lines, come up from where they stood. */
dialog.review[data-mode="review"] .review-top h2, dialog.review[data-mode="review"] .review-line { width: fit-content; white-space: nowrap; }
dialog.review[data-mode="review"] > .review-top, dialog.review[data-mode="review"].open > .review-top { opacity: 1; transition: none; }
/* (Back a little sooner than the sheet closes: they have arrived when the sheet lets go of the button, and are not seen beside its own.) */
dialog.review[data-mode="review"] .fly { transform-origin: 0 0; transform: translate(var(--fx, 0px), var(--fy, 0px)) scale(var(--fs, 1)); transition: transform 400ms var(--ease-soft); }
dialog.review[data-mode="review"].open .fly { transform: none; transition: transform 560ms var(--ease-soft); }
dialog.review.measuring .fly { transform: none !important; }
/* Opened to look, the sheet has its way out at its top; the two answers belong to the question of ending. */
dialog.review[data-mode="review"] .dialog-actions { display: none; }
.review-top { display: flex; align-items: center; gap: 12px; margin: 0 0 12px; }
.review-top > .icon { flex: none; width: 22px; height: 22px; color: var(--ink-soft); stroke-width: 2; }
.review-top-text { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.review-top h2 { margin: 0; font-size: 1.25rem; font-weight: 700; line-height: 1.25; }
.review-line { margin: 0; font-size: .875rem; line-height: 1.3; color: var(--muted); font-variant-numeric: tabular-nums; }
.review-close { display: inline-grid; place-content: center; flex: none; width: var(--touch); height: var(--touch); padding: 0; background: var(--neutral-soft); color: var(--ink-soft); border: 0; border-radius: 50%; cursor: pointer; transition: background-color var(--motion-fast) var(--ease), color var(--motion-fast) var(--ease), opacity var(--motion-base) var(--ease); }
.review-close:hover { background: var(--track); color: var(--ink); }
.review-close .icon { width: 20px; height: 20px; }
/* The two answers to "end it?" are of the footer's own make: soft, round, and no line round either. */
dialog.review .dialog-actions { margin-block-start: 4px; flex-wrap: nowrap; }
dialog.review .dialog-actions .btn { flex: 1 1 0; min-width: 0; min-height: 52px; border-radius: 26px; }
/* The way back is as wide as its word; the answer takes the rest, on a phone too. */
dialog.review .dialog-actions .btn.ghost { flex: 0 0 auto; padding-inline: 24px; }
dialog.review .dialog-actions .btn.ghost, dialog.review .dialog-actions .btn.ghost:hover { border-color: transparent; }
dialog.review .dialog-actions .btn.ghost { background: var(--neutral-soft); }
dialog.review .dialog-actions .btn.ghost:hover { background: var(--track); }
.review-map { display: flex; flex-direction: column; margin-bottom: 14px; }
/* (.review-part is also a part of a result's review: these are the sheet's own.) */
dialog.review .review-part, dialog.review .review-part + .review-part { margin: 0; padding-block: 12px 6px; border-top: 1px solid var(--line-soft); }
dialog.review .review-part h3, dialog.review .review-part h4 { margin: 0 0 8px; font-size: .9375rem; color: var(--ink-soft); }
/* Where the review is of several sections: the name of each over its parts. */
.review-section-name { margin: 0; padding-block: 14px 0; border-top: 1px solid var(--line-soft); font-size: 1rem; }
dialog.review .review-map > .review-section-name + .review-part { border-top: 0; padding-block-start: 8px; }
.review-items { display: grid; grid-template-columns: repeat(auto-fill, minmax(44px, 1fr)); gap: 8px; padding-block-end: 6px; }
.review-items .qnav-item { width: auto; height: 44px; }
.review-legend { list-style: none; display: flex; flex-wrap: wrap; gap: 6px 18px; margin: 0 0 16px; padding: 0; font-size: .8125rem; color: var(--muted); }
.review-legend li { display: inline-flex; align-items: center; gap: 8px; }
.review-legend .qnav-item { width: 18px; height: 18px; border-radius: 5px; cursor: default; }
.review-legend .qnav-item.answered { background: var(--primary); }
.review-legend .qnav-item.flagged::after { width: 8px; height: 8px; border-start-end-radius: 5px; border-end-start-radius: 5px; box-shadow: none; }
.review-legend .legend-here { width: 18px; height: 4px; border-radius: 2px; background: var(--accent); }

/* ---------- Teacher panel ---------- */

.code-chip { padding: 4px 12px; border-radius: 8px; border: 1px dashed var(--control); background: var(--ground); font-size: 1.125rem; font-weight: 700; letter-spacing: .1em; color: var(--ink); }
.code-chip.small { padding: 2px 8px; font-size: .875rem; }
/* A field and the one button that sends it, on one line while there is room for both (a choice, then "Add"). */
.pick-row { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.pick-row > :is(.select, select, input) { flex: 1 1 240px; width: auto; }

.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 12px; margin-bottom: 24px; }
.stat { padding: 16px 20px; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-card); color: var(--muted); }
.stat strong { display: block; font-size: 1.875rem; line-height: 1.1; color: var(--ink); font-variant-numeric: tabular-nums; }

.danger-zone {
    display: flex; align-items: center; justify-content: space-between; gap: 12px 24px; flex-wrap: wrap;
    margin-top: 24px; padding: 20px 24px; background: var(--surface); border: 1px solid var(--bad-line); border-radius: var(--radius-card);
}
.danger-zone > div { flex: 1 1 280px; }
.danger-zone h2 { margin: 0 0 2px; font-size: 1.0625rem; }
.danger-zone p { margin: 0; font-size: .9375rem; color: var(--muted); }

/* A bar that keeps Save in reach on long forms. */
.save-bar {
    position: sticky; inset-block-end: 12px; z-index: 10; margin-top: 24px;
    display: flex; align-items: center; justify-content: space-between; gap: 12px 24px; flex-wrap: wrap;
    padding: 12px 12px 12px 20px; padding-inline: 20px 12px;
    background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-card); box-shadow: var(--shadow-bar);
}
.save-bar .help { display: flex; align-items: center; gap: 8px; flex: 1 1 240px; }
.save-bar .help .icon { width: 18px; height: 18px; stroke-width: 2; }
.save-bar .btn { padding-inline: 32px; }

/* Settings and answer key */
.inline-field { display: flex; align-items: center; gap: 10px; margin: 0; font-weight: 600; }
.inline-field.light { font-size: .875rem; font-weight: 400; color: var(--ink-soft); }
.narrow-input { width: 84px !important; min-height: var(--touch) !important; padding-inline: 10px !important; text-align: center; font-weight: 700; }
.part-head { display: flex; align-items: center; justify-content: space-between; gap: 8px 24px; flex-wrap: wrap; margin: 18px 0 12px; }
.part-head:first-child { margin-top: 0; }
.part-head h3, .part-head h4 { margin: 0; font-size: .9375rem; font-weight: 600; color: var(--muted); }
.key-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 12px; }
.key { display: flex; min-width: 0; }
.key .qnum { height: auto; min-width: 40px; border-start-end-radius: 0; border-end-end-radius: 0; }
.key input, .key select, .key .key-note {
    flex: 1; min-width: 0; min-height: var(--touch); padding-block: 6px;
    border-inline-start: 0; border-start-start-radius: 0; border-end-start-radius: 0;
}
.key .key-note {
    display: flex; align-items: center; padding-inline: 12px;
    border: 1.5px dashed var(--control); border-inline-start: 0; border-radius: var(--radius-control);
    border-start-start-radius: 0; border-end-start-radius: 0; font-size: .875rem; color: var(--muted);
}

/* A list of places to jump to on a long page: a lesson's steps, a paper's sections */
.jump { display: flex; flex-direction: column; gap: 4px; }
.jump a { display: flex; align-items: center; gap: .35em; min-height: 40px; padding: 4px 12px; border-radius: var(--radius-control); font-size: .9375rem; text-decoration: none; color: var(--ink-faint); }
.jump a:hover { background: var(--neutral-soft); color: var(--ink); }
.jump a.top { min-height: var(--touch); font-size: 1rem; font-weight: 700; color: var(--ink); }
.jump a.sub { padding-inline-start: 26px; }
.columns > .side.jump { flex: 1 1 220px; }
/* A lesson has thirty steps and more: on a wide screen its list scrolls by itself, so its end is never out of reach. */
@media (min-width: 980px) {
    .columns > .side.sticky.jump { max-height: calc(100dvh - 32px); overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; }
}

/* Media and import */
.file-name { display: flex; align-items: center; gap: 12px; }
.file-name .thumb { flex: none; width: 40px; height: 40px; border-radius: var(--radius-control); background: var(--neutral-soft); color: var(--ink-faint); display: flex; align-items: center; justify-content: center; }
.file-name .thumb .icon { width: 20px; height: 20px; stroke-width: 2; }
.file-name code { padding: 0; background: transparent; font-size: .9375rem; font-weight: 600; }
.step-head { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
.step-head > h2, .step-head > label { margin: 0; font-size: 1.25rem; font-weight: 700; }
.file-box { padding: 12px; border: 1.5px solid var(--control-soft); border-radius: var(--radius-control); background: var(--surface-subtle); }

/*
| The file manager (Settings → Files). A row of the list is a box to choose it with, its picture or sign, and its name
| as the way to its page; the box's press area is the 44 px the site asks for and takes no room of its own.
*/
.file-line { display: flex; align-items: center; gap: 12px; }
.file-line .file-name { flex: 1 1 auto; min-width: 0; color: inherit; text-decoration: none; }
.file-line .file-name code { overflow-wrap: anywhere; }
.file-line .file-name:hover code { color: var(--primary); }
.file-name .thumb { overflow: hidden; }
.file-name .thumb img { width: 100%; height: 100%; object-fit: cover; }
.row-pick { flex: none; display: grid; place-items: center; width: var(--touch); height: var(--touch); margin: -11px; cursor: pointer; }
.row-pick > input:disabled { cursor: default; }
.search.filters { flex: 1 1 100%; flex-wrap: wrap; }
.search.filters .search-box { flex: 1 1 100%; }
.search.filters .select, .search.filters > select { flex: 1 1 130px; min-width: 0; }
.file-line .file-name + .help, .file-tile-link > .help.note { margin: 0; }
.file-line-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.bulk-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 24px; margin-block-start: 16px; }
.bulk-actions { margin-inline-start: auto; display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
/* A choice that goes with the action (the teacher students are given to) stands before its button, on its line. */
.bulk-actions > :is(.select, select) { flex: 0 1 auto; width: 15rem; }
.bulk-actions .btn[hidden] { display: none; }
.file-gallery { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: 14px; }
.file-tile { position: relative; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-card); overflow: hidden; }
.file-tile > .row-pick { position: absolute; inset-block-start: 6px; inset-inline-start: 6px; z-index: 1; margin: 0; background: var(--surface); border-radius: var(--radius-control); width: var(--touch); height: var(--touch); }
.file-tile-link { display: flex; flex-direction: column; gap: 4px; padding: 0 12px 12px; color: inherit; text-decoration: none; }
.file-tile-link code { padding: 0; background: transparent; font-size: .875rem; font-weight: 600; overflow-wrap: anywhere; }
.file-tile-link:hover code { color: var(--primary); }
.file-tile-art { display: flex; align-items: center; justify-content: center; aspect-ratio: 4 / 3; margin-inline: -12px; margin-block-end: 6px; background: var(--neutral-soft); color: var(--ink-faint); }
.file-tile-art img { width: 100%; height: 100%; object-fit: cover; }
.file-tile-art .icon { width: 36px; height: 36px; }
.space-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 16px; }
.space-list li { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: baseline; gap: 6px 16px; }
.space-list progress { grid-column: 1 / -1; width: 100%; }
.file-title { overflow-wrap: anywhere; }
.file-preview { display: flex; justify-content: center; padding: 16px; background: var(--surface-subtle); border-radius: var(--radius-control); }
.file-preview img { display: block; max-width: 100%; height: auto; border-radius: 6px; }
.file-facts { display: grid; gap: 14px; margin: 0; }
.file-facts > div { display: grid; gap: 2px; }
.file-facts dt { font-size: .75rem; font-weight: 600; color: var(--muted); }
.file-facts dd { margin: 0; overflow-wrap: anywhere; }
/* Everything about one thing, in a main card: two columns of facts, one on a phone; a long one spans both. */
.facts-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr)); gap: 14px 24px; margin: 0; }
.facts-grid > div { display: grid; gap: 2px; align-content: start; min-width: 0; }
.facts-grid > .wide { grid-column: 1 / -1; }
.facts-grid dt { font-size: .75rem; font-weight: 600; color: var(--muted); }
.facts-grid dd { margin: 0; overflow-wrap: anywhere; }
.facts-grid dd > .fact-line { display: block; }

.errors-card { margin-bottom: 20px; background: var(--surface); border: 2px solid var(--bad); border-radius: var(--radius-card); overflow: hidden; }
.errors-head { display: flex; gap: 12px; align-items: flex-start; padding: 18px 24px; background: var(--bad-soft); color: var(--bad-strong); }
.errors-head .icon { width: 22px; height: 22px; margin-block-start: 2px; }
.errors-head h2 { margin: 0 0 2px; font-size: 1.1875rem; }
.errors-head p { margin: 0; font-size: .9375rem; }
.errors-body { padding: 16px 24px 20px; display: flex; flex-direction: column; align-items: flex-start; gap: 12px; }
pre.copyable {
    width: 100%; margin: 0; padding: 16px; max-height: 360px; overflow: auto; white-space: pre-wrap; overflow-wrap: anywhere; text-align: left;
    background: var(--surface-subtle); border: 1px solid var(--line); border-radius: var(--radius-control); line-height: 1.7;
}

/* ---------- A recording's transcript (admin/exams/transcripts) ---------- */

/* Between two ways of doing one thing on a page: the heading of the second. */
.or-title { margin: 8px 0 0; padding-block-start: 20px; border-top: 1px solid var(--line-soft); font-size: 1.0625rem; }

/* What an answer would change, before it is applied: a question to a row, what is there now struck through, what will be after an arrow. */
.changes { list-style: none; margin: 0; padding: 0; }
.changes > li { display: flex; align-items: flex-start; gap: 12px; padding-block: 12px; border-top: 1px solid var(--line-soft); }
.changes > li:first-child { border-top: 0; padding-block-start: 0; }
.change-main { flex: 1 1 0; min-width: 0; }
.changes dl { display: flex; flex-direction: column; gap: 6px; margin: 0; }
.changes dl > div { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 12px; }
.changes dt { flex: 0 0 8rem; font-size: .875rem; font-weight: 600; color: var(--muted); }
.changes dd { flex: 1 1 14rem; display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; min-width: 0; margin: 0; }
.changes dd .icon { align-self: center; width: 16px; height: 16px; color: var(--ink-faint); }
.changes del { color: var(--muted); }
.changes ins { font-weight: 600; text-decoration: none; }
.changes .was-empty { font-size: .875rem; color: var(--muted); }
.changes .wide dd { flex-direction: column; }
.changes .wide ins { font-weight: 400; }

/* The questions of a recording: the key of each, why it is the key, and the stretch in which it is heard. */
.heard { list-style: none; margin: 0; padding: 0; }
.heard > li { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 0 12px; padding-block: 8px; border-top: 1px solid var(--line-soft); }
.heard > li:first-child { border-top: 0; }
/* Where the row is too narrow for both, the time goes under the words, at the far end. */
.heard-main { flex: 1 1 13rem; min-width: 0; padding-block-start: 3px; }
/* The key and its reason each keep the direction of their own text, so the two always start at the same side. */
.heard-main strong { display: block; }
.heard-main .help, .heard-main .review-why { margin: 2px 0 0; }
.heard .btn { flex: none; margin-inline-start: auto; }

/* Marking */
.grade-grid { display: flex; gap: 16px; flex-wrap: wrap; }
.grade-grid > .score { flex: 0 1 150px; }
.grade-grid > .grow { flex: 1 1 320px; }
.score-input { display: flex; align-items: center; gap: 8px; font-size: 1.125rem; color: var(--muted); }
/* A score reads the same way in both directions, like every other one on the site: "7 / 10". */
[dir="rtl"] .score-input { direction: ltr; justify-content: flex-end; }
.score-input input { width: 84px; min-height: 52px; text-align: center; font-size: 1.25rem; font-weight: 700; color: var(--ink); }
.essay-review { display: flex; flex-direction: column; gap: 16px; }
/* The task as the student read it, behind its number. */
.essay-review > .task { display: flex; align-items: flex-start; gap: 12px; }
.essay-review > .task > div { flex: 1 1 0; min-width: 0; }
.essay-review > .task > div > :last-child { margin-bottom: 0; }
.essay-review + .essay-review, .table-scroll + .essay-review { margin-top: 24px; padding-top: 24px; border-top: 1px solid var(--line-soft); }
.word-note { display: inline-flex; align-items: center; gap: 8px; font-size: .875rem; font-weight: 600; color: var(--muted); }
.word-note.ok { color: var(--good); }
.word-note .icon { width: 16px; height: 16px; stroke-width: 2.8; }

/*
| How an attempt was taken (decision 148): the facts of the card, every moment in order under a line that opens them,
| and under a typed answer how it was written. A number that says something came in at once is in the colour of a
| notice, and says it in words too.
*/
.moments-box > summary { display: inline-flex; align-items: center; gap: 8px; min-height: var(--touch); cursor: pointer; font-weight: 600; color: var(--primary); list-style: none; }
.moments-box > summary::-webkit-details-marker { display: none; }
.moments-box > summary .icon { flex: none; width: 18px; height: 18px; }
.moments-box > summary:hover > span:not(.num) { text-decoration: underline; }
.moments { list-style: none; margin: 4px 0 0; padding: 0; display: grid; gap: 2px; font-size: .875rem; }
.moment { display: grid; grid-template-columns: auto 18px 1fr; align-items: start; gap: 4px 10px; padding: 6px 0; border-block-start: 1px solid var(--line); }
.moment:first-child { border-block-start: 0; }
.moment .icon { width: 18px; height: 18px; margin-block-start: 1px; stroke-width: 2; color: var(--muted); }
.moment-time { color: var(--muted); white-space: nowrap; }
.moment:is(.is-away, .is-paste, .is-bulk, .is-second_page, .is-tools) .icon { color: var(--warn); }
.writing-note { display: flex; align-items: flex-start; gap: 8px; margin: 6px 0 0; font-size: .875rem; color: var(--muted); }
.writing-note .icon { flex: none; width: 16px; height: 16px; margin-block-start: 2px; stroke-width: 2; }
.writing-note .flagged { color: var(--warn); font-weight: 600; }
/* A spoken task kept from sight until the student presses to see it (decision 149): what takes its place. */
.task-veil { display: flex; align-items: flex-start; gap: 10px; margin: 0; padding: 12px 14px; background: var(--surface-subtle); border-radius: var(--radius-control); color: var(--ink-soft); }
.task-veil .icon { flex: none; width: 18px; height: 18px; margin-block-start: 2px; stroke-width: 2; color: var(--primary); }
/*
| How strict an exam is (decision 150). Out of full screen, the site's dialog covers the exam: its ground is the page's,
| so nothing of the exam shows around it. It says one thing the first time and another after full screen was left.
*/
.full-screen-gate::backdrop { background: var(--ground); }
.full-screen-gate:not(.back) .when-back, .full-screen-gate.back .when-first { display: none; }
/* On a phone the time of a moment stands over what happened, which keeps the width of the card. */
@media (max-width: 699px) {
    .moment { grid-template-columns: 18px 1fr; }
    .moment-time { grid-column: 1 / -1; }
}
.result-mark { display: inline-flex; align-items: center; gap: 6px; font-size: .8125rem; font-weight: 600; white-space: nowrap; }
.result-mark .icon { width: 14px; height: 14px; stroke-width: 3; }
.result-mark.good { color: var(--good); }
.result-mark.bad { color: var(--bad); font-weight: 700; }
.result-mark.warn { color: var(--warn); }
.result-mark.none { color: var(--muted); }

/* The way back to the exam and, beside it, the way from one finished exam to the next. */
.marking-bar { display: flex; align-items: center; justify-content: space-between; gap: 4px 24px; flex-wrap: wrap; margin-bottom: 20px; }
.pager { display: flex; align-items: center; gap: 4px; margin-inline-start: auto; }
.pager-link {
    display: inline-flex; align-items: center; gap: 6px; min-height: var(--touch); padding: 4px 12px;
    border-radius: var(--radius-control); font-size: .9375rem; font-weight: 600; text-decoration: none; white-space: nowrap;
}
a.pager-link:hover { background: var(--primary-soft); color: var(--primary-strong); }
/* At the first and the last one the word stays in its place, without a link. */
.pager-link[aria-disabled="true"] { color: var(--muted); opacity: .6; }
.pager-link .icon { width: 16px; height: 16px; }
.pager-place { padding-inline: 8px; font-size: .9375rem; color: var(--muted); white-space: nowrap; }

/* A right answer the key did not foresee: the button stands in the row without making it taller. */
.btn.accept { margin-block: -10px; white-space: nowrap; }
.side-scores > .total { margin-block-start: -1px; border-top: 1px solid var(--control-soft); font-weight: 700; }
.side-scores > .total strong { font-size: 1.125rem; }
/* Said only while it is true: the result is about to be shown and a score is still empty. */
.check > span > .release-open { display: none; color: var(--warn); }
.release-open .icon { flex: none; width: 16px; height: 16px; margin-block-start: 2px; }
form:has(.score-input input:placeholder-shown):has(input[name="release"]:checked) .release-open { display: flex; align-items: flex-start; gap: 6px; }
/* Saving, at the bottom of the screen under both columns. A side column that holds more than a short
   screen can show scrolls by itself there, so its last field stays in reach above the bar. */
.columns > .save-bar { flex: 1 1 100%; margin-top: 0; }
.save-actions { display: flex; gap: 10px; margin-inline-start: auto; }
@media (min-width: 980px) {
    .columns:has(> .save-bar) > .side.sticky { max-height: calc(100dvh - 118px); overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; }
}

/* Marked answers: every table of the page has its columns in the same places. */
table.data.marks { table-layout: fixed; }
table.data.marks th:nth-child(1) { width: 48px; }
table.data.marks th:nth-child(2) { width: 34%; }
table.data.marks th:nth-child(4) { width: 132px; }
table.data.marks th:nth-child(5) { width: 176px; }
table.data.marks td { overflow-wrap: anywhere; }

/* What waits for the teacher on an exam: a tile, two lines, and the one button that deals with it. */
.todos { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 440px), 1fr)); gap: 12px; margin-bottom: 24px; }
.todo { display: flex; align-items: center; gap: 12px 16px; flex-wrap: wrap; padding: 14px 20px; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-card); }
.todo-text { flex: 1 1 220px; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.todo .btn { flex: none; }
.stats.lines .stat strong { white-space: nowrap; }
.stats.lines .stat strong > span { unicode-bidi: isolate; }

/* How a question was answered by everybody: its number, the key and the other answers, and how many were right. */
.breakdown-row { display: flex; align-items: flex-start; gap: 10px 14px; padding: 14px 0; border-top: 1px solid var(--line-soft); }
.breakdown-row:first-child { border-top: 0; padding-top: 4px; }
.breakdown-main { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 8px; }
.breakdown-main .review-prompt > :last-child { margin-bottom: 0; }
.breakdown-line { margin: 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; }
.breakdown-rate { flex: 0 0 132px; display: flex; flex-wrap: wrap; align-items: baseline; justify-content: flex-end; gap: 2px 6px; }
.breakdown-rate progress { flex: 1 1 100%; }
.answer-list { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 6px 14px; }
.answer-list li { display: inline-flex; align-items: center; gap: 2px; min-width: 0; }
.answer-chip { display: inline-flex; align-items: baseline; gap: 8px; min-width: 0; padding: 4px 12px; border-radius: 8px; background: var(--bad-faint); font-weight: 600; overflow-wrap: anywhere; }
.answer-chip .num { font-weight: 400; color: var(--muted); white-space: nowrap; }
/* Without a key an answer is neither right nor wrong; nobody's answer is muted. */
.answer-chip.plain { background: var(--neutral-soft); }
.answer-chip.none { background: var(--neutral-soft); font-weight: 400; color: var(--muted); }
.answer-list .btn.accept { margin-block: -6px; }

/* Under a table: what a star beside a number means. */
.table-notes { list-style: none; margin: 12px 0 0; padding: 0 4px; font-size: .875rem; color: var(--muted); }

/* Students */
.toolbar { display: flex; align-items: center; gap: 12px 20px; flex-wrap: wrap; margin-bottom: 20px; }

/*
| The levels of the site: a table of them, each with the two arrows that change its place.
*/
.order-buttons { display: inline-flex; gap: 2px; }
.order-buttons .btn { padding-inline: 10px; }
.order-buttons .btn:disabled { opacity: .3; cursor: not-allowed; }
.uses { display: inline-flex; flex-wrap: wrap; gap: 2px 16px; }
.table-note { display: flex; align-items: center; gap: 8px; margin: 12px 4px 0; }
.table-note .icon { flex: none; width: 18px; height: 18px; stroke-width: 2; }
.short-input { max-width: 320px; }
/* A few plain facts under a choice, each with its sign: what the choice reaches, and whom it does not. */
.facts { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; font-size: .875rem; color: var(--muted); }
.facts li { display: flex; align-items: flex-start; gap: 8px; }
.facts .icon { flex: none; width: 18px; height: 18px; margin-block-start: 1px; stroke-width: 2; }

.search { flex: 1 1 320px; display: flex; gap: 8px; }
.search-box { flex: 1; min-width: 0; position: relative; }
/* A search with a choice beside it (an administrator's students: whose to look at). The box takes what is left and
   never less than a line's worth; where the three do not fit, the choice and the button go under it. */
.search:has(> :is(.select, select)) { flex-wrap: wrap; }
.search:has(> :is(.select, select)) > .search-box { flex: 999 1 240px; }
.search > :is(.select, select) { flex: 1 1 200px; }
/* A choice with no search box (whose classes an administrator looks at): as wide as a name, not the toolbar. */
.search.narrow { flex: 0 1 24rem; }
.search-box .icon { position: absolute; inset-block-start: 15px; inset-inline-start: 14px; width: 18px; height: 18px; color: var(--muted); }
.search-box input { padding-inline-start: 42px; }
.history-card { display: flex; flex-direction: column; gap: 12px; padding: 20px 24px; }
.history-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.history-head .status { text-align: end; }

/* Guide */
.guide-nav { display: flex; flex-direction: column; gap: 4px; }
.guide-nav a { display: flex; align-items: center; gap: .35em; min-height: var(--touch); padding: 6px 14px; border-radius: var(--radius-control); font-weight: 600; text-decoration: none; color: var(--ink-soft); }
.guide-nav a:hover { background: var(--neutral-soft); }
.guide-nav a[aria-current="page"] { background: var(--primary-soft); color: var(--primary); }
.columns > .side.guide-nav { flex: 1 1 220px; }

.prose { font-size: 1.0625rem; line-height: 1.75; padding: 40px 48px; }
.prose > * { max-width: 720px; }
.prose > table { max-width: none; }
.prose h1 { font-size: 1.875rem; margin: 0 0 16px; }
.prose h2 { font-size: 1.375rem; margin: 36px 0 12px; }
.prose h3 { font-size: 1.125rem; margin: 28px 0 8px; }
.prose p { margin: 0 0 16px; }
.prose ul, .prose ol { margin: 0 0 16px; padding-inline-start: 26px; }
.prose li + li { margin-top: 10px; }
.prose table { display: block; overflow-x: auto; width: 100%; border-collapse: collapse; margin: 0 0 20px; font-size: 1rem; line-height: 1.5; }
.prose th, .prose td { padding: 12px 16px; border-bottom: 1px solid var(--line-soft); text-align: start; vertical-align: top; }
.prose th { font-size: .875rem; font-weight: 600; color: var(--muted); background: var(--surface-subtle); border-bottom-color: var(--line); }
.prose pre { padding: 16px; overflow: auto; border-radius: var(--radius-control); background: var(--surface-subtle); border: 1px solid var(--line); }
.prose pre code { padding: 0; background: transparent; }
@media (max-width: 600px) {
    .prose { padding: 24px 20px; }
}

/* ---------- Exercise library ---------- */

/*
| A book of exercises, its units, and a unit's exercises as a student will see
| them (views/admin/library). An exercise is drawn with the exam screen's own
| classes (.question, .choices, .blank, …) inside a card; what is added here is
| the card around it and the answer key under it.
*/
.unit-title { margin: 0; font-size: 1.5rem; }
.count { font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ink-soft); }
.jump-note { font-size: .875rem; font-weight: 400; color: var(--muted); }
.jump a:has(.jump-note) { flex-direction: column; align-items: flex-start; justify-content: center; gap: 0; padding-block: 6px; }

.exercise { scroll-margin-block-start: 16px; }
.exercise-name { display: flex; align-items: baseline; gap: 4px 12px; flex-wrap: wrap; min-width: 0; }
.exercise-name h3 { margin: 0; font-size: 1.25rem; }
.exercise-topic { color: var(--muted); }
/* Shown, not taken: the fields are switched off and say so by their cursor, without being dimmed out of reading. */
:is(.exercise, .booklet) fieldset:disabled :is(input, select, textarea, button, label) { cursor: default; }
:is(.exercise, .booklet) fieldset:disabled .choices label:hover { background: none; }
/* What tells a student how to answer is not said where nothing is answered (design system, "Words on the screen"). */
fieldset:disabled :is(.order-hint, .crayon-hint, .join-hint, .word-line .word-hint) { display: none; }
.exercise .voice { align-self: flex-start; }
.passage.inset { padding: 20px 24px; font-size: 1.0625rem; background: var(--surface-subtle); border: 1px solid var(--line); border-radius: var(--radius-control); }
.passage.inset :is(h3, h4) { margin: 0 0 12px; font-size: 1.25rem; font-weight: 600; }
.passage.inset p:last-child { margin-bottom: 0; }

.exercise-key { padding-block-start: 16px; border-top: 1px solid var(--line); }
.exercise-key .kicker { margin: 0 0 8px; }
.answer-key { list-style: none; margin: 0; padding: 0; columns: 300px; column-gap: 32px; }
.answer-key li { display: flex; align-items: flex-start; gap: 10px; padding-block: 5px; break-inside: avoid; font-size: .9375rem; }
.answer-key li > div { min-width: 0; padding-block-start: 3px; }
.answer-key .help { display: block; }

/*
| A lesson of a book (views/lessons, decision 87): the lines printed under its title and its goals, its blocks of
| every kind in cards of the exercise's make, where a step points, and the teacher's notes beside what they are
| about. The same on the teacher's page, the class's screen and a student's page.
*/
.block { scroll-margin-block-start: 16px; }
.lesson-aims { display: flex; flex-wrap: wrap; gap: 16px 40px; }
.lesson-aims > * { flex: 1 1 280px; min-width: 0; }
.lesson-aims .kicker { margin: 0; }
.lesson-focus { display: flex; flex-direction: column; gap: 10px; margin: 0; }
.lesson-focus > div { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 12px; }
/* The book's own word for each line, as it prints it ("GRAMMAR", "How to …"). */
.lesson-focus dt { flex: 0 0 8.5rem; font-size: .8125rem; font-weight: 700; letter-spacing: .04em; color: var(--muted); }
.lesson-focus dd { flex: 1 1 200px; margin: 0; font-weight: 600; }
.lesson-goals { list-style: none; margin: 10px 0 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
/* A goal's skill in a column of its own, so that the goals' words start in one line under each other. */
.lesson-goals li { display: grid; grid-template-columns: 6.5rem minmax(0, 1fr) auto; align-items: baseline; gap: 4px 10px; }
.lesson-goals li > .badge { justify-self: start; }

/* A step's words are its whole content: no rule under them. */
.instructions.flush { margin: 0; padding-block-end: 0; }
.instructions.flush::after { content: none; }
.block .voice, .block .lesson-link { align-self: flex-start; }
.lesson-link { display: inline-flex; align-items: center; gap: 8px; min-height: var(--touch); font-weight: 600; text-decoration: none; color: var(--primary); }
.lesson-link:hover > span { text-decoration: underline; }
.lesson-link .icon { flex: none; width: 18px; height: 18px; }

.activity-language .kicker { display: flex; align-items: center; gap: 6px; }
.activity-language > :last-child { margin-bottom: 0; }
.activity-language .kicker .icon { width: 16px; height: 16px; }
.activity-goal { display: flex; align-items: flex-start; gap: 8px; margin: 0; }
.activity-goal > .icon { flex: none; width: 18px; height: 18px; color: var(--primary); translate: 0 3px; }
.role-list, .done-list { list-style: none; margin: 8px 0 0; padding: 0; display: flex; flex-direction: column; }
.role-list { gap: 16px; }
.role-name { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 12px; margin: 0; }
.role-list .help { margin: 2px 0 0; }
.role-card { margin-block-start: 8px; font-size: 1rem; }
.done-list { gap: 6px; }
.done-list li { display: flex; align-items: flex-start; gap: 8px; }
.done-list .icon { flex: none; width: 18px; height: 18px; color: var(--good); translate: 0 3px; }

/* A word list: as many columns as fit, each word with its example and its own recording; where the book gives
   pictures, each word is a tile with its picture above it, as a picture dictionary prints it. */
.vocab-list { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 240px), 1fr)); gap: 16px 24px; }
.vocab-list li { display: flex; align-items: center; gap: 12px; min-width: 0; }
.vocab-list.pictured { grid-template-columns: repeat(auto-fill, minmax(min(100%, 180px), 1fr)); }
.vocab-list.pictured li { flex-wrap: wrap; align-items: flex-start; }
.vocab-list.pictured .vocab-picture { flex: 1 1 100%; }
.vocab-picture { flex: none; line-height: 0; }
.vocab-word { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 2px; overflow-wrap: anywhere; }
/* A word's own recording: the button alone (the voice component, with its bar and its time kept unseen). */
.voice.compact { flex: none; padding: 0; border: 0; background: none; }
.voice.compact :is(input[type="range"], .voice-time) { display: none; }
.voice.compact .voice-button { width: 40px; height: 40px; }
.voice.compact .voice-button .icon { width: 16px; height: 16px; }

/* A note for the teacher: not part of the page as printed, so it stands on its own ground, behind a dashed line. */
.lesson-note { display: flex; flex-direction: column; gap: 6px; padding: 14px 16px; background: var(--surface-subtle); border: 1px dashed var(--control); border-radius: var(--radius-control); }
.lesson-note-head { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; margin: 0; }
.lesson-note-head .kicker { display: inline-flex; align-items: center; gap: 4px; }
.lesson-note-head .kicker .icon { width: 14px; height: 14px; }
.lesson-note-text > :last-child { margin-bottom: 0; }
/* What a course's guide says to a learner alone, as the teacher reads it on the guide's page (the self-study
   proposal, section 5). Its Persian line is the one Persian a page of the site shows: content of a book, after the
   English and smaller, right to left in its own element (CLAUDE.md, "The site is in English only"). */
.coach-fa { margin: 0; font-size: .9375rem; line-height: 1.9; color: var(--ink-soft); text-align: start; }
.coach-practice { margin: 0; padding: 0; border: 0; min-inline-size: 0; display: flex; flex-direction: column; gap: 10px; }

/* The lesson in class, on the teacher's page of a unit: its screen, and the classes it is open for. */
.in-class-row { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 16px; }
.in-class-row > .pick-row { flex: 1 1 360px; }
.open-for { list-style: none; margin: 0; padding: 0; }
.open-for li { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 4px 16px; padding-block: 4px; border-top: 1px solid var(--line-soft); }
.open-for-name { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 4px 8px; min-width: 0; }
.open-for-name > a { font-weight: 600; }
/* The lessons opened for a class, under its homework (views/admin/classes/show). */
.class-lessons { margin-block-start: 32px; }

/* ---------- The page of books (decision 116) ---------- */

/*
| The books of the site's library and a teacher's own (views/admin/library/index): one element per book, which is a
| card with a cover in the covers and a row in the list — the same element, so that choosing the other way to see
| them moves each cover to its place instead of drawing everything again. What narrows them and the choice of how to
| see them are done on the page, by ui.js ([data-books]); the server draws every book once. A book is a thing, not
| a box: its cover stands on the page without a line round it, and the keyboard's mark lies on the cover.
*/
.books-toolbar { margin-bottom: 12px; }
.books-toolbar .search { flex: 1 1 280px; min-width: 0; }
.books-who { flex: 0 1 240px; min-width: 200px; }

/* Two ways to see the books; the pill under the chosen one slides to the other. Both are as wide as the wider. */
.view-switch { position: relative; display: inline-grid; grid-template-columns: 1fr 1fr; padding: 3px; border-radius: 12px; background: var(--track); }
.view-switch::before {
    content: ""; position: absolute; inset-block: 3px; inset-inline-start: 3px; inline-size: calc(50% - 3px);
    border-radius: 9px; background: var(--surface); box-shadow: var(--shadow-raised);
    transition: translate var(--motion-base) var(--ease-out);
}
.view-switch:has([data-books-view="list"][aria-pressed="true"])::before { translate: 100% 0; }
[dir="rtl"] .view-switch:has([data-books-view="list"][aria-pressed="true"])::before { translate: -100% 0; }
.view-switch button {
    position: relative; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
    min-height: 38px; padding: 4px 16px; border: 0; border-radius: 9px; background: transparent;
    font: inherit; font-size: .9375rem; font-weight: 600; color: var(--muted); cursor: pointer;
    transition: color var(--motion-fast) var(--ease);
}
.view-switch button[aria-pressed="true"] { color: var(--ink); }
.view-switch button:focus-visible { outline: 0; box-shadow: var(--halo); }
.view-switch .icon { width: 18px; height: 18px; stroke-width: 2; }

/* The kinds of book: a row of choices that scrolls sideways where it does not fit. The chosen one differs in its
   ground and colour, never in its weight (design system, "What does not change does not move"). */
.chips { display: flex; gap: 8px; margin: 0 -2px 8px; padding: 2px 2px 10px; overflow-x: auto; scrollbar-width: none; }
.chips::-webkit-scrollbar { display: none; }
.chip {
    display: inline-flex; align-items: center; gap: 8px; flex: none; min-height: var(--touch); padding: 0 16px;
    border: 0; border-radius: var(--radius-pill); background: var(--track); color: var(--ink-soft);
    font: inherit; font-size: .9375rem; font-weight: 600; cursor: pointer;
    transition: background-color var(--motion-fast) var(--ease), color var(--motion-fast) var(--ease);
}
.chip:hover { background: var(--neutral-soft); color: var(--ink); }
.chip[aria-pressed="true"] { background: var(--primary-soft); color: var(--primary); }
.chip:focus-visible { outline: 0; box-shadow: var(--halo); }
.chip-count { min-inline-size: 1.25em; text-align: center; font-size: .8125rem; color: var(--muted); }
.chip[aria-pressed="true"] .chip-count { color: var(--primary); }

.books { display: flex; flex-direction: column; gap: 36px; margin-top: 18px; }
.book-group-head { margin-bottom: 18px; }
.book-group-head h2 { font-size: 1.25rem; line-height: 1.2; }
.book-group-head .meta { margin: 2px 0 0; }
.book-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(176px, 1fr)); gap: 28px 24px; }

.book { display: flex; flex-direction: column; gap: 14px; min-width: 0; color: var(--ink); text-decoration: none; }
.book:hover { color: var(--ink); }
.book:focus-visible { outline: 0; }
.book:focus-visible .book-cover { box-shadow: var(--halo); }

.book-cover {
    --cover: var(--kind-other); --shape: var(--kind-other-shape);
    position: relative; display: block; aspect-ratio: 3 / 4; overflow: hidden; color: var(--on-cover);
    border-radius: 4px 10px 10px 4px; background: var(--cover); box-shadow: var(--shadow-lift);
    transition: translate var(--motion-base) var(--ease-out), box-shadow var(--motion-base) var(--ease);
}
.book-cover[data-kind="students_book"] { --cover: var(--kind-students_book); --shape: var(--kind-students_book-shape); }
.book-cover[data-kind="workbook"] { --cover: var(--kind-workbook); --shape: var(--kind-workbook-shape); }
.book-cover[data-kind="teachers_book"] { --cover: var(--kind-teachers_book); --shape: var(--kind-teachers_book-shape); }
.book-cover[data-kind="guide"] { --cover: var(--kind-guide); --shape: var(--kind-guide-shape); }
.book-cover[data-kind="tests"] { --cover: var(--kind-tests); --shape: var(--kind-tests-shape); }
.book-cover[data-kind="resources"] { --cover: var(--kind-resources); --shape: var(--kind-resources-shape); }
.book-cover[data-kind="method"] { --cover: var(--kind-method); --shape: var(--kind-method-shape); }
/* The spine: a shade down the side the book is bound on, with a thread of light beside it. */
.book-cover::before {
    content: ""; position: absolute; z-index: 1; inset-block: 0; inset-inline-start: 0; inline-size: 7px;
    background: var(--cover-shade); box-shadow: 1px 0 0 var(--cover-glow);
}
@media (hover: hover) {
    .book:hover .book-cover { translate: 0 -3px; box-shadow: var(--shadow-pop); }
}
.book-picture { display: block; inline-size: 100%; block-size: 100%; object-fit: cover; }

/* A book without a picture: a cover drawn from its course and its kind. */
.book-art { position: absolute; inset: 0; display: flex; flex-direction: column; gap: 6px; padding: 16px 14px 14px 21px; }
.book-art::before, .book-art::after { content: ""; position: absolute; border-radius: 50%; pointer-events: none; }
.book-art::before { inline-size: 130%; aspect-ratio: 1; inset-inline-end: -48%; inset-block-end: -56%; background: var(--shape); }
.book-art::after { inline-size: 38%; aspect-ratio: 1; inset-inline-start: 16%; inset-block-end: 34%; background: var(--cover-glow); }
.book-art > * { position: relative; z-index: 1; }
.book-art-kind { order: -1; font-size: .6875rem; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; opacity: .9; }
.book-art-name {
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden;
    font-family: var(--font-read); font-size: 1.375rem; font-weight: 700; line-height: 1.12; overflow-wrap: anywhere;
}
.book-art-initial { display: none; }
.book-level {
    position: absolute; z-index: 2; inset-block-end: 10px; inset-inline-end: 10px; padding: 1px 10px; border-radius: var(--radius-pill);
    background: var(--surface); color: var(--ink); font-size: .8125rem; font-weight: 700; box-shadow: var(--shadow-raised);
}

.book-info { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.book-title {
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
    font-size: 1.0625rem; font-weight: 700; line-height: 1.3; overflow-wrap: anywhere;
}
.book-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.book-tags:empty { display: none; }
.book-level-tag, .book-go { display: none; }
.book .meta-row { gap: 2px 14px; }

/* The list: the same books, one to a row, the cover as small as a spine you can still read the colour of. */
.books[data-view="list"] .book-grid { display: flex; flex-direction: column; gap: 10px; }
.books[data-view="list"] .book {
    flex-direction: row; align-items: center; gap: 16px 20px; padding: 12px 20px 12px 12px;
    border-radius: var(--radius-card); background: var(--surface); box-shadow: var(--shadow-raised);
    transition: box-shadow var(--motion-base) var(--ease);
}
.books[data-view="list"] .book:hover { box-shadow: var(--shadow-pop); }
.books[data-view="list"] .book:focus-visible { box-shadow: var(--halo); }
.books[data-view="list"] .book:focus-visible .book-cover { box-shadow: var(--shadow-raised); }
.books[data-view="list"] .book-cover { flex: none; inline-size: 48px; border-radius: 3px 6px 6px 3px; box-shadow: var(--shadow-raised); }
.books[data-view="list"] .book-cover::before { inline-size: 4px; }
.books[data-view="list"] .book:hover .book-cover { translate: 0 0; box-shadow: var(--shadow-raised); }
.books[data-view="list"] .book-art { align-items: center; justify-content: center; padding: 0; }
.books[data-view="list"] .book-art::before { inline-size: 160%; inset-inline-end: -70%; inset-block-end: -50%; }
.books[data-view="list"] .book-art::after, .books[data-view="list"] :is(.book-art-name, .book-art-kind, .book-level) { display: none; }
.books[data-view="list"] .book-art-initial { display: block; padding-inline-start: 3px; font-family: var(--font-read); font-size: 1.25rem; font-weight: 700; line-height: 1; }
.books[data-view="list"] .book-info { flex: 999 1 240px; gap: 4px; }
.books[data-view="list"] .book-title { -webkit-line-clamp: 1; }
.books[data-view="list"] .book-level-tag { display: inline-flex; }
.books[data-view="list"] .book-go { display: block; flex: none; inline-size: 20px; block-size: 20px; color: var(--muted); transition: translate var(--motion-base) var(--ease-out); }
.books[data-view="list"] .book:hover .book-go { translate: 3px 0; }
[dir="rtl"] .books[data-view="list"] .book:hover .book-go { translate: -3px 0; }

/* Books moved to their places by a choice made on the page: each glides there on the curve the tabs' line does. */
.book, .book-cover { view-transition-class: book; }
::view-transition-group(.book) { animation-duration: 340ms; animation-timing-function: var(--ease-out); }

@media (max-width: 700px) {
    .books-toolbar .search { flex-basis: 100%; }
    .books-who { flex: 1 1 100%; }
    .view-switch { flex: 1 1 100%; }
    .book-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px 16px; }
    .books[data-view="list"] .book { padding: 10px 14px 10px 10px; gap: 14px; }
    .books[data-view="list"] .book-cover { inline-size: 44px; }
}

/* A book's details: the cover it is shown by, as the list will show it, beside the means to change it. */
.cover-field { display: flex; align-items: flex-start; gap: 24px 32px; flex-wrap: wrap; }
.cover-preview { flex: none; inline-size: 168px; }
.cover-field > .stack { flex: 1 1 280px; min-width: 0; }
.cover-heading { margin: 0 0 2px; font-size: 1.125rem; }
.cover-field .help { margin: 0; }
.cover-field .dropzone { min-height: 110px; }
.cover-preview .book-cover { translate: 0 0; }
.cover-preview.is-removed .book-cover { opacity: .35; }

/* ---------- An activity in class (decision 99) ----------
   Pair and group work done with a class: begun under the activity on the teacher's page of a lesson, followed on the
   teacher's page of it, shown on the class's screen and done on the students' phones. Its live parts are drawn again
   by ui.js only where they differ; a part that comes in (a group, a recording, a student's row) fades in, once. */
.arrives { animation: fade-in var(--motion-enter) var(--ease) both; }

/* Under the activity on the lesson's page: where it is going on, and beginning it with a class. */
.activity-start { display: flex; flex-direction: column; gap: 8px; padding-block-start: 12px; border-top: 1px solid var(--line-soft); }
.activity-start .lesson-link { align-self: flex-start; }

/* The teacher's page: what to do with the groups, the groups, their recordings, the class beside them. */
.activity-actions { display: flex; flex-direction: column; gap: 16px; }
.activity-actions-lead { display: flex; align-items: center; gap: 12px; }
.activity-actions-lead p { margin: 0; }
.activity-joined { font-size: 1.0625rem; }
.activity-buttons { display: flex; flex-wrap: wrap; gap: 10px; }
.activity-actions > .notice { margin: 0; }
.group-grid { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr)); gap: 16px; }
.activity-group { display: flex; flex-direction: column; gap: 12px; }
.activity-group-head { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 6px 12px; margin: 0; }
.activity-group-head h3 { margin: 0; font-size: 1.0625rem; }
.group-members { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.group-members li { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 10px; min-height: 32px; }
.group-members .person { gap: 8px; font-weight: 600; }
.group-members .avatar { width: 32px; height: 32px; font-size: .8125rem; }
.tick-list { list-style: none; margin: 0; padding: 10px 0 0; display: flex; flex-direction: column; gap: 6px; border-top: 1px solid var(--line-soft); font-size: .9375rem; color: var(--muted); }
.tick-list li { display: flex; align-items: flex-start; gap: 8px; }
.tick-list .icon { flex: none; width: 18px; height: 18px; translate: 0 2px; }
.tick-list li.ticked { color: var(--ink); }
.tick-list li.ticked .icon { color: var(--good); }

.recording-list { list-style: none; margin: 0; padding: 0; }
.activity-recording { display: flex; flex-direction: column; gap: 10px; padding: 16px 20px; }
.activity-recording + .activity-recording { border-top: 1px solid var(--line-soft); }
.activity-recording-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 6px 16px; }
.activity-recording-who { display: flex; flex-direction: column; gap: 2px; margin: 0; min-width: 0; }
.activity-feedback textarea { min-height: 0; }
.activity-feedback .row { align-items: center; }
.activity-feedback .row > .help { flex: 1 1 200px; margin: 0; }
.activity-teacher-words .kicker { display: flex; align-items: center; gap: 6px; }
.activity-teacher-words .kicker .icon { width: 16px; height: 16px; }
.activity-teacher-words p:last-child { margin: 0; }

.people-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.person-row { display: flex; flex-direction: column; gap: 8px; padding-block: 12px; border-top: 1px solid var(--line-soft); }
.person-row:first-child { border-top: 0; padding-block-start: 0; }
.person-row .person { gap: 10px; }
.person-text { display: flex; flex-direction: column; min-width: 0; }
.person-name { font-weight: 600; overflow-wrap: anywhere; }
/* Whose phone has the activity open: a dot and a word, never the colour alone. ui.js keeps data-presence true. */
.presence { display: inline-flex; align-items: center; gap: 6px; font-size: .8125rem; color: var(--muted); }
.presence-dot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--line); }
[data-presence="here"] .presence-dot { background: var(--good); }
[data-presence="away"] .presence-dot { background: var(--warn); }
[data-presence]:not([data-presence="here"]) .when-here,
[data-presence]:not([data-presence="away"]) .when-away,
[data-presence]:not([data-presence="none"]) .when-none { display: none; }
.person-move > .btn { flex: none; }
.person-move > :is(.select, select) { flex: 1 1 140px; }

.activity-task-card > summary { cursor: pointer; list-style: none; }
.activity-task-card > summary::-webkit-details-marker { display: none; }
.activity-task-card > summary > span:not(.tile) { flex: 1; display: flex; flex-direction: column; }
.activity-task-card > summary .card-title { margin: 0; }
.activity-task-card .details-sign { flex: none; width: 20px; height: 20px; color: var(--muted); transition: rotate var(--motion-fast) var(--ease); }
.activity-task-card[open] .details-sign { rotate: 180deg; }
.activity-task { margin-block-start: 16px; }
.activity-task-card .activity-task { padding-block-start: 16px; border-top: 1px solid var(--line-soft); }

/* The class's screen: read from the back of the room. */
.activity-task.on-screen { font-size: 1.25rem; }
.activity-task.on-screen .instructions { font-size: 1.375rem; }
.activity-join { display: flex; flex-direction: column; gap: 8px; }
.activity-join p { margin: 0; }
.activity-join-count { display: flex; align-items: center; gap: 10px; font-size: 1.25rem; }
.activity-join-count .icon { flex: none; width: 24px; height: 24px; color: var(--primary); }
.activity-join-how { font-size: 1.125rem; color: var(--muted); }
.activity-screen-none { margin: 0; font-size: 1.25rem; color: var(--muted); }
.group-grid.on-screen { grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr)); }
.group-grid.on-screen .activity-group-head { font-size: 1.25rem; }
.group-grid.on-screen .group-members li { font-size: 1.25rem; }

/* A student's phone: what shows follows data-state (waiting, grouped, ended), which ui.js keeps true. */
.activity-phone:not([data-state="grouped"]) .when-grouped { display: none; }
.activity-phone > .notice { margin: 0; }
/* A live part with nothing in it takes no room (it holds the spaces the view leaves, so not :empty). */
.activity-status:not(:has(*)), .activity-own:not(:has(*)), .activity-recordings:not(:has(*)) { display: none; }
/* A card that is its heading alone has no room under it. */
:is(.activity-waiting, .activity-over) > .heading { margin-bottom: 0; }
.activity-with { display: flex; flex-direction: column; gap: 10px; }
.activity-with .card-title { margin: 0; }
.activity-with .kicker, .role-own .kicker { margin: 0; }
.role-own { display: flex; flex-direction: column; gap: 10px; border-color: var(--primary); }
.role-own-name { margin: 0; font-size: 1.5rem; }
.role-own .role-card { margin: 0; font-size: 1.0625rem; }
.role-own .role-card .kicker { display: flex; align-items: center; gap: 6px; margin-block-end: 6px; }
.role-own .role-card .kicker .icon { width: 14px; height: 14px; }
.role-own .help { margin: 0; }
.activity-done .card-title, .activity-record .card-title { margin: 0; }
.activity-recorder .recorder { max-width: none; }

/* A sample answer the book prints, beside the key of a task the teacher marks (decision 88): a text to read, so the
   key's row that holds it spans the key's columns. */
.answer-key li:has(.model-answer) { column-span: all; }
.model-answer { max-width: 75ch; margin-block-start: 6px; padding: 10px 12px; background: var(--surface-subtle); border-radius: var(--radius-control); }
.model-answer .kicker { display: block; margin-block-end: 4px; }
.model-answer p { margin: 0 0 .5em; }
.model-answer > div > :last-child { margin-bottom: 0; }

/* A unit as printed (admin/library/units/pages, decision 93): the pictures of its pages, two side by side where there
   is room, as the book lies open, each under its number. */
.book-pages { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 420px), 1fr)); gap: 24px; align-items: start; }
.book-page { display: flex; flex-direction: column; align-items: center; gap: 8px; margin: 0; }
.book-page .picture-open, .book-page .picture { inline-size: 100%; }
.book-page .picture { block-size: auto; border: 1px solid var(--line); }

/*
| The cutter (admin/library/cut, decision 94): the picture of a page, and on it the frame of the picture to cut out,
| the rest of the page dimmed. The frame stands in hundredths of the page (--x, --y, --w, --h, set by ui.js); the page
| is left to right in every language, as a picture's own corners are. A corner is a small round handle with a larger
| place to press; the frame and its corners are moved with a finger too, so the page around them still scrolls.
*/
.cutter { position: relative; line-height: 0; overflow: hidden; border: 1px solid var(--line); border-radius: var(--radius-control); background: var(--surface-subtle); cursor: crosshair; touch-action: pan-y pinch-zoom; user-select: none; -webkit-user-select: none; }
.cutter-page { display: block; inline-size: 100%; block-size: auto; pointer-events: none; }
.cutter-box {
    position: absolute; inset-inline-start: calc(var(--x, 25) * 1%); inset-block-start: calc(var(--y, 25) * 1%);
    inline-size: calc(var(--w, 50) * 1%); block-size: calc(var(--h, 30) * 1%);
    border: 2px solid var(--primary); box-shadow: 0 0 0 100vmax var(--scrim); cursor: move; touch-action: none;
}
.cutter-handle { position: absolute; inline-size: 18px; block-size: 18px; border: 2px solid var(--primary); border-radius: 50%; background: var(--surface); touch-action: none; }
.cutter-handle::before { content: ""; position: absolute; inset: -13px; }
.cutter-handle[data-corner="nw"] { inset-inline-start: -10px; inset-block-start: -10px; cursor: nwse-resize; }
.cutter-handle[data-corner="ne"] { inset-inline-end: -10px; inset-block-start: -10px; cursor: nesw-resize; }
.cutter-handle[data-corner="sw"] { inset-inline-start: -10px; inset-block-end: -10px; cursor: nesw-resize; }
.cutter-handle[data-corner="se"] { inset-inline-end: -10px; inset-block-end: -10px; cursor: nwse-resize; }
.cutter-fields { margin: 0; padding: 0; border: 0; }
.cutter-fields .field-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.cutter-preview, .cutter-now { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
.cutter-preview canvas { max-inline-size: 100%; block-size: auto; border: 1px solid var(--line); border-radius: 6px; background: var(--surface-subtle); }

/* The script of a recording or a video (views/partials/script, decision 90): a line under its player that opens into
   the book's text. Its words stay where they stand when it opens; only the sign beside them turns. */
.script > summary { display: inline-flex; align-items: center; gap: 8px; min-height: var(--touch); cursor: pointer; font-weight: 600; color: var(--primary); list-style: none; }
.script > summary::-webkit-details-marker { display: none; }
.script > summary .icon { flex: none; width: 18px; height: 18px; }
.script > summary:hover > span { text-decoration: underline; }
.script-turn { transition: rotate var(--motion-fast) var(--ease); }
.script[open] .script-turn { rotate: 180deg; }
.script-text { max-width: 75ch; margin-block-start: 4px; }

/*
| A principle of a course's method (views/lessons/blocks/principle, decision 123): whom it is for, the principle a little
| larger than the text around it, its reason, the rule for a learner alone in a note's frame, and a line of facts.
*/
.principle-head { gap: 6px 10px; margin: 0; }
.principle-statement { max-width: 75ch; font-size: 1.0625rem; font-weight: 600; }
.principle-statement > :last-child, .principle-why > div > :last-child { margin-bottom: 0; }
.principle-why { display: flex; flex-direction: column; gap: 2px; max-width: 75ch; color: var(--ink-soft); }
.principle-facts { display: flex; flex-wrap: wrap; gap: 8px 28px; margin: 0; font-size: .875rem; }
.principle-facts dt { font-weight: 600; color: var(--muted); }
.principle-facts dd { margin: 0; }

/*
| What an exercise is about (views/lessons). A concept is a small
| link of its own (.concept-row > a.tag), to its page; a concept of a question is named under its key.
*/
.concept-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; margin: 0; }
.concept-row > .kicker { margin: 0; margin-inline-end: 4px; }
a.tag { text-decoration: none; transition: background-color var(--motion-fast) var(--ease), color var(--motion-fast) var(--ease); }
a.tag:hover { background: var(--primary-soft); color: var(--primary); }
.course-books { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 16px; }

/*
| A book's contents and a lesson's head (decision 95). The contents are the book's units, each a heading over its
| rows, a lesson's row naming the sections printed for it elsewhere. A lesson's page begins with the way back, the
| unit it is in, its title, and a tab for its own pages and one for each section: the same on each of those pages
| (data-persist), so only what is under it changes.
*/
.contents-sep { margin-inline: .3em; }
.contents-sections { margin-top: 2px; }
.contents-sections .count { margin-inline-start: 4px; font-weight: 600; }
.lesson-head { display: flex; flex-direction: column; gap: 12px; margin-bottom: 24px; }
.lesson-title { display: flex; flex-direction: column; gap: 2px; }
.lesson-title .kicker { margin: 0; }
.lesson-title h1 { margin: 0; }
.lesson-tabs { border-bottom: 1px solid var(--line); }
.lesson-meta { margin-bottom: 24px; }

/*
| A course learned alone (views/learn, the self-study proposal). The course's page: the way on, and every lesson of the
| course in the book's order, a Workbook practice standing in under its lesson. A lesson's page: its head and its
| parts as numbered steps (the same on every part: data-persist, and only the steps' marks change), one part shown at
| a time; the guide's words, each beside its own mark; and under every question, once it is answered, what it came
| to — a tick, a hint, or the answer with why, more examples and one more to try — on the ground of its state.
*/
.course-next { display: flex; align-items: center; flex-wrap: wrap; gap: 12px 20px; color: var(--ink); text-decoration: none; border-color: var(--primary); }
.course-next:hover { color: var(--ink); box-shadow: var(--shadow-lift); }
.course-next .help { margin: 0; }
.practice-row { margin-inline-start: 28px; padding-block: 12px; }
.practice-row .list-title { font-size: 1rem; }
.course-card-head { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 20px; }
.course-card-head > div { flex: 1 1 200px; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.course-card-head p { margin: 2px 0 0; }
.course-card-head progress { flex: 1 1 160px; height: 8px; }

.study-head { margin-bottom: 20px; }
.study-stages ol { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
.study-stages a {
    position: relative; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
    min-width: var(--touch); height: var(--touch); padding-inline: 12px; border: 1px solid var(--line); border-radius: var(--radius-control);
    background: var(--surface); color: var(--ink-soft); font-weight: 700; text-decoration: none;
    transition: border-color var(--motion-fast) var(--ease), background-color var(--motion-fast) var(--ease), color var(--motion-fast) var(--ease);
}
.study-stages a:hover { border-color: var(--primary); color: var(--ink); }
.study-stages a.done { background: var(--primary-soft); border-color: transparent; color: var(--primary-strong); }
.study-stages a[aria-current="step"] { background: var(--primary); border-color: var(--primary); color: var(--on-primary); }
/* A part gone through has a tick on its corner: on the corner, so that the steps beside it do not move when it comes. */
.study-stages a:not([data-stage-link="end"]) .study-stage-mark {
    position: absolute; inset-block-start: -6px; inset-inline-end: -6px; width: 18px; height: 18px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center; background: var(--good); color: var(--on-state);
}
.study-stages a:not([data-stage-link="end"]) .study-stage-mark:empty { display: none; }
.study-stage-mark .icon { width: 12px; height: 12px; stroke-width: 3.2; }
.study-stages a[data-stage-link="end"] .study-stage-mark { display: inline-flex; }
.study-stages a[data-stage-link="end"] .study-stage-mark .icon { width: 16px; height: 16px; }
.study-stage-title { margin: 0; font-size: 1.25rem; }
.study-stage-title:focus { outline: none; }
.study-section-head { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 10px; margin: 8px 0 0; padding-block-start: 16px; border-top: 1px solid var(--line); }
.study-section-head .icon { width: 16px; height: 16px; }
.study-ways { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px; margin-block-start: 8px; }

/*
| The course's teacher (decision 113): her mark — the first letter of her name on the warm accent, the colour of
| "where you are" — beside a bubble of her words, its name line on top. Large on a screen of her own (a part's start,
| a short lesson, the summary), small inside what an answer came to. Her Persian line is the guide's, after its
| English (.coach-fa).
*/
.teacher-says { display: flex; align-items: flex-start; gap: 12px; }
.teacher-mark {
    flex: none; width: 40px; height: 40px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
    background: var(--accent-soft); color: var(--accent-strong); font-weight: 700; font-size: 1.0625rem;
}
.teacher-bubble {
    position: relative; flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 8px;
    padding: 12px 16px 14px; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-card);
    border-start-start-radius: 4px;
}
.teacher-name { margin: 0; font-size: .8125rem; font-weight: 700; color: var(--accent-strong); }
.teacher-kicker { font-weight: 600; color: var(--muted); }
.teacher-bubble p { margin: 0; }
.teacher-words > :last-child { margin-bottom: 0; }
.teacher-words p { margin: 0 0 .5em; }
.teacher-says.large .teacher-mark { width: 52px; height: 52px; font-size: 1.375rem; }
.teacher-says.large .teacher-bubble { padding: 16px 20px 18px; font-size: 1.0625rem; line-height: 1.6; }
.teacher-says.small { gap: 10px; }
.teacher-says.small .teacher-mark { width: 28px; height: 28px; font-size: .8125rem; }
.teacher-says.small .teacher-bubble { padding: 0; background: none; border: 0; gap: 4px; }

/* How far the learner is in the lesson: a bar that fills (--done, from 0 to 1, written by study.js). */
.study-progress { position: relative; height: 6px; border-radius: var(--radius-pill); background: var(--track); overflow: hidden; }
.study-progress-bar {
    position: absolute; inset: 0; border-radius: inherit; background: var(--primary);
    transform: scaleX(var(--done, 0)); transform-origin: left center; transition: transform var(--motion-base) var(--ease-settle);
}
[dir="rtl"] .study-progress-bar { transform-origin: right center; }

/* Today's minutes under the teacher's word at the end of a part or a review (decision 114): in line with her bubble. */
.study-today { margin: 0; padding-inline-start: 64px; }
/* The day on the course's page: the minutes, a bar of them against the goal, the days in a row. */
.study-day progress { flex: none; width: 100%; }
.study-day .help .icon { vertical-align: -2px; }

/* A part, and its screens: one at a time with the page's script, one under another without it. */
.study-stage { display: flex; flex-direction: column; gap: 20px; }
.study-screen { display: flex; flex-direction: column; gap: 16px; }
.study:not(.by-screen) .study-screen + .study-screen { padding-block-start: 24px; border-top: 1px solid var(--line-soft); }
.study-screen-ways { margin-block-start: 4px; }
/* Writing in a lesson is a few lines, not an exam's essay: the box is the height of a short answer, and grows. */
.study .essay-editor textarea { min-height: 9rem; field-sizing: content; max-height: 60vh; }

/* Say it, then hear yourself: the recorder of a learner alone. */
.study-say { display: flex; flex-direction: column; gap: 10px; padding: 14px 16px; border-radius: var(--radius-control); background: var(--surface-subtle); }
.study-say-row { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; }
.study-say-row progress { flex: 1 1 160px; }
.study-say [data-say-status]:empty { display: none; }
.study-say.recording [data-say-record] { border-color: var(--bad); color: var(--bad); }

.study-answer { display: flex; flex-direction: column; gap: 16px; }
.study-press { display: flex; }
.study-feedback-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.study-feedback-list:not(:has(> li:not([hidden]))) { display: none; }
.study-feedback-list > li { display: flex; align-items: flex-start; gap: 10px; }
.study-feedback-list .qnum { margin-block-start: 10px; }
.study-feedback-body { flex: 1 1 0; min-width: 0; }
.study-teach { display: flex; flex-direction: column; gap: 10px; padding: 12px 14px; border-radius: var(--radius-control); background: var(--neutral-soft); }
.study-teach[data-teach^="right"] { background: var(--good-soft); }
.study-teach[data-teach="again"] { background: var(--warn-soft); }
.study-teach[data-teach="shown"] { background: var(--bad-faint); }
.study-verdict { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 8px; margin: 0; color: var(--ink); }
.study-verdict .icon { flex: none; width: 18px; height: 18px; stroke-width: 2.6; }
[data-teach^="right"] > .study-verdict, [data-teach="kept"] > .study-verdict { color: var(--good); }
[data-teach="again"] > .study-verdict { color: var(--warn); }
[data-teach="shown"] > .study-verdict .icon { color: var(--bad); }
.study-verdict.error { color: var(--bad); }
.study-hint, .study-why, .study-examples, .study-practice { display: flex; flex-direction: column; gap: 6px; }
:is(.study-why, .study-examples, .study-practice) { padding-block-start: 10px; border-top: 1px solid color-mix(in srgb, var(--ink) 10%, transparent); }
:is(.study-hint, .study-why, .study-examples, .study-practice) .kicker { margin: 0; }
:is(.study-hint, .study-why, .study-examples) p { margin: 0 0 .5em; }
:is(.study-hint, .study-why, .study-examples) > div > :last-child { margin-bottom: 0; }
.study-teach .model-answer { margin: 0; background: var(--surface); }
.study-practice .study-answer { padding: 12px; border-radius: var(--radius-control); background: var(--surface); }

/* The lesson's end: each goal, and how sure the learner is of it now. */
.study-goals { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.study-goals li { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px 20px; padding-block: 12px; border-top: 1px solid var(--line-soft); }
.study-goals li > span { flex: 1 1 260px; min-width: 0; }
.study-scale { flex-wrap: wrap; }

/* What a learner did, for the administrator (views/admin/users/enrolment). */
.record-goals { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; font-size: .9375rem; }
.record-goals li { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; }
.record-questions { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.record-questions > li { display: flex; align-items: flex-start; gap: 10px; padding-block: 8px; border-top: 1px solid var(--line-soft); }
.record-tries { flex: 1 1 0; min-width: 0; display: flex; flex-wrap: wrap; gap: 6px 14px; }
.record-try { display: inline-flex; align-items: baseline; gap: 6px; overflow-wrap: anywhere; color: var(--ink-soft); }
.record-try .icon { flex: none; width: 14px; height: 14px; stroke-width: 2.8; translate: 0 2px; }
.record-try.good .icon { color: var(--good); }
.record-try.bad .icon { color: var(--bad); }

/*
| The concepts (views/admin/concepts): the list reads as a tree, a concept under another standing in a little; a key
| is shown as it is written, small and left to right.
*/
.concept-key { font-family: var(--font-code); font-size: .8125rem; color: var(--muted); overflow-wrap: anywhere; }
.concept-cell > a { font-weight: 600; text-decoration: none; color: var(--ink); }
.concept-cell > a:hover { color: var(--primary); }
.concept-cell > .badge { margin-inline-start: 8px; }
.concept-cell > .concept-key { display: block; }
table.concepts th:not(:first-child), table.concepts td:not(:first-child) { inline-size: 9rem; }
table.concepts tr.depth-1 > .concept-cell { padding-inline-start: 40px; }
table.concepts tr.depth-2 > .concept-cell { padding-inline-start: 64px; }
table.concepts tr.depth-3 > .concept-cell { padding-inline-start: 88px; }
.concept-facts { display: flex; flex-direction: column; gap: 14px; margin: 0; }
.concept-facts dt { font-size: .875rem; font-weight: 600; color: var(--muted); }
.concept-facts dd { margin: 2px 0 0; overflow-wrap: anywhere; }

/* ---------- On paper: the exam to read (exam/booklet, docs/features/answer-sheets.md) ----------
| Every section and part one after another, as a paper prints them, with the exam screen's own texts and questions
| inside a switched-off fieldset. On a student's screen, on the class's screen, and printed.
*/
.booklet { display: flex; flex-direction: column; gap: 40px; }
.booklet-section { display: flex; flex-direction: column; gap: 16px; scroll-margin-block-start: 16px; }
.booklet-head { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 16px; }
.booklet-head h2 { margin: 0; font-size: 1.5rem; }
.booklet-part { display: flex; flex-direction: column; gap: 20px; scroll-margin-block-start: 16px; }
.booklet-part > .voice { align-self: flex-start; }
.booklet-head { break-after: avoid; }
.booklet-listen { align-items: center; gap: 8px; margin: 0; color: var(--ink-soft); }
.booklet-listen:not(.print-only) { display: flex; }
.booklet-listen .icon { flex: none; color: var(--primary); }

/* A unit as its file: tall enough to read a whole exercise without scrolling inside the box. */
textarea.code.tall { min-height: 60vh; }

/* ---------- Homework ---------- */

/*
| A homework is an exam of another kind, so it is done on the exam screen and
| marked on the exam's pages. What is added here: the two things its bar has
| in place of a clock and sections, the check of an exercise, and the pages
| where it is put together and followed (views/admin/homework, admin/classes).
*/

/* The exam bar, for a homework: the way out where the clock is, the day it is due where the sections are. */
.exam-leave { grid-area: timer; }
.exam-due {
    grid-area: steps; margin: 8px 0 0; padding: 8px 0; min-width: 0;
    display: flex; align-items: center; gap: 8px; font-size: .875rem; color: var(--muted); border-top: 1px solid var(--line-soft);
}
.exam-due .icon { width: 16px; height: 16px; stroke-width: 2; flex: none; }
.exam-due strong { color: var(--ink); }
.exam-due.late, .exam-due.late strong { color: var(--bad); }
.part > .voice { max-width: 520px; }

/* Checking an exercise before the homework is handed in. */
.check-bar {
    display: flex; align-items: center; gap: 12px 16px; flex-wrap: wrap; padding: 14px 20px;
    background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-card);
}
.check-text { flex: 1 1 260px; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.check-result { font-weight: 700; }
.check-result:empty { display: none; }
.check-bar[data-result="right"] .check-result { color: var(--good); }
.check-bar[data-result="wrong"] .check-result { color: var(--bad); }
/* What a check wrote beside an answer: a sign and a word under a listed question, the sign alone beside a gap in a text. */
.check-mark { display: inline-flex; align-items: center; gap: 6px; align-self: flex-start; font-size: .875rem; font-weight: 700; }
.check-mark .icon { width: 16px; height: 16px; stroke-width: 3; }
.check-mark.right { color: var(--good); }
.check-mark.wrong { color: var(--bad); }
.blank > .check-mark { align-self: center; margin-inline: 6px 2px; }
.blank > .check-mark > span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
/* A gap that was checked keeps its line of what it came to, over the light line of a gap answered (:is() takes the
   weight of its heaviest argument, so this outweighs .blank:has(...:not(:placeholder-shown)), which a checked gap
   always matches). A choice in the sentence that was wrong is said so in its own words too. */
.blank.checked-right:is(:has(input[type="text"]:not(:placeholder-shown)), *) { box-shadow: inset 0 -2px 0 var(--good); }
.blank.checked-wrong:is(:has(input[type="text"]:not(:placeholder-shown)), *) { box-shadow: inset 0 -2px 0 var(--bad); }
.blank.checked-right:is(:has(input[type="text"]:not(:placeholder-shown)), *) > b { color: var(--good); }
.blank.checked-wrong:is(:has(input[type="text"]:not(:placeholder-shown)), *) > b { color: var(--bad); }
.blank.pick.checked-wrong label:has(:checked) > span { color: var(--bad); text-decoration: line-through; }
/* The answer, shown to a learner alone where it stands among the options (views/learn): in the colour of right. */
.blank.pick:is(:has(:checked), *) label.is-key:is(:not(:has(:checked)), *) { opacity: 1; color: var(--good); }
.blank.pick label.is-key > span { font-weight: 700; }
.choices label.is-key { box-shadow: inset 0 0 0 2px var(--good); }
.choices label.is-key .opt-key { background: var(--good); color: var(--on-state); }

/* Means something only with a day: gone while "no due day" is chosen. */
form:has(.date-field[data-empty] > [name="due"]) .needs-due { display: none; }
form:has([name="result_detail"][value="scores"]:checked) .needs-answers { display: none; }
/* Leaving the page is recorded only while writing aids are kept out, so the warning goes with them (decision 148). */
form:has([name="writing_aids"][type="checkbox"]:checked) .needs-closed-aids { display: none; }
/* Means something only for a student's account: whose student it is (decision 132), gone while the role is Teacher. */
form:has(select[name="role"] option[value="teacher"]:checked) .needs-student { display: none; }

/* The exercises of a homework being prepared, and where a given one is from. */
.picked { list-style: none; margin: 0; padding: 0; }
.picked li { display: flex; align-items: center; gap: 8px 16px; flex-wrap: wrap; padding: 12px 0; border-top: 1px solid var(--line-soft); }
.picked li:first-child { border-top: 0; padding-block-start: 0; }
.picked li:last-child { padding-block-end: 0; }
.picked-name { flex: 1 1 240px; min-width: 0; display: flex; flex-direction: column; }
/* The homework being prepared (decision 126): each thing to do is a head (its name, where it is from, its arrows) and,
   for the teacher's own questions (decision 127), the list of their parts under it, and the way to the builder. */
.homework-items > li { display: block; }
.item-head { display: flex; align-items: center; gap: 8px 16px; flex-wrap: wrap; }
.item-actions { display: inline-flex; align-items: center; gap: 8px; margin-inline-start: auto; }
.own-item > * + * { margin-block-start: 12px; }
.own-parts { list-style: none; margin: 0; padding: 12px 16px; border: 1px solid var(--line-soft); border-radius: var(--radius-card); background: var(--surface-subtle); }
.homework-items .own-parts li { display: flex; align-items: baseline; justify-content: space-between; gap: 4px 16px; flex-wrap: wrap; padding: 6px 0; border-top: 0; }
.own-parts li > span:first-child { min-width: 0; overflow-wrap: anywhere; }
.own-questions { padding-block-start: 16px; border-top: 1px solid var(--line-soft); }
.own-questions h3 { margin: 0; }
label.check.picked-name { flex-direction: row; align-items: flex-start; }
label.check.picked-name > span { display: flex; flex-direction: column; min-width: 0; }
.homework-library { padding-block-start: 16px; border-top: 1px solid var(--line-soft); }
.homework-library h3 { margin: 0; }
.source-lines { list-style: none; margin: 0 0 20px; padding: 0; color: var(--muted); }
.source-lines li { display: flex; align-items: baseline; gap: 8px; }
.source-lines .icon { width: 16px; height: 16px; stroke-width: 2; flex: none; translate: 0 2px; }

.table-notes + form.card { margin-block-start: 20px; }

/* How many of its students have handed a homework in, at the end of its row. */
.handed-in { display: flex; flex-direction: column; align-items: flex-end; line-height: 1.3; }
.handed-in strong { font-size: 1.125rem; }

/* Ticking an exercise for a homework, in the head of its card. */
.exercise-pick { min-height: 0; font-size: .875rem; font-weight: 600; }

/* A class over time: students down the side, its homework across the top, where each stands in between. */
table.class-work th.wraps { min-width: 120px; max-width: 180px; vertical-align: bottom; white-space: normal; }
table.class-work th.wraps a { display: block; font-weight: 700; color: var(--ink); text-decoration: none; }
table.class-work th.wraps a:hover { color: var(--primary); text-decoration: underline; }
table.class-work th.wraps .small { display: block; }
table.class-work tfoot th, table.class-work tfoot td, table.class-work tfoot td.pinned { border-top: 1px solid var(--line); border-bottom: 0; background: var(--surface-subtle); }
table.class-work tfoot td.pinned::before { content: none; }
.work-cell { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; min-height: 28px; text-decoration: none; color: var(--ink); }
a.work-cell:hover strong { color: var(--primary); text-decoration: underline; }
.late-count { color: var(--warn); }
/* A student's homework, one after another: each row is the way into that work. */
.work-list { list-style: none; margin: 0; padding: 0; }
.work-list li + li { border-top: 1px solid var(--line-soft); }
.work-list a { display: flex; align-items: center; justify-content: space-between; gap: 8px 16px; flex-wrap: wrap; padding: 12px 20px; text-decoration: none; color: var(--ink); }
.work-list a:hover { background: var(--surface-subtle); }
.work-list li > .work-name { padding: 12px 20px; }
.work-name { flex: 1 1 160px; min-width: 0; display: flex; flex-direction: column; gap: 2px; }


/* ---------- Interface language ---------- */

/*
| Bilingual mode: tr() puts the Persian text after the English one in
| <span class="tr-alt">. How it sits depends on the element:
|   - short controls (buttons, tabs, badges, labels, table headers): on the same line, lighter;
|   - headings: on their own line, smaller;
|   - running text (paragraphs, list items, help text): on their own line.
*/
.tr-alt { font-family: var(--font-fa); font-size: .88em; font-weight: 400; opacity: .8; margin-inline-start: .35em; unicode-bidi: isolate; }
h1 > .tr-alt, h2 > .tr-alt, legend > .tr-alt, .auth-title > .tr-alt, .step-num ~ h3 > .tr-alt, .step-num ~ div > h3 > .tr-alt, .or-title > .tr-alt {
    display: block; margin: .2em 0 0; font-size: .6em; font-weight: 500; line-height: 1.6; text-align: end;
}
h2 > .tr-alt, legend > .tr-alt, .step-num ~ h3 > .tr-alt, .step-num ~ div > h3 > .tr-alt, .or-title > .tr-alt { font-size: .75em; }
p > .tr-alt, li > .tr-alt, li > span > .tr-alt, .help > .tr-alt, .help > span > .tr-alt, .lead > .tr-alt, .timeline span > .tr-alt, .auth-steps li > .tr-alt {
    display: block; margin: 0; text-align: end;
}
p > a > .tr-alt, .timer-label > .tr-alt { display: inline; }
/* The main menu says each link in both languages one above the other, as its bar on a phone does: side by side,
   an administrator's six links are wider than the header and it would break into three rows. */
.nav-label > .tr-alt { display: block; margin: 0; font-size: .75rem; line-height: 1.3; }
[data-ui-locale="fa-en"] .nav-label { line-height: 1.25; }
/* Where two languages leave no room for a sentence, its short form (.when-tight) takes its place. */
[data-ui-locale="fa-en"] .pager-place .when-wide { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
[data-ui-locale="fa-en"] .pager-place .when-tight { display: inline; }
.grade-grid > .score > label > .tr-alt { display: block; margin: 0; }
.auth-steps li { flex-wrap: wrap; }

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

@media (max-width: 600px) {
    :root { --gutter: 16px; }
    h1 { font-size: 1.625rem; }
    .wrap { padding-block: 24px 48px; }
    .site-head-inner { padding-block: 8px; }
    .main-nav a { min-height: var(--touch); }
    .list-row { padding: 16px 18px; }
    .practice-row { margin-inline-start: 16px; padding-block: 12px; }
}

/* ---------- Spacing between stacked blocks ---------- */

.card { margin-bottom: 20px; }
.card:last-child, .stack > .card, .columns > .card, .split > .card, .list > .card, .side > .card:last-child { margin-bottom: 0; }
.stack.tight { gap: 10px; }
.band .page-head { margin-bottom: 0; }
.kicker > .tr-alt { display: inline; }

/* ---------- Select ---------- */

/*
| public/js/ui.js puts a button and a list in place of every <select>; the native
| element stays inside .select, invisible, and keeps the value. Without JavaScript
| the native select below is what people see, so it is styled too.
*/
select {
    appearance: none; -webkit-appearance: none;
    padding-inline-end: 40px;
    background-image: var(--select-arrow);
    background-repeat: no-repeat; background-size: 18px; background-position: right 12px center;
}
[dir="rtl"] select { background-position: left 12px center; }

.select { position: relative; display: inline-flex; width: 100%; min-width: 0; vertical-align: middle; }
.select > select { position: absolute; inset: 0; width: 100%; height: 100%; min-height: 0; opacity: 0; pointer-events: none; }
.select-button {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    width: 100%; min-height: var(--control-height); padding: 10px 14px;
    font: inherit; color: inherit; text-align: start; cursor: pointer;
    background: var(--surface); border: 1.5px solid var(--control); border-radius: var(--radius-control);
}
.select-button:hover { border-color: var(--ink-faint); }
.select-button[aria-expanded="true"] { border-color: var(--primary); }
.select-button:disabled { opacity: .5; cursor: not-allowed; }
.select-value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.select-value.placeholder { color: var(--muted); }
.select-arrow { width: 18px; height: 18px; color: var(--muted); transition: rotate var(--motion-base) var(--ease-out); }
.select-button[aria-expanded="true"] .select-arrow { rotate: 180deg; color: var(--primary); }

.select-list {
    position: fixed; inset: auto; margin: 0; padding: 6px;
    max-height: min(320px, 50vh); overflow-y: auto; overscroll-behavior: contain;
    font: 1rem/1.4 var(--font-ui); color: var(--ink); white-space: nowrap;
    background: var(--surface); border: 1px solid var(--line); border-radius: 12px; box-shadow: var(--shadow-pop);
    opacity: 0; translate: 0 -6px;
    transition: opacity var(--motion-fast) var(--ease), translate var(--motion-base) var(--ease-out),
        display var(--motion-base) allow-discrete, overlay var(--motion-base) allow-discrete;
}
html[lang="fa"] .select-list { font-family: var(--font-fa); }
.select-list:popover-open { opacity: 1; translate: 0 0; }
@starting-style {
    .select-list:popover-open { opacity: 0; translate: 0 -6px; }
}
.select-option { display: flex; align-items: center; justify-content: space-between; gap: 16px; min-height: var(--touch); padding: 8px 12px; border-radius: 8px; cursor: pointer; }
.select-option.active { background: var(--primary-soft); }
.select-option[aria-selected="true"] { font-weight: 700; color: var(--primary); }
.select-check { width: 16px; height: 16px; stroke-width: 3; visibility: hidden; }
.select-option[aria-selected="true"] .select-check { visibility: visible; }

/*
| A select with many options is searched in (data-search on the select, ui.js): the list opens on a search box that
| stays at its top while the options scroll, and the options of a book stand under its name.
*/
.select-search { display: block; padding: 8px; }
.select-search-input { display: block; width: 100%; min-height: 40px; padding: 8px 12px; font: inherit; }
.select-list.searchable { scroll-padding-block-start: 60px; }
.select-list.searchable > .select-search {
    position: sticky; inset-block-start: -6px; z-index: 1;
    margin: -6px -6px 4px; background: var(--surface); border-bottom: 1px solid var(--line-soft); border-radius: 12px 12px 0 0;
}
.select-group { display: block; padding: 10px 12px 4px; font-size: .8125rem; font-weight: 700; color: var(--muted); }
.select-empty { display: block; padding: 12px; color: var(--muted); white-space: normal; }

/*
| A day to choose (<x-date-field>, ui.js enhanceDateField, decision 140). The field is the select's button with a
| calendar's sign; the native date input stays inside it, invisible, and holds the day. It opens a calendar in the top
| layer: the two calendars as tabs (the pill slides between them), the month with the buttons beside it, and a board of
| six weeks — or a year's months, or twelve years, in the same room: the box keeps its size whatever it shows, so
| nothing in it moves but what changed. Today is ringed, the chosen day filled, a day outside the month faint, a day
| that cannot be chosen fainter and struck through.
*/
.date-field { position: relative; display: flex; width: 100%; min-width: 0; }
.date-field > .date-native { position: absolute; inset: 0; width: 100%; height: 100%; min-height: 0; opacity: 0; pointer-events: none; }
.date-field.native > .date-button { display: none; }
.date-field.native > .date-native { position: static; opacity: 1; pointer-events: auto; }
.date-button {
    display: flex; align-items: center; gap: 10px;
    width: 100%; min-height: var(--control-height); padding: 10px 14px;
    font: inherit; color: inherit; text-align: start; cursor: pointer;
    background: var(--surface); border: 1.5px solid var(--control); border-radius: var(--radius-control);
}
.date-button:hover { border-color: var(--ink-faint); }
.date-button[aria-expanded="true"] { border-color: var(--primary); }
.date-button > .icon { flex: none; width: 18px; height: 18px; color: var(--muted); }
.date-button[aria-expanded="true"] > .icon { color: var(--primary); }
.date-value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.date-value.placeholder { color: var(--muted); }

.date-pop {
    position: fixed; inset: auto; margin: 0; padding: 12px;
    width: 336px; max-width: calc(100vw - 16px); max-height: calc(100dvh - 16px); overflow-y: auto; overscroll-behavior: contain;
    font: 1rem/1.4 var(--font-ui); color: var(--ink);
    background: var(--surface); border: 1px solid var(--line); border-radius: 14px; box-shadow: var(--shadow-pop);
    opacity: 0; translate: 0 -6px;
    transition: opacity var(--motion-fast) var(--ease), translate var(--motion-base) var(--ease-out),
        display var(--motion-base) allow-discrete, overlay var(--motion-base) allow-discrete;
}
.date-pop:popover-open { display: flex; flex-direction: column; gap: 10px; opacity: 1; translate: 0 0; }
@starting-style {
    .date-pop:popover-open { opacity: 0; translate: 0 -6px; }
}
.date-calendars { display: grid; width: 100%; }
.date-calendars:has([data-calendar="gregorian"][aria-pressed="true"])::before { translate: 100% 0; }
[dir="rtl"] .date-calendars:has([data-calendar="gregorian"][aria-pressed="true"])::before { translate: -100% 0; }

.date-head { display: flex; align-items: center; gap: 4px; }
.date-step {
    flex: none; display: inline-flex; align-items: center; justify-content: center;
    width: var(--touch); height: var(--touch); padding: 0; border: 0; border-radius: var(--radius-control);
    background: transparent; color: var(--ink-soft); cursor: pointer;
}
.date-step .icon { width: 20px; height: 20px; stroke-width: 2.4; }
.date-title {
    flex: 1; min-width: 0; min-height: var(--touch); padding: 4px 8px; border: 0; border-radius: var(--radius-control);
    background: transparent; font: inherit; font-weight: 700; color: var(--ink); cursor: pointer;
}
.date-title:disabled { cursor: default; }
:is(.date-step, .date-title):not(:disabled):hover { background: var(--neutral-soft); }
.date-step:disabled { color: var(--control-soft); cursor: not-allowed; }
:is(.date-step, .date-title, .date-day, .date-choice):focus-visible { outline: 0; box-shadow: var(--halo); }

/* The board: a head of weekdays and six weeks, or twelve months or years filling the same height. */
.date-board { display: grid; }
.date-board > * { grid-area: 1 / 1; }
.date-board[data-view="days"] > .date-cells, .date-board:not([data-view="days"]) > .date-grid { visibility: hidden; }
.date-grid { width: 100%; border-collapse: collapse; table-layout: fixed; }
.date-grid th { padding: 0 0 6px; font-size: .75rem; font-weight: 700; color: var(--muted); text-align: center; }
.date-grid td { padding: 1px; text-align: center; }
.date-day, .date-choice {
    display: inline-flex; align-items: center; justify-content: center;
    width: 100%; padding: 0; border: 0; border-radius: var(--radius-control);
    background: transparent; font: inherit; color: var(--ink); cursor: pointer;
}
.date-day { aspect-ratio: 1; max-height: var(--touch); font-size: .9375rem; }
.date-day.outside { color: var(--muted); }
:is(.date-day, .date-choice):not([aria-disabled]):hover { background: var(--primary-soft); }
:is(.date-day, .date-choice)[aria-current] { box-shadow: inset 0 0 0 1.5px var(--primary); font-weight: 700; }
[aria-selected="true"] > .date-day, .date-choice[aria-pressed="true"] { background: var(--primary); color: var(--on-primary); font-weight: 700; }
:is(.date-day, .date-choice)[aria-disabled] { color: var(--control-soft); text-decoration: line-through; cursor: not-allowed; }
.date-cells { display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(4, 1fr); gap: 4px; }
.date-choice { min-height: var(--touch); font-size: .9375rem; }
.date-foot { display: flex; justify-content: space-between; gap: 8px; padding-block-start: 4px; border-top: 1px solid var(--line-soft); }

/*
| A choice of several among many (<x-picker>, ui.js enhancePicker): the chosen ones as chips in a box with the button
| that opens the list, which is the select's list with a search box and the tools of choosing many. What a row says is
| .picker-row, once, for the list and for the page without a script (the boxes in a grid). An option's box is ticked
| when it is chosen; the ground under it is where the pointer or the arrows are: never one mark for both, and never
| bold for chosen (a chosen word must not be wider). Options of a group stand under its name, which stays at the top
| of the list while its options scroll.
*/
.picker + .picker { margin-block-start: 4px; }
.picker-options { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 4px 20px; }
.picker-group-name { grid-column: 1 / -1; margin: 12px 0 0; font-size: .8125rem; font-weight: 700; color: var(--muted); }
.picker-group-name:first-child { margin-block-start: 0; }
.picker-row { display: flex; align-items: center; gap: 12px; flex: 1; min-width: 0; }
.picker-row .avatar { width: 32px; height: 32px; font-size: .8125rem; }
.picker-text { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.picker-label { font-weight: 600; }
.picker-label, .picker-detail { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.picker-detail { font-size: .8125rem; }
.picker-row .badge { flex: none; }

.picker-field {
    display: flex; flex-direction: column; align-items: flex-start; gap: 10px;
    padding: 12px; background: var(--surface); border: 1.5px solid var(--control); border-radius: var(--radius-control);
}
.picker-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 0; padding: 0; list-style: none; }
.picker-chip {
    display: inline-flex; align-items: center; gap: 4px; max-width: 100%;
    padding: 2px 2px 2px 12px; font-weight: 600; font-size: .9375rem;
    color: var(--primary); background: var(--primary-soft); border-radius: 999px;
}
.picker-chip > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.picker-chip-remove {
    position: relative; display: inline-flex; align-items: center; justify-content: center; flex: none;
    width: 28px; height: 28px; padding: 0; color: inherit; background: transparent; border: 0; border-radius: 50%; cursor: pointer;
}
.picker-chip-remove::after { content: ""; position: absolute; inset: -8px; }
.picker-chip-remove:hover { background: var(--surface); }
.picker-chip-remove:focus-visible { outline: 0; box-shadow: var(--halo); }
.picker-chip-remove .icon { width: 16px; height: 16px; }
.picker-none { color: var(--muted); }

.picker-list { width: min(440px, calc(100vw - 16px)); max-height: min(460px, 70vh); padding: 0; white-space: normal; flex-direction: column; overflow: hidden; }
.picker-list:popover-open { display: flex; }
.picker-list > .select-search { flex: none; border-bottom: 1px solid var(--line-soft); }
.picker-tools { display: flex; align-items: center; gap: 4px; flex: none; padding: 2px 8px 2px 14px; border-bottom: 1px solid var(--line-soft); }
.picker-count { flex: 1; font-size: .875rem; color: var(--muted); }
.picker-options-list { display: block; flex: 1; min-height: 0; padding: 6px; overflow-y: auto; overscroll-behavior: contain; }
.picker-option { justify-content: flex-start; gap: 12px; }
.picker-option[aria-selected="true"] { font-weight: inherit; color: inherit; }
.picker-tick {
    display: inline-grid; place-items: center; flex: none; width: 22px; height: 22px;
    color: var(--on-primary); background: var(--surface); border: 1.5px solid var(--control); border-radius: 6px;
    transition: background-color var(--motion-fast) var(--ease), border-color var(--motion-fast) var(--ease);
}
.picker-tick .icon { width: 14px; height: 14px; stroke-width: 3; opacity: 0; transition: opacity var(--motion-fast) var(--ease); }
.picker-option[aria-selected="true"] .picker-tick { background: var(--primary); border-color: var(--primary); }
.picker-option[aria-selected="true"] .picker-tick .icon { opacity: 1; }
.picker-group { display: block; }
.picker-group + .picker-group { margin-block-start: 4px; }
.picker-group-head {
    position: sticky; inset-block-start: -6px; z-index: 1;
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
    margin: -6px -6px 2px; padding: 8px 8px 4px 18px; background: var(--surface);
}
.picker-group-head .btn { flex: none; }
.picker-list > .select-empty { flex: none; }
.recipients-summary .icon { vertical-align: -.2em; }

/* The places where a select is joined to a number or sits inside a sentence. */
.q-row .select { width: auto; min-width: 150px; }
.blank .select { width: auto; min-width: 7.5em; }
.blank .select-button { min-height: 0; padding: 0 .25rem; gap: 6px; font-weight: 600; color: var(--primary-strong); background: transparent; border: 0; border-radius: 0; }
.blank .select-value.placeholder { font-weight: 400; }
.key .select-button {
    min-height: var(--touch); padding-block: 4px;
    border-inline-start: 0; border-start-start-radius: 0; border-end-start-radius: 0;
}
.key .select { flex: 1; }

/* ---------- Motion ---------- */

/*
| Movement is feedback, never decoration: controls answer a press, new content
| arrives softly, and one page hands over to the next without a flash.
| Inside an exam it stays quieter still: the clock never moves or blinks.
*/
@view-transition { navigation: auto; }
::view-transition-old(root), ::view-transition-new(root) { animation-duration: var(--motion-page); animation-timing-function: var(--ease); }

/*
| Moving between pages (public/js/ui.js replaces the content in place): the old
| page cross-fades into the new one, so whatever looks the same in both — the
| header, an exam's title band, a side menu — simply stays still. The line under
| the current link is carried across by name, so it slides to its new place.
*/
.main-nav .nav-line { view-transition-name: nav-line; }
/* The chosen one of a row of views slides to its new place, like the line under a tab — but only between
   views of one page (ui.js says so with html.staying). Two pages that each have such a row are not one row. */
.staying .segmented a.current { view-transition-name: segment; }
::view-transition-group(segment) { animation-duration: 340ms; animation-timing-function: var(--ease-out); }
.tabs .nav-line { view-transition-name: tab-line; }
.lesson-tabs .nav-line { view-transition-name: lesson-line; }
::view-transition-group(nav-line), ::view-transition-group(tab-line), ::view-transition-group(lesson-line) { animation-duration: 340ms; animation-timing-function: var(--ease-out); }

/*
| The page loader: a bar across the top that shows how far the next page is. ui.js writes
| the stage into data-state and the share that is done into --progress (0 to 1); how the
| bar gets there is decided here. While the server is thinking nothing can be measured, so
| the bar creeps towards a point it never reaches; an upload and a download are measured
| and shown as they are. A request that is answered at once never shows the bar.
*/
.page-loader {
    --progress: 0;
    position: fixed; inset-block-start: 0; inset-inline: 0; z-index: 100; height: 4px;
    background: color-mix(in srgb, var(--primary) 16%, var(--surface));
    opacity: 0; pointer-events: none;
    transition: opacity var(--motion-base) var(--ease);
}
.page-loader::after {
    content: ""; position: absolute; inset: 0; background: var(--primary);
    border-start-end-radius: 2px; border-end-end-radius: 2px;
    transform: scaleX(var(--progress)); transform-origin: 0 50%;
    transition: transform var(--motion-base) linear;
}
[dir="rtl"] .page-loader::after { transform-origin: 100% 50%; }
.page-loader[data-state] { opacity: 1; transition-delay: 120ms; }
.page-loader[data-state="start"]::after { transition: none; }
.page-loader[data-state="waiting"]::after { transition: transform 14s cubic-bezier(.08, .7, .2, 1); }
.page-loader[data-state="done"] { opacity: 0; transition-delay: var(--motion-base); }
.page-loader[data-state="done"]::after { transition: transform var(--motion-base) var(--ease-out); }
html.is-loading, html.is-loading a, html.is-loading button { cursor: progress; }

/* ---------- Developer mode ---------- */

/* A line above the page, for a teacher who switched developer mode on: what this page cost. ui.js fills it. */
.dev-bar {
    display: flex; align-items: baseline; flex-wrap: wrap; gap: 2px 18px; padding: 6px var(--gutter);
    font-size: .8125rem; line-height: 1.5; background: var(--emphasis); color: var(--on-emphasis);
}
.dev-bar a { color: inherit; margin-inline-start: auto; }
/* The list of pages: the slow ones stand out, and in each the number that made it slow. */
.dev-log td { white-space: nowrap; }
.dev-log tr.slow td { background: var(--warn-soft); }
.dev-log .culprit { color: var(--bad); font-weight: 700; }

/* A field that belongs to switches is there only while one of them is on (Settings → Speed: how often to ask). */
form:not(:has([data-asks]:checked)) .asks-every { display: none; }
/* The key, address and model of a speech service are asked for only while a service is the way that is chosen. */
form:not(:has([data-service]:checked)) .needs-service { display: none; }
/* What a result adds to its key means something only while the key is what it shows. */
form:not(:has([name="result_detail"][value="key"]:checked)) .needs-key { display: none; }
/* An exam's paper code means something only while it is answered either on the screen or on paper. */
form:not(:has([name="answering"][value="both"]:checked)) .needs-paper-code { display: none; }

/* ---------- Site log ---------- */

/* What the host gives the site: a label, its value, a verdict; advice on its own line under them. */
.host-check { list-style: none; margin: 0; padding: 0; }
.host-check li { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 12px; padding-block: 12px; border-block-start: 1px solid var(--line-soft); }
.host-check li:first-child { border-block-start: 0; }
.host-check-label { flex: 1 1 260px; color: var(--ink-soft); }
.host-check .help { flex: 1 1 100%; margin: 0; }
/* A message from inside the application can be one long word: a path, a class name. */
.log-value { overflow-wrap: anywhere; }

/* The button that was pressed says so itself: a turning ring, and for an upload how much of it has gone.
   The ring takes the place of the button's own icon, so nothing is added and the words do not move; only
   a button without an icon gets one after its words. */
.btn.is-busy { pointer-events: none; opacity: .75; }
.btn.is-busy > .icon, .btn.is-busy:not(:has(> .icon))::after {
    box-sizing: border-box; border-radius: 50%;
    border: 2px solid currentColor; border-inline-end-color: transparent;
    animation: spin 900ms linear infinite;
}
.btn.is-busy > .icon > * { visibility: hidden; }
.btn.is-busy:not(:has(> .icon))::after { content: ""; flex: none; width: 1em; height: 1em; }
.btn.is-busy[data-progress]::before { content: attr(data-progress) "%"; font-variant-numeric: tabular-nums; direction: ltr; }

html { scroll-behavior: smooth; }

.btn, .select-button, input, select, textarea, .list-row, .main-nav a, .tabs a,
.segmented a, .jump a, .guide-nav a, .dropzone, .timer,
.steps .mark, .essay-editor, .select-option, .row-link, .back-link, a {
    transition: color var(--motion-fast) var(--ease), background-color var(--motion-fast) var(--ease),
        border-color var(--motion-fast) var(--ease), box-shadow var(--motion-base) var(--ease),
        transform var(--motion-fast) var(--ease), opacity var(--motion-fast) var(--ease);
}

/* Pressing and hovering */
.btn:active, .select-button:active, .part-tabs button:active { transform: scale(.97); }
.btn:not(.text, .quiet):hover { box-shadow: var(--shadow-lift); }
a.list-row:hover { transform: translateY(-2px); box-shadow: var(--shadow-lift); }
.dropzone:hover { border-color: var(--primary); background: var(--primary-soft); }
.list-row > .icon, .list-go .icon, .row-link .icon, .back-link .icon { transition: translate var(--motion-base) var(--ease-out); }
a.list-row:hover > .icon, a.list-row:hover .list-go .icon, .row-link:hover .icon { translate: 3px 0; }
.back-link:hover .icon { translate: -3px 0; }
[dir="rtl"] a.list-row:hover > .icon, [dir="rtl"] a.list-row:hover .list-go .icon, [dir="rtl"] .row-link:hover .icon { translate: -3px 0; }
[dir="rtl"] .back-link:hover .icon { translate: 3px 0; }

/* Content arriving: each block of a page rises into place, one after another. */
@keyframes rise { from { opacity: 0; transform: translateY(10px); } }
@keyframes fade-in { from { opacity: 0; } }
@keyframes fade-out { to { opacity: 0; } }
@keyframes drop-in { from { opacity: 0; transform: translateY(-100%); } }
@keyframes pop { 0% { transform: scale(.82); } 60% { transform: scale(1.08); } 100% { transform: scale(1); } }
@keyframes dialog-in { from { opacity: 0; transform: translateY(12px) scale(.97); } }
@keyframes spin { to { rotate: 360deg; } }
@keyframes breathe { 50% { opacity: .45; } }
/* The exam screen (decision 54). */
@keyframes turn { to { transform: rotate(360deg); } }
@keyframes roll-out { to { transform: translateY(100%); opacity: 0; } }
@keyframes roll-in { from { transform: translateY(-100%); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
@keyframes item-out { to { opacity: 0; transform: translate3d(-8px, 0, 0); } }
@keyframes item-out-back { to { opacity: 0; transform: translate3d(8px, 0, 0); } }
@keyframes part-in { from { opacity: 0; transform: translate3d(14px, 0, 0); } }
@keyframes part-in-back { from { opacity: 0; transform: translate3d(-14px, 0, 0); } }
@keyframes part-rise { from { opacity: 0; transform: translate3d(0, 8px, 0); } }
@keyframes chip-in { from { opacity: 0; transform: scale(.7); } }
@keyframes rule-in { from { transform: scaleX(0); } }
@keyframes rule-move { from { transform: translateY(var(--rule-shift, 0)); } }
@keyframes eq { 0%, 100% { transform: scaleY(.3); } 50% { transform: scaleY(1); } }
@keyframes sweep { from { clip-path: inset(0 100% 0 0 round 5px); } to { clip-path: inset(0 0 0 0 round 5px); } }
@keyframes land { from { opacity: 0; transform: translateY(-.5em) scale(1.12); } }
/* A small shake, side to side: "here first". */
@keyframes nudge { 30% { translate: -4px 0; } 60% { translate: 4px 0; } }
@keyframes letter { from { opacity: 0; transform: translateY(.45em) scale(.9); } }
@keyframes letter-out { to { opacity: 0; transform: translateY(.5em) scale(.85); } }
@keyframes arrive { 28% { background-color: var(--accent-soft); } }
@keyframes arrive-num { 40% { transform: scale(1.14); } }
@keyframes open-tile { from { width: 0; margin-inline-start: -6px; opacity: 0; transform: scale(.6); } }
@keyframes soft-out { to { opacity: 0; transform: translateY(5px); } }
@keyframes disc-out { 45% { opacity: 0; } to { opacity: 0; transform: translateX(-14px) scale(.92); } }
@keyframes disc-out-back { 45% { opacity: 0; } to { opacity: 0; transform: translateX(14px) scale(.92); } }
@keyframes drift-in { from { transform: translateY(-6px); } }
@keyframes num-out { to { opacity: 0; transform: translateY(60%); } }
@keyframes num-in { from { transform: translateY(-60%); } }
@keyframes ring { 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--primary) 35%, transparent); } 100% { box-shadow: 0 0 0 10px transparent; } }

.wrap > :not(.kept, :has(.kept)), .band:not(.kept) .band-inner > *, .auth-pitch > *, .auth-box > *,
.columns > .main > *, .columns > .side:not(.kept) > *, .list:not(.kept) > *, .stats:not(.kept) > * {
    animation: rise var(--motion-enter) var(--ease-out) backwards;
}
:is(.wrap, .auth-pitch, .auth-box, .columns > .main, .columns > .side, .list, .stats) > :nth-child(2) { animation-delay: 50ms; }
:is(.wrap, .auth-pitch, .auth-box, .columns > .main, .columns > .side, .list, .stats) > :nth-child(3) { animation-delay: 100ms; }
:is(.wrap, .auth-pitch, .auth-box, .columns > .main, .columns > .side, .list, .stats) > :nth-child(4) { animation-delay: 150ms; }
:is(.wrap, .auth-pitch, .auth-box, .columns > .main, .columns > .side, .list, .stats) > :nth-child(5) { animation-delay: 200ms; }
:is(.wrap, .auth-pitch, .auth-box, .columns > .main, .columns > .side, .list, .stats) > :nth-child(n + 6) { animation-delay: 250ms; }

/* A message is always news, also on a page that shows itself again: it fades in. A success has said what it
   had to after a few seconds and fades out; it keeps its place at the top of the page, so nothing moves. */
@keyframes flash-leave { to { opacity: 0; visibility: hidden; } }
.wrap > .flash, .same-page .wrap > .flash { animation: fade-in var(--motion-base) var(--ease); }
.wrap > .flash:not(.bad), .same-page .wrap > .flash:not(.bad) { animation: fade-in var(--motion-base) var(--ease), flash-leave var(--motion-enter) var(--ease) 6s forwards; }

/*
| The same page shown again — another view of a list, the next page of a table, a form that
| was saved — is not a new page: nothing on it enters. ui.js marks it (body.same-page) and
| only what is different is seen to change, through the cross-fade.
*/
.same-page :is(.wrap > *, .band .band-inner > *, .columns > .main > *, .columns > .side > *, .list > *, .stats > *) { animation: none; }

/* The exam: how a part comes and goes is with the exam screen's own rules; here, what is left of the site's. */
.mark-bar:not([hidden]), .mark-pop:not([hidden]) { animation: fade-in var(--motion-fast) var(--ease); }
.save-banner:not([hidden]) { animation: drop-in var(--motion-base) var(--ease-out); }
/* A listed question gone to from the footer: its number swells a little and settles, once. The ground under it has glided there. */
.question.arrived > .qnum { animation: arrive-num 720ms var(--ease); }
.speaking[data-state="recording"] .recorder-dot { animation: breathe 1.4s ease-in-out infinite; }
progress::-webkit-progress-value { transition: inline-size var(--motion-base) var(--ease); }
.word-count .icon { animation: pop 320ms var(--ease-out); }

dialog[open] { animation: dialog-in var(--motion-base) var(--ease-out); }
dialog[open]::backdrop { animation: fade-in var(--motion-base) var(--ease); }

.timeline .now .mark { animation: ring 2.2s var(--ease) infinite; }

@media (prefers-reduced-motion: reduce) {
    @view-transition { navigation: none; }
    html { scroll-behavior: auto; }
}

/* ---------- Bilingual mode in tight places ---------- */

/* A button keeps each language on one line; the second drops below when there is no room. */
.btn { flex-wrap: wrap; row-gap: 0; text-align: center; }
.btn > .tr-alt { white-space: nowrap; margin-inline-start: 0; }
.check > span > .tr-alt, .danger-zone p > .tr-alt { display: block; margin: 0; text-align: end; }
.dialog-missing { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.dialog-missing strong { font-size: 1.125rem; }
.dialog-actions .btn { flex: 1 1 180px; }
[data-ui-locale="fa-en"] dialog { max-width: 520px; }

/* The header: what belongs to the account sits at the far end. */
.head-tools { margin-inline-start: auto; }

/* ---------- Phones ---------- */

/*
| Most people use this on a phone. Below 700px the interface changes shape, not
| just size: the main links become a bar at the bottom of the screen, tab rows
| scroll sideways instead of wrapping, wide tables become lists of cards, and
| the exam screen keeps only what a student needs in its two bars.
*/
@media (max-width: 700px) {
    :root { --gutter: 16px; }
    h1 { font-size: 1.5rem; }
    h2 { font-size: 1.125rem; }
    .wrap { padding-block: 20px 40px; }
    .card { padding: 18px; }
    .card-head { padding: 14px 18px; }
    .card-body { padding: 14px 18px 18px; }
    input, select, textarea, .select-button { font-size: 1rem; }

    /* Top bar and bottom navigation */
    .site-head-inner { flex-wrap: nowrap; min-height: 56px; gap: 8px; padding-block: 0; }
    .brand { min-width: 0; font-size: 1rem; }
    .brand .tag { display: none; }
    .head-tools .icon { display: block; width: 22px; height: 22px; stroke-width: 2; }
    .head-tools .btn > span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
    .head-tools .btn { min-width: var(--touch); padding: 0; }
    .head-tools .head-profile { display: inline-flex; }
    .head-profile[aria-current="page"] { color: var(--primary); }
    .main-nav .up-on-phone { display: none; }

    .main-nav {
        position: fixed; inset-inline: 0; inset-block-end: 0; z-index: 30;
        display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 0;
        padding-bottom: env(safe-area-inset-bottom);
        background: var(--surface); border-top: 1px solid var(--line); box-shadow: var(--shadow-bar);
    }
    .main-nav a {
        flex-direction: column; justify-content: center; gap: 3px;
        min-height: 60px; padding: 6px 2px; border-bottom: 0;
        font-size: .75rem; line-height: 1.25; text-align: center;
    }
    .main-nav .icon { display: block; width: 22px; height: 22px; stroke-width: 2; }
    .nav-label > .tr-alt { display: block; margin: 0; font-size: .6875rem; }
    .main-nav .nav-line { inset-block: 0 auto; inset-inline: 24%; border-radius: 0 0 3px 3px; }
    body:has(.main-nav) { padding-bottom: calc(60px + env(safe-area-inset-bottom)); }
    .save-bar { inset-block-end: calc(68px + env(safe-area-inset-bottom)); padding: 8px; }
    .save-bar .help { display: none; }
    .save-bar .btn { width: 100%; }

    /* Title band: actions in a tidy grid, tabs in one scrolling row */
    .band-inner { padding-top: 6px; gap: 8px; }
    .band .page-head { gap: 8px; }
    .band .stack.tight { gap: 6px; }
    /* The exam's actions share one row that scrolls sideways, so the band stays short. */
    .band .page-head > .row {
        flex-wrap: nowrap; gap: 8px; width: calc(100% + 2 * var(--gutter));
        margin-inline: calc(-1 * var(--gutter)); padding-inline: var(--gutter);
        overflow-x: auto; scrollbar-width: none;
    }
    .band .page-head > .row::-webkit-scrollbar { display: none; }
    .band .page-head > .row > form { order: -1; flex: none; }
    .band .page-head > .row .btn { flex: none; flex-wrap: nowrap; min-height: var(--touch); padding: 6px 14px; font-size: .9375rem; white-space: nowrap; }
    .band .page-head > .row > a.btn.text, .band .page-head > .row > a.btn.secondary { background: var(--surface); border-color: var(--control-soft); }
    /* The one thing the page offers first (an exam's builder) is first in the row too, and looks as it does on a wide screen. */
    .band .page-head > .row > a.btn:not(.text, .secondary) { order: -2; }
    .page-meta { gap: 6px 10px; }
    .band .page-meta { flex-wrap: nowrap; }
    .band .page-meta > .code-label { display: none; }
    /* Only an exam's code must stay on one line; a class and a day may take two. */
    .band .page-meta:not(:has(.code-chip)) { flex-wrap: wrap; }
    .code-chip { font-size: 1rem; }

    .tabs, .jump, .guide-nav, .part-tabs {
        flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; overscroll-behavior-x: contain;
    }
    .tabs::-webkit-scrollbar, .jump::-webkit-scrollbar, .guide-nav::-webkit-scrollbar, .part-tabs::-webkit-scrollbar { display: none; }
    .tabs { margin-inline: calc(-1 * var(--gutter)); padding-inline: var(--gutter); }
    .tabs a, .part-tabs button { flex: none; white-space: nowrap; }

    .page-head { margin-bottom: 16px; }
    .page-head > .btn { width: 100%; }
    .back-link { min-height: var(--touch); }
    .wrap > .back-link { margin-bottom: 4px; }

    .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; margin-bottom: 16px; }
    .stat { padding: 12px 14px; font-size: .875rem; }
    .stat strong { font-size: 1.5rem; }

    .list-row { padding: 14px 16px; gap: 8px 16px; }
    .list-title { font-size: 1rem; }
    .list-cell { flex: 1 1 110px; }
    .list-row > .icon { margin-inline-start: auto; }
    .grade-grid > .score { flex: 1 1 100%; }
    .marking-bar { margin-bottom: 8px; }
    .pager { justify-content: space-between; margin-inline: -12px; width: calc(100% + 24px); }
    .todos { margin-bottom: 16px; }
    /* The choice of a bulk action and its button share the line: the choice takes what the button leaves. */
    .bulk-actions:has(> :is(.select, select)) { inline-size: 100%; margin-inline-start: 0; flex-wrap: nowrap; }
    .bulk-actions > :is(.select, select) { flex: 1 1 auto; width: auto; }
    .todo { padding: 14px 16px; }
    .todo .btn { flex: 1 1 100%; }
    .columns > .save-bar .save-actions { flex: 1 1 100%; }
    .columns > .save-bar .save-actions .btn { flex: 1 1 0; width: auto; padding-inline: 12px; }

    /* Marked answers: the number, the answer with its key under it, and the verdict, without scrolling sideways. */
    table.data.marks { display: block; }
    table.data.marks thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
    table.data.marks tbody { display: block; }
    table.data.marks tr { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 2px 12px; padding: 10px 8px; border-bottom: 1px solid var(--line-soft); }
    table.data.marks tr:last-child { border-bottom: 0; }
    table.data.marks td, table.data.marks td:first-child, table.data.marks td:last-child { display: block; padding: 0; border: 0; }
    table.data.marks td:nth-child(1) { grid-area: 1 / 1 / span 2 / auto; align-self: start; }
    table.data.marks td:nth-child(2) { grid-area: 1 / 2; overflow-wrap: anywhere; }
    table.data.marks td:nth-child(3) { grid-area: 2 / 2; font-size: .875rem; overflow-wrap: anywhere; }
    table.data.marks td:nth-child(3)::before { content: attr(data-label) ": "; }
    table.data.marks td:nth-child(3):empty { display: none; }
    table.data.marks td:nth-child(4) { grid-area: 1 / 3 / span 2 / auto; }
    table.data.marks td:nth-child(5) { grid-area: 3 / 2 / auto / -1; text-align: start; }
    .btn.accept { margin-block: 0; margin-inline-start: -12px; }
    .answer-list .btn.accept { margin: 0; }
    .stats.lines { grid-template-columns: minmax(0, 1fr); }
    .stats.lines .stat > div { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 12px; }
    .breakdown-row { flex-wrap: wrap; }
    .breakdown-rate { flex: 1 1 100%; justify-content: flex-start; padding-inline-start: 42px; }
    .breakdown-rate progress { order: 3; }
    .inline-field.light { flex-wrap: wrap; }
    .danger-zone { padding: 16px; }
    .danger-zone .btn { width: 100%; }
    .toolbar { gap: 12px; }
    .toolbar .segmented { width: 100%; }
    .toolbar .segmented a { flex: 1; justify-content: center; }
    /* More choices than fit a phone: one row that scrolls sideways, never a second line. */
    .toolbar .segmented.scroll { overflow-x: auto; scrollbar-width: none; }
    .toolbar .segmented.scroll a { flex: none; white-space: nowrap; }
    .search .btn { padding-inline: 14px; }
    .code-entry .btn { flex: 1 1 100%; }
    .copy-field .btn { flex: 1 1 100%; }
    .row > .btn.ghost, .row > .btn:not(.text):not(.quiet):not(.small) { flex: 1 1 auto; }

    /* Jump lists and guide pages: a row of chips that scrolls sideways */
    .columns { gap: 16px; }
    .columns > .side.jump, .columns > .side.guide-nav {
        position: sticky; inset-block-start: 0; z-index: 5; flex: 1 1 100%;
        flex-direction: row; gap: 8px; margin-inline: calc(-1 * var(--gutter)); padding: 8px var(--gutter);
        background: var(--ground);
    }
    .jump > .kicker { display: none; }
    .jump a, .guide-nav a {
        flex: none; min-height: 40px; padding: 4px 14px; white-space: nowrap;
        background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-pill);
    }
    .jump a.sub { padding-inline-start: 14px; }
    .guide-nav a[aria-current="page"] { border-color: var(--primary); }

    /* Tables as cards */
    table.data.cards { display: block; min-width: 0; }
    table.data.cards thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
    table.data.cards tbody { display: block; }
    table.data.cards tr { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 10px 20px; padding: 14px 16px; border-bottom: 1px solid var(--line-soft); }
    table.data.cards tr:last-child { border-bottom: 0; }
    table.data.cards td, table.data.cards td:first-child, table.data.cards td:last-child { display: block; padding: 0; border: 0; text-align: start; }
    table.data.cards td:first-child, table.data.cards td:nth-child(2) { flex: 1 1 100%; }
    table.data.cards td:last-child { flex: 1 1 100%; }
    table.data.cards td.pinned { position: static; }
    /* A level's card: what uses it takes the line, and the arrows that order it stand at its end. */
    table.data.cards.levels td:nth-child(3) { flex: 1 1 0; }
    /* A concept is one short row: its name over its key, then its level and how often it is used, side by side and
       without their headings; one that stands under another stands in, as on a wide screen. */
    table.data.cards.concepts tr { gap: 6px 14px; padding-block: 12px; }
    table.data.cards.concepts tr.depth-1 { padding-inline-start: 36px; }
    table.data.cards.concepts tr.depth-2 { padding-inline-start: 56px; }
    table.data.cards.concepts tr.depth-3 { padding-inline-start: 76px; }
    table.data.cards.concepts td:not(:first-child) { flex: 0 0 auto; inline-size: auto; }
    table.data.cards.concepts td[data-label]::before { content: none; }
    table.data.cards.concepts td > .muted { display: none; }
    table.data.cards td.pinned::before { content: none; }
    table.data.cards td:last-child .row-link { width: 100%; justify-content: center; border: 1.5px solid var(--primary); border-radius: var(--radius-control); }
    /* A table inside a padded card (.plain) is not padded a second time, and a cell with nothing in it takes no line. */
    table.data.plain.cards tr { padding-inline: 0; }
    table.data.plain.cards td:empty { display: none; }
    table.data.cards td[data-label]::before { content: attr(data-label); display: block; margin-bottom: 2px; font-size: .75rem; font-weight: 600; color: var(--muted); }
    /* A class over time: the count at the end of a student's card says what it counts, and the counts of the class are a card too. */
    table.data.cards.class-work td.pinned[data-label]::before { content: attr(data-label); }
    table.data.cards tfoot { display: block; }
    table.data.cards tfoot th { display: block; flex: 1 1 100%; padding: 0; border: 0; }
    table.data.cards.class-work tfoot td:last-child { display: none; }
    table.data th:first-child, table.data td:first-child { padding-inline-start: 16px; }
    table.data th:last-child, table.data td:last-child { padding-inline-end: 16px; }
    table.data.plain th:first-child, table.data.plain td:first-child { padding-inline-start: 0; }
    table.data.plain th:last-child, table.data.plain td:last-child { padding-inline-end: 0; }
    table.data th, table.data td { padding-inline: 8px; }

    /* Front door and sign-in: the coloured panel becomes a short heading */
    .auth { flex-direction: column; flex-wrap: nowrap; }
    .auth-aside { flex: none; padding: 18px 20px 22px; gap: 14px; }
    .auth-pitch { flex: none; gap: 6px; }
    .auth-title { font-size: 1.75rem; }
    .auth-lead { font-size: 1rem; }
    .auth-steps { display: none; }
    .auth-main { flex: 1 1 auto; padding: 16px 16px 32px; gap: 16px; }
    .auth-box { justify-content: flex-start; gap: 16px; }
    .auth-box .card { padding: 20px 18px; }
    .auth-alt .row { width: 100%; }
    .auth-alt .btn { flex: 1 1 auto; }

    /* Before and after the exam */
    .done-head h1 { font-size: 1.375rem; }
    .done-mark { width: 40px; height: 40px; }
    .score-row .value { min-width: 0; }
    .answer-text { padding: 14px 16px; font-size: 1.0625rem; }
    .prose { padding: 20px 18px; font-size: 1rem; }

    /*
    | The exam screen. With a keyboard up, the top bar stays and the keyboard covers the foot of the page: so the
    | top bar is one row, and the clock the heaviest thing in it.
    |   - Top: the exam and which section this is; saved, as its sign alone; text size, quiet; the clock.
    |   - Under it the parts: one row that runs from edge to edge of the screen and on past it, pulled sideways.
    |   - Foot, one row: the list of questions (its button says how many are answered), flag, and the capsule of
    |     the two ways as two discs. The end of the section is not left to a picture: there the way on says so.
    */
    .exam-bar-inner { padding: 6px var(--gutter); grid-template-columns: minmax(0, 1fr) auto auto auto; grid-template-areas: "id save size timer"; column-gap: 6px; }
    .exam-bar-inner:has(.steps.free), .exam-bar-inner:has(.exam-due) { padding-block-end: 0; grid-template-areas: "id save size timer" "steps steps steps steps"; }
    .exam-id .avatar { display: none; }
    .exam-id strong { font-size: .9375rem; }
    .exam-id .section-now { display: block; }
    .exam-id:has(.section-now) .who { display: none; }
    .steps:not(.free) { display: none; }
    .save-words { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
    .text-size { width: 40px; height: 40px; background: transparent; }
    .text-size .icon { width: 20px; height: 20px; }
    .timer { min-height: var(--touch); padding: 2px 12px; gap: 8px; }
    .timer > .icon { width: 18px; height: 18px; }
    .timer-value { font-size: 1.25rem; }
    /* The clock alone, as long as there is time; in the last five minutes the words that say so stand over it. */
    .timer-label.when-normal { display: none; }
    .timer-label > .tr-alt { display: none; }
    /* Sections the student moves between: a row of tabs that scrolls sideways and has the line to itself. */
    .steps.free { margin-block-start: 6px; margin-inline: calc(-1 * var(--gutter)); padding: 0 var(--gutter); gap: 2px; font-size: .8125rem; border-top: 1px solid var(--line-soft); }
    .steps.free li, .steps.free li + li { flex: none; min-width: 0; }
    .steps.free button { white-space: nowrap; }
    .steps .mark { width: 22px; height: 22px; font-size: .75rem; }
    .pager-place .when-wide { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
    .pager-place .when-tight { display: inline; }
    .save-banner { padding: 8px var(--gutter); font-size: .875rem; }

    .exam-main { padding: 12px var(--gutter) 24px; }
    .exam-main > * + *, .part > * + *, .exam-section > * + * { margin-top: 12px; }
    .exam-main > .exam-section { margin-top: 0; }
    .part-tabs { display: flex; max-width: none; gap: 6px; margin-inline: calc(-1 * var(--gutter)); padding: 4px var(--gutter); background: transparent; border-radius: 0; scroll-padding-inline: var(--gutter); }
    .part-tabs button { min-height: 38px; padding-inline: 6px 14px; font-size: .875rem; background: var(--track); transition: background-color var(--motion-base) var(--ease), color var(--motion-base) var(--ease), box-shadow var(--motion-base) var(--ease); }
    .part-tabs button[aria-selected="true"] { background: var(--plate); box-shadow: var(--shadow-raised); }
    .part-tabs .mark { width: 24px; height: 24px; }
    .tab-glide { display: none; }
    .part-title { font-size: 1.25rem; }
    .instructions { margin-block-end: 16px; padding-block-end: 14px; font-size: 1.0625rem; }
    .player { padding: 14px; gap: 12px; }
    .player-button { width: 100%; }
    .player .volume { width: 100%; }
    .player .volume input { flex: 1; width: auto; }
    .question { gap: .75rem; }
    .qnum { min-width: 30px; height: 30px; font-size: .875rem; }
    .q-prompt { font-size: 1.0625rem; }
    /* Short answers stand under their question, at its start, not squeezed beside it. */
    .q-row .q-prompt { flex-basis: 100%; }
    .question:has(> .q-row) { align-items: flex-start; }
    .choices { grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); }
    .choices.labels label { min-width: 4.5rem; min-height: 2.25rem; }
    .content { font-size: 1.0625rem; --gap-w: 6em; }
    .content.gaps-short { --gap-w: 4.25em; }
    .content.gaps-long { --gap-w: 8em; }
    .content th, .content td { padding: 8px 10px; }
    /* A conversation's lines are narrower than the text's: beside its names its gaps are shorter, so that a gap and
       the words around it share a line. */
    .content .dialogue { --gap-w: 5em; gap: .375rem .625rem; margin-inline: -.625rem; padding: .75rem .75rem .875rem; }
    .content.gaps-short .dialogue { --gap-w: 4.25em; }
    .content.gaps-long .dialogue { --gap-w: 5.75em; }
    /*
    | A table of three columns or more does not fit a phone, and a text with gaps is not something to pull sideways
    | while it is being answered. So each row is set out as a list: every cell on a line of its own under the heading
    | of its column (data-label, written by RichText::boxTables()), and the rows as soft plates under each other.
    | A table of two columns fits as it is; one without headings has nothing to head its lines with, and scrolls.
    | Only the tables of an exam's own texts: a table of the guide stays a table.
    */
    :is(.content, .instructions) .table-box.listed { overflow: visible; }
    :is(.content, .instructions) .listed :is(table, tbody, tr, td) { display: block; }
    :is(.content, .instructions) .listed thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
    :is(.content, .instructions) .listed tr { padding: .75rem .875rem; background: var(--surface-subtle); border-radius: var(--radius-control); }
    :is(.content, .instructions) .listed tr + tr { margin-block-start: .5rem; }
    :is(.content, .instructions) .listed td { padding: 0; border: 0; }
    :is(.content, .instructions) .listed td + td { margin-block-start: .625rem; }
    :is(.content, .instructions) .listed td:empty { display: none; }
    :is(.content, .instructions) .listed td[data-label]::before { content: attr(data-label); display: block; font: 600 .75rem/1.5 var(--font-ui); color: var(--muted); }
    html[lang="fa"] :is(.content, .instructions) .listed td[data-label]::before { font-family: var(--font-fa); }
    /* A line with a gap in it is set apart like a paragraph's, and the gap is as long as the exercise's gaps are. */
    :is(.content, .instructions) .listed td:has(.blank) { line-height: 2.5rem; }
    :is(.content, .instructions) .listed td .blank input[type="text"] { width: var(--gap-w); }
    .word { padding: 6px 12px; font-size: 1rem; }
    .option-key { grid-template-columns: 1fr; padding: 10px 14px; }
    .choices.pictures.n4, .option-key.pictures.n4 { --cols: 2; }
    /* Pictures need the room: under the question they also take the place under its number. */
    .choices.pictures { gap: 4px; margin-inline: calc(-30px - .75rem - .375rem) -.375rem; }
    .choices.pictures label { padding: .375rem .375rem .5rem; }
    /* Things to put in order take the room under the question's number too, and stack their two arrows. */
    .order-items { margin-inline-start: calc(-30px - .75rem); }
    /* So do things to join with lines (decision 97): three columns need every pixel of the phone. */
    .question .join-board { margin-inline-start: calc(-30px - .75rem); }
    .join-board { column-gap: 1.5rem; }
    .join-board.cols-4 { column-gap: 1rem; }
    .join-item { padding: .5rem .625rem; font-size: 1rem; }
    .order-grip { width: 1.75rem; }
    /* Items of a paragraph's length: the grip, the place and the arrows on a line above the words, which have the width. */
    .order.long .order-item { display: grid; grid-template-columns: auto auto 1fr auto; grid-template-areas: "grip place . moves" "text text text text"; row-gap: 0; padding-block-end: .5rem; }
    .order.long .order-grip { grid-area: grip; }
    .order.long .order-place { grid-area: place; }
    .order.long .order-moves { grid-area: moves; flex-direction: row; gap: 2px; }
    .order.long .order-move { height: 2.25rem; }
    .order.long .order-text { grid-area: text; padding-inline: .75rem; }
    .order-moves { flex-direction: column; gap: 0; }
    .order-move { height: 2rem; }
    .order-move::after { inset: 0 -4px; }
    .option-key.pictures { grid-template-columns: repeat(var(--cols), minmax(0, 1fr)); padding: 10px; }
    .letters-block { margin-inline: 0; }
    .letters { gap: 2px; }
    .letters > span, .letters > input { width: 27px; height: 40px; font-size: 1.0625rem; border-radius: 6px; }
    .letters > span.joint { width: 8px; }
    .passage { padding: 20px 18px; font-size: 1.0625rem; }
    .passage h2 { font-size: 1.375rem; }
    .essay { gap: 16px; }
    .essay-editor label { padding: 12px 16px 0; }
    .essay-editor textarea { min-height: 300px; padding: 8px 16px 16px; font-size: 1.0625rem; }
    .word-meter { padding: 10px 16px; gap: 6px 12px; }

    .exam-footer-inner { padding: 8px var(--gutter) calc(8px + env(safe-area-inset-bottom)); gap: 8px; }
    .qnav { gap: 8px; }
    .qnav-group { display: none; }
    /* The list of questions: here its button takes all the room the flag and the capsule leave. */
    .qnav-count { flex: 1 1 0; min-width: 0; padding-inline: 14px; }
    .qnav-count .count-word, .qnav-count-text > [role="status"] { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .qnav-count .sizer { display: none; }
    .qnav-flag { width: 52px; min-height: 52px; margin: 0; padding: 0; justify-content: center; border-radius: 50%; }
    .qnav-flag .icon { width: 24px; height: 24px; }
    .qnav-flag > span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
    .pg-next { width: auto; padding: 0; gap: 0; }
    .pg-next .nav-text, .pg-next .when-wide { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
    .pg-next.ends { padding-inline-start: 8px; gap: 12px; }
    .pg-next.ends .nav-text { position: static; width: auto; height: auto; overflow: visible; clip-path: none; }
    .pg-next.ends .roll-win:first-child { display: none; }
    .pg-next.ends .nav-title { translate: none; }
    .pg-next.ends .when-tight { display: inline; }
    .exam-footer.at-end .qnav-count > .icon { display: none; }
    /* Beside the word of the end there is room for the count as two numbers only. */
    .exam-footer.at-end .qnav-count-text > [role="status"] { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
    .exam-footer.at-end .qnav-count .count-short { display: block; }

    /* The sheet of the review stands on the screen's two edges here, whichever button it grew out of. */
    dialog.review, dialog.review[data-mode="review"] { inset-inline: 0; width: 100%; max-height: 84dvh; padding: 18px 16px calc(20px + env(safe-area-inset-bottom)); }
    /* At the end of a section the Questions button says its count as two numbers, which the sheet's heading does
       not: so there too the sheet comes over its button softly, a breath before it grows, as over the capsule. */
    .exam:has(.exam-footer.at-end) dialog.review[data-mode="review"].open { transition: clip-path 560ms var(--ease-soft) 70ms, background-color 420ms var(--ease) 70ms, opacity 90ms linear; }
    .exam:has(.exam-footer.at-end) dialog.review[data-mode="review"].open .fly { transition: transform 560ms var(--ease-soft) 70ms; }
    .review-top { margin-block-end: 8px; }
    .review-top > .icon { width: 20px; height: 20px; }
    .review-top h2 { font-size: 1.125rem; line-height: 1.2; }
    .review-line { font-size: .8125rem; line-height: 1.2; }

    dialog { padding: 22px 18px; }
    .select-list { max-width: calc(100vw - 16px); white-space: normal; }
}

/* The narrowest phones: the list's button keeps its two lines whole by giving up its picture. */
@media (max-width: 359px) {
    .qnav-count { gap: 0; padding-inline: 12px; }
    .qnav-count > .icon { display: none; }
    .exam-footer-inner, .qnav { gap: 6px; }
}
/* Phones too narrow for "Questions" beside the word of the end (it was cut short there: "Questio…"): at the end of
   a section the button is its picture and the two numbers. The picture is what stays; the word gives way. */
@media (max-width: 389px) {
    .exam-footer.at-end .qnav-count { gap: 8px; }
    .exam-footer.at-end .qnav-count > .icon { display: block; }
    .exam-footer.at-end .qnav-count .count-word { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
    .exam-footer.at-end .qnav-count .count-short { font-size: .9375rem; font-weight: 700; color: var(--ink); }
}

/* ---------- Visual vocabulary: icons that carry the eye ---------- */

/*
| A page should be readable at a glance: every block starts with an icon tile,
| a person is an avatar, a fact is an icon and a few words (.meta), and a count
| is a number beside its icon. Long sentences are the exception.
*/
.tile {
    display: inline-flex; align-items: center; justify-content: center; flex: none;
    width: 44px; height: 44px; border-radius: 12px;
    background: var(--primary-soft); color: var(--primary);
}
.tile .icon { width: 22px; height: 22px; stroke-width: 2; }
.tile.small { width: 36px; height: 36px; border-radius: 10px; }
.tile.small .icon { width: 18px; height: 18px; }
.tile.large { width: 60px; height: 60px; border-radius: 18px; }
.tile.large .icon { width: 28px; height: 28px; }
.tile.good { background: var(--good-soft); color: var(--good); }
.tile.warn { background: var(--warn-soft); color: var(--warn); }
.tile.bad { background: var(--bad-soft); color: var(--bad); }
.tile.neutral { background: var(--neutral-soft); color: var(--ink-faint); }

.avatar {
    display: inline-flex; align-items: center; justify-content: center; flex: none;
    width: 40px; height: 40px; border-radius: 50%;
    background: var(--primary-soft); color: var(--primary);
    font: 700 .875rem/1 var(--font-ui); text-transform: uppercase; letter-spacing: .02em;
}
.avatar.large { width: 56px; height: 56px; font-size: 1.125rem; }

/* A heading with its tile: the start of a card or a page. */
.heading { display: flex; align-items: center; gap: 12px; min-width: 0; }
.heading > div { min-width: 0; }
.heading h1, .heading h2, .heading h3 { margin: 0; }
.heading .help { margin: 0; }
.card > .heading { margin-bottom: 16px; }

.person { display: flex; align-items: center; gap: 12px; min-width: 0; }
.person > div { min-width: 0; }
.person .name { font-weight: 700; }

.meta { display: inline-flex; align-items: center; gap: 6px; font-size: .875rem; color: var(--muted); }
.meta .icon { width: 16px; height: 16px; stroke-width: 2; }
.meta strong { color: var(--ink); }
.meta-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 16px; }

.stat { display: flex; align-items: center; gap: 12px; }
.stat > div { min-width: 0; line-height: 1.3; }

.empty { display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 40px 24px; }
.empty > p { margin: 0; max-width: 46ch; }

.tabs .icon { width: 18px; height: 18px; stroke-width: 2; }
.btn .icon { stroke-width: 2.2; }

/* Scores of the sections of one exam, side by side. */
.score-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.score-chip { display: inline-flex; align-items: baseline; gap: 6px; padding: 4px 10px; border-radius: 8px; background: var(--neutral-soft); font-size: .8125rem; color: var(--muted); }
.score-chip strong { font-size: .9375rem; color: var(--ink); font-variant-numeric: tabular-nums; }
.score-chip.warn { background: var(--warn-soft); color: var(--warn); font-weight: 700; }

@media (max-width: 700px) {
    .main-nav .icon { width: 22px; height: 22px; }
    .tile { width: 40px; height: 40px; }
    .stat { gap: 10px; }
    .stat .tile { width: 36px; height: 36px; border-radius: 10px; }
    .tabs .icon { display: none; }
    .empty { padding: 28px 18px; }
}

/* A few large choices, each a card: the site's language, for example. */
.option-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 10px; }
.option-card {
    display: flex; align-items: center; gap: 12px; min-height: 56px; padding: 10px 16px;
    font-size: 1rem; font-weight: 600; cursor: pointer;
    background: var(--surface); border: 1.5px solid var(--control-soft); border-radius: 12px;
    transition: background-color var(--motion-fast) var(--ease), border-color var(--motion-fast) var(--ease);
}
/* A choice that needs a sentence of explanation under its name. */
.option-card.tall { align-items: flex-start; padding-block: 14px; }
.option-card.tall input { margin-block-start: 2px; }
.option-card.tall > span { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.option-card.tall .help { font-weight: 400; color: var(--muted); }
.option-card:hover { border-color: var(--control); }
.option-card:has(input:checked) { background: var(--primary-soft); border-color: var(--primary); color: var(--primary); }
.option-card:has(input:focus-visible) { outline: 3px solid var(--focus); outline-offset: 2px; }
.option-card input:focus-visible { outline: 0; }
.option-card > .icon { width: 20px; height: 20px; stroke-width: 2; }
.option-cards.compact { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }

/* A colour to choose: a dot of it in an option card. The last one is the browser's colour picker. */
.swatch, .swatch-input { flex: none; width: 22px; height: 22px; border-radius: 50%; border: 1.5px solid var(--control); }
.swatch-input {
    appearance: none; -webkit-appearance: none; padding: 0; overflow: hidden; cursor: pointer;
    background: none;
}
.swatch-input::-webkit-color-swatch-wrapper { padding: 0; }
.swatch-input::-webkit-color-swatch { border: 0; }
.swatch-input::-moz-color-swatch { border: 0; }

/* ---------- The site's brand (Settings) ---------- */

.field-legend { padding: 0; margin-bottom: 4px; font-weight: 600; font-size: .9375rem; }
.field-legend ~ .help { margin-bottom: 12px; }
.brand-upload { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.brand-upload-current {
    flex: none; height: var(--touch); width: auto; max-width: 132px; object-fit: contain; padding: 4px;
    border: 1px solid var(--line); border-radius: var(--radius-control); background: var(--logo-ground);
}

/* A small picture of the site, drawn with the brand tokens; ui.js gives it those of a colour not saved yet. */
.brand-preview {
    display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
    border: 1px solid var(--line); border-radius: var(--radius-card); overflow: hidden;
}
.brand-preview-panel { display: flex; flex-direction: column; justify-content: center; gap: 6px; padding: 20px; background: var(--panel); color: var(--on-panel); }
.brand-preview-panel .brand { color: var(--on-panel); }
.brand-preview-panel .brand::before { background: var(--on-panel); }
.brand-preview-lead { font-size: .875rem; color: var(--on-panel-soft); }
.brand-preview-body { display: flex; flex-direction: column; gap: 12px; padding: 20px; background: var(--surface); }
.brand-preview-link { color: var(--primary); font-weight: 700; text-decoration: underline; }
.brand-preview .btn { pointer-events: none; }

/* What a new version brings (Settings → Updates): the release's own notes, kept small. */
.release-notes { font-size: .9375rem; color: var(--ink-soft); }
.release-notes > :last-child { margin-bottom: 0; }
.release-notes ul, .release-notes ol { margin: 0 0 1em; padding-inline-start: 22px; }
.release-notes h1, .release-notes h2, .release-notes h3 { font-size: 1rem; }

.section-heading { margin: 28px 0 16px; }
.rules li { align-items: center; }
.danger-zone > .heading { flex: 1 1 280px; }
.heading > label { font-size: 1.125rem; font-weight: 700; }
.page-head > .heading { flex: 1 1 320px; }
.page-head .lead { margin-top: 4px; font-size: 1rem; }
.score-row .value { flex: none; min-width: 0; }
.side .score-row { padding: 12px 0; }
.side .score-row progress { flex: 1 1 100%; }
.columns > .side > .heading { margin-bottom: 0; }
.list-row > .tile { align-self: flex-start; }
.stats { grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); }

/* A side column that matters most comes first when the columns stack. */
@media (max-width: 979px) {
    .columns > .lead-side { order: -1; }
}

@media (max-width: 700px) {
    .heading { gap: 10px; }
    .tile.large, .avatar.large { width: 48px; height: 48px; border-radius: 14px; }
    .avatar.large { border-radius: 50%; font-size: 1rem; }
    .tile.large .icon { width: 24px; height: 24px; }
    .page-head > .heading { flex-basis: 100%; }
    .list-row { align-items: flex-start; }
    .list-row > .tile { width: 36px; height: 36px; border-radius: 10px; }
    .list-row > .tile .icon { width: 18px; height: 18px; }
    .list-main { flex: 1 1 calc(100% - 60px); }
    .list-row > .badge, .list-row > .list-go { margin-inline-start: 52px; }
    .list-row > .icon { display: none; }
    .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .option-cards { grid-template-columns: 1fr; }
    .option-cards.compact { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .brand-preview { grid-template-columns: 1fr; }
    .rules li { align-items: flex-start; }
}

@media (max-width: 700px) {
    .page-head .heading { align-items: flex-start; }
    .band .heading > .tile { display: none; }
}

/* ---------- Answer sheets (exam/sheet, docs/features/answer-sheets.md) ----------
| A sheet a student writes on, and whoever reads it afterwards: a person, or an assistant shown a photograph. Its
| measures are a paper's (mm, pt) and its colours the sheet's own, the same in the dark. Every question is a row:
| its number, then the room its kind of answer needs. Two rows to a line, a row of writing across the whole line.
*/
.print-bar {
    position: sticky; top: 0; z-index: 5; display: flex; align-items: center; flex-wrap: wrap; gap: 10px 20px;
    padding: 12px var(--gutter); background: var(--surface); border-block-end: 1px solid var(--line);
}
.print-bar .back-link { margin: 0; }
.print-bar-text { flex: 1 1 260px; display: flex; flex-direction: column; min-width: 0; }
.sheets { display: flex; flex-direction: column; align-items: center; gap: 24px; padding: 24px var(--gutter) 48px; }
.sheet {
    page: sheet; box-sizing: border-box; width: 210mm; max-width: 100%; padding: 12mm 14mm;
    background: var(--sheet-ground); color: var(--sheet-ink); box-shadow: var(--shadow-raised);
    font-family: var(--font-ui); font-size: 10.5pt; line-height: 1.35;
}
.sheet-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 4mm; padding-block-end: 3mm; border-block-end: 1.5pt solid var(--sheet-ink); }
.sheet-title { display: flex; flex-direction: column; min-width: 0; }
.sheet-brand, .sheet-level { font-size: 8.5pt; color: var(--sheet-line); }
.sheet-title h1 { margin: 0; font-size: 15pt; line-height: 1.2; }
.sheet-code { flex: none; display: flex; align-items: baseline; gap: 2mm; margin: 0; padding: 1.5mm 3mm; border: 1.2pt solid var(--sheet-ink); border-radius: 2mm; font-size: 9pt; white-space: nowrap; }
.sheet-code strong { font-family: var(--font-code); font-size: 14pt; letter-spacing: .14em; }
.sheet-who { display: flex; flex-wrap: wrap; gap: 2mm 6mm; margin: 4mm 0 0; }
.sheet-who > div { display: flex; align-items: flex-end; gap: 2mm; flex: 0 1 38mm; }
.sheet-who > .grow { flex: 1 1 70mm; }
.sheet-who dt { font-size: 9pt; }
.sheet-who dd { flex: 1; display: flex; align-items: flex-end; min-height: 7mm; margin: 0; border-block-end: .8pt solid var(--sheet-line); }
.sheet-how { display: flex; align-items: center; flex-wrap: wrap; gap: 1.5mm 2mm; margin: 3mm 0 0; font-size: 8.5pt; }
.sheet-how .bubble + span { margin-inline-start: 2mm; }
.sheet-section { margin-block-start: 5mm; }
.sheet-section h2 { margin: 0 0 1.5mm; padding-block: 1mm; font-size: 11.5pt; border-block-end: .8pt solid var(--sheet-ink); }
.sheet-part { margin-block-start: 2.5mm; }
.sheet-part h3 { margin: 0 0 1mm; font-size: 9.5pt; font-weight: 600; break-after: avoid; }
.sheet-rows { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.5mm 8mm; }
.sheet-row { display: flex; align-items: center; flex-wrap: wrap; gap: 1.5mm 2.5mm; min-height: 8mm; break-inside: avoid; }
.sheet-row.wide { grid-column: 1 / -1; }
.sheet-row.writing { align-items: flex-start; }
.sheet-n { flex: none; min-width: 7mm; font-size: 10.5pt; font-weight: 700; text-align: end; }
.bubbles { display: flex; flex-wrap: wrap; gap: 2mm; }
.bubble {
    box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; min-width: 6.5mm; height: 6.5mm;
    padding-inline: 1.2mm; border: .9pt solid var(--sheet-ink); border-radius: var(--radius-pill);
    font-size: 8pt; font-weight: 600; line-height: 1; color: var(--sheet-ink);
}
.bubble.square { border-radius: 1mm; }
/* Filled and crossed are drawn as a pen draws them, and printed so: a printer leaves grounds out unless told. */
.bubble.filled { background: var(--sheet-ink); color: var(--sheet-ground); print-color-adjust: exact; -webkit-print-color-adjust: exact; }
.bubble.crossed {
    background: linear-gradient(45deg, transparent 44%, var(--sheet-ink) 44% 56%, transparent 56%), linear-gradient(-45deg, transparent 44%, var(--sheet-ink) 44% 56%, transparent 56%);
    print-color-adjust: exact; -webkit-print-color-adjust: exact;
}
.sheet-line { flex: 1 1 30mm; display: flex; align-items: flex-end; min-width: 20mm; min-height: 7mm; border-block-end: .8pt solid var(--sheet-line); }
.sheet-lines { flex: 1; display: flex; flex-direction: column; }
.sheet-lines .sheet-line { flex: none; min-height: 8.5mm; }
.sheet-write { padding-inline: 1mm; font-family: var(--font-read); font-size: 11pt; }
.sheet-label { flex: none; font-size: 9pt; }
.sheet-none { font-size: 9pt; font-style: italic; color: var(--sheet-line); }
.order-boxes { display: flex; flex-wrap: wrap; gap: 2mm; margin: 0; padding: 0; list-style: none; counter-reset: place; }
.order-boxes li { counter-increment: place; display: flex; flex-direction: column; align-items: center; gap: .5mm; font-size: 7pt; color: var(--sheet-line); }
.order-boxes li::after { content: counter(place); }
.order-boxes .sheet-write { box-sizing: border-box; display: flex; align-items: center; justify-content: center; width: 8mm; height: 8mm; padding: 0; border: .9pt solid var(--sheet-ink); border-radius: 1mm; color: var(--sheet-ink); }
.sheet-chains { flex: 1 1 100%; display: flex; flex-direction: column; gap: 2mm; margin: 0; padding: 0; list-style: none; }
.sheet-chains li { display: flex; align-items: flex-end; gap: 2mm; }
.sheet-chains .sheet-label { min-width: 22mm; font-size: 10pt; }
.sheet-arrow { flex: none; font-size: 10pt; color: var(--sheet-line); }
.sheet-things { flex: 1 1 100%; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1mm 6mm; margin: 0; padding: 0; list-style: none; }
.sheet-things li { display: flex; align-items: flex-end; gap: 2mm; font-size: 9pt; }
.sheet-foot { margin: 6mm 0 0; padding-block-start: 2mm; border-block-start: .8pt solid var(--sheet-line); font-size: 8.5pt; color: var(--sheet-line); text-align: end; }
@page sheet { size: A4; margin: 11mm 13mm; }

/* Entering a sheet by hand (admin/attempts/sheet): a question to a line, the number then its field. */
.entry-part h3 { margin: 0 0 4px; font-size: 1rem; }
.entry-rows { list-style: none; margin: 0; padding: 0; }
.entry-row { display: flex; align-items: flex-start; gap: 14px; padding-block: 10px; border-block-start: 1px solid var(--line-soft); }
.entry-row:first-child { border-block-start: 0; }
.entry-row > .qnum { margin-block-start: 12px; }
.entry-main { flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
.entry-main > :is(input, textarea) { width: 100%; }
.entry-main > .help { margin: 0; }
.entry-row.unclear { background: var(--warn-soft); margin-inline: -12px; padding-inline: 12px; border-radius: var(--radius-control); }
/* The options of the sheet as round buttons, as its circles are: one pressed, or a square for each to tick. */
.letter-pick { display: flex; flex-wrap: wrap; gap: 6px; }
.letter-pick label { position: relative; display: inline-flex; }
.letter-pick input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
.letter-pick span {
    display: inline-flex; align-items: center; justify-content: center; min-width: var(--touch); height: var(--touch); padding-inline: 12px;
    border: 1.5px solid var(--control-soft); border-radius: var(--radius-pill); font-weight: 600; color: var(--ink);
    transition: background-color var(--motion-fast) var(--ease), border-color var(--motion-fast) var(--ease), color var(--motion-fast) var(--ease);
}
.letter-pick.square span { border-radius: var(--radius-control); }
.letter-pick label:hover span { border-color: var(--control); }
.letter-pick input:checked + span { background: var(--primary); border-color: var(--primary); color: var(--on-primary); }
.letter-pick input:focus-visible + span { outline: 3px solid var(--focus); outline-offset: 2px; }
.letter-pick .none span { border-style: dashed; color: var(--muted); }
.letter-pick .none input:checked + span { background: var(--neutral-soft); border-color: var(--control); color: var(--ink-soft); }
/* Sheets an assistant read (admin/exams/paper/read): each with whose it would be, and what was read on it. */
.read-sheets { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }
.read-sheet-head { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 12px 24px; }
.read-sheet-who { display: flex; flex-direction: column; gap: 4px; min-width: 0; flex: 1 1 240px; }
.read-sheet-who strong { font-size: 1.125rem; }
.read-sheet-whose { flex: 1 1 280px; max-width: 420px; }
.read-answers summary { cursor: pointer; font-weight: 600; color: var(--primary); }
.read-answers ol { list-style: none; margin: 10px 0 0; padding: 0; columns: 300px; column-gap: 32px; }
.read-answers li { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 10px; padding: 4px 0; break-inside: avoid; font-size: .9375rem; }
.read-answers li.unclear { color: var(--warn); }
.read-place { flex: 1 1 100%; font-size: .8125rem; color: var(--muted); }
.read-written { font-weight: 600; }
.sheet-photos { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }
.sheet-photos li { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; }
.sheet-photos .picture { max-inline-size: 100%; block-size: auto; border: 1px solid var(--line); border-radius: var(--radius-control); }

@media (max-width: 600px) {
    .sheet { padding: 20px 16px; }
    .sheet-rows, .sheet-things { grid-template-columns: minmax(0, 1fr); }
}

/* ---------- Paper ---------- */

/*
| A result is the one page worth printing. On paper there is nothing to press
| and nothing to navigate: the content alone, in one column, dark on white.
*/
/* The exam screen, where writing aids are kept out, prints nothing: its texts are not to leave it (decision 148). */
@media print { [data-aids-closed], .aid-note, .away-dialog { display: none !important; } }

@media print {
    .site-head, .main-nav, .back-link, .no-print, .flash, .page-loader, .dev-bar { display: none !important; }
    .print-only { display: inline-flex; }
    body { background: none; }
    .wrap { max-width: none; padding: 0; }
    .card { box-shadow: none; break-inside: avoid; }
    .review-section { break-inside: auto; }
    .review-row, .review-part > .part-head { break-inside: avoid; }
    .columns { display: block; }
    .columns > * + * { margin-block-start: 16px; }
    * { animation: none !important; }
    /* A script is printed where it was opened; closed, its line alone would say nothing on paper. */
    .script:not([open]), .script-turn { display: none; }
    /* Answer sheets: the sheets alone, each from a new page, at the margins of the page they are printed on. */
    .sheets { display: block; padding: 0; }
    .sheet { width: auto; padding: 0; box-shadow: none; }
    .sheet + .sheet { break-before: page; }
}
