/*
 * Lernfuchs design system.
 *
 * Loaded after Bootstrap 5.3, and deliberately narrow in what it overrides:
 * Bootstrap supplies the grid, the utilities and the form controls, and
 * everything here is the part that has to feel like it was made for an
 * eight-year-old rather than for an admin panel.
 *
 * Three rules run through all of it.
 *
 *   1. Nothing important is smaller than a thumb. Every control a child taps
 *      while under time pressure is at least 56px, because a mis-tap in a
 *      five-minute test is indistinguishable from not knowing the answer.
 *
 *   2. Colour is never the only signal. Right and wrong carry a shape and an
 *      icon as well as green and red — roughly one boy in twelve cannot rely on
 *      the colour, and this app is used in classrooms.
 *
 *   3. Every animation is off under prefers-reduced-motion, and none of them is
 *      load-bearing. The fox celebrating is a reward; the score going up is the
 *      information, and it is legible with the animation removed.
 */

/* ==========================================================================
   Tokens
   ========================================================================== */

:root {
	/* --- Palette ---------------------------------------------------------
	   Warm and high-contrast. The orange is the fox and the brand; the ink is
	   near-black with a hint of blue so that a whole screen of equations does
	   not look like a photocopy. */
	--lf-fox: #f97c1c;
	--lf-fox-dark: #d95f06;
	--lf-fox-light: #ffb26b;
	--lf-ink: #21243d;
	--lf-ink-soft: #4a4e6d;
	--lf-muted: #8a8fa8;
	--lf-cream: #fff7ee;
	--lf-paper: #ffffff;
	--lf-line: #ece3d8;

	/* Feedback. Green and red, but see rule 2 above — they never travel alone. */
	--lf-right: #17b978;
	--lf-right-soft: #d8f5e9;
	--lf-wrong: #f2545b;
	--lf-wrong-soft: #ffe2e3;

	/* Accents for skills, badges and the goal ring. */
	--lf-sky: #3d9be9;
	--lf-grape: #8a6fdf;
	--lf-gold: #ffc93c;
	--lf-mint: #4ecdc4;

	/* --- Shape ----------------------------------------------------------- */
	--lf-radius: 22px;
	--lf-radius-lg: 32px;
	--lf-radius-pill: 999px;

	/* --- Depth -----------------------------------------------------------
	   A soft ambient shadow plus, on buttons, a solid offset that reads as a
	   physical edge. The solid one is what makes a button look pressable to
	   somebody who has not been taught that a rectangle is a button. */
	--lf-shadow: 0 6px 18px rgba(33, 36, 61, .08);
	--lf-shadow-lg: 0 18px 40px rgba(33, 36, 61, .14);
	--lf-press: 5px;

	/* --- Type -------------------------------------------------------------
	   Nunito is round, open and has unambiguous digits, which matters more here
	   than anywhere else in a normal app: the whole interface is numbers. The
	   fallbacks are the roundest system faces available per platform. */
	--lf-font: "Nunito", "Segoe UI Variable", "SF Pro Rounded", system-ui,
		-apple-system, "Segoe UI", Roboto, sans-serif;

	/* Tabular figures so a score ticking from 9 to 10 does not shift the layout. */
	--lf-font-num: "Nunito", ui-monospace, system-ui, sans-serif;

	--lf-speed: .18s;
	--lf-ease: cubic-bezier(.34, 1.56, .64, 1); /* a small overshoot: springy, not silly */
}

/* ==========================================================================
   Base
   ========================================================================== */

html,
body {
	height: 100%;
}

body {
	font-family: var(--lf-font);
	color: var(--lf-ink);
	background: var(--lf-cream);
	/* The app is a game; letting a stray drag select the equation text is the
	   sort of thing that turns a tap into a highlight and loses a second. */
	-webkit-tap-highlight-color: transparent;
	overscroll-behavior-y: contain;
}

/* Selection is re-enabled everywhere text is meant to be read rather than
   tapped — forms, the progress screen, anything with prose. */
input,
textarea,
.lf-selectable {
	-webkit-user-select: text;
	user-select: text;
}

h1, h2, h3, h4 {
	font-weight: 800;
	letter-spacing: -.01em;
}

.lf-num {
	font-family: var(--lf-font-num);
	font-variant-numeric: tabular-nums;
	font-weight: 800;
}

/* The app shell. min-height rather than height so a long progress screen can
   scroll, and dvh so an iOS address bar appearing does not crop the number pad. */
#lernfuchs-root {
	min-height: 100dvh;
	display: flex;
	flex-direction: column;
}

/* Shown by Lernfuchs.html before the module loads, replaced on boot. */
.lf-boot {
	flex: 1;
	display: grid;
	place-items: center;
	gap: 1rem;
}

.lf-boot-fox {
	font-size: 4rem;
	animation: lf-bob 1.4s ease-in-out infinite;
}

.noscript {
	margin: 3rem auto;
	max-width: 32rem;
	padding: 1.5rem;
	border-radius: var(--lf-radius);
	background: var(--lf-paper);
	box-shadow: var(--lf-shadow);
	text-align: center;
}

/* ==========================================================================
   Buttons
   ========================================================================== */

.lf-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: .5rem;
	min-height: 56px;
	padding: .75rem 1.75rem;
	border: 0;
	border-radius: var(--lf-radius-pill);
	font: inherit;
	font-weight: 800;
	font-size: 1.05rem;
	color: #fff;
	background: var(--lf-fox);
	/* The solid offset shadow, not a blur: this is the edge that "compresses". */
	box-shadow: 0 var(--lf-press) 0 var(--lf-fox-dark);
	cursor: pointer;
	transition: transform var(--lf-speed) var(--lf-ease),
		box-shadow var(--lf-speed) ease, background-color var(--lf-speed) ease;
	text-decoration: none;
}

.lf-btn:hover {
	background: var(--lf-fox-dark);
	color: #fff;
}

/* Pressing moves the face down by exactly the depth of its edge, so the button
   ends up flat against the page. It is one line of CSS and it is most of why
   the interface feels physical. */
.lf-btn:active {
	transform: translateY(var(--lf-press));
	box-shadow: 0 0 0 var(--lf-fox-dark);
}

.lf-btn:focus-visible {
	outline: 3px solid var(--lf-ink);
	outline-offset: 3px;
}

.lf-btn[disabled],
.lf-btn.lf-btn-off {
	opacity: .45;
	pointer-events: none;
}

.lf-btn-lg {
	min-height: 68px;
	font-size: 1.25rem;
	padding: 1rem 2.5rem;
}

.lf-btn-block {
	display: flex;
	width: 100%;
}

.lf-btn-ghost {
	color: var(--lf-ink);
	background: var(--lf-paper);
	box-shadow: 0 var(--lf-press) 0 var(--lf-line);
}

.lf-btn-ghost:hover {
	color: var(--lf-ink);
	background: #fffdfa;
}

.lf-btn-ghost:active {
	box-shadow: 0 0 0 var(--lf-line);
}

.lf-btn-go {
	background: var(--lf-right);
	box-shadow: 0 var(--lf-press) 0 #0f8f5d;
}

.lf-btn-go:hover {
	background: #14a86c;
}

.lf-btn-go:active {
	box-shadow: 0 0 0 #0f8f5d;
}

/* ==========================================================================
   Cards and surfaces
   ========================================================================== */

.lf-card {
	background: var(--lf-paper);
	border-radius: var(--lf-radius);
	box-shadow: var(--lf-shadow);
	padding: 1.25rem;
}

.lf-panel {
	background: var(--lf-paper);
	border-radius: var(--lf-radius-lg);
	box-shadow: var(--lf-shadow-lg);
	padding: 1.5rem;
}

.lf-eyebrow {
	display: inline-block;
	font-size: .8rem;
	font-weight: 800;
	letter-spacing: .08em;
	text-transform: uppercase;
	color: var(--lf-fox-dark);
}

/* ==========================================================================
   Top bar — hearts, progress, clock
   ========================================================================== */

.lf-topbar {
	display: flex;
	align-items: center;
	gap: .75rem;
	padding: .75rem 1rem;
	/* Sticky rather than fixed: fixed positioning and a mobile keyboard are a
	   bad combination, and the practice screen has an input on it. */
	position: sticky;
	top: 0;
	z-index: 20;
	background: var(--lf-cream);
}

.lf-topbar-quit {
	flex: 0 0 auto;
	width: 44px;
	height: 44px;
	display: grid;
	place-items: center;
	border: 0;
	border-radius: 50%;
	background: transparent;
	color: var(--lf-muted);
	font-size: 1.5rem;
	line-height: 1;
	cursor: pointer;
}

.lf-topbar-quit:hover {
	background: rgba(33, 36, 61, .06);
	color: var(--lf-ink);
}

.lf-progress {
	flex: 1;
	height: 18px;
	border-radius: var(--lf-radius-pill);
	background: var(--lf-line);
	overflow: hidden;
}

.lf-progress-fill {
	height: 100%;
	width: 0;
	border-radius: var(--lf-radius-pill);
	background: linear-gradient(90deg, var(--lf-fox-light), var(--lf-fox));
	transition: width .4s var(--lf-ease);
}

.lf-hearts {
	display: flex;
	gap: .25rem;
	font-size: 1.35rem;
	line-height: 1;
}

.lf-heart {
	transition: transform .3s var(--lf-ease), opacity .3s ease;
}

/* A lost heart stays in place, greyed. Removing it would shift the row and
   make it hard to see how many are left at a glance. */
.lf-heart-gone {
	opacity: .25;
	filter: grayscale(1);
	transform: scale(.8);
}

