/* App-specific overrides on top of PicoCSS. Keep this small. */

:root {
	--pico-font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

	/* ── Theme: one accent, everything derived ──────────────────────────
	   --mb-accent is the single brand input. layout.html injects a per-school
	   value here from settings.theme_accent; this is the fallback default.
	   Every other accent token is derived with color-mix. Subtle fills mix
	   into --mb-surface, which tracks the real page background (--pico-
	   background-color) and so flips automatically in dark mode — meaning
	   the same formulas yield light- and dark-appropriate tints. */
	--mb-accent: #4f46e5;
	--mb-surface: var(--pico-background-color);
	--mb-accent-contrast: #ffffff;                                              /* text on a solid accent fill */
	--mb-accent-text: var(--mb-accent);                                         /* accent as a link/text colour */
	--mb-accent-text-hover: color-mix(in srgb, var(--mb-accent) 78%, black);
	--mb-accent-strong: color-mix(in srgb, var(--mb-accent) 88%, black);        /* solid-button hover */
	--mb-accent-subtle: color-mix(in srgb, var(--mb-accent) 12%, var(--mb-surface));
	--mb-accent-border: color-mix(in srgb, var(--mb-accent) 32%, var(--mb-surface));
	--mb-motif-opacity: .05;

	/* Map onto Pico so every existing button / link / pill / focus ring
	   adopts the accent. Links use the contrast-adjusted text token; solid
	   buttons use the raw accent with white text. */
	--pico-primary: var(--mb-accent-text);
	--pico-primary-hover: var(--mb-accent-text-hover);
	--pico-primary-focus: var(--mb-accent-subtle);
	--pico-primary-background: var(--mb-accent);
	--pico-primary-hover-background: var(--mb-accent-strong);
	--pico-primary-border: var(--mb-accent);
	--pico-primary-hover-border: var(--mb-accent-strong);
	--pico-primary-inverse: var(--mb-accent-contrast);
	--pico-primary-underline: color-mix(in srgb, var(--mb-accent) 35%, transparent);
}

/* Dark mode. Pico's own dark block targets :root:not([data-theme]) — higher
   specificity than a bare :root — so we re-assert the accent mappings here at
   matching specificity (and later in source) to win, and lighten the
   text/hover accents for contrast on a dark surface. The subtle/border/motif
   tokens re-derive automatically because --mb-surface tracks the page bg. */
@media (prefers-color-scheme: dark) {
	:root:not([data-theme]),
	[data-theme="dark"] {
		--mb-accent-text: color-mix(in srgb, var(--mb-accent) 55%, white);
		--mb-accent-text-hover: color-mix(in srgb, var(--mb-accent) 42%, white);
		--mb-accent-strong: color-mix(in srgb, var(--mb-accent) 78%, white);
		--mb-motif-opacity: .08;

		--pico-primary: var(--mb-accent-text);
		--pico-primary-hover: var(--mb-accent-text-hover);
		--pico-primary-focus: var(--mb-accent-subtle);
		--pico-primary-background: var(--mb-accent);
		--pico-primary-hover-background: var(--mb-accent-strong);
		--pico-primary-border: var(--mb-accent);
		--pico-primary-hover-border: var(--mb-accent-strong);
		--pico-primary-inverse: var(--mb-accent-contrast);
		--pico-primary-underline: color-mix(in srgb, var(--mb-accent) 35%, transparent);
	}
}

[x-cloak] { display: none !important; }

/* ── Ambient page texture: a sparse, large repeating motif of Material
   glyphs (open book, pencil, star) drawn behind the content area. It's a
   CSS mask tinted with the accent, kept very low-opacity so it reads as a
   faint watermark in the gaps between cards and text — not clutter. The
   accent + opacity flow from the theme tokens, so it tracks light/dark and
   any per-school accent. Disabled via the `no-motif` body class.
   Fixed to the viewport so it stays calm while content scrolls. */
body::before {
	content: "";
	position: fixed;
	inset: 0;
	z-index: -1;
	pointer-events: none;
	background-color: var(--mb-accent);
	opacity: var(--mb-motif-opacity);
	-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='360' height='360' viewBox='0 0 360 360'%3E%3Cg fill='black'%3E%3Cg transform='translate(46 52) scale(2.6)'%3E%3Cpath d='M21 5c-1.11-.35-2.33-.5-3.5-.5-1.95 0-4.05.4-5.5 1.5-1.45-1.1-3.55-1.5-5.5-1.5S2.45 4.9 1 6v14.65c0 .25.25.5.5.5.1 0 .15-.05.25-.05C3.1 20.45 5.05 20 6.5 20c1.95 0 4.05.4 5.5 1.5 1.35-.85 3.8-1.5 5.5-1.5 1.65 0 3.35.3 4.75 1.05.1.05.15.05.25.05.25 0 .5-.25.5-.5V6c-.6-.45-1.25-.75-2-1zm0 13.5c-1.1-.35-2.3-.5-3.5-.5-1.7 0-4.15.65-5.5 1.5V8c1.35-.85 3.8-1.5 5.5-1.5 1.2 0 2.4.15 3.5.5v11.5z'/%3E%3C/g%3E%3Cg transform='translate(238 150) scale(2.4)'%3E%3Cpath d='M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34a.9959.9959 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z'/%3E%3C/g%3E%3Cg transform='translate(120 248) scale(2.2)'%3E%3Cpath d='M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
	mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='360' height='360' viewBox='0 0 360 360'%3E%3Cg fill='black'%3E%3Cg transform='translate(46 52) scale(2.6)'%3E%3Cpath d='M21 5c-1.11-.35-2.33-.5-3.5-.5-1.95 0-4.05.4-5.5 1.5-1.45-1.1-3.55-1.5-5.5-1.5S2.45 4.9 1 6v14.65c0 .25.25.5.5.5.1 0 .15-.05.25-.05C3.1 20.45 5.05 20 6.5 20c1.95 0 4.05.4 5.5 1.5 1.35-.85 3.8-1.5 5.5-1.5 1.65 0 3.35.3 4.75 1.05.1.05.15.05.25.05.25 0 .5-.25.5-.5V6c-.6-.45-1.25-.75-2-1zm0 13.5c-1.1-.35-2.3-.5-3.5-.5-1.7 0-4.15.65-5.5 1.5V8c1.35-.85 3.8-1.5 5.5-1.5 1.2 0 2.4.15 3.5.5v11.5z'/%3E%3C/g%3E%3Cg transform='translate(238 150) scale(2.4)'%3E%3Cpath d='M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34a.9959.9959 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z'/%3E%3C/g%3E%3Cg transform='translate(120 248) scale(2.2)'%3E%3Cpath d='M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
	-webkit-mask-repeat: repeat;
	mask-repeat: repeat;
	-webkit-mask-size: 360px;
	mask-size: 360px;
}
body.no-motif::before {
	display: none;
}

/* ── Themed hero banner: a clean accent panel for page hero headers
   (home, subject hub, profile). Texture lives in the page background, not
   here, so the band stays calm. */
.mb-banner {
	border-radius: var(--pico-border-radius);
	padding: 1.4rem 1.5rem;
	margin-block-end: 1.75rem;
	background: var(--mb-accent-subtle);
	border: 1px solid var(--mb-accent-border);
}
.mb-banner > * {
	margin: 0;
}

/* Button/link with a leading icon. Selector is element-qualified so it
   outranks Pico's `[role=button] { display: inline-block }` — otherwise the
   element stays inline-block and the gap below never takes effect. */
a.btn-icon,
button.btn-icon {
	display: inline-flex;
	align-items: center;
	gap: .55rem;
}
.btn-icon .mb-icon {
	width: 1.15em;
	height: 1.15em;
}

/* Exam submit actions — the primary "Submit" gets a touch more weight so it
   reads as the main action; the header variant stays compact. */
.exam-submit {
	font-size: 1.02rem;
	padding-inline: 1.4rem;
}
.exam-header__submit {
	white-space: nowrap;
}

/* Toast host — fixed at the top-right of the viewport. Each toast is a
   self-removing block that the user can also click to dismiss early.
   Used by the global htmx:sendError / htmx:responseError listeners in
   layout.html. */
#toast-host {
	position: fixed;
	top: 1rem;
	right: 1rem;
	z-index: 1000;
	display: flex;
	flex-direction: column;
	gap: .5rem;
	max-width: min(28rem, calc(100vw - 2rem));
	pointer-events: none;
}

.toast {
	pointer-events: auto;
	background: var(--pico-card-background-color);
	color: var(--pico-color);
	border: 1px solid var(--pico-muted-border-color);
	border-radius: var(--pico-border-radius);
	padding: .85rem 1rem;
	box-shadow: 0 4px 16px rgba(0, 0, 0, .12);
	font-size: .92em;
	line-height: 1.4;
	cursor: pointer;
	animation: toast-in 180ms ease-out;
}

.toast--error {
	border-color: #d97706;
	color: #8a5a00;
	background: #fff8ec;
}

/* Inline form / precondition errors rendered next to the form that
   produced them (see _macros.html → form_errors). Sits flush under
   the action; uses the same warm warning palette as toast--error so
   the two error surfaces feel related. */
.form-errors {
	margin: .5rem 0 0;
	padding: .6rem .85rem .6rem 1.6rem;
	background: #fff8ec;
	border: 1px solid #d97706;
	border-radius: var(--pico-border-radius);
	color: #8a5a00;
	font-size: .92em;
	line-height: 1.4;
}

.form-errors > li {
	margin: 0;
}

.form-errors > li + li {
	margin-top: .2rem;
}

@keyframes toast-in {
	from { transform: translateY(-8px); opacity: 0; }
	to   { transform: translateY(0); opacity: 1; }
}

main.container {
	padding-block: 2rem;
}

/* Opt-in wide container for dense ops pages (e.g. the session dashboard's
   multi-column attempts table). Higher specificity than Pico's .container
   so it widens at every breakpoint; still leaves a gutter on huge screens. */
main.container.page-wide {
	max-width: min(75rem, calc(100% - 2rem));
}

.subject-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
	gap: 1rem;
	margin-block: 1.5rem 2rem;
}

.subject-grid:empty + details,
.subject-grid > p {
	/* keep empty-state copy unstyled */
}

a.card-link {
	text-decoration: none;
	color: inherit;
}

a.card-link > article {
	margin: 0;
	transition: transform 60ms ease, border-color 60ms ease;
}

a.card-link:hover > article {
	transform: translateY(-1px);
	border-color: var(--pico-primary);
}

/* Disclosure summaries styled as secondary outline buttons. */
details {
	margin-block: 1rem 1.5rem;
}

details > summary {
	display: inline-block;
	padding: .55rem 1rem;
	border: 1px solid var(--pico-secondary);
	border-radius: var(--pico-border-radius);
	background: transparent;
	color: var(--pico-secondary);
	cursor: pointer;
	font-weight: 500;
	font-size: .95em;
	user-select: none;
	transition: background-color 100ms, color 100ms;
}

details > summary:hover {
	background: var(--pico-secondary-hover-background, var(--pico-secondary));
	color: var(--pico-secondary-inverse, var(--pico-contrast-inverse));
}

/* Chevron: a Unicode angle bracket (U+203A) rotated on open. We have
   to *override* PicoCSS's own ::after, which paints an SVG background-
   image rotated -90deg — leaving its background-image / width / height /
   transform alone produced two glyphs stacked in the same spot. */
details > summary::after {
	content: "›";
	display: inline-block;
	margin-inline-start: 1rem;
	font-size: 1.2em;
	line-height: 1;
	font-weight: 400;
	transition: transform 150ms ease;
	transform: rotate(0deg);
	transform-origin: center;
	/* override Pico defaults */
	background-image: none;
	background: none;
	width: auto;
	height: auto;
	float: none;
}

details[open] > summary::after {
	transform: rotate(90deg);
}

/* Pico's default marker would conflict with our ::after — hide it.
   list-style:none alone isn't enough in headless Chromium; the ::marker
   pseudo still emits the browser's disclosure glyph alongside our
   chevron. Emptying its content removes it for good. */
details > summary::-webkit-details-marker { display: none; }
details > summary { list-style: none; }
details > summary::marker { content: ""; }

details[open] > summary {
	margin-block-end: 1rem;
}

/* Roster table */

table.roster {
	width: 100%;
}

/* Header labels never break mid-word ("Accommodatio/ns", "Submitt/ed"). */
table.roster th {
	white-space: nowrap;
}

/* The students roster (Name | Student ID | Accommodations | Actions) is a
   simple table — let columns size to their content rather than inherit the
   attempts table's fixed widths, which are tuned for a different column set. */
table.roster--students {
	table-layout: auto;
}

/* The attempts table packs pills + buttons into many columns, so it uses a
   fixed layout with explicit widths so nothing gets squeezed. Widths fit the
   longest content the column can hold (e.g. "submitted" in State, "Ready to
   review" in Progress) plus the pill's padding and border-radius. The Student
   column gets an explicit minimum so it never collapses to vertical-text
   shape; the table itself gets min-width so the layout stays correct down to
   ~660px viewports — narrower than that the ancestor scroll wrapper takes over.
   Mapping: Student | State | Started | Submitted | Integrity | Progress | Actions */
table.roster--attempts {
	table-layout: fixed;
	min-width: 55rem;
}
table.roster--attempts th:nth-child(1), table.roster--attempts td:nth-child(1) { width: 11rem; }   /* Student */
table.roster--attempts th:nth-child(2), table.roster--attempts td:nth-child(2) { width: 7rem; }    /* State — "submitted" pill */
table.roster--attempts th:nth-child(3), table.roster--attempts td:nth-child(3) { width: 5.5rem; }  /* Started */
table.roster--attempts th:nth-child(4), table.roster--attempts td:nth-child(4) { width: 5.5rem; }  /* Submitted */
table.roster--attempts th:nth-child(5), table.roster--attempts td:nth-child(5) { width: 7.5rem; }  /* Integrity — "mismatch" pill */
table.roster--attempts th:nth-child(6), table.roster--attempts td:nth-child(6) { width: 9.5rem; }  /* Progress — "Ready to review" pill */
/* Actions column needs room for the "Unlock name" / "Approve" / "Review"
   buttons — without an explicit width it collapsed and the button was
   clipped. The released-flag annotation wraps within the cell. */
