/* FEL Forge — interactive worksheet widget. Scoped to .fel-forge.
 *
 * RESTYLING: every visual choice is a --forge-* custom property declared in the token
 * block below. To retune Forge to the final design, override those tokens in one place
 * (e.g. in the theme's lab.css) — you should not need to touch the component rules.
 * Tokens inherit the theme's OKLCH palette (--accent, --wrong, --line, --sans …) where
 * one exists, each with a safe standalone fallback.
 */

.fel-forge {
	/* palette */
	--forge-font:       var(--sans, "Onest", system-ui, sans-serif);
	--forge-ink:        var(--ink, currentColor);
	--forge-muted:      var(--muted, color-mix(in oklch, currentColor 55%, transparent));
	--forge-surface:    var(--surface, transparent);
	--forge-line:       var(--line, color-mix(in oklch, currentColor 16%, transparent));
	--forge-soft:       var(--accent-soft, color-mix(in oklch, currentColor 6%, transparent));
	--forge-accent:     var(--accent, oklch(0.55 0.198 262));
	--forge-accent-ink: var(--accent-ink, var(--forge-accent));
	--forge-on-accent:  #fff;
	--forge-ok:         var(--lime-ink, oklch(0.52 0.13 144));
	--forge-ok-soft:    var(--lime-soft, color-mix(in oklch, var(--forge-ok) 16%, transparent));
	/* The bright lime, for fills and rings that sit next to --forge-ok text (the Done pill's
	   border, a completed TOC dot). --forge-ok is the ink; this is the paint. */
	--forge-ok-fill:    var(--lime, var(--forge-ok));
	/* Second accent, for the gradient on the overall progress bar. */
	--forge-accent-2:   var(--accent-2, var(--forge-accent-ink));
	--forge-bad:        var(--wrong-ink, oklch(0.58 0.19 25));
	--forge-bad-soft:   var(--wrong-soft, color-mix(in oklch, var(--forge-bad) 12%, transparent));
	/* shape & rhythm */
	--forge-radius:     1rem;
	--forge-radius-sm:  0.8rem;
	/* Inline icon masks (data-URI SVG, Feather-style) for the mobile contents trigger. */
	--forge-ico-contents: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='8' y1='6' x2='21' y2='6'/%3E%3Cline x1='8' y1='12' x2='21' y2='12'/%3E%3Cline x1='8' y1='18' x2='21' y2='18'/%3E%3Cline x1='3' y1='6' x2='3.01' y2='6'/%3E%3Cline x1='3' y1='12' x2='3.01' y2='12'/%3E%3Cline x1='3' y1='18' x2='3.01' y2='18'/%3E%3C/svg%3E");
	--forge-ico-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
	/* The `::: spoiler icon=name` set (fel_forge_icons() in parser.php is the name list —
	   keep the two in step). Same shape as the two above: a stroked 24×24 outline, drawn as
	   a mask over currentColor, so an icon takes the title's colour and a multiple of its
	   size. That is the whole reason these exist rather than an emoji — see the comment on
	   fel_forge_icons(). Adding one: a var here, a line there, an [data-ico] arm below. */
	--forge-ico-book: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 19.5A2.5 2.5 0 0 1 6.5 17H20'/%3E%3Cpath d='M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z'/%3E%3C/svg%3E");
	--forge-ico-info: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cline x1='12' y1='16' x2='12' y2='12'/%3E%3Cline x1='12' y1='8' x2='12.01' y2='8'/%3E%3C/svg%3E");
	--forge-ico-star: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolygon points='12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2'/%3E%3C/svg%3E");
	--forge-ico-pencil: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z'/%3E%3C/svg%3E");
	--forge-ico-ear: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 18v-6a9 9 0 0 1 18 0v6'/%3E%3Cpath d='M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3zM3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z'/%3E%3C/svg%3E");
	--forge-ico-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
	/* underline-style gap: rounded top, near-square bottom (it sits on the accent rule) */
	--forge-gap-radius: 0.45em 0.45em 0.12em 0.12em;
	--forge-card-pad:   1.4rem 1.55rem;
	--forge-line-strong: var(--line-strong, color-mix(in oklch, var(--forge-line) 60%, currentColor));
	--forge-faint:      var(--faint, var(--forge-muted));
	/* Drop-area fill. Answer areas (slots, answer line) are boxes on --forge-panel with a
	   --forge-line-strong dash; the word bank deliberately has no box, so the two never
	   read as the same thing. */
	--forge-panel:      var(--panel, color-mix(in oklch, currentColor 5%, transparent));
	/* none = fill the content column (matches native content width). A readable cap
	   like 46rem can be reinstated here during the final design pass. */
	--forge-maxw:       none;
	/* Answer-option lists (choice / true-false) are capped + left-aligned; the exercise
	   card itself stays full width. Add margin-inline:auto on .forge-opts to centre them. */
	--forge-opts-maxw: 34rem;
	/* An illustration standing alone in its paragraph is capped HERE, not at the content
	   width: a 1200px picture filling the column dwarfs the 16px prose around it, which is
	   exactly what a graded reader looked like before this token existed. An explicit
	   `![alt](url){width=…}` writes an inline style and beats it. */
	--forge-fig-maxw: 34rem;
	/* An illustration *inside* an exercise is capped smaller again. It is context for the
	   question — Duolingo's coffee pot — and at the figure's 34rem it becomes the exercise,
	   with the thing to actually do below the fold on a phone. */
	--forge-illus-maxw: 20rem;

	font-family: var(--forge-font);
	color: var(--forge-ink);
	font-size: 1rem;               /* explicit ~16px base — see FORGE-DOM.md (TT5's 22px is not ours) */
	line-height: 1.6;
	-webkit-font-smoothing: antialiased;
	max-width: var(--forge-maxw);
}

.fel-forge .forge-title { font-size: 1.5rem; font-weight: 700; margin: 0 0 1rem; }
.fel-forge .forge-h { font-size: 1.15rem; font-weight: 650; margin: 1.75rem 0 .6rem; }
.fel-forge .forge-p { margin: .5rem 0; }
.fel-forge .forge-img { max-width: 100%; height: auto; border-radius: var(--forge-radius-sm); margin: .5rem 0; }

/* An image alone in its paragraph gets a line of its own, centred. Inside a sentence it
   stays inline — see fel_forge_block_image(). `{align=left|right}` overrides the centring
   with an inline style on the <figure>. */
.fel-forge .forge-figure { margin: 1rem 0; text-align: center; }
.fel-forge .forge-figure .forge-img { margin: 0; max-width: min(100%, var(--forge-fig-maxw)); }
/* An exercise's illustration — a picture on its own line inside the block, drawn at the top
   of the card. Smaller than a standalone figure by default: it is context for the question,
   and a picture taller than the question it illustrates buries it. `{width=…}` overrides
   (inline style beats this), and `{align=…}` positions it in the column. */
.fel-forge .forge-illus { margin: 0 0 .9rem; }
.fel-forge .forge-illus .forge-figure { margin: 0; }
.fel-forge .forge-illus .forge-figure + .forge-figure { margin-top: .5rem; }
.fel-forge .forge-illus .forge-img { max-width: min(100%, var(--forge-illus-maxw)); border-radius: var(--forge-radius-sm); }
/* `{align=left}` / `{align=right}` — the picture sits beside the exercise and the question
   wraps around it. A real float, because that is the only thing that makes *text* flow
   around a picture; every sub-layout an exercise draws below the question (the option grid,
   the word bank, the chip row, the pairs) is flex or grid, so each keeps to its own side of
   the float without being told to. Capped at 45%: past that the picture and the question
   are two narrow columns and neither reads.
   `align=center` and no align both keep the picture above — that is the default. */
.fel-forge .forge-illus--left { float: left; margin: .15rem 1.1rem .6rem 0; max-width: 45%; }
.fel-forge .forge-illus--right { float: right; margin: .15rem 0 .6rem 1.1rem; max-width: 45%; }
/* The card has to grow to hold the picture — a float does not add to its parent's height,
   so a tall illustration would hang out of the bottom of the card and over the next one. */
.fel-forge .forge-ex:has(> .forge-illus--left, > .forge-illus--right)::after { content: ""; display: block; clear: both; }
/* The full-width "type what you hear" box is a plain block, so it would run *under* a
   floated picture rather than beside it. flow-root keeps it to its own side. (The
   given-letters variant is already a flex box, which does this for free.) */
.fel-forge .forge-typehear .forge-blank:not(.forge-blank--given) { display: flow-root; }
/* On a phone, "beside" is two columns of about 150px each — the picture too small to read
   and the question one word per line. The float is dropped and the picture goes back on
   top; it keeps its horizontal alignment there (`align=right` stays right), because that
   comes from an inline style on the figure and overriding it would take !important to say
   nothing useful. Immediately after the rules it overrides, so this is ordering rather than
   a specificity fight. Verified at real 390 / 560 / 561 / 600px viewports. */
@media (max-width: 560px) {
	.fel-forge .forge-illus--left,
	.fel-forge .forge-illus--right { float: none; margin: 0 0 .9rem; max-width: 100%; }
}

/* Emphasis carries meaning here — a bold L2 instruction over a muted L1 line — so the
   weight is stated rather than left to whatever the surrounding theme does with <strong>. */
.fel-forge strong, .fel-forge b { font-weight: 700; }

/* Secondary text. `~~run~~` mutes part of a line; `~ ` at the start of a line mutes and
   shrinks the whole of it — the Russian line under an English instruction, the pattern the
   H5P worksheets used for L1 glosses. Same colour token as every other muted thing here. */
.fel-forge .forge-muted { color: var(--forge-muted); }
.fel-forge .forge-muted-line { font-size: .9em; }

/* Display title + subtitle (::: title) — big text that is NOT a heading and so adds no
   contents row. The lead page of a reader is what this is for. */
.fel-forge .forge-titleblock { margin: 1.25rem 0 1.5rem; }
.fel-forge .forge-display {
	font-size: clamp(1.6rem, 1.25rem + 1.6vw, 2.1rem);
	font-weight: 800;
	letter-spacing: -.025em;
	line-height: 1.12;
	margin: 0 0 .4rem;
	text-wrap: balance;
}
.fel-forge .forge-subtitle {
	font-size: 1.05rem;
	color: var(--forge-muted);
	margin: 0 0 .25rem;
	text-wrap: balance;
}
.fel-forge .forge-titleblock > :last-child { margin-bottom: 0; }

/* ::: center — a cover block (title + picture) centred as one. text-align inherits, so
   this one declaration reaches the prose, the title and the figure inside it. */
.fel-forge .forge-center { text-align: center; }
.fel-forge .forge-center .forge-read > .forge-p { margin-inline: auto; }

/* Pipe tables. Vocabulary is a table — word · part of speech · перевод · example.
   The wrapper scrolls rather than the page: a wide table on a phone would otherwise push the
   whole document sideways, and in multipage that takes the contents column and the nav row
   with it. --forge-table-colw is the readable floor per column; the renderer writes the
   column count into --forge-table-cols, so a four-column table scrolls at 320px while a
   two-column glossary still just fits. */
.fel-forge .forge-tablewrap { overflow-x: auto; margin: 1rem 0; }
.fel-forge .forge-table {
	--forge-table-colw: 6.5rem;
	border-collapse: collapse;
	width: 100%;
	min-width: calc(var(--forge-table-cols, 1) * var(--forge-table-colw));
	font-size: .95em;
	text-align: left;               /* per-column `|:--:|` writes an inline style, which wins */
}
.fel-forge .forge-table th,
.fel-forge .forge-table td {
	padding: .45rem .7rem;
	border-bottom: 1px solid var(--forge-line);
	vertical-align: top;
}
.fel-forge .forge-table thead th {
	font-weight: 700;
	color: var(--forge-accent-ink);
	background: var(--forge-soft);
	border-bottom-color: var(--forge-line-strong);
}
.fel-forge .forge-table tbody tr:last-child > * { border-bottom: 0; }
.fel-forge .forge-table .forge-img { margin: 0; max-width: 100%; }

/* Guidance callout */
.fel-forge .forge-hint {
	background: var(--forge-soft);
	border-left: 3px solid var(--forge-accent);
	border-radius: var(--forge-radius-sm);
	padding: .6rem .9rem;
	margin: 1rem 0;
	font-size: .95em;
}

/* Folded reference matter (::: spoiler). The soft fill is on the SUMMARY ONLY, not on the
   whole block: filling both title and body made an open spoiler a slab the size of ::: hint,
   which says "read me now" — the opposite of what this block is for. Tinting just the title
   keeps the affordance (it is a control, and it looks like one) without the slab.
   The fill spans the column, so the title row reads as a bar. Open, a neutral --forge-line
   rule ties the body to its title, so the body stays plain prose and competes with nothing.
   Native <details>; no JS anywhere in this component. */