.lf-clock {
	font-family: var(--lf-font-num);
	font-variant-numeric: tabular-nums;
	font-weight: 800;
	font-size: 1.15rem;
	min-width: 4.2rem;
	text-align: right;
}

/* The last thirty seconds. Pulses, and also turns red — but the digits
   themselves are the information, so neither is required to read it. */
.lf-clock-urgent {
	color: var(--lf-wrong);
	animation: lf-pulse 1s ease-in-out infinite;
}

/* ==========================================================================
   The round
   ========================================================================== */

/*
 * A column that fills the viewport: bar at the top, keypad at the bottom, and
 * the question taking whatever is left. dvh rather than vh so that an iOS
 * address bar sliding in does not push the keypad off the bottom of the screen
 * — which is the single most common way a layout like this breaks on a phone.
 *
 * The safe-area inset keeps the bottom row of keys clear of the home indicator.
 */
.lf-round {
	display: flex;
	flex-direction: column;
	min-height: 100dvh;
	padding-bottom: env(safe-area-inset-bottom);

	/*
	 * Capped and centred, so a laptop gets a phone-shaped column rather than a
	 * stretched one.
	 *
	 * Without this the round is technically fine on a wide window and looks
	 * abandoned: the keypad spreads to whatever width it is given, the card ends
	 * up marooned in the middle of an empty page, and the two are so far apart
	 * that answering means crossing the screen. 30rem is about the width of a
	 * large phone, which is the shape every part of this screen was designed at.
	 */
	width: min(100%, 30rem);
	margin-inline: auto;
}

/* --- The "really stop?" sheet ------------------------------------------- */

/*
 * Slides up from the bottom rather than appearing in the middle: it is a
 * decision made with a thumb, and the buttons should arrive where the thumb
 * already is.
 *
 * visibility, not just opacity, so that while hidden it cannot be tabbed into
 * or read out by a screen reader — an invisible dialog that still takes focus
 * is worse than a visible one.
 */
.lf-quit-sheet {
	position: fixed;
	inset: 0;
	z-index: 40;
	display: flex;
	align-items: flex-end;
	justify-content: center;
	padding: 1rem;
	background: rgba(33, 36, 61, .45);
	opacity: 0;
	visibility: hidden;
	transition: opacity var(--lf-speed) ease, visibility var(--lf-speed) ease;
}

.lf-quit-sheet > .lf-panel {
	width: min(100%, 26rem);
	transform: translateY(16px);
	transition: transform var(--lf-speed) var(--lf-ease);
}

.lf-quit-sheet-open {
	opacity: 1;
	visibility: visible;
}

.lf-quit-sheet-open > .lf-panel {
	transform: none;
}

/* ==========================================================================
   The task card — the centre of the whole app
   ========================================================================== */

.lf-stage {
	flex: 1;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 1rem;
	padding: 1rem;
	/* The floating "+12" is positioned against this, so it has to be a
	   containing block. */
	position: relative;

	/*
	 * No min-height. It used to reserve 40vh, which on a tall window added to
	 * flex:1 and pushed the keypad hard against the bottom edge while leaving a
	 * void above the card. Growing to fill whatever is left over is the whole
	 * job here, and flex:1 already does it.
	 */
}

.lf-task {
	width: min(100%, 34rem);
	background: var(--lf-paper);
	border-radius: var(--lf-radius-lg);
	box-shadow: var(--lf-shadow-lg);
	padding: 2rem 1.5rem;
	text-align: center;
	/* Makes the card itself the reference for the equation's font size below. */
	container-type: inline-size;
}

.lf-equation {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: .35em;
	font-family: var(--lf-font-num);
	font-variant-numeric: tabular-nums;
	font-weight: 800;
	line-height: 1.1;

	/*
	 * Fallback for browsers without container queries: scale on the viewport.
	 * Wrong whenever the card is much narrower than the window — which is every
	 * desktop — but never illegible.
	 */
	font-size: clamp(2rem, 9vw, 3.25rem);

	/*
	 * The real rule. cqi is a percentage of *this card's* width, so the longest
	 * equation the decks can produce — "10 · 10 = 100", thirteen characters —
	 * fits on one line at every width the card is ever given.
	 *
	 * Sizing on vw instead is the bug this replaces: on a desktop the viewport is
	 * three times the card, the clamp pins the font to its maximum, and the
	 * equation wraps after the equals sign. "7 · 8 =" on one line and "56" on the
	 * next is not a smaller version of the question, it is a different one.
	 *
	 * A browser that does not understand cqi drops this declaration and keeps the
	 * one above, which is why the fallback is written first.
	 */
	font-size: clamp(2rem, 11cqi, 3.5rem);

	/*
	 * Last resort only. The sizing above is what keeps the equation on one line;
	 * this stops an unforeseen combination overflowing the card instead of
	 * wrapping inside it.
	 */
	flex-wrap: wrap;
}

/*
 * A division that leaves something over is the longest card the decks produce:
 * "89 : 9 = ___ R _" is two answer slots and an R past the thirteen characters
 * the rule above was calibrated for, so at the shared size it overflowed and the
 * last-resort wrap put the R box on its own line. That is the exact failure the
 * comment above describes — the remainder on a second line is not a smaller
 * version of the question, it is a different one, and it reads as a second sum.
 *
 * Roughly ten em of content against the thirteen the coefficient allows, so it
 * fits at every card width with room to spare.
 */
.lf-task-rest .lf-equation {
	font-size: clamp(1.6rem, 7vw, 2.6rem);
	font-size: clamp(1.6rem, 8.5cqi, 2.6rem);
}

/*
 * The hundreds cards: "1000 − 300 = ___" is sixteen characters to the thirteen
 * the shared rule was calibrated for, and at that size it wraps after the equals
 * sign. TaskCard sets this on any card with more than seven digits.
 */
.lf-task-long .lf-equation {
	font-size: clamp(1.75rem, 7.5vw, 2.8rem);
	font-size: clamp(1.75rem, 9cqi, 2.8rem);
}

/*
 * The year-5 cards: "5000 · 5000 = 25 000 000" and "38 760 : 38 = 1020 R 0" run
 * to sixteen digits, and even the long size wraps them. TaskCard sets this past
 * eleven digits. After .lf-task-rest on purpose: a long division with a remainder
 * is both, and it is the length that decides.
 */
.lf-task-longer .lf-equation {
	font-size: clamp(1.3rem, 5.5vw, 2.2rem);
	font-size: clamp(1.3rem, 6.6cqi, 2.2rem);
}

.lf-equation-op {
	color: var(--lf-fox);
}

/* "und", where a worded card would otherwise print its operator. Deliberately
   not fox-orange and not in the number face: at the weight the operators carry,
   a coloured "und" reads as a sign spelled out, and the whole point of the card
   is that no sign has been given. */
.lf-equation-joiner {
	font-family: var(--lf-font);
	font-size: .5em;
	font-weight: 700;
	color: var(--lf-ink-soft);
}

/* "Berechne die Summe." — the question on a worded card. Above the numbers and
   quieter than them: it is read once, and then the child is working with the
   two numbers underneath it. */
.lf-prompt {
	margin-bottom: .75rem;
	font-size: 1.05rem;
	font-weight: 700;
	color: var(--lf-ink-soft);
}

/* The R between the quotient and what is left over. Smaller than the operators
   and set in the body face rather than the number face: it is a word, not a sign,
   and at the same weight as the colon it reads as part of the arithmetic. */
.lf-rest-op {
	font-family: var(--lf-font);
	font-size: .62em;
	font-weight: 700;
	letter-spacing: .04em;
}

/* The blank. A slot with a floor line, echoing the ruled line on the paper
   worksheet the app is modelled on — children recognise it immediately. */
.lf-blank {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 2.4em;
	min-height: 1.2em;
	padding: 0 .15em;
	border-bottom: .12em solid var(--lf-fox);
	border-radius: .12em;
	color: var(--lf-fox-dark);
	background: linear-gradient(180deg, transparent 55%, rgba(249, 124, 28, .12));
	transition: background-color var(--lf-speed) ease, border-color var(--lf-speed) ease;
}

/* The R box holds exactly one digit — a remainder is always below the divisor,
   and no deck here divides by more than nine — so it does not need the width the
   rule above reserves for a three-digit answer. A box that promises room for a
   second digit is inviting an answer that could never be right.

   It has to come after .lf-blank rather than with the other remainder rules:
   same specificity, so source order is what decides, and written above it the
   2.4em simply won. */
.lf-rest-blank {
	min-width: 1.2em;
}

/* Written division goes up to 99, so its remainders can take two digits. */
.lf-task-rest-wide .lf-rest-blank {
	min-width: 1.9em;
}

/* The caret only blinks while the slot is empty; once a digit is typed it is
   the digit that should be drawing the eye. */
.lf-blank-empty::after {
	content: "";
	width: .06em;
	height: .85em;
	background: var(--lf-fox);
	animation: lf-caret 1.1s steps(1) infinite;
}

.lf-blank-right {
	border-color: var(--lf-right);
	color: var(--lf-right);
	background: var(--lf-right-soft);
}

.lf-blank-wrong {
	border-color: var(--lf-wrong);
	color: var(--lf-wrong);
	background: var(--lf-wrong-soft);
}

/* Shown under the equation after a wrong answer: the same sum, completed. The
   child never leaves a card without having seen the right answer once. */
.lf-solution {
	margin-top: 1rem;
	font-size: 1.1rem;
	font-weight: 700;
	color: var(--lf-ink-soft);
}

.lf-solution .lf-num {
	color: var(--lf-right);
}

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

.lf-task-in {
	animation: lf-slide-in .32s var(--lf-ease) both;
}

