/* Lesson engine styles. Lunar tokens from lib/theme.dart. Single-theme on
   purpose: a scene at night, painted explicitly so it holds on any host. Fonts
   are subset woff2 files next door; the app host inlines them. */
@font-face { font-family: "DM Sans"; font-weight: 400; font-style: normal; font-display: block; src: url(../fonts/dmsans-400.woff2) format("woff2"); }
@font-face { font-family: "DM Sans"; font-weight: 600; font-style: normal; font-display: block; src: url(../fonts/dmsans-600.woff2) format("woff2"); }
@font-face { font-family: "DM Sans"; font-weight: 800; font-style: normal; font-display: block; src: url(../fonts/dmsans-800.woff2) format("woff2"); }
@font-face { font-family: "DM Sans"; font-weight: 900; font-style: normal; font-display: block; src: url(../fonts/dmsans-900.woff2) format("woff2"); }
@font-face { font-family: "Source Serif 4"; font-weight: 500; font-style: normal; font-display: block; src: url(../fonts/sourceserif-500.woff2) format("woff2"); }
@font-face { font-family: "Source Serif 4"; font-weight: 700; font-style: normal; font-display: block; src: url(../fonts/sourceserif-700.woff2) format("woff2"); }
@font-face { font-family: "IBM Plex Mono"; font-weight: 600; font-style: normal; font-display: block; src: url(../fonts/plexmono-600.woff2) format("woff2"); }
:root {
  --bg: #0A0E1A;
  --surface: #121829;
  --raised: #1A2136;
  --line: rgba(234, 227, 215, .14);
  --line-strong: rgba(234, 227, 215, .28);
  --text: #EAE3D7;
  --dim: #A69F93;
  --faint: #6E6960;
  --violet: #7B5CFD;
  --violet-bright: #A99BFF;
  --violet-deep: #5B45C8;
  --gold: #FCC050;
  --gold-deep: #C98C1B;
  --mint: #3FD69A;
  --mint-ink: #0B2E20;
  --red: #D85555;
  --red-ink: #2E0B0B;
  --streak: #FB9679;
  --serif: "Source Serif 4", Georgia, "Times New Roman", serif;
  --sans: "DM Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, Menlo, Consolas, monospace;
}
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; touch-action: manipulation; }
html, body { height: 100%; }
body {
  margin: 0; background: #05070E; color: var(--text); font-family: var(--sans);
  font-size: 16px; line-height: 1.4; -webkit-font-smoothing: antialiased;
  overflow: hidden;
}
button { font: inherit; color: inherit; border: 0; background: none; cursor: pointer; }
button:focus-visible, [tabindex]:focus-visible { outline: 2px solid var(--violet-bright); outline-offset: 2px; }

/* ---------- phone frame ---------- */
#frame {
  position: fixed; inset: 0; margin: auto; width: 100%; height: 100%;
  background: var(--bg); overflow: hidden; display: flex; flex-direction: column;
}
@media (min-width: 520px) and (min-height: 700px) {
  #frame { width: 400px; height: min(860px, 96vh); border-radius: 44px; border: 8px solid #1c1f2b; box-shadow: 0 30px 90px rgba(0,0,0,.6); }
}

/* ---------- top bar ---------- */
#top {
  display: flex; align-items: center; gap: 12px; padding: 14px 16px 6px; flex: none;
}
#close { width: 44px; height: 44px; margin-left: -12px; display: grid; place-items: center; color: var(--dim); font-size: 22px; }
/* Look back: hit areas abut the close button's and the bar, and the chevron
   keeps its slot while hidden, like the combo pill, so the bar never jumps. */
#back, #fwd { flex: none; width: 44px; height: 44px; margin: 0 -12px; display: grid; place-items: center; color: var(--dim); }
#fwd { display: none; margin-left: 0; } #top.lk #fwd { display: grid; }
#back svg, #fwd svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
#back { visibility: hidden; } #back.on { visibility: visible; } #back[disabled] { opacity: .35; }
#track { flex: 1; position: relative; }
#bar { height: 18px; border-radius: 999px; background: var(--raised); overflow: hidden; position: relative; }
/* The fill springs past its mark and settles. It never reads empty: the first
   beat already shows a nub. */
#menu { width: 44px; height: 44px; margin-right: -12px; display: grid; place-items: center; color: var(--dim); }
#menu svg { width: 20px; height: 20px; fill: currentColor; }
#menu[hidden] { display: none; }
#bar > i {
  display: block; position: relative; height: 100%; width: 0; min-width: 20px; border-radius: 999px;
  background: linear-gradient(90deg, var(--violet-deep), var(--violet), var(--violet-bright));
  transition: width .7s cubic-bezier(.34,1.5,.5,1), filter .3s;
}
#bar > i::after { content: ""; position: absolute; top: 3px; left: 8px; right: 8px; height: 4px; border-radius: 4px; background: rgba(255,255,255,.3); }
/* A right answer moves the bar and a gloss runs along the new fill. */
#bar > i::before { content: ""; position: absolute; inset: 0; border-radius: inherit; background: linear-gradient(100deg, transparent 30%, rgba(255,255,255,.55) 50%, transparent 70%) no-repeat; background-size: 60% 100%; background-position: -120% 0; }
#bar.grow > i::before { animation: barGloss .8s .15s ease-out; }
@keyframes barGloss { to { background-position: 220% 0; } }
/* The run's gold is a layer over the violet, so a broken run drains back
   rather than snapping; the gloss stays on top of it. */
#bar > i > b { position: absolute; inset: 0; border-radius: inherit; background: linear-gradient(90deg, var(--gold-deep), var(--gold), #FFE7A3); opacity: 0; transition: opacity .7s ease-in; }
#bar.hot > i > b { opacity: 1; transition: opacity .25s ease-out; }
#bar > i::before { z-index: 1; }
/* The run's heat: a warm glow round the bar at 5 in a row, white-hot at 10.
   Static, so a hot bar at rest costs nothing; it fades with the gold. */
#bar { transition: box-shadow .6s; }
#bar.h5 { box-shadow: 0 0 12px rgba(252,192,80,.32); }
#bar.h10 { box-shadow: 0 0 18px rgba(252,192,80,.5), 0 0 0 1px rgba(255,231,163,.5); }
#bar.h10 > i > b { background: linear-gradient(90deg, var(--gold), #FFE7A3 60%, #FFFBEA); }
/* The bar's current (engine.js, Current): a canvas round the bar, shown only while it draws. */
#track > .current { position: absolute; pointer-events: none; z-index: 2; visibility: hidden; }
#track > .current.on { visibility: visible; }
#bar.flare > i { animation: barFlare .7s ease-out; }
@keyframes barFlare { 25% { filter: brightness(1.7) drop-shadow(0 0 8px var(--gold)); } }
/* The combo is a label over the bar, absent until three right in a row, and
   gold. Never the streak's orange or a flame: those are the app's daily
   streak. Absolute, so the bar never moves when it comes and goes. */
#combo {
  position: absolute; left: 4px; bottom: calc(100% + 3px); font-size: 11.5px; font-weight: 900; letter-spacing: .1em; text-transform: uppercase;
  color: var(--gold); white-space: nowrap; font-variant-numeric: tabular-nums; text-shadow: 0 0 10px rgba(252,192,80,.55);
  opacity: 0; translate: 0 6px; transition: opacity .25s, translate .35s cubic-bezier(.34,1.56,.64,1);
}
#combo b { font-weight: 900; }
#combo.on { opacity: 1; translate: 0 0; }
#combo.pop { animation: comboPop .45s cubic-bezier(.34,1.56,.64,1); }
@keyframes comboPop { 35% { scale: 1.35; } }
.stat { display: inline-flex; align-items: center; gap: 5px; font-weight: 700; font-size: 14px; font-variant-numeric: tabular-nums; padding: 5px 9px; border-radius: 999px; }
.stat.pop { transform: scale(1.18); }

