/* Mini app layer over the design files (design/app.css + design/flows.css): the mock 390×844 screen becomes the
   whole Telegram viewport, the lists scroll, and every card that is a link in the mock-ups is a real button. */
html, body { height: 100%; overscroll-behavior: none; }
html, body { background: #fff; }
html[data-theme="dark"], html[data-theme="dark"] body { background: #000; }
body { -webkit-tap-highlight-color: transparent; }
.theme-night img.logo { filter: invert(1); }

.screen.app { position: fixed; inset: 0; width: auto; min-height: 0; height: auto; }
.app .scroll { overflow-y: auto; -webkit-overflow-scrolling: touch; padding-top: 12px; padding-bottom: 28px; }
.app .scroll > :last-child { margin-bottom: 4px; }
.app .boot { flex: 1; display: flex; align-items: center; justify-content: center; }
.app .wrap { width: 100%; max-width: 560px; margin: 0 auto; box-sizing: border-box; }
.app .scroll.wrap, .app .tgmain.wrap, .app .sheet .wrap { max-width: 560px; }

/* tappable things */
:where(.app) button { font: inherit; color: inherit; background: none; border: 0; padding: 0; margin: 0; text-align: inherit; cursor: pointer; }
.app [data-act] { cursor: pointer; user-select: none; -webkit-user-select: none; }
.app .lvl, .app .topic, .app .opt, .app .tag, .app .mini, .app .pill, .app .grid40 span, .app .lang, .app .toggle, .app .tgmain .b { transition: transform .08s ease, opacity .15s ease; }
.app .lvl:active, .app .topic:active, .app .opt:not(.locked):active, .app .tag:active, .app .mini:active, .app .pill:active, .app .grid40 span:active, .app .tgmain .b:active { transform: scale(.98); }
.app .opt.locked { cursor: default; }
.app .lvl.soon { cursor: default; }
.app .tag.off, .app .mini.off { opacity: .4; pointer-events: none; }

/* figures: a bit taller than the mock so chart extracts stay readable */
.app .fig { height: auto; min-height: 150px; max-height: 240px; aspect-ratio: 16 / 10; padding: 10px 12px; box-sizing: border-box; }

/* header and bottom bar are drawn only outside Telegram (inside, Telegram draws its own) */
.app .tgh { border-bottom: 1px solid var(--line); }
.app .tgh > :first-child, .app .tgh > :last-child { width: 72px; }
.app .tgh > :last-child { text-align: right; }
.app .tgh b { flex: 1; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.app .tgmain { padding-bottom: max(14px, env(safe-area-inset-bottom)); }
.app .tgmain .b.off { pointer-events: none; }
.app .tgmain .b { padding: 0 14px; min-width: 0; text-align: center; }
.app .tgmain .b svg { flex: none; }
.app .tgmain .b .t { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.app .nav { padding-bottom: max(12px, env(safe-area-inset-bottom)); box-shadow: 0 -10px 28px -8px rgba(0, 0, 0, .14); position: relative; z-index: 1; }
.app.theme-night .nav { box-shadow: 0 -10px 28px -8px rgba(0, 0, 0, .7); }
/* a button bar above the tab bar: no edge of its own and no safe-area padding (the tab bar carries both) */
.app .tgmain:has(+ .nav) { border-top: 0; padding-top: 6px; padding-bottom: 12px; }
.app .nav a { cursor: pointer; }
.app .hint-line { text-align: center; }

/* bottom sheet over the current screen */
.app .scrim { z-index: 10; animation: fade .18s ease; }
.app .sheet { z-index: 11; max-height: 88%; overflow-y: auto; animation: up .22s cubic-bezier(.2, .8, .2, 1); padding-bottom: max(24px, env(safe-area-inset-bottom)); }
.app .sheet .grip { width: 40px; height: 5px; border-radius: 3px; background: var(--tag-line); margin: 0 auto; flex: none; }
@keyframes up { from { transform: translateY(40px); opacity: 0; } }
@keyframes fade { from { opacity: 0; } }

/* small pieces the mock-ups did not need */
.app .empty { padding: 28px 8px; text-align: center; }
.app .tg-ava { overflow: hidden; }
.app .tg-ava img { width: 100%; height: 100%; object-fit: cover; }
.app .att { display: flex; justify-content: space-between; gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--line); }
.app .att:last-child { border-bottom: 0; }
.app .bad-t { color: var(--danger); }
.app .continue { border: 2px solid var(--ink); border-radius: 24px; padding: 16px; display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 12px; align-items: center; }
.app .rev .ans.ok.plain .l { background: var(--accent); }
.app .toast { position: absolute; left: 50%; bottom: 96px; transform: translateX(-50%); z-index: 20; background: var(--sel-bg); color: var(--sel-ink); font: 700 14px 'Carlito', Arial, sans-serif; padding: 10px 16px; border-radius: 18px; animation: fade .2s ease; white-space: nowrap; }

/* progress bars carry two segments: mastered, then halfway (right once) */
.app .bar { position: relative; }
.app .bar i { position: absolute; left: 0; top: 0; }
.app .bar i.half { position: absolute; background: var(--accent); opacity: .35; }
.app .bar i:first-child { z-index: 1; }
.app .sheet:has(.sheet-foot) { padding-bottom: 0; }
/* practice set-up: Start stays in view while the sheet scrolls */
.app .sheet-foot { position: sticky; bottom: 0; background: var(--bg); padding: 10px 0 max(20px, env(safe-area-inset-bottom)); margin-top: -4px; box-shadow: 0 -10px 16px -6px var(--bg); }
/* text-only "card" figures render as HTML, not a fixed-height drawing */
.app .figcard { position: relative; background: var(--band); border-radius: 20px; padding: 14px 18px 16px; display: flex; flex-direction: column; gap: 8px; }
.app .figcard ul { margin: 0; padding: 0 0 0 18px; display: flex; flex-direction: column; gap: 4px; font: 400 15px/1.35 'Merriweather', Georgia, serif; color: var(--ink); }
.app .figtag { position: absolute; top: 12px; right: 14px; background: var(--danger); color: #fff; font: 700 12px 'Carlito', Arial, sans-serif; padding: 4px 10px; border-radius: 12px; }
/* home: levels without a bank, grouped */
.app .soonlist { padding: 14px 18px 4px; }
.app .soonlist .kicker { display: block; margin-bottom: 2px; }
/* MCP card on Home: what the MCP server is, a pill to its page on the site (/mcp) */
.app .mcpband { display: flex; flex-direction: column; gap: 10px; padding: 18px 18px 20px; }
.app .mcpband .body-muted { margin: 0 0 4px; }
.app .mcpband .pill.pill-quiet { height: 50px; background: var(--bg); color: var(--ink); }
/* Feedback card on Home: the author's photo and handle, a line of text, a pill to Telegram */
.app .fback { display: flex; flex-direction: column; gap: 14px; padding: 18px 18px 20px; }
.app .fback .fb-who { gap: 14px; align-items: center; }
.app .fback .fb-face { width: 56px; height: 56px; border-radius: 50%; object-fit: cover; flex: none; }
.app .fback .fb-nick { font-family: 'Merriweather', Georgia, serif; font-size: 20px; line-height: 1.2; color: var(--ink); }
.app .fback .body-muted { margin: 0; }
.app .fback .pill.fb-go { height: 50px; background: var(--bg); color: var(--ink); }
/* topic rows: a "seen · right" line under the title once a topic has answers (red when the topic is weak),
   and a third, grey bar step for questions seen but not yet answered right */
.app .topic.x3 { grid-template-rows: auto auto auto; }
.app .topic.x3 .ico, .app .topic.x3 .n { grid-row: 1 / span 3; }
.app .topic .tmeta { grid-column: 2; grid-row: 2; font: 400 13px 'Carlito', Arial, sans-serif; color: var(--muted); }
.app .topic .tmeta.bad-t { color: var(--danger); font-weight: 700; }
.app .topic.x3 .bar { grid-row: 3; }
.app .bar i.seen { background: var(--ink); opacity: .22; }
/* results: what the next sessions do with this one's answers */
.app .next-band { padding: 16px 18px; display: grid; gap: 12px; }
.app .next-band .r { display: grid; grid-template-columns: 28px minmax(0, 1fr); gap: 10px; align-items: start; }
.app .next-band .r > span { width: 28px; height: 28px; border-radius: 50%; background: var(--bg); color: var(--accent); display: flex; align-items: center; justify-content: center; }
/* profile: a value on the right of a settings row (current level) */
.app .set .val { font: 400 15px 'Carlito', Arial, sans-serif; color: var(--muted); display: inline-flex; align-items: center; gap: 2px; }
/* a weak result bar can be 0 px wide, so the count carries the red too */
.app .tbar.weak .n { color: var(--danger); }

/* accent as text: the bright fill blue is 3.5:1 on white, too low for 12–13 px labels; this one is 5.3:1.
   At night the fill blue already reads well on black. */
.app { --accent-ink: #0072ad; }
.app.theme-night { --accent-ink: var(--accent); }
.app .acc, .app .mini-accent, .app .pill:not(.pill-primary), .app .tgh button, .app .tgmain .b2, .app .tag-accent { color: var(--accent-ink); }
.app .mini-accent, .app .pill:not(.pill-primary):not(.pill-quiet) { border-color: var(--accent-ink); }
.app .pill-quiet { color: var(--ink); }

/* mastery ring with a second, lighter step: seen but not yet mastered */
.app .ring.seen { background: conic-gradient(var(--accent) calc(var(--v) * 1%), color-mix(in srgb, var(--accent) 30%, var(--tag-line)) 0 calc(var(--s) * 1%), var(--tag-line) 0); }

/* home for a returning student: where they left off, one tap to keep going */
.app .resume { padding: 16px 18px 18px; display: grid; gap: 12px; }
.app .resume .pill { height: 50px; }

/* keyboard focus */
.app [data-act]:focus-visible, .app button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.app .lang svg { margin-right: 5px; }
.app.theme-night .ring.seen { background: conic-gradient(var(--accent) calc(var(--v) * 1%), color-mix(in srgb, var(--accent) 55%, var(--tag-line)) 0 calc(var(--s) * 1%), var(--tag-line) 0); }

/* ---- audit fixes: contrast ---- */
/* day --muted was 4.4:1 on white and 4.1:1 on the band; this one is 5.2:1 / 4.8:1. Night stays as designed (10:1). */
.app:not(.theme-night) { --muted: #62706f; }
/* white on the bright accent is 3.5:1, so anything with text on a blue fill uses the darker blue (5.2:1);
   rings, bars and the "seen" arc keep the bright one. Night: --accent-ink is the night accent already. */
.app .pill-primary, .app .tgmain .b, .app .pass:not(.no) { background: var(--accent-ink); border-color: var(--accent-ink); }
.app .tgmain .b.off { background: var(--band); color: var(--muted); }
.app .tgmain .b2 { background: var(--band); }
.app .grid40 span.now { color: var(--accent-ink); border-color: var(--accent-ink); }
.app.theme-night .figtag { color: #1a0400; }

/* ---- audit fixes: tap targets ---- */
/* text links keep their size but get a 44 px hit area */
.app button.meta, .app .tgh button { position: relative; }
.app button.meta::after, .app .tgh button::after { content: ''; position: absolute; left: -8px; right: -8px; top: 50%; height: 44px; transform: translateY(-50%); }
.app .mini { height: 40px; }
.app .lang { height: 36px; }
.app .grid40 { grid-template-columns: repeat(6, minmax(0, 1fr)); }

/* ---- one shape for every button: the pill (cards keep their 20–28 px corners) ---- */
.app .pill, .app .tag, .app .mini, .app .lang, .app .langs, .app .toggle, .app .pass, .app .tgmain .b, .app .grid40 span { border-radius: 999px; }
.app .grid40 span.f::after { top: 5px; right: 5px; }

/* ---- audit fixes: 11 px labels → 12 px ---- */
.app .nav a, .app .stat .meta, .app .days span, .app .ring.l small { font-size: 12px; }

/* ---- audit fixes: layout ---- */
.app .lvl-btn { flex: 1; padding: 0 14px; font-size: 18px; }
@media (max-width: 359px) {
  .app .lvl-btn { font-size: 16px; gap: 6px; padding: 0 8px; }
  .app .ring.l { --sz: 120px; } .app .ring.l b { font-size: 30px; }
}
.app .tgmain.wrap { padding-left: 24px; padding-right: 24px; }
.app .sheet { overscroll-behavior: contain; }
@media (prefers-reduced-motion: reduce) {
  .app .sheet, .app .scrim, .app .toast, .app .scroll.in, .app .tgmain.in, .app .in *, .app .opt, .app .verdict, .app .why { animation: none !important; }
  .app .ring { transition: none; }
  .app .sk::after { animation: none; }
  .app .lvl, .app .topic, .app .opt, .app .tag, .app .mini, .app .pill, .app .grid40 span, .app .lang, .app .toggle, .app .tgmain .b { transition: none; }
}

/* admin: one row per student (a topic row with initials, the level bar and the mastery % on the right) */
.app .stu .ico { font: 700 13px 'Carlito', Arial, sans-serif; letter-spacing: -.02em; overflow: hidden; }
.app .stu .ico img { width: 100%; height: 100%; object-fit: cover; display: block; }
.app .stu .n { max-width: 96px; white-space: nowrap; }
.app .stu:not(.x3) .tmeta { grid-column: 2; grid-row: 2; font: 400 13px 'Carlito', Arial, sans-serif; color: var(--muted); }
.app .stu:not(.x3) .ico, .app .stu:not(.x3) .n { grid-row: 1 / span 2; }
.app .sheet .tbar .card-meta { margin-top: 2px; }
/* the two flags under a question: the plain one (come back to it) and the red one (this question is wrong) */
.app .qflags { display: flex; gap: 8px; min-width: 0; }
.app .mini.red svg { color: var(--danger); }
.app .mini.red.on { background: var(--danger); border-color: var(--danger); color: #fff; }
.app .mini.red.on svg { color: inherit; }

/* admin: the flagged and reported questions (a topic row with the question on two lines; the sheet shows the whole card and who marked it) */
.app .flq .ico { color: var(--muted); }
.app .flq.bad .ico { color: var(--danger); }
.app .flq .card-title { font-size: 15px; font-weight: 400; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.app .flq .n { max-width: 96px; white-space: nowrap; }
.app .flq-card { border: 0; padding: 0; }
.app .flq-card .ans.plain .l { box-shadow: inset 0 0 0 1.5px var(--line); }
.app .sheet .att.who { align-items: center; justify-content: flex-start; gap: 12px; padding: 8px 0; }
.app .sheet .att.who .card-title { flex: 1; font-weight: 400; }
.app .sheet .att.who .tg-ava { flex: none; }
.app .sheet .att.who svg { color: var(--muted); }

/* ---- motion: screens rise in, lists stagger, bars and rings fill, state changes answer the tap ---- */
.app { --ease-out: cubic-bezier(.23, 1, .32, 1); --ease-drawer: cubic-bezier(.32, .72, 0, 1); }
@keyframes rise { from { opacity: 0; transform: translateY(8px); } }
@keyframes fadeIn { from { opacity: 0; } }
@keyframes growX { from { transform: scaleX(0); } }
@keyframes growY { from { transform: scaleY(0); } }
@keyframes pop { 50% { transform: scale(.97); } }
@keyframes shake { 20%, 60% { transform: translateX(-3px); } 40%, 80% { transform: translateX(3px); } }
@keyframes down { to { transform: translateY(24px); opacity: 0; } }
/* a screen just navigated to (render(true)): the content rises; a tab switch only fades, it happens too often */
.app .scroll.in, .app .tgmain.in { animation: rise .22s var(--ease-out) both; }
.app .scroll.in-tab, .app .tgmain.in-tab { animation: fadeIn .16s ease both; }
.app .in:not(.in-tab) .lvl, .app .in:not(.in-tab) .topic, .app .in:not(.in-tab) .opt, .app .in:not(.in-tab) .stat, .app .in:not(.in-tab) .set,
.app .in:not(.in-tab) .att, .app .in:not(.in-tab) .tbar, .app .in:not(.in-tab) .step { animation: rise .26s var(--ease-out) both; animation-delay: calc(var(--i, 0) * 35ms); }
/* bars fill from the left and the week's columns grow up, after the row has landed */
.app .in .bar i { transform-origin: left; animation: growX .5s var(--ease-out) both; animation-delay: calc(var(--i, 0) * 35ms + 120ms); }
.app .in .days i { transform-origin: bottom; animation: growY .5s var(--ease-out) both; }
/* rings sweep up to their value (browsers without @property / @starting-style show them filled at once) */
@property --v { syntax: '<number>'; inherits: false; initial-value: 0; }
@property --s { syntax: '<number>'; inherits: false; initial-value: 0; }
.app .in .ring { transition: --v .6s var(--ease-out), --s .6s var(--ease-out); }
@starting-style { .app .in .ring { --v: 0; --s: 0; } }
/* practice: the picked option presses in, the wrong one shakes, the verdict and explanation rise */
.app .opt.sel { animation: pop .16s var(--ease-out); }
.app .opt.bad { animation: shake .3s var(--ease-out); }
.app .verdict { animation: rise .24s var(--ease-out) both; }
.app .why { animation: rise .24s var(--ease-out) both; animation-delay: 60ms; }
/* sheet: iOS drawer curve in, quick drop out; the scrim follows */
.app .sheet { animation: up .28s var(--ease-drawer); }
.app .sheet.out { animation: down .18s var(--ease-out) both; }
.app .scrim.out { animation: fadeIn .18s ease both reverse; }
/* toast: rises from below, fades away */
@keyframes toastIn { from { opacity: 0; transform: translate(-50%, 10px); } }
.app .toast { animation: toastIn .22s var(--ease-out); transition: opacity .18s ease, transform .18s ease; }
.app .toast.out { opacity: 0; transform: translate(-50%, 6px); }
/* toggles slide their knob (the screen is rebuilt only after the knob has moved) */
.app .toggle { transition: background-color .2s ease; }
.app .toggle::after { left: 3px; transition: transform .2s var(--ease-out); }
.app .toggle.on::after { left: 3px; transform: translateX(20px); }
/* press feedback a touch longer, with the same curve */
.app .lvl, .app .topic, .app .opt, .app .tag, .app .mini, .app .pill, .app .grid40 span, .app .lang, .app .toggle, .app .tgmain .b { transition-duration: .12s; transition-timing-function: var(--ease-out), ease; }

/* ---- skeletons: grey shapes in the layout of what is loading, with a slow sheen ---- */
.app .sk { display: block; flex: none; border-radius: 8px; background: var(--band); position: relative; overflow: hidden; }
.app .band .sk, .app .stat .sk { background: color-mix(in srgb, var(--ink) 7%, var(--band)); }
.app .sk::after { content: ''; position: absolute; inset: 0; transform: translateX(-100%); background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--ink) 7%, transparent), transparent); animation: sheen 1.6s linear infinite; }
@keyframes sheen { to { transform: translateX(100%); } }
.app .sk-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
.app .sk-wrap { display: contents; }
.app .boot-sk { pointer-events: none; }
.app .boot-sk .lvl { border-color: var(--line); }
/* a line of text: the bar stands for the letters, the margins pad it to the line box of the real style */
.app .sk-display { height: 34px; margin: 4px 0; }
.app .sk-line { height: 13px; margin: 4px 0; }
.app .sk-t { height: 16px; margin: 2px 0; }
.app .sk-m { height: 12px; margin: 2px 0; }
.app .sk-k { height: 13px; margin: 1.5px 0; }
.app .sk-ring { width: 52px; height: 52px; border-radius: 50%; }
.app .topic .ico.sk { border-radius: 50%; }
.app .topic .n .sk { margin-left: auto; }

/* ---- critique fixes (2026-11-03) ---- */
/* a heading for every screen (inside Telegram the page has no header of its own) */
.app .sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
/* the flag / report / skip row wraps instead of running off the screen (Russian labels, "Flagged") */
.app .qtools { flex-wrap: wrap; gap: 8px; }
.app .qflags { flex-wrap: wrap; }
/* tap targets: 44 px pills, 40 px segments, a 44 px hit area around the 30 px switch */
.app .mini { height: 44px; }
.app .lang { height: 40px; }
.app .toggle::before { content: ''; position: absolute; inset: -8px -4px; }
/* options ruled out after Check: muted by colour, not opacity (4.5:1 stays) */
.app .opt.dim { opacity: 1; color: var(--muted); border-color: var(--line); }
.app .opt.dim .l { color: var(--muted); border-color: var(--line); }
/* two-line metadata breathes */
.app .card-meta { line-height: 1.35; }
/* figures: nothing pokes out of the rounded box; shorter on short screens so option D stays in reach */
.app .fig { overflow: hidden; }
@media (max-height: 760px) { .app .fig { max-height: 190px; } }
/* the toast drops in from the top, clear of the flag / report row it talks about */
.app .toast { top: 12px; bottom: auto; }
@keyframes toastIn { from { opacity: 0; transform: translate(-50%, -10px); } }
.app .toast.out { transform: translate(-50%, -6px); }
/* a weak topic at 0 right still shows a red stub */
.app .tbar.weak .bar i { min-width: 6px; }
/* exam grid: the current cell is ringed, answered or not */
.app .grid40 span.now { box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--accent-ink); }
.app .grid40 span.a.now { color: var(--sel-ink); border-color: var(--sel-bg); }
/* the inert screen behind a sheet does not react */
.app [inert] { pointer-events: none; }

/* ---- motion, second pass (2026-11-03): one authored moment (the verdict), state that moves from where it was,
   cause → effect on the theme switch ---- */
:root { --ease-out: cubic-bezier(.23, 1, .32, 1); --ease-drawer: cubic-bezier(.32, .72, 0, 1); }

/* 1 · The theme switch: the new theme spreads from the tapped segment (view transitions; app.js render(…, true)). */
html[data-nav="theme"]::view-transition-image-pair(root) { isolation: auto; }
html[data-nav="theme"]::view-transition-old(root) { animation: none; mix-blend-mode: normal; }
html[data-nav="theme"]::view-transition-new(root) { animation: vt-theme .55s var(--ease-out) both; mix-blend-mode: normal; }
@keyframes vt-theme { from { clip-path: circle(0 at var(--tx, 50%) var(--ty, 50%)); } to { clip-path: circle(150% at var(--tx, 50%) var(--ty, 50%)); } }

/* 2 · The verdict (practice, Check). The picked option answers first: a check stamps into its letter, or the row shakes
   with a cross. If it was wrong, the right answer lights up a beat later, so the eye is led from the mistake to the
   answer. The other options step back, the explanation follows. The answered progress segment fills, the main button's
   new label rises in. */
@keyframes stamp { from { transform: scale(.3); opacity: 0; } 60% { transform: scale(1.2); opacity: 1; } }
@keyframes light { from { border-color: var(--tag-line); background: var(--bg); } }
@keyframes lightL { from { background: transparent; border-color: var(--tag-line); } }
@keyframes stepBack { from { color: var(--ink); border-color: var(--tag-line); } }
@keyframes fillX { from { transform: scaleX(0); } }
@keyframes wake { from { background: var(--band); color: var(--muted); } }
.app .opt.picked .l svg { animation: stamp .3s var(--ease-out) both; }
.app .opt.ok.picked { animation: light .3s var(--ease-out) both; }
.app .opt.ok:not(.picked) { animation: light .3s var(--ease-out) .3s both; }
.app .opt.ok:not(.picked) .l { animation: lightL .3s var(--ease-out) .3s both; }
.app .opt.ok:not(.picked) .l svg { animation: stamp .3s var(--ease-out) .3s both; }
.app .opt.dim { animation: stepBack .25s ease both; }
.app .why { animation-delay: .12s; }
.app .verdict.bad ~ .why { animation-delay: .42s; }
.app .progress span.just { transform-origin: left; animation: fillX .35s var(--ease-out) .1s both; }
.app .tgmain .b .t.swap { animation: rise .2s var(--ease-out) both; }
.app .tgmain .b.wake { animation: wake .25s ease both; }

/* 3 · Bars and rings move from the value they last showed (app.js, PREV), not from zero, and keep still when nothing
   changed; only a first view grows from zero. The topic bars are driven by app.js; the ring's starting style reads the
   previous value. */
.app .in .bar[data-k] i { animation: none; }
@starting-style { .app .in .ring { --v: var(--v0, 0); --s: var(--s0, 0); } }

/* 4 · Small acknowledgements: the streak pill on Home after the first answer of the day; the exam timer when the last
   five minutes begin; the sheet's scrim softens the screen behind it. */
@keyframes flare { 30% { transform: scale(1.3) rotate(-10deg); } 60% { transform: scale(1.1) rotate(8deg); } }
.app .streak.grew { animation: rise .3s var(--ease-out) .2s both; }
.app .streak.grew .fire { animation: flare .6s var(--ease-out) .45s both; }
@keyframes beat { 40% { transform: scale(1.2); } }
.app .timer.beat { animation: beat .5s var(--ease-out); }
.app .scrim { backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px); }

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
  .app .opt.picked .l svg, .app .opt.ok, .app .opt.ok .l, .app .opt.ok .l svg, .app .opt.dim, .app .progress span.just,
  .app .tgmain .b .t.swap, .app .tgmain .b.wake, .app .streak.grew, .app .streak.grew .fire, .app .timer.beat { animation: none; }
}

/* UI pass 04.11 */
/* the night-lights picture fills its card edge to edge */
.app .fig.night { background: #07101a; }
/* narrow phones (320–360 px): wide pills and the bottom buttons shrink their text instead of cutting it */
.app .pill.pill-wide { font-size: clamp(14px, 4.6vw, 18px); }
.app .tgmain .b { font-size: clamp(14px, 4.4vw, 17px); }
.app .tgmain .row2 .b { padding: 0 10px; gap: 6px; }
/* the label's letters (not its line box) and the icon sit on the button's centre line */
.app .tgmain .b .t { line-height: 1; text-box: trim-both cap alphabetic; padding: .3em 0; margin: -.3em 0; } /* the padding keeps descenders out of the ellipsis clip */
.app .tgmain .b > [data-i] { display: flex; }

/* C4 Admin: the four pages share a bottom menu like the app's; the question lists carry a count */
.app .anav .ai { position: relative; display: flex; }
.app .anav .badge { position: absolute; top: -6px; left: calc(100% - 8px); min-width: 16px; height: 16px; padding: 0 4px; box-sizing: border-box; border-radius: 8px; background: var(--ink); color: var(--bg); font: 700 10px/16px 'Carlito', Arial, sans-serif; text-align: center; letter-spacing: 0; }
.app .anav .badge.bad { background: var(--danger); color: #fff; }
.app .anav a { white-space: nowrap; }
/* Overview charts: no background, a hairline above each; a bar per hour, day or week on a baseline, rounded at the top,
   the highest one on a dashed line. Each has a small segmented switch for its range. */
.app .dchart { display: flex; flex-direction: column; gap: 8px; padding-top: 14px; border-top: 1px solid var(--line); }
.app .dhead { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.app .dhead .stack { gap: 2px; min-width: 0; }
.app .dread { font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.app .dread b { color: var(--ink); }
.app .dsw { flex: none; }
.app .dsw .lang { height: 28px; padding: 0 8px; font-size: 12px; letter-spacing: .02em; }
.app .dplot { position: relative; display: grid; grid-template-columns: repeat(var(--n), minmax(0, 1fr)); gap: 2px; align-items: end; height: 64px; padding-top: 14px; border-bottom: 1px solid var(--tag-line); }
.app .dplot::before { content: ''; position: absolute; left: 0; right: 0; top: 14px; border-top: 1px dashed var(--tag-line); }
.app .dplot .dmax { position: absolute; top: 0; left: 0; font: 400 11px/12px 'Carlito', Arial, sans-serif; color: var(--muted); font-variant-numeric: tabular-nums; }
.app .dplot > div { height: 100%; display: flex; align-items: flex-end; justify-content: center; cursor: default; }
.app .dplot i { display: block; width: 100%; max-width: 32px; background: var(--accent); border-radius: 3px 3px 0 0; transition: opacity .15s ease; }
.app .dchart.bad .dplot i { background: var(--danger); }
.app .dplot:has(> .on) > div:not(.on) i { opacity: .35; }
.app .dx { margin-top: -2px; }
.app .dx span { font: 700 11px 'Carlito', Arial, sans-serif; text-transform: uppercase; color: var(--muted); }
/* Learning now: a row per course, its name, a bar on a faint track and the number of students */
.app .dbars { display: flex; flex-direction: column; gap: 8px; }
.app .dbar { display: grid; grid-template-columns: minmax(0, 10.5em) 1fr 2.5em; align-items: center; gap: 10px; }
.app .dbar .card-meta { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.app .dtrack { height: 10px; border-radius: 5px; background: var(--line); overflow: hidden; }
.app .dtrack i { display: block; height: 100%; background: var(--accent); border-radius: 5px; }
.app .dbar b { text-align: right; color: var(--ink); font: 700 14px/1 'Carlito', Arial, sans-serif; font-variant-numeric: tabular-nums; }
.app .in .dtrack i, .app .dchart.grow .dtrack i { transform-origin: left; animation: growX .45s var(--ease-out) both; }
@media (prefers-reduced-motion: reduce) { .app .in .dtrack i, .app .dchart.grow .dtrack i { animation: none; } }
.app .in .dplot i, .app .dchart.grow .dplot i { transform-origin: bottom; animation: growY .45s var(--ease-out) both; }
@media (prefers-reduced-motion: reduce) { .app .in .dplot i, .app .dchart.grow .dplot i { animation: none; } }
/* C4 a flagged question's sheet: each student opens their results; Remove takes the flag off */
.app .sheet .att.who .whob { flex: 1; min-width: 0; display: flex; align-items: center; gap: 12px; }
.app .sheet .att.who .whob .card-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.app .sheet .att.who .mini { flex: none; height: 36px; padding: 0 14px; font-size: 14px; }
/* C1b the student's own reports: the whole question, and a button to withdraw it */
.app .flq.mine .card-title { display: block; -webkit-line-clamp: unset; overflow: visible; }
.app .flq.mine .n { max-width: none; }
.app .flq.mine:active { transform: none; }

/* ---------- theory: the tab's list of topics and a topic's article (docs/theory.md) ---------- */
/* a row of chips that scrolls sideways to the screen's edges: the levels on the tab, the sections in an article */
.app .th-toc { display: flex; gap: 8px; overflow-x: auto; margin: 0 -24px; padding: 0 24px; scrollbar-width: none; position: relative; }
.app .th-toc::-webkit-scrollbar { display: none; }
.app .th-toc .chip { flex: none; cursor: pointer; }
.app .th-row .n { display: flex; align-items: center; color: var(--muted); }
.app .th-row.off { opacity: .5; }
.app .th-read { font: 700 12px 'Carlito', Arial, sans-serif; text-transform: uppercase; letter-spacing: .02em; color: var(--accent); }
.app .th-sec { display: flex; flex-direction: column; gap: 12px; padding-top: 6px; }
.app .th-h { font: 300 25px/1.1 'Merriweather', Georgia, serif; letter-spacing: -.02em; margin: 0; color: var(--ink); text-wrap: balance; }
.app .th-p { font: 400 15.5px/1.55 'Merriweather', Georgia, serif; margin: 0; color: var(--ink); }
.app .th-lead { color: var(--muted); }
.app .th-p b, .app .th-list b, .app .th-rule b, .app .th-table b { font-weight: 700; }
.app .th-list { margin: 0; padding: 0 0 0 20px; display: flex; flex-direction: column; gap: 6px; font: 400 15.5px/1.5 'Merriweather', Georgia, serif; color: var(--ink); }
.app .th-cap { font: 400 13px/1.3 'Carlito', Arial, sans-serif; color: var(--muted); text-align: center; margin: -4px 0 0; }
.app .th-rule { padding: 15px 18px; display: flex; flex-direction: column; gap: 5px; }
.app .th-rule .kicker { color: var(--accent); }
/* a section's opener: the question, then its short answer, set off by the accent line */
.app .th-qa { display: flex; flex-direction: column; gap: 4px; padding: 2px 0 2px 14px; border-left: 3px solid var(--accent); }
.app .th-q { font: 700 16px/1.3 'Carlito', Arial, sans-serif; margin: 0; color: var(--ink); }
/* small figures side by side, each with its name: things to tell apart */
.app .th-tiles { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px 8px; }
.app .th-tiles.c2 { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 10px; }
.app .th-tiles.c3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.app .th-tile { display: flex; flex-direction: column; gap: 5px; text-align: center; min-width: 0; }
.app .th-tile .fig { aspect-ratio: auto; min-height: 0; max-height: none; height: 104px; padding: 8px; border-radius: 18px; }
.app .th-tiles.c2 .fig { height: 132px; }
.app .th-tile b { font: 700 13px/1.15 'Carlito', Arial, sans-serif; color: var(--ink); overflow-wrap: anywhere; }
.app .th-tile span { font: 400 12px/1.2 'Carlito', Arial, sans-serif; color: var(--muted); }
.app .th-table { width: 100%; border-collapse: collapse; font: 400 14px/1.3 'Carlito', Arial, sans-serif; color: var(--ink); }
.app .th-table th { text-align: left; font: 700 12px 'Carlito', Arial, sans-serif; text-transform: uppercase; letter-spacing: .02em; color: var(--muted); padding: 0 12px 8px 0; }
.app .th-table td { padding: 9px 12px 9px 0; border-top: 1px solid var(--line); vertical-align: top; hyphens: auto; -webkit-hyphens: auto; } /* a long word in a narrow column breaks with a hyphen, in the page's language */
.app .th-table td:first-child { font-weight: 700; }
.app .th-table th:last-child, .app .th-table td:last-child { padding-right: 0; }
.app .th-nextprev { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.app .th-near { padding: 14px 16px; display: flex; flex-direction: column; gap: 4px; min-width: 0; cursor: pointer; }
.app .th-near:last-child { text-align: right; }
.app .th-near:active { transform: scale(.98); }
.app .th-end { display: block; height: 1px; margin-top: -22px; }
.app .ref-go { cursor: pointer; color: var(--accent); gap: 6px; }

/* a section's flag (report a mistake) and the comment box of its sheet */
.app .th-flag { flex: none; width: 40px; height: 40px; margin: -10px -10px -10px 0; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: var(--muted); cursor: pointer; }
.app .th-flag:active { transform: scale(.92); }
.app .th-flag.on { color: var(--danger); }
.app .th-flag.on svg { fill: currentColor; }
.app .th-foot { display: flex; align-items: center; gap: 8px; }
.app .th-foot svg { flex: none; }
.app .th-note { width: 100%; box-sizing: border-box; min-height: 124px; padding: 14px 16px; border-radius: 20px; border: 2px solid var(--tag-line); background: var(--bg); color: var(--ink); font: 400 16px/1.45 'Merriweather', Georgia, serif; resize: none; outline: none; display: block; }
.app .th-note:focus { border-color: var(--ink); }
.app .th-note::placeholder { color: var(--faint); }
.app .pill.off { opacity: .4; pointer-events: none; }
/* "Where to read" on a result: the rows of the "What next" band, as links */
.app .read-band .r { grid-template-columns: 28px minmax(0, 1fr) 16px; align-items: center; text-align: left; cursor: pointer; }
.app .read-band .r > svg { color: var(--muted); }
.app .read-band .r:active { transform: scale(.98); }
/* admins: what students wrote about the articles */
.app .anotes { display: flex; flex-direction: column; border-top: 1px solid var(--line); }
.app .anote { display: flex; flex-direction: column; gap: 4px; padding: 13px 0; border-bottom: 1px solid var(--line); text-align: left; cursor: pointer; }
.app .anote .body-s { overflow-wrap: anywhere; }
.app .anote .card-title { overflow-wrap: anywhere; }
/* the section's opening lines, as the student read them, set off by a rule like a quotation; then what they wrote */
.app .anote .anq { font-size: 13px; line-height: 1.45; padding-left: 10px; border-left: 2px solid var(--line); overflow-wrap: anywhere;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; line-clamp: 3; overflow: hidden; }
.app .anote .ant { margin-top: 4px; }