.lf-task-right {
	animation: lf-pop .34s var(--lf-ease);
}

.lf-task-wrong {
	animation: lf-shake .4s ease;
}


/* ==========================================================================
   Number pad
   ========================================================================== */

/*
 * The answer area: the mode switch, and below it the keypad or the draw box.
 *
 * Deliberately bare. Each of the three children already carries the same
 * width: min(100%, 24rem), the same auto margins and its own padding — that is
 * what keeps the bottom of the screen from jumping when the child switches
 * between tapping and writing. Anything this panel adds is added on top of all
 * three at once, so a padding here is an inset applied twice and a border here
 * is a box drawn around a keypad that is not a field.
 *
 * It exists as a class of its own only so the landscape layout further down has
 * something to put in its column.
 */
.lf-answer {
	width: 100%;
}

.lf-pad {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	gap: .6rem;
	width: min(100%, 24rem);
	margin: 0 auto;
	padding: 1rem;
}

.lf-key {
	min-height: 64px;
	border: 0;
	border-radius: var(--lf-radius);
	background: var(--lf-paper);
	box-shadow: 0 4px 0 var(--lf-line);
	font-family: var(--lf-font-num);
	font-size: 1.75rem;
	font-weight: 800;
	color: var(--lf-ink);
	cursor: pointer;
	transition: transform var(--lf-speed) var(--lf-ease), box-shadow var(--lf-speed) ease;
}

.lf-key:active {
	transform: translateY(4px);
	box-shadow: 0 0 0 var(--lf-line);
}

.lf-key:focus-visible {
	outline: 3px solid var(--lf-fox);
	outline-offset: 2px;
}

.lf-key-wide {
	grid-column: span 2;
}

.lf-key-go {
	color: #fff;
	background: var(--lf-right);
	box-shadow: 0 4px 0 #0f8f5d;
}

.lf-key-go:active {
	box-shadow: 0 0 0 #0f8f5d;
}

.lf-key-del {
	color: var(--lf-ink-soft);
	font-size: 1.4rem;
}

/* Jump to the R box. Spans the full width on its own row below the twelve, so
   that showing and hiding it between cards never moves a digit key — see
   NumberPad.ui.xml. Lower than the others, because it is a step within an answer
   rather than a digit, and it should not compete with them for the thumb. */
.lf-key-rest {
	grid-column: 1 / -1;
	min-height: 48px;
	color: var(--lf-fox-dark);
	background: #fff1e3;
	box-shadow: 0 4px 0 var(--lf-fox-light);
	letter-spacing: .08em;
}

/* ==========================================================================
   Vocabulary: the word card and its two pads
   ========================================================================== */

/*
 * A word card reuses .lf-task for its paper, shadow and width, and overrides
 * only what an equation needed and a word does not: the equation's container
 * query sized three numbers against the card, where here there is one string
 * of unpredictable length.
 */
.lf-word {
	font-family: var(--lf-font);
	font-size: clamp(1.75rem, 8cqi, 2.75rem);
	font-weight: 800;
	line-height: 1.15;
	color: var(--lf-ink);
	/* Long German entries wrap rather than push the card wider than the screen:
	   "das Sandwich, (zusammengeklapptes) belegtes Brot" is one of these. */
	overflow-wrap: break-word;
}

/* What is being typed, echoed under the word. Dotted rather than solid so it
   reads as a line still being filled in, not as an answer already given. */
/* A NaWi question: a sentence, read once, then the answers are what matter.
   Smaller and lighter than a word, and still the biggest thing on the card. */
.lf-word.lf-word-question {
	font-size: clamp(1.15rem, 5.2cqi, 1.6rem);
	font-weight: 700;
	line-height: 1.3;
	text-wrap: balance;
}

.lf-word-typed {
	min-height: 2.2rem;
	margin-top: 1rem;
	padding-bottom: .2rem;
	border-bottom: 3px dotted var(--lf-line);
	font-family: var(--lf-font);
	font-size: 1.5rem;
	font-weight: 700;
	color: var(--lf-fox);
}

/* --- Four meanings to choose between ------------------------------------ */

/*
 * Overrides .lf-pad's three-column grid, which is the keypad's shape and not
 * this one's. Two columns, and wider than the keypad because the options carry
 * words rather than digits.
 */
.lf-choice-pad {
	display: block;
	width: min(100%, 32rem);
	padding: 1rem;
}

.lf-choice-grid {
	display: grid;
	/* minmax(0, 1fr), not 1fr. A bare 1fr track has an automatic minimum of
	   min-content, so the column holding "der Kugelschreiber, der Stift, der
	   Füller" grew past its half and the two columns stopped matching. The
	   explicit zero minimum is what lets a long entry wrap instead of pushing. */
	grid-template-columns: repeat(2, minmax(0, 1fr));
	/* All four the same height, however long the longest entry is. Without this
	   each row sizes to its own content and the pad steps down the screen, which
	   reads as the shorter options being less important. */
	grid-auto-rows: 1fr;
	gap: .6rem;
}

/* One option per row, when any of them is too long for half the pad — see
   ChoicePad.needsFullWidth. Shorter rows, since each now has the full width. */
.lf-choice-grid.lf-choice-grid-stacked {
	grid-template-columns: minmax(0, 1fr);
}

.lf-choice-grid-stacked .lf-choice {
	min-height: 52px;
}

.lf-choice {
	min-height: 64px;
	padding: .7rem .8rem;
	/* Transparent rather than none, so that the 3px the reveal adds is already
	   in the box. With `border: 0` here every button grew by 6px the instant it
	   was marked, and the whole pad jumped at the moment the child was reading
	   it. */
	border: 3px solid transparent;
	border-radius: var(--lf-radius);
	background: var(--lf-paper);
	box-shadow: 0 4px 0 var(--lf-line);
	font-family: var(--lf-font);
	font-size: 1.05rem;
	font-weight: 700;
	line-height: 1.25;
	color: var(--lf-ink);
	cursor: pointer;
	/* Wrap between words, and break inside one only when it genuinely cannot
	   fit. NOT hyphens:auto — browsers hyphenate German compounds by dictionary
	   and get them wrong ("zu-sammengeklapp-tes"), which on a vocabulary card is
	   not a cosmetic problem: the child is reading the word to learn it. */
	overflow-wrap: break-word;
	transition: transform var(--lf-speed) var(--lf-ease), box-shadow var(--lf-speed) ease;
}

/* German compounds are long and the buttons are half a phone wide:
   "Gehörknöchelchen" and "Longitudinalwellen" broke at whatever letter hit the
   edge. ChoicePad sets lang="de" on every button, which is what lets the browser
   hyphenate at a syllable instead. */
.lf-choice {
	hyphens: auto;
	-webkit-hyphens: auto;
}

.lf-choice:active:not(:disabled) {
	transform: translateY(4px);
	box-shadow: 0 0 0 var(--lf-line);
}

.lf-choice:focus-visible {
	outline: 3px solid var(--lf-fox);
	outline-offset: 2px;
}

/*
 * The reveal. Both states carry a thick border as well as the colour: roughly
 * one boy in twelve cannot use the colour, and this is an app for classrooms —
 * the same reason TaskCard.markRight gives for its green border.
 */
.lf-choice-right {
	background: var(--lf-right-soft);
	border-color: var(--lf-right);
	color: var(--lf-right);
}

.lf-choice-wrong {
	background: var(--lf-wrong-soft);
	border-color: var(--lf-wrong);
	color: var(--lf-wrong);
}

/* Disabled only after the reveal, so it must not look unavailable — the whole
   point of the pause is that the buttons are being read. */
.lf-choice:disabled {
	cursor: default;
	opacity: 1;
}

/* --- Writing the word --------------------------------------------------- */

.lf-type-pad {
	display: flex;
	flex-direction: column;
	gap: .6rem;
	width: min(100%, 24rem);
	padding: 1rem;
}

/*
 * 16px minimum, and not for looks: every iOS Safari zooms the page when a
 * focused input is smaller than that, and the zoom does not come back when the
 * field is blurred. A round would end with the app wider than the screen.
 */
.lf-word-input {
	width: 100%;
	min-height: 56px;
	padding: .6rem .9rem;
	border: 3px solid var(--lf-line);
	border-radius: var(--lf-radius);
	background: var(--lf-paper);
	font-family: var(--lf-font);
	font-size: 1.25rem;
	font-weight: 700;
	color: var(--lf-ink);
	text-align: center;
}

.lf-word-input:focus {
	outline: none;
	border-color: var(--lf-fox);
}

.lf-word-input:disabled {
	background: var(--lf-paper);
	opacity: .6;
}

.lf-check {
	min-height: 56px;
	font-size: 1.15rem;
}

/* --- Subject headings on the picker ------------------------------------- */

/*
 * Only drawn when there is more than one subject — see Dashboard.renderSkills.
 * A heading over the only group there is would be a label for the whole screen
 * pretending to be a division of it.
 */
.lf-subject-heading {
	grid-column: 1 / -1;
	margin: 1.25rem 0 .25rem;
	font-size: 1rem;
	font-weight: 800;
	letter-spacing: .02em;
	color: var(--lf-ink-soft);
}

.lf-subject-heading:first-child {
	margin-top: 0;
}

/* One tint per subject, matching the accents the skill cards already use.
   Maths keeps the fox's orange because it is the subject the app is about;
   English takes the sky blue, which is the one accent no maths deck uses. */
.lf-subject-heading.lf-subject-mathematik {
	color: var(--lf-fox-dark);
}

.lf-subject-heading.lf-subject-englisch {
	color: var(--lf-sky);
}