.fel-forge .forge-spoiler {
	margin: 1rem 0;
	font-size: .95em;
}
.fel-forge .forge-spoiler__sum {
	/* Full-column fill (asked for 2026-07-26): the title row reads as a section bar rather
	   than a chip. Swap back to inline-flex to make it hug the title again. */
	display: flex; align-items: flex-start; gap: .45rem;
	padding: .4rem .75rem .4rem .6rem;
	background: var(--forge-soft);
	border-radius: var(--forge-radius-sm);
	cursor: pointer;
	font-weight: 650;
	color: var(--forge-accent-ink);
	list-style: none;              /* Firefox */
	user-select: none;
	transition: background-color .15s;
}
.fel-forge .forge-spoiler__sum:hover { background: color-mix(in oklch, var(--forge-accent) 14%, transparent); }
.fel-forge .forge-spoiler__sum::-webkit-details-marker { display: none; }
/* Chevron leads the label rather than trailing it: the row is only as wide as its text, so
   a far-right marker would float away from short titles. Rotates on open. The top margin
   pins it to the FIRST line — a title long enough to wrap must not centre it between two. */
.fel-forge .forge-spoiler__sum::before {
	content: ""; flex: 0 0 auto; width: 15px; height: 15px; margin-top: .28em;
	background: currentColor;
	-webkit-mask: var(--forge-ico-chevron) center / contain no-repeat;
	mask: var(--forge-ico-chevron) center / contain no-repeat;
	transform: rotate(-90deg);
	transition: transform .2s ease;
}
.fel-forge .forge-spoiler[open] .forge-spoiler__sum::before { transform: rotate(0); }
/* `::: spoiler icon=name`. Sized in `em` so it tracks the title, and pinned to the first
   line with the same margin-top as the chevron — `align-items: flex-start` would otherwise
   centre it against a title long enough to wrap. It is a real flex item, so the summary's
   `gap` already spaces it from the label; no margin of its own. */
.fel-forge .forge-spoiler__ico {
	flex: 0 0 auto; width: 1.05em; height: 1.05em; margin-top: .22em;
	background: currentColor;
	-webkit-mask: var(--forge-ico) center / contain no-repeat;
	mask: var(--forge-ico) center / contain no-repeat;
}
.fel-forge .forge-spoiler__ico[data-ico="book"]   { --forge-ico: var(--forge-ico-book); }
.fel-forge .forge-spoiler__ico[data-ico="info"]   { --forge-ico: var(--forge-ico-info); }
.fel-forge .forge-spoiler__ico[data-ico="star"]   { --forge-ico: var(--forge-ico-star); }
.fel-forge .forge-spoiler__ico[data-ico="pencil"] { --forge-ico: var(--forge-ico-pencil); }
.fel-forge .forge-spoiler__ico[data-ico="ear"]    { --forge-ico: var(--forge-ico-ear); }
.fel-forge .forge-spoiler__ico[data-ico="check"]  { --forge-ico: var(--forge-ico-check); }
.fel-forge .forge-spoiler__sum:focus-visible { outline: 2px solid var(--forge-accent); outline-offset: 1px; }
.fel-forge .forge-spoiler__body {
	margin: .3rem 0 0 .55rem;
	padding: .15rem 0 .15rem .9rem;
	border-left: 2px solid var(--forge-line);
}
.fel-forge .forge-spoiler__body > :first-child { margin-top: 0; }
.fel-forge .forge-spoiler__body > :last-child { margin-bottom: 0; }
.fel-forge .forge-ul { margin: .4rem 0; padding-left: 1.15rem; }
.fel-forge .forge-ul li { margin: .2rem 0; }

/* The block card — one rule for every top-level block, so retuning the tokens moves them all.
   Not all of them are checkpoints:
     .forge-pairs      self-grades, so it has no Check button and no .forge-checkpoint class
     .forge-textblock  reading matter (::: text)
     .forge-listen     reading matter with audio (::: listen)
   They're carded anyway — a bare block between carded neighbours reads as broken. Only the
   graded ones get the :has() border reaction below. */
.fel-forge .forge-checkpoint,
.fel-forge .forge-pairs,
.fel-forge .forge-textblock,
.fel-forge .forge-listen {
	border: 1px solid var(--forge-line);
	border-radius: var(--forge-radius);
	background: var(--forge-surface);
	padding: var(--forge-card-pad);
	margin: .9rem 0;
	transition: border-color .15s;
}
/* Whole-card reaction to grading — there is no state class on the card, so key off feedback */
.fel-forge .forge-checkpoint:has(.forge-fb.is-pass),
.fel-forge .forge-pairs:has(.forge-fb.is-pass) { border-color: color-mix(in srgb, var(--forge-ok) 45%, var(--forge-line)); }
.fel-forge .forge-checkpoint:has(.forge-fb.is-partial) { border-color: color-mix(in srgb, var(--forge-bad) 40%, var(--forge-line)); }
.fel-forge .forge-group .forge-ex,
.fel-forge .forge-group .forge-choice { margin: .4rem 0; }
.fel-forge .forge-group .forge-ex + .forge-choice { margin-top: .8rem; }

/* Blanks */
.fel-forge .forge-blank { white-space: nowrap; }
/* A gap is a ~1.9em-tall box dropped into a 1.6 line box, so four gapped sentences under
   each other had their fields touching. Only paragraphs that actually carry a gap get the
   taller leading — prose keeps its reading rhythm. (Same trick as .forge-complete-line and
   .forge-tapword-line, which set their own leading for exactly this reason.) */