/* ---------- effects layer: the combo moment ---------- */
.fx { position: absolute; inset: 0; z-index: 20; pointer-events: none; overflow: hidden; }
.fx > svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.fx .bolt { fill: none; stroke-linejoin: round; stroke-linecap: round; stroke-dasharray: 1; stroke-dashoffset: 1; animation: boltDraw .16s ease-out forwards, boltFlicker .7s linear forwards; }
.fx .bolt.core { stroke: #FFFBEA; stroke-width: 4.5; }
.fx .bolt.glow { stroke: var(--gold); stroke-width: 16; opacity: .7; filter: blur(4px); }
.fx .bolt.fork.core { stroke-width: 2.2; } .fx .bolt.fork.glow { stroke-width: 8; } .fx .bolt.fork { animation-delay: .06s, .06s; }
.fx .bolt.run { stroke-width: 2.4; }
.fx .bolt.run.glow { stroke-width: 7; }
@keyframes boltDraw { to { stroke-dashoffset: 0; } }
@keyframes boltFlicker { 0%, 22% { opacity: 1; } 30% { opacity: .25; } 40% { opacity: 1; } 52% { opacity: .5; } 60% { opacity: 1; } 100% { opacity: 0; } }
.fx .flash { position: absolute; inset: 0; background: radial-gradient(circle at var(--x) var(--y), rgba(255,243,196,.5), rgba(252,192,80,.18) 35%, transparent 70%); animation: fxFlash .45s ease-out forwards; }
@keyframes fxFlash { 0% { opacity: 0; } 20% { opacity: 1; } 100% { opacity: 0; } }
.fx .ring { position: absolute; width: 120px; height: 120px; margin: -60px 0 0 -60px; border-radius: 50%; border: 3px solid var(--gold); box-shadow: 0 0 16px rgba(252,192,80,.7); animation: fxRing .6s cubic-bezier(.2,.8,.2,1) forwards; }
@keyframes fxRing { from { scale: .3; opacity: 1; } to { scale: 2.4; opacity: 0; } }
.fx .spark { position: absolute; width: 7px; height: 7px; margin: -3.5px 0 0 -3.5px; border-radius: 50%; background: #FFE7A3; box-shadow: 0 0 8px var(--gold); animation: fxSpark .6s cubic-bezier(.2,.8,.2,1) forwards; }
@keyframes fxSpark { to { translate: var(--dx) var(--dy); scale: .2; opacity: 0; } }
/* The reward itself: the XP it pays, big, then it flies back into the bar. */
.fx .badge { position: absolute; display: flex; flex-direction: column; align-items: center; gap: 8px; translate: -50% -50%; animation: badgeIn .5s cubic-bezier(.34,1.56,.64,1) backwards; transition: left .42s cubic-bezier(.55,0,.8,.4), top .42s cubic-bezier(.55,0,.8,.4), scale .42s cubic-bezier(.55,0,.8,.4), opacity .2s .3s; }
.fx .badge .pill {
  display: flex; align-items: center; gap: 10px; padding: 12px 26px 12px 14px; border-radius: 28px;
  background: linear-gradient(180deg, #FFE7A3, var(--gold) 55%); border: 4px solid #FFF6D8; box-shadow: 0 7px 0 var(--gold-deep), 0 0 40px rgba(252,192,80,.65);
  font: 900 58px/1 var(--sans); color: #2A1B00; font-variant-numeric: tabular-nums;
}
.fx .badge .pill svg { flex: none; width: 58px; height: 58px; color: var(--gold-deep); }
.fx .badge::before { content: ""; position: absolute; left: 50%; top: 42%; width: 340px; height: 260px; translate: -50% -50%; z-index: -1; border-radius: 50%; background: radial-gradient(closest-side, rgba(5,7,14,.82), rgba(5,7,14,.5) 55%, transparent); }
.fx .badge .cap { font-size: 15px; font-weight: 900; letter-spacing: .14em; text-transform: uppercase; color: var(--gold); text-shadow: 0 0 12px rgba(252,192,80,.7); }
.fx .badge.go { scale: .2; opacity: 0; }
.fx .badge.go::before { opacity: 0; transition: opacity .15s; }
@keyframes badgeIn { 0% { scale: .2; rotate: -10deg; opacity: 0; } 60% { scale: 1.15; rotate: 3deg; opacity: 1; } 100% { scale: 1; rotate: 0deg; } }

/* ---------- stage ---------- */
#stage { flex: 1; min-height: 0; display: flex; flex-direction: column; padding: 4px 18px 0; overflow: hidden; position: relative; }
#eyebrow, #look .lk-eb { font-size: 12px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: var(--violet-bright); margin: 6px 0 4px; min-height: 16px; }
#copy, #look .lk-copy {
  font-family: var(--sans); font-size: 20px; line-height: 1.36; font-weight: 600; letter-spacing: -0.01em;
  text-wrap: pretty; margin: 0 0 10px; min-height: 56px;
}
#copy.small, #look .lk-copy.small { font-size: 18.5px; }
#copy strong, #look .lk-copy strong { font-weight: 800; color: #fff; }
/* The copy is the game's voice, in the app's sans. The serif is kept for the
   concept itself: its name on the first screen, and the card. */
#stage:has(.hero.orient) #copy strong { font-family: var(--serif); font-weight: 700; font-size: 1.12em; }
#copy .g, #look .lk-copy .g { color: var(--gold); font-weight: 600; }
#copy.in, #look .lk-copy.in { animation: copyIn .32s cubic-bezier(.2,.8,.2,1) both; }
@keyframes copyIn { from { opacity: 0; transform: translateY(6px); } }
/* The Duolingo layout: the copy is pinned at the top so the question sits
   at the same height on every beat, the scene sits directly under it, and
   the controls sit at the bottom; spare height falls between the scene and
   the controls. Nothing moves when a feedback sheet rises: it covers the
   controls, and on a tall phone rarely reaches the scene. The controls area
   reserves one height (three options' worth) whether the beat shows one
   button or three, so tap targets stay put. When the screen is too short
   the reserve gives way first (its flex-shrink is high), and a scene that
   still does not fit clips at its bottom rather than painting over the
   copy; play.mjs fails a lesson that gets there. */
#scene, #look .lk-scene { flex: 0 1 auto; min-height: 0; position: relative; display: flex; flex-direction: column; padding-top: 10px; overflow: hidden; }
/* Room below the scene for a hero image's drop-shadow glow, which overflow
   would otherwise cut in a hard line; the negative margin keeps layout as it
   was, and the controls sit above the overlap so they still take the taps.
   engine.js subtracts it wherever it measures the scene. The back arrow's
   copy of a screen gets the same room, or its glows are cut where the live
   one's weren't (cold-reading-v2's thumb pad). */
#scene, #look .lk-scene { padding-bottom: 44px; margin-bottom: -44px; }
#controls, #look .lk-controls { position: relative; z-index: 1; }
#controls { transition: opacity .2s; }
#controls.held { opacity: 0; pointer-events: none; }
#controls, #look .lk-controls { flex: 0 20 184px; margin-top: auto; display: flex; flex-direction: column; justify-content: flex-end; gap: 10px; padding: 12px 0 calc(16px + env(safe-area-inset-bottom)); }
/* A screen whose scene is the control (tap the word, tap the spot) has
   nothing in the controls, so the scene drops into the free space toward the
   thumb. engine.js sets the offset once the screen opens and eases it when
   the scene grows. */
#scene { transition: translate .4s cubic-bezier(.2,.8,.2,1); }
/* The bold question gets its own line in the app's sans, as an instruction,
   while the setup before it stays in the serif. */
#copy .ask { display: block; margin-top: 8px; font-family: var(--sans); font-weight: 800; font-size: 21px; line-height: 1.25; letter-spacing: -.01em; color: #fff; }
#copy .ask.solo { margin-top: 0; }
#copy.small .ask { font-size: 19px; margin-top: 6px; }
/* DM Sans sets wider than the serif it replaced; the small phone takes it a
   size down so prompts written for the serif keep their line count. */
@media (max-height: 700px) { #copy, #look .lk-copy { font-size: 19px; } #copy.small, #look .lk-copy.small { font-size: 17.5px; } #copy .ask { font-size: 20px; } #copy.small .ask { font-size: 18.5px; } }
/* The last give on a screen that doesn't fit (engine.js fit()). */
#stage.tight #copy { line-height: 1.28; margin-bottom: 6px; }
#copy.small .ask.solo { margin-top: 0; }
/* The eyebrow of a screen that asks something carries its kind's glyph. */
#eyebrow { display: flex; align-items: center; gap: 8px; }
/* A guess's tag, so its scene isn't read as the answer. */
#eyebrow .guess { color: var(--gold); white-space: nowrap; }
#eyebrow .guess:not(:first-child)::before { content: '\00B7'; margin-right: 8px; color: var(--violet-bright); }
#eyebrow .ico { flex: none; width: 22px; height: 22px; margin: -3px 0; border-radius: 7px; display: grid; place-items: center; background: linear-gradient(145deg, var(--violet-bright), var(--violet)); box-shadow: 0 2px 0 var(--violet-deep); animation: icoIn .4s cubic-bezier(.34,1.56,.64,1) backwards; }
#eyebrow .ico svg { width: 14px; height: 14px; fill: none; stroke: #fff; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
@keyframes icoIn { from { scale: .3; opacity: 0; } }
/* Duolingo's header has air: a larger eyebrow with a clear gap under it, a
   larger question, and room before the scene. The small phone has none to
   spare and keeps the tight set. */
@media (min-height: 701px) {
  #top { padding-top: 20px; }
  #eyebrow { font-size: 13.5px; margin: 10px 0 14px; }
  #eyebrow .ico { width: 26px; height: 26px; margin: -5px 0; border-radius: 8px; }
  #eyebrow .ico svg { width: 16px; height: 16px; }
  #copy, #look .lk-copy { font-size: 22px; line-height: 1.38; letter-spacing: -.012em; }
  #copy.small, #look .lk-copy.small { font-size: 20px; }
  #copy .ask { font-size: 24px; margin-top: 14px; }
  #copy.small .ask { font-size: 21px; margin-top: 10px; }
  #scene { padding-top: 24px; }
  /* A screen that can't spare the air gets the tight set back. */
  #stage.tight #eyebrow { margin: 6px 0 4px; }
  #stage.tight #scene { padding-top: 10px; }
}
/* Entrances. Options, chips and bins rise in one after another; a new
   screen's scene rises with them. :where keeps these at zero specificity, so
   any animation a lesson gives its own elements wins. */
@keyframes rise { from { opacity: 0; translate: 0 14px; } }
:where(.choices, .pairs) > .choice, :where(.bank) > .word, :where(.sortrow) > .btn, :where(#controls) > .btn, :where(#controls) > .slider {
  animation: rise .34s cubic-bezier(.2,.8,.2,1) calc(var(--i, 0) * 45ms) backwards;
}
:where(.choices, .pairs, .bank, .sortrow) > :nth-child(2) { --i: 1; } :where(.choices, .pairs, .bank, .sortrow) > :nth-child(3) { --i: 2; }
:where(.choices, .pairs, .bank, .sortrow) > :nth-child(4) { --i: 3; } :where(.choices, .pairs, .bank, .sortrow) > :nth-child(5) { --i: 4; }
:where(.choices, .pairs, .bank, .sortrow) > :nth-child(6) { --i: 5; } :where(.choices, .pairs, .bank, .sortrow) > :nth-child(n+7) { --i: 6; }
:where(#scene[data-enter]:not(.takeaway):not(.ending)) > * { animation: rise .4s cubic-bezier(.2,.8,.2,1) backwards; }
/* A screen the reader left, laid over the live stage (which keeps running
   under it) with the stage's own layout. Its scene is frozen: animations
   jump to where they end rather than replaying on every visit. */
#look { position: absolute; inset: 0; z-index: 7; display: flex; flex-direction: column; padding: 4px 18px 0; background: var(--bg); }
#look .lk-scene { pointer-events: none; }
/* The live clip lent to a screen looked back at plays there. */
#look .clip-slot > .clip:not(.copy) { pointer-events: auto; }
#look .lk-scene *, #look .lk-scene *::before, #look .lk-scene *::after { animation-duration: 0s !important; animation-delay: 0s !important; animation-iteration-count: 1 !important; transition: none !important; }

/* ---------- buttons ---------- */
/* The app's lit EdgiSlab (lib/widgets/ui/edgi_button.dart): gel lit from
   inside, a soft streak across the top third, light pooled low in the face, a
   glass rim, and a halo in the face's colour that grows when pressed. The
   layers are white over background-color, so a variant only sets its colours.
   No pseudo-elements: lessons use .btn::before and ::after themselves. */
.btn {
  --ledge: var(--violet-deep); --halo: 136, 108, 253;
  --rim: inset 0 1px 0 rgba(255,255,255,.44), inset 0 -1px 0 rgba(255,255,255,.12);
  display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; min-height: 52px;
  border-radius: 999px; font-weight: 900; font-size: 16px; letter-spacing: .04em; text-transform: uppercase;
  background-color: var(--violet); color: #fff;
  background-image:
    radial-gradient(52% 120% at 50% 0%, rgba(255,255,255,.26), rgba(255,255,255,.12) 50%, rgba(255,255,255,0)),
    radial-gradient(42% 55% at 50% 105%, rgba(255,255,255,.14), rgba(255,255,255,0)),
    linear-gradient(rgba(255,255,255,.14), rgba(0,0,0,.05) 55%, rgba(255,255,255,.04));
  background-size: calc(100% - 36px) 32%, 100% 100%, 100% 100%;
  background-position: 50% 3px, 0 0, 0 0;
  background-repeat: no-repeat;
  box-shadow: var(--rim), 0 5px 0 var(--ledge), 0 2.5px 14px 1px rgba(var(--halo), .22);
  transition: transform .08s, box-shadow .08s, opacity .2s;
}
.btn:active {
  transform: translateY(4px); box-shadow: var(--rim), 0 1px 0 var(--ledge), 0 .5px 22px 2.5px rgba(var(--halo), .4);
  background-image:
    radial-gradient(52% 120% at 50% 0%, rgba(255,255,255,.26), rgba(255,255,255,.12) 50%, rgba(255,255,255,0)),
    radial-gradient(46% 60% at 50% 105%, rgba(255,255,255,.22), rgba(255,255,255,0)),
    linear-gradient(rgba(255,255,255,.14), rgba(0,0,0,.05) 55%, rgba(255,255,255,.04));
}
.btn .unit { text-transform: none; }
/* Outline: the ledge is the border colour; an outer shadow never paints under the face. */
.btn.ghost { background: transparent; color: var(--text); border: 2px solid var(--line-strong); box-shadow: 0 5px 0 var(--line-strong); }
.btn.ghost:active { background: transparent; box-shadow: 0 1px 0 var(--line-strong); }
.btn.gold { background-color: var(--gold); color: #2A1B00; --ledge: var(--gold-deep); --halo: 252, 198, 98; }
.btn.red { background-color: var(--red); color: #fff; --ledge: #8f2f2f; --halo: 220, 102, 102; }
/* An empty slot, sunk: a whisper of fill and a hairline, not a dark live face. */
.btn[disabled] { background: rgba(255,255,255,.07); color: rgba(234,227,215,.4); border: 1px solid rgba(255,255,255,.11); box-shadow: none; transform: translateY(5px); pointer-events: none; }
.btn.hide { opacity: 0; pointer-events: none; }

/* choices */
.choices { display: flex; flex-direction: column; gap: 10px; }
.choice {
  text-align: left; padding: 14px 16px; min-height: 54px; border-radius: 14px; font-size: 16px; font-weight: 600;
  background: var(--surface); border: 2px solid var(--line); box-shadow: 0 4px 0 var(--ledge, #2b2e38);
  display: flex; align-items: center; gap: 12px; transition: transform .08s, border-color .15s, background .15s, opacity .3s;
}
.choice:active { transform: translateY(3px); box-shadow: 0 1px 0 var(--ledge, #2b2e38); }
.choice.picked { --ledge: color-mix(in srgb, var(--violet-bright) 50%, #070a14); border-color: var(--violet-bright); background: rgba(123,92,253,.12); }
.choice.right { --ledge: color-mix(in srgb, var(--mint) 50%, #070a14); border-color: var(--mint); background: rgba(63,214,154,.14); }
.choice.wrong { --ledge: color-mix(in srgb, var(--red) 50%, #070a14); border-color: var(--red); background: rgba(216,85,85,.14); }
.choice.locked { pointer-events: none; }
.choice.off { opacity: .6; pointer-events: none; } .choice.right.off { opacity: 1; }
.choice .mini { flex: none; width: 56px; height: 34px; }

/* pair matching */
.pairs { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.pairs .choice { min-height: 60px; justify-content: center; text-align: center; font-size: 15px; padding: 10px 12px; }
.pairs .choice.done { opacity: .28; pointer-events: none; border-color: var(--mint); }
/* Image options: cls 'pics' on a pick, 'pics two' for a pair. Each option's
   html is an img (the lesson's own webp) followed by a span caption. Contain,
   not cover: cover cropped the toy art's subject out of square renders. */
.choices.pics { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.choices.pics.two { grid-template-columns: 1fr 1fr; }
.choices.pics .choice { flex-direction: column; align-items: stretch; gap: 6px; padding: 8px; font-size: 13px; font-weight: 700; text-align: center; line-height: 1.2; }
.choices.pics .choice span { text-align: center; }
.choices.pics:has(> :nth-child(4)) { grid-template-columns: 1fr 1fr; }
.choices.pics.rows { grid-template-columns: 1fr; }
.choices.pics.rows .choice { font-size: 14px; }
.choices.pics img { width: 100%; height: clamp(96px, 14vh, 132px); object-fit: contain; border-radius: 10px; display: block; }
.choices.pics.two img, .choices.pics:has(> :nth-child(4)) img { height: clamp(118px, 17vh, 160px); }
@media (max-height: 700px) { .choices.pics img { height: 76px; } .choices.pics.two img, .choices.pics:has(> :nth-child(4)) img { height: 96px; } }
/* gauge(): a labelled bar. .ok / .bad / .cap tint it. */
.gauge { display: flex; flex-direction: column; gap: 5px; }
.gauge .gl { display: flex; justify-content: space-between; font-size: 12px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--dim); }
.gauge .gl b { font: 600 13px var(--mono); color: var(--text); text-transform: none; letter-spacing: 0; }
.gauge .gt { height: 8px; border-radius: 999px; background: var(--raised); overflow: hidden; }
.gauge .gt i { display: block; height: 100%; width: 0; border-radius: 999px; background: var(--violet-bright); transition: width .45s cubic-bezier(.2,.8,.2,1); }
.gauge.ok .gt i { background: var(--mint); } .gauge.bad .gt i, .gauge.cap .gt i { background: var(--red); }
/* compare(): two images, one wiper. */
.compare { display: flex; flex-direction: column; gap: 6px; }
.compare .cmp { position: relative; width: 100%; aspect-ratio: 3 / 2; border-radius: 14px; overflow: hidden; background: var(--surface); }
.compare .cmp img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
.compare .cmp .edge { position: absolute; top: 0; bottom: 0; left: 50%; width: 2px; margin-left: -1px; background: #fff; box-shadow: 0 0 0 1px rgba(0,0,0,.35); pointer-events: none; }
.compare .cmp .wiper { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: ew-resize; }
.compare .caps { display: flex; justify-content: space-between; font-size: 12px; font-weight: 700; color: var(--dim); letter-spacing: .04em; text-transform: uppercase; }
/* credits in the browser ending */
.endstage .credits { position: absolute; left: 0; right: 0; bottom: 4px; text-align: center; font-size: 10px; color: var(--faint); padding: 0 12px; }

/* sort */
.rulecard { font-size: 14px; line-height: 1.4; padding: 10px 14px; margin-bottom: 10px; border-radius: 12px; background: var(--surface); border: 1px solid var(--line); color: var(--dim); text-align: center; transition: opacity .35s; }
.rulecard b { color: var(--text); }
.rulecard.gone { opacity: 0; }
.sorthold { min-height: 120px; display: grid; place-items: center; }
.sortcard {
  width: 100%; background: var(--surface); border: 2px solid var(--line); border-radius: 20px; padding: 20px 18px; text-align: center;
  font-family: var(--sans); font-size: 19px; font-weight: 700; line-height: 1.28; display: flex; flex-direction: column; gap: 8px; text-wrap: balance;
  animation: cardIn .25s cubic-bezier(.2,.8,.2,1) both;
}
@keyframes cardIn { from { opacity: 0; transform: scale(.94) translateY(8px); } }
.sortcard.right { border-color: var(--mint); }
.sortcard.wrong { border-color: var(--red); }
.sortcard .why { font-family: var(--sans); font-size: 14px; font-weight: 600; color: #FF8A8A; }
.sortrow { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
/* three or more bins leave about 105px a button on the small phone: shrink the label rather than wrap it */
.sortrow.many .btn { font-size: 13px; letter-spacing: .03em; padding-left: 6px; padding-right: 6px; }
.dots { display: flex; gap: 6px; justify-content: center; margin-top: 10px; }
.dots i { width: 8px; height: 8px; border-radius: 50%; background: var(--raised); }
.dots i.ok { background: var(--mint); } .dots i.no { background: var(--red); }

/* slider */
.slider { display: flex; flex-direction: column; gap: 8px; padding: 12px 14px; border-radius: 16px; background: var(--surface); border: 1px solid var(--line); }
/* Reset the home screen's .row card, which otherwise draws a box inside the slider. */
.slider .row { display: flex; justify-content: space-between; align-items: baseline; padding: 0; min-height: 0; border: 0; border-radius: 0; background: none; font-size: 13px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--dim); }
.slider .val { font-family: var(--mono); font-size: 18px; color: var(--text); text-transform: none; letter-spacing: 0; }
input[type=range] { -webkit-appearance: none; appearance: none; width: 100%; height: 44px; background: transparent; margin: 0; }
input[type=range]::-webkit-slider-runnable-track { height: 10px; border-radius: 999px; background: var(--raised); }
input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 30px; height: 30px; margin-top: -10px; border-radius: 50%; background: var(--violet-bright); border: 4px solid var(--bg); box-shadow: 0 0 0 2px var(--violet); }
input[type=range]::-moz-range-track { height: 10px; border-radius: 999px; background: var(--raised); }
input[type=range]::-moz-range-thumb { width: 22px; height: 22px; border-radius: 50%; background: var(--violet-bright); border: 4px solid var(--bg); box-shadow: 0 0 0 2px var(--violet); }

/* word bank (fill, label, place) */
.bank { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
.word {
  padding: 10px 16px; min-height: 44px; border-radius: 12px; font-weight: 700; font-size: 15px; background: var(--surface); border: 2px solid var(--line); box-shadow: 0 4px 0 var(--ledge, #2b2e38);
  transition: transform .08s, border-color .15s, background .15s, opacity .2s;
}
.word:active { transform: translateY(3px); box-shadow: 0 1px 0 var(--ledge, #2b2e38); }
.word.picked { border-color: var(--violet-bright); background: rgba(123,92,253,.16); }
.word.right { border-color: var(--mint); background: rgba(63,214,154,.14); }
.word.wrong { border-color: var(--red); background: rgba(216,85,85,.14); }
.word.locked { pointer-events: none; opacity: .6; } .word.locked.right { opacity: 1; }
.word.off, .spot.off { opacity: .6; pointer-events: none; }
/* slots (place) */
.slot {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; min-height: 64px; padding: 6px 4px;
  border-radius: 14px; border: 2px dashed var(--line-strong); background: var(--surface); color: var(--dim);
  transition: border-color .15s, background .15s;
}
.slot .n { font-family: var(--mono); font-size: 12px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--faint); }
.slot .v { font-weight: 800; font-size: 14px; color: var(--mint); line-height: 1.1; text-align: center; }
.slot.done { border-style: solid; border-color: var(--mint); background: rgba(63,214,154,.12); pointer-events: none; }
.slot.done .v { animation: slotIn .25s cubic-bezier(.34,1.56,.64,1) both; }
@keyframes slotIn { from { transform: scale(.6); opacity: 0; } }
.blank { display: inline-block; min-width: 110px; border-bottom: 3px solid var(--violet-bright); padding: 0 6px; margin: 0 2px; color: #fff; font-weight: 700; transition: border-color .15s; }
.blank.right { border-color: var(--mint); color: var(--mint); } .blank.wrong { border-color: var(--red); color: #FF8A8A; }

/* figure with tap markers */
.figure { position: relative; width: min(100%, 300px); margin: 0 auto; }
.figure img { width: 100%; height: auto; display: block; filter: drop-shadow(0 16px 24px rgba(0,0,0,.5)); }
.spot { position: absolute; width: 44px; height: 44px; margin: -22px 0 0 -22px; display: grid; place-items: center; }
.spot i { width: 22px; height: 22px; border-radius: 50%; background: var(--violet-bright); border: 3px solid var(--bg); box-shadow: 0 0 0 3px var(--violet); animation: pulse 1.6s ease-in-out infinite; }
@keyframes pulse { 50% { transform: scale(1.25); box-shadow: 0 0 0 7px rgba(123,92,253,.35); } }
.spot.locked { pointer-events: none; } .spot.locked i { animation: none; }
.spot.right i { background: var(--mint); box-shadow: 0 0 0 3px var(--mint), 0 0 24px 6px rgba(63,214,154,.5); }
.spot.wrong i { background: var(--red); box-shadow: 0 0 0 3px var(--red); }
.spot b { position: absolute; top: 40px; left: 50%; transform: translateX(-50%); font-size: 12px; font-weight: 700; white-space: nowrap; background: rgba(10,14,26,.85); padding: 3px 8px; border-radius: 999px; }
.spot.right b { color: var(--mint); } .spot.wrong b { color: #FF8A8A; }

/* labeled diagram */
.diagram { width: min(100%, 300px); margin: 0 auto; }
.diagram svg { width: 100%; height: auto; display: block; overflow: visible; }
.diagram .cap { fill: var(--faint); font: 700 10px var(--sans); letter-spacing: .1em; text-transform: uppercase; }
.diagram .tgt { cursor: pointer; }
.diagram .tgt .hit { fill: transparent; }
.diagram .tgt .pin { fill: var(--violet); stroke: var(--bg); stroke-width: 2.5; animation: pinpulse 1.6s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }
@keyframes pinpulse { 50% { transform: scale(1.2); } }
.diagram .tgt .num { fill: #fff; font: 800 13px var(--sans); text-anchor: middle; pointer-events: none; }
.diagram .tgt .tag { fill: var(--mint); font: 700 12px var(--sans); }
.diagram .tgt.done .pin { fill: var(--mint); animation: none; } .diagram .tgt.done .num { opacity: 0; }
.diagram .tgt.done .hit { pointer-events: none; }
.shake { animation: shake .4s; }
/* nudge(): three pulses over a scene target, then gone. */
/* It grows by its shadow, not by scale, and nudge() keeps its box inside the scene: anything past the edge adds to #scene's scroll height and fails play.mjs. */
/* A full screen (full()): the scene takes the stage edge to edge, and the
   controls reserve only what they hold. */
#stage.full #copy:empty, #stage.full #eyebrow:empty { display: none; }
#scene.full { flex: 1 1 auto; padding-top: 0; padding-bottom: 0; margin: 0 -18px; }
#stage.full #controls { flex: 0 0 auto; padding-inline: 0; }
.moment { position: relative; flex: 1; display: flex; flex-direction: column; justify-content: center; padding: 0 26px 12px; overflow: hidden; }
.moment.art { justify-content: flex-end; }
.moment > img:not(.mo-photo) { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; animation: moKb 10s ease-out both; }
.moment.art::before { content: ""; position: absolute; inset: 0; z-index: 1; background: linear-gradient(to bottom, rgba(10,14,26,0) 30%, rgba(10,14,26,.72) 62%, var(--bg) 94%); }
.mo-t { position: relative; z-index: 2; }
.moment.photo { justify-content: flex-start; padding: 0 0 12px; }
.moment.photo .mo-photo { display: block; width: 100%; height: auto; max-height: 46%; object-fit: contain; flex: none; animation: moKb 10s ease-out both; }
.moment.photo .mo-t { padding: 20px 26px 0; }
.mo-hold { visibility: hidden; pointer-events: none; }
.mo-line { margin: 0; font-family: var(--serif); font-weight: 700; font-size: clamp(30px, 8.6vw, 42px); line-height: 1.08; letter-spacing: -.015em; color: var(--gold); text-wrap: balance; text-shadow: 0 0 34px rgba(252,192,80,.3); }
.mo-line > span { display: inline-block; animation: moWord .55s cubic-bezier(.2,.8,.2,1) both; }
.mo-sub { margin: 14px 0 0; font-size: 19px; line-height: 1.38; font-weight: 600; color: var(--text); text-wrap: pretty; }
@keyframes moWord { from { opacity: 0; transform: translateY(10px); filter: blur(4px); } }
@keyframes moKb { from { transform: scale(1.08); } }
.cue { position: absolute; left: 18px; right: 18px; bottom: calc(26px + env(safe-area-inset-bottom)); z-index: 2; pointer-events: none; text-align: center; font-size: 16px; font-weight: 600; color: var(--dim); animation: cueIn .4s cubic-bezier(.2,.8,.2,1) both, cueBreathe 2.4s ease-in-out .4s 4; }
@keyframes cueIn { from { opacity: 0; transform: translateY(8px); } }
@keyframes cueBreathe { 50% { opacity: .5; } }
.nudge { position: absolute; pointer-events: none; border-radius: 16px; border: 2px solid var(--gold); animation: nudge 1.1s ease-out 3 both; z-index: 3; }
@keyframes nudge { 0% { opacity: 0; box-shadow: 0 0 0 0 rgba(252,192,80,.5); } 30% { opacity: .95; } 100% { opacity: 0; box-shadow: 0 0 0 10px rgba(252,192,80,0); } }
@keyframes shake { 25% { transform: translateX(-4px); } 75% { transform: translateX(4px); } }

.mini { display: block; flex: none; width: 56px; height: 34px; }
.mini path { fill: none; stroke: var(--text); stroke-width: 2.5; stroke-linecap: round; }
.mini line { stroke: var(--faint); stroke-width: 1; stroke-dasharray: 3 3; }

/* hero image, reveal box */
.hero { display: grid; place-items: center; }
.hero img { width: 210px; max-width: 100%; height: auto; filter: drop-shadow(0 18px 30px rgba(123,92,253,.35)); }
/* The opening screen: the card art, large, under the sentence that names the concept. */
.hero.orient img { width: 270px; max-height: 38vh; object-fit: contain; }
.revealbox { display: grid; place-items: center; }
@media (max-height: 700px) { .hero img { width: 170px; } .choice { min-height: 48px; padding: 11px 14px; } }


/* ---------- ending: the card beat, then the map beat ---------- */
#scene.ending { padding-top: 0; flex: 1 1 auto; }
.endstage { position: absolute; inset: 0; margin: 0 !important; }
.endcanvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.ecard {
  position: absolute; left: 50%; top: 50%; width: min(196px, 40vh * .66); aspect-ratio: 2 / 3;
  transform: translate(-50%, -54%) scale(.86); opacity: 0; transition: transform .6s cubic-bezier(.2,.8,.2,1), opacity .5s;
}
.ecard.in { opacity: 1; transform: translate(-50%, -54%) scale(1); }
.ecard.claimed { transform: translate(-50%, -54%) scale(1.06); }
.ecard.collapse { transform: translate(-50%, -50%) scale(.08); opacity: 0; transition: transform .45s cubic-bezier(.6,0,.9,.4), opacity .3s .15s; }
.ecard .face {
  position: absolute; inset: 0; border-radius: 16px; overflow: hidden; display: flex; flex-direction: column; padding: 9px;
  background: linear-gradient(160deg, #1d2440 0%, #101528 55%, #1a1f3a 100%); border: 1.5px solid rgba(234,227,215,.22);
  box-shadow: 0 18px 40px rgba(0,0,0,.55), 0 0 0 0 rgba(252,192,80,0); transition: box-shadow .7s;
}
.ecard.claimed .face { box-shadow: 0 18px 40px rgba(0,0,0,.55), 0 0 40px 6px rgba(252,192,80,.38); border-color: rgba(252,192,80,.7); }
.ecard.foil .face { border-color: rgba(169,155,255,.8); }
.ecard.gold .face { border-color: var(--gold); background: linear-gradient(160deg, #3a2a10, #1a1408 55%, #3a2c12); }
.ecard .art { flex: 0 0 52%; border-radius: 10px; background: radial-gradient(circle at 50% 40%, #2a2f55, #121829 75%); display: grid; place-items: center; overflow: hidden; }
.ecard .art img { width: 82%; height: 82%; object-fit: contain; filter: drop-shadow(0 8px 14px rgba(0,0,0,.5)); }
.ecard .name { font-family: var(--serif); font-size: 17px; font-weight: 700; line-height: 1.15; margin: 9px 2px 4px; color: #fff; text-wrap: balance; }
.ecard .line { font-size: 10.5px; line-height: 1.32; color: var(--dim); margin: 0 2px; flex: 1; min-height: 0; overflow: hidden; }
.ecard .meter { position: relative; height: 14px; border-radius: 999px; background: rgba(234,227,215,.1); overflow: hidden; margin-top: 6px; }
.ecard .meter i { position: absolute; left: 0; top: 0; bottom: 0; width: 0; border-radius: 999px; background: linear-gradient(90deg, var(--gold-deep), var(--gold)); transition: width .9s cubic-bezier(.2,.8,.2,1); }
.ecard .meter span { position: absolute; inset: 0; display: grid; place-items: center; font-size: 8.5px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; color: var(--text); }
.ecard .shine { position: absolute; inset: 0; background: linear-gradient(115deg, transparent 38%, rgba(255,255,255,.22) 48%, rgba(169,155,255,.25) 52%, transparent 62%); transform: translateX(-120%); }
.ecard.claimed .shine { animation: sheen .9s ease-in-out 3; }
@keyframes sheen { to { transform: translateX(120%); } }
.ecard .tag {
  position: absolute; left: 50%; top: -14px; transform: translate(-50%, 6px); opacity: 0; z-index: 2;
  font-size: 10.5px; font-weight: 800; letter-spacing: .12em; text-transform: uppercase; color: #2A1B00; background: var(--gold); padding: 4px 10px; border-radius: 999px; box-shadow: 0 3px 0 var(--gold-deep); white-space: nowrap;
  transition: opacity .3s, transform .3s cubic-bezier(.34,1.56,.64,1);
}
.ecard.claimed .tag { opacity: 1; transform: translate(-50%, 0); }
.endxp {
  position: absolute; left: 50%; bottom: 6px; transform: translate(-50%, 8px); opacity: 0; transition: opacity .3s, transform .3s;
  font-family: var(--mono); font-size: 15px; color: var(--gold); font-variant-numeric: tabular-nums; text-align: center; white-space: nowrap;
}
.endxp b { font-family: var(--sans); font-size: 26px; font-weight: 900; text-shadow: 0 0 18px rgba(252,192,80,.6); }
.endxp small { display: block; font-family: var(--sans); font-size: 12px; font-weight: 700; color: var(--dim); }
.endxp.in { opacity: 1; transform: translate(-50%, 0); }
@media (max-height: 700px) { .ecard { width: min(150px, 34vh * .66); } .ecard .name { font-size: 14px; margin-top: 6px; } .ecard .line { font-size: 9.5px; } .endxp { bottom: 0; } .endxp b { font-size: 22px; } }


/* ---------- feedback sheet ---------- */
#sheet {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 18px 18px calc(16px + env(safe-area-inset-bottom)); border-radius: 24px 24px 0 0;
  transform: translateY(110%); transition: transform .3s cubic-bezier(.2,.8,.2,1); z-index: 5;
  background: var(--raised); box-shadow: 0 -10px 40px rgba(0,0,0,.5);
}
#sheet.on { transform: none; }
#sheet.ok { background: #143327; } #sheet.ok .t { color: var(--mint); } #sheet.ok .btn { background-color: var(--mint); color: var(--mint-ink); --ledge: #1f8a63; --halo: 82, 218, 164; }
#sheet.neu { background: #221E3F; } #sheet.neu .t { color: var(--violet-bright); } #sheet.neu .btn { background-color: var(--violet); color: #fff; --ledge: #4a36b3; }
#sheet.no { background: #3A1717; } #sheet.no .t { color: #FF8A8A; } #sheet.no .btn { background-color: var(--red); color: #fff; --ledge: #8f2f2f; --halo: 220, 102, 102; }
#sheet .t { font-weight: 800; font-size: 20px; display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
#sheet .t .ic { width: 28px; height: 28px; border-radius: 50%; display: grid; place-items: center; font-size: 16px; background: currentColor; flex: none; }
#sheet .t .ic span { color: var(--bg); font-weight: 900; }
/* A wrong sheet whose title is the right answer says so above it. */
#sheet .t .tt { display: flex; flex-direction: column; gap: 1px; }
#sheet .t .k { font-size: 12px; font-weight: 900; letter-spacing: .12em; text-transform: uppercase; opacity: .85; }
/* The story's report flag: a ghost 32px circle on a 44px hit area, at the title's end.
   The negative margins keep the hit area from making the title row taller than its icon. */
#sheet .t .flag { margin: -8px -6px -8px auto; flex: none; width: 44px; height: 44px; display: grid; place-items: center; background: none; border: 0; padding: 0; }
#sheet .t .flag svg { width: 32px; height: 32px; padding: 8.5px; border-radius: 50%; background: rgba(255,255,255,.06); border: .8px solid rgba(255,255,255,.18); fill: none; stroke: rgba(234,227,215,.55); stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; box-sizing: border-box; }
#sheet .t .flag.on svg { fill: rgba(234,227,215,.55); }
#sheet .b { font-size: 15.5px; line-height: 1.45; color: var(--text); margin-bottom: 14px; }
/* Swipe the sheet down (or tap its handle) and it folds to its button, to see
   the scene behind it; swipe up to read it again. As the story's sheet does. */
#sheet { touch-action: none; }
#sheet .grip { display: block; width: 100%; height: 36px; margin: -18px 0 -6px; position: relative; }
#sheet .grip::before { content: ""; position: absolute; left: 50%; top: 10px; width: 36px; height: 4px; margin-left: -18px; border-radius: 2px; background: rgba(234,227,215,.3); }
#sheet .t, #sheet .b { max-height: 360px; transition: max-height .3s cubic-bezier(.2,.8,.2,1), opacity .2s, margin .3s; }
#sheet.min .t, #sheet.min .b { max-height: 0; opacity: 0; margin-bottom: 0; overflow: hidden; }
#sheet .b b { color: #fff; }
@media (min-height: 701px) { #sheet .b { font-size: 17px; } }
#sheet .acts { display: flex; gap: 10px; }
#sheet .acts .btn { flex: 1; width: auto; }
#sheet .nova { flex: none; width: 56px; min-height: 56px; padding: 0; border-radius: 50%; display: grid; place-items: center; background: none; transition: transform .1s; }
#sheet .nova:active { transform: scale(.9); }
#sheet .nova img { display: block; width: 52px; height: 52px; filter: drop-shadow(0 4px 10px rgba(0,0,0,.45)); }

/* toasts */
#toast { position: absolute; top: 2px; right: 18px; pointer-events: none; z-index: 6; }
/* Tappable term. The negative margin grows the hit area without moving the line. */
.term { text-decoration: underline dotted var(--gold-deep); text-decoration-thickness: 1.5px; text-underline-offset: 3px; cursor: pointer; padding: 10px 2px; margin: -10px -2px; }
.defpop { position: fixed; z-index: 40; max-width: min(320px, calc(100vw - 24px)); background: var(--raised); border: 1px solid var(--line-strong); border-radius: 12px; padding: 10px 12px; font-size: 14.5px; line-height: 1.4; color: var(--text); box-shadow: 0 8px 24px rgba(0,0,0,.5); animation: copyIn .2s ease-out both; }
.defpop b { display: block; color: var(--gold); margin-bottom: 2px; }
/* A clip: hidden until it is the answer, at its own shape (README "Clips"). */
.clip { display: none; position: relative; margin: 0 auto; max-width: 100%; border-radius: 16px; overflow: hidden; background: #000; }
.clip.shown { display: block; }
.clip.v { height: min(50vh, 440px); aspect-ratio: 9 / 16; }
.clip.h { width: 100%; aspect-ratio: 16 / 9; }
.clip video { width: 100%; height: 100%; object-fit: contain; display: block; opacity: 0; }
.clip.live video { opacity: 1; }
.clip-cap { position: absolute; left: 10px; right: 10px; bottom: 14px; text-align: center; font: 700 15px/1.3 var(--sans); color: #fff; text-shadow: 0 1px 3px #000, 0 0 10px rgba(0,0,0,.85); pointer-events: none; }
.clip-cap:empty { display: none; }
.clip-skip { position: absolute; right: 8px; top: 8px; min-width: 64px; min-height: 44px; padding: 0 14px; border-radius: 22px; font: 700 13px var(--sans); color: #fff; background: rgba(0,0,0,.55); opacity: 0; pointer-events: none; transition: opacity .2s; }
.clip.on .clip-skip, .clip.on .clip-back { opacity: 1; pointer-events: auto; }
.clip-back { position: absolute; right: 80px; top: 8px; width: 44px; height: 44px; padding: 0; border: 0; border-radius: 50%; display: grid; place-items: center; color: #fff; background: rgba(0,0,0,.55); opacity: 0; pointer-events: none; transition: opacity .2s, transform .12s; }
.clip-back:active { transform: scale(.94); }
.clip-back svg { width: 20px; height: 20px; fill: currentColor; }
.clip-fail { display: none; position: absolute; inset: 0; align-items: center; justify-content: center; padding: 16px; text-align: center; font: 700 16px/1.3 var(--sans); color: #fff; background: #000; }
.clip.failed .clip-fail { display: flex; }
.clip.failed .clip-go, .clip.failed .clip-skip, .clip.failed .clip-back { display: none; }
.clip-gone { display: none !important; }
/* Play before the first tap, replay after the end; hidden while it plays. */
.clip-go { position: absolute; left: 50%; top: 50%; width: 72px; height: 72px; margin: -36px 0 0 -36px; padding: 0; border: 0; border-radius: 50%; display: grid; place-items: center; background: rgba(0,0,0,.5); color: #fff; transition: opacity .2s, transform .12s; }
.clip-go:active { transform: scale(.94); }
.clip-go svg { width: 34px; height: 34px; fill: currentColor; }
.clip-go .g-again, .clip.done .clip-go .g-play { display: none; }
.clip.done .clip-go .g-again { display: block; }
.clip.on .clip-go, .clip.copy .clip-go { opacity: 0; pointer-events: none; }
/* The app's clip credit: a face and a handle top-left, a short scrim, never the bottom (the source's own subtitles live there). */
.clip-by { position: absolute; left: 0; right: 0; top: 0; display: flex; align-items: center; gap: 8px; padding: 10px 12px 22px; background: linear-gradient(rgba(0,0,0,.42), rgba(0,0,0,0)); font: 600 13px var(--sans); color: rgba(255,255,255,.92); text-shadow: 0 1px 2px rgba(0,0,0,.6); pointer-events: none; opacity: 0; transition: opacity .2s; }
.clip.live .clip-by { opacity: 1; }
.clip-by img { width: 20px; height: 20px; border-radius: 50%; object-fit: cover; }
.clip-by span { min-width: 0; overflow-wrap: anywhere; }
.clip.on .clip-by { right: 132px; }
@media (max-height: 700px) { .clip.v { height: min(46vh, 300px); } }
.tst { font-weight: 800; color: var(--gold); font-size: 14px; background: rgba(252,192,80,.16); border: 1px solid var(--gold-deep); padding: 4px 10px; border-radius: 999px; animation: tst 1.1s ease-out both; }
.tst small { font-size: 11px; color: var(--dim); }
@keyframes tst { 0% { opacity: 0; transform: translateY(6px); } 20% { opacity: 1; } 100% { opacity: 0; transform: translateY(-26px); } }

/* ---------- screens (home, end) ---------- */
.screen { position: absolute; inset: 0; display: flex; flex-direction: column; padding: 24px 22px; background: var(--bg); z-index: 4; overflow-y: auto; }
#scene .screen { padding: 8px 0 0; z-index: 1; position: relative; inset: auto; }
#home { display: none; } #home.on { display: flex; }
.screen .eyebrow { font-size: 12px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: var(--violet-bright); }
.screen h1 { font-family: var(--serif); font-size: 32px; line-height: 1.1; margin: 8px 0 10px; letter-spacing: -0.01em; text-wrap: balance; font-weight: 600; }
.screen p { color: var(--dim); font-size: 15.5px; margin: 0 0 16px; text-wrap: pretty; }
.screen .grow { flex: 1; }
.rows { display: flex; flex-direction: column; gap: 8px; }
.row { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 10px; padding: 12px 14px; min-height: 52px; border-radius: 14px; background: var(--surface); border: 1px solid var(--line); text-align: left; color: var(--faint); }
.row.live { color: var(--text); border-color: var(--line-strong); box-shadow: 0 4px 0 #070a14; }
.row.got { border-color: var(--gold-deep); background: rgba(252,192,80,.08); }
.row .rt { font-weight: 700; font-size: 15px; display: flex; flex-direction: column; }
.row .rt small { font-size: 11px; font-weight: 600; color: var(--dim); }
.row .st { font-size: 11.5px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--dim); }
.row.live .st { color: var(--violet-bright); } .row.got .st { color: var(--gold); }

/* ---------- takeaway: the back of the card, turned over to claim it ---------- */
#copy:empty, #look .lk-copy:empty { min-height: 0; margin: 0; }
#scene.takeaway { flex: 1 1 auto; align-items: center; justify-content: center; padding-top: 4px; }
/* The takeaway has one button and no prompt, so the room the beats hold for
   answer options and the eyebrow goes to the card. */
#stage.tk #controls { flex-basis: auto; }
#stage.tk #eyebrow { display: none; }
.tkcard { position: relative; width: min(100%, 340px); perspective: 1400px; opacity: 0; transform: translateY(46px) rotate(-3deg) scale(.94); transition: transform .7s cubic-bezier(.2,.8,.2,1), opacity .5s; }
.tkcard.in { opacity: 1; transform: none; }
.tkcard.away { opacity: 0; transform: translateY(-24px) scale(.9); transition-duration: .4s; }
.tk-inner { position: relative; transform-style: preserve-3d; transition: transform .9s cubic-bezier(.3,.7,.2,1); }
.tkcard.flipped .tk-inner { transform: rotateY(180deg); }
.tk-back, .tk-front { backface-visibility: hidden; -webkit-backface-visibility: hidden; }
/* The back has the real card's shape and corners; engine.js sizes it and sets --r. */
.tk-back { position: relative; border-radius: var(--r, 22px); overflow: hidden; border: 1.5px solid rgba(234,227,215,.22); box-shadow: 0 24px 50px rgba(0,0,0,.55); padding: 20px 20px 24px; display: flex; flex-direction: column;
  background: radial-gradient(120% 70% at 100% 0%, rgba(123,92,253,.28), transparent 60%), radial-gradient(90% 60% at 0% 100%, rgba(252,192,80,.14), transparent 60%), linear-gradient(160deg, #1d2440 0%, #101528 55%, #1a1f3a 100%); }
.tk-back::before { content: ""; position: absolute; inset: 7px; border-radius: max(4px, calc(var(--r, 22px) - 7px)); border: 1px solid rgba(252,192,80,.22); pointer-events: none; }
.tkcard.foil .tk-back { border-color: rgba(169,155,255,.8); }
.tkcard.gold .tk-back { border-color: var(--gold); }
.tk-head { display: grid; gap: 2px; padding-bottom: 14px; margin-bottom: 18px; border-bottom: 1px solid rgba(234,227,215,.14); }
.tk-head span { font-size: 12px; font-weight: 800; letter-spacing: .14em; text-transform: uppercase; color: var(--gold); }
.tk-head b { font-family: var(--serif); font-size: 27px; font-weight: 700; line-height: 1.1; color: #fff; text-wrap: balance; }
.tk-lines { flex: none; display: grid; gap: calc(var(--tk-fs, 19px) * .9); }
/* What the words leave over goes mostly under them. */
.tk-sp { flex: 2 1 0; } .tk-sp.b { flex-grow: 3; }
.tk-line { display: grid; grid-template-columns: 24px 1fr; gap: 10px; align-items: start; opacity: 0; transform: translateY(10px); transition: opacity .5s, transform .5s cubic-bezier(.2,.8,.2,1); }
.tk-line.in { opacity: 1; transform: none; }
.tk-line p { margin: 0; font-family: var(--serif); font-size: var(--tk-fs, 19px); line-height: 1.36; font-weight: 500; color: var(--text); text-wrap: pretty; }
.tk-line p b { font-weight: 700; color: #fff; -webkit-box-decoration-break: clone; box-decoration-break: clone; background: linear-gradient(transparent 60%, rgba(252,192,80,.42) 60%) no-repeat left / 0% 100%; transition: background-size .8s .35s cubic-bezier(.2,.8,.2,1); }
.tk-line.in p b { background-size: 100% 100%; }
.tk-tick { width: 22px; height: 22px; margin-top: 3px; fill: none; stroke: var(--gold); stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 24; stroke-dashoffset: 24; transition: stroke-dashoffset .45s .1s ease-out; }
.tk-line.in .tk-tick { stroke-dashoffset: 0; }
.tk-shine { position: absolute; inset: 0; pointer-events: none; background: linear-gradient(115deg, transparent 38%, rgba(255,255,255,.14) 48%, rgba(169,155,255,.16) 52%, transparent 62%); transform: translateX(-120%); }
.tkcard.in .tk-shine { animation: sheen 1.4s .3s ease-in-out 1; }
/* The front is the ending's own card, laid out at the ending's size and scaled
   up to fill the takeaway, so it can be handed over without changing. */
.tk-front { position: absolute; inset: 0; transform: rotateY(180deg); }
.tkcard .tk-front > .ecard { left: 0; top: 0; transform-origin: 0 0; transform: scale(var(--k, 1)); transition: none; }
.btn.tk-cta { position: relative; overflow: hidden; animation: tkIn .45s cubic-bezier(.2,.8,.2,1) both; }
.btn.tk-cta::after { content: ""; position: absolute; top: 0; bottom: 0; left: -60%; width: 45%; background: linear-gradient(100deg, transparent, rgba(255,255,255,.6), transparent); animation: tkShine 1.5s .5s ease-in-out 2; }
@keyframes tkIn { from { opacity: 0; transform: translateY(10px); } }
@keyframes tkShine { to { left: 130%; } }
@media (max-height: 700px) {
  .tk-back { padding: 16px 16px 18px; }
  .tk-head { padding-bottom: 10px; margin-bottom: 12px; } .tk-head b { font-size: 22px; }
  .tk-lines { gap: 12px; } .tk-line p { font-size: var(--tk-fs, 16.5px); line-height: 1.32; }
}

/* ---------- intro: the card that opened the lesson ----------
   Sits where the app's card was, in frame coordinates; the top bar and the
   words arrive around it, then it lifts away for the first beat. */
.herocard { position: absolute; z-index: 5; display: block; border-radius: 16px; transition: transform .42s cubic-bezier(.5,0,.2,1), opacity .36s ease-in; transform-origin: 50% 0; }
.herocard.art { overflow: hidden; background: var(--surface); border: 1px solid var(--line-strong); }
.herocard.art img { width: 100%; height: 72%; object-fit: cover; display: block; }
.herocard.art b { display: block; padding: 12px 14px; font-family: var(--serif); font-weight: 700; font-size: 20px; color: var(--text); }
.herocard.away { transform: translateY(-40px) scale(.86); opacity: 0; }
.herocard.dive { transform: translateY(48vh) scale(.28); opacity: 0; transition: transform .42s cubic-bezier(.6,0,.9,.6), opacity .3s ease-in .12s; }
.heroflip { position: absolute; z-index: 15; transform-style: preserve-3d; pointer-events: none; border-radius: 16px; }
.heroflip > .front, .heroflip > .heroback { position: absolute; inset: 0; border-radius: inherit; backface-visibility: hidden; -webkit-backface-visibility: hidden; transition: none; }
.heroback { transform: rotateY(180deg); background-size: cover; background-position: center; box-shadow: 0 0 60px rgba(124,92,255,.45); }
.herotext { position: absolute; left: 24px; right: 24px; z-index: 5; text-align: center; opacity: 0; transform: translateY(10px); transition: opacity .34s ease-out, transform .34s cubic-bezier(.2,.8,.2,1); }
.herotext h1 { margin: 0 0 8px; font-family: var(--serif); font-weight: 700; font-size: 30px; line-height: 1.1; color: #fff; }
.herotext p { margin: 0; font-size: 18px; line-height: 1.35; color: var(--text); }
.herotext.away { opacity: 0; transform: translateY(-12px); transition-duration: .22s; }
#frame.introin .herotext:not(.away) { opacity: 1; transform: none; }
/* The bar and the button wait for the words, so the hand-off from the app
   changes nothing but the words arriving. Hidden at once and shown with a
   fade: a fade out was still running when the first beat lifted the app's
   cover, so the bar flickered over the card. */
#frame.intro #top, #frame.intro #controls { opacity: 0; }
#frame.intro.introin #top, #frame.intro.introin #controls { opacity: 1; transition: opacity .3s; }
@media (max-height: 700px) { .herotext h1 { font-size: 26px; } .herotext p { font-size: 16.5px; } }

/* Pending coins (engine.js, Pend): at the eyebrow's end, outlined rather than
   filled, because they are not owned yet; it fills toward a perfect run's as
   coins land. The XP toast rises from under it. */
.pend { --fill: 0; position: absolute; top: 4px; right: 16px; z-index: 5; display: flex; align-items: center; gap: 5px; padding: 3px 10px 3px 4px; border-radius: 999px; border: 1.5px dashed var(--gold-deep); background: linear-gradient(90deg, rgba(252,192,80,.26) calc(var(--fill) * 100%), rgba(23,19,13,.72) 0); pointer-events: none; white-space: nowrap; transition: opacity .3s; }
.pend img { width: 22px; height: 22px; }
.pend img.hit { animation: pendHit .26s ease-out; }
.pend b { font: 800 15px/1 var(--sans); color: var(--gold); font-variant-numeric: tabular-nums; }
.pend span { font: 600 12px/1 var(--sans); color: var(--dim); }
.pend.bump { animation: pendBump .26s ease-out; }
@keyframes pendBump { 40% { transform: scale(1.08); border-color: var(--gold); box-shadow: 0 0 14px rgba(252,192,80,.45); } }
@keyframes pendHit { 35% { transform: scale(1.45); } }
.fx .pcoin { position: absolute; left: 0; top: 0; width: 28px; height: 28px; pointer-events: none; z-index: 8; filter: drop-shadow(0 0 5px rgba(252,192,80,.75)); }
#frame.coins #toast { top: 34px; }
#frame.coins #eyebrow { padding-right: 150px; }
#frame.intro .pend, #stage.tk .pend { opacity: 0; }
/* onEarn: the tally rises in just ahead of the first coin and fades after
   the last; the eyebrow keeps its room, so nothing moves when it does. */
#frame.coins-earn .pend { opacity: 0; transform: translateY(-8px); transition: opacity .16s ease-out, transform .16s ease-out; }
#frame.coins-earn .pend.up { opacity: 1; transform: none; }
#frame.coins-earn.intro .pend.up, #frame.coins-earn #stage.tk .pend.up { opacity: 0; }