/* NaWi: a deep teal. The mint accent itself is too pale to read as text. */
.lf-subject-heading.lf-subject-naturwissenschaften {
	color: #1b8f7a;
}


/* ==========================================================================
   Writing the answer instead of tapping it
   ========================================================================== */

/*
 * The switch between the two. A quiet pill above the input, right-aligned and
 * deliberately smaller than everything it sits over: it is used twice a week,
 * where the keys under it are used sixty times in five minutes, and a control
 * that size next to the 3 would be tapped by accident all round.
 *
 * It keeps its place in both modes so that switching back is never a hunt —
 * which is the whole reason the row exists rather than the button being tucked
 * into one of the two layouts.
 */
.lf-mode-row {
	display: flex;
	justify-content: flex-end;
	width: min(100%, 24rem);
	margin: 0 auto;
	padding: .25rem 1rem 0;
}

.lf-mode {
	/* 44px, the smallest a control may be and still be reliably hit; the rule at
	   the top of this file asks for more from anything used under time pressure,
	   and this is not. */
	min-height: 44px;
	padding: 0 .9rem;
	border: 0;
	border-radius: var(--lf-radius-pill);
	background: transparent;
	font-weight: 700;
	font-size: .95rem;
	color: var(--lf-ink-soft);
	cursor: pointer;
}

.lf-mode:active {
	background: var(--lf-line);
}

.lf-mode:focus-visible {
	outline: 3px solid var(--lf-fox);
	outline-offset: 2px;
}

/* The box and its three keys, side by side. Same width and padding as the
   keypad it replaces, so the bottom of the screen does not jump when the child
   switches. */
.lf-draw {
	display: flex;
	gap: .6rem;
	width: min(100%, 24rem);
	margin: 0 auto;
	padding: 1rem;
}

/*
 * Where the digit goes.
 *
 * Dashed like the blank on the card, and ruled at the height of a baseline,
 * because a plain white rectangle gets a 6 drawn in one corner at half the size
 * of the box. A child who has been taught to write between two lines will use
 * a line if there is one, and the recogniser has a much easier job with a digit
 * that fills the space it was given.
 */
.lf-draw-box {
	position: relative;
	flex: 1;
	/* Roughly the height of the four rows of keys it stands in for, so that the
	   card above does not move when the mode changes. */
	height: 15rem;
	border: 2px dashed var(--lf-line);
	border-radius: var(--lf-radius);
	background-color: var(--lf-paper);
	background-image: linear-gradient(to bottom,
			transparent calc(78% - 2px),
			var(--lf-line) calc(78% - 2px),
			var(--lf-line) 78%,
			transparent 78%);
	overflow: hidden;
}

/*
 * touch-action: none is the whole of what makes writing possible here. Without
 * it the browser claims the first movement of every stroke as a scroll or a
 * pinch, and the child gets the page sliding about instead of a 7.
 */
.lf-draw-canvas {
	position: absolute;
	inset: 0;
	display: block;
	width: 100%;
	height: 100%;
	touch-action: none;
	/* A long press on a canvas still offers to save the image on iOS, in the
	   middle of a stroke. */
	-webkit-touch-callout: none;
	-webkit-user-select: none;
	user-select: none;
}

/* "Ziffer hier malen", in the middle of the empty box. Never in the way: it is
   hidden by the first point of the first stroke, and it cannot take a tap. */
.lf-draw-hint {
	position: absolute;
	inset: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	font-weight: 700;
	color: var(--lf-muted);
	pointer-events: none;
}

/* After a drawing that could not be read. Fox-orange rather than red: nothing
   here was wrong, the app simply did not recognise it, and a child who is told
   off for their handwriting stops using the feature. */
.lf-draw-hint-retry {
	color: var(--lf-fox-dark);
}

.lf-draw-reject {
	animation: lf-shake .4s ease;
}

/* Delete, R and check, in a column beside the box. The same keys as on the
   keypad — see NumberPad.press — stretched to share the height of the box
   rather than given a fixed one, so the two layouts end at the same line. */
.lf-draw-keys {
	display: flex;
	flex-direction: column;
	gap: .6rem;
	width: 4.75rem;
}

.lf-draw-keys .lf-key {
	flex: 1;
	min-height: 56px;
}

/* ==========================================================================
   Combo and floating XP
   ========================================================================== */

.lf-combo {
	display: inline-flex;
	align-items: center;
	gap: .35rem;
	padding: .3rem .85rem;
	border-radius: var(--lf-radius-pill);
	background: var(--lf-gold);
	color: #6b4c00;
	font-weight: 800;
	font-size: .95rem;
	box-shadow: var(--lf-shadow);
}

.lf-combo-bump {
	animation: lf-pop .3s var(--lf-ease);
}

/* "+12" drifting up off the card. Pointer-events none so it can never eat a
   tap meant for the next question — it is on screen for exactly as long as the
   child is trying to answer. */
.lf-xp-float {
	position: absolute;
	left: 50%;
	top: 40%;
	transform: translateX(-50%);
	font-family: var(--lf-font-num);
	font-weight: 800;
	font-size: 1.6rem;
	color: var(--lf-right);
	pointer-events: none;
	animation: lf-float-up .9s ease-out forwards;
}

/* ==========================================================================
   The mascot

   Two independent classes on the <svg>, because they answer two questions.
   .lf-shape-* says which of the optional parts in Mascot.ui.xml are drawn, and
   .lf-char-* says what colour they are. Finn and Fiene are one shape and two
   palettes; Luna and Zora are two shapes. Neither class knows about the other,
   which is what keeps "another colourway" and "another animal" from being the
   same amount of work.
   ========================================================================== */

.lf-mascot {
	width: clamp(88px, 22vw, 140px);
	height: auto;
	overflow: visible;
	transform-origin: 50% 90%;
}

/* --- Which parts get drawn -------------------------------------------------
   Hidden by one class and shown by two, so the show always wins on specificity
   and neither rule depends on where in this file it sits. The alternative —
   .lf-mascot .lf-part for the hide — is 0,2,0 against the same 0,2,0 below, and
   would be decided by source order alone. */
.lf-part {
	display: none;
}

.lf-shape-fox     .lf-part-fox,
.lf-shape-cat     .lf-part-cat,
.lf-shape-owl     .lf-part-owl,
.lf-shape-bear    .lf-part-bear,
.lf-shape-unicorn .lf-part-unicorn,
.lf-shape-mermaid .lf-part-mermaid,
.lf-shape-dragon  .lf-part-dragon {
	display: inline;
}

/* --- Palettes --------------------------------------------------------------
   Four properties, and only --m-coat and --m-belly are ever required:

     --m-coat / --m-coat-dark  the animal itself
     --m-belly                 muzzle, ear insides, the lighter underside
     --m-head                  only when the head is not the coat, which so far
                               is only the mermaid, whose tail is teal and whose
                               face is not
     --m-cheek                 with it, for the same reason
     --m-hair                  mane, tail hair, the mermaid's hair
     --m-accent                horn, beak, crown, spikes

   The last three fall back in the fills below rather than being repeated here,
   so a new character needs the two lines that actually differ. */

/* The three starters. Identical but for colour, which is the whole point —
   see EnumMascot. */
.lf-char-finn {
	--m-coat: #f97c1c;
	--m-coat-dark: #d95f06;
	--m-belly: #fff3e3;
}

.lf-char-fiene {
	--m-coat: #7fb7e8;
	--m-coat-dark: #4a8fc7;
	--m-belly: #f2fbff;
}

.lf-char-flip {
	--m-coat: #e8b84b;
	--m-coat-dark: #c9962a;
	--m-belly: #fffaea;
}

/* Paul, the owl. The beak is the only warm thing on him. */
.lf-char-paul {
	--m-coat: #a98467;
	--m-coat-dark: #7f5f4a;
	--m-belly: #f6ecdd;
	--m-accent: #f5a623;
}

/* Rosa, the cat. */
.lf-char-rosa {
	--m-coat: #d98cb3;
	--m-coat-dark: #b3648c;
	--m-belly: #fff0f7;
}

/* Benno, the bear. */
.lf-char-benno {
	--m-coat: #997260;
	--m-coat-dark: #6f5244;
	--m-belly: #f2e4d8;
}

/* Luna, the unicorn. Nearly white, so the horn and mane carry her. */
.lf-char-luna {
	--m-coat: #efe6ff;
	--m-coat-dark: #cdbcf0;
	--m-belly: #ffffff;
	--m-hair: #b98cf0;
	--m-accent: #ffcf4d;
}

/* Nele, the mermaid. The one character whose head is not her coat. */
.lf-char-nele {
	--m-coat: #3fc7bd;
	--m-coat-dark: #27a098;
	--m-belly: #b9f2ee;
	--m-head: #f7d3b4;
	--m-cheek: #e98f70;
	--m-hair: #ff7aa8;
	--m-accent: #ffb3c9;
}

/* Zora, the dragon. */
.lf-char-zora {
	--m-coat: #6cc070;
	--m-coat-dark: #45924b;
	--m-belly: #eaf7d9;
	--m-accent: #ff6b6b;
}

/* --- Fills -----------------------------------------------------------------
   The fallbacks are written here rather than as defaults on .lf-mascot. A
   default there would be a one-class rule competing with the one-class
   .lf-char-* beside it on the same element, and which won would come down to
   source order; var()'s own fallback cannot be overridden by accident. */

.lf-mascot-coat,
.lf-mascot-body {
	fill: var(--m-coat);
}

.lf-mascot-coat-dark {
	fill: var(--m-coat-dark);
}

.lf-mascot-belly {
	fill: var(--m-belly);
}

.lf-mascot-head {
	fill: var(--m-head, var(--m-coat));
}