.fel-forge .forge-p:has(.forge-blank) { line-height: 2.6; }
.fel-forge .forge-gap {
	/* `font: inherit` is the shorthand, so it inherits the *line-height* too — and the line
	   above deliberately sets 2.4 on any paragraph carrying a gap. A form control does not
	   just sit on that leading, it grows to it: the field became a ~43px box holding one
	   15px word, and consecutive gapped sentences ended up with 0px between their fields.
	   The paragraph keeps its airy leading; the control gets its own. */
	font: inherit; line-height: 1.4; font-size: .95em; font-weight: 700;
	color: var(--forge-accent-ink); text-align: center;
	padding: .28em .7em; min-width: 5.5em;
	border: 0; border-bottom: 2.5px solid var(--forge-accent);
	border-radius: var(--forge-gap-radius);
	background: var(--forge-soft);
	transition: border-color .15s, background .15s, box-shadow .15s;
}
.fel-forge select.forge-gap {
	min-width: 7em; cursor: pointer;
	-webkit-appearance: none; appearance: none; padding-right: 1.9em;
	background-image: url("data:image/svg+xml,%3Csvg width='10' height='7' viewBox='0 0 10 7' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%234c46c6' stroke-width='1.6' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
	background-repeat: no-repeat; background-position: right .7em center;
}
/* NB: these use background-color, not the `background` shorthand — the shorthand resets
   background-image, which would wipe select.forge-gap's custom dropdown arrow. */
.fel-forge .forge-gap:focus { outline: 0; background-color: var(--forge-surface); box-shadow: 0 0 0 3px var(--forge-soft); }
.fel-forge .forge-gap::placeholder { color: var(--forge-accent-ink); font-style: italic; font-weight: 600; opacity: .5; }

.fel-forge .forge-gap.is-correct { border-color: var(--forge-ok); background-color: var(--forge-ok-soft); color: var(--forge-ok); }
.fel-forge .forge-gap.is-wrong { border-color: var(--forge-bad); background-color: var(--forge-bad-soft); color: var(--forge-bad); }
.fel-forge .forge-gap.is-revealed { border-color: var(--forge-ok); border-style: dashed; }
.fel-forge .forge-gap[disabled] { opacity: 1; cursor: default; }
/* Full-width "type what you hear" box keeps a bordered box, not the inline underline. */
.fel-forge .forge-gap--wide { text-align: left; font-weight: 600; color: var(--forge-ink); border: 1.5px solid var(--forge-line); border-radius: var(--forge-radius-sm); background: color-mix(in oklch, currentColor 3%, transparent); }
.fel-forge .forge-gap--wide:focus { border-color: var(--forge-accent); box-shadow: 0 0 0 3px var(--forge-soft); }
.fel-forge .forge-gap--wide.is-correct, .fel-forge .forge-gap--wide.is-wrong { color: var(--forge-ink); }

/* Explanatory feedback — `>> note` on a gap, a choice option or a T/F statement.
   Rendered by PHP and hidden here; forge.js adds .is-shown once the item resolves, so with
   JS off it never appears, like every other verdict. Not a `hidden` attribute: a CSS
   display rule would silently outrank it and the note would leak onto an unanswered page. */
.fel-forge .forge-note { display: none; }
.fel-forge .forge-note.is-shown {
	display: block;
	/* .forge-blank sets white-space:nowrap so a gap never breaks across lines — inherited,
	   that would run a whole explanation off the side of the page. */
	white-space: normal;
	margin: .45rem 0 .1rem;
	padding: .5em .8em;
	border-left: 3px solid var(--forge-line-strong);
	border-radius: 0 var(--forge-radius-sm) var(--forge-radius-sm) 0;
	background: color-mix(in oklch, currentColor 5%, transparent);
	font-size: .875rem; font-weight: 500; line-height: 1.45;
	text-align: left;   /* .forge-gap centres its text; the note is prose */
	color: var(--forge-ink);
}
/* Colour follows the item's verdict. A choice note is inside the option's <li> and rides
   that li's class; a gap note lives after the paragraph (so the sentence is not split) and
   carries its own, set by forge.js. */
.fel-forge .forge-note.is-wrong,
.fel-forge .forge-opts li.is-wrong > .forge-note.is-shown { border-left-color: var(--forge-bad); background: var(--forge-bad-soft); }
.fel-forge .forge-note.is-correct,
.fel-forge .forge-opts li.is-correct > .forge-note.is-shown { border-left-color: var(--forge-ok); background: var(--forge-ok-soft); }
.fel-forge .forge-note.is-revealed { border-left-color: var(--forge-ok); border-left-style: dashed; background: var(--forge-ok-soft); }
/* A T/F row is a flex row; its note is a third item and has to claim the full width rather
   than squeeze in beside the statement and the True/False pills. */
.fel-forge .forge-tf-row { flex-wrap: wrap; }
.fel-forge .forge-tf-row > .forge-note.is-shown { flex: 1 0 100%; margin-bottom: .35rem; }
/* A ::: match row is a two-column grid (fixed left, slot right); its note is a third cell
   and must span both, or it lands under the left column at a third of the width. Written
   `1 / -1` rather than `span 2` so a future third column cannot leave it short. */
.fel-forge .forge-match-row > .forge-note.is-shown { grid-column: 1 / -1; margin: .1rem 0 .35rem; }

/* Hint marker — a superscript "?" like a footnote reference */
.fel-forge .forge-tip {
	vertical-align: super;
	font-size: .62em;
	line-height: 0;
	margin-left: .1em;
	padding: 0 .12em;
	border: 0;
	background: none;
	color: var(--forge-accent);
	font-weight: 700;
	cursor: help;
}
.fel-forge .forge-tip:hover { text-decoration: underline; }

/* Multiple choice — full-width card, left-aligned question, and a capped, left-aligned
   options list (true-false will inherit). The card matches other exercises; only the
   options are tidied. */
.fel-forge .forge-q { font-weight: 700; font-size: 1.125rem; letter-spacing: -.01em; margin: 0 0 .7rem; }
.fel-forge .forge-opts { list-style: none; margin: 0; padding: 0; display: grid; gap: .6rem; max-width: var(--forge-opts-maxw); }
.fel-forge .forge-opts label {
	display: flex; align-items: center; gap: .7em;
	padding: .8rem 1rem; font-size: .97rem; font-weight: 600;
	border: 1.5px solid var(--forge-line); border-radius: var(--forge-radius-sm); cursor: pointer;
	transition: border-color .14s, background .14s;
}
.fel-forge .forge-opts label:hover { border-color: var(--forge-line-strong); background: var(--forge-soft); }
.fel-forge .forge-opts label:has(input:checked) { border-color: var(--forge-accent); background: var(--forge-soft); }
.fel-forge .forge-opts li.is-correct label { border-color: var(--forge-ok); background: var(--forge-ok-soft); color: var(--forge-ok); }
.fel-forge .forge-opts li.is-wrong label { border-color: var(--forge-bad); background: var(--forge-bad-soft); color: var(--forge-bad); }
.fel-forge .forge-opts input { accent-color: var(--forge-accent); width: 1.05em; height: 1.05em; flex: 0 0 auto; }
/* An option the learner has run out of ticks for: a multi-select asks for TWO letters and two
   are taken, so the rest go unavailable until one is given back (forge.js → syncMultiCap). Muted
   and not-allowed, never hidden or red — the option is still a legitimate answer, just not a
   third one, and greying is the affordance every checkbox group uses for "no more of these".
   Left off the checked ones on purpose: they must stay clickable, since unticking is the way out. */
.fel-forge .forge-opts li.is-capped label { opacity: .55; cursor: not-allowed; }
.fel-forge .forge-opts li.is-capped label:hover { border-color: var(--forge-line); background: none; }

/* Image choice — "select the correct picture": options become a grid of picture tiles.
   Inherits the choice feedback (li.is-correct/.is-wrong label) for green/red borders. */
.fel-forge .forge-opts--img { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: .6rem; max-width: none; align-items: start; }
.fel-forge .forge-opts--img label { flex-direction: column; align-items: center; justify-content: center; text-align: center; gap: .4rem; padding: .5rem; }
.fel-forge .forge-opts--img input { position: absolute; width: 1px; height: 1px; opacity: 0; }
.fel-forge .forge-opts--img .forge-img { width: auto; max-width: 100%; max-height: 150px; height: auto; margin: 0; object-fit: contain; pointer-events: none; }
.fel-forge .forge-opts--img label:has(input:checked) { border-color: var(--forge-accent); background: var(--forge-soft); }

/* True / False — compact rows: statement left, inline options right */
.fel-forge .forge-tf-row { display: flex; align-items: center; gap: 1rem; justify-content: space-between; padding: .45rem 0; border-bottom: 1px solid var(--forge-line); }
.fel-forge .forge-tf-row:last-of-type { border-bottom: 0; }
/* `flex: 1 1 0` + `min-width: 0`, not `1 1 auto`: the row is wrap-enabled (the note needs a
   line of its own), and a wrap-enabled line is broken on the items' *hypothetical* sizes —
   so a statement whose unwrapped width exceeded the row pushed the True/False pills onto a
   second line, left-aligned, while short statements kept theirs on the right. With a zero
   basis the statement never asks for more than the space that is left, and wraps instead. */
.fel-forge .forge-tf-row .forge-q { margin: 0; font-weight: 500; font-size: 1.02rem; flex: 1 1 0; min-width: 0; }
.fel-forge .forge-opts--inline { display: flex; gap: .5rem; max-width: none; flex: 0 0 auto; }
.fel-forge .forge-opts--inline label { padding: .45rem 1.1rem; border-radius: 2rem; }
/* On a phone the statement + inline options don't fit one row — the statement collapsed to one word
   per line. Stack: statement full-width on its own line, options below it. */
@media (max-width: 640px) {
	.fel-forge .forge-tf-row { flex-direction: column; align-items: stretch; gap: .55rem; }
	.fel-forge .forge-tf-row .forge-opts--inline { flex: 0 0 auto; }
	/* Column direction — the zero basis above would be a zero *height*. Back to content-sized. */
	.fel-forge .forge-tf-row .forge-q { flex: 0 0 auto; }
	/* Three pills (True / False / Not Given) measure ~331px and a 390px phone gives the card
	   299px, so the set overflowed its own card — invisibly, because the page does not scroll
	   sideways: "Not Given" simply sat under the card's right edge. Two pills always fitted,
	   which is why this survived. Let them wrap and share the width instead. */
	.fel-forge .forge-tf-row .forge-opts--inline { flex-wrap: wrap; }
	.fel-forge .forge-tf-row .forge-opts--inline li { flex: 1 1 auto; }
	.fel-forge .forge-tf-row .forge-opts--inline label { justify-content: center; padding-inline: .7rem; }
}

/* Drag-to-reorder — unscramble (inline words) / sort (stacked blocks) */
.fel-forge .forge-reorder-list { list-style: none; margin: 0; padding: 0; display: flex; gap: .45rem; }
.fel-forge .forge-unscramble .forge-reorder-list { flex-wrap: wrap; }
.fel-forge .forge-sort .forge-reorder-list { flex-direction: column; }
/* ── Tiled exercises inside a ::: group — one numbered lane each ──────────────────────
   Several tile puzzles in one checkpoint are the hard case for this whole engine: every
   exercise is a field of identical pills, so two of them under each other read as one
   bank with too many chips in it. The DOM was never the problem — each block is its own
   .forge-ex with its own bank and slots, and forge_validate reports them separately — but
   the learner cannot see the DOM, and .45rem inside a wrapped row against .4rem of
   collapsed margin between rows told them nothing.

   Three things separate them, and it takes all three: a lane rule, air on both sides of
   it, and a number. The number is the one that actually answers "which sentence am I on" —
   the rule alone says *something* changed, not *what*. It is drawn from a CSS counter, so
   nothing is authored and nothing is stored: the lanes are numbered in document order,
   which is the order the learner works through them.

   :has() guards it to groups that hold more than one lane — a group with a single
   exercise, or one tiled exercise among prose, gets no number, because a lone "1" is a
   list of one. Kept as a descendant selector for the lane rules (a group nests, and a lane
   is a lane wherever it sits) but as a child selector for the counter, so nesting cannot
   restart the numbering halfway down.
   Adjacent-sibling, not :last-of-type — a group may mix tiled exercises with choices and
   prose, and only lane↔lane needs the rule. */
.fel-forge .forge-group :is(.forge-reorder, .forge-match) { margin: 0; padding: .9rem 0; }
.fel-forge .forge-group :is(.forge-reorder, .forge-match) + :is(.forge-reorder, .forge-match) {
	border-top: 1px solid var(--forge-line);
}
.fel-forge .forge-group :is(.forge-reorder, .forge-match):first-child { padding-top: 0; }

/* Whether the numbers are *shown* is one custom property, flipped by :has() on the group;
   every rule that positions them is a plain selector reading that property. Written this
   way rather than repeating the :has() on each rule because a `:has()` selector takes its
   argument's specificity — (0,4,0) here — and the narrow-screen overrides at the bottom
   would have silently lost to it. That exact failure has cost this project a day once
   already (materials.css, where a mobile block sat above the rules it was overriding). */
.fel-forge .forge-group { counter-reset: forge-lane; }
.fel-forge .forge-group:has(:is(.forge-reorder, .forge-match) + :is(.forge-reorder, .forge-match)) {
	--forge-lane-mark: counter(forge-lane);
	--forge-lane-pad: 1.9rem;
}
.fel-forge .forge-group > :is(.forge-reorder, .forge-match) {
	counter-increment: forge-lane;
	/* The number is an absolutely-positioned marker in the gutter, so it costs the lane no
	   width — the chips keep the full column, which matters at 390px where a bank of eight
	   words has none to spare. */
	position: relative;
	padding-left: var(--forge-lane-pad, 0);
}
.fel-forge .forge-group > :is(.forge-reorder, .forge-match)::before {
	/* `none` when the group holds a single lane, so no box is generated at all and a lone
	   "1" — a list of one — never appears. */
	content: var(--forge-lane-mark, none);
	position: absolute; left: 0; top: .9rem;
	font-size: .78rem; font-weight: 800; line-height: 1.6;
	color: var(--forge-faint);
	font-variant-numeric: tabular-nums;
}
.fel-forge .forge-group > :is(.forge-reorder, .forge-match):first-child::before { top: 0; }
@media (max-width: 560px) {
	/* 1.9rem of gutter is ~7% of a 390px screen and the chips need it back. The number goes
	   above the lane instead of beside it, which also puts it where the eye already is. */
	.fel-forge .forge-group > :is(.forge-reorder, .forge-match) { padding-left: 0; }
	.fel-forge .forge-group > :is(.forge-reorder, .forge-match)::before {
		position: static; display: block; margin-bottom: .35rem;
	}
}
.fel-forge .forge-chip {
	list-style: none; /* the floating drag-clone is an <li> outside the <ul> — kill its marker */
	font-size: .95rem; font-weight: 700;
	padding: .5em 1em;
	border: 1.5px solid var(--forge-line); border-radius: 999px;
	background: var(--forge-surface); color: var(--forge-ink);
	cursor: grab; touch-action: none; user-select: none;
	transition: border-color .15s, background .15s, color .15s;
}
.fel-forge .forge-chip:hover { border-color: var(--forge-accent); color: var(--forge-accent-ink); }
.fel-forge .forge-sort .forge-chip { border-radius: var(--forge-radius-sm); padding: .7em 1em; text-align: left; }
.fel-forge .forge-chip.is-dragging { opacity: .35; }
/* Floating clone that follows the cursor while dragging */
.fel-forge .forge-chip-ghost {
	position: fixed; z-index: 10000; pointer-events: none; margin: 0; left: 0; top: 0;
	background: var(--surface, #fff); box-shadow: 0 10px 26px rgba(15, 23, 42, .22);
	opacity: .97; cursor: grabbing;
}
.fel-forge .forge-reorder.is-locked .forge-chip { cursor: default; }
/* Per-position feedback: correct words/blocks lock green (become anchors); the rest
   stay draggable and neutral (no "all-red"). */
.fel-forge .forge-reorder .forge-chip.is-correct { border-color: var(--forge-ok); background: var(--forge-ok-soft); color: var(--forge-ok); }
.fel-forge .forge-reorder .forge-chip.is-revealed { border-color: var(--forge-ok); border-style: dashed; }
.fel-forge .forge-reorder .forge-chip.is-locked { cursor: default; }

/* Match / drag-fill — word bank on top (full width), fixed left column, empty slots.
   The bank is a bare row of chips under a caption, NOT a box: the box shape is reserved
   for answer areas, so a filled bank can never be mistaken for the answer. It is still a
   drop target (drag a chip back here to clear its slot), so the dashed outline fades in
   only while a drag is over it — the border is transparent, not absent, so nothing shifts. */
.fel-forge .forge-bank {
	display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; margin: 0 0 1rem;
	padding: .5rem .55rem; min-height: 2.9rem;
	border: 1.5px dashed transparent; border-radius: var(--forge-radius-sm);
	transition: border-color .14s, background .14s;
}
.fel-forge .forge-bank.is-over { border-color: var(--forge-accent); background: var(--forge-soft); }
.fel-forge .forge-bank-lbl {
	flex: 1 0 100%; margin-bottom: -.15rem;
	font-size: .72rem; font-weight: 800; letter-spacing: .05em; text-transform: uppercase;
	color: var(--forge-faint);
}
.fel-forge .forge-match-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .5rem; }
.fel-forge .forge-match-row { display: grid; grid-template-columns: minmax(6rem, 1fr) 2fr; align-items: stretch; gap: .8rem; }
/* NOT display:flex. A label is `**25** Use of local products`, so the row holds an element and a
   text run — as flex children those become two flex items, and the space between them is leading
   white space in an anonymous item, which flex discards. That is the whole of the reported
   `28products related to`: no margin was missing, the space was never rendered. It only showed on
   short labels because the author had put a second one in the source. A block box keeps the label
   as one flow of inline content (bold number, space, prose, wrapping where it likes) and
   align-self does the vertical centring the flexbox was there for. */
.fel-forge .forge-match-left { align-self: center; padding: .45rem .8rem; font-size: .97rem; font-weight: 700; }
/* Slot column sized from the widest chip (--forge-slot-ch, set per block by the renderer) instead
   of a flat 2fr: a one-letter IELTS answer needs ~4rem, not 370px, and everything it gives back
   goes to the label that was breaking over three lines. Floor keeps the drop target thumb-sized;
   45% ceiling keeps a long chip from re-creating the problem in the other direction. Scoped to
   ::: match — ::: translate and ::: complete reuse .forge-match-row with layouts of their own. */
.fel-forge .forge-match > .forge-match-list > .forge-match-row {
	grid-template-columns: minmax(6rem, 1fr) clamp(4rem, calc(var(--forge-slot-ch, 12) * 1ch + 2.9rem), 45%);
}
.fel-forge .forge-slot { display: flex; align-items: center; justify-content: center; min-height: 2.9rem; border: 1.5px dashed var(--forge-line-strong); border-radius: .7rem; background: var(--forge-panel); padding: 4px; transition: border-color .14s, background .14s; }
.fel-forge .forge-slot.is-over { border-color: var(--forge-accent); background: var(--forge-soft); }
/* Chips wrap their text so phrases (not just words) fit and slots stretch. */
.fel-forge .forge-bank-chip { max-width: 100%; white-space: normal; line-height: 1.3; }
.fel-forge .forge-bank-chip.is-selected { outline: 2px solid var(--forge-accent); outline-offset: 1px; }
/* image on a chip (word ↔ picture match) stays compact */
.fel-forge .forge-bank-chip .forge-img { max-height: 56px; width: auto; margin: 0; vertical-align: middle; }
.fel-forge .forge-slot .forge-bank-chip { flex: 1 1 auto; display: flex; align-items: center; justify-content: center; text-align: center; }
.fel-forge .forge-match-row.is-correct .forge-bank-chip { border-color: var(--forge-ok); background: var(--forge-ok-soft); color: var(--forge-ok); }
.fel-forge .forge-match-row.is-wrong .forge-bank-chip { border-color: var(--forge-bad); background: var(--forge-bad-soft); color: var(--forge-bad); }
.fel-forge .forge-match-row.is-revealed .forge-bank-chip { border-color: var(--forge-ok); border-style: dashed; }
.fel-forge .forge-match-row.is-locked .forge-bank-chip { cursor: default; }

/* Word-bank translate — reuses the match engine, but the "rows" are positional slots
   laid out as one wrapping sentence line under the prompt. */
.fel-forge .forge-translate-prompt { font-weight: 600; margin: 0 0 .85rem; }
.fel-forge .forge-answer-line { flex-direction: row; flex-wrap: wrap; gap: .45rem; align-items: center; align-content: center; padding: .7rem .8rem; border: 1.5px dashed var(--forge-line-strong); border-radius: var(--forge-radius-sm); background: var(--forge-panel); min-height: 3.4rem; }
.fel-forge .forge-translate .forge-match-row { display: block; grid-template-columns: none; }
.fel-forge .forge-translate .forge-slot { min-width: 3rem; min-height: 2.4rem; }
/* The answer line already supplies the panel fill, so flip its slots to the surface colour —
   each blank reads as a hole instead of a box-on-a-box of the same shade. */
.fel-forge .forge-answer-line .forge-slot { background: var(--forge-surface); }
.fel-forge .forge-translate .forge-slot .forge-bank-chip { white-space: nowrap; }
/* Terminal punctuation — fixed, non-draggable, pinned at the end of the line. */
.fel-forge .forge-answer-line .forge-punct { list-style: none; display: flex; align-items: flex-end; padding: 0 .15rem .35rem; font-weight: 600; color: var(--forge-muted); }

/* Dictation ("tap what you hear") — audio player above the bank: a primary Play button
   and a quieter, smaller Slow button. */
.fel-forge .forge-dictate-audio { display: flex; align-items: center; gap: .55rem; margin: 0 0 1rem; }
.fel-forge .forge-play {
	display: inline-flex; align-items: center; gap: .4rem;
	font: inherit; font-weight: 700; cursor: pointer;
	border: 0; border-radius: 2rem;
	background: var(--forge-accent); color: var(--forge-on-accent);
	padding: .55rem 1.1rem; box-shadow: 0 6px 16px -6px var(--forge-accent);
	transition: background .15s, transform .12s;
}
.fel-forge .forge-play:hover { background: var(--forge-accent-ink); }
.fel-forge .forge-play:active { transform: translateY(1px); }
.fel-forge .forge-play-slow { padding: .5rem 1rem; font-size: .85em; background: var(--forge-surface); border: 1px solid var(--forge-line); color: var(--forge-muted); box-shadow: none; }
.fel-forge .forge-play-slow:hover { background: var(--forge-soft); border-color: var(--forge-accent); color: var(--forge-accent-ink); }
.fel-forge .forge-play-slow svg { width: 17px; height: 17px; }
.fel-forge .forge-play-slow span { font-weight: 600; }
.fel-forge .forge-dictate-pending { font-size: .9em; font-style: italic; color: var(--forge-muted); margin: 0 0 1rem; }

/* Type what you hear — audio + a full-width text box for the whole sentence.
   The box is the thing being asked for, and it was losing to its own Play button: 34px of
   pale fill under a 48px accent pill, three of them stacked, so the eye read a column of
   buttons with grey strips between them. It matches the button's height now, sits on the
   surface colour with the stronger rule, and says "field" before it is focused. */
.fel-forge .forge-typehear .forge-blank { display: block; }
.fel-forge .forge-typehear .forge-gap--wide {
	width: 100%; min-width: 0; text-align: left;
	padding: .72rem 1rem; font-size: 1rem; line-height: 1.5;
	background-color: var(--forge-surface);
	border-color: var(--forge-line-strong);
}
/* Grouping: the audio belonged to the box below it, but the gap inside a question (1rem) was
   *larger* than the gap between two questions (.4rem+.4rem collapsed), so the pairs read as
   the wrong pairs. Tighten inside, separate between. */
.fel-forge .forge-typehear .forge-dictate-audio { margin-bottom: .5rem; }
.fel-forge .forge-group .forge-typehear + .forge-typehear { margin-top: 1.4rem; }

/* ── The closing verdict — `::: outcome` ──────────────────────────────────────────────
   What the paper *meant*, once it has been sat. Every band is rendered and hidden here;
   forge.js adds .is-shown to the panel and .is-hit to the one that matched — with JS off
   nothing appears, exactly like every other piece of feedback, and with JS on the bands
   the learner did not land in stay hidden rather than reading as a menu.

   Deliberately heavier than a card: it is the last thing on the page and the answer to the
   question the whole test was solved for. The accent rule down the side is the same device
   a `>> note` uses, at the scale of a whole worksheet rather than one item. */
.fel-forge .forge-outcome { display: none; }
.fel-forge .forge-outcome.is-shown {
	display: block;
	margin: 1.6rem 0 .9rem;
	padding: 1.2rem 1.4rem;
	border: 1px solid var(--forge-line);
	border-left: 4px solid var(--forge-accent);
	border-radius: var(--forge-radius);
	background: var(--forge-surface);
}
.fel-forge .forge-outcome-head {
	margin: 0;
	font-size: .74rem; font-weight: 800; letter-spacing: .05em; text-transform: uppercase;
	color: var(--forge-faint);
}
/* The number, and the two words that stop it contradicting the Check button above it.
   Tabular figures so "9 / 40" and "24 / 40" do not shuffle the line when it updates. */
.fel-forge .forge-outcome-score {
	margin: .15rem 0 .7rem;
	font-size: 1.45rem; font-weight: 800; line-height: 1.2;
	font-variant-numeric: tabular-nums;
	color: var(--forge-ink);
}
/* `~ …` — where the boundaries came from. It belongs to the whole panel rather than to the
   band that matched, so it is always visible once the panel is, and quiet enough not to
   compete with the sentence above it. */
.fel-forge .forge-outcome-note {
	margin: .7rem 0 0;
	font-size: .84rem; line-height: 1.5;
	color: var(--forge-muted);
}
.fel-forge .forge-outcome-note + .forge-outcome-note { margin-top: .3rem; }
.fel-forge .forge-outcome-bands { list-style: none; margin: 0; padding: 0; }
.fel-forge .forge-outcome-band { display: none; }
.fel-forge .forge-outcome-band.is-hit {
	display: block;
	font-size: 1rem; font-weight: 500; line-height: 1.55;
	color: var(--forge-ink);
}
.fel-forge .forge-outcome-band .forge-muted { color: var(--forge-muted); font-weight: 400; }

/* The question-bank panel — how far through the bank, and the way to the next set.
   A quiet row, not a second Check: the questions above it are the exercise, and this is the
   furniture around them. It sits inside the pool so it moves with it in a multipage sheet. */
.fel-forge .forge-pool-foot {
	display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap;
	margin: .2rem 0 .4rem; padding-top: .8rem;
	border-top: 1px solid var(--forge-line);
}
.fel-forge .forge-pool-count { font-size: .9rem; color: var(--forge-muted); }
.fel-forge .forge-pool-next {
	font: inherit; font-size: .92rem; font-weight: 600; cursor: pointer;
	padding: .5em 1.2em; border-radius: 999px;
	border: 1.5px solid var(--forge-accent); background: transparent; color: var(--forge-accent-ink);
	transition: background .14s, color .14s, opacity .14s;
}
.fel-forge .forge-pool-next:hover:not([disabled]) { background: var(--forge-accent); color: var(--forge-on-accent); }
/* Disabled until every question in the set has been checked — which is exactly when the
   server's round turns. Muted rather than hidden: it is the answer to "and then what?", and
   a button that appears from nowhere is a button nobody was waiting for. */
.fel-forge .forge-pool-next[disabled] { opacity: .45; cursor: default; border-color: var(--forge-line-strong); color: var(--forge-muted); }

/* Given letters — `{{wealthy ^2}}` and `station ^1`.
 *
 * They sit in a <span> in front of the field, so the box has to move up to the wrapper or
 * the learner sees two boxes. Muted and unselectable: the point of the scaffold is that
 * "we" reads as *given* and the rest of the field as *waiting*, which is the one thing the
 * first cut got wrong (the letters were the input's value, so they looked typed and the
 * gap looked answered).
 *
 * Placed after the .forge-gap and .forge-typehear blocks deliberately: same specificity,
 * so this is ordering, not a specificity fight. `:has()` moves the verdict colours to the
 * wrapper — the state classes stay on the input, where the JS has always put them. */
.fel-forge .forge-blank--given {
	display: inline-flex; align-items: stretch;
	border-radius: var(--forge-gap-radius);
	border-bottom: 2.5px solid var(--forge-accent);
	background: var(--forge-soft);
	transition: border-color .15s, background .15s, box-shadow .15s;
}
.fel-forge .forge-blank--given .forge-given {
	font: inherit; line-height: 1.4; font-size: .95em; font-weight: 700;
	padding: .28em .04em .28em .7em;
	color: var(--forge-accent-ink); opacity: .45;
	user-select: none; cursor: text;
}
.fel-forge .forge-blank--given .forge-gap {
	min-width: 4em; text-align: left; padding-left: 0;
	background: none; border-bottom-color: transparent;
}
/* The box's focus ring belongs to the box, not to half of it. */
.fel-forge .forge-blank--given:focus-within { background-color: var(--forge-surface); box-shadow: 0 0 0 3px var(--forge-soft); }
.fel-forge .forge-blank--given .forge-gap:focus { background: none; box-shadow: none; }
.fel-forge .forge-blank--given:has(.forge-gap.is-correct) { border-color: var(--forge-ok); background-color: var(--forge-ok-soft); }
.fel-forge .forge-blank--given:has(.forge-gap.is-wrong) { border-color: var(--forge-bad); background-color: var(--forge-bad-soft); }
.fel-forge .forge-blank--given:has(.forge-gap.is-revealed) { border-color: var(--forge-ok); border-bottom-style: dashed; }
.fel-forge .forge-blank--given:has(.forge-gap.is-correct) .forge-given { color: var(--forge-ok); }
.fel-forge .forge-blank--given:has(.forge-gap.is-wrong) .forge-given { color: var(--forge-bad); }
.fel-forge .forge-blank--given .forge-gap:is(.is-correct, .is-wrong, .is-revealed) { background: none; border-bottom-color: transparent; }

/* The same thing on the full-width "type what you hear" box. */
.fel-forge .forge-typehear .forge-blank--given {
	display: flex; align-items: center; width: 100%;
	border: 1.5px solid var(--forge-line-strong); border-radius: var(--forge-radius-sm);
	background-color: var(--forge-surface);
}
.fel-forge .forge-typehear .forge-blank--given .forge-given {
	font-size: 1rem; font-weight: 600; line-height: 1.5;
	padding: .72rem 0 .72rem 1rem;
	color: var(--forge-ink); opacity: .45;
}
.fel-forge .forge-typehear .forge-blank--given .forge-gap--wide {
	border: 0; background: none; padding-left: 0;
}
.fel-forge .forge-typehear .forge-blank--given:focus-within { border-color: var(--forge-accent); box-shadow: 0 0 0 3px var(--forge-soft); }
.fel-forge .forge-typehear .forge-blank--given:has(.forge-gap.is-correct) { border-color: var(--forge-ok); background-color: var(--forge-ok-soft); }
.fel-forge .forge-typehear .forge-blank--given:has(.forge-gap.is-wrong) { border-color: var(--forge-bad); background-color: var(--forge-bad-soft); }

/* Complete the translation — gaps inline in a sentence, filled from the bank below. Each
   gap is a match-row, so it reuses the drag/tap + per-slot green-lock; only the layout
   changes from a column to inline. */
.fel-forge .forge-complete-line { line-height: 2.9; margin: 0 0 1rem; }
.fel-forge .forge-complete .forge-match-row.forge-complete-gap { display: inline-flex; grid-template-columns: none; vertical-align: middle; }
/* The empty gap is already a full chip's height, so dropping a chip in doesn't shove the line
   apart; the in-slot chip is shortened to sit inside it. */
.fel-forge .forge-complete .forge-complete-gap .forge-slot { min-width: 5em; min-height: 2.5rem; display: inline-flex; align-items: center; }
.fel-forge .forge-complete .forge-complete-gap .forge-slot .forge-bank-chip { padding: .25em .7em; line-height: 1.25; }
.fel-forge .forge-complete .forge-bank { margin: .3rem 0 0; }

/* Highlight / tap-to-select ("mark the words") — a text of tappable words; correct ones
   are `*marked*` in the source. Multi-select choice with inline word tokens (no extra JS). */
.fel-forge .forge-opts.forge-mark-text { display: block; list-style: none; margin: 0 0 .3rem; padding: 0; max-width: none; line-height: 2.1; }
.fel-forge .forge-mark-text li { display: inline; }
.fel-forge .forge-mark-text label { display: inline; padding: 0; border: 0; background: none; cursor: pointer; }
.fel-forge .forge-mark-text label:hover { background: none; }
.fel-forge .forge-mark-text input { position: absolute; width: 1px; height: 1px; opacity: 0; }
.fel-forge .forge-mark-word { padding: .18em .5em; border-radius: .5em; font-weight: 600; box-shadow: inset 0 0 0 1.5px transparent; transition: background .12s, color .12s, box-shadow .12s; }
.fel-forge .forge-mark-word:hover { background: var(--forge-soft); }
.fel-forge .forge-mark-text label:has(input:checked) .forge-mark-word { background: var(--forge-soft); color: var(--forge-accent-ink); box-shadow: inset 0 0 0 1.5px var(--forge-accent); }
.fel-forge .forge-mark-text li.is-correct label, .fel-forge .forge-mark-text li.is-wrong label { background: none; border-color: transparent; }
.fel-forge .forge-mark-text li.is-correct .forge-mark-word,
.fel-forge .forge-mark-text li.is-correct label:has(input:checked) .forge-mark-word { background: var(--forge-ok-soft); color: var(--forge-ok); box-shadow: inset 0 0 0 1.5px var(--forge-ok); }
.fel-forge .forge-mark-text li.is-wrong .forge-mark-word,
.fel-forge .forge-mark-text li.is-wrong label:has(input:checked) .forge-mark-word { background: var(--forge-bad-soft); color: var(--forge-bad); box-shadow: inset 0 0 0 1.5px var(--forge-bad); }
.fel-forge .forge-mark-sep { display: inline; white-space: pre-wrap; }

/* Tap the pairs (vanishing pairs) — tiles sized to their word (flex-wrap), tap a tile
   then its partner. (A rigid equal grid clipped long words.) */
.fel-forge .forge-pairs-grid { display: flex; flex-wrap: wrap; gap: .5rem; margin: 0 0 .5rem; }
.fel-forge .forge-pair-tile {
	font: inherit; font-size: .95rem; font-weight: 700; cursor: pointer; white-space: nowrap;
	padding: .6rem 1.1rem; border: 1.5px solid var(--forge-line); border-radius: 999px;
	background: var(--forge-surface); color: inherit;
	transition: border-color .15s, background .15s, opacity .25s, transform .25s;
}
.fel-forge .forge-pair-tile:hover { border-color: var(--forge-accent); }
.fel-forge .forge-pair-tile.is-sel { border-color: var(--forge-accent); background: var(--forge-soft); font-weight: 600; }
.fel-forge .forge-pair-tile.is-match { border-color: var(--forge-ok); background: var(--forge-ok-soft); color: var(--forge-ok); }
.fel-forge .forge-pair-tile.is-bad { border-color: var(--forge-bad); background: var(--forge-bad-soft); animation: forge-shake .35s; }
.fel-forge .forge-pair-tile.is-gone { opacity: 0; transform: scale(.82); pointer-events: none; }
/* A finished board is all-.is-gone: invisible, but the tiles still hold their grid space, so
   the card would wrap a large void. Collapse the board once it's done. Reset drops .is-pass
   (status() in forge.js) and the board comes straight back. */
.fel-forge .forge-pairs:has(.forge-fb.is-pass) .forge-pairs-grid { display: none; }
@keyframes forge-shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-4px); } 75% { transform: translateX(4px); } }