table.roster--attempts th:nth-child(7), table.roster--attempts td:nth-child(7) { width: 9rem; }    /* Actions */
/* Tighter horizontal cell padding so all seven columns fit ~1024px-wide
   windows without horizontal scroll (Pico's default ~1rem each side adds
   up across 7 columns). */
table.roster--attempts th,
table.roster--attempts td {
	padding-inline: 0.5rem;
}

/* The session page wraps the attempts table in #attempts-panel; below
   roughly tablet width the table needs horizontal scroll rather than
   squashing its columns. */
#attempts-panel {
	overflow-x: auto;
}

table.roster th,
table.roster td {
	vertical-align: middle;
}

/* Pills never break across lines. */
table.roster .status-pill,
table.roster .integrity-pill,
table.roster .stage-pill {
	white-space: nowrap;
}

/* Student-name link wraps as a unit at word boundaries, never per-
   character. */
table.roster td:nth-child(1) a {
	word-break: keep-all;
	overflow-wrap: normal;
}

table.roster .roster-actions {
	width: 9.5rem;
	text-align: right;
	white-space: nowrap;
	overflow: visible;
}

/* Compact row-action buttons — Approve, Unapprove, Review, Unlock.
   Pico's defaults are too chunky for a roster row. */
table.roster .roster-actions button,
table.roster .roster-actions [role="button"] {
	display: inline-block;
	margin: 0 0 0 .25rem;
	padding-block: .15rem;
	padding-inline: .55rem;
	font-size: .8em;
	line-height: 1.4;
}
table.roster .roster-actions form {
	display: inline-block;
	margin: 0 0 0 .25rem;
}

table.roster input[type="text"],
table.roster input[type="number"] {
	margin: 0;
}

table.roster .row-editing input {
	width: 100%;
}

table.roster .acc-edit {
	display: flex;
	gap: .5rem;
	align-items: center;
}

table.roster .acc-edit input {
	max-width: 5rem;
}

/* Borderless row list of links (used for assessments). */

.row-list {
	margin-block: 1rem 1.5rem;
}

.row-list > .row-link {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	padding-block: .65rem;
	border-block-end: 1px solid var(--pico-muted-border-color);
	color: inherit;
	text-decoration: none;
}

.row-list > .row-link:last-child {
	border-block-end: none;
}

.row-list > .row-link:hover {
	background: var(--pico-card-background-color);
}

/* Visual affordance: a › chevron at the end of each row reads as
   "click to drill in." Without this, .row-link rows look like static
   text — same color, no underline, no border distinct from prose. */
.row-list > .row-link::after {
	content: "›";
	color: var(--pico-muted-color);
	font-size: 1.2rem;
	line-height: 1;
	flex-shrink: 0;
	transition: transform 80ms, color 80ms;
}

.row-list > .row-link:hover::after {
	color: var(--pico-primary);
	transform: translateX(2px);
}

.row-list > .row-link:hover {
	color: var(--pico-primary);
}

.session-row-main {
	display: flex;
	flex-direction: column;
	gap: .15rem;
	min-width: 0;
	/* Grow to absorb the row's slack so the status pill + chevron pack to
	   the right edge as a unit — keeps pills in a clean column instead of
	   drifting with each row's content width. */
	flex: 1 1 auto;
}

.session-row-meta {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: .35rem;
	font-size: .85em;
	color: var(--pico-muted-color);
	line-height: 1.4;
}

.session-row-meta code {
	padding: 0 .3em;
	font-size: 1em;
	line-height: 1;
	letter-spacing: .1em;
	vertical-align: baseline;
}

/* Empty wrapper inside the form — only takes space when an error is present. */
#open-session-result:empty {
	display: none;
}
#open-session-result:not(:empty) {
	margin-block: 1rem;
}

/* Home page: two side-by-side entry-point cards. */

.home-paths {
	display: grid;
	gap: 1rem;
	grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
	margin-block-start: 2rem;
	align-items: stretch;
}

/* Equal-height cards with the action(s) pinned to the bottom, so the
   "Join" and "Sign in" buttons line up across cards regardless of how
   much body text each carries. */
.home-path {
	display: flex;
	flex-direction: column;
}
.home-path__actions {
	margin-block-start: auto;
	padding-block-start: .75rem;
	display: flex;
	flex-wrap: wrap;
	gap: .6rem;
}

.home-path__head {
	display: flex;
	align-items: center;
	gap: .6rem;
}
.home-path__icon {
	width: 1.6em;
	height: 1.6em;
	color: var(--mb-accent-text);
	flex-shrink: 0;
}

/* Per-path hero illustration. Flat, multi-tone, accent-driven inline SVG so
   it tracks the brand accent and light/dark like everything else. */