/* Follows the head, not the coat — see the note in Mascot.ui.xml. */
.lf-mascot-cheek {
	fill: var(--m-cheek, var(--m-coat-dark));
}

.lf-mascot-hair {
	fill: var(--m-hair, var(--m-coat-dark));
}

.lf-mascot-accent-fill {
	fill: var(--m-accent, var(--m-coat-dark));
}

.lf-mascot-ink {
	fill: var(--lf-ink);
}

/* The line work: whiskers, mouths, the grooves on a horn. Stroked rather than
   filled, so they stay the same weight as the drawing scales. */
.lf-mascot-line {
	fill: none;
	stroke: var(--lf-ink);
	stroke-width: 2;
	stroke-linecap: round;
}

.lf-mascot-line-accent {
	fill: none;
	stroke: var(--m-coat-dark);
	stroke-width: 1.6;
	stroke-linecap: round;
	opacity: .5;
}

.lf-mascot-stroke-dark {
	fill: none;
	stroke: var(--m-coat-dark);
	stroke-width: 7;
	stroke-linecap: round;
}

.lf-mascot-eye {
	fill: var(--lf-ink);
	transform-origin: center;
}

/* The blink. Scaling the eye on Y is enough to read as one, and costs nothing
   next to swapping artwork. */
.lf-mood-idle .lf-mascot-eye {
	animation: lf-blink 4.5s infinite;
}

.lf-mood-idle .lf-mascot-tail {
	animation: lf-sway 3.2s ease-in-out infinite;
	transform-origin: 20% 80%;
}

.lf-mood-thinking .lf-mascot {
	transform: rotate(-4deg);
}

.lf-mood-happy .lf-mascot {
	animation: lf-bounce .5s var(--lf-ease);
}

.lf-mood-oops .lf-mascot {
	animation: lf-wobble .45s ease;
}

/*
 * Ears drooping outwards.
 *
 * transform-box is the whole trick. On an SVG element a percentage
 * transform-origin resolves against the *viewBox* by default, not against the
 * element — so "50% 100%" on an ear is a point near the fox's feet, and
 * rotating about it swings the ears clean off the head. fill-box makes the
 * percentage mean what it does everywhere else in CSS: the element's own box,
 * and therefore the base of the ear.
 *
 * The two ears carry their own classes rather than being told apart with
 * :nth-of-type, which would count the tail's <g> as well and quietly pick the
 * wrong elements the first time somebody adds a group to the drawing.
 *
 * These two groups are the same ones whatever the species — a dragon's horns
 * and an owl's tufts live inside them — so this is the only ear rule in the
 * file and adding an animal needs none.
 */
.lf-mood-oops .lf-mascot-ear {
	transform-box: fill-box;
	transform-origin: 50% 90%;
}

.lf-mood-oops .lf-mascot-ear-left {
	transform: rotate(-18deg);
}

.lf-mood-oops .lf-mascot-ear-right {
	transform: rotate(18deg);
}

.lf-mood-celebrate .lf-mascot {
	animation: lf-jump .7s var(--lf-ease) 2;
}

.lf-mood-sleepy .lf-mascot {
	animation: lf-bob 2.4s ease-in-out infinite;
}

/* What the mascot says. A short line, never a paragraph: it is read in the
   second between two questions or it is not read at all. */
.lf-mascot-speech {
	position: relative;
	max-width: 20rem;
	padding: .7rem 1.1rem;
	border-radius: var(--lf-radius);
	background: var(--lf-paper);
	box-shadow: var(--lf-shadow);
	font-weight: 700;
	text-align: center;
}

.lf-mascot-speech::after {
	content: "";
	position: absolute;
	left: 50%;
	bottom: -9px;
	width: 18px;
	height: 18px;
	transform: translateX(-50%) rotate(45deg);
	background: var(--lf-paper);
	border-radius: 0 0 4px 0;
}

.lf-mascot-row {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: .6rem;
}

/* ==========================================================================
   Dashboard
   ========================================================================== */

/* The mascot in the dashboard header, and the picker it opens.
   Smaller than the one on the landing page: here it shares a line with the
   greeting, and at the full clamp it out-sizes the child's own name. */
.lf-mascot-corner {
	display: flex;
	flex: 0 0 auto;
	flex-direction: column;
	align-items: center;
	gap: .35rem;
}

.lf-mascot-corner .lf-mascot {
	width: clamp(56px, 14vw, 88px);
}

/* Capped hard. The bubble is 20rem elsewhere, which in this column would push
   the greeting off its own line the moment the mascot says anything. */
.lf-mascot-corner .lf-mascot-speech {
	max-width: 11rem;
	padding: .45rem .7rem;
	font-size: .8rem;
}

/* The drawing, used as a control. Affordance only — it is aria-hidden, and the
   labelled button beneath it is what the keyboard and a screen reader get. */
.lf-mascot-tap {
	cursor: pointer;
	transition: transform var(--lf-speed) var(--lf-ease);
}

.lf-mascot-tap:hover {
	transform: translateY(-2px);
}

.lf-mascot-toggle {
	padding: .15rem .45rem;
	border: 0;
	border-radius: var(--lf-radius-pill);
	background: none;
	color: var(--lf-muted);
	font: inherit;
	font-size: .78rem;
	font-weight: 700;
	white-space: nowrap;
	cursor: pointer;
}

.lf-mascot-toggle:hover {
	color: var(--lf-ink);
}

.lf-mascot-toggle:focus-visible {
	outline: 3px solid var(--lf-ink);
	outline-offset: 2px;
}

/*
 * Both classes are on the same element and .lf-choice-row sets display:flex, so
 * this is qualified rather than left as a bare .lf-mascot-picker-shut: one
 * class each is the same specificity, and the choosers block is further down
 * the file — the flex would win on source order alone and the picker would
 * never shut.
 */
.lf-choice-row.lf-mascot-picker-shut {
	display: none;
}

.lf-goal-ring {
	--lf-goal: 0; /* 0..100, set inline by Dashboard */
	width: 132px;
	height: 132px;
	border-radius: 50%;
	display: grid;
	place-items: center;
	/* conic-gradient is the whole ring: no SVG, no JS, and it animates by
	   changing one custom property. */
	background: conic-gradient(var(--lf-fox) calc(var(--lf-goal) * 1%),
		var(--lf-line) 0);
	transition: --lf-goal .8s ease;
}

.lf-goal-ring-inner {
	width: 104px;
	height: 104px;
	border-radius: 50%;
	background: var(--lf-paper);
	display: grid;
	place-items: center;
	text-align: center;
	line-height: 1.1;
}

.lf-goal-count {
	font-family: var(--lf-font-num);
	font-size: 1.9rem;
	font-weight: 800;
}

.lf-goal-of {
	font-size: .8rem;
	color: var(--lf-muted);
}

/* The seven-day strip. */
.lf-week {
	display: flex;
	gap: .5rem;
	justify-content: space-between;
}

.lf-day {
	flex: 1;
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: .35rem;
	font-size: .75rem;
	color: var(--lf-muted);
}

.lf-day-dot {
	width: 34px;
	height: 34px;
	border-radius: 50%;
	display: grid;
	place-items: center;
	background: var(--lf-line);
	color: var(--lf-muted);
	font-weight: 800;
}

/* Practised, goal not reached: a partial marker rather than an empty one. A
   day where a child did fifteen of twenty is not the same as one they missed,
   and showing it as empty is the fastest way to make a streak feel unfair. */
.lf-day-some {
	background: var(--lf-fox-light);
	color: var(--lf-fox-dark);
}

.lf-day-done {
	background: var(--lf-right);
	color: #fff;
}

.lf-day-today .lf-day-dot {
	outline: 3px solid var(--lf-ink);
	outline-offset: 2px;
}

.lf-streak {
	display: inline-flex;
	align-items: center;
	gap: .4rem;
	font-weight: 800;
	color: var(--lf-fox-dark);
}

/* --- The dashboard and its start bar ------------------------------------- */

/* What Bootstrap's py-4 was doing, spelled out so that .lf-dashboard-barred can
   add to it. A utility class writes !important and nothing can add to that. */
.lf-dashboard {
	padding-top: 1.5rem;
	padding-bottom: 1.5rem;
}

/*
 * A second practise button, pinned to the bottom of a phone.
 *
 * Only there. On anything wider the skill cards and the real button are on
 * screen at once, and a duplicate of a control the learner can already see is
 * one more thing to read — hence display:none here and a width to turn it on,
 * rather than the other way round.
 */
.lf-start-bar {
	display: none;
}

@media (max-width: 575.98px) {
	.lf-start-bar {
		display: block;
		/* Fixed is safe here in a way it would not be on the practice screen:
		   this page has no text input, so no mobile keyboard can open under it.
		   See .lf-topbar for the other half of that argument. */
		position: fixed;
		left: 0;
		right: 0;
		bottom: 0;
		z-index: 30;
		padding: .75rem 1rem calc(.75rem + env(safe-area-inset-bottom));
		background: var(--lf-cream);
		/* Upwards, so the bar reads as sitting above the page rather than being
		   cut out of it — the page keeps scrolling underneath. */
		box-shadow: 0 -10px 24px rgba(33, 36, 61, .12);
		transition: transform var(--lf-speed) var(--lf-ease),
			visibility var(--lf-speed) ease;
	}

	/*
	 * .lf-btn-lg's side padding is there to stop a button standing alone in a
	 * page from looking stretched. This one is as wide as the phone and has a
	 * deck name in it, and that padding was what pushed the three longest names
	 * — "Dividieren mit Rest" among them — onto a second line, which made the
	 * bar change height as the child moved from card to card.
	 */
	.lf-start-bar .lf-btn {
		padding-inline: 1.25rem;
	}

	/*
	 * Parked below the fold rather than display:none, so it slides in instead of
	 * appearing, and visibility with it so that while it is away the button
	 * cannot be tabbed into or read out — the same pairing as .lf-quit-sheet.
	 */
	.lf-start-bar-shut {
		transform: translateY(130%);
		visibility: hidden;
	}

	/*
	 * Room for the bar to stand in, added only while it is up. The last skill
	 * card is the one the child scrolled down for; it must not end up behind it.
	 *
	 * The bar measures 116px — a 68px button, .75rem of padding above and below
	 * it — so 7rem left the last card four pixels underneath it, which is enough
	 * to look like the list had been cut off rather than ended. 8rem clears it
	 * with a gap you can see, and the safe-area inset is added on top for the
	 * same reason the bar adds it to its own padding: on a phone with a home
	 * indicator the bar is that much taller.
	 */
	.lf-dashboard-barred {
		padding-bottom: calc(8rem + env(safe-area-inset-bottom));
	}
}