/* Tap the missing word — sentence with an inline gap + option tiles below. The gap is a
   readonly .forge-gap input (reuses the green/red feedback); tiles write into it. */
.fel-forge .forge-tapword-line { line-height: 2; margin: 0 0 .9rem; }
.fel-forge .forge-tap-value { text-align: center; min-width: 5em; margin-inline: .35em; cursor: default; caret-color: transparent; }

/* Broken/not-yet-added image → a labelled placeholder tile (shows the alt text) so the
   slot is never invisible while authoring. Swapped in by forge.js on image load error. */
.fel-forge .forge-img-fallback {
	display: inline-flex; align-items: center; justify-content: center;
	min-width: 120px; min-height: 90px; padding: .5rem; box-sizing: border-box;
	border: 1px dashed var(--forge-line); border-radius: var(--forge-radius-sm);
	color: var(--forge-muted); font-size: .85em; text-align: center; line-height: 1.3;
	background: repeating-linear-gradient(45deg, transparent, transparent 9px,
		color-mix(in oklch, currentColor 5%, transparent) 9px,
		color-mix(in oklch, currentColor 5%, transparent) 18px);
}
.fel-forge .forge-opts--img .forge-img-fallback { width: 100%; }
.fel-forge .forge-tap-opts { display: flex; flex-wrap: wrap; gap: .5rem; }
.fel-forge .forge-tap-opt {
	font: inherit; font-size: .95rem; font-weight: 700; cursor: pointer;
	padding: .55rem 1.1rem; border: 1.5px solid var(--forge-line); border-radius: 999px;
	background: var(--forge-surface); color: inherit;
	transition: border-color .15s, background .15s, opacity .15s;
}
.fel-forge .forge-tap-opt:hover { border-color: var(--forge-accent); }
.fel-forge .forge-tap-opt.is-selected { border-color: var(--forge-accent); background: var(--forge-soft); color: var(--forge-accent-ink); font-weight: 600; }
.fel-forge .forge-tap-opts.is-locked { pointer-events: none; }
.fel-forge .forge-tap-opts.is-locked .forge-tap-opt { opacity: .5; }
.fel-forge .forge-tap-opts.is-locked .forge-tap-opt.is-selected { opacity: 1; }

