/*
 * 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;
}

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

/* 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 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
   ========================================================================== */

.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;
}

/* ==========================================================================
   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 fox
   ========================================================================== */

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

/* Palettes. The three foxes differ only in colour, which is the whole point —
   see EnumMascot. */
.lf-fox-finn {
	--fox-coat: #f97c1c;
	--fox-coat-dark: #d95f06;
	--fox-belly: #fff3e3;
}

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

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

.lf-fox-coat {
	fill: var(--fox-coat);
}

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

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

.lf-fox-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-fox-eye {
	animation: lf-blink 4.5s infinite;
}

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

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

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

.lf-mood-oops .lf-fox {
	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.
 */
.lf-mood-oops .lf-fox-ear {
	transform-box: fill-box;
	transform-origin: 50% 90%;
}

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

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

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

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

/* What the fox says. A short line, never a paragraph: it is read in the second
   between two questions or it is not read at all. */
.lf-fox-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-fox-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-fox-row {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: .6rem;
}

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

.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);
}

/* --- 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);
}

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

.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-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); }

/* 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 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 colour swatch in the fox picker: a coloured disc rather than three
 * identical emoji, which is what the picker showed before and gave the child no
 * way at all to tell the three apart. Each swatch takes its own --fox-coat from
 * the .lf-fox-finn / -fiene / -flip class beside it, so the swatch and the
 * actual fox can never disagree about what Fiene looks like.
 */
.lf-fox-swatch {
	width: 40px;
	height: 40px;
	border-radius: 50%;
	background: var(--fox-coat);
	box-shadow: inset 0 -6px 0 var(--fox-coat-dark);
}

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

.lf-nav {
	display: flex;
	align-items: center;
	gap: 1rem;
	padding: .85rem 1.25rem;
	background: var(--lf-paper);
	box-shadow: var(--lf-shadow);
}

.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;
}

.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);
}

/* ==========================================================================
   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;
	}
}

/* On a short landscape phone the pad and the card together do not fit. The
   fox is the thing worth losing: it is company, not information. */
@media (max-height: 560px) and (orientation: landscape) {
	.lf-fox-row {
		display: none;
	}

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