/* --- Skill cards -------------------------------------------------------- */

.lf-skill {
	display: flex;
	align-items: center;
	gap: 1rem;
	width: 100%;
	text-align: left;
	border: 0;
	padding: 1rem 1.25rem;
	border-radius: var(--lf-radius);
	background: var(--lf-paper);
	box-shadow: 0 4px 0 var(--lf-line), var(--lf-shadow);
	cursor: pointer;
	transition: transform var(--lf-speed) var(--lf-ease),
		box-shadow var(--lf-speed) ease, background var(--lf-speed) ease;
}

.lf-skill:active {
	transform: translateY(4px);
}

/*
 * The chosen deck — the one the practise button will start.
 *
 * A ring drawn inside the card rather than a border: .lf-skill sets border 0
 * and draws its whole edge with box-shadow, so a real border would push the
 * icon and the bar of one card three pixels out of line with the other seven
 * the moment it was picked.
 *
 * Two signals, ring and fill, because one of them is a colour — and the cards
 * are already eight different colours, one per skill, so a child looking for
 * "the orange one" has no way to read a ninth. No lift here on purpose: a card
 * that sits proud of the others cannot also press down when tapped, and being
 * tappable still matters — this one is not disabled, it is chosen.
 */
.lf-skill-on {
	background: var(--lf-cream);
	box-shadow: 0 4px 0 var(--lf-fox-light), var(--lf-shadow),
		inset 0 0 0 3px var(--lf-fox);
}

.lf-skill-icon {
	flex: 0 0 auto;
	width: 52px;
	height: 52px;
	border-radius: 16px;
	display: grid;
	place-items: center;
	font-size: 1.5rem;
	background: var(--lf-cream);
}