/* Audio */
.fel-forge .forge-audio { margin: .75rem 0; }
.fel-forge .forge-audio audio { width: 100%; }
.fel-forge .forge-audio figcaption { font-size: .85em; color: var(--forge-muted); margin-top: .25rem; }

/* Check button + feedback */
.fel-forge .forge-check {
	font: inherit; font-weight: 700; font-size: .9rem;
	padding: .72em 1.7em;
	/* forge.js swaps the label "Check" → "Done ✓", which is ~7px wider. Without a floor the
	   button resizes under the cursor on the very click that grades the exercise. */
	min-width: 7.25em;
	border: 0; border-radius: .7rem; cursor: pointer;
	background: var(--forge-accent); color: var(--forge-on-accent);
	/* Keep the negative spread small. At -10px the shadow was 20px narrower than the button, so
	   it stopped short of the side edges and the gap under the corners read as a white stripe
	   between the button and its shadow. -6px keeps it floating but hugging. */
	box-shadow: 0 6px 16px -6px var(--forge-accent);
	transition: background .15s, box-shadow .15s, transform .12s, opacity .15s;
}
/* `:not(.is-done)` is load-bearing: with `:hover:not(:disabled)` at (0,4,0) this rule outranks
   `.is-done` at (0,3,0), so hovering a finished exercise repainted its green Done badge accent
   blue, as if it were live again. */
.fel-forge .forge-check:hover:not(:disabled):not(.is-done) { background: var(--forge-accent-ink); transform: translateY(-1px); }
/* Done is a soft-lime pill, not a solid one: it's a finished state, not an action, so it steps
   back. Its outline is an inset shadow rather than a border — same reason as .forge-reset below.
   A real 1.5px border rounds to 1px on screen while padding compensation subtracts the full
   1.5px, which left Done a pixel shorter than the Reset beside it. */
/* …but not *filled*, and not in the answers' green. `--forge-ok-soft` is exactly what a
   correct chip, gap and option are filled with, so a finished exercise ended in a stack of
   green tiles with a sixth green tile that happens to be the control. A deeper green fill was
   the first attempt and it was the wrong axis: darker or lighter, a green box under a column
   of green boxes is still one of them. What separates a control from an answer here is the
   *material* — answers are filled, controls are outlined. So Done keeps the surface it shares
   with the Reset beside it and says "finished" in the lime rule and the lime label. */
.fel-forge .forge-check.is-done {
	background: var(--forge-surface);
	color: var(--forge-ok);
	box-shadow: inset 0 0 0 1.5px var(--forge-ok-fill);
}
/* Not answerable yet (forge.js gates Check until every open item has an answer). A faded
   accent fill read as "broken" or "still loading" — and dropping the label to 45% opacity put
   it under contrast. A neutral outline reads as "not yet" instead, and lets the button *fill
   in* with accent the moment the exercise becomes answerable: the state change is the cue. */
.fel-forge .forge-check:disabled {
	background: transparent; color: var(--forge-faint);
	box-shadow: inset 0 0 0 1.5px var(--forge-line);
	opacity: 1; cursor: not-allowed; transform: none;
}
/* Reset / try-again — icon-only. Same height and radius as Check so the two read as one
   control group; ghost so it never competes with the primary action. (As a text button it
   differed from Check in both size and shape, which looked accidental rather than deliberate.) */
.fel-forge .forge-reset {
	display: inline-flex; align-items: center; justify-content: center;
	font: inherit; font-size: .9rem; line-height: 1.6;
	margin-left: .5rem; padding: .72em;
	/* Outline via inset shadow, not a border: a 1px border would make this 2px taller than
	   Check, whose padding and line-height it otherwise matches exactly. */
	border: 0; border-radius: .7rem; box-shadow: inset 0 0 0 1px var(--forge-line);
	background: transparent; color: var(--forge-muted); cursor: pointer;
	transition: color .15s, box-shadow .15s, background .15s;
}
.fel-forge .forge-reset svg { width: 1.6em; height: 1.6em; display: block; }
.fel-forge .forge-reset:hover { color: var(--forge-accent-ink); box-shadow: inset 0 0 0 1px var(--forge-accent); background: var(--forge-soft); }
/* ── Focus, once, for every control that takes the standard ring ───────────────────────────
   WordPress core ships `:where(.wp-site-blocks :focus){outline-width:2px;outline-style:solid}`.
   Note `:focus`, NOT `:focus-visible` — so it fires on mouse clicks too and then sits there until
   you click elsewhere. It sets no colour, so the ring is `currentColor`, and what that looks like
   depends on the control it lands on: a dark box round a contents row, colliding with the rows 5px
   either side; an invisible white halo on a white-text button; a stray band between a button and
   its drop shadow. Anything focusable we don't name here inherits it.
   This was fixed four times in four places before it was written down once. The list below is
   both the fix and the register: a new interactive control goes in it. Controls that need a
   bespoke treatment style themselves next to their own rules — the gaps (fill + ring), the
   glosses (needs a radius, being inline) and the seek thumb.
   The register was still being kept by hand and still incomplete — the dictation Play and Slow
   buttons, the listening player's Slow and Reveal, the mobile contents button and the hint
   marker were all missing, which is why a tapped Play kept a **white** halo (its own
   `currentColor` on the accent fill) and a tapped Slow a dark one. So the rule below no longer
   names controls one by one: it takes every `<button>` and every focusable control inside
   `.fel-forge`, and the handful that need their own treatment override it *after* — the gaps
   (fill + ring), the glosses (needs a radius, being inline) and the seek thumb, each beside
   its own rules. A new control now arrives with the ring already on it. */
.fel-forge :is(button, [tabindex], summary, input, select):focus { outline: none; }
.fel-forge :is(button, [tabindex], summary, input, select):not(.forge-gap, .forge-listen-seek):focus-visible { outline: 2px solid var(--forge-accent); outline-offset: 2px; }
/* Check, Reset and the verdict share one line as inline siblings, not flex children. So they
   must agree on the top margin as well as `vertical-align`: `middle` centres each element's
   *margin* box, so a margin-top on Check alone pushed it ~9px below the other two. */
.fel-forge .forge-check,
.fel-forge .forge-reset,
.fel-forge .forge-fb { vertical-align: middle; margin-top: 1.1rem; }

/* inline-block, not inline: .forge-fb is a <span>, and vertical margins do nothing on a
   non-replaced inline box — so it ignored the shared margin-top and sat ~9px above the buttons. */
.fel-forge .forge-fb { display: inline-block; margin-left: .8rem; font-weight: 700; font-size: .875rem; }
.fel-forge .forge-fb.is-pass { color: var(--forge-ok); }
.fel-forge .forge-fb.is-partial { color: var(--forge-bad); }
/* Verdict glyph. Safe as generated content: forge.js writes the label with textContent, which
   replaces child nodes and never touches ::before.
   Only "wrong" gets a badge: on a passing exercise the Check button already reads "Done ✓", so a
   second tick beside it was saying the same thing twice. */
.fel-forge .forge-pairs .forge-fb.is-pass::before,
.fel-forge .forge-fb.is-partial::before {
	display: inline-flex; align-items: center; justify-content: center;
	width: 1.2em; height: 1.2em; margin-right: .45em; vertical-align: -.25em;
	border-radius: 50%; color: var(--forge-on-accent);
	font-size: .92em; font-weight: 700; line-height: 1;
}
.fel-forge .forge-fb.is-partial::before { content: "✕"; background: var(--forge-bad); }
/* …except pairs, which self-grades and therefore has NO Check button to carry the tick. Its
   feedback is the only thing on the card that can say "done", and forge.js deliberately leaves the
   ✓ out of the text so it isn't drawn twice — so the badge has to stay here. */
.fel-forge .forge-pairs .forge-fb.is-pass::before { content: "✓"; background: var(--forge-ok-fill); }

.fel-forge .forge-todo { opacity: .6; font-size: .9em; }

@media (max-width: 480px) {
	/* Check + Reset stay side-by-side on one row (they are inline siblings) — only the feedback wraps
	   to its own line below. width:100% on Check used to shove Reset onto a second row. */
	.fel-forge .forge-check { width: auto; }
	.fel-forge .forge-fb { display: block; margin: .5rem 0 0; }
}

/* ── Flat worksheet: the progress column ─────────────────────────────────────
   Built by forge.js (it's chrome, and must vanish without JS). Deliberately thin: the card and
   the section list ARE the book's components, so there is one progress vocabulary across both
   layouts and one place to retune it. Only the column's own frame lives here. */
.fel-forge .forge-flat-body { display: flex; gap: 2.2rem; align-items: flex-start; }
/* The sticky column clears the site header via the shared token — the header is position:sticky
   top:0 z-30 at every width, so a bare top:1rem parked the column's head behind it. Same rule as .fbar
   and the mobile lane: anything pinned on this site offsets by --fel-header-h. */