.home-illo-wrap {
	display: flex;
	justify-content: center;
	padding: 1rem 0 1.25rem;
	margin-block-end: .5rem;
	border-radius: var(--pico-border-radius);
	background: color-mix(in srgb, var(--mb-accent) 7%, var(--pico-card-background-color));
}
.home-illo {
	display: block;
	width: 100%;
	max-width: 230px;
	height: auto;
}
.home-illo .il-paper {
	fill: var(--pico-card-background-color);
	stroke: var(--mb-accent-border);
	stroke-width: 2;
}
.home-illo .il-line { fill: var(--mb-accent-subtle); }
.home-illo .il-accent { fill: var(--mb-accent); }
.home-illo .il-accent-soft { fill: color-mix(in srgb, var(--mb-accent) 45%, var(--pico-card-background-color)); }
.home-illo .il-warm { fill: #e0922b; }
.home-illo .il-stroke-accent { fill: none; stroke: var(--mb-accent); stroke-width: 3; stroke-linecap: round; }

/* Student join + roster pages. */

/* Centered, focused single-action join page with a hero illustration. */
.join-page {
	max-width: 30rem;
	margin-inline: auto;
	display: flex;
	flex-direction: column;
	gap: 1.25rem;
	min-height: 70vh;
	justify-content: center;
}

.join-card {
	background: var(--pico-card-background-color);
	border: 1px solid var(--pico-muted-border-color);
	border-radius: var(--pico-border-radius);
	overflow: hidden;
	text-align: center;
}
.join-card__illo {
	display: flex;
	justify-content: center;
	padding: 1.5rem;
	background: color-mix(in srgb, var(--mb-accent) 8%, var(--pico-card-background-color));
	border-block-end: 1px solid var(--mb-accent-border);
}
.join-card__body {
	padding: 1.5rem;
}
.join-card hgroup {
	margin-block-end: 1.25rem;
}

.join-form {
	margin: 0;
}
.join-form label {
	text-align: start;
}
.join-form button[type="submit"] {
	width: 100%;
	margin-block-start: 1rem;
}

.exam-code-input {
	font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
	font-size: 2rem;
	letter-spacing: .3em;
	text-align: center;
	text-transform: uppercase;
}

.roster-pick fieldset {
	border: none;
	padding: 0;
	margin: 0 0 1.5rem 0;
	display: grid;
	gap: .25rem;
}

.student-pick {
	display: flex;
	align-items: center;
	gap: .75rem;
	padding: .75rem 1rem;
	border: 1px solid var(--pico-muted-border-color);
	border-radius: var(--pico-border-radius);
	cursor: pointer;
	transition: background-color 80ms, border-color 80ms;
	margin: 0;
}

.student-pick:hover {
	background: var(--pico-card-background-color);
}

.student-pick input[type="radio"] {
	margin: 0;
}

.student-pick:has(input:checked) {
	border-color: var(--pico-primary);
	background: var(--pico-primary-focus);
}

.student-pick-taken {
	opacity: .65;
}

.student-pick-taken small {
	margin-inline-start: auto;
}

/* Exam taker shell */

.exam-shell {
	margin-block-start: 1rem;
}

.exam-header {
	position: sticky;
	top: 0;
	z-index: 2;
	display: flex;
	justify-content: space-between;
	align-items: center;
	gap: .5rem 1rem;
	flex-wrap: wrap;
	background: var(--pico-background-color);
	padding-block: .5rem;
}

.exam-header-actions {
	display: flex;
	align-items: center;
	gap: .75rem;
	flex-wrap: wrap;
}

/* Header utility buttons (Re-enter fullscreen, Submit now) share one
   compact, filled style so they read as a matching pair. A shared min-width
   (sized for the longest label) keeps them identical and stops the fullscreen
   button resizing as its label toggles between "Fullscreen" and
   "Re-enter fullscreen". */
.exam-header-actions button {
	margin: 0;
	padding-block: .35rem;
	font-size: .9em;
	white-space: nowrap;
	min-width: 9.5rem;
	justify-content: center;
}

/* Compact integrity status row above the writing area. Three pills:
   green when good, red when in a bad state, yellow when an ambiguous
   tampering signal fires. Tone is "calm reassurance" — the colours
   are saturated enough to be glanceable but the type stays small. */
.exam-integrity-status {
	margin-block-end: .9rem;
	padding-block-end: .5rem;
	border-block-end: 1px dashed var(--pico-muted-border-color);
}

.exam-integrity-pills {
	display: flex;
	flex-wrap: wrap;
	gap: .5rem;
	list-style: none;
	margin: 0;
	padding: 0;
}

.exam-integrity-pills > li {
	display: inline-flex;
	align-items: center;
	gap: .35rem;
	padding: .15rem .55rem;
	border-radius: 1rem;
	font-size: .82em;
	border: 1px solid transparent;
}

.exam-integrity-pills > li.is-ok {
	background: color-mix(in srgb, #2ecc71 14%, transparent);
	color: #1d7a48;
	border-color: color-mix(in srgb, #2ecc71 40%, transparent);
}

.exam-integrity-pills > li.is-bad {
	background: color-mix(in srgb, #e74c3c 14%, transparent);
	color: #b1322a;
	border-color: color-mix(in srgb, #e74c3c 40%, transparent);
}

.exam-integrity-pills > li.is-warn {
	background: color-mix(in srgb, #f1c40f 18%, transparent);
	color: #8a6500;
	border-color: color-mix(in srgb, #f1c40f 50%, transparent);
}

@media (prefers-color-scheme: dark) {
	.exam-integrity-pills > li.is-ok { color: #6be29c; }
	.exam-integrity-pills > li.is-bad { color: #f08a82; }
	.exam-integrity-pills > li.is-warn { color: #f3d97a; }
}

.exam-integrity-pill__icon {
	font-weight: 700;
}

/* Inline SVG icons rendered by the macros in _macros.html.
   currentColor stroking + 1em sizing means an icon adopts the colour
   and size of the surrounding text. Use the *-md/-lg modifiers when
   you need an obviously larger glyph (banner heads, projector use). */
.mb-icon {
	display: inline-block;
	width: 1em;
	height: 1em;
	vertical-align: -0.125em;  /* visually centre against the text baseline */
	flex-shrink: 0;
}
.mb-icon--md { width: 1.25em; height: 1.25em; }
.mb-icon--lg { width: 1.5em; height: 1.5em; }

/* Verification-badge pill in the integrity row. Clickable; clicking
   opens the big-badge modal. The emoji inline-display stays compact
   so the row doesn't reflow when the badge loads. */
.exam-integrity-pills > li.exam-integrity-pills__badge {
	cursor: pointer;
	user-select: none;
}
.exam-integrity-pills > li.exam-integrity-pills__badge:hover {
	filter: brightness(1.05);
}
.exam-integrity-pills > li.exam-integrity-pills__badge:focus-visible {
	outline: 2px solid var(--pico-primary);
	outline-offset: 2px;
}

.exam-badge__emojis-inline {
	/* Constrain so emojis line up with adjacent text and don't shift
	   the row height on first render. */
	font-size: 1em;
	letter-spacing: .15em;
}

/* Big-badge display in the precheck overlay. The visible verification
   layer: every screen in the room should show the same pair, so peer
   verification works at a glance. See docs/INTEGRITY.md §2a. */
.exam-badge {
	margin-block-start: 1.5rem;
	padding: 1.25rem;
	border-radius: var(--pico-border-radius);
	text-align: center;
}
.exam-badge--big {
	background: color-mix(in srgb, var(--pico-primary) 8%, transparent);
	border: 1px solid color-mix(in srgb, var(--pico-primary) 30%, transparent);
}
.exam-badge--missing {
	background: color-mix(in srgb, var(--pico-color-red-500, #c0392b) 12%, transparent);
	border: 1px solid color-mix(in srgb, var(--pico-color-red-500, #c0392b) 50%, transparent);
}
.exam-badge--missing p { margin: .35rem 0 0; }
.exam-badge__label {
	font-size: .9em;
	color: var(--pico-muted-color);
	margin-block-end: .4rem;
}
.exam-badge__emojis {
	/* Very large emoji display so a teacher can read it from across
	   the room. Letter-spacing keeps the pair visually distinct. */
	font-size: 4.5rem;
	line-height: 1.1;
	letter-spacing: .15em;
}
.exam-badge__help {
	margin: .6rem 0 0;
	color: var(--pico-muted-color);
}

/* Modal opened by clicking the badge pill during writing. Same emoji
   size as the precheck big-display so the teacher's visual target is
   identical across the exam's phases. */
.exam-badge-modal {
	position: fixed;
	inset: 0;
	background: color-mix(in srgb, #000 60%, transparent);
	display: flex;
	align-items: center;
	justify-content: center;
	z-index: 1000;
	padding: 1rem;
}
.exam-badge-modal__panel {
	background: var(--pico-card-background-color);
	border-radius: var(--pico-border-radius);
	padding: 2rem;
	max-width: 30rem;
	width: 100%;
	text-align: center;
	box-shadow: 0 10px 40px rgba(0, 0, 0, .3);
}
.exam-badge-modal__label {
	margin: 0 0 .5rem;
	font-size: 1.1rem;
	color: var(--pico-muted-color);
}
.exam-badge-modal__emojis {
	font-size: 6rem;
	line-height: 1.1;
	letter-spacing: .15em;
	margin-block: 1rem;
}

.exam-integrity-status__note {
	margin: .35rem 0 0;
	color: var(--pico-muted-color);
}

.exam-timer {
	font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
	font-size: 1.4em;
	font-weight: 700;
	color: var(--pico-primary);
}

/* Resume / I'm ready button on the pre-start overlays. Sat directly
   below the badge/requirements with no breathing room — the gap below
   the help text plus the button margin reads as cramped. */
.exam-gate-action {
	margin-block-start: 1.5rem;
}

.exam-overlay {
	background: var(--pico-card-background-color);
	border: 1px solid var(--pico-muted-border-color);
	border-radius: var(--pico-border-radius);
	padding: 1.5rem;
	margin-block: 1.5rem;
}

.exam-overlay-warning {
	border-color: var(--pico-color-red-500, #c0392b);
	/* Translucent red tint reads correctly on both light and dark backgrounds. */
	background: color-mix(in srgb, var(--pico-color-red-500, #c0392b) 12%, transparent);
}

.exam-shell textarea {
	font-family: Georgia, "Times New Roman", serif;
	font-size: 1.05rem;
	line-height: 1.55;
}

/* Side-by-side prompt + response. Prompt on left in its own
   scrollable container; response on right. Collapses to single
   column under 900px so phones/narrow viewports get a stacked
   layout (prompt above response). */
.exam-question-grid {
	display: grid;
	gap: 1.25rem;
	grid-template-columns: minmax(0, 1fr);
	margin-block: 1rem;
}

@media (min-width: 900px) {
	.exam-question-grid {
		/* Prompt narrower than response — students spend more time
		   typing than reading, but the prompt still needs enough
		   width for poetry/passages to read naturally. */
		grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
	}
}

.exam-prompt-pane {
	display: flex;
	flex-direction: column;
	min-height: 0;
	background: var(--pico-card-background-color);
	border: 1px solid var(--pico-muted-border-color);
	border-radius: var(--pico-border-radius);
	padding: .85rem 1rem;
}

.exam-prompt-toolbar {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: .5rem;
	margin-block-end: .55rem;
	padding-block-end: .55rem;
	border-block-end: 1px solid var(--pico-muted-border-color);
}

.exam-prompt-toolbar h3 {
	margin: 0;
	font-size: .9rem;
	text-transform: uppercase;
	letter-spacing: .04em;
	color: var(--pico-muted-color);
}

.exam-prompt-toolbar button {
	margin: 0;
	padding: .25rem .65rem;
	font-size: .85rem;
	display: inline-flex;
	align-items: center;
	gap: .35rem;
}

.exam-prompt-toolbar button .highlight-icon {
	font-size: 1.05em;
	line-height: 1;
}

/* Toggled-on state: visually distinct so the student knows the
   drag-to-highlight gesture is active. */
.exam-prompt-toolbar button.is-on {
	background: var(--pico-primary);
	color: var(--pico-primary-inverse, white);
	border-color: var(--pico-primary);
}

/* The scrollable container for the prompt body. max-height keeps
   it bounded; the response pane stays alongside. The serif body
   font matches the response textarea for visual rhythm. */
.exam-prompt-body {
	flex: 1 1 auto;
	min-height: 0;
	max-height: 65vh;
	overflow-y: auto;
	padding-right: .25rem;  /* breathing room for the scrollbar */
	font-family: Georgia, "Times New Roman", serif;
	font-size: 1.02rem;
	line-height: 1.6;
	/* Preserve teacher's intended line breaks — important for
	   poems and quoted passages. */
	white-space: pre-wrap;
	word-wrap: break-word;
	/* User-select stays on — selection is the highlight gesture. */
	user-select: text;
}

.exam-prompt-body.is-highlight-mode {
	/* No background tint on the prompt body — the toggle button's
	   own primary-coloured state is the affordance. Cursor changes
	   to text-select so the drag gesture reads. */
	cursor: text;
}

.prompt-highlight {
	/* Yellow highlight with EXPLICIT dark text so it stays readable
	   in dark mode (where inherited color would be near-white on
	   yellow — unreadable). The hard-coded #1a1300 keeps contrast
	   regardless of theme. */
	background: #fff59d;
	color: #1a1300;
	padding: 0 1px;
	border-radius: 2px;
}

.exam-prompt-body.is-highlight-mode .prompt-highlight {
	/* In highlight mode, marks are clickable to remove — show it. */
	cursor: pointer;
	outline: 1px dashed transparent;
}

.exam-prompt-body.is-highlight-mode .prompt-highlight:hover {
	outline-color: var(--pico-color-red-500, #c0392b);
}

.exam-response-pane {
	display: flex;
	flex-direction: column;
	min-height: 0;
}

.exam-response-pane textarea {
	flex: 1 1 auto;
	min-height: 22rem;
}

.exam-response-pane footer {
	display: flex;
	justify-content: space-between;
	align-items: center;
	margin-block-start: .35rem;
}

/* Inline "link-like" submit button. */
button.link-like {
	background: none;
	border: none;
	padding: 0;
	margin: 0;
	color: var(--pico-primary);
	text-decoration: underline;
	cursor: pointer;
	font: inherit;
	display: inline;
}
button.link-like:hover { color: var(--pico-primary-hover); }

/* Integrity pill — same shape as status-pill but colours by integrity state. */

.integrity-pill {
	display: inline-block;
	padding: .1rem .5rem;
	border-radius: 1rem;
	font-size: .75em;
	text-transform: uppercase;
	letter-spacing: .05em;
}
.integrity-verified {
	background: var(--pico-color-green-100, #e8f8f0);
	color: var(--pico-color-green-700, #1e8449);
}
.integrity-review {
	background: var(--pico-color-amber-100, #fff4d6);
	color: var(--pico-color-amber-800, #8a6300);
}
.integrity-mismatch {
	background: var(--pico-color-red-100, #fdecea);
	color: var(--pico-color-red-700, #a93226);
}
.integrity-pending,
.integrity-queued,
.integrity-running {
	background: var(--pico-muted-border-color);
	color: var(--pico-muted-color);
}
.integrity-error {
	background: var(--pico-color-red-100, #fdecea);
	color: var(--pico-color-red-700, #a93226);
}

/* Plain-English findings list — sits above the technical grid. Each
   item gets a coloured severity stripe on the left so the eye can scan
   "what's actually wrong" without reading every word. The high/medium/
   info palette mirrors the student-side integrity pills so the visual
   language is consistent across teacher + student pages. */
.integrity-findings {
	margin-block: 1rem 1.5rem;
}

.integrity-findings > h2 {
	margin-block-end: .65rem;
}

.integrity-findings__list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: .55rem;
}

.integrity-finding {
	padding: .65rem .85rem .65rem 1rem;
	border-radius: var(--pico-border-radius);
	border-left: 4px solid transparent;
	background: var(--pico-card-background-color);
}

.integrity-finding--high {
	border-left-color: #e74c3c;
	background: color-mix(in srgb, #e74c3c 7%, var(--pico-card-background-color));
}

.integrity-finding--medium {
	border-left-color: #f1c40f;
	background: color-mix(in srgb, #f1c40f 8%, var(--pico-card-background-color));
}

.integrity-finding--info {
	border-left-color: #2ecc71;
}

.integrity-finding__title {
	display: flex;
	gap: .6rem;
	align-items: baseline;
	line-height: 1.45;
}

.integrity-finding__sev {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 1.3rem;
	height: 1.3rem;
	border-radius: 50%;
	font-size: .85em;
	font-weight: 700;
	flex-shrink: 0;
	background: var(--pico-background-color);
}

.integrity-finding--high .integrity-finding__sev { color: #c0392b; }
.integrity-finding--medium .integrity-finding__sev { color: #8a6500; }
.integrity-finding--info .integrity-finding__sev { color: #1d7a48; }

.integrity-finding__details {
	margin-block-start: .55rem;
	font-size: .92em;
}

.integrity-finding__details > summary {
	cursor: pointer;
	color: var(--pico-muted-color);
}

.integrity-finding__detail {
	margin-block: .5rem 0;
	padding: .45rem .6rem;
	border: 1px solid var(--pico-muted-border-color);
	border-radius: var(--pico-border-radius);
	background: var(--pico-background-color);
}

.integrity-finding__content {
	margin: .25rem 0 0;
	white-space: pre-wrap;
	word-wrap: break-word;
	font-family: inherit;
	font-size: .9em;
	color: var(--pico-muted-color);
}

.integrity-card {
	margin-block: 1rem 1.5rem;
}

.integrity-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
	gap: .5rem 1.5rem;
	font-size: .95em;
}

.response-block {
	margin-block-end: 2rem;
}

.response-prompt {
	color: var(--pico-muted-color);
	white-space: pre-wrap;
	margin-block: .5rem 1rem;
}

.response-text, .deadline-snapshot, .version-text {
	background: var(--pico-card-background-color);
	border: 1px solid var(--pico-muted-border-color);
	border-radius: var(--pico-border-radius);
	padding: .9rem 1.1rem;
	white-space: pre-wrap;
	font-family: Georgia, "Times New Roman", serif;
	font-size: 1.02rem;
	line-height: 1.55;
}

.version-list {
	list-style: none;
	padding-left: 0;
	margin: 0;
}

.version-list > li {
	border-block-end: 1px solid var(--pico-muted-border-color);
	padding-block: .75rem;
}

.version-list > li.post-deadline {
	background: var(--pico-color-amber-50, #fffbed);
}

.version-meta {
	display: flex;
	gap: .5rem;
	align-items: center;
	color: var(--pico-muted-color);
	font-size: .9em;
	margin-block-end: .25rem;
}

.post-deadline-flag {
	background: var(--pico-color-amber-200, #ffe9a8);
	color: var(--pico-color-amber-800, #8a6300);
	padding: .05rem .4rem;
	border-radius: .25rem;
	font-size: .85em;
}

.version-promote { margin-block-start: .5rem; }

/* Student confirmation page */

.confirm-card {
	text-align: center;
	padding: 2rem 1rem;
	margin-block: 1.5rem;
}

.confirm-card .confirm-name {
	font-size: clamp(1.75rem, 5vw, 2.5rem);
	font-weight: 700;
	margin-block: .5rem;
}

.confirm-actions {
	display: flex;
	gap: .75rem;
	align-items: center;
	margin-block-start: 1.5rem;
}

/* Big-display join code + verification badge for the teacher session
   page — designed to project to the room. The two cards sit side by
   side on wide screens, stack on narrow ones. See docs/INTEGRITY.md §2a. */

.session-projector {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
	gap: 1rem;
	margin-block: 1rem;
}
.session-projector > article {
	margin: 0;
}

.session-badge-card {
	text-align: center;
	padding: 2rem 1rem;
}
.session-badge-card__emojis {
	/* Same scale as the join code so the two cards balance visually
	   on the projector. clamp() so they shrink gracefully on a teacher's
	   laptop screen but stay massive when projected. */
	font-size: clamp(2.5rem, 8vw, 5rem);
	line-height: 1.1;
	letter-spacing: .15em;
	margin-block: .5rem;
}

.join-code-card {
	text-align: center;
	padding: 2rem 1rem;
}

.session-live-summary {
	margin-block: 0 .75rem;
	color: var(--pico-muted-color);
}

.join-code-card .join-code {
	font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
	font-size: clamp(2.5rem, 8vw, 5rem);
	letter-spacing: .2em;
	font-weight: 700;
	margin-block: .5rem;
}

/* Inline validation message (e.g. open-session form). */

.error-msg {
	margin: 0;
	padding: .65rem .9rem;
	border: 1px solid var(--pico-color-red-500, #c0392b);
	border-radius: var(--pico-border-radius);
	background: color-mix(in srgb, var(--pico-color-red-500, #c0392b) 12%, transparent);
	color: var(--pico-color-red-700, #a93226);
	font-size: .95em;
}

/* Pronounced flash to draw the eye to a freshly added row. */

@keyframes row-highlight {
	0% {
		background: var(--pico-primary-focus, #fff3cd);
		box-shadow: inset 0 0 0 2px var(--pico-primary, #2c87f0);
	}
	60% {
		background: var(--pico-primary-focus, #fff3cd);
		box-shadow: inset 0 0 0 2px var(--pico-primary, #2c87f0);
	}
	100% {
		background: transparent;
		box-shadow: inset 0 0 0 0 transparent;
	}
}

.row-link.highlight-new {
	animation: row-highlight 4s ease-out;
	border-radius: var(--pico-border-radius);
}

/* Status pill */

.status-pill {
	display: inline-block;
	padding: .1rem .5rem;
	border-radius: 1rem;
	font-size: .75em;
	text-transform: uppercase;
	letter-spacing: .05em;
	margin-inline-start: .5rem;
	white-space: nowrap;
}

/* Assessment status pills share the same adaptive hue formula as the
   sitting pills (see .sitting-pill below): amber = drafting, green = ready
   to run, slate = archived. */
.status-pill.status-draft { --pill-hue: #d97706; }
.status-pill.status-ready { --pill-hue: #16a34a; }
.status-pill.status-archived { --pill-hue: #64748b; }

.status-pill.status-released {
	background: var(--pico-color-green-100, #e8f8f0);
	color: var(--pico-color-green-700, #1e8449);
}

.status-pill.status-released-edits {
	background: var(--pico-color-amber-100, #fff4d6);
	color: var(--pico-color-amber-800, #8a6300);
}

/* Sitting lifecycle pills — a small semantic colour set so the stages read
   at a glance instead of a wall of grey. Each status sets a --pill-hue and
   one shared formula derives a tint + readable text that adapt to light and
   dark automatically (the text mixes toward --pico-color, which flips per
   mode; the fill mixes toward the page background). */
.sitting-pill,
.status-pill.status-draft,
.status-pill.status-ready,
.status-pill.status-archived {
	background: color-mix(in srgb, var(--pill-hue) 16%, var(--pico-background-color));
	color: color-mix(in srgb, var(--pill-hue) 78%, var(--pico-color));
}
.sitting-pill--scheduled    { --pill-hue: #6366f1; }  /* indigo — upcoming */
.sitting-pill--on_now       { --pill-hue: #16a34a; }  /* green — live */
.sitting-pill--to_mark      { --pill-hue: #d97706; }  /* amber — needs you */
.sitting-pill--being_marked { --pill-hue: #2563eb; }  /* blue — working */
.sitting-pill--to_hand_back { --pill-hue: #0d9488; }  /* teal — ready */
.sitting-pill--handed_back  { --pill-hue: #64748b; }  /* slate — done */
.sitting-pill--ended        { --pill-hue: #64748b; }  /* slate — over */

/* Prev/Next pager on the attempt-detail page so teachers can sit
   down and walk through every student without bouncing back to the
   session page each time. */
.attempt-pager {
	display: flex;
	gap: .75rem;
	align-items: center;
	justify-content: space-between;
	margin-block: .25rem 1rem;
	flex-wrap: wrap;
}
.attempt-pager > [role="button"] {
	margin: 0;
}
.attempt-pager > [role="button"][aria-disabled="true"] {
	opacity: .45;
	pointer-events: none;
}
.attempt-pager__counter {
	color: var(--pico-muted-color);
	white-space: nowrap;
}

/* Per-attempt release action — sits above the marking review section */
.release-action {
	margin-block-end: 1rem;
}
.release-action [role="group"] {
	display: flex;
	gap: .5rem;
	flex-wrap: wrap;
}

/* Teacher inbox (cross-session) on /teacher/me */
.inbox {
	display: flex;
	flex-direction: column;
	gap: 1rem;
	margin-block: 1.5rem 2.5rem;
}
.inbox-bucket {
	margin: 0;
	border-inline-start: 4px solid var(--bucket-hue, var(--pico-muted-border-color));
	padding-inline-start: .85rem;
}
.inbox-bucket > header {
	margin-block-end: .5rem;
}
.inbox-bucket > header h2 {
	margin: 0;
	font-size: 1.1rem;
	color: var(--bucket-hue, inherit);
}
.inbox-bucket > header p {
	margin: 0;
	font-size: .9em;
}
.inbox-list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: .35rem;
}
.inbox-list li > a {
	display: grid;
	grid-template-columns: 1fr auto;
	grid-template-areas:
		"title counts"
		"where counts";
	gap: .15rem .75rem;
	align-items: baseline;
	padding: .6rem .75rem;
	border-radius: .4rem;
	text-decoration: none;
	color: inherit;
	background: var(--pico-card-background-color);
	border: 1px solid var(--pico-card-border-color);
}
.inbox-list li > a:hover {
	border-color: var(--pico-primary);
	background: color-mix(in srgb, var(--pico-primary) 5%, var(--pico-card-background-color));
}
.inbox-row__title { grid-area: title; font-weight: 600; }
.inbox-row__where { grid-area: where; font-size: .85em; color: var(--pico-muted-color); }
.inbox-row__counts {
	grid-area: counts;
	font-size: .9em;
	text-align: right;
	white-space: nowrap;
}
.inbox-row__counts.muted { color: var(--pico-muted-color); }
/* Per-bucket semantic hue drives both the left stripe and the heading,
   aligned with the sitting-pill colours (amber=approve, teal=hand back,
   blue=marking, green=on now, slate=done). */
.inbox-bucket--awaiting { --bucket-hue: #d97706; }
.inbox-bucket--release  { --bucket-hue: #0d9488; }
.inbox-bucket--marking  { --bucket-hue: #2563eb; }
.inbox-bucket--live     { --bucket-hue: #16a34a; }
.inbox-bucket--done     { --bucket-hue: #64748b; }

/* Approval ribbon — counts + bulk approve sits above the attempts table */
.approval-ribbon {
	display: flex;
	gap: 1rem;
	align-items: center;
	justify-content: space-between;
	flex-wrap: wrap;
	margin-block: 1rem;
	padding: .75rem 1rem;
}
.approval-counts {
	display: flex;
	gap: .5rem;
	align-items: baseline;
	flex-wrap: wrap;
	font-size: .95em;
}
.approval-counts .sep { color: var(--pico-muted-color); }
.approval-counts .flagged-count strong {
	color: var(--pico-color-amber-800, #8a6300);
}
.approval-counts .released-count strong {
	color: var(--pico-color-green-700, #1e8449);
}

/* Progress-stage pill on the session attempts table. Each stage gets
   its own colour so a teacher can scan the column at a glance — the
   labels are named after the *next* action they need to take
   (rather than the past state). */
.stage-pill {
	display: inline-block;
	padding: .1rem .55rem;
	border-radius: 1rem;
	font-size: .75em;
	text-transform: uppercase;
	letter-spacing: .04em;
	white-space: nowrap;
	border: 1px solid transparent;
}
.stage-pill.stage-needs-review {
	/* Action needed: amber, like the inbox "Awaiting approval" bucket. */
	background: var(--pico-color-amber-100, #fff4d6);
	color: var(--pico-color-amber-800, #8a6300);
	border-color: var(--pico-color-amber-300, #f0c970);
}
.stage-pill.stage-ready-to-mark {
	/* System action pending the teacher's click: muted blue. */
	background: var(--pico-color-azure-100, #e8f1fb);
	color: var(--pico-color-azure-700, #1f5aa6);
}
.stage-pill.stage-marking {
	/* In-flight: a slightly more vivid blue + subtle pulse to read as
	   "happening right now". */
	background: var(--pico-color-azure-200, #cfe1f5);
	color: var(--pico-color-azure-800, #154178);
	border-color: var(--pico-color-azure-400, #6196d3);
	animation: stage-pulse 1.6s ease-in-out infinite;
}
@keyframes stage-pulse {
	0%, 100% { opacity: 1; }
	50%      { opacity: .55; }
}
.stage-pill.stage-ready-to-review {
	/* Teacher action needed again — amber, matches "Needs review" tone. */
	background: var(--pico-color-amber-100, #fff4d6);
	color: var(--pico-color-amber-800, #8a6300);
	border-color: var(--pico-color-amber-300, #f0c970);
}
.stage-pill.stage-released {
	/* Done: green, like the integrity 'verified' pill. */
	background: var(--pico-color-green-100, #e8f8f0);
	color: var(--pico-color-green-700, #1e8449);
	border-color: var(--pico-color-green-300, #87d3a2);
}
.approval-ribbon form { margin: 0; }

/* Questions table */

table.questions {
	table-layout: fixed;
}

table.questions th,
table.questions td {
	vertical-align: top;
}

table.questions .ordinal-col {
	width: 2.5rem;
	text-align: right;
}

table.questions .words-col,
table.questions .time-col {
	width: 7rem;
	text-align: right;
}

table.questions .prompt-preview {
	white-space: pre-wrap;
	font-size: .95em;
}

table.questions .roster-actions {
	width: 14rem;
	text-align: right;
}

table.questions .roster-actions button,
table.questions .roster-actions form {
	display: inline-block;
	margin: 0 0 0 .25rem;
	padding-block: .25rem;
	font-size: .85em;
}

table.questions textarea,
table.questions input {
	margin: 0;
	width: 100%;
}

/* Criteria section */

#criteria-section {
	margin-block: 2rem;
}

.criteria-list {
	margin-block: .5rem 1rem;
}

.criteria-list .descriptor {
	margin-left: .25rem;
	color: var(--pico-muted-color);
}

/* Card layout for criteria on the assessment editor — mirrors the
   marking-review per-criterion cards so the descriptor has full row
   width instead of being squeezed into a table cell. */
.criterion-row--readonly {
	padding: .75rem 1rem;
	border: 1px solid var(--pico-muted-border-color);
	border-radius: var(--pico-border-radius);
	background: color-mix(in srgb, var(--pico-card-background-color) 50%, transparent);
}
.criterion-row--readonly .criterion-name {
	font-weight: 600;
}
.criterion-descriptor-readonly {
	margin-block-start: .4rem;
	color: var(--pico-muted-color);
	line-height: 1.5;
}

.criteria-rows--edit .criterion-name-input {
	margin: 0;
	font-weight: 600;
	max-width: 28rem;
}
.criteria-rows--edit .criterion-descriptor-input {
	margin-block-start: .4rem;
	margin-block-end: 0;
	width: 100%;
	min-height: 4rem;
	line-height: 1.5;
}
.criteria-rows--edit .criterion-score-input {
	width: 5rem;
	min-width: 5rem;
	max-width: 5rem;
	flex: 0 0 5rem;
	margin: 0;
	text-align: right;
}
.criteria-rows--edit .criterion-score-cell {
	display: inline-flex;
	align-items: baseline;
	gap: .4rem;
	white-space: nowrap;
	flex-shrink: 0;
}
.criteria-rows--edit .criterion-row-head {
	display: flex;
	justify-content: space-between;
	align-items: center;
	gap: 1rem;
}

table.criteria {
	table-layout: fixed;
}

table.criteria th,
table.criteria td {
	vertical-align: top;
}

table.criteria textarea,
table.criteria input {
	margin: 0;
	width: 100%;
}

table.criteria .words-col {
	width: 5rem;
}

.row-actions {
	display: flex;
	gap: .5rem;
	flex-wrap: wrap;
	align-items: center;
	margin-block: .5rem 1rem;
}

.row-actions > * {
	margin: 0;
}

.htmx-indicator {
	display: none;
}

.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator {
	display: inline-block;
}

article.error {
	border-color: var(--pico-color-red-500, #c33);
	background-color: color-mix(in srgb, var(--pico-color-red-500, #c33) 12%, transparent);
}

/* Widen the criteria 'Max' column so the input value is readable. */
table.criteria .words-col {
	width: 6.5rem;
}

table.criteria .words-col input[type="number"] {
	min-width: 4rem;
}

/* Section separators on the assessment editor page */
.assessment-section {
	padding-block: 1.5rem;
}

.assessment-section + .assessment-section {
	border-block-start: 1px solid var(--pico-muted-border-color);
}

.assessment-section > h2:first-child {
	margin-block-start: 0;
}

/* Teacher's integrity-events drill-down */

.integrity-events {
	margin-block: 1rem 1.5rem;
}

table.integrity-events-table {
	font-size: .9em;
}

table.integrity-events-table .seq-col {
	width: 3.5rem;
	text-align: right;
}

table.integrity-events-table .time-col {
	width: 7rem;
	white-space: nowrap;
}

table.integrity-events-table tr.event-sev-high {
	background: color-mix(in srgb, var(--pico-color-red-500, #c0392b) 14%, transparent);
}

table.integrity-events-table tr.event-sev-medium {
	background: color-mix(in srgb, var(--pico-color-amber-500, #d4951d) 14%, transparent);
}

/* Soft inline warning shown to the student during writing when a
   tamper-style signal is active. Distinct from .exam-overlay-warning
   (which fully locks the editor for the wifi-back case). */
.exam-integrity-warning {
	margin-block: 1rem;
	padding: .85rem 1rem;
	border-radius: var(--pico-border-radius);
	border: 1px solid var(--pico-color-amber-500, #d4951d);
	background: color-mix(in srgb, var(--pico-color-amber-500, #d4951d) 14%, transparent);
	font-size: .95em;
	line-height: 1.45;
}

.exam-integrity-warning strong {
	display: block;
	margin-block-end: .15rem;
}

/* Student-side pre-exam explainer + post-submit response copy. */

.exam-requirements {
	margin-block: 1rem;
	padding: 1rem 1.1rem;
	border: 1px solid var(--pico-muted-border-color);
	border-radius: var(--pico-border-radius);
	background: var(--pico-card-background-color);
}

.exam-requirements > h3 {
	margin-block: 0 .55rem;
	font-size: 1.05rem;
}

.exam-requirements__reassurance {
	margin-block: .85rem 0;
	color: var(--pico-muted-color);
	font-size: .92em;
}

.exam-requirements details {
	margin-block-start: .85rem;
}

.exam-explainer {
	margin-block: .75rem 0;
	padding-inline-start: 1.25rem;
	line-height: 1.5;
}

.exam-explainer li {
	margin-block: .35rem;
}

.submitted-response {
	margin-block: 1rem;
}

.submitted-response .response-prompt {
	margin-block-end: .5rem;
	color: var(--pico-muted-color);
	font-style: italic;
}

pre.submitted-text {
	white-space: pre-wrap;
	word-break: break-word;
	background: var(--pico-card-background-color);
	padding: 1rem;
	border-radius: var(--pico-border-radius);
	font-family: Georgia, "Times New Roman", serif;
	font-size: 1.05rem;
	line-height: 1.55;
	margin: 0;
}

.copy-status {
	margin-block: .5rem 0;
	min-height: 1.2em;
	font-size: .9em;
	color: var(--pico-muted-color);
}

/* Make the integrity pill clickable to the per-attempt drill-down. */
a.attempt-link {
	display: inline-block;
	text-decoration: none;
	color: inherit;
}

a.attempt-link:hover .integrity-pill {
	filter: brightness(.92);
}

/* Whole-row click affordance for list tables. Paired with the
   tr.row-clickable[data-href] JS handler in layout.html. The inner cell
   <a> still works for keyboard / screen-reader users; clicks elsewhere
   in the row navigate via the delegated handler. */
tr.row-clickable {
	cursor: pointer;
	transition: background-color 80ms;
}

tr.row-clickable:hover {
	background: var(--pico-card-background-color);
}

/* Sticky bright wifi-on prompt during the post-write window. Sits above
   everything in the exam shell so the student can't miss it while their
   work waits to send. */
.exam-wifi-banner {
	position: sticky;
	top: 0;
	z-index: 10;
	margin-block-end: 1rem;
	padding: 1rem 1.25rem;
	border-radius: var(--pico-border-radius);
	background: #f59e0b;     /* amber-500 — readable on light & dark */
	color: #1f1300;
	font-size: 1.05rem;
	line-height: 1.45;
	box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
	display: flex;
	flex-direction: column;
	gap: .25rem;
	animation: examWifiBannerPulse 1.6s ease-in-out infinite;
}

.exam-wifi-banner strong {
	font-size: 1.15rem;
}

@keyframes examWifiBannerPulse {
	0%, 100% { box-shadow: 0 4px 12px rgba(245, 158, 11, 0.35); }
	50%      { box-shadow: 0 4px 24px rgba(245, 158, 11, 0.7); }
}

@media (prefers-reduced-motion: reduce) {
	.exam-wifi-banner { animation: none; }
}

/* Attempt-detail page tabs */

.attempt-tabs {
	margin-block-start: 1rem;
}

.attempt-tabs [x-cloak] { display: none !important; }

.tab-bar {
	display: flex;
	gap: .25rem;
	border-block-end: 1px solid var(--pico-muted-border-color);
	margin-block-end: 1rem;
}

.tab-bar button {
	margin: 0;
	padding: .5rem 1rem;
	background: transparent;
	/* Pico v2 buttons set --pico-color: var(--pico-primary-inverse) (white)
	   inside their own scope, so `color: var(--pico-color)` evaluates to
	   white-on-transparent. `inherit` falls back to the page's body color. */
	color: inherit;
	border: 1px solid transparent;
	border-block-end: none;
	border-radius: var(--pico-border-radius) var(--pico-border-radius) 0 0;
	font-weight: 500;
	cursor: pointer;
}

.tab-bar button:hover {
	background: var(--pico-card-background-color);
}

.tab-bar button.active {
	background: var(--pico-card-background-color);
	border-color: var(--pico-muted-border-color);
	border-block-end-color: var(--pico-card-background-color);
	margin-block-end: -1px;
}

.tab-flag {
	display: inline-block;
	width: 1.25em;
	height: 1.25em;
	margin-inline-start: .35em;
	border-radius: 50%;
	background: var(--pico-color-red-500, #c0392b);
	color: white;
	text-align: center;
	line-height: 1.25em;
	font-weight: 700;
	font-size: .85em;
	vertical-align: middle;
}

/* Marking review — full-width marking form, response shown in a slide-in
   drawer on demand (opened by per-question "View response" or per-quote
   "Show in response" buttons). On wide viewports a sticky TOC sidebar
   appears alongside; on narrower viewports the inline jump-nav at the top
   handles navigation instead. */

.marking-review-layout {
	display: flex;
	align-items: flex-start;
	gap: 1.75rem;
}

.marking-review-root {
	position: relative;
	flex: 1;
	min-width: 0;
}

.marking-toc {
	display: none;
	/* Slightly wider so common section labels ("Conventions of evidence",
	   "Structural expectations", "Level expectations") fit on one line. */
	flex: 0 0 230px;
	position: sticky;
	top: 1.25rem;
	/* No inner scroll: let the sidebar grow with content and rely on
	   the page scroll. position: sticky keeps the head of the TOC
	   anchored until the sidebar's bottom edge passes the viewport,
	   then it scrolls out naturally with the page. */
	padding: .85rem .9rem;
	background: var(--pico-card-background-color);
	border: 1px solid var(--pico-muted-border-color);
	border-radius: var(--pico-border-radius);
	font-size: .85em;
}

@media (min-width: 1200px) {
	.marking-toc {
		display: block;
	}
}

.marking-toc h4 {
	margin: 0 0 .65rem 0;
	font-size: .82em;
	text-transform: uppercase;
	letter-spacing: .04em;
	color: var(--pico-muted-color);
}

.marking-toc nav {
	margin: 0;
}

.marking-toc .toc-list,
.marking-toc .toc-sublist {
	list-style: none;
	padding-left: 0;
	margin: 0;
}

.marking-toc .toc-list > .toc-question + .toc-question {
	margin-block-start: .4rem;
	padding-block-start: .35rem;
	border-block-start: 1px solid var(--pico-muted-border-color);
}

.marking-toc .toc-sublist {
	padding-left: .55rem;
	margin-block: .2rem .25rem;
}

.marking-toc li {
	margin-block: .1rem;
}

.marking-toc a {
	display: block;
	padding: .25rem .5rem;
	text-decoration: none;
	color: var(--pico-muted-color);
	border-radius: 4px;
	border-left: 2px solid transparent;
	line-height: 1.3;
}

.marking-toc a:hover {
	background: color-mix(in srgb, var(--pico-primary, #1095c1) 8%, transparent);
	color: var(--pico-color);
}

.marking-toc .toc-question > a {
	font-weight: 600;
	color: var(--pico-color);
	margin-block-end: .15rem;
}

.marking-toc a.active {
	background: color-mix(in srgb, var(--pico-primary, #1095c1) 15%, transparent);
	color: var(--pico-color);
	border-left-color: var(--pico-primary, #1095c1);
}

/* Horizontal TOC for medium viewports — sticky at the top of the
   marking content column. Same data as the sidebar TOC; just laid out
   left-to-right and scrollable horizontally on overflow. */
.marking-toc-horizontal {
	display: none;
}

@media (min-width: 700px) and (max-width: 1199px) {
	.marking-toc-horizontal {
		display: flex;
		align-items: center;
		/* Wrap onto multiple rows instead of horizontally scrolling.
		   row-gap gives air between wrapped rows; column-gap stays
		   tight to maximise per-row density. */
		flex-wrap: wrap;
		row-gap: .35rem;
		column-gap: .35rem;
		position: sticky;
		top: 0;
		z-index: 30;
		background: color-mix(in srgb, var(--pico-background-color) 92%, transparent);
		backdrop-filter: blur(8px);
		-webkit-backdrop-filter: blur(8px);
		padding: .55rem .75rem;
		border-block-end: 1px solid var(--pico-muted-border-color);
		margin-block: -1rem 1rem;
	}
}

.toc-h-group {
	display: inline-flex;
	align-items: center;
	gap: .1rem;
	/* Each group stays atomic — its question and subsections wrap
	   together. Wrapping happens BETWEEN groups, not inside one. */
	flex-shrink: 0;
	white-space: nowrap;
}

/* No 1-pixel vertical separator between groups: when the bar wraps,
   a trailing separator at a line end looks broken. The visual gap +
   the question-link's heavier weight signal group boundaries clearly. */

.toc-h-question,
.toc-h-section {
	display: inline-block;
	padding: .3rem .55rem;
	text-decoration: none;
	color: var(--pico-muted-color);
	border-radius: 4px;
	font-size: .85em;
	line-height: 1.25;
	font-weight: 500;
}

/* Constant weight across both states — active is signalled by colour
   and background only, never by weight changes, so toggling can't
   reflow neighbouring labels. Question labels stay heavier than their
   subsections to preserve visual hierarchy. */
.toc-h-question {
	font-weight: 700;
	color: var(--pico-color);
}

.toc-h-question:hover,
.toc-h-section:hover {
	background: color-mix(in srgb, var(--pico-primary, #1095c1) 8%, transparent);
	color: var(--pico-color);
}

.toc-h-question.active,
.toc-h-section.active {
	background: color-mix(in srgb, var(--pico-primary, #1095c1) 18%, transparent);
	color: var(--pico-color);
}

.marking-toolbar {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	margin-block: 0 1.25rem;
	flex-wrap: wrap;
}

.marking-toolbar-actions {
	display: flex;
	gap: .5rem;
	margin-inline-start: auto;
}

.marking-block {
	margin-block: 2rem;
	padding: 1.5rem 1.75rem;
	border: 1px solid var(--pico-muted-border-color);
	border-radius: var(--pico-border-radius);
	background: color-mix(in srgb, var(--pico-card-background-color) 35%, transparent);
	scroll-margin-block-start: 1rem;
}

.marking-block-header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	flex-wrap: wrap;
	padding-block-end: .75rem;
	border-block-end: 1px solid var(--pico-muted-border-color);
	margin-block-end: 1rem;
}

.marking-block-title strong {
	font-size: 1.15rem;
}

.marking-block-title .muted {
	display: block;
	color: var(--pico-muted-color);
	font-size: .88em;
	margin-block-start: .15rem;
}

.marking-block-actions {
	display: flex;
	gap: .5rem;
	align-items: center;
}

.open-response-btn {
	margin: 0;
	padding: .4rem .85rem;
	font-size: .9em;
}

.prompt-disclosure,
.forensic-disclosure {
	margin-block: .75rem;
}

.prompt-disclosure > summary,
.forensic-disclosure > summary {
	cursor: pointer;
	font-weight: 600;
	font-size: .95em;
	color: var(--pico-muted-color);
	padding-block: .25rem;
}

.prompt-disclosure[open] > summary,
.forensic-disclosure[open] > summary {
	color: var(--pico-color);
}

.prompt-disclosure .response-prompt,
.forensic-disclosure .forensic-body {
	margin-block-start: .5rem;
	padding: .85rem 1rem;
	background: var(--pico-card-background-color);
	border-radius: var(--pico-border-radius);
	border: 1px solid var(--pico-muted-border-color);
}

.prompt-disclosure .response-prompt {
	white-space: pre-wrap;
	font-style: italic;
}

.marking-form {
	margin-block: 1rem 0;
}

.marking-form .marking-section {
	margin-block: 1.75rem;
}

.marking-form .marking-section h4 {
	margin-block: 0 .75rem;
	font-size: 1rem;
	color: var(--pico-color);
}

.marking-field {
	display: block;
	margin-block: 1.25rem;
}

.marking-field > span {
	display: block;
	margin-block-end: .35rem;
	font-weight: 600;
}

/* Read-first comment field. Renders prose by default; clicking "Edit"
   swaps in a textarea that's two-way bound to the same model so the
   form submission always sees the current value. */
.comment-field {
	position: relative;
	margin: 0;
}

/* Read view and edit view share identical box geometry — same padding,
   border, radius, font and line-height — so swapping between them
   produces no layout reflow. The Alpine commentField() factory copies
   the read-view's measured height onto the textarea before it appears. */
.comment-read,
textarea.comment-edit {
	width: 100%;
	box-sizing: border-box;
	padding: 1rem 1.15rem;
	border: 1px solid var(--pico-muted-border-color);
	border-radius: var(--pico-border-radius);
	background: var(--pico-card-background-color);
	font-family: inherit;
	font-size: 1rem;
	line-height: 1.65;
	margin: 0;
}

.comment-read {
	white-space: pre-wrap;
	min-height: 3rem;
}

.comment-read:empty::before {
	content: "(no comment)";
	color: var(--pico-muted-color);
	font-style: italic;
}

textarea.comment-edit {
	resize: vertical;
	overflow: auto;
}

.comment-toolbar {
	display: flex;
	justify-content: flex-end;
	margin-block-start: .55rem;
}

button.comment-toggle-btn {
	margin: 0;
	padding: .55rem 1.1rem;
	font-size: .9em;
	min-width: 5rem;
}

/* Share the same comfortable padding across the other small action
   buttons (View response, Show in response). */
button.open-response-btn,
button.finding-highlight-btn {
	padding: .55rem 1rem;
	font-size: .9em;
}

.criteria-rows {
	display: flex;
	flex-direction: column;
	gap: 1rem;
}

.criterion-row {
	padding: .85rem 1rem;
	border: 1px solid var(--pico-muted-border-color);
	border-radius: var(--pico-border-radius);
	background: color-mix(in srgb, var(--pico-card-background-color) 50%, transparent);
}

.criterion-row-head {
	display: flex;
	justify-content: space-between;
	align-items: center;
	gap: 1rem;
	margin-block-end: .65rem;
}

.criterion-name {
	font-weight: 600;
	margin: 0;
}

.criterion-score-cell {
	display: inline-flex;
	align-items: baseline;
	gap: .4rem;
	white-space: nowrap;
	flex-shrink: 0;
}

/* High specificity so Pico's input[type=number] { width: 100% } can't win. */
.criterion-row .criterion-score-cell input.criterion-score-input {
	width: 4.5rem;
	min-width: 4.5rem;
	max-width: 4.5rem;
	flex: 0 0 4.5rem;
	margin: 0;
	padding-block: .35rem;
	text-align: center;
	font-weight: 600;
	font-size: 1.05rem;
}

.criterion-max-label {
	color: var(--pico-muted-color);
	font-size: .95em;
}

/* Quote highlights inside the response drawer. Strengths are tinted green,
   improvements amber; the most-recently-activated quote has a stronger
   tint. All are clickable — the drawer body delegates the click to scroll
   the corresponding finding card into view in the marking form. */
mark.quote-highlight {
	background: color-mix(in srgb, var(--pico-color-amber-500, #d4951d) 18%, transparent);
	padding: 0 .15em;
	border-radius: 2px;
	cursor: pointer;
	transition: background 120ms, outline-color 120ms;
	outline: 1px solid transparent;
}

mark.quote-highlight.improvement {
	background: color-mix(in srgb, var(--pico-color-amber-500, #d4951d) 18%, transparent);
}

mark.quote-highlight.strength {
	background: color-mix(in srgb, #2ecc71 18%, transparent);
}

mark.quote-highlight.improvement.active {
	background: color-mix(in srgb, var(--pico-color-amber-500, #d4951d) 55%, transparent);
	outline-color: color-mix(in srgb, var(--pico-color-amber-500, #d4951d) 65%, transparent);
}

mark.quote-highlight.strength.active {
	background: color-mix(in srgb, #2ecc71 55%, transparent);
	outline-color: color-mix(in srgb, #2ecc71 65%, transparent);
}

mark.quote-highlight:hover {
	filter: brightness(1.08);
}

.marking-finding {
	margin-block: 1rem;
	padding: .9rem 1.1rem;
	border: 1px solid var(--pico-muted-border-color);
	border-radius: var(--pico-border-radius);
	background: color-mix(in srgb, var(--pico-card-background-color) 60%, transparent);
	scroll-margin-block: 1rem;
	transition: background 220ms, box-shadow 220ms;
}

.marking-finding.flash-finding,
.marking-finding.finding-flash {
	animation: findingFlash 1.4s ease;
}

@keyframes findingFlash {
	0% { box-shadow: 0 0 0 0 transparent; }
	30% { box-shadow: 0 0 0 4px color-mix(in srgb, var(--pico-primary, #1095c1) 35%, transparent); }
	100% { box-shadow: 0 0 0 0 transparent; }
}

.marking-finding.strength .finding-quote {
	border-left-color: color-mix(in srgb, #2ecc71 65%, transparent);
	background: color-mix(in srgb, #2ecc71 8%, transparent);
}

.marking-finding:first-of-type {
	margin-block-start: .5rem;
}

.finding-quote-row {
	display: flex;
	gap: .85rem;
	align-items: flex-start;
	margin-block-end: .7rem;
}

.finding-quote {
	flex: 1;
	margin: 0;
	padding: .5rem .85rem;
	border-left: 3px solid color-mix(in srgb, var(--pico-color-amber-500, #d4951d) 55%, transparent);
	background: color-mix(in srgb, var(--pico-color-amber-500, #d4951d) 8%, transparent);
	font-style: italic;
	font-size: .96em;
	line-height: 1.5;
	border-radius: 0 var(--pico-border-radius) var(--pico-border-radius) 0;
}

button.finding-highlight-btn {
	flex-shrink: 0;
	margin: 0;
	padding: .35rem .75rem;
	font-size: .85em;
	background: transparent;
	border: 1px solid var(--pico-muted-border-color);
	/* `inherit`, not var(--pico-color) — see tab-bar button. */
	color: inherit;
	cursor: pointer;
	border-radius: var(--pico-border-radius);
	white-space: nowrap;
}

button.finding-highlight-btn:hover {
	background: var(--pico-card-background-color);
	border-color: var(--pico-color);
}

.marking-finding textarea {
	min-height: 5rem;
}

.quote-warning {
	display: block;
	margin-block-start: .4rem;
	color: var(--pico-color-red-500, #c0392b);
	font-size: .88em;
}

.row-actions {
	display: flex;
	align-items: center;
	gap: 1rem;
	margin-block-start: 1.5rem;
}

.row-actions small {
	color: var(--pico-muted-color);
}

/* ---- Response drawer ---- */

.response-drawer-tab {
	position: fixed;
	right: 0;
	top: 50%;
	transform: translateY(-50%);
	z-index: 51;
	margin: 0;
	padding: 1.1rem .6rem;
	/* Explicit min dimensions so the tab is sized + clickable + visible
	   regardless of how `writing-mode: vertical-rl` happens to compute
	   the content box — a handful of browser / text-zoom configurations
	   collapse vertical-mode content to a near-zero height, which is
	   how the cross-browser "the tab is missing" reports happen. With
	   these fallbacks the tab is always a visible 2.4rem × 9rem chip
	   regardless of writing-mode quirks. */
	min-width: 2.4rem;
	min-height: 9rem;
	background: var(--pico-primary, #1095c1);
	color: var(--pico-primary-inverse, #fff);
	border: none;
	border-radius: var(--pico-border-radius) 0 0 var(--pico-border-radius);
	writing-mode: vertical-rl;
	text-orientation: mixed;
	cursor: pointer;
	font-size: .92em;
	font-weight: 600;
	letter-spacing: .02em;
	/* Two-tier box-shadow: the first declaration is the universally-
	   supported fallback. The second uses color-mix(), which silently
	   drops the whole declaration in Safari < 16.4 / Firefox < 113 /
	   Chrome < 111 — without the fallback, those browsers render the
	   tab as a flat coloured block against the page edge with no
	   depth cue, which is what users were describing as "missing". */
	box-shadow: -3px 0 14px rgba(0, 0, 0, 0.22);
	box-shadow: -3px 0 14px color-mix(in srgb, black 22%, transparent);
	transition: right 220ms ease, background 120ms, filter 120ms;
}

/* When the drawer is open, slide the tab leftward to sit at the visible
   edge of the drawer so it can be clicked to close. */
.response-drawer-tab.drawer-open {
	right: min(560px, 92vw);
}

@media (max-width: 600px) {
	.response-drawer-tab.drawer-open {
		right: calc(100vw - 2.5rem);
	}
}

.response-drawer-tab:hover {
	filter: brightness(1.08);
}

.response-drawer-tab span {
	display: inline-block;
}

.response-drawer {
	position: fixed;
	top: 0;
	right: 0;
	height: 100vh;
	width: min(560px, 92vw);
	background: var(--pico-background-color);
	border-inline-start: 1px solid var(--pico-muted-border-color);
	box-shadow: -8px 0 32px rgba(0, 0, 0, 0.18);
	box-shadow: -8px 0 32px color-mix(in srgb, black 18%, transparent);
	transform: translateX(100%);
	transition: transform 220ms ease;
	display: flex;
	flex-direction: column;
	z-index: 50;
}

.response-drawer.open {
	transform: translateX(0);
}

.response-drawer-header {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: 1rem;
	padding: 1rem 1.25rem;
	border-block-end: 1px solid var(--pico-muted-border-color);
}

.response-drawer-header h3 {
	margin: 0;
	font-size: 1.05rem;
}

.response-drawer-header .muted {
	color: var(--pico-muted-color);
	font-size: .88em;
}

.response-drawer-close {
	margin: 0;
	padding: 0;
	width: 2rem;
	height: 2rem;
	border-radius: 50%;
	background: transparent;
	border: 1px solid var(--pico-muted-border-color);
	color: inherit;
	font-size: 1.25rem;
	line-height: 1;
	cursor: pointer;
	flex-shrink: 0;
}

.response-drawer-close:hover {
	background: var(--pico-card-background-color);
}

.response-drawer-warning {
	margin: 0;
	padding: .65rem 1.25rem;
	background: color-mix(in srgb, var(--pico-color-red-500, #c0392b) 12%, transparent);
	border-block-end: 1px solid color-mix(in srgb, var(--pico-color-red-500, #c0392b) 35%, transparent);
	font-size: .88em;
	color: var(--pico-color);
}

.response-drawer-body {
	flex: 1;
	overflow-y: auto;
	padding: 1.25rem 1.5rem;
	white-space: pre-wrap;
	font-family: Georgia, "Times New Roman", serif;
	font-size: 1.04rem;
	line-height: 1.7;
}

@media (max-width: 600px) {
	.response-drawer {
		width: 100vw;
	}
}

table.marking-criteria {
	font-size: .9em;
}

table.marking-criteria .words-col {
	width: 7rem;
	white-space: nowrap;
}

table.marking-criteria input[type="number"] {
	width: 4rem;
	display: inline-block;
	margin: 0 .35em 0 0;
	padding: .15rem .35rem;
}

table.marking-criteria textarea {
	margin: 0;
	min-height: 2.5rem;
}

.marking-versions,
.marking-snapshot {
	margin-block-start: 1rem;
}

/* Re-enter fullscreen prompt shown when the student has dropped out
   of fullscreen during writing. Less alarming than the wifi banner
   but still attention-grabbing. */
.exam-fullscreen-prompt {
	display: flex;
	align-items: center;
	gap: .75rem;
	flex-wrap: wrap;
	margin-block: 1rem;
	padding: .75rem 1rem;
	border-radius: var(--pico-border-radius);
	border: 1px solid var(--pico-color-amber-500, #d4951d);
	background: color-mix(in srgb, var(--pico-color-amber-500, #d4951d) 14%, transparent);
}

.exam-fullscreen-prompt button {
	margin: 0;
	padding-block: .35rem;
}

/* Released-attempt indicator on the session roster */
small.released-flag {
	display: inline-block;
	padding: .15rem .5rem;
	border-radius: 1rem;
	background: color-mix(in srgb, var(--pico-color-amber-500, #d4951d) 18%, transparent);
	color: var(--pico-color);
}

/* Roster: disabled (taken) names */
.student-pick:has(input:disabled) {
	cursor: not-allowed;
	background: var(--pico-card-background-color);
	opacity: .75;
}

.student-pick:has(input:disabled):hover {
	background: var(--pico-card-background-color);
}

.student-pick .taken-hint {
	margin-inline-start: auto;
	font-size: .85em;
	color: var(--pico-muted-color);
}

.marking-toolbar {
	display: flex;
	justify-content: flex-end;
	margin-block-end: 1rem;
}

.marking-toolbar a {
	margin: 0;
	padding-block: .35rem;
}

/* --- Text dossier authoring (docs/MARKING_QUALITY.md §8) ------------------ */

/* Source materials list: per-row layout — name on the left, delete inline. */
.material-list {
	list-style: none;
	padding-left: 0;
	margin-block: .5rem 1rem;
}

.material-list > li {
	display: flex;
	gap: 1rem;
	align-items: flex-start;
	justify-content: space-between;
	padding-block: .5rem;
	border-block-end: 1px solid var(--pico-muted-border-color);
}

.material-list > li:last-child {
	border-block-end: none;
}

/* The expandable view of extracted text — flex item that can grow. */
.material-list > li > details {
	flex: 1;
	min-width: 0;
}

.material-list > li > details > summary {
	cursor: pointer;
	padding-block: .15rem;
}

.material-extracted {
	margin-block-start: .5rem;
	max-height: 12rem;
	overflow: auto;
	font-size: .85rem;
	background: var(--pico-card-background-color);
	padding: .75rem;
	border-radius: var(--pico-border-radius);
	white-space: pre-wrap;
	word-wrap: break-word;
}

/* Per-row delete button: small, inline, not a section CTA. */
.material-list > li > form {
	margin: 0;
}

.material-list > li > form > button {
	margin: 0;
	padding: .25rem .75rem;
	font-size: .85rem;
	width: auto;
}

/* Spinner used inline with the Generate-from-sources HTMX indicator.
   htmx-indicator handles visibility (hidden by default, shown while a
   request is in flight). */
.dossier-generate-indicator {
	display: inline-flex;
	align-items: center;
	gap: .5rem;
}

.spinner {
	display: inline-block;
	width: 1rem;
	height: 1rem;
	border: 2px solid var(--pico-muted-border-color);
	border-top-color: var(--pico-primary);
	border-radius: 50%;
	animation: spinner-rotate 700ms linear infinite;
}

@keyframes spinner-rotate {
	to { transform: rotate(360deg); }
}

/* Inline error banner — yellow-ish, not destructive. */
.error-banner {
	background: #fff4e5;
	border-color: #f0c674;
	color: #8a5a00;
	padding: .75rem 1rem;
	margin-block: .75rem;
}

/* Inline info banner — used for long-running background jobs
   (dossier generation) while polling. */
.info-banner {
	background: #e7f2ff;
	border-color: #9ec5f5;
	color: #1c4f8a;
	padding: .75rem 1rem;
	margin-block: .75rem;
	display: flex;
	align-items: center;
	gap: .75rem;
}

/* --- Dossier editor: read/edit-in-place ---------------------------------
   Guiding principles (docs/MARKING_QUALITY.md §8):
   1. Populated fields render as static text. Click Edit → swap to a
      textarea of identical dimensions. No reflow.
   2. No textarea ever scrolls internally — they auto-grow with content
      via `field-sizing: content`. Single-line content stays one line.
   3. The read view and the textarea share IDENTICAL typography (font,
      line-height, padding, border) so the toggle is purely visual.
   Empty rows start in edit mode automatically.
----------------------------------------------------------------------- */

/* Each top-level section block (the single-field sections plus each
   fieldset) gets a subtle divider on top so the eye can chunk the page.
   The first section in the dossier-section column drops its border so
   we don't double up with the "Dossier" h2. */
.dossier-section-field,
#dossier-section fieldset {
	border-block-start: 1px solid var(--pico-muted-border-color);
	padding-block-start: 1.5rem;
	margin-block: 2rem 0;
	/* <fieldset> has a UA-stylesheet `min-width: min-content` that makes
	   it ignore container width and expand to fit its widest descendant.
	   Without this, long passage quotes / context lines push the whole
	   page wide. This is the line that prevents horizontal overflow on
	   the dossier editor — leave it. */
	min-width: 0;
}

#dossier-section > div[x-data] > form > .dossier-section-field:first-of-type,
#dossier-section > div[x-data] > form > fieldset:first-of-type {
	border-block-start: none;
	padding-block-start: 0;
	margin-block-start: 1rem;
}

/* The whole-dossier single-field sections (Identity, Plot, Conventions)
   wrap their label + field in this container. */
.dossier-section-field {
	position: relative;
}

.dossier-section-label {
	font-weight: 600;
}

/* Heading row that pairs each section's label/legend with its
   "teacher reviewed" toggle on the right. Works for both <label>
   (section-fields) and <legend> (fieldsets). */
.dossier-section-heading {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: 1rem;
	flex-wrap: wrap;
	margin-block-end: .5rem;
}

#dossier-section fieldset > .dossier-section-heading > legend {
	/* legend defaults to a special positioning inside fieldset; flex
	   parent strips that, so re-state explicit padding to avoid
	   crashing into the fieldset's top border. */
	padding-inline: .35rem;
	font-weight: 600;
}

/* Per-section review toggle. Two visual states: --reviewed (green tick)
   and --unreviewed (muted, asking-for-it). Same outline shape so
   toggling doesn't reflow neighbouring elements. */
.section-review-btn {
	flex: 0 0 auto;
	margin: 0;
	padding: .2rem .65rem;
	font-size: .82em;
	font-weight: 500;
	line-height: 1.2;
	border-radius: 999px;
	cursor: pointer;
	white-space: nowrap;
	border: 1px solid;
	transition: background-color 80ms, color 80ms, border-color 80ms;
}

.section-review-btn--reviewed {
	background: color-mix(in srgb, #15803d 12%, transparent);
	border-color: color-mix(in srgb, #15803d 40%, transparent);
	color: #15803d;
}

.section-review-btn--reviewed:hover {
	background: color-mix(in srgb, #15803d 20%, transparent);
}

.section-review-btn--unreviewed {
	background: transparent;
	border-color: var(--pico-muted-border-color);
	color: var(--pico-muted-color);
}

.section-review-btn--unreviewed:hover {
	background: var(--pico-card-background-color);
	color: var(--pico-color);
}

/* A row of one or more fields plus row actions. align-items: stretch so
   the actions column sits flush with the fields. */
.dossier-row {
	display: flex;
	gap: .75rem;
	align-items: stretch;
	margin-block-end: .65rem;
}

.dossier-row-fields {
	flex: 1;
	display: flex;
	gap: .5rem;
	min-width: 0;
}

.dossier-row-actions {
	flex: 0 0 auto;
	display: flex;
	gap: .35rem;
	align-items: flex-start;
}

/* Single-field rows (framings, out-of-scope) — the field should grow */
.dossier-row-fields > .dossier-field:only-child {
	flex: 1 1 100%;
}

/* A field is a vertically-stacked read-view + textarea (only one shown
   at a time via x-show). flex:1 inside .dossier-row-fields. */
.dossier-field {
	flex: 1;
	min-width: 0;
	display: flex;
	flex-direction: column;
}

/* ----- Identical typography on read view AND textarea ----------------
   This is the no-reflow guarantee. Any divergence here (font, padding,
   line-height) makes the row twitch on toggle. */
.dossier-field-read,
.dossier-input {
	font-size: 1rem;
	line-height: 1.5;
	padding: .55rem .8rem;
	margin: 0;
	border: 1px solid transparent;
	border-radius: var(--pico-border-radius);
	background: transparent;
	font-family: inherit;
}

/* Read view: looks like prose, click-to-edit hint via cursor + hover. */
.dossier-field-read {
	cursor: text;
	white-space: pre-wrap;
	word-wrap: break-word;
	min-height: 1em;
	transition: background-color 100ms;
}

.dossier-field-read:hover {
	background: var(--pico-card-background-color);
}

.dossier-field-read--quote {
	font-style: italic;
	color: var(--pico-muted-color);
}

/* Edit view: textarea that grows with its content (no internal scroll). */
.dossier-input {
	resize: none;
	/* Chrome 123+ — grow to content, no scrollbar. Safari falls back to
	   the rows attribute + JS autoResize (see _text_dossier_section.html). */
	field-sizing: content;
	border-color: var(--pico-form-element-border-color);
	background: var(--pico-form-element-background-color);
	width: 100%;
	/* Crucial in a CSS-grid cell: textareas default to a ~20em intrinsic
	   min-width that overrides `1fr` and pushes the column past its
	   nominal allocation. Without this the whole card overflows its
	   container on the right. */
	min-width: 0;
	max-width: 100%;
	box-sizing: border-box;
}

/* The row container picks up a subtle treatment when in edit mode so
   it reads as a discrete focus context. */
.dossier-row.is-editing,
.dossier-field.is-editing {
	/* (placeholder — reserved if we want a wrapper highlight later) */
}

/* Spacing fix: Pico tightens adjacent heading siblings on the text
   detail page (the H1 sits directly above the materials section's H2). */
#materials-section > h2:first-child,
#dossier-section > h2:first-child {
	margin-block-start: 1.5rem;
}

/* Row action buttons (Edit / Done / ×). Sit beside the fields, not
   underneath them. Small, neutral, square-ish. */
.dossier-row-action {
	margin: 0;
	padding: .35rem .7rem;
	line-height: 1;
	font-size: .9rem;
	background: transparent;
	color: var(--pico-secondary);
	border: 1px solid var(--pico-secondary);
	border-radius: var(--pico-border-radius);
	cursor: pointer;
	width: auto;
	white-space: nowrap;
	align-self: flex-start;
}

.dossier-row-action:hover {
	background: var(--pico-card-background-color);
}

/* Section-fields (Identity, Plot, Conventions) use the same Edit/Done
   in the top-right as the card layout — visual consistency across the
   editor. The trick is to reserve padding-right on the read view AND
   the textarea so the button doesn't overlap content. min-height
   matches between the two so the Edit ↔ Done toggle produces zero
   reflow even with short or empty content. */

.dossier-section-field .dossier-field {
	position: relative;
}

.dossier-section-field .dossier-row-action {
	position: absolute;
	top: .4rem;
	right: .4rem;
	z-index: 1;
}

.dossier-section-field .dossier-field-read,
.dossier-section-field .dossier-input {
	padding-right: 4.5rem;
}

/* Prose-section min-heights. Both the read view and the textarea use
   identical values so toggling Edit ↔ Done never shifts the page.
   field-sizing on the textarea still lets it grow past the min when
   content is long. Same values applied to .dossier-field-read so its
   natural height also starts at min. */
#dossier-identity .dossier-field-read,
#dossier-identity .dossier-input,
#dossier-conventions .dossier-field-read,
#dossier-conventions .dossier-input {
	min-height: 5.6rem;   /* ≈ 3 lines at line-height 1.5 + padding */
}

#dossier-plot .dossier-field-read,
#dossier-plot .dossier-input {
	min-height: 10.1rem;  /* ≈ 6 lines */
}

/* "+ Add …" buttons sit at the bottom of each fieldset. */
.dossier-add {
	width: auto;
	padding: .35rem .85rem;
	font-size: .9rem;
	margin-block-start: .5rem;
}

/* Help text under each section heading. ~0.9em, muted, sits flush under
   the heading without adding a full margin gap. max-width keeps lines
   readable on wide screens. */
.dossier-section-help {
	display: block;
	margin-block: -.25rem .85rem;
	color: var(--pico-muted-color);
	font-size: .9em;
	line-height: 1.5;
	max-width: 56em;
}

#dossier-section fieldset > legend + .dossier-section-help {
	margin-block: .15rem .85rem;
}

/* --- Card layout for variable-length items (Passages, Misreadings) -----
   These sections hold items where one field (a verbatim quote, a
   "why_wrong" explanation) can be several lines tall while sibling
   fields stay short. A horizontal row of mixed-size cells looks broken;
   a vertical card with stacked fields reads naturally. */

.dossier-card {
	border: 1px solid var(--pico-muted-border-color);
	border-radius: var(--pico-border-radius);
	padding: .85rem 1rem;
	margin-block-end: .75rem;
	background: transparent;
	transition: background-color 100ms, border-color 100ms;
	/* Defensive: prevent any descendant (a long quote without spaces,
	   a wide textarea) from stretching the card past its container. */
	min-width: 0;
	overflow: hidden;
}

.dossier-card.is-editing {
	background: var(--pico-card-background-color);
	border-color: var(--pico-form-element-border-color);
}

.dossier-card-head {
	display: flex;
	gap: 1rem;
	align-items: baseline;
	justify-content: space-between;
	margin-block-end: .65rem;
}

.dossier-card-title {
	flex: 1;
	min-width: 0;
	line-height: 1.4;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.dossier-card-head-actions {
	flex: 0 0 auto;
	display: flex;
	gap: .35rem;
}

/* Definition-list grid — labels in the left column (auto-width), values
   in the right (flexible). Each row is one field. The <dd>'s inner
   element (read view OR textarea) swaps without changing dimensions,
   so toggling Edit ↔ Done produces zero reflow. */
.dossier-card-grid {
	display: grid;
	grid-template-columns: max-content 1fr;
	column-gap: 1.25rem;
	row-gap: .35rem;
	margin: 0;
}

.dossier-card-grid > dt {
	font-weight: 500;
	font-size: .92em;
	color: var(--pico-muted-color);
	padding-block-start: .55rem;
	align-self: start;
}

.dossier-card-grid > dd {
	margin: 0;
	min-width: 0;
}

.dossier-card-grid > dd > .dossier-field-read,
.dossier-card-grid > dd > .dossier-input {
	width: 100%;
}

/* Quote in a passage card — italic, muted, no extra ornamentation
   (the field is already in its labelled row). */
.dossier-field-read--quote {
	font-style: italic;
	color: var(--pico-muted-color);
	white-space: pre-wrap;
	word-wrap: break-word;
}

/* --- Save toolbar (Save button + dirty/saved indicator + spinner) -----
   Used in the dossier editor; pattern documented in UI_PATTERNS.md §9.
   Goal: the teacher always knows whether their edits are persisted. */

.save-toolbar {
	display: flex;
	align-items: center;
	gap: .85rem;
	margin-block-start: 1rem;
	flex-wrap: wrap;
}

/* Sticky variant: the dossier editor is tall and the Save button
   otherwise sits at the bottom out of sight. Keeping it pinned to the
   viewport while the user is anywhere inside the editor means they
   can save without scrolling. Disappears naturally once they scroll
   past the parent section. */
#dossier-section .save-toolbar {
	position: sticky;
	bottom: 0;
	background: color-mix(in srgb, var(--pico-background-color) 92%, transparent);
	backdrop-filter: blur(8px);
	-webkit-backdrop-filter: blur(8px);
	padding: .75rem .25rem;
	border-block-start: 1px solid var(--pico-muted-border-color);
	z-index: 5;
	margin-inline: -.25rem;
}

.save-toolbar > button[type="submit"] {
	margin: 0;
	width: auto;
	flex: 0 0 auto;
}

.save-toolbar > button[type="submit"]:disabled,
.save-toolbar > button[type="submit"][aria-disabled="true"] {
	opacity: .55;
	cursor: not-allowed;
}

.save-status {
	font-size: .9em;
	display: inline-flex;
	align-items: center;
	gap: .4rem;
}

.save-status--unsaved {
	color: #a67100;
	font-weight: 600;
}

.save-status--saved {
	color: var(--pico-muted-color);
}

/* Subject-level marker context editor sits at the top of the subject
   detail page. Bordered card so it doesn't blur into the page flow. */
/* Numbered steps on the assessment-detail page. The badge is a circle
   the size of the section h2; the heading text sits beside it. The
   step is just a reading aid (not a state machine), so we don't dim
   later steps or anything — each section is independently editable. */
.assessment-step {
	margin-block: 1.75rem;
	padding-block-start: 1rem;
	border-block-start: 1px solid var(--pico-muted-border-color);
}

.assessment-step:first-of-type {
	border-block-start: none;
	padding-block-start: 0;
}

.assessment-step > h2 {
	display: flex;
	align-items: center;
	gap: .65rem;
	margin-block: 0 1rem;
	font-size: 1.25rem;
}

.assessment-step__num {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 1.7rem;
	height: 1.7rem;
	border-radius: 50%;
	background: var(--pico-primary);
	color: var(--pico-primary-inverse);
	font-size: .9rem;
	font-weight: 700;
	flex-shrink: 0;
}

/* Step 5 layout: status actions sit on one row above the class list. */
.assessment-run-actions {
	display: flex;
	align-items: center;
	gap: .75rem;
	flex-wrap: wrap;
	margin-block-end: 1rem;
}

.assessment-run-actions > button {
	margin: 0;
}

/* Cohort-year heading on the subjects dashboard. Smaller than h1, no
   underline, just enough to separate one year's list of subjects from
   the next. */
.subject-group-heading {
	font-size: 1.05rem;
	margin-block: 1.5rem .35rem;
	color: var(--pico-muted-color);
	letter-spacing: .04em;
	text-transform: uppercase;
	font-weight: 600;
}

.marker-context-section {
	border: 1px solid var(--pico-muted-border-color);
	border-radius: var(--pico-border-radius);
	padding: 1rem 1.25rem;
	margin-block: 1.25rem 2rem;
	background: var(--pico-card-background-color);
}

.marker-context-section > h2 {
	margin-block: 0 .35rem;
	font-size: 1.15rem;
}

.dashboard-toolbar {
	display: flex;
	justify-content: flex-end;
	gap: 1rem;
	margin-block-end: 1rem;
}

.dashboard-toolbar > a {
	margin: 0;
}

/* --- App shell: left rail (desktop) / off-canvas drawer (mobile) ----- *
   One <nav class="app-nav"> authored once in layout.html. On desktop it's
   a persistent left sidebar (body.has-nav is a flex row); below 900px it
   becomes an off-canvas drawer opened by the .app-topbar hamburger and
   dimmed by .nav-backdrop. State is a single body.nav-open class toggled
   by the vanilla handler in layout.html's head. Pages without a teacher
   nav (student/login) render body.no-nav with no chrome — plain centered
   container, exactly as before. */

.skip-link {
	position: absolute;
	left: -999px;
	top: 0;
	z-index: 100;
	background: var(--pico-primary);
	color: #fff;
	padding: .5rem .9rem;
	border-radius: 0 0 .35rem 0;
}

.skip-link:focus {
	left: 0;
}

/* Topbar appearance is defined here (active whenever it's shown — mobile,
   or desktop nav-collapsed pages); the breakpoints only flip `display`. */
.app-topbar {
	display: none;
	align-items: center;
	gap: .75rem;
	position: sticky;
	top: 0;
	z-index: 40;
	padding: .55rem .9rem;
	background: var(--pico-background-color);
	border-block-end: 1px solid var(--pico-muted-border-color);
}

.app-topbar__brand {
	display: flex;
	align-items: baseline;
	gap: .4rem;
	min-width: 0;
	color: var(--pico-contrast);
	text-decoration: none;
}

.app-topbar__sep {
	color: var(--pico-muted-color);
}

.app-topbar__ctx {
	color: var(--pico-muted-color);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.app-nav {
	display: flex;
	flex-direction: column;
	background: var(--pico-card-background-color);
	border-inline-end: 1px solid var(--pico-muted-border-color);
	padding: 1.25rem .85rem 1.5rem;
	overflow-y: auto;
}

.app-nav__brand {
	line-height: 1.25;
	margin-block-end: 1.1rem;
}

.app-nav__brand strong {
	display: block;
}

.app-nav__brand small {
	color: var(--pico-muted-color);
}

.app-nav__brand a {
	color: inherit;
	text-decoration: none;
}

.app-nav__subject {
	margin-block-end: 1rem;
}

.app-nav__subject select {
	margin: 0;
	width: 100%;
	box-sizing: border-box;
	/* The rail inherits Pico's fluid root size (~19px on wide screens), which
	   pushed the subject name into the dropdown chevron and clipped it. A
	   smaller, fixed size fits typical names; longer ones ellipsis cleanly. */
	font-size: .85rem;
	padding-block: .4rem;
	/* Keep room on the right for Pico's dropdown chevron so long names clip
	   before it instead of running underneath. */
	padding-inline-start: .7rem;
	padding-inline-end: 2rem;
	height: auto;
	text-overflow: ellipsis;
}

.app-nav__subject-name {
	font-weight: 600;
	text-decoration: none;
	color: var(--pico-contrast);
}

.app-nav__heading {
	margin: 0 0 .35rem;
	padding-inline: .95rem;
	font-size: .72em;
	text-transform: uppercase;
	letter-spacing: .05em;
	color: var(--pico-muted-color);
}

.app-nav__list {
	/* Pico forces `nav ul { display: flex }` (row); make it a column with a
	   real gap so item spacing is reliable (margins on <li> collapse and,
	   worse, Pico's negative anchor margins below would eat them). */
	display: flex;
	flex-direction: column;
	/* Pico's `nav ul` sets align-items: center (for its horizontal bar);
	   in a column that centres links — stretch them to fill the rail. */
	align-items: stretch;
	gap: .35rem;
	list-style: none;
	margin: 0;
	padding: 0;
}

.app-nav__list li {
	margin: 0;
	padding: 0;
	display: block;
}

/* Pico gives anchors inside <nav> a negative vertical margin (to keep its
   horizontal bar compact). In our vertical rail that makes the highlight
   boxes overlap — neutralise it on every nav link. */
.app-nav a {
	margin: 0;
}

.app-nav__list a {
	display: flex;
	align-items: center;
	gap: .6rem;
	padding: .5rem .95rem;
	border-radius: .4rem;
	/* Pico redefines --pico-color to the primary hue on <a>; --pico-contrast
	   gives the normal text colour so links don't all read as blue. */
	color: var(--pico-contrast);
	text-decoration: none;
	line-height: 1.3;
}

/* Nav glyphs sit muted until the row is hovered/active, then pick up the
   accent — a quiet wayfinding cue. */
.app-nav__list a .mb-icon,
.app-nav__profile .mb-icon {
	width: 1.2em;
	height: 1.2em;
	color: var(--pico-muted-color);
	flex-shrink: 0;
}
.app-nav__list a:hover .mb-icon,
.app-nav__list a.is-active .mb-icon,
.app-nav__profile:hover .mb-icon,
.app-nav__profile.is-active .mb-icon {
	color: var(--pico-primary);
}

.app-nav__list a:hover {
	background: color-mix(in srgb, var(--pico-primary, #1095c1) 9%, transparent);
}

.app-nav__list a.is-active {
	background: color-mix(in srgb, var(--pico-primary, #1095c1) 15%, transparent);
	font-weight: 600;
}

.app-nav__list--secondary a {
	font-size: .95em;
	color: var(--pico-muted-color);
}

.app-nav__list--secondary a:hover,
.app-nav__list--secondary a.is-active {
	color: var(--pico-contrast);
}

.app-nav__divider {
	margin: 1rem .3rem;
	border: 0;
	border-block-start: 1px solid var(--pico-muted-border-color);
}

/* Account block pinned to the bottom of the rail. */
.app-nav__account {
	margin-block-start: auto;
	padding-block-start: 1.25rem;
	display: flex;
	flex-direction: column;
	gap: .4rem;
}

.app-nav__profile {
	display: flex;
	align-items: center;
	gap: .6rem;
	padding: .4rem .65rem;
	border-radius: .4rem;
	color: var(--pico-contrast);
	text-decoration: none;
}

.app-nav__profile:hover {
	color: var(--pico-primary);
}

.app-nav__profile.is-active {
	background: color-mix(in srgb, var(--pico-primary, #1095c1) 15%, transparent);
	font-weight: 600;
}

.app-nav__account form {
	margin: 0;
}

.app-nav__logout {
	margin: 0;
	width: 100%;
	background: none;
	border: 1px solid var(--pico-muted-border-color);
	color: var(--pico-muted-color);
	padding: .4rem .65rem;
	border-radius: .4rem;
	font-size: .95em;
	text-align: left;
	cursor: pointer;
}

.app-nav__logout:hover {
	color: var(--pico-color);
	border-color: var(--pico-muted-color);
}

/* Hamburger (mobile topbar). */
.nav-toggle {
	flex: 0 0 auto;
	margin: 0;
	width: 2.6rem;
	height: 2.6rem;
	padding: 0;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	background: none;
	border: 1px solid var(--pico-muted-border-color);
	border-radius: .4rem;
	cursor: pointer;
}

.nav-toggle__icon,
.nav-toggle__icon::before,
.nav-toggle__icon::after {
	display: block;
	width: 1.15rem;
	height: 2px;
	/* --pico-color is redefined to white inside Pico's <button>; use the
	   theme's text-contrast colour so the bars are visible. */
	background: var(--pico-contrast);
	border-radius: 1px;
}

.nav-toggle__icon {
	position: relative;
}

.nav-toggle__icon::before,
.nav-toggle__icon::after {
	content: "";
	position: absolute;
	left: 0;
}

.nav-toggle__icon::before { top: -6px; }
.nav-toggle__icon::after  { top: 6px; }

/* Backdrop is mobile-drawer-only; shown via body.nav-open below. */
.nav-backdrop {
	display: none;
	position: fixed;
	inset: 0;
	z-index: 55;
	background: rgba(0, 0, 0, .42);
}

/* Desktop: persistent left rail. The rail is fixed (out of flow) and we
   reserve its width with body padding, so Pico's <main class="container">
   keeps its normal max-width + centring inside the remaining space — no
   flex fighting the container, no horizontal overflow. */
@media (min-width: 900px) {
	/* Reserve the rail's 15rem + a gutter so content doesn't butt up
	   against the rail's border at widths where Pico's container fills
	   the remaining space. */
	body.has-nav:not(.nav-collapsed) {
		padding-inline-start: calc(15rem + 2rem);
		/* Breathing room on the right so content doesn't run to the window
		   edge; grows a little on wider monitors. */
		padding-inline-end: clamp(2rem, 4vw, 5rem);
	}

	body.has-nav:not(.nav-collapsed) .app-nav {
		position: fixed;
		inset: 0 auto 0 0;
		width: 15rem;
	}

	/* Fullscreen-task pages (body_class "nav-collapsed", e.g. the session
	   dashboard and marking screen) hide the rail behind the topbar
	   hamburger — same off-canvas drawer the mobile view uses — so the
	   content gets the full width. Nav stays one tap away. */
	body.has-nav.nav-collapsed .app-topbar {
		display: flex;
	}

	body.has-nav.nav-collapsed .app-nav {
		position: fixed;
		inset: 0 auto 0 0;
		z-index: 60;
		width: min(18rem, 84vw);
		transform: translateX(-100%);
		transition: transform 220ms ease;
		box-shadow: 4px 0 24px rgba(0, 0, 0, .18);
	}

	body.has-nav.nav-collapsed.nav-open {
		overflow: hidden;
	}

	body.has-nav.nav-collapsed.nav-open .app-nav {
		transform: translateX(0);
	}

	body.has-nav.nav-collapsed.nav-open .nav-backdrop {
		display: block;
	}
}

/* Mobile / tablet: off-canvas drawer. */
@media (max-width: 899px) {
	.app-topbar {
		display: flex;
	}

	.app-nav {
		position: fixed;
		inset: 0 auto 0 0;
		z-index: 60;
		width: min(18rem, 84vw);
		transform: translateX(-100%);
		transition: transform 220ms ease;
		box-shadow: 4px 0 24px rgba(0, 0, 0, .18);
	}

	body.nav-open {
		overflow: hidden;
	}

	body.nav-open .app-nav {
		transform: translateX(0);
	}

	body.nav-open .nav-backdrop {
		display: block;
	}
}

@media (prefers-reduced-motion: reduce) {
	.app-nav {
		transition: none;
	}
}

/* --- Version history table ------------------------------------------- */
.version-history {
	width: 100%;
	margin-block: .5rem 1rem;
	/* auto layout — columns size to content (fixed layout was too rigid
	   on narrow viewports, where it compressed cells so hard that words
	   wrapped a letter at a time). The hidden-cell media query below
	   handles narrow widths by dropping non-essential columns. */
}

.version-history th,
.version-history td {
	padding: .55rem .85rem;
	vertical-align: top;
}

.version-history thead th {
	font-size: .82em;
	text-transform: uppercase;
	letter-spacing: .04em;
	color: var(--pico-muted-color);
	font-weight: 600;
}

.version-history tbody tr:hover {
	background: var(--pico-card-background-color);
}


.version-history__current {
	background: color-mix(in srgb, var(--pico-primary, #1095c1) 4%, transparent);
}

.version-history__num {
	width: 6.5rem;
	white-space: nowrap;
}

.version-history__num > .dossier-badge {
	margin-inline-start: .35rem;
	font-size: .7em;
}

.version-history__when {
	width: 10rem;
	white-space: nowrap;
	color: var(--pico-muted-color);
	font-size: .9em;
}

.version-history__note {
	color: var(--pico-muted-color);
	font-style: italic;
	overflow-wrap: break-word;
	word-wrap: break-word;
}

.version-history__chev {
	width: 1.5rem;
	text-align: right;
	font-size: 1.2rem;
	color: var(--pico-muted-color);
}

.version-history__chev > a {
	color: inherit;
	text-decoration: none;
	transition: transform 80ms, color 80ms;
	display: inline-block;
}

.version-history tbody tr:hover .version-history__chev > a {
	color: var(--pico-primary);
	transform: translateX(2px);
}

/* Narrow viewports: drop the lower-priority columns rather than
   squashing them. The teacher still gets #, source label, and the
   chevron — enough to pick a version to view. Saved time and change
   note are visible on the read-only version page they click through to. */
@media (max-width: 720px) {
	.version-history__when,
	.version-history__note {
		display: none;
	}
}

/* --- Read-only version detail page ----------------------------------- */
.version-detail {
	margin-block: 1rem 0;
}

.version-detail-meta {
	display: flex;
	flex-wrap: wrap;
	justify-content: space-between;
	align-items: center;
	gap: .75rem;
	margin-block-end: 2rem;
	padding-block-end: 1rem;
	border-block-end: 1px solid var(--pico-muted-border-color);
}

.version-detail-section {
	margin-block: 1.75rem;
}

.version-detail-section > h2 {
	margin-block: 0 .75rem;
	font-size: 1.25rem;
}

.version-detail-list,
.version-detail-passages,
.version-detail-misreadings {
	margin-block: 0;
}

.version-detail-misreadings > li,
.version-detail-passages > li {
	margin-block: .75rem;
}

.version-detail-quote {
	margin: .35rem 0 0;
	padding: .35rem 0 .35rem .85rem;
	border-inline-start: 3px solid var(--pico-muted-border-color);
	color: var(--pico-muted-color);
	font-style: italic;
	white-space: pre-wrap;
}

.prewrap {
	white-space: pre-wrap;
}

/* --- Assessment ↔ Text attachment picker ----------------------------- */
.attached-texts {
	list-style: none;
	padding-left: 0;
	margin-block: 1rem .5rem;
}

.attached-text-row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	padding-block: .55rem;
	border-block-end: 1px solid var(--pico-muted-border-color);
}

.attached-text-row:last-child {
	border-block-end: none;
}

.attached-text-info {
	flex: 1;
	min-width: 0;
}

.attached-text-actions {
	flex: 0 0 auto;
	display: flex;
	gap: .35rem;
}

.attach-picker {
	display: flex;
	flex-wrap: wrap;
	gap: .65rem;
	align-items: flex-start;
	margin-block-end: 1rem;
}

.attach-picker > .text-search {
	flex: 1 1 14rem;
	max-width: none;
	margin: 0;
}

.attach-picker > select {
	flex: 1 1 100%;
	margin: 0;
}

.attach-picker > button {
	margin: 0;
	width: auto;
	flex: 0 0 auto;
}

.comparative-notes {
	margin-block-start: 2rem;
	padding-block-start: 1.5rem;
	border-block-start: 1px solid var(--pico-muted-border-color);
}

.comparative-notes > h3 {
	margin-block: 0 .5rem;
	font-size: 1.1rem;
}

/* --- School-wide text library --------------------------------------- */
.text-search-label {
	display: flex;
	align-items: center;
	gap: .75rem;
	margin-block: 1rem .5rem;
}

.text-search {
	flex: 1;
	max-width: 28rem;
	margin: 0;
}

/* Proper table layout so every column lines up across rows. PicoCSS
   provides table base styles; we just lay out columns and pad cells
   uniformly. */
.text-library-table {
	width: 100%;
	margin-block: .25rem 1rem;
}

.text-library-table th,
.text-library-table td {
	padding: .65rem .85rem;
	vertical-align: middle;
}

.text-library-table thead th {
	font-size: .82em;
	text-transform: uppercase;
	letter-spacing: .04em;
	color: var(--pico-muted-color);
	font-weight: 600;
	background: var(--pico-card-background-color);
}

.text-library-table tbody tr {
	transition: background-color 80ms;
}

.text-library-table tbody tr:hover {
	background: var(--pico-card-background-color);
}

.text-library-table td > a {
	color: inherit;
	text-decoration: none;
	font-weight: 600;
}

.text-library-table tbody tr:hover td > a {
	color: var(--pico-primary);
}

.text-library-table__author { width: 28%; }
.text-library-table__form   { width: 10%; }
.text-library-table__status { width: 12%; }
.text-library-table__chev   {
	width: 1.5rem;
	text-align: right;
	color: var(--pico-muted-color);
	font-size: 1.2rem;
}

.text-library-table__chev > a {
	color: inherit;
	text-decoration: none;
	font-weight: 400;
	transition: transform 80ms, color 80ms;
	display: inline-block;
}

.text-library-table tbody tr:hover .text-library-table__chev > a {
	color: var(--pico-primary);
	transform: translateX(2px);
}

.text-library-table__empty td {
	text-align: center;
	padding-block: 1.5rem;
}

@media (max-width: 720px) {
	.text-library-table__author,
	.text-library-table__form {
		display: none;
	}
}

/* Dossier status badge — pill, muted by default; primary when ready. */
.dossier-badge {
	display: inline-block;
	padding: .15rem .55rem;
	border-radius: 999px;
	font-size: .8em;
	font-weight: 500;
	background: var(--pico-card-background-color);
	border: 1px solid var(--pico-muted-border-color);
	color: var(--pico-muted-color);
}

.dossier-badge--ready {
	background: color-mix(in srgb, var(--pico-primary, #1095c1) 12%, transparent);
	border-color: color-mix(in srgb, var(--pico-primary, #1095c1) 35%, transparent);
	color: var(--pico-color);
}

/* Fieldset spacing — Pico's default is too generous between sections. */
#dossier-section fieldset {
	margin-block: 1.25rem;
	padding: .75rem 1rem;
}

#dossier-section fieldset > legend {
	font-weight: 600;
	padding-inline: .35rem;
}

/* Inline "+ Add" button under each fieldset — secondary, small. */
#dossier-section fieldset > button[type="button"] {
	margin-block-start: .25rem;
	width: auto;
	padding: .35rem .85rem;
	font-size: .9rem;
}

/* Save-as-new-version: primary, but not screaming full-width on desktop. */
#dossier-section form > button[type="submit"]:not([disabled]) {
	max-width: 18rem;
}

/* Generate-from-sources: matches the existing secondary CTA height. */
#dossier-section form button[type="submit"][hx-target="#dossier-section"] {
	width: auto;
}

/* ── Phase: mobile polish ─────────────────────────────────────────────
   Narrow-viewport fixes for the teacher views. */
@media (max-width: 600px) {
	/* Breadcrumbs: keep on one scrollable line instead of stacking each
	   crumb onto its own row on deep pages (subject ▸ class ▸ sitting ▸…). */
	nav[aria-label="breadcrumb"] ul {
		flex-wrap: nowrap;
		overflow-x: auto;
		white-space: nowrap;
		font-size: .85rem;
		-webkit-overflow-scrolling: touch;
	}

	/* Students roster → stacked cards. The four-column table squeezes
	   names to two or three wrapped lines on a phone and burns width on
	   empty "—" cells. Stack name + secondary facts, pin Edit top-right,
	   and drop empty secondary cells entirely. */
	table.roster--students,
	table.roster--students tbody,
	table.roster--students tr,
	table.roster--students td {
		display: block;
	}
	table.roster--students thead {
		display: none;
	}
	table.roster--students tr {
		position: relative;
		padding: .55rem 5rem .55rem 0;
		border-block-end: 1px solid var(--pico-muted-border-color);
	}
	table.roster--students td {
		border: none;
		padding: 0;
	}
	table.roster--students td:first-child {
		font-weight: 600;
	}
	table.roster--students td.cell-empty {
		display: none;
	}
	table.roster--students td[data-label] {
		color: var(--pico-muted-color);
		font-size: .9em;
	}
	table.roster--students td[data-label]::before {
		content: attr(data-label) ": ";
	}
	table.roster--students td.roster-actions {
		position: absolute;
		top: .55rem;
		right: 0;
		width: auto;
	}
}