.lf-skill-tables-easy .lf-skill-icon { background: #e6f6ff; }
.lf-skill-multiplication-tables .lf-skill-icon { background: #ffeede; }
.lf-skill-division-tables .lf-skill-icon { background: #eae4ff; }
.lf-skill-mixed-tables .lf-skill-icon { background: #dff7ef; }
.lf-skill-tables-tricky .lf-skill-icon { background: #fff0d6; }
.lf-skill-division-remainder .lf-skill-icon { background: #ffe3e3; }
.lf-skill-plus-minus-100 .lf-skill-icon { background: #e4f0ff; }
.lf-skill-plus-minus-hundreds .lf-skill-icon { background: #ffe8f0; }
.lf-skill-rounding .lf-skill-icon { background: #e6f7ee; }
.lf-skill-big-numbers-mental .lf-skill-icon { background: #ececf4; }
.lf-skill-missing-numbers .lf-skill-icon { background: #fff6d6; }
.lf-skill-written-multiply .lf-skill-icon { background: #e3f4f7; }
.lf-skill-written-divide .lf-skill-icon { background: #f3e8ff; }
.lf-skill-terms .lf-skill-icon { background: #f0ebe2; }

/* The English decks, by direction rather than one rule each. There are ten of
   them and they differ only in unit, so a colour per unit would be ten shades
   nobody can tell apart and ten more to invent when Unit 5 arrives. Two tints
   carry the only distinction that matters on the picker: tapping or writing. */
[class*="lf-skill-access1-"][class*="-choice"] .lf-skill-icon { background: #e6f6ff; }
[class*="lf-skill-access1-"][class*="-typed"] .lf-skill-icon { background: #e4f0ff; }
.lf-skill-access1-numbers .lf-skill-icon { background: #eae4ff; }
[class*="lf-skill-nawi-"][class*="-choice"] .lf-skill-icon { background: #e2f7f3; }
[class*="lf-skill-nawi-"][class*="-typed"] .lf-skill-icon { background: #d8f0ea; }

.lf-skill-body {
	flex: 1;
	min-width: 0;
}

.lf-skill-name {
	font-weight: 800;
}

.lf-skill-bar {
	height: 10px;
	margin-top: .4rem;
	border-radius: var(--lf-radius-pill);
	background: var(--lf-line);
	overflow: hidden;
}

.lf-skill-bar-fill {
	height: 100%;
	border-radius: var(--lf-radius-pill);
	background: var(--lf-right);
	transition: width .6s var(--lf-ease);
}

/* "3 fällig" — the count of due facts. */
.lf-due-badge {
	flex: 0 0 auto;
	min-width: 28px;
	height: 28px;
	padding: 0 .5rem;
	border-radius: var(--lf-radius-pill);
	display: grid;
	place-items: center;
	background: var(--lf-fox);
	color: #fff;
	font-size: .85rem;
	font-weight: 800;
}

/* ==========================================================================
   Results
   ========================================================================== */

.lf-stars {
	display: flex;
	justify-content: center;
	gap: .5rem;
	font-size: 2.5rem;
}

.lf-star {
	color: var(--lf-line);
	transform: scale(.8);
}

.lf-star-on {
	color: var(--lf-gold);
	animation: lf-pop .45s var(--lf-ease) both;
}

/* Staggered so the three stars land one after another rather than together. */
.lf-star:nth-child(1) { animation-delay: .05s; }
.lf-star:nth-child(2) { animation-delay: .25s; }
.lf-star:nth-child(3) { animation-delay: .45s; }

.lf-score {
	font-family: var(--lf-font-num);
	font-size: 3.5rem;
	font-weight: 800;
	line-height: 1;
}

.lf-stat {
	text-align: center;
}

.lf-stat-value {
	font-family: var(--lf-font-num);
	font-size: 1.6rem;
	font-weight: 800;
}

.lf-stat-label {
	font-size: .8rem;
	color: var(--lf-muted);
}

.lf-rank-master { color: var(--lf-gold); }
.lf-rank-on-the-way { color: var(--lf-sky); }
.lf-rank-keep-practising { color: var(--lf-fox); }

/* A mascot just unlocked, on the results screen.

   One card, usually. The row is centred and wraps, because two can land in the
   same round — finishing a first-ever round on the seventh consecutive day
   hands over Paul and Luna at once, and that is a nice moment rather than a
   case to prevent. */
.lf-unlock-row {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: 1rem;
}

.lf-unlock-card {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: .25rem;
	padding: 1rem 1.5rem .75rem;
	border-radius: var(--lf-radius);
	background: var(--lf-paper);
	box-shadow: var(--lf-shadow);
	/* Arrives rather than appears. The badge medals below already use this, and
	   the one thing on this screen that is bigger news than a badge should not
	   be the one thing that is simply there when the page renders. */
	animation: lf-pop .5s var(--lf-ease) both;
}

.lf-unlock-name {
	font-weight: 800;
	font-size: 1.1rem;
	color: var(--lf-ink);
}

/* Badges. */
.lf-award {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: .4rem;
	width: 88px;
	text-align: center;
	font-size: .72rem;
	font-weight: 700;
	color: var(--lf-ink-soft);
}

.lf-award-medal {
	width: 60px;
	height: 60px;
	border-radius: 50%;
	display: grid;
	place-items: center;
	font-size: 1.6rem;
	background: var(--lf-gold);
	box-shadow: var(--lf-shadow);
}

.lf-award-new .lf-award-medal {
	animation: lf-pop .5s var(--lf-ease) both;
}

/* Not yet earned: shown, but plainly not yours. Hiding them would remove the
   only hint about what there is to aim for. */
.lf-award-locked .lf-award-medal {
	background: var(--lf-line);
	color: var(--lf-muted);
}

.lf-award-locked {
	opacity: .7;
}

/* --- The collection ------------------------------------------------------
   One row per companion, earned or not, on the progress screen — and the same
   card again on the results screen for the next one due.

   A card and not a medal, because unlike a badge this one has a sentence to
   carry: the name says which character and the line under it says what to go
   and do. A disc with a name under it, badge-style, has nowhere to put that.

   Deliberately not the Mascot drawing. The full character is what a child
   meets at the moment it is unlocked, and spending that on a greyed preview
   would leave the unlock itself with nothing left to show. */
.lf-collect {
	display: flex;
	align-items: center;
	gap: .75rem;
	padding: .6rem .9rem;
	border-radius: var(--lf-radius);
	background: var(--lf-paper);
	box-shadow: var(--lf-shadow);
	/* The results screen centres its contents; this card reads as a label with
	   a sentence under it and wants its two lines flush left on both screens. */
	text-align: left;
	/* Wide enough for the longest hint to take two lines rather than ten. Also
	   what stops the single card on the results screen collapsing to the width
	   of "Noch 180 XP". */
	min-width: 220px;
}

/* The collection on the progress screen, as a grid rather than a wrapped row.
   Nine cards whose text runs from "Noch 180 XP" to "lerne eine Reihe ganz
   durch" have nine different widths, and a row of them wraps into a ragged
   edge with two orphans on the end — which reads as a jumble, where the point
   of the screen is a ladder. Equal columns cost nothing and are scannable. */
.lf-collect-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
	gap: .5rem;
}

.lf-collect-text {
	display: flex;
	flex-direction: column;
}

.lf-collect-name {
	font-weight: 800;
	color: var(--lf-ink);
}

.lf-collect-hint {
	font-size: .78rem;
	color: var(--lf-ink-soft);
}

/* Still to come. Greyed rather than hidden, and the species still legible: a
   child who can see that the locked one is a unicorn has something to want,
   which a blank silhouette does not give them. */
.lf-collect-locked {
	opacity: .7;
}

/* The swatch keeps its .lf-char-* class so the colours are right the moment it
   is earned; these two lines are what hold them back until then. A filter
   rather than another background, because the species is an emoji and does not
   take a colour. */
.lf-collect-locked .lf-mascot-swatch {
	background: var(--lf-line);
	box-shadow: none;
	filter: grayscale(1);
}

/* The wrong answers offered for another go. */
.lf-review-chip {
	display: inline-flex;
	align-items: center;
	padding: .45rem .9rem;
	margin: .25rem;
	border-radius: var(--lf-radius-pill);
	background: var(--lf-wrong-soft);
	color: #a3262c;
	font-family: var(--lf-font-num);
	font-weight: 800;
}

/* ==========================================================================
   Forms
   ========================================================================== */

/*
 * .lf-field is the <label> itself, wrapping its own input — see the note at the
 * top of Login.ui.xml. Block, so the caption sits above the box rather than
 * beside it.
 */
.lf-field {
	display: block;
	margin-bottom: 1rem;
}

.lf-label {
	display: block;
	margin-bottom: .35rem;
	font-weight: 700;
	font-size: .9rem;
	color: var(--lf-ink-soft);
}

.lf-input {
	width: 100%;
	min-height: 56px;
	padding: .75rem 1rem;
	border: 2px solid var(--lf-line);
	border-radius: var(--lf-radius);
	background: var(--lf-paper);
	font: inherit;
	font-size: 1.05rem;
	transition: border-color var(--lf-speed) ease, box-shadow var(--lf-speed) ease;
}

.lf-input:focus {
	outline: 0;
	border-color: var(--lf-fox);
	box-shadow: 0 0 0 4px rgba(249, 124, 28, .18);
}

.lf-input-bad {
	border-color: var(--lf-wrong);
}

/* Error and status messages. role="alert" in the markup does the announcing;
   this only makes it visible. */
.lf-alert {
	padding: .85rem 1.1rem;
	border-radius: var(--lf-radius);
	font-weight: 700;
	margin-bottom: 1rem;
}

.lf-alert-bad {
	background: var(--lf-wrong-soft);
	color: #a3262c;
}

.lf-alert-good {
	background: var(--lf-right-soft);
	color: #0d6b47;
}

/*
 * Hiding, for anything toggled from Java.
 *
 * !important is load-bearing here rather than lazy: these classes have to win
 * against Bootstrap's own display utilities, which are all !important
 * themselves. Without it, adding .lf-hidden to an element that also carries
 * .d-flex does nothing at all.
 */
.lf-hidden,
.lf-alert-hidden {
	display: none !important;
}

/* --- Choosers: fox, school year -----------------------------------------
   Radio groups that do not look like radio groups. The input stays in the DOM
   and keeps the keyboard and screen-reader behaviour; only its appearance is
   replaced. */

.lf-choice-row {
	display: flex;
	gap: .75rem;
	flex-wrap: wrap;
}

.lf-choice {
	position: relative;
	flex: 1 1 6rem;
}

.lf-choice input {
	position: absolute;
	opacity: 0;
	width: 100%;
	height: 100%;
	margin: 0;
	cursor: pointer;
}

.lf-choice-face {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: .4rem;
	padding: .85rem .5rem;
	border: 3px solid var(--lf-line);
	border-radius: var(--lf-radius);
	background: var(--lf-paper);
	font-weight: 700;
	text-align: center;
	transition: border-color var(--lf-speed) ease, transform var(--lf-speed) var(--lf-ease);
}

.lf-choice input:checked + .lf-choice-face {
	border-color: var(--lf-fox);
	transform: translateY(-2px);
}

.lf-choice input:focus-visible + .lf-choice-face {
	outline: 3px solid var(--lf-ink);
	outline-offset: 2px;
}

/*
 * The swatch in the mascot picker: a disc in the character's own colours with
 * the species on it.
 *
 * Both halves are needed and neither is enough. The disc alone was right while
 * there were three foxes and is not now — a brown disc does not say bear rather
 * than owl. The emoji alone was what this showed before the three foxes got
 * their colours, and three identical foxes gave the child no way at all to tell
 * them apart. Together they carry the two things that actually vary.
 *
 * The colours come from the .lf-char-* class beside this one, which is the same
 * class the drawing uses, so the swatch and the mascot it opens can never
 * disagree about what Fiene looks like.
 */
.lf-mascot-swatch {
	display: grid;
	place-items: center;
	width: 40px;
	height: 40px;
	border-radius: 50%;
	background: var(--m-coat);
	box-shadow: inset 0 -6px 0 var(--m-coat-dark);
	font-size: 1.1rem;
	/* The emoji sits on the coat colour, which for Luna is nearly white and for
	   Benno is nearly brown. A shadow rather than a colour, so one value works
	   on both. */
	text-shadow: 0 1px 2px rgba(0, 0, 0, .35);
}

/* --- Settings -------------------------------------------------------------
   The sound switch is a checkbox wearing .lf-choice, so it gets the same face
   as the fox and year pickers. Unlike a radio it has no sibling to contrast
   with, and an unchecked box that differs from a checked one only by the colour
   of its border is not a state anybody reads — so the face carries both
   speakers and shows whichever one is true. */

.lf-choice input:checked + .lf-choice-face .lf-sound-off,
.lf-choice input:not(:checked) + .lf-choice-face .lf-sound-on {
	display: none;
}

/* The way to the mascot picker, which lives on the dashboard. A link, not a
   control: the disc says which one is in use, and the whole row goes there. */
.lf-mascot-link {
	display: inline-flex;
	align-items: center;
	gap: .75rem;
	font-weight: 700;
	color: var(--lf-ink);
}

/* ==========================================================================
   Navigation
   ========================================================================== */

.lf-nav {
	display: flex;
	align-items: center;
	gap: 1rem;
	padding: .85rem 1.25rem;
	background: var(--lf-paper);
	box-shadow: var(--lf-shadow);
	/*
	 * Never wider than the screen, whatever is in it.
	 *
	 * A flex item does not shrink below its content, so before this the row of
	 * brand, XP, gear and "Abmelden" simply stuck out past the right edge of a
	 * phone and made the whole document scroll sideways. That is not only ugly
	 * here: a position:fixed element is laid out against the viewport, so the
	 * dashboard's start bar stayed behind while the page slid under it and
	 * arrived cut in half. One overflowing row breaks screens that have nothing
	 * to do with it.
	 *
	 * The rules below are sized so this never actually wraps on a phone. It is
	 * here for the language that writes "Abmelden" in four more letters than
	 * German does — two rows is a worse bar and a working page; sideways scroll
	 * is neither.
	 */
	flex-wrap: wrap;
}

.lf-brand {
	display: inline-flex;
	align-items: center;
	gap: .5rem;
	font-weight: 900;
	font-size: 1.25rem;
	color: var(--lf-ink);
	text-decoration: none;
}

.lf-brand:hover {
	color: var(--lf-fox-dark);
}

.lf-nav-spacer {
	flex: 1;
}

/*
 * The two halves of the bar: account links when signed out, XP and sign-out
 * when signed in.
 *
 * Deliberately NOT Bootstrap's .d-flex. Every Bootstrap display utility is
 * `display: … !important`, which silently beats the inline style GWT writes
 * when it hides an element — so a .d-flex container toggled from Java stays
 * visible and both halves of this bar show at once. Anything whose visibility
 * is controlled from code gets its own class and .lf-hidden below.
 */
.lf-nav-group {
	display: flex;
	align-items: center;
	gap: .5rem;
}

/* A button in the bar whose whole label is its icon — the settings gear. The
   name lives on title= and aria-label=, so only the horizontal padding meant for
   a word has to go; the tap target keeps its 56px height. */
.lf-nav-icon {
	padding: .75rem 1rem;
	font-size: 1.2rem;
}

.lf-xp-pill {
	display: inline-flex;
	align-items: center;
	gap: .35rem;
	padding: .35rem .85rem;
	border-radius: var(--lf-radius-pill);
	background: var(--lf-cream);
	font-weight: 800;
	color: var(--lf-fox-dark);
}

/* The door only exists on a phone; see the media query below. */
.lf-nav-out-icon {
	display: none;
}

/*
 * The bar on a phone: same controls, less of the wording around them.
 *
 * At 375px the signed-in row wanted about 490px and the signed-out row about
 * as much, so something had to go. What goes is words that are recoverable
 * from where they sit — the fox in the corner of every app is the way home,
 * and a door at the far end of a bar is the way out — and never a control.
 * Both keep their names on title= and aria-label=, which is what the gear
 * beside them already did.
 */
@media (max-width: 575.98px) {
	.lf-nav {
		gap: .5rem;
		padding: .75rem;
	}

	.lf-brand-word {
		display: none;
	}

	/* The fox is now the whole brand, so it is drawn at the size the brand was
	   rather than the size a character beside a word needs to be. */
	.lf-brand {
		font-size: 1.4rem;
	}

	/* The generous side padding is for a button standing on its own in a page.
	   Four of them in a row is a different problem. */
	.lf-nav .lf-btn {
		padding: .75rem 1rem;
	}

	.lf-nav-out-word {
		display: none;
	}

	.lf-nav-out-icon {
		display: inline;
		font-size: 1.2rem;
	}
}

/* ==========================================================================
   Confetti
   ========================================================================== */

/* Fixed and non-interactive: it covers the results screen and must never
   intercept the tap on "nochmal". */
.lf-confetti {
	position: fixed;
	inset: 0;
	pointer-events: none;
	z-index: 60;
}

/* ==========================================================================
   Keyframes
   ========================================================================== */

@keyframes lf-slide-in {
	from { opacity: 0; transform: translateX(28px) scale(.96); }
	to   { opacity: 1; transform: none; }
}

@keyframes lf-pop {
	0%   { transform: scale(1); }
	45%  { transform: scale(1.09); }
	100% { transform: scale(1); }
}

@keyframes lf-shake {
	0%, 100% { transform: translateX(0); }
	20%      { transform: translateX(-9px); }
	40%      { transform: translateX(8px); }
	60%      { transform: translateX(-5px); }
	80%      { transform: translateX(3px); }
}

@keyframes lf-bounce {
	0%, 100% { transform: translateY(0); }
	40%      { transform: translateY(-14px); }
	70%      { transform: translateY(-4px); }
}

@keyframes lf-jump {
	0%, 100% { transform: translateY(0) rotate(0); }
	30%      { transform: translateY(-22px) rotate(-7deg); }
	60%      { transform: translateY(-6px) rotate(5deg); }
}

@keyframes lf-wobble {
	0%, 100% { transform: rotate(0); }
	25%      { transform: rotate(-7deg); }
	75%      { transform: rotate(6deg); }
}

@keyframes lf-bob {
	0%, 100% { transform: translateY(0); }
	50%      { transform: translateY(-8px); }
}

@keyframes lf-sway {
	0%, 100% { transform: rotate(-6deg); }
	50%      { transform: rotate(8deg); }
}

@keyframes lf-blink {
	0%, 92%, 100% { transform: scaleY(1); }
	95%           { transform: scaleY(.1); }
}

@keyframes lf-caret {
	0%, 49%   { opacity: 1; }
	50%, 100% { opacity: 0; }
}

@keyframes lf-pulse {
	0%, 100% { transform: scale(1); }
	50%      { transform: scale(1.12); }
}

@keyframes lf-float-up {
	from { opacity: 1; transform: translate(-50%, 0) scale(1); }
	to   { opacity: 0; transform: translate(-50%, -70px) scale(1.25); }
}

/* ==========================================================================
   Reduced motion
   ========================================================================== */

/*
 * Everything above is decoration. Under prefers-reduced-motion all of it stops,
 * and the app still says the same things: the score is a number, right and
 * wrong are an icon and a word as well as a colour, and the fox simply sits
 * still. Nothing here removes information, which is the test for whether an
 * animation was load-bearing.
 */
@media (prefers-reduced-motion: reduce) {
	*,
	*::before,
	*::after {
		animation-duration: .001ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: .001ms !important;
		scroll-behavior: auto !important;
	}

	.lf-btn:active,
	.lf-key:active,
	.lf-skill:active {
		transform: none;
	}
}

/* ==========================================================================
   Small screens
   ========================================================================== */

@media (max-width: 420px) {
	.lf-task {
		padding: 1.5rem 1rem;
	}

	.lf-pad {
		gap: .45rem;
		padding: .5rem;
	}

	.lf-key {
		min-height: 58px;
		font-size: 1.5rem;
	}

	.lf-draw,
	.lf-mode-row {
		padding-left: .5rem;
		padding-right: .5rem;
	}

	.lf-draw {
		gap: .45rem;
		padding-top: .5rem;
		padding-bottom: .5rem;
	}

	.lf-draw-box {
		height: 13rem;
	}
}

/* A short landscape phone has no room for a box this tall, and a digit written
   in a letterbox is a digit the recogniser has to guess at. The keypad is the
   better answer at this height, but taking the choice away mid-round would be
   worse than a smaller box, so it shrinks instead. */
@media (max-height: 560px) and (orientation: landscape) {
	.lf-draw-box {
		height: 9rem;
	}
}

/* On a short landscape phone the pad and the card together do not fit. The
   mascot is the thing worth losing: it is company, not information.

   Scoped to the round. .lf-mascot-row is the mascot widget's own class and the
   dashboard now shows one too, where nothing is fighting for the height — that
   page scrolls — and hiding it would take the picker's tap target with it. */
@media (max-height: 560px) and (orientation: landscape) {
	.lf-round .lf-mascot-row {
		display: none;
	}

	.lf-equation {
		font-size: clamp(1.75rem, 7vw, 2.5rem);
	}
}


/* ==========================================================================
   Landscape: the question beside the keypad
   ========================================================================== */

/*
 * The round is a vertical column by design — bar, question, fox, keypad — and
 * that column needs about 885px of height to stand up straight. A tablet in
 * landscape has between 540 and 834, so the stack overflowed and took the ✓ key
 * with it: the one control a round cannot be finished without ended up below
 * the fold, on the device most likely to be handed to a child.
 *
 * Scrolling is not the fix. The keys are found by muscle memory and the
 * question has to stay visible while they are being pressed; a layout that puts
 * a scroll between the two has already lost, and a child who cannot find the
 * tick does not think to swipe — they think the app is stuck.
 *
 * So in landscape the one column becomes two: question and fox on the left,
 * keypad on the right, the bar still across the top. The vertical room this
 * frees is exactly the room that was missing, and it spends the horizontal room
 * that the portrait cap was otherwise leaving empty on both sides.
 *
 * Bounded by max-height rather than applied to every landscape window: above
 * this the portrait stack fits comfortably, and there the narrow centred column
 * is the deliberate choice documented on .lf-round.
 */
@media (orientation: landscape) and (max-height: 900px) {
	.lf-round {
		/*
		 * Wider than the portrait cap but still bounded. A 30rem column down one
		 * side of a 1194px iPad is not a layout, it is two margins; a full-width
		 * one puts the question and the answer a whole arm apart.
		 */
		width: min(100%, 58rem);

		display: grid;
		/*
		 * The keypad takes a fixed, thumb-sized column and the question takes
		 * whatever is left — not 1fr each, which would squeeze the keys below
		 * aiming size on a 960px window and stretch them past it on a 1194px one.
		 */
		grid-template-columns: minmax(0, 1fr) minmax(0, 20rem);
		grid-template-rows: auto 1fr auto;
		column-gap: 1rem;
		align-items: center;

		/* Landscape is where a notch sits on a side rather than the top. */
		padding-left: env(safe-area-inset-left);
		padding-right: env(safe-area-inset-right);

		/*
		 * The fox's speech bubble is the bottom-most thing in the left column now,
		 * and its tail is an 18px square rotated 45° hung below the bubble — about
		 * 13px that is painted outside the bubble's own box. Flush against the
		 * viewport that tail is what makes the page scrollable by a hair. Room for
		 * it, on top of whatever the home indicator already asks for.
		 */
		padding-bottom: calc(env(safe-area-inset-bottom) + .9rem);
	}

	.lf-round .lf-topbar {
		grid-column: 1 / -1;
		grid-row: 1;
	}

	.lf-round .lf-stage {
		grid-column: 1;
		grid-row: 2;
	}

	.lf-round .lf-mascot-row {
		grid-column: 1;
		grid-row: 3;
	}

	/*
	 * Spans both content rows and centres against them, so the keys sit level
	 * with the question rather than hanging from the bar.
	 */
	.lf-round .lf-answer {
		grid-column: 2;
		grid-row: 2 / 4;
		align-self: center;
	}

	/* The pad's own 24rem cap is sized for a phone's full width; inside a 20rem
	   column it only stops the keys filling the space they were just given. */
	.lf-round .lf-pad {
		width: 100%;
		padding: .5rem 0;
	}
}

/*
 * A phone on its side is the one case two columns do not rescue on their own.
 * Four rows of 64px keys, the mode switch and the bar come to about 421px, and
 * a landscape iPhone has 390 — so the bottom row, the ✓ among it, still fell
 * off the end.
 *
 * 64px is the thumb-target size the rest of the stylesheet is built on and it is
 * not given up lightly. But the trade here is not "big key" against "small key",
 * it is a slightly smaller key against a key that cannot be reached at all. 52px
 * is still above both the 44px Apple asks for and the 48dp Material does.
 *
 * Deliberately keyed to the height at which the full-size pad stops fitting
 * rather than to the 560px used above: a 540px-tall tablet has room for the real
 * keys once the layout is two columns, and shrinking them there would be a
 * regression bought for nothing.
 */
@media (orientation: landscape) and (max-height: 450px) {
	.lf-round .lf-key {
		min-height: 52px;
		font-size: 1.5rem;
	}

	.lf-round .lf-pad {
		gap: .4rem;
		padding: .2rem 0;
	}

	/*
	 * The allowance above is room for the speech bubble's tail — and by this
	 * height the mascot has already been hidden by the rule further up, so there
	 * is no bubble and no tail to leave room for. Reserving it anyway spends 14px
	 * of the scarcest height in the app on something that is not being painted.
	 */
	.lf-round {
		padding-bottom: env(safe-area-inset-bottom);
	}

	/* The switch keeps its 44px — it is a tap target and the note on .lf-mode is
	   right that it should not shrink. Only the gap above it goes. */
	.lf-round .lf-mode-row {
		padding-top: 0;
	}
}