.fel-forge .forge-side {
	flex: 0 0 16.5rem; position: sticky; top: calc(var(--fel-header-h, 74px) + 1rem);
	display: flex; flex-direction: column; max-height: calc(100vh - 2rem);
	/* min-width:0 is load-bearing, not tidiness — see the note on .forge-mp-toc below. */
	min-height: 0; min-width: 0;
}
.fel-forge .forge-flat-main { flex: 1 1 auto; min-width: 0; }
@media (max-width: 820px) {
	/* Progress is motivation, not navigation, and the owner's rule is that it stays in front of the
	   learner at every width. So the flat sheet no longer hides its column on a phone — it splits it.
	   The column can't simply go sticky here: it precedes the content and its box ends before the
	   exercises do, so a sticky child would scroll away with it. Instead display:contents dissolves
	   the column's box and lets the progress card and the item list become flow siblings of the body,
	   where the progress card can be pinned on its own (below) while the item list scrolls away. */
	.fel-forge .forge-flat-body { display: block; }
	.fel-forge .forge-side { display: contents; }

	/* THE PINNED PROGRESS STRIP — shared by the flat sheet AND the book (both build .forge-mp-progress;
	   the book's mobile block relies on this rule, it does not repeat it). Progress rides in front of
	   the learner the whole way down.
	   Sticks under the site header, NOT at 0: .fel-site-header is position:sticky; top:0; z-index:30 at
	   every width and its .nav is 74px tall with no mobile override, so top:0 would slide the strip
	   under the header (the exact bug .fbar has on Free Materials ≤1000px). z-index stays below 30 so
	   the header always wins. Surface bg is inherited from the desktop rule and is opaque, so content
	   scrolls invisibly beneath it. */
	/* THIN GLUED LANE (owner-approved). One line per bar, 3px bars, tight padding. Opaque surface is
	   the PRIMARY look — it reads as a lane fixed to the top, not a translucent panel; a blurred
	   translucent variant lives commented below if we ever want to match the header's glass. Card
	   chrome is trimmed to just a bottom hairline + a soft downward shadow, enough to separate it from
	   the text beneath without looking like a floating window in the flow.
	   The `.fel-design` prefix is load-bearing, not decoration: the multipage BASE rule
	   `.fel-forge .forge-mp-progress` (below, ~line 560) re-declares padding/gap/margin/border/radius
	   at EQUAL specificity but LATER in source, so without this bump it silently won and the book strip
	   kept the full desktop card (measured 86px tall, full border) on a phone. `.fel-design` is on
	   <body> site-wide, so (0,3,0) beats the base (0,2,0) whatever the source order. */
	.fel-design .fel-forge .forge-mp-progress {
		position: sticky; top: var(--fel-header-h, 74px); z-index: 20;
		width: 100vw; margin: 0 0 1rem; margin-inline: calc(50% - 50vw);
		padding: .34rem 1.15rem .4rem; gap: .28rem;
		background: var(--forge-surface);
		border: 0; border-bottom: 1px solid var(--forge-line); border-radius: 0;
		box-shadow: 0 6px 12px -9px oklch(0.4 0.1 262 / .32);
	}
	/* Translucent-glass alternative (match the site header). Enable INSTEAD of the two bg lines above:
	   background: color-mix(in oklab, var(--forge-surface) 82%, transparent); backdrop-filter: blur(10px); */
	/* PINNED BAND — FULLSCREEN ONLY, book only. The parser wraps the Fullscreen button + the progress
	   strip in <div class="forge-mp-pinned">. In fullscreen the exit button must stay reachable while
	   scrolling (prior regression) AND must not shift the strip's pin point — two separate stickies
	   OVERLAP (measured: both pin to 0, strip paints over the button), so in fullscreen button + strip
	   share ONE sticky wrapper. OUTSIDE fullscreen the wrapper is display:contents (below): the strip
	   pins alone as the thin 57px lane and the enter-fullscreen button scrolls away, exactly as today —
	   pinning it there would have bloated the normal-view lane to ~100px. */
	.fel-design .fel-forge .forge-multipage:fullscreen .forge-mp-pinned {
		position: sticky; top: var(--fel-header-h, 74px); z-index: 20;
		display: flex; flex-direction: column; gap: .34rem;
		/* NOT full-bleed in fullscreen: the fullscreen container is already ~viewport-wide with its own
		   .75rem gutter, so a 100vw child overflowed to the right and clipped the OVERALL numbers off-screen
		   (owner's shot). Fill the padded container instead. Full-bleed stays on the normal-view lane. */
		width: auto; margin: 0 0 1rem; margin-inline: 0;
		padding: .38rem .85rem .44rem;
		background: var(--forge-surface);
		border-bottom: 1px solid var(--forge-line);
		border-radius: 0;
		box-shadow: 0 6px 12px -9px oklch(0.4 0.1 262 / .32);
	}
	.fel-forge .forge-multipage:fullscreen .forge-mp-pinned .forge-mp-fs { margin: 0; }
	.fel-design .fel-forge .forge-multipage:fullscreen .forge-mp-pinned .forge-mp-progress {
		position: static; margin: 0; padding: 0; gap: .28rem;
		/* Undo the normal lane's full-bleed here too: the strip still carried width:100vw +
		   margin-inline:calc(50% - 50vw), so inside the padded fullscreen container it ran off the
		   right and clipped the OVERALL numbers. The band already fills the container; the strip fills
		   the band. */
		width: auto; margin-inline: 0;
		background: none; border: 0; border-radius: 0; box-shadow: none;
	}
	/* Collapse each bar onto ONE line — label · bar · numbers — instead of the desktop two-line prow
	   (.forge-mp-bar's flex:1 0 100% pushes the bar below its label). On a 390px phone the two-bar
	   strip ran ~110px, a third of the screen (owner's real-device screenshot). Single-line prows +
	   tighter padding roughly halve it. BOTH bars stay — sectional progress is motivation, not chrome. */
	.fel-forge .forge-mp-progress .forge-mp-prow { flex-wrap: nowrap; align-items: center; gap: .5rem; }
	.fel-forge .forge-mp-progress .forge-mp-plabel { order: 1; flex: 0 0 auto; }
	.fel-forge .forge-mp-progress .forge-mp-bar { order: 2; flex: 1 1 auto; min-width: 0; margin: 0; height: 3px; }
	.fel-forge .forge-mp-progress .forge-mp-stats,
	.fel-forge .forge-mp-progress .forge-mp-sec-stats { order: 3; flex: 0 0 auto; margin-left: 0; }
	/* The item list is the navigation half: framed as its own card, free to leave the viewport. */
	.fel-forge .forge-mp-toc-list {
		max-height: none; padding: .5rem;
		border: 1px solid var(--forge-line); border-radius: var(--forge-radius); background: var(--forge-surface);
	}
}

/* ── Multipage layout (the "Multipage" worksheet type) ───────────────────────
   Split on H1 into pages. Left column (sticky) = progress + TOC (per-page dots);
   main = Prev / N-of-M / Next (top + bottom) + fullscreen. Driven by forge.js. */
.fel-forge.fel-multipage { max-width: none; }
.fel-forge .forge-mp-body { display: flex; gap: 2.2rem; align-items: flex-start; }

/* Left column: sticky; progress + heading pinned, TOC list scrolls if long.
   `min-width: 0` is load-bearing. A flex item's automatic minimum size is its min-content, and
   `.forge-toc-text` is `nowrap`, so min-content is the *entire* entry — a real book called
   "BPD — Streptococcus suis: From Culture to Sequencing" forced this column to 535px and left
   the reading text 470px. flex-basis loses that argument; only min-width:0 settles it. Demo
   books have short titles, which is exactly why this hid until a real one arrived. */
.fel-forge .forge-mp-toc {
	flex: 0 0 16.5rem; position: sticky; top: calc(var(--fel-header-h, 74px) + 1rem);
	display: flex; flex-direction: column; max-height: calc(100vh - 2rem);
	min-height: 0; min-width: 0;
}
.fel-forge .forge-mp-progress {
	display: flex; flex-direction: column; gap: .7rem;
	padding: 1rem 1.1rem; margin: 0 0 1.1rem;
	border: 1px solid var(--forge-line); border-radius: var(--forge-radius); background: var(--forge-surface);
}
.fel-forge .forge-mp-prow { display: flex; flex-wrap: wrap; align-items: center; gap: .3rem .6rem; }
.fel-forge .forge-mp-plabel { font-weight: 800; font-size: .72rem; letter-spacing: .05em; text-transform: uppercase; color: var(--forge-faint); }
.fel-forge .forge-mp-stats, .fel-forge .forge-mp-sec-stats { font-size: .8rem; font-weight: 700; color: var(--forge-muted); margin-left: auto; }
.fel-forge .forge-mp-bar { flex: 1 0 100%; height: .5rem; background: var(--forge-panel); border-radius: 1rem; overflow: hidden; }
.fel-forge .forge-mp-fill, .fel-forge .forge-mp-sec-fill { display: block; height: 100%; width: 0; border-radius: 1rem; transition: width .35s cubic-bezier(.2,.7,.3,1); }
.fel-forge .forge-mp-fill { background: linear-gradient(90deg, var(--forge-accent), var(--forge-accent-2)); }
/* The chapter bar is lime, so it can't be mistaken for the blue overall one. Note it only exists
   when the book has more than one chapter — the column must not look broken with a single bar. */
.fel-forge .forge-mp-sec-fill { background: linear-gradient(90deg, var(--forge-ok-fill), var(--forge-ok)); }
/* Collapsible sections (# = chapter) with child pages (##); only the current one open. */
.fel-forge .forge-toc-section { display: flex; flex-direction: column; }
.fel-forge .forge-toc-sechead { display: flex; align-items: stretch; gap: 2px; }
.fel-forge .forge-toc-sechead .forge-toc-sectitle { flex: 1 1 auto; min-width: 0; font-weight: 700; }
.fel-forge .forge-toc-toggle { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 1.7rem; border: 0; background: none; color: var(--forge-muted); cursor: pointer; border-radius: var(--forge-radius-sm); }
.fel-forge .forge-toc-toggle:hover { background: var(--forge-soft); color: inherit; }
.fel-forge .forge-toc-toggle svg { transition: transform .18s; }
.fel-forge .forge-toc-section.is-open .forge-toc-toggle svg { transform: rotate(90deg); }
/* A leaf chapter has no toggle, so reserve the chevron's gutter or its title would run wider than
   its siblings' and the column would look ragged. This is what .is-leaf exists for. */
.fel-forge .forge-toc-section.is-leaf .forge-toc-sechead { padding-right: 1.7rem; }
.fel-forge .forge-toc-children { list-style: none; margin: 0 0 1px; padding: 0 0 0 1.5rem; display: flex; flex-direction: column; gap: .25rem; }
.fel-forge .forge-toc-section:not(.is-open) .forge-toc-children { display: none; }
.fel-forge .forge-toc-child { font-size: .92em; }
.fel-forge .forge-toc-child .forge-toc-text { color: var(--forge-muted); }
.fel-forge .forge-toc-child.is-current .forge-toc-text { color: inherit; }
.fel-forge .forge-mp-toc-head { font-weight: 900; font-size: 1.1rem; letter-spacing: -.02em; margin: 0 0 .5rem; padding: 0 .5rem; }
/* padding so the highlighted item's rounded box isn't clipped by the list's overflow */
.fel-forge .forge-mp-toc-list { list-style: none; margin: 0; padding: 3px; display: flex; flex-direction: column; gap: .35rem; overflow-y: auto; min-height: 0; }
.fel-forge .forge-toc-link {
	display: flex; align-items: center; gap: .6rem; width: 100%;
	font: inherit; text-align: left; cursor: pointer;
	background: none; border: 0; border-radius: var(--forge-radius-sm);
	padding: .5rem .6rem; color: var(--forge-muted); line-height: 1.25;
	transition: background .13s, color .13s;
}
.fel-forge .forge-toc-link:hover { background: var(--forge-soft); color: var(--forge-ink); }
.fel-forge .forge-toc-link.is-current { background: var(--forge-soft); color: var(--forge-accent-ink); font-weight: 800; }
/* The dots ARE the progress, and they speak the same language as an exercise's feedback:
   lime = done, accent = started. */
.fel-forge .forge-toc-dot { position: relative; width: 1.05rem; height: 1.05rem; border: 2px solid var(--forge-line-strong); border-radius: 50%; flex: 0 0 auto; box-sizing: border-box; transition: background .15s, border-color .15s; }
.fel-forge .forge-toc-link.is-current .forge-toc-dot { border-color: var(--forge-accent); }
.fel-forge .forge-toc-dot.is-partial { border-color: var(--forge-accent); background: var(--forge-soft); }
/* Nothing to grade in this section — reading matter. A solid empty ring would claim it is
   unfinished, and it never could be finished. */
.fel-forge .forge-toc-dot.is-none { border-style: dotted; border-color: var(--forge-line); }
.fel-forge .forge-toc-dot.is-done { background: var(--forge-ok-fill); border-color: var(--forge-ok-fill); }
/* done must win the ring even on the current row, which sets an accent border */
.fel-forge .forge-toc-link.is-current .forge-toc-dot.is-done { border-color: var(--forge-ok-fill); }
.fel-forge .forge-toc-dot.is-done::after { content: ""; position: absolute; left: 50%; top: 46%; width: .22rem; height: .42rem; border: solid var(--forge-surface, #fff); border-width: 0 2px 2px 0; transform: translate(-50%,-50%) rotate(42deg); }
.fel-forge .forge-toc-num { color: var(--forge-faint); font-size: .78rem; font-weight: 700; min-width: 1.3em; flex: 0 0 auto; }
/* Same story one level down: this is a flex item inside .forge-toc-link, so without min-width:0
   its automatic minimum is the whole nowrap string and the ellipsis never gets to happen. */
.fel-forge .forge-toc-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Clicking a section in the flat sheet's contents scrollIntoView()s its heading, which lands at
   y=0 — behind the site's sticky header. Same defect the book's page turn had (see
   scrollToTopNav in forge.js); here the browser can be told declaratively. On a phone the
   progress lane pins under the header too, so it clears that as well. */
.fel-forge .forge-h { scroll-margin-top: calc(var(--fel-header-h, 74px) + 1rem); }
@media (max-width: 820px) { .fel-forge .forge-h { scroll-margin-top: calc(var(--fel-header-h, 74px) + 4.5rem); } }

.fel-forge .forge-mp-main { flex: 1 1 auto; min-width: 0; }
/* A grid, not space-between: the top row also carried Fullscreen, so its Prev/counter/Next sat
   at different x than the bottom row's — the counter jumped as your eye moved down the page.
   1fr auto 1fr pins the counter to the true centre in both rows. */
.fel-forge .forge-mp-nav { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: .8rem; }
.fel-forge .forge-mp-prev { justify-self: start; }
.fel-forge .forge-mp-next { justify-self: end; }
/* Equal-width Prev/Next on mobile: stretch each into its 1fr grid column so they match regardless of
   label width (‹ Previous vs Next › sized differently before). */
@media (max-width: 640px) {
	.fel-forge .forge-mp-prev, .fel-forge .forge-mp-next { justify-self: stretch; text-align: center; }
}
.fel-forge .forge-mp-nav--top { margin: 0 0 1rem; }
.fel-forge .forge-mp-nav--bottom { margin: 1.4rem 0 0; padding-top: 1rem; border-top: 1px solid var(--forge-line); }
.fel-forge .forge-mp-prev, .fel-forge .forge-mp-next { font: inherit; font-weight: 700; cursor: pointer; padding: .55rem 1.3rem; border: 1px solid var(--forge-line-strong); border-radius: .7rem; background: var(--forge-surface); color: var(--forge-ink); transition: border-color .14s, background .14s, transform .12s, box-shadow .14s; }
.fel-forge .forge-mp-prev:hover:not(:disabled) { border-color: var(--forge-accent); color: var(--forge-accent-ink); transform: translateY(-1px); }
/* Next carries the accent — it's the forward action, the same role Check plays on a flat sheet. */
.fel-forge .forge-mp-next { background: var(--forge-accent); border-color: var(--forge-accent); color: var(--forge-on-accent); box-shadow: 0 8px 20px -10px var(--forge-accent); }
.fel-forge .forge-mp-next:hover:not(:disabled) { background: var(--forge-accent-ink); border-color: var(--forge-accent-ink); transform: translateY(-1px); }
.fel-forge .forge-mp-prev:disabled, .fel-forge .forge-mp-next:disabled { opacity: .4; cursor: default; box-shadow: none; }
.fel-forge .forge-mp-counter { justify-self: center; font-weight: 800; font-size: .9rem; color: var(--forge-muted); font-variant-numeric: tabular-nums; }
/* A flow item at the top of the sticky column: it rides along instead of scrolling away. */
.fel-forge .forge-mp-fs { align-self: flex-start; margin: 0 0 .8rem; display: inline-flex; align-items: center; gap: .4rem; padding: .45rem .85rem; border: 1px solid var(--forge-line-strong); border-radius: .6rem; background: var(--forge-surface); color: var(--forge-muted); cursor: pointer; font: inherit; font-size: .82rem; font-weight: 700; transition: border-color .14s, color .14s, background .14s; }
/* The label comes from the button's own aria-label, so the text and the accessible name can't
   drift apart. forge.js swaps that attribute when fullscreen toggles, and this follows. */
.fel-forge .forge-mp-fs::after { content: attr(aria-label); }
/* Guard the accent hover to a real pointer so it can't stick on touch (a phone applies :hover on tap
   and holds it until you tap elsewhere — the button stayed blue after toggling fullscreen). `hover:hover`
   alone wasn't enough: some Android browsers report hover:hover=true on touch, so add `pointer:fine`
   (a finger is pointer:coarse) — that's the combination that actually excludes touch. */
@media (hover: hover) and (pointer: fine) {
	.fel-forge .forge-mp-fs:hover { border-color: var(--forge-accent); color: var(--forge-accent-ink); }
}
/* ── Mobile contents sheet + rail (structure only; Design owns the finish) ──────────────
   Desktop: the trigger is hidden and .forge-mp-sheet is just the column it always was.
   ≤820: the list moves into a sheet behind the trigger, so a 35-section book stops being a
   wall in the flow. The rail is one dot per page, mirroring Prev/Next. */
.fel-forge .forge-mp-sheet-btn { display: none; }
/* ── The rail (one dot per page). It is an INDICATOR, not a tap target, and it has to survive from a
   6-page demo to a 60-page book in the same fixed width. The trick is to treat it as a segmented
   progress track rather than "dots": each segment is flex:1 so it divides the width evenly, with a
   1px hair-gap. Few pages → chunky segments; many pages → fine stripes, reading as a near-continuous
   filled bar. The current position never dissolves into the stripes because it is the ONE segment
   with a fixed min-width and extra height — a knob that pokes out at any density. */
.fel-forge .forge-mp-rail { display: flex; gap: 1px; align-items: center; height: 6px; }
.fel-forge .forge-rail-dot { flex: 1 1 auto; min-width: 2px; height: 4px; border-radius: 9px;
	background: var(--forge-line); transition: background .18s ease; }
.fel-forge .forge-rail-dot.is-past { background: color-mix(in oklab, var(--forge-accent) 55%, var(--forge-line)); }
.fel-forge .forge-rail-dot.is-current { flex: 0 0 auto; min-width: 16px; height: 6px;
	background: var(--forge-accent); box-shadow: 0 0 0 2px var(--forge-soft); }

@media (max-width: 820px) {
	/* The trigger IS the mobile progress indicator now (title + count + rail), so the standalone
	   progress lane would be a second one side-by-side — the duplication the owner flagged. Hide the
	   lane in the BOOK's normal view (scoped to .forge-multipage: a flat worksheet has the same lane
	   but NO sheet-trigger, so it must keep it). Fullscreen is the exception: it keeps its pinned band
	   (exit button + lane) as the established chrome and hides the trigger instead, so each view has
	   exactly one progress readout. (Both pinning to top:0 in fullscreen would re-open the round-3
	   overlap.) */
	.fel-forge .forge-multipage .forge-mp-progress { display: none; }
	.fel-forge .forge-multipage:fullscreen .forge-mp-pinned .forge-mp-progress { display: flex; }
	.fel-forge .forge-multipage:fullscreen .forge-mp-sheet-btn { display: none; }
	/* THE TRIGGER — it replaces the progress lane on a phone, so it carries BOTH "where am I" (the page
	   title) and "how far in" (the count + rail). Full-bleed and pinned like the lane it replaces, and
	   kept to two slim rows — the lane fight taught us a fat pinned block eats the screen. */
	.fel-design .fel-forge .forge-mp-sheet-btn {
		display: flex; flex-direction: column; gap: .4rem; width: 100vw;
		margin: 0 0 1rem; margin-inline: calc(50% - 50vw);
		font: inherit; text-align: left; cursor: pointer;
		padding: .5rem 1.15rem .55rem;
		background: var(--forge-surface); border: 0; border-bottom: 1px solid var(--forge-line);
		box-shadow: 0 6px 12px -9px oklch(0.4 0.1 262 / .32);
		position: sticky; top: var(--fel-header-h, 74px); z-index: 20;
		-webkit-tap-highlight-color: transparent;
	}
	.fel-forge .forge-mp-sheet-txt { display: flex; align-items: center; gap: .55rem; }
	.fel-forge .forge-mp-sheet-txt::before { content: ""; flex: 0 0 auto; width: 15px; height: 15px;
		background: currentColor; color: var(--forge-muted);
		-webkit-mask: var(--forge-ico-contents) center / contain no-repeat; mask: var(--forge-ico-contents) center / contain no-repeat; }
	.fel-forge .forge-mp-sheet-label { font-weight: 800; font-size: .95rem; color: var(--forge-ink);
		overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
	.fel-forge .forge-mp-sheet-count { margin-left: auto; flex: 0 0 auto; font-size: .8rem;
		font-weight: 700; color: var(--forge-muted); font-variant-numeric: tabular-nums; }
	/* A chevron on the far right so the row reads as an opener; rotates when the sheet is open. */
	.fel-forge .forge-mp-sheet-txt::after { content: ""; flex: 0 0 auto; width: 16px; height: 16px;
		background: currentColor; color: var(--forge-muted);
		-webkit-mask: var(--forge-ico-chevron) center / contain no-repeat; mask: var(--forge-ico-chevron) center / contain no-repeat;
		transition: transform .2s ease; }
	.fel-forge .forge-mp-sheet-btn[aria-expanded="true"] .forge-mp-sheet-txt::after { transform: rotate(180deg); }

	/* THE SHEET — a bottom sheet. A 35-row contents list is too much for a dropdown, so it slides up
	   from the bottom edge with the whole lower screen to scroll in. No dimming backdrop on purpose:
	   forge.js dismisses on any outside tap (the reading text stays live behind it), and a pseudo
	   backdrop would be *inside* the sheet and swallow that dismiss. Opaque surface + strong top
	   shadow make it read as a layer without one. */
	.fel-forge .forge-mp-sheet { display: none; }
	.fel-forge .forge-mp-sheet.is-open {
		display: flex; flex-direction: column;
		position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
		max-height: 74vh; margin: 0;
		padding: .35rem .5rem calc(.6rem + env(safe-area-inset-bottom, 0px));
		background: var(--forge-panel); border-top: 1px solid var(--forge-line);
		border-radius: 20px 20px 0 0;
		box-shadow: 0 -20px 50px -18px oklch(0.3 0.08 262 / .55);
	}
	/* Drag-handle affordance. */
	.fel-forge .forge-mp-sheet.is-open::before { content: ""; flex: 0 0 auto; width: 40px; height: 4px;
		border-radius: 9px; background: var(--forge-line); margin: .25rem auto .5rem; }
	.fel-forge .forge-mp-sheet.is-open .forge-mp-toc-head { flex: 0 0 auto; margin: 0;
		padding: 0 .75rem .5rem; font-weight: 800; font-size: 1.05rem; color: var(--forge-ink); }
	.fel-forge .forge-mp-sheet.is-open .forge-mp-toc-list { flex: 1 1 auto; min-height: 0;
		overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain;
		margin: 0; padding: .1rem .25rem .25rem; max-height: none; }
	/* The current chapter has to be unmistakable when the sheet opens. */
	.fel-forge .forge-mp-sheet.is-open .forge-toc-link.is-current { background: var(--forge-soft); }
	.fel-forge .forge-mp-sheet.is-open .forge-toc-link.is-current .forge-toc-text { color: var(--forge-accent-ink); font-weight: 800; }
	.fel-forge .forge-mp-sheet.is-open .forge-toc-link.is-current .forge-toc-num { color: var(--forge-accent); }
}
@media (max-width: 820px) and (prefers-reduced-motion: no-preference) {
	.fel-forge .forge-mp-sheet.is-open { animation: felSheetUp .28s cubic-bezier(.22, 1, .36, 1); }
	@keyframes felSheetUp { from { transform: translateY(100%); } to { transform: translateY(0); } }
}

/* Wrapper around Fullscreen + progress (parser-emitted). display:contents keeps BOTH the desktop
   column layout AND the ≤820 normal view untouched — the strip pins alone (thin lane) and the button
   scrolls away. It only becomes a real box in :fullscreen ≤820, as the pinned band (rule above). */
.fel-forge .forge-mp-pinned { display: contents; }
.fel-forge .forge-page-title { font-size: 1.9rem; font-weight: 900; letter-spacing: -.03em; line-height: 1.08; margin: 0 0 1.1rem; }

/* Fullscreen: .forge-multipage fills the screen, so it is no longer inside the page's card and
   needs its own surface + padding. NOTE the top padding is ZERO on purpose: this element is the
   scroll container in fullscreen (overflow:auto), and the mobile progress strip is position:sticky
   inside it. A sticky box is clamped to its containing block (the container's *content* box), so a
   padding-top here would push the strip's highest reachable point down by that much — top:0 could
   never reach the real top edge and the strip stalled ~2rem below it (owner's fullscreen screenshot:
   the dead band). Top space instead comes from the strip's own presence + the main column's pad. */
.fel-forge .forge-multipage:fullscreen { background: var(--forge-panel); padding: 0 2.5rem 2rem; overflow: auto; }
.fel-forge .forge-multipage:fullscreen .forge-mp-main { padding-top: 1.4rem; }
/* No site header in fullscreen, so the mobile progress strip must pin to the top edge, not 74px
   below it (which would leave a dead band). Zeroing the header token here does exactly that. */
.fel-forge .forge-multipage:fullscreen { --fel-header-h: 0px; }
.fel-forge .forge-multipage:fullscreen .forge-mp-toc { max-height: calc(100vh - 4rem); }
/* …but the reading column must NOT take the whole screen with it. Let loose on a 1920 display the
   text ran to 1541px — 189 characters a line, where 45–75 is comfortable and the eye starts losing
   its place on the return sweep. Capped and centred it measures 789px / 97 characters: the same
   reading experience as the in-page column (741px / ~91), just with the site's chrome gone and the
   whole height to play with. Fullscreen is a focus mode; it buys quiet and vertical space, not line
   length. Going narrower than the in-page column would be the opposite of what the button promises. */
.fel-forge .forge-multipage:fullscreen .forge-mp-body {
	max-width: 68rem;
	margin-inline: auto;
}
/* Fullscreen on a phone must be WIDER than the inset page, not narrower. The 2.5rem side padding +
   68rem centred body ate the width on a 390px screen. Drop the sides to a thin gutter and let the
   body fill it. (Top padding stays 0 — that's the sticky-pin fix above.) */
@media (max-width: 820px) {
	.fel-forge .forge-multipage:fullscreen { padding-inline: .75rem; }
	.fel-forge .forge-multipage:fullscreen .forge-mp-body { max-width: none; margin-inline: 0; }
}

@media (max-width: 820px) {
	/* display:block, not flex-column: the progress strip below is position:sticky, and sticky items
	   inside a flex column have long been unreliable across engines. Block flow keeps the pin honest. */
	.fel-forge .forge-mp-body { display: block; }
	/* Same split as the flat sheet, and for the same reason: the column's box ends before the pages do,
	   so nothing inside it can stay pinned. display:contents dissolves it — Fullscreen, the progress
	   card and the contents list become flow siblings of the body. Role split (owner, 2026-07): the
	   strip is PROGRESS — always in front of the learner; the contents card is NAVIGATION — reached for
	   when wanted, so it is free to scroll away. The strip itself is styled by the shared
	   .forge-mp-progress rule in the flat sheet's ≤820 block above. */
	.fel-forge .forge-mp-toc { display: contents; }
	.fel-forge .forge-mp-fs { margin: 0 0 1rem; }
	/* Title + contents list read as the one navigation card that scrolls away. */
	.fel-forge .forge-mp-toc-head { margin: 0 0 .5rem; }
	.fel-forge .forge-mp-toc-list {
		max-height: none; padding: .5rem; margin: 0 0 1.2rem;
		border: 1px solid var(--forge-line); border-radius: var(--forge-radius); background: var(--forge-surface);
	}
	.fel-forge .forge-page-title { font-size: 1.55rem; }
}

/* ──────────────────────────────────────────────────────────────────────────
   GLOSSED WORDS  [[word|translation]]
   DESIGNED — a port of the H5P worksheets' vocab tooltip (uploads/h5p/custom-h5p.css),
   which Claude Design authored; the geometry below is that design, tokenised, plus a
   :focus-visible ring and .is-open for tap. Not a placeholder: keep the two in step, and
   restyle both together or the same word reads differently in H5P and Forge.
   ────────────────────────────────────────────────────────────────────────── */
.fel-forge .forge-gl {
	border-bottom: 2px dotted var(--forge-accent);
	color: var(--forge-accent-ink);
	font-weight: 600;
	text-decoration: none;
	cursor: help;
	position: relative;
}
/* The ring left behind after a mouse click isn't :focus-visible — it's WordPress core's
   `:where(.wp-site-blocks :focus){outline-width:2px;outline-style:solid}`, which fires on plain
   :focus and so catches pointer clicks on anything focusable, this abbr[tabindex] included.
   Suppress it there; keep the real ring for keyboard. Same fix as Check/Reset. */
.fel-forge .forge-gl:focus { outline: none; }
.fel-forge .forge-gl:focus-visible { outline: 2px solid var(--forge-accent); outline-offset: 2px; border-radius: 3px; }

/* The bubble. content:attr(data-tip) — the tip never goes in `title`, or the browser
   draws its own tooltip on top of this one. */
.fel-forge .forge-gl:hover::after,
.fel-forge .forge-gl:focus-visible::after,
.fel-forge .forge-gl.is-open::after {
	content: attr(data-tip);
	position: absolute; left: 0; bottom: 135%; z-index: 50;
	width: max-content; max-width: 260px;
	background: var(--forge-ink); color: var(--forge-surface, #fff);
	font-size: .85rem; font-weight: 500; line-height: 1.4;
	padding: .55em .75em; border-radius: .6rem;
	box-shadow: 0 14px 30px -12px oklch(0.4 0.1 262 / .7);
	white-space: normal; text-align: left;
}
.fel-forge .forge-gl:hover::before,
.fel-forge .forge-gl:focus-visible::before,
.fel-forge .forge-gl.is-open::before {
	content: ""; position: absolute; left: 13px; bottom: 123%; z-index: 50;
	border: 6px solid transparent; border-top-color: var(--forge-ink);
}

/* ──────────────────────────────────────────────────────────────────────────
   TEXT + LISTENING TEXT  ::: text / ::: listen
   SETTLED — reading matter wears the content card on purpose. This file has three
   silhouettes and they are a vocabulary:

     surface + 1px border + radius 1rem      → worksheet content (exercise, pairs, reading)
     --soft fill + 3px left rule + radius sm → a hint / an instruction (.forge-hint)
     the same card, border-COLOR → --forge-ok → that content is complete

   A reading passage IS worksheet content, so it takes the content card. Tinting or
   un-bordering it (proposed once, rejected 2026-07-16) would dress it as the second row —
   chrome the learner skims past — when it is the material itself. Don't re-litigate: the
   card is not decoration around the prose, it is the statement that the prose belongs to
   the worksheet.
   ────────────────────────────────────────────────────────────────────────── */
/* (The card itself is declared once, with the other blocks, near the top of this file.) */
/* Reading measure. The card stays full width — that is the content silhouette we defend — but the
   prose inside gets a ~66ch cap so a long line doesn't run the column's whole width. Scoped to the
   paragraphs (.forge-read > .forge-p), so it never touches the card, the audio player, or the listen
   controls; and it covers ::: text and ::: listen alike, since both blocks carry .forge-read. This is
   the cap that --forge-maxw deliberately does NOT apply to the column. */
.fel-forge .forge-read > .forge-p { max-width: 46rem; }
.fel-forge .forge-textblock > .forge-p:first-child,
.fel-forge .forge-listen > .forge-p:first-child { margin-top: 0; }
.fel-forge .forge-textblock > .forge-p:last-child,
.fel-forge .forge-listen > .forge-p:last-child { margin-bottom: 0; }

/* Player row: the native control takes the space, Slow sits beside it. */
.fel-forge .forge-listen-bar {
	display: flex; align-items: center; gap: .6rem;
	margin: 0 0 1rem;
}
/* The native element stays in the DOM as the engine. It is only hidden once forge.js has
   actually built the transport, so with JS off the stock player still plays. */
.fel-forge .forge-listen-audio { flex: 1; min-width: 0; height: 2.4rem; }
.fel-forge .forge-listen-bar.has-transport .forge-listen-audio { display: none; }

/* Play / pause — the primary control, so it carries the accent like Check does. */
.fel-forge .forge-listen-play {
	flex: none; display: inline-flex; align-items: center; justify-content: center;
	width: 2.6rem; height: 2.6rem; padding: 0;
	border: 0; border-radius: 50%; cursor: pointer;
	background: var(--forge-accent); color: var(--forge-on-accent);
	box-shadow: 0 6px 16px -6px var(--forge-accent);
	transition: background .15s, transform .12s;
}
.fel-forge .forge-listen-play svg { width: 1.35rem; height: 1.35rem; display: block; }
.fel-forge .forge-listen-play:hover { background: var(--forge-accent-ink); }
.fel-forge .forge-listen-play:active { transform: translateY(1px); }

/* Seek. A range input, not <audio controls>: this one is actually styleable, and it hands us
   keyboard seeking and the right a11y role for nothing. --p is the played fraction, written by
   forge.js, and paints the filled part of the track. */
.fel-forge .forge-listen-seek {
	flex: 1; min-width: 4rem; height: 1.1rem; margin: 0;
	-webkit-appearance: none; appearance: none;
	background: transparent; cursor: pointer;
}
.fel-forge .forge-listen-seek::-webkit-slider-runnable-track {
	height: .4rem; border-radius: 1rem;
	background: linear-gradient(to right,
		var(--forge-accent) var(--p, 0%), var(--forge-panel) var(--p, 0%));
}
.fel-forge .forge-listen-seek::-webkit-slider-thumb {
	-webkit-appearance: none; appearance: none;
	width: .85rem; height: .85rem; margin-top: -.225rem;
	border-radius: 50%; background: var(--forge-accent);
	border: 2px solid var(--forge-surface); box-shadow: 0 2px 6px -2px rgb(0 0 0 / .5);
}
/* Firefox styles the track and the played part separately. */
.fel-forge .forge-listen-seek::-moz-range-track { height: .4rem; border-radius: 1rem; background: var(--forge-panel); }
.fel-forge .forge-listen-seek::-moz-range-progress { height: .4rem; border-radius: 1rem; background: var(--forge-accent); }
.fel-forge .forge-listen-seek::-moz-range-thumb {
	width: .85rem; height: .85rem; border: 2px solid var(--forge-surface);
	border-radius: 50%; background: var(--forge-accent);
}
.fel-forge .forge-listen-seek:focus { outline: none; }
.fel-forge .forge-listen-seek:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 3px var(--forge-soft); }
.fel-forge .forge-listen-seek:focus-visible::-moz-range-thumb { box-shadow: 0 0 0 3px var(--forge-soft); }

/* Tabular figures so the timer doesn't jitter as the digits change. */
.fel-forge .forge-listen-time {
	flex: none; font-size: .78rem; font-weight: 600; color: var(--forge-muted);
	font-variant-numeric: tabular-nums; white-space: nowrap;
}
.fel-forge .forge-listen-slow {
	flex: none; font: inherit; font-size: .8rem; font-weight: 600;
	padding: .35em .9em; border-radius: 999px; cursor: pointer;
	border: 1.5px solid var(--forge-line); background: transparent; color: var(--forge-muted);
	transition: border-color .14s, background .14s, color .14s;
}
.fel-forge .forge-listen-slow:hover { border-color: var(--forge-accent); color: var(--forge-accent-ink); }
.fel-forge .forge-listen-slow.is-on {
	background: var(--forge-accent); border-color: var(--forge-accent); color: var(--forge-on-accent);
}
.fel-forge .forge-listen-pending {
	font-size: .8rem; color: var(--forge-muted); font-style: italic;
}
@media (max-width: 560px) {
	.fel-forge .forge-listen-bar { flex-wrap: wrap; }
	.fel-forge .forge-listen-audio { flex-basis: 100%; }
}

/* Veiled listening text (reveal=button | listened | both) — BASELINE. */
.fel-forge .forge-listen-veil {
	display: flex; align-items: center; justify-content: center;
	min-height: 4.5rem; padding: 1rem;
	border: 1.5px dashed var(--forge-line-strong);
	border-radius: var(--forge-radius-sm);
	background: var(--forge-panel);
	text-align: center;
}
.fel-forge .forge-listen-veil-note { color: var(--forge-muted); font-size: .9rem; font-style: italic; }
.fel-forge .forge-listen-reveal {
	font: inherit; font-size: .9rem; font-weight: 600; cursor: pointer;
	padding: .5em 1.2em; border-radius: 999px;
	border: 1.5px solid var(--forge-accent); background: transparent; color: var(--forge-accent-ink);
	transition: background .14s, color .14s;
}
.fel-forge .forge-listen-reveal:hover { background: var(--forge-accent); color: var(--forge-on-accent); }

/* Open state. The veil does not vanish — it becomes the head of the panel holding the revealed
   text: same --forge-panel tint, one outline around both, the toggle where it already was.
   Two reasons it is a single box and not a bare button over loose prose:
     · The script has to end somewhere visible. Under the panel comes the exercise that asks
       about it, and running prose into questions with nothing between them was the complaint.
     · The button is the one the learner just pressed. A toggle that slides to the left edge,
       or loses its box, on its own click reads as a different control.
   **Nothing here touches the veil's own box** — not min-height, not padding. Show text and
   Hide text are then the same button in the same place, in both axes, by construction rather
   than by arithmetic: any collapse of that 4.5rem has to be paid back in padding to keep the
   button still, and the sum depends on the button's rendered height and on a box-sizing this
   stylesheet does not set. Two rounds of "the toggle jumped" were exactly that debt.
   The outline turns **solid** here. Dashed is the closed veil's own signal — "something is
   hidden behind this" — and keeping it on revealed text says the opposite of what is true.
   The border is split across the two elements (no bottom on the veil, no top on the text) so
   the seam closes and the pair draws as one card.
   (The `listened` note is dropped by forge.js on first open, not hidden here — it must stay
   gone once the recording has been heard, open or closed.) */
.fel-forge .forge-listen.is-open .forge-listen-veil {
	border: 1px solid var(--forge-line);
	border-bottom: 0;
	border-radius: var(--forge-radius-sm) var(--forge-radius-sm) 0 0;
}
.fel-forge .forge-listen.is-open .forge-listen-text {
	padding: 0 1rem 1rem;
	border: 1px solid var(--forge-line);
	border-top: 0;
	border-radius: 0 0 var(--forge-radius-sm) var(--forge-radius-sm);
	background: var(--forge-panel);
}
/* The prose sits inside .forge-listen-text, so the card's own first/last-child margin trims
   (.forge-listen > .forge-p) do not reach it — the panel would open with a .5rem gap above
   the first line and pad unevenly at the bottom. */
.fel-forge .forge-listen-text > .forge-p:first-child { margin-top: 0; }
.fel-forge .forge-listen-text > .forge-p:last-child { margin-bottom: 0; }

/* Read cue — the "this passage has been seen" mark. The 3px lime left rule was removed 2026-07-18
   (owner: looks odd): a left rule is the hint silhouette and lime means "correct", neither of which
   is what "read" is. No cue for now (progress still counts it — the TOC dot and X-of-N move); the
   replacement is a choice pending in FORGE-ROADMAP.md. */
