/* =================================================================
   THEME TOKENS — light is the default; dark is the same park at night.
   Menus and game scenes share one palette so the whole app reads as one
   place. Only these values change between themes.
   ================================================================= */
:root, [data-theme="light"] {
  /* --on is the ink of whichever surface we are on, as raw channels, and every
     quieter shade of text is a fade of it rather than a colour of its own.
     Hard-coded muted greys are how the app ended up with a navy #2b3550 sitting
     on a navy night surface at 1:1 — invisible, because the high contrast rule
     that set it had been written assuming a white page. Faded ink cannot make
     that mistake: it is always the current surface's own ink. */
  --on: 34 48 85;
  --surface: #ffffff;
  --surface-2: #f2f7fd;
  --surface-3: #f6faff;
  --ink: #223055;
  --ink-2: #29355A;
  /* Declared once, here, on purpose: a theme changes --on and these re-resolve
     against it, so there is one formula for the whole app rather than a pair of
     hexes per theme that drift apart. Measured — .78 gives 6.5:1 on white and
     8.1:1 on the night surface, .72 gives 5.1:1 and 6.8:1. Both clear 4.5. */
  --muted:   rgb(var(--on) / .82);
  --muted-2: rgb(var(--on) / .72);
  /* The chosen pill in a segmented control. White on the old #4a90d9 measured
     3.34:1, under the 4.5 that 15px text needs; this one is 5.17:1. */
  --pill: #2f6fb5;
  --line: #dbe6f5;
  --line-2: #eef3fa;
  --edge: rgba(41,53,90,.15);
  --edge-soft: rgba(41,53,90,.12);
  --drop: rgba(41,53,90,.14);
  /* menu scenery */
  --sky-1: #7EC8F5; --sky-2: #A8DCF8; --sky-3: #DFF3FD;
  --hill-1: #8ED07C; --hill-2: #6FBE5E;
  /* game scenes */
  --scene-sky-1: #8FD3F7; --scene-sky-2: #BEE7FA; --scene-sky-3: #E9F6FD;
  --scene-grd-1: #8ED07C; --scene-grd-2: #5FA94F;
  --ground-lip: #a8e096;
  --day: 1;    /* sun + clouds */
  --night: 0;  /* moon + stars */
  /* bandstand */
  --stage-back: #d9ae7e; --stage-lip: #e0b487; --curtain: #d0463c; --light-rail: #e8d9a8;
  --bulb-off: .75;
  --outline: #3d2b1a;
  --beam-0: rgba(255,214,92,0); --beam-1: rgba(255,208,68,.60); --beam-2: rgba(255,192,40,.86);
  --beam-pool: rgba(255,222,120,.95);
  /* in-scene info panel (the song sheet) */
  --panel-bg: rgba(255,255,255,.82); --panel-edge: rgba(41,53,90,.22);
  --panel-line: rgba(41,53,90,.28); --panel-mark: rgba(41,53,90,.45);
  /* report chart */
  --chart-hit: #3f86cf; --chart-other: #c3daf1;
  --accent-soft: #d9ecff; --accent-ink: #1f4f80;
}
[data-theme="dark"] {
  /* Night flips the ink and everything quieter follows on its own. No --muted
     pair here: the formulas in :root re-resolve against this --on. */
  --on: 238 242 255;
  --surface: #242c4a;
  --surface-2: #1b2138;
  --surface-3: #2c3454;
  --ink: #eef2ff;
  --ink-2: #e2e8fb;
  --line: #3a4470;
  --line-2: #2d3455;
  --edge: rgba(0,0,0,.45);
  --edge-soft: rgba(0,0,0,.4);
  --drop: rgba(0,0,0,.45);
  --sky-1: #131a38; --sky-2: #22294f; --sky-3: #38315f;
  --hill-1: #2f5540; --hill-2: #204030;
  --scene-sky-1: #20204a; --scene-sky-2: #4b3a72; --scene-sky-3: #a2557a;
  --scene-grd-1: #5e4a7a; --scene-grd-2: #3a2d51;
  --ground-lip: #6b5590;
  --day: 0;
  --night: 1;
  --stage-back: #5b3a52; --stage-lip: #8a5570; --curtain: #8a1f2f; --light-rail: #c9b6ee;
  --bulb-off: .28;
  --outline: #fff4dd;
  --beam-0: rgba(255,240,190,0); --beam-1: rgba(255,238,175,.48); --beam-2: rgba(255,234,150,.72);
  --beam-pool: rgba(255,244,200,.9);
  --panel-bg: rgba(0,0,0,.42); --panel-edge: rgba(255,255,255,.25);
  --panel-line: rgba(255,255,255,.18); --panel-mark: rgba(255,255,255,.35);
  --chart-hit: #7fb6ee; --chart-other: #46527f;
  --accent-soft: #33436f; --accent-ink: #dbe8ff;
}

/* scene backdrops read their palette from the tokens */
.backdrop .sky1 { stop-color: var(--scene-sky-1); }
.backdrop .sky2 { stop-color: var(--scene-sky-2); }
.backdrop .sky3 { stop-color: var(--scene-sky-3); }
.backdrop .grd1 { stop-color: var(--scene-grd-1); }
.backdrop .grd2 { stop-color: var(--scene-grd-2); }
.backdrop .ground-lip { fill: var(--ground-lip); }
.backdrop .stars, .backdrop .moon { opacity: var(--night); }
.backdrop .day-clouds, .backdrop .sun, .backdrop .sun-glow { opacity: var(--day); }
.backdrop .sun-glow { opacity: calc(var(--day) * .3); }
.backdrop .curtain     { fill: var(--curtain); }
.backdrop .stage-back  { fill: var(--stage-back); }
.backdrop .stage-lip   { fill: var(--stage-lip); }
.backdrop .light-rail  { stroke: var(--light-rail); }
.backdrop .sheet-panel { fill: var(--panel-bg); stroke: var(--panel-edge); }
.backdrop .staff-line  { stroke: var(--panel-line); }
.backdrop .note-head   { stroke: var(--panel-mark); }
.backdrop .note-stem   { fill: var(--panel-mark); }
/* The backdrop brightness does NOT change with game state. Dimming on ready
   and blazing on celebrate read as the screen misbehaving, and the real
   feedback (the pop, the highlight, the confetti, the chip) already carries it. */

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body {
  margin: 0; padding: 0; height: 100%;
  font-family: 'Nunito', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  font-weight: 800;
  background: var(--sky-1); color: var(--ink-2);
  overflow: hidden; user-select: none;
}
h1, h2, h3, .brand h1, .ac-name, .shelf-head h2, .rh-text h2, .page-title, .big-btn {
  font-family: 'Fredoka One', 'Nunito', sans-serif; font-weight: 400; letter-spacing: .2px;
}
.screen { display: none; height: 100%; padding: 20px; flex-direction: column; overflow-y: auto; background: var(--surface-3); }
.screen.themed {
  background: linear-gradient(var(--sky-1) 0%, var(--sky-2) 45%, var(--sky-3) 100%);
  color: var(--ink-2); padding: 16px clamp(16px, 4vw, 44px) 30px;
}
.screen.themed > * { position: relative; z-index: 1; }

/* the park behind everything — selector must out-rank .screen.themed > * */
.screen.themed > .park-scenery { position: fixed; inset: 0; z-index: 0; pointer-events: none; }
.park-scenery svg { width: 100%; height: 100%; }
.park-scenery.low svg { opacity: .7; }
/* Day and night are both drawn; the tokens decide which is visible. */
.park-sun       { fill: #FFD93D; opacity: var(--day); }
.park-sun-glow  { fill: #FFD93D; opacity: calc(var(--day) * .25); }
.park-moon      { fill: #F3F0D8; opacity: var(--night); }
.park-clouds    { fill: #ffffff; opacity: calc(var(--day) * .9); }
.park-stars     { fill: #ffffff; opacity: calc(var(--night) * .85); }
.park-hill-1    { fill: var(--hill-1); }
.park-hill-2    { fill: var(--hill-2); }

.brand { display: flex; align-items: center; gap: 12px; }
.brand-mark {
  width: 52px; height: 52px; border-radius: 18px; display: grid; place-items: center;
  background: var(--surface); font-size: 1.5rem; box-shadow: 0 4px 0 var(--edge);
}
/* Shrinks rather than wraps: on a phone the wrapped second line crowded the
   chip below it and slid behind the header buttons. */
.brand h1 { margin: 0; font-size: clamp(1.25rem, 4.5vw, 2.3rem); color: var(--ink); white-space: nowrap; }
.home-sub { margin: 2px 0 18px 64px; color: var(--muted); font-size: 1.02rem; }

/* ---- level shelves ---- */
.shelf { margin-bottom: 22px; }
.shelf-head { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }
.shelf-badge {
  width: 46px; height: 46px; border-radius: 50%; display: grid; place-items: center;
  background: var(--lv); font-size: 1.25rem; box-shadow: 0 4px 0 var(--lvd);
}
.shelf-titles h2 { margin: 0; font-size: clamp(1.15rem, 2.6vw, 1.5rem); color: var(--ink); }
.shelf-titles p { margin: 0; font-size: .9rem; color: var(--muted); }
/* One scrolling row per shelf, on a phone exactly as on a tablet. Wrapping
   into a grid meant a phone (one card fitting per row) and a tablet (three
   abreast) showed the same shelf as two different shapes: a short wide strip
   on one, a long column on the other. A row that scrolls sideways instead
   keeps every shelf the same shape everywhere — it only ever gets longer to
   scroll through, never taller to stack through. */
.shelf-cards {
  display: flex; gap: 16px; flex-wrap: nowrap;
  overflow-x: auto; overflow-y: hidden; scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch; padding-bottom: 6px; margin-bottom: -6px;
}

.activity-card {
  flex: 0 0 240px; scroll-snap-align: start;
  display: flex; flex-direction: column; align-items: stretch; text-align: left;
  background: var(--surface); border: none; border-radius: 24px; padding: 0 0 16px; cursor: pointer;
  box-shadow: 0 6px 0 var(--lvd), 0 14px 26px var(--drop);
  transition: transform .12s ease, box-shadow .12s ease;
}
.activity-card:hover { transform: translateY(-3px); box-shadow: 0 9px 0 var(--lvd), 0 18px 30px var(--drop); }
.activity-card:active { transform: translateY(3px); box-shadow: 0 2px 0 var(--lvd), 0 8px 16px var(--drop); }
.ac-art {
  display: block; height: 118px; border-radius: 24px 24px 0 0; overflow: hidden; position: relative;
  background: linear-gradient(160deg, color-mix(in srgb, var(--lv) 26%, var(--surface)), color-mix(in srgb, var(--lv) 8%, var(--surface)));
}
.ac-name { font-family: 'Fredoka One', sans-serif; font-size: 1.28rem; color: var(--ink); margin: 12px 16px 2px; }
.ac-tag { font-size: .92rem; color: var(--muted); margin: 0 16px; }

/* ---- card art vignettes ---- */
.ga { position: absolute; inset: 0; }
.ga-char svg, .ga-inst svg, .ga-b svg, .ga-food svg { width: 100%; height: auto; display: block; }
.peekaboo .ga-crate { position: absolute; left: 50%; bottom: -8px; width: 120px; transform: translateX(-50%); }
.peekaboo .pk-char { position: absolute; left: 50%; bottom: 44px; width: 74px; transform: translateX(-50%); animation: pkBob 2.4s ease-in-out infinite; }
@keyframes pkBob { 0%,100% { transform: translateX(-50%) translateY(10px) } 50% { transform: translateX(-50%) translateY(-4px) } }
.balloonart .b1 { position: absolute; left: 28%; bottom: 6px; width: 74px; animation: pkBob 3s ease-in-out infinite; }
.balloonart .b2 { position: absolute; left: 56%; bottom: 20px; width: 52px; animation: pkBob 3.6s ease-in-out infinite reverse; }
.musicart .i1 { position: absolute; left: 16%; bottom: 4px; width: 78px; }
.musicart .i2 { position: absolute; left: 55%; bottom: 12px; width: 66px; }
.ga-note { position: absolute; font-size: 1.5rem; color: #29355A; opacity: .75; }
.musicart .n1 { left: 46%; top: 12px; animation: pkBob 2.2s ease-in-out infinite; }
.musicart .n2 { left: 80%; top: 26px; animation: pkBob 2.8s ease-in-out infinite reverse; }
.catchart .catch-char { position: absolute; left: 50%; bottom: 2px; width: 84px; transform: translateX(-50%); animation: pkBob 2.2s ease-in-out infinite; }
.ga-burst {
  position: absolute; left: 50%; top: 52%; width: 108px; height: 108px; transform: translate(-50%,-50%);
  background: radial-gradient(circle, rgba(255,217,61,.85), rgba(255,217,61,0) 65%);
  border-radius: 50%;
}
.beatart .beat-inst { position: absolute; left: 50%; bottom: 6px; width: 86px; transform: translateX(-50%); }
.ga-ring { position: absolute; left: 50%; top: 46%; border: 3px solid rgba(41,53,90,.3); border-radius: 50%; transform: translate(-50%,-50%); }
.ga-ring.r1 { width: 74px; height: 74px; animation: ringGrow 1.6s ease-out infinite; }
.ga-ring.r2 { width: 74px; height: 74px; animation: ringGrow 1.6s ease-out .8s infinite; }
@keyframes ringGrow { 0% { width: 60px; height: 60px; opacity: .8 } 100% { width: 120px; height: 120px; opacity: 0 } }
.spotart { background: #2f2a55; }
.ac-art:has(.spotart) { background: #2f2a55; }
.ga-beam {
  position: absolute; left: 50%; top: -8px; width: 96px; height: 130px; transform: translateX(-50%);
  background: linear-gradient(to bottom, rgba(255,236,170,.1), rgba(255,236,170,.5));
  clip-path: polygon(36% 0, 64% 0, 100% 100%, 0 100%);
}
.spotart .spot-char { position: absolute; left: 50%; bottom: 0; width: 76px; transform: translateX(-50%); }
.screen.active { display: flex; }
#screen-game { padding: 0; background: #20204a; }

h1 { font-size: 2rem; margin: 10px 0 0; text-align: center; }
h2 { font-size: 1.4rem; margin: 10px 0; }
.subtitle { text-align: center; color: var(--muted); margin-top: 4px; }

.top-bar { display: flex; align-items: center; justify-content: space-between; font-size: 1.1rem; font-weight: 600; margin-bottom: 10px; }
#screen-game .top-bar { position: absolute; top: 0; left: 0; right: 0; z-index: 6; color: #efe6ff; padding: 10px 16px; margin: 0; pointer-events: none; }
#screen-game .top-bar > span { background: rgba(26,20,48,.55); padding: 4px 12px; border-radius: 12px; backdrop-filter: blur(3px); }
.phase-label { font-weight: 400; opacity: .7; font-size: .85rem; }
.icon-btn { font-size: 1.4rem; background: none; border: none; padding: 8px 14px; border-radius: 12px; cursor: pointer; }
.icon-btn:active { background: #eee; }
.big-btn { font-size: 1.3rem; padding: 18px; border-radius: 20px; border: none; background: #4a90d9; color: #fff; margin: 14px 0; cursor: pointer; min-height: 64px; }
.big-btn:active { background: #357abd; }
.link-btn { background: none; border: none; color: #4a90d9; font-size: 1rem; padding: 10px; cursor: pointer; text-decoration: underline; }

.profile-list { display: flex; flex-direction: column; gap: 12px; margin-top: 20px; }
.profile-card { font-size: 1.3rem; padding: 22px; border-radius: 20px; background: #ffe3b3; border: none; cursor: pointer; text-align: left; display: flex; justify-content: space-between; align-items: center; }
.profile-card small { color: var(--muted-2); font-size: .9rem; }

.mode-list { display: flex; flex-direction: column; gap: 14px; }
.mode-card { padding: 20px; border-radius: 20px; border: none; cursor: pointer; text-align: left; background: #d9ecff; font-size: 1.1rem; }
.mode-card b { display: block; font-size: 1.3rem; }
.mode-card .alp-target { color: var(--muted-2); font-size: .85rem; float: right; }
.hint { color: var(--muted-2); font-size: .95rem; text-align: center; margin-top: 6px; }
.promo-banner { background: #fff3cd; border-radius: 16px; padding: 14px; margin-top: 10px; font-size: .95rem; }

/* ============================ THE CARNIVAL ============================ */

.game-root { flex: 1; position: relative; overflow: hidden; }
.scene { position: absolute; inset: 0; overflow: hidden; }
/* The scenes FIT the screen rather than cropping to fill it (see the BLEED note
   in art.js), which leaves bare strips wherever the screen is a different shape
   from the 1000x560 world. The sky, ground and curtains are drawn past the edge
   of that world to cover those strips, and an SVG clips to its viewBox unless
   told otherwise — so this is what actually lets the bleed show. `.scene` above
   still clips it, so nothing escapes into the rest of the screen. */
.backdrop { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }

.scene .backdrop { transition: filter .35s ease; }
.scene.flash::after {
  content: ''; position: absolute; inset: 0; background: #fff6d0;
  animation: flashfade .55s ease forwards; pointer-events: none; z-index: 4;
}
@keyframes flashfade { 0% { opacity: .55 } 100% { opacity: 0 } }

/* The carnival is STILL until the child makes something happen. Idle motion
   competes with the target for attention and blurs the cause-effect link —
   twinkle, flutter and chasing bulbs are rewards, not wallpaper. */
.star { opacity: .5; }
.scene.party .star { animation: twinkle 1.4s ease-in-out infinite; animation-delay: var(--d, 0s); }
@keyframes twinkle { 0%,100% { opacity: .35 } 50% { opacity: 1 } }

/* ferris wheel: still by default, spins on the 'ferris' attraction */
.wheel-spin { }
.scene.party .wheel-spin { animation: spin 6s linear infinite; }
.scene.party .cabin { animation: counterspin 6s linear infinite; }
@keyframes spin { to { transform: rotate(360deg) } }
@keyframes counterspin { to { transform: rotate(-360deg) } }

/* an animal taking its seat */
.cabin .seat { transition: fill .3s ease, stroke .3s ease; }
.cabin.filled .seat { fill: #6b5590; stroke: #ffd93d; }
.cabin.just-filled { animation: seatIn .85s cubic-bezier(.34,1.56,.64,1); }
@keyframes seatIn {
  0% { transform: scale(0) rotate(-40deg) } 60% { transform: scale(1.35) rotate(8deg) } 100% { transform: scale(1) rotate(0) }
}
.cabin .rider { pointer-events: none; }
/* an unlit bulb must still read as a bulb — in daylight low opacity
   makes it vanish into the sky, so day gets a warm off-state instead. */
.wheel-bulb { opacity: var(--bulb-off, .28); }
.cabin.filled .wheel-bulb { opacity: .9; }
.scene.party .wheel-bulb { opacity: 1; animation: bulb .6s ease-in-out infinite; animation-delay: var(--d, 0s); }
@keyframes bulb { 0%,100% { opacity: .4; r: 8 } 50% { opacity: 1; r: 11 } }

/* bunting: still by default, flies during a celebration */
.flag { transform-origin: top center; }
.tent-flag { transform-origin: left center; }
.scene.party .flag { animation: flutter 1.4s ease-in-out infinite; animation-delay: var(--d, 0s); }
.scene.party .tent-flag { animation: flutter 1s ease-in-out infinite; }
@keyframes flutter { 0%,100% { transform: rotate(-4deg) } 50% { transform: rotate(6deg) } }
.scene.party .tent { animation: tentglow 1.6s ease-in-out infinite; }
@keyframes tentglow { 0%,100% { filter: none } 50% { filter: drop-shadow(0 0 26px #ffd93d) brightness(1.25) } }

/* ---- booths / items row ---- */
.stage-row {
  position: absolute; left: 0; right: 0; bottom: 8%;
  display: flex; align-items: flex-end; justify-content: center;
  gap: clamp(10px, 3vw, 42px); padding: 0 3vw; z-index: 3;
}
.stage-row.pop-stage { bottom: 12%; }

.booth {
  position: relative; width: clamp(96px, 17vw, 190px); aspect-ratio: 6/7;
  display: flex; align-items: flex-end; justify-content: center;
  transition: transform .22s cubic-bezier(.34,1.56,.64,1), filter .22s ease;
  /* unlit booths stay warm and inviting — dimmed enough that the scan
     highlight is unmistakable, not so much that they read as "switched off" */
  filter: brightness(.8) saturate(.92);
}
.booth-svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.booth-item {
  position: relative; z-index: 2; width: 68%; margin-bottom: 16%;
  transition: transform .3s cubic-bezier(.34,1.56,.64,1);
}
.booth-item .char { width: 100%; height: auto; display: block; filter: drop-shadow(0 3px 5px rgba(60,40,90,.35)); }

/* the scan highlight: size-grow + high-contrast ring + warm spotlight.
   Deliberately multi-channel (size AND colour AND light) for CVI support. */
.spotlight {
  position: absolute; inset: -14% -10% -6%; border-radius: 26px;
  background: radial-gradient(ellipse at 50% 55%, rgba(255,233,168,.55), rgba(255,233,168,0) 68%);
  opacity: 0; transition: opacity .18s ease; pointer-events: none;
}
.booth.lit { filter: none; transform: translateY(-14px) scale(1.14); }
.booth.lit .spotlight { opacity: 1; }
.booth.lit .booth-svg { filter: drop-shadow(0 0 0 #fff); }
.booth.lit::before {
  content: ''; position: absolute; inset: -10px; border-radius: 24px;
  border: 6px solid #ffe36e; box-shadow: 0 0 26px #ffd93d, inset 0 0 18px rgba(255,217,61,.4);
  animation: ringpulse 1s ease-in-out infinite;
}
@keyframes ringpulse { 0%,100% { opacity: .85 } 50% { opacity: 1 } }

/* Pop!: character hides behind the counter, then pops up */
/* The animal was parked at the bottom of the stall at a quarter opacity — a
   ghost of it sat on screen the whole time, which gives the answer away and
   turns "press when it appears" into "press when the faint thing gets darker".
   It is not on screen at all until it pops. */
.pop-stage .booth .booth-item { transform: translateY(52%) scale(.9); opacity: 0; visibility: hidden; }
.pop-stage .booth.popped .booth-item { transform: translateY(-6%) scale(1); opacity: 1; visibility: visible;
  animation: popup .45s cubic-bezier(.34,1.56,.64,1); }
@keyframes popup { 0% { transform: translateY(52%) scale(.8) } 60% { transform: translateY(-14%) scale(1.08) } 100% { transform: translateY(-6%) scale(1) } }
/* The shake fills the build-up, whatever the build-up is. It used to be a
   fixed .35s x 3 = 1050ms against a build-up that happened to be 1000ms — so
   the two agreed only by coincidence, and once the build-up became a fraction
   of the speed setting the stall either stopped rustling and sat still before
   opening (Slow) or was cut off mid-shake (Fast). The game sets --rustle to
   the length of the build-up; three shakes still fit it exactly. */
.pop-stage .booth.rustle { animation: rustle calc(var(--rustle, 1050ms) / 3) ease-in-out 3; }
@keyframes rustle { 0%,100% { transform: translateX(0) } 25% { transform: translateX(-4px) rotate(-1deg) } 75% { transform: translateX(4px) rotate(1deg) } }

/* success + gentle re-cue */
.booth.cheer { animation: cheer .7s cubic-bezier(.34,1.56,.64,1); }
@keyframes cheer {
  0% { transform: translateY(-14px) scale(1.14) }
  30% { transform: translateY(-46px) scale(1.28) rotate(-5deg) }
  55% { transform: translateY(-6px) scale(1.1) rotate(4deg) }
  100% { transform: translateY(-14px) scale(1.14) rotate(0) }
}
.booth.wobble { animation: wobble .5s ease; }
@keyframes wobble { 0%,100% { transform: rotate(0) } 25% { transform: rotate(-4deg) } 75% { transform: rotate(4deg) } }

/* character idle breathing so the world never feels dead */
.booth-item .char .head { transform-origin: 51px 60px; animation: breathe 3.4s ease-in-out infinite; }
@keyframes breathe { 0%,100% { transform: scale(1) } 50% { transform: scale(1.035) } }
.booth-item .char .eyes { animation: blink 5.5s infinite; transform-origin: center; }
@keyframes blink { 0%,96%,100% { transform: scaleY(1) } 98% { transform: scaleY(.1) } }
.booth.lit .char { animation: bob .9s ease-in-out infinite; }
@keyframes bob { 0%,100% { transform: translateY(0) } 50% { transform: translateY(-7px) } }
@keyframes flap { 0%,100% { transform: scaleY(1) } 50% { transform: scaleY(.4) } }

/* ---- prompt bubble: shows the child WHAT to look for ---- */
.prompt-bubble {
  position: absolute; top: 9%; left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: 12px; z-index: 5;
  background: rgba(255,251,240,.96); border-radius: 22px;
  padding: 10px 22px 10px 12px; box-shadow: 0 8px 24px rgba(0,0,0,.35);
  max-width: 88%; border: 4px solid #ffd93d;
}
.prompt-bubble::after {
  content: ''; position: absolute; bottom: -16px; left: 50%; transform: translateX(-50%);
  border: 10px solid transparent; border-top-color: #ffd93d;
}
.prompt-icon { width: clamp(44px, 7vw, 64px); flex: 0 0 auto; }
.prompt-icon .char { width: 100%; height: auto; display: block; }
.prompt-text { font-size: clamp(1rem, 2.4vw, 1.5rem); font-weight: 700; color: #3a2d51; }
.prompt-text b { color: #c94a34; }
.prompt-bubble.pop-in { animation: promptin .45s cubic-bezier(.34,1.56,.64,1); }
@keyframes promptin { 0% { transform: translateX(-50%) scale(.7); opacity: 0 } 100% { transform: translateX(-50%) scale(1); opacity: 1 } }

/* "Ask first, then search": the SAME bubble starts big in the middle of the
   screen, holds while the child takes in the animal and hears it, then travels
   up to its corner. One element moving reads as one idea; two elements swapping
   reads as a glitch. */
.prompt-bubble {
  transition: top .55s cubic-bezier(.34,1.56,.64,1),
              transform .55s cubic-bezier(.34,1.56,.64,1),
              padding .45s ease, box-shadow .45s ease;
}
.prompt-bubble.ask {
  top: 42%; transform: translate(-50%, -50%) scale(1.5);
  box-shadow: 0 16px 50px rgba(41,53,90,.4);
}
.prompt-bubble.ask::after { opacity: 0; }
/* the pop-in animation and the travel both drive transform — let the travel win */
.prompt-bubble.ask.pop-in { animation: askin .5s cubic-bezier(.34,1.56,.64,1); }
@keyframes askin {
  0%   { transform: translate(-50%, -50%) scale(.6); opacity: 0 }
  100% { transform: translate(-50%, -50%) scale(1.5); opacity: 1 }
}

/* A reminder, not a new question: the bubble stays where it is and pulses,
   so nothing about the screen suggests the animal has changed. */
.prompt-bubble.reask { animation: reask 1.3s ease; }
@keyframes reask {
  0%, 100% { transform: translateX(-50%) scale(1); }
  30%, 60% { transform: translateX(-50%) scale(1.12); }
}
@media (prefers-reduced-motion: reduce) { .prompt-bubble.reask { animation: none; } }

/* The Band's goal display lives at the TOP of the scene, which is exactly
   where the bubble sits by default — the message about the music was covering
   the notes lighting up to play it. There is open ground below the stage. */
.game-band .prompt-bubble { top: auto; bottom: 12%; }
/* same reason — the start-of-session hint also lands on the song sheet.
   #game-hint is a sibling of .scene, not a child, so it is tagged on the screen. */
#screen-game[data-game="band"] .game-hint { top: auto; bottom: 22%; }
.game-band .prompt-bubble::after { bottom: auto; top: -16px; border-top-color: transparent; border-bottom-color: #ffd93d; }

/* ---- wait cue (Pop!) ---- */
.wait-cue {
  position: absolute; bottom: 4%; left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: 10px; z-index: 5;
  font-size: 1.1rem; color: #efe6ff; background: rgba(0,0,0,.35);
  padding: 8px 18px; border-radius: 20px;
}
.wait-dots { display: inline-flex; gap: 5px; }
.wait-dots i { width: 8px; height: 8px; border-radius: 50%; background: #ffd93d; animation: dots 1.2s ease-in-out infinite; }
.wait-dots i:nth-child(2) { animation-delay: .2s }
.wait-dots i:nth-child(3) { animation-delay: .4s }
@keyframes dots { 0%,100% { opacity: .3; transform: translateY(0) } 50% { opacity: 1; transform: translateY(-5px) } }

/* On the Beat: the dots are a COUNT-IN, not decoration. They light one at a
   time across the gap, the third landing on the beat — the pulse made visible
   before it is audible. Catch Me rustles the stall for the same reason. */
.game-band .wait-dots i {
  animation: none; opacity: .25; transform: scale(.8);
  transition: opacity .1s ease, transform .1s cubic-bezier(.34,1.56,.64,1);
}
.game-band .wait-dots i.on { opacity: 1; transform: scale(1.4); }
/* THE BEAT IS A CONTINUOUS BOB.
   A beat is by definition continuous; an earlier version had a gap of dead air
   with a shrinking box to fill it, which read as a UI element rather than
   music. The instrument now falls into the beat and rises back out of it, and
   the previous rise is the count-in for the next fall.

   Two animations, not one, because the halves have different lengths — the
   lead-in and the response window come from Catch Me's timing settings. That
   asymmetry is useful: while the window is open the instrument is rising, so
   how far it has risen IS how much time is left. */
.game-band.mode-beat .booth.falling {
  animation: beatFall var(--fall, 2200ms) cubic-bezier(.5,0,.85,.4) forwards;
}
.game-band.mode-beat .booth.rising {
  animation: beatRise var(--rise, 5000ms) cubic-bezier(.2,.6,.4,1) forwards;
}
@keyframes beatFall { from { transform: translateY(-14%) } to { transform: translateY(0) } }
@keyframes beatRise { from { transform: translateY(0) }     to { transform: translateY(-14%) } }
/* the bob is the cue, so the worded one is redundant */
.mode-beat .wait-cue { display: none !important; }
@media (prefers-reduced-motion: reduce) {
  .game-band.mode-beat .booth { animation: none !important; }
}

/* ---- progress + stars ---- */
.progress-wrap { position: absolute; top: 9%; right: 3%; z-index: 5; flex-direction: column; align-items: flex-end; gap: 6px; }
.progress-bar { width: clamp(90px, 18vw, 190px); height: 14px; border-radius: 10px; background: rgba(0,0,0,.35); overflow: hidden; border: 2px solid rgba(255,255,255,.35); }
.progress-fill { height: 100%; width: 0; border-radius: 8px; background: linear-gradient(90deg,#ffd93d,#4ec3a7); transition: width .5s cubic-bezier(.34,1.56,.64,1); }
.star-tray { display: flex; gap: 2px; flex-wrap: wrap; justify-content: flex-end; max-width: 200px; }
.earned-star { color: #ffd93d; font-size: 1.2rem; font-style: normal; text-shadow: 0 2px 4px rgba(0,0,0,.5); animation: starin .5s cubic-bezier(.34,1.56,.64,1); }
@keyframes starin { 0% { transform: scale(0) rotate(-180deg) } 100% { transform: scale(1) rotate(0) } }

/* Calm mode is gone. It did two things: it stopped the decorative animation,
   and it hid the confetti and the celebration balloon. Both are now what
   `prefers-reduced-motion` does — the tablet's own accessibility switch, which
   a family has usually already set — so the app was asking a second time for
   something the device already knew. Quieter celebration audio was the one
   thing it did that nothing else does; Sound off covers the need, if less
   finely. See the reduced-motion blocks below for the behaviour that stayed. */

/* ---- particles ---- */
.fx-layer { position: absolute; inset: 0; pointer-events: none; z-index: 4; overflow: hidden; }
.particle {
  position: absolute; width: 11px; height: 14px; border-radius: 2px;
  animation: burst 1.2s cubic-bezier(.15,.6,.4,1) forwards; animation-delay: var(--d, 0s);
}
.particle.spark { width: 7px; height: 7px; border-radius: 50%; }
@keyframes burst {
  0% { transform: translate(0,0) rotate(0) scale(.4); opacity: 1 }
  70% { opacity: 1 }
  100% { transform: translate(var(--tx), calc(var(--ty) + 130px)) rotate(var(--rot)) scale(1); opacity: 0 }
}
.balloon {
  position: absolute; bottom: -60px; width: 34px; height: 42px; border-radius: 50% 50% 50% 50%/45% 45% 60% 60%;
  animation: rise 3.2s ease-in forwards; animation-delay: var(--d, 0s);
}
.balloon::after { content: ''; position: absolute; top: 100%; left: 50%; width: 1px; height: 26px; background: rgba(255,255,255,.5) }
@keyframes rise { 0% { transform: translateY(0) translateX(0); opacity: 0 } 12% { opacity: 1 } 100% { transform: translateY(-115vh) translateX(var(--sway)); opacity: .9 } }

@media (prefers-reduced-motion: reduce) {
  .char .head, .booth.lit .char, .flag, .star, .wheel-bulb { animation: none !important }
}

/* Finish is the ONLY way out of a game, so it is a real button in the house
   style — surface, pressable edge, depresses on :active — not a ghost chip. */
.end-btn {
  position: absolute; bottom: 14px; right: 16px; z-index: 6;
  display: flex; align-items: center; gap: 8px;
  min-height: 46px; padding: 10px 20px;
  font-family: 'Fredoka One', 'Nunito', sans-serif; font-weight: 400;
  font-size: 1rem; color: var(--ink); background: var(--surface);
  border: none; border-radius: 18px; cursor: pointer;
  box-shadow: 0 5px 0 var(--edge), 0 10px 20px var(--drop);
  transition: transform .12s ease, box-shadow .12s ease;
}
.end-btn::before { content: '✓'; font-size: 1.05rem; color: #3fb56b; }
.end-btn:hover  { transform: translateY(-2px); box-shadow: 0 7px 0 var(--edge), 0 14px 24px var(--drop); }
.end-btn:active { transform: translateY(3px); box-shadow: 0 2px 0 var(--edge), 0 6px 12px var(--drop); }

/* ---- the round flag: "we are starting again" ----
   Big, centred, and STILL — it has to be readable at a glance by an adult and
   unmissable to a child, which a small animated toast is not. */
.round-flag {
  position: absolute; left: 50%; top: 42%; z-index: 7;
  transform: translate(-50%, -50%) scale(.7);
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 20px 40px; border-radius: 26px;
  background: var(--surface); box-shadow: 0 8px 0 var(--edge), 0 20px 40px var(--drop);
  opacity: 0; pointer-events: none;
  transition: opacity .25s ease, transform .35s cubic-bezier(.34,1.56,.64,1);
}
.round-flag.show { opacity: 1; transform: translate(-50%, -50%) scale(1); }
.rf-again {
  font-family: 'Fredoka One', 'Nunito', sans-serif; font-weight: 400;
  font-size: clamp(1.5rem, 4vw, 2.1rem); color: var(--ink); white-space: nowrap;
}
.rf-round { font-size: .95rem; color: var(--muted-2); letter-spacing: .4px; }

/* the wheel unloading, one cabin at a time */
.cabin.emptying .rider { transition: opacity .25s ease, transform .25s ease; opacity: 0; transform: translateY(8px); }
@media (prefers-reduced-motion: reduce) {
  .round-flag, .round-flag.show { transform: translate(-50%, -50%); transition: opacity .2s ease; }
}

/* ============================ REPORTS ============================ */

.report { text-align: left; }
.report-header { font-size: 1.6rem; margin: 10px 0; }
.report-mode { color: var(--muted); margin-bottom: 14px; }
.stat-row { display: flex; gap: 10px; justify-content: center; margin-bottom: 16px; }
.stat-tile { background: var(--surface); border-radius: 16px; padding: 14px 10px; flex: 1; box-shadow: 0 2px 6px rgba(0,0,0,.08); }
.stat-num { font-size: 1.6rem; font-weight: 700; color: #4a90d9; }
.stat-label { font-size: .85rem; color: var(--muted-2); }
.guidance-line { font-size: 1.05rem; margin: 10px 0; color: var(--ink); }
.report-actions { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; margin: 18px 0 10px; }
.report-actions button {
  padding: 12px 18px; border-radius: 14px; cursor: pointer;
  border: 1.5px solid var(--line); background: var(--surface); color: var(--ink-2);
  font-family: inherit; font-size: .95rem; font-weight: 700;
}
.report-actions button:first-child {
  border-color: transparent; background: var(--accent-soft); color: var(--accent-ink);
}

.details-report h3 { margin-bottom: 4px; font-size: 1.05rem; }
.details-report ul { margin: 4px 0 12px; padding-left: 18px; }
.coverage { font-size: .7rem; padding: 2px 8px; border-radius: 10px; margin-left: 6px; }
.coverage.strong { background: #d4f7dc; color: #14803c; }
.coverage.moderate { background: #fff3cd; color: #8a6d00; }
.coverage.proxy { background: #ffe3cc; color: #a35400; }
.coverage.none { background: rgb(var(--on) / .07); color: var(--muted-2); }
.alp-band { background: #f4f0ff; padding: 12px; border-radius: 12px; }
.caveat { font-size: .8rem; color: var(--muted-2); }
.promo-nudge { background: #fff3cd; padding: 10px; border-radius: 12px; }

.settings-body { display: flex; flex-direction: column; gap: 16px; padding-bottom: 24px; }
.settings-group {
  background: var(--surface); border-radius: 22px; padding: 18px 20px;
  box-shadow: 0 4px 0 var(--edge-soft), 0 10px 22px var(--drop);
}
.settings-group h4 {
  margin: 0 0 6px; font-family: 'Fredoka One', sans-serif; font-weight: 400;
  font-size: 1.2rem; color: var(--ink);
}
.settings-group .step-note { color: var(--muted-2); font-size: .9rem; margin: 0 0 12px; line-height: 1.45; }
.settings-group a { color: #3372b5; font-weight: 800; }

/* ---- switch presets ---- */
.chip-grid { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 6px; }
.chip {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 12px 16px; border-radius: 16px; border: 2px solid var(--line);
  background: var(--surface-3); color: var(--muted); font-size: .98rem; font-weight: 800;
  font-family: inherit; cursor: pointer;
}
.chip .chip-icon { font-size: 1.1rem; }
.chip:hover { border-color: var(--muted-2); }
.chip.on { background: var(--pill); border-color: var(--pill); color: #fff; }
.chip.chip-capture { border-style: dashed; }
.chip.listening { background: #FFD93D; border-color: #d9b32a; color: #6b4d00; }

/* ---- stepper ---- */
.stepper-row {
  display: flex; align-items: center; justify-content: space-between; gap: 18px;
  padding: 14px 0; border-top: 2px solid var(--line-2);
}
.stepper-row:first-of-type { border-top: none; }
.stepper-text { display: flex; flex-direction: column; }
.stepper-text b { color: var(--ink); font-size: 1.02rem; }
.stepper-text small { color: var(--muted-2); font-size: .86rem; margin-top: 2px; line-height: 1.4; }
.stepper {
  display: flex; align-items: center; gap: 4px; flex: 0 0 auto;
  background: var(--surface-2); border: 2px solid var(--line); border-radius: 16px; padding: 4px;
}
.st-btn {
  width: 42px; height: 42px; border: none; border-radius: 12px; cursor: pointer;
  background: var(--surface); color: #4a90d9; font-size: 1.4rem; font-weight: 800; line-height: 1;
  box-shadow: 0 2px 0 var(--edge-soft);
}
.st-btn:active { transform: translateY(2px); box-shadow: none; }
.st-val {
  min-width: 74px; text-align: center; font-weight: 800; color: var(--ink); font-size: 1rem;
}
.ctrl-block.plain { background: none; box-shadow: none; padding: 0; }
.settings-row { display: flex; justify-content: space-between; align-items: center; margin: 8px 0; gap: 10px; }
.settings-row label { flex: 1; font-size: .95rem; }
.settings-row input[type=range] { flex: 1; }
.settings-row select, .settings-row input[type=text] { padding: 6px; border-radius: 8px; border: 1px solid #ccc; }

.overlay {
  position: fixed; inset: 0; z-index: 50;
  background: rgba(23,36,66,.55); backdrop-filter: blur(6px);
  display: flex; align-items: center; justify-content: center; padding: 24px;
  animation: overlayIn .18s ease;
}
@keyframes overlayIn { from { opacity: 0 } to { opacity: 1 } }

/* flex column + align-items centres the button; text-align could not, because
   the hold button is a grid container and therefore block-level */
.gate-box {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  background: var(--surface); padding: 30px 34px 22px; border-radius: 30px;
  width: min(360px, 100%);
  box-shadow: 0 8px 0 var(--line), 0 24px 50px rgba(10,16,34,.4);
  animation: gateIn .22s cubic-bezier(.34,1.5,.64,1);
}
@keyframes gateIn { from { transform: translateY(14px) scale(.96); opacity: 0 } to { transform: none; opacity: 1 } }
.gate-lock {
  width: 60px; height: 60px; border-radius: 50%; display: grid; place-items: center;
  background: var(--surface-2); font-size: 1.6rem; margin-bottom: 12px;
}

#new-profile-name { width: 100%; padding: 10px; font-size: 1.1rem; margin: 10px 0; border-radius: 10px; border: 1px solid var(--line); }
#history-body .session-row { background: var(--surface); border-radius: 14px; padding: 14px; margin-bottom: 10px; display: flex; justify-content: space-between; cursor: pointer; }

/* landscape phones / small tablets */
@media (max-height: 480px) {
  .stage-row { bottom: 4%; }
  .prompt-bubble { top: 4%; padding: 6px 16px 6px 8px; }
}

/* =================================================================
   MODE-SPECIFIC STAGING — the four games must not look alike
   ================================================================= */

/* ---- Explore: no stall, no highlight. One big character, whole world reacts. */
.mode-explore .booth-svg, .mode-warmup .booth-svg,
.mode-explore .spotlight, .mode-warmup .spotlight { display: none; }
.mode-explore .stage-row, .mode-warmup .stage-row { bottom: 6%; }
.mode-explore .booth, .mode-warmup .booth {
  width: clamp(150px, 26vw, 290px); filter: none; aspect-ratio: 1/1;
}
.mode-explore .booth-item, .mode-warmup .booth-item { width: 100%; margin-bottom: 0; }
.mode-explore .booth.lit, .mode-warmup .booth.lit { transform: none; }
.crate { display: none; }
.mode-explore .crate, .mode-warmup .crate {
  display: block; position: absolute; left: 50%; bottom: 6%;
  transform: translateX(-50%); width: clamp(160px, 27vw, 300px); z-index: 4; pointer-events: none;
}
.crate-svg { width: 100%; height: auto; display: block; filter: drop-shadow(0 6px 12px rgba(0,0,0,.45)); }
.mode-explore .stage-row, .mode-warmup .stage-row { bottom: 9%; z-index: 3; }
/* Same in the crate: the animal used to sit below it, in full view, waiting.
   Nothing of it shows until the press — so it reads as coming OUT of the box
   rather than rising up from behind it. */
.mode-explore .booth-item, .mode-warmup .booth-item {
  transform: translateY(58%) scale(.7); opacity: 1;
  transition: transform .35s cubic-bezier(.34,1.56,.64,1), opacity .2s ease;
}
/* Scoped to the carnival on purpose. `mode-explore` is shared with Balloon Pop,
   whose balloon lives in the same .booth-item and is never `.popped` — it
   inflates. Hiding on the mode alone made the balloon vanish for the whole
   game. */
.game-carnival.mode-explore .booth-item, .game-carnival.mode-warmup .booth-item {
  opacity: 0; visibility: hidden;
}
.game-carnival.mode-explore .booth.popped .booth-item,
.game-carnival.mode-warmup .booth.popped .booth-item {
  opacity: 1; visibility: visible;
}
/* the animal leaps clear of the crate, hangs, then settles back inside */
.mode-explore .booth.popped .booth-item, .mode-warmup .booth.popped .booth-item {
  transform: translateY(-46%) scale(1); animation: jumpOut 1.5s cubic-bezier(.3,1.2,.5,1);
}
@keyframes jumpOut {
  0% { transform: translateY(58%) scale(.7) }
  30% { transform: translateY(-62%) scale(1.1) }
  45% { transform: translateY(-46%) scale(1) }
  100% { transform: translateY(-46%) scale(1) }
}
.mode-explore .booth.lit::before, .mode-warmup .booth.lit::before { display: none; }
/* the character reacts to every press even when no attraction is centred on it */
.scene.party .cabin .char .head { animation: breathe 1.2s ease-in-out infinite; }

/* ---- Pop!: one big fairground stall with a curtain the animal hides behind */
.mode-pop .stage-row { bottom: 10%; }
.mode-pop .booth { width: clamp(170px, 28vw, 320px); filter: none; }
.mode-pop .booth::after {
  content: ''; position: absolute; left: 4%; right: 4%; bottom: 8%; height: 30%;
  background: linear-gradient(#c94a34, #a53a27); border-radius: 6px 6px 10px 10px;
  box-shadow: inset 0 4px 0 rgba(255,255,255,.25); z-index: 3;
}
.mode-pop .booth-item { z-index: 2; }

/* ---- Choose / Play: a real spotlight beam travelling along the row ---- */
.spot-beam {
  position: absolute; left: 0; top: 0; bottom: 16%; width: var(--w, 160px);
  transform: translateX(calc(var(--x, 50%) - var(--w, 160px) / 2));
  /* Additive light is invisible against a bright sky, so the day beam is a
     stronger, warmer amber than the night one. This is the game's whole
     mechanic, so it is deliberately the loudest thing on screen. */
  background: linear-gradient(to bottom, var(--beam-0), var(--beam-1) 45%, var(--beam-2));
  clip-path: polygon(41% 0, 59% 0, 108% 100%, -8% 100%);
  opacity: 0; transition: transform .18s ease, opacity .2s ease; z-index: 2; pointer-events: none;
}
/* the pool of light where the beam lands — a beam with no floor reads as a
   gradient, a beam with one reads as a lamp pointed at something */
.spot-beam::after {
  content: ''; position: absolute; left: 50%; bottom: -5%;
  width: 150%; height: 22%; transform: translateX(-50%); border-radius: 50%;
  background: radial-gradient(ellipse at center, var(--beam-pool), transparent 70%);
}
.spot-beam.on { opacity: 1; }

/* Everything the light is NOT on steps back. Contrast is what makes a
   spotlight read; a bright beam over equally bright neighbours does not. */
.mode-choose .booth { filter: brightness(.66) saturate(.7); }
.mode-choose .booth.lit { filter: none; }

/* ---- Play: a serving counter, so it reads as a food stall not a prize row */
.counter { display: none; }
.mode-play .counter {
  display: block; position: absolute; left: 0; right: 0; bottom: 0; height: 14%;
  background: linear-gradient(#8d6247, #6b4632);
  border-top: 6px solid #b78a67; z-index: 3;
  box-shadow: 0 -6px 18px rgba(0,0,0,.35);
}
.mode-play .stage-row { bottom: 13%; }
.mode-play .booth { width: clamp(84px, 14vw, 155px); }
.mode-play .prompt-bubble { border-color: #f28fb0; }
.mode-play .prompt-bubble::after { border-top-color: #f28fb0; }

/* ---- in-game hint banner: tells the ADULT what is about to happen ---- */
.game-hint {
  /* top, not bottom — at the bottom it sat across the row of stalls */
  position: absolute; left: 50%; top: 11%; transform: translateX(-50%);
  z-index: 7; background: rgba(26,20,48,.88); color: #fff6e2;
  padding: 12px 24px; border-radius: 18px; font-size: clamp(.95rem, 2vw, 1.15rem);
  max-width: 82%; text-align: center; border: 2px solid rgba(255,217,61,.6);
  animation: hintin .4s ease;
}
.game-hint.fade { opacity: 0; transition: opacity .8s ease; }
@keyframes hintin { from { opacity: 0; transform: translate(-50%, -12px) } to { opacity: 1; transform: translate(-50%, 0) } }

/* ---- turn-the-tablet gate ----------------------------------------------
   Only the GAME screen is gated. Picking a name, choosing a game and reading
   the progress screen are ordinary pages and read fine upright; it is the
   scenes that need the width. Gating the whole app would mean a grown-up
   could not so much as glance at last week's sessions on a phone.

   The test is on the shape of the window, not on `orientation:`, because a
   desktop browser window dragged narrow has the same problem and no
   orientation to report. */
.turn-gate { display: none; }
@media (max-aspect-ratio: 1/1) {
  #screen-game.active .turn-gate {
    display: flex; position: absolute; inset: 0; z-index: 40;
    align-items: center; justify-content: center; padding: 24px;
    background: var(--scene-sky-2, #8fd3f4);
  }
  /* The Finish button stays reachable: a grown-up who turned the tablet by
     accident mid-session should not have to turn it back to stop. */
  #screen-game.active .end-btn { z-index: 41; }
}
.turn-gate-card {
  background: rgba(26,20,48,.88); color: #fff6e2; border: 2px solid rgba(255,217,61,.6);
  border-radius: 22px; padding: 28px 26px; text-align: center; max-width: 22rem;
}
.turn-gate-icon { width: 64px; height: 64px; color: #ffd93d; }
.turn-gate-title { font-size: 1.25rem; font-weight: 700; margin: 10px 0 6px; }
.turn-gate-note { font-size: .95rem; opacity: .85; margin: 0; }
@media (prefers-reduced-motion: no-preference) {
  .turn-gate-icon { animation: turnhint 2.4s ease-in-out infinite; }
}
@keyframes turnhint {
  0%, 55%, 100% { transform: rotate(0) }
  75%, 90% { transform: rotate(-90deg) }
}

/* =================================================================
   FLOW: page heads, mode cards, get-ready screen
   ================================================================= */

/* 6px was drawn for a head that carried only a title. Now that it carries the
   who-pill, the first shelf badge sat right under the pill's shadow and the
   two read as one crowded block. */
.page-head { display: flex; align-items: center; justify-content: space-between; gap: 8px;
  margin-bottom: 18px; min-height: 48px; }
/* Left, next to the button, like the greeting and the who-screen title. A
   centred title only works when both sides of the row are balanced, and on
   these screens they never are. */
.page-title { font-weight: 700; font-size: 1.05rem; flex: 1; text-align: left; }
.settings-btn {
  background: var(--surface); border: none; color: var(--muted); font-size: 1.15rem;
  width: 46px; height: 46px; border-radius: 16px; cursor: pointer;
  box-shadow: 0 4px 0 var(--edge);
}
.settings-btn:active { transform: translateY(2px); box-shadow: 0 2px 0 var(--edge); }
.themed .icon-btn {
  color: var(--muted); background: var(--surface); width: 46px; height: 46px; border-radius: 16px;
  font-size: 1.15rem; box-shadow: 0 4px 0 var(--edge); padding: 0;
}
.themed .icon-btn:active { transform: translateY(2px); box-shadow: 0 2px 0 var(--edge); background: var(--surface); }
.themed .link-btn { color: #3372b5; }
.themed .page-title { color: var(--ink); font-size: 1.3rem; }
.footnote { text-align: center; color: var(--muted-2); font-size: .85rem; margin-top: auto; padding-top: 12px; }
.step-note { color: var(--muted); font-size: .95rem; margin: 0 0 12px; }

/* richer mode cards */
.mode-list { gap: 12px; }
.mode-card {
  display: flex; gap: 14px; align-items: center; padding: 16px 18px;
  border-radius: 22px; border: none; cursor: pointer; text-align: left;
  background: rgba(255,255,255,.09); color: #f4eeff;
  border-left: 10px solid var(--accent, #4a90d9);
  box-shadow: 0 4px 14px rgba(0,0,0,.28);
}
.mode-card:active { background: rgba(255,255,255,.18); }
.mode-card .mc-emoji { font-size: 2.2rem; line-height: 1; }
.mode-card .mc-body { flex: 1; display: flex; flex-direction: column; }
.mode-card .mc-name { font-size: 1.35rem; font-weight: 700; display: flex; align-items: center; gap: 10px; }
.mode-card .mc-sub { color: #cdc0e8; font-size: .95rem; margin-top: 2px; }
.mode-card .mc-go { color: var(--accent, #ffd93d); font-size: 1.3rem; }
.badge-rec { background: #ffd93d; color: #3a2d51; font-size: .65rem; padding: 3px 9px; border-radius: 10px; font-weight: 800; text-transform: uppercase; }

/* home: the shelf of games */
.game-grid { display: flex; flex-direction: column; gap: 14px; }
.game-tile {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 30px 20px; border-radius: 26px; border: none; cursor: pointer;
  background: rgba(255,255,255,.10); color: #f4eeff;
  border-bottom: 8px solid var(--accent, #ffd93d); box-shadow: 0 6px 20px rgba(0,0,0,.3);
}
.game-tile:active { background: rgba(255,255,255,.2); }
.game-tile .gt-art { font-size: 3.4rem; }
.game-tile .gt-name { font-size: 1.4rem; font-weight: 700; }
.game-tile .gt-sub { color: #cdc0e8; font-size: .9rem; }
.game-tile.soon { opacity: .35; border-bottom-color: #7d6ba8; cursor: default; }

/* get ready screen */
.ready-hero {
  display: flex; align-items: stretch; gap: 0; overflow: hidden;
  border-radius: 26px; margin-bottom: 14px; background: var(--surface);
  box-shadow: 0 6px 0 var(--lvd, #3372b5), 0 14px 26px var(--drop);
}
.rh-art {
  flex: 0 0 clamp(150px, 26vw, 220px); position: relative; min-height: 128px;
  background: linear-gradient(160deg, color-mix(in srgb, var(--lv, #4a90d9) 26%, var(--surface)), color-mix(in srgb, var(--lv, #4a90d9) 8%, var(--surface)));
}
.rh-art:has(.spotart) { background: #2f2a55; }
.rh-text { padding: 16px 20px; display: flex; flex-direction: column; justify-content: center; }
.rh-level {
  align-self: flex-start; font-size: .78rem; color: #fff; background: var(--lv, #4a90d9);
  padding: 4px 12px; border-radius: 12px; margin-bottom: 6px;
}
.ready-hero h2 { margin: 0; font-size: 1.7rem; color: var(--ink); }
.ready-hero p { margin: 4px 0 0; font-size: 1.02rem; color: var(--muted); }

.ctrl-row { display: flex; gap: 12px; margin-bottom: 12px; }
.pill {
  flex: 1; display: flex; align-items: center; justify-content: center; gap: 8px;
  padding: 14px; border-radius: 18px; border: none;
  background: var(--surface); color: var(--muted-2); font-size: 1.02rem; font-weight: 800; cursor: pointer;
  box-shadow: 0 4px 0 var(--edge-soft);
}
.pill span { font-size: 1.25rem; filter: grayscale(1) opacity(.55); }
.pill.on { background: #FFD93D; color: #6b4d00; box-shadow: 0 4px 0 #d9b32a; }
.pill.on span { filter: none; }
.pill:active { transform: translateY(2px); box-shadow: 0 2px 0 var(--edge-soft); }

.ctrl-block {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin-bottom: 12px; background: var(--surface); border-radius: 18px; padding: 10px 16px;
  box-shadow: 0 4px 0 var(--edge-soft);
}
.ctrl-block label { font-size: 1rem; color: var(--ink); }

.tips { margin: 4px 0 14px; }
.tips summary { cursor: pointer; color: #3372b5; font-size: .95rem; padding: 8px 0; font-weight: 800; }
.tips p { margin: 6px 0 0; color: var(--muted); font-size: .95rem; line-height: 1.5; }

.switch-check {
  display: flex; align-items: center; gap: 14px; border-radius: 18px;
  padding: 14px 16px; margin-bottom: 12px; border: 3px dashed var(--line);
  background: color-mix(in srgb, var(--surface) 80%, transparent);
}
.switch-check.ok { border-color: #3fb56b; border-style: solid; background: #eafaf0; }
.switch-check b { color: var(--ink); }
.switch-check-icon { font-size: 1.8rem; }
.switch-check.ok .switch-check-icon { animation: pop-ok .5s cubic-bezier(.34,1.56,.64,1); }
@keyframes pop-ok { 0% { transform: scale(.4) } 100% { transform: scale(1) } }
.switch-check-text { display: flex; flex-direction: column; }
.switch-check-text small, .switch-check-text span { color: var(--muted); font-size: .88rem; }

.quick-settings { background: var(--surface); border-radius: 16px; padding: 14px; margin-bottom: 14px; }
.quick-settings h4 { margin: 0 0 8px; }
.toggle-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 0; border-top: 1px solid var(--line-2); }
.toggle-row:first-of-type { border-top: none; }
.toggle-row > span { display: flex; flex-direction: column; }
/* Was a hard-coded #888: the same grey in day, night and high contrast,
   answering to none of them. It measured 3.5:1 on the settings panel and
   was the last thing on that screen still doing its own thing. */
.toggle-row small { color: var(--muted-2); font-size: .82rem; }
.toggle-row input[type=checkbox] { width: 26px; height: 26px; flex: 0 0 auto; }
.seg { display: flex; border-radius: 14px; overflow: hidden; border: 2px solid var(--line); flex: 0 0 auto; background: var(--surface-2); }
.seg button { border: none; background: transparent; padding: 11px 16px; font-size: .95rem; cursor: pointer; color: var(--muted-2); font-weight: 800; }
.seg button.on { background: var(--pill); color: #fff; }
.start-btn {
  background: #3fb56b; font-size: 1.6rem; margin-top: auto; border-radius: 24px;
  box-shadow: 0 7px 0 #2b8f50, 0 14px 24px rgba(43,143,80,.3);
}
.start-btn:active { transform: translateY(4px); box-shadow: 0 3px 0 #2b8f50; }

/* adult gate ring */
.hold-btn {
  position: relative; width: 138px; height: 138px; border-radius: 50%;
  padding: 0; border: none; cursor: pointer;
  display: grid; place-items: center;
  background: #4a90d9; color: #fff;
  box-shadow: 0 6px 0 #3372b5, 0 12px 24px rgba(51,114,181,.3);
  transition: transform .1s ease, box-shadow .1s ease;
}
.hold-btn:active, .hold-btn.holding { transform: translateY(4px); box-shadow: 0 2px 0 #3372b5; }
.hold-label {
  grid-area: 1 / 1; z-index: 2;
  font-family: 'Fredoka One', sans-serif; font-size: 1.2rem; letter-spacing: .3px;
}
.gate-ring {
  grid-area: 1 / 1; width: 100%; height: 100%;
  transform: rotate(-90deg); overflow: visible;
}
.gate-ring circle { fill: none; stroke-width: 9; stroke-linecap: round; }
.ring-track { stroke: rgba(255,255,255,.3); }
/* circumference of r=52 is ~327 */
.ring-fill { stroke: #FFD93D; stroke-dasharray: 327; stroke-dashoffset: 327; }
.hold-btn.holding .ring-fill { transition: stroke-dashoffset 1.2s linear; stroke-dashoffset: 0; }

.gate-box h3 { margin: 0 0 4px; font-size: 1.5rem; color: var(--ink); }
.gate-hint { color: var(--muted-2); font-size: .95rem; margin: 0 0 20px; }
.ghost-btn {
  margin-top: 18px; background: none; border: none; cursor: pointer;
  color: var(--muted-2); font-family: inherit; font-weight: 800; font-size: 1rem;
  padding: 10px 22px; border-radius: 14px;
}
.ghost-btn:hover { background: var(--surface-2); color: #4a90d9; }

/* warm-up banner: the first minute of every session is the same activity in
   every mode, so it must be unmistakably labelled and skippable */
.warmup-banner {
  position: absolute; top: 46px; left: 50%; transform: translateX(-50%);
  z-index: 8; display: flex; align-items: center; gap: 16px;
  background: rgba(26,20,48,.92); color: #fff6e2; border: 2px solid #c9b6ee;
  padding: 10px 14px 10px 18px; border-radius: 18px; max-width: 92%;
}
.warmup-banner .wb-left { display: flex; flex-direction: column; line-height: 1.35; }
.warmup-banner .wb-left b { font-size: 1rem; }
.warmup-banner .wb-left span { font-size: .86rem; opacity: .85; }
.warmup-banner #wb-count { color: #ffd93d; font-size: 1rem; }
#btn-skip-warmup {
  background: #ffd93d; color: #3a2d51; border: none; font-weight: 700;
  padding: 10px 16px; border-radius: 14px; cursor: pointer; white-space: nowrap;
}
#btn-skip-warmup:active { background: #e8c22f; }
@media (max-height: 480px) { .warmup-banner { top: 40px; padding: 6px 10px 6px 14px; } }

/* ---- turn counter: how much is left, always on screen ---- */
.progress-wrap { display: flex; }
.turn-label {
  background: rgba(26,20,48,.72); color: #fff6e2; padding: 5px 14px;
  border-radius: 12px; font-size: clamp(.85rem, 1.8vw, 1.05rem);
  border: 2px solid rgba(255,217,61,.5);
}
.turn-label b { color: #ffd93d; font-size: 1.15em; }

/* ---- miss feedback: obvious, but calm blue rather than punitive red ---- */
.booth.miss { animation: missShake .55s cubic-bezier(.36,.07,.19,.97); }
@keyframes missShake {
  0%,100% { transform: translateX(0) }
  15% { transform: translateX(-11px) rotate(-3deg) }
  35% { transform: translateX(9px) rotate(3deg) }
  55% { transform: translateX(-7px) rotate(-2deg) }
  75% { transform: translateX(5px) rotate(1deg) }
}
.booth.miss::after {
  content: ''; position: absolute; inset: -10px; border-radius: 24px;
  border: 6px solid #7fb3e8; box-shadow: 0 0 22px rgba(127,179,232,.75);
  animation: missRing .8s ease forwards; pointer-events: none; z-index: 4;
}
@keyframes missRing { 0% { opacity: 0; transform: scale(.9) } 25% { opacity: 1; transform: scale(1) } 100% { opacity: 0; transform: scale(1.05) } }
.scene.dim-pulse .backdrop { animation: dimPulse .65s ease; }
@keyframes dimPulse { 0%,100% { filter: none } 45% { filter: brightness(.6) saturate(.65) } }

.feedback-chip {
  position: absolute; left: 50%; top: 27%; transform: translate(-50%, 14px);
  z-index: 7; display: flex; align-items: center; gap: 10px;
  background: #2b5f96; color: #fff; border: 3px solid #7fb3e8;
  padding: 12px 22px; border-radius: 20px; opacity: 0; pointer-events: none;
  font-size: clamp(1rem, 2.2vw, 1.35rem); font-weight: 700;
  box-shadow: 0 8px 22px rgba(0,0,0,.4); white-space: nowrap;
}
.feedback-chip.show { animation: chipIn 1.6s ease forwards; }
.feedback-chip .chip-icon { font-size: 1.4em; }
@keyframes chipIn {
  0% { opacity: 0; transform: translate(-50%, 14px) scale(.85) }
  12% { opacity: 1; transform: translate(-50%, 0) scale(1) }
  80% { opacity: 1; transform: translate(-50%, 0) scale(1) }
  100% { opacity: 0; transform: translate(-50%, -10px) scale(.97) }
}

/* =================================================================
   WORLDS — shared goal-slot styling, then per-world theming
   ================================================================= */

.slot .slot-ring { transition: fill .3s ease, stroke .3s ease; }
.slot.filled .slot-ring { fill: rgba(255,217,61,.18); stroke: #ffd93d; }
.slot.just-filled { animation: seatIn .85s cubic-bezier(.34,1.56,.64,1); }
.slot { transform-box: fill-box; transform-origin: center; }
.slot .rider { pointer-events: none; }

/* ---- Band: stage lights, and the beat is the point ---- */

/* ---- Balloon Pop: it swells with every press, then bursts ---- */
@keyframes balloonFly {
  0%   { transform: scale(calc(0.34 + var(--inflate,0) * 0.8)) translate(0,0) rotate(0) }
  100% { transform: scale(calc(0.34 + var(--inflate,0) * 0.8)) translate(var(--fly,0), -130vh) rotate(18deg) }
}
@keyframes balloonBurst {
  0%   { transform: scale(1.14) }
  35%  { transform: scale(1.42) }
  100% { transform: scale(1.75); opacity: 0 }
}

/* ---- Band: cleaner stage, bigger instruments ---- */


/* the pump line, so it is obvious the balloon is being filled by holding */

/* Band: the stage player that just joined gives a bounce */
@keyframes bandBounce { from { transform: translateY(0) } to { transform: translateY(-8px) } }

/* per-game theme hooks (renamed from world- to game-) */
.game-balloon .booth { width: clamp(150px, 26vw, 300px); filter: none; }
.game-balloon .stage-row { bottom: 8%; }
.game-balloon .booth-item {
  transform: scale(calc(0.34 + var(--inflate, 0) * 0.8));
  transition: transform .3s cubic-bezier(.34,1.7,.6,1); transform-origin: bottom center;
}
.game-balloon .booth.inflating .booth-item {
  transition: transform .15s linear; filter: drop-shadow(0 0 16px rgba(255,255,255,.55));
}
.game-balloon .booth.flyaway .booth-item { animation: balloonFly 1.5s cubic-bezier(.3,.1,.5,1) forwards; }
@keyframes balloonFly {
  0%   { transform: scale(calc(0.34 + var(--inflate,0) * 0.8)) translate(0,0) rotate(0) }
  100% { transform: scale(calc(0.34 + var(--inflate,0) * 0.8)) translate(var(--fly,0), -130vh) rotate(18deg) }
}
.game-balloon .booth.burst .booth-item { animation: balloonBurst .4s ease-out forwards; }
@keyframes balloonBurst { 0% { transform: scale(1.15) } 35% { transform: scale(1.45) } 100% { transform: scale(1.8); opacity: 0 } }
.game-balloon .booth::after {
  content: ''; position: absolute; left: 50%; bottom: -8%; width: 3px; height: 16%;
  background: rgba(255,255,255,.65); transform: translateX(-50%);
}
.game-balloon .booth.flyaway::after, .game-balloon .booth.burst::after { opacity: 0; }
/* Balloon: the sky is the goal — balloons you let go stay up there */
.sky { position: absolute; inset: 0; pointer-events: none; z-index: 2; }
.sky-balloon {
  position: absolute; width: clamp(54px, 8.5vw, 92px);
  animation: skyRise .9s cubic-bezier(.34,1.4,.6,1) backwards;
}
.sky-balloon svg { width: 100%; height: auto; display: block;
  filter: drop-shadow(0 3px 6px rgba(0,0,0,.18)); }
@keyframes skyRise { 0% { transform: translateY(60vh) scale(.6); opacity: 0 } 100% { transform: translateY(0) scale(1); opacity: 1 } }
@keyframes skyBob { 0%,100% { translate: 0 0 } 50% { translate: var(--sway, 4px) -10px } }
.scene.party .sky-balloon { animation: skyBob 1.1s ease-in-out infinite; animation-delay: var(--d, 0s); }

/* the player stands ON the stage deck, which is now empty but for them */
.game-band .stage-row { bottom: 30%; }
/* With the line-up gone this is the only thing on the stage, so it is big:
   the actor must be unmistakably the thing to look at (see §2.2). */
.game-band .booth { width: clamp(140px, 23vw, 230px); filter: none; position: relative; }
/* A wooden drum standing on a wooden stage has no silhouette at all, so the
   artwork gets a crisp outline of its own shape.
   A soft glow was not enough: a blur has no edge, so it reads as haze rather
   than as separation. And the colour cannot be fixed — contrast is relative to
   what is behind it, so light theme outlines dark on the pale deck and dark
   theme outlines light on the deep one. Chained zero-blur drop-shadows compound
   into a sticker edge that follows the art exactly. */
.game-band .booth-item svg {
  filter:
    drop-shadow(2px 0 0 var(--outline)) drop-shadow(-2px 0 0 var(--outline))
    drop-shadow(0 2px 0 var(--outline)) drop-shadow(0 -2px 0 var(--outline))
    drop-shadow(0 10px 14px rgba(0,0,0,.28));
}

/* The lit ring hugs the instrument. The generic booth is a 6/7 stall box with
   the artwork at 68% inside it — around a lone instrument that ring lands way
   out in the sky and the grass. */
.game-band .booth { aspect-ratio: 1; }
.game-band .booth-item { width: 100%; margin-bottom: 0; }
.game-band .booth.lit::before { inset: -2px; border-width: 5px; border-radius: 26px; }
.game-band .booth-svg { display: none; }
.scene.party.game-band .slot.filled { animation: bandBounce .45s ease-in-out infinite alternate; }
@keyframes bandBounce { from { transform: translateY(0) } to { transform: translateY(-8px) } }
.scene.party.game-band .wheel-bulb { animation: bulb .35s ease-in-out infinite; }

.game-carnival .spot-beam { }


/* Band: the song sheet is the goal, and players stand on the stage floor */
.note-head, .note-stem { transition: fill .3s ease, stroke .3s ease; }
.note.played .note-head { stroke: #ffd93d; }
.note.played .note-stem { fill: #ffd93d; }
.note.played { animation: noteInk .5s cubic-bezier(.34,1.56,.64,1); }
@keyframes noteInk { 0% { transform: scale(.4) } 60% { transform: scale(1.25) } 100% { transform: scale(1) } }
.note { transform-box: fill-box; transform-origin: center; }

/* a game with its own goal display keeps just a small counter */
.progress-wrap.bare .turn-label { font-size: clamp(.8rem, 1.6vw, .95rem); padding: 4px 12px; }


/* the whole sky lets go at once */
.sky-balloon.fly-off {
  animation: skyFlyOff 1.8s cubic-bezier(.4,.05,.6,1) forwards !important;
}
@keyframes skyFlyOff {
  0%   { transform: translateY(0); opacity: 1 }
  100% { transform: translate(var(--fly, 0), -120vh) rotate(14deg); opacity: .9 }
}
/* replayed note pulses as it sounds in the finale */
.note.pulse .note-head { animation: notePulse .3s ease; }
@keyframes notePulse { 0% { transform: scale(1) } 50% { transform: scale(1.5) } 100% { transform: scale(1) } }


/* ============================ WHO IS PLAYING + SESSIONS ============================ */

/* WHO IS PLAYING, as a header pill.
   This was a full-width white bar sitting between the header and the shelves,
   and it read exactly like what it was: a late addition wedged into a gap.
   In the header, where a page title would otherwise sit, it is the thing the
   screen is about. Sized to its contents, never full width. */
.who-pill {
  display: flex; align-items: center; gap: 10px; min-width: 0;
  background: var(--surface); border: none; border-radius: 999px;
  padding: 5px 16px 5px 5px; cursor: pointer; text-align: left;
  box-shadow: 0 3px 0 var(--edge);
  transition: transform .12s ease, box-shadow .12s ease;
}
.who-pill:active { transform: translateY(2px); box-shadow: 0 1px 0 var(--edge); }
/* Capped, not left to its own content width. With the back arrow pinned out
   of flow (see the who/home/ready header rules further down) there is no
   longer a sibling in the flex row to squeeze this against, so a long name
   had nothing forcing it to shrink — it just ran full-width, straight over
   the pinned back arrow and grown-up menu. The cap recreates that squeeze:
   whatever is too wide to fit the gutter shrinks and ellipsises instead. */
.head-left { display: flex; align-items: center; gap: 10px; min-width: 0; max-width: calc(100% - 140px); }
.wp-avatar {
  width: 38px; height: 38px; border-radius: 50%; display: grid; place-items: center;
  background: #4a90d9; color: #fff; font-size: 1.05rem; flex: 0 0 auto;
  font-family: 'Fredoka One', sans-serif;
}
.wp-text { display: flex; flex-direction: column; min-width: 0; }
.wp-text small { font-size: .7rem; color: var(--muted-2); line-height: 1.15; }
/* A long name shortens rather than pushing the settings buttons off the edge. */
.wp-text b { font-size: 1rem; color: var(--ink); line-height: 1.2;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wp-change { font-size: .78rem; color: var(--muted-2); text-decoration: underline; flex: 0 0 auto; }
/* Unset it ASKS. A quiet grey pill saying "add a name" reads as a label. */
.who-pill.unset { border: 2px dashed #f59e2d; padding: 3px 14px 3px 3px;
  background: color-mix(in srgb, #f59e2d 8%, var(--surface)); }
.who-pill.unset .wp-avatar { background: #f59e2d; font-size: 1.3rem; }
.who-pill.unset .wp-text b { color: #b06d09; }
/* On a phone the header has to hold a back arrow, this, and two buttons, so
   the pill drops to the part that matters: who. */
@media (max-width: 560px) {
  .wp-change, .wp-text small { display: none; }
  .wp-avatar { width: 34px; height: 34px; }
}
.sheet-icon { font-size: 2.2rem; text-align: center; display: block; margin-bottom: 6px; }

.sheet-box {
  background: var(--surface); border-radius: 30px; padding: 26px 24px;
  width: min(460px, 92vw); display: flex; flex-direction: column; align-items: stretch;
  box-shadow: 0 10px 0 var(--edge), 0 26px 60px var(--drop);
}
.sheet-box h3 { margin: 0 0 4px; color: var(--ink); text-align: center; }
.sheet-hint { margin: 0 0 16px; font-size: .9rem; color: var(--muted); text-align: center; }
.player-list { display: flex; flex-direction: column; gap: 8px; }
.player-row {
  display: flex; align-items: center; gap: 12px; padding: 10px 14px;
  background: var(--surface-2); border: 2px solid transparent; border-radius: 16px;
  cursor: pointer; font-size: 1.05rem; color: var(--ink); font-weight: 800;
}
.player-row.on { border-color: #4a90d9; }
.pr-avatar {
  width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center;
  background: #4a90d9; color: #fff; flex: 0 0 auto; font-family: 'Fredoka One', sans-serif;
}
.add-someone {
  display: block; width: 100%; margin: 10px 0 4px; padding: 14px;
  background: var(--surface-2); border: 2px dashed var(--line); border-radius: 16px;
  color: var(--muted); font-family: inherit; font-weight: 800; font-size: 1rem; cursor: pointer;
}
.add-someone:hover { border-color: #4a90d9; color: #3372b5; }
.player-new { display: flex; flex-direction: column; gap: 8px; margin: 14px 0 4px; }
.player-new label { font-size: .88rem; color: var(--muted); font-weight: 800; }
.player-new input {
  padding: 16px; font-size: 1.15rem; font-family: inherit; font-weight: 800;
  border-radius: 16px; border: 2px solid var(--line); background: var(--surface-2); color: var(--ink);
}
.player-new input:focus { outline: none; border-color: #4a90d9; background: var(--surface); }
.player-new .big-btn { margin: 4px 0 0; min-height: 56px; font-size: 1.1rem; }
.player-new .big-btn:disabled { background: var(--surface-2); color: var(--muted-2); cursor: default; }

.nav-row {
  display: flex; align-items: center; justify-content: space-between; width: 100%;
  background: none; border: none; padding: 10px 0; cursor: pointer; text-align: left;
}
.nav-row-text { display: flex; flex-direction: column; }
.nav-row-text b { color: var(--ink); font-size: 1.02rem; }
.nav-row-text small { color: var(--muted-2); font-size: .86rem; }
.nav-row-caret { color: var(--muted-2); font-size: 1.4rem; }

.child-tabs { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 4px; }
.child-tab {
  display: flex; align-items: center; gap: 8px; padding: 8px 14px 8px 8px;
  background: var(--surface); border: 2px solid transparent; border-radius: 999px;
  cursor: pointer; font-weight: 800; color: var(--ink); font-size: .95rem;
  box-shadow: 0 3px 0 var(--edge);
}
.child-tab.on { border-color: #4a90d9; }
.child-tab .pr-avatar { width: 28px; height: 28px; font-size: .9rem; }

.rec-card { border-left: 6px solid #ffd93d; }
.rec-card p { margin: 0; color: var(--ink-2); font-size: .98rem; line-height: 1.45; }

.session-list { display: flex; flex-direction: column; }
.session-row {
  display: flex; align-items: center; gap: 14px; width: 100%;
  background: none; border: none; border-top: 1px solid var(--line-2);
  padding: 12px 0; cursor: pointer; text-align: left;
}
.session-row:first-child { border-top: none; }
.sr-when { width: 84px; flex: 0 0 auto; color: var(--muted-2); font-size: .86rem; }
.sr-body { display: flex; flex-direction: column; flex: 1; }
.sr-body b { color: var(--ink); font-size: 1rem; }
.sr-body small { color: var(--muted); font-size: .88rem; }
.empty-note { color: var(--muted); text-align: center; padding: 40px 20px; }

/* ---- the one chart in the report ----
   Bars are stacked: on-target at the bottom, other presses above. Height says
   how busy, colour says how accurate. Both colours are theme tokens. */
.chart-card { margin: 0; }
.chart-title { font-family: 'Fredoka One', sans-serif; font-weight: 400; font-size: 1rem;
  color: var(--ink); margin-bottom: 10px; }
.chart {
  display: flex; align-items: flex-end; justify-content: space-between;
  height: 92px; gap: 4px; border-bottom: 1.5px solid var(--line);
}
.chart .col {
  flex: 1 1 0; min-width: 4px; height: 100%;
  display: flex; flex-direction: column; justify-content: flex-end;
  border-radius: 5px 5px 0 0; overflow: hidden;
}
.chart .seg { display: block; width: 100%; }
.chart .seg.other { background: var(--chart-other); }
.chart .seg.hit { background: var(--chart-hit); }
.chart-sub { font-size: .84rem; color: var(--muted-2); margin: -4px 0 12px; }

/* ---- what to try next: the actionable half of the report ---- */
.todo .dl-head { margin-top: 4px; }
.step { display: flex; gap: 12px; padding: 14px 0; border-top: 1px solid var(--line-2); }
.step:first-of-type { border-top: none; padding-top: 6px; }
.step-n {
  flex: 0 0 auto; width: 26px; height: 26px; border-radius: 50%;
  background: var(--accent-soft); color: var(--accent-ink);
  display: grid; place-items: center; font-weight: 800; font-size: .9rem; margin-top: 2px;
}
.step-body { flex: 1; }
.step-body b { display: block; font-family: 'Fredoka One', sans-serif; font-weight: 400;
  font-size: 1.1rem; color: var(--ink); margin-bottom: 3px; }
.step-body p { margin: 0; color: var(--muted); font-size: .95rem; line-height: 1.45; }
.step-do {
  margin-top: 10px; padding: 10px 16px; border-radius: 12px; cursor: pointer;
  border: none; background: var(--accent-soft); color: var(--accent-ink);
  font-family: inherit; font-size: .93rem; font-weight: 800;
}
.step-now { display: block; margin-top: 8px; font-size: .88rem; color: var(--muted-2); }

/* The linked control announces itself for a moment — otherwise arriving on a
   long settings page leaves you hunting for the thing you were sent to. */
@keyframes settingFlash {
  0%, 100% { background: transparent; box-shadow: 0 0 0 0 transparent; }
  15%, 65% { background: color-mix(in srgb, #ffd93d 30%, var(--surface));
             box-shadow: 0 0 0 6px color-mix(in srgb, #ffd93d 30%, var(--surface)); }
}
.flash { border-radius: 12px; animation: settingFlash 2.4s ease; }
@media (prefers-reduced-motion: reduce) { .flash { animation-duration: 1.2s; } }
.dl-row.wide { grid-template-columns: 1fr; }
.dl-row.wide .dl-value { text-align: left; white-space: normal; line-height: 1.5; }
.chart-axis { display: flex; justify-content: space-between; margin-top: 7px;
  font-size: .78rem; color: var(--muted-2); }
.chart-key { display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 10px;
  font-size: .82rem; color: var(--muted); align-items: center; }
.chart-key span { display: inline-flex; align-items: center; gap: 6px; }
.chart-key i { width: 11px; height: 11px; border-radius: 3px; }
.chart-key .k-hit { background: var(--chart-hit); }
.chart-key .k-other { background: var(--chart-other); }
.chart-peak { margin-left: auto; color: var(--muted-2); }

.report .settings-group + .settings-group { margin-top: 14px; }
.report-top { text-align: left; }

.head-actions { display: flex; gap: 10px; }


/* ---- parent report: findings, not figures ---- */

.pr-head { font-family: 'Fredoka One', sans-serif; font-size: 1.5rem; color: var(--ink); text-align: center; }
.pr-sub { text-align: center; color: var(--muted); font-size: .95rem; margin-bottom: 18px; }
.finding { border-top: 1px solid var(--line-2); padding: 14px 0; }
.finding:first-of-type { border-top: none; }
.finding b { display: block; font-family: 'Fredoka One', sans-serif; font-weight: 400;
  font-size: 1.12rem; color: var(--ink); margin-bottom: 3px; }
.finding p { margin: 0; color: var(--muted); font-size: .96rem; line-height: 1.45; }
.finding-offer { display: inline-block; margin-top: 8px; font-size: .9rem; color: #3372b5; font-weight: 800; }
.pr-thin { color: var(--muted); font-size: .95rem; text-align: center; padding: 10px 0 4px; }
.find-head { margin: 18px 0 2px; }
/* The shortfall, quantified. Quiet, but present under every provisional read —
   an adult who knows it is four balloons short can decide to play four more. */
.pr-more {
  margin: 12px 0 0; padding: 10px 12px; border-radius: 12px;
  background: var(--surface-2); color: var(--muted-2); font-size: .9rem; line-height: 1.45;
}
.pr-made {
  margin: 16px 0 10px; padding: 12px; border-radius: 16px; text-align: center;
  background: var(--surface-2); color: var(--ink); font-family: 'Fredoka One', sans-serif; font-size: 1.05rem;
}


/* ---- plain-language detail ---- */
.dl-head { font-family: 'Fredoka One', sans-serif; font-weight: 400; font-size: 1.1rem;
  color: var(--ink); margin: 22px 0 6px; }
.dl-head:first-child { margin-top: 4px; }
.dl-sub { margin: 0 0 8px; font-size: .9rem; color: var(--muted-2); }
.dl-row { display: grid; grid-template-columns: 1fr auto; gap: 4px 16px;
  padding: 10px 0; border-top: 1px solid var(--line-2); align-items: baseline; }
.dl-label { color: var(--ink-2); font-size: .98rem; }
.dl-value { color: var(--ink); font-weight: 800; text-align: right; white-space: nowrap; }
.dl-note { grid-column: 1 / -1; font-size: .86rem; color: var(--muted-2); line-height: 1.4; }
.dl-nudge { margin: 16px 0 0; padding: 12px 14px; border-radius: 14px;
  background: color-mix(in srgb, #ffd93d 22%, var(--surface)); color: var(--ink-2); font-size: .95rem; }
.dl-observe { margin: 4px 0 0; padding-left: 20px; color: var(--muted); font-size: .95rem; line-height: 1.6; }


/* ---- backup card ---- */
/* .toggle-row's label has no flex basis, so a long line collapses it to a few
   characters wide. Scoped here rather than changed globally. */
#sync-body .toggle-row > span { flex: 1; min-width: 0; }
.sync-field { margin: 14px 0 10px; }
.sync-field label { display: block; margin-bottom: 6px; font-size: .95rem; color: var(--ink); }
.sync-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 10px 0; }
.sync-row span { color: var(--muted); font-size: .95rem; }
.sync-row .pill { flex: 0 0 auto; }
.sync-err { color: #b4462f; }
/* Text fields only. Unscoped, this caught the checkbox and stretched it across
   the row, which squeezed the toggle's own label to nothing. */
.sync-field input {
  width: 100%; padding: 12px 14px; border-radius: 12px; font: inherit;
  border: 1.5px solid var(--line); background: var(--surface); color: var(--ink);
}


/* ============================ WELCOME + WHO IS PLAYING ============================
   The way in. Welcome holds one control; who's-playing holds one question.
   Both stack and centre the same way on every screen — a phone, a tablet
   held either way, a desktop window. A side-by-side layout for wide screens
   used to live here, meant to protect a landscape phone from the button
   being pushed off the bottom, but the stacked layout was tested down to a
   320px-tall viewport without that happening, so the extra layout (and the
   inconsistency it caused next to mobile) was removed rather than kept. */

.screen.welcome { align-items: center; justify-content: center; text-align: center; }
.welcome .w-block {
  flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
}
.w-art { font-size: clamp(3.4rem, 12vw, 5.2rem); line-height: 1; }
.w-title {
  font-family: 'Fredoka One', 'Nunito', sans-serif; font-weight: 400;
  font-size: clamp(1.9rem, 6vw, 2.6rem); color: var(--ink); margin: 10px 0 6px;
}
.w-sub { margin: 0; color: var(--muted); font-size: 1.02rem; }
.w-cta { width: 100%; max-width: 420px; margin-top: auto; }
.w-cta .big-btn { margin: 0; }

/* Centred against the WHOLE row, not just the leftover space beside the back
   button (and, on Home/Ready, the grown-up menu). Centring text inside that
   leftover flex:1 box alone put it in the middle of a row whose right half
   was empty — off-centre drift, not symmetry, which is what an early version
   of this rule looked like. Fixed below by taking the corner buttons out of
   flow entirely (see "centred header, corner buttons pinned" further down),
   so there is no leftover box left to drift inside — this text-align: centre
   lands on the true middle of the row. */
.page-head > .page-h1 { flex: 1; }
.page-h1, .greet { text-align: center; }
.page-h1 {
  margin: 0; font-family: 'Fredoka One', 'Nunito', sans-serif; font-weight: 400;
  font-size: clamp(1.3rem, 4vw, 1.9rem); color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
}
/* Centred header, corner buttons pinned. The back arrow (and, on Home/Ready,
   the grown-up menu) are taken out of flow and pinned to the row's corners,
   so the title is the only thing left in flow and can sit at the row's true
   centre — the same centre the name-grid/greeting content lines up with —
   instead of just right of whichever button happens to share the row. */
#screen-who .page-head, #screen-home .page-head, #screen-ready .page-head {
  position: relative; justify-content: center;
}
#screen-who .page-head > .icon-btn,
#screen-home .page-head .head-left > .icon-btn,
#screen-ready .page-head .head-left > .icon-btn {
  position: absolute; left: 0; top: 50%; transform: translateY(-50%);
}
#screen-home .page-head > .settings-btn, #screen-ready .page-head > .settings-btn {
  position: absolute; right: 0; top: 50%; transform: translateY(-50%);
}
/* Sits directly under the header. Centring it vertically was worse: the title
   stayed at the top and the names drifted to the middle, so the two halves of
   one question stopped looking related. Content starts at the top on every
   other screen too. */
.who-body { width: 100%; }
.who-hint { margin: 0 0 18px; color: var(--muted); font-size: .98rem; line-height: 1.45; text-align: center; }
/* As many columns as fit, and NEVER a wider row. The max is 340px rather than
   1fr because 1fr stretches: on a tablet held either way a single 1fr row runs
   the whole width, leaving the avatar and the name at opposite ends of a white
   plank with a hand's width of nothing between them. A phone gets one column,
   a portrait tablet three, a 13-inch landscape three — all the same size. */
/* Centred, not left-anchored. The columns cap at 340px so on a wide screen
   two of them (690px) rarely fill the whole shared column (up to 1000px);
   grid's default justify-content: start left that spare width sit entirely
   on the right, so the block of names read as pushed toward the left edge
   instead of sitting in the middle of the page. */
.name-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 340px)); gap: 10px; justify-content: center; }
.name-grid .player-row { padding: 12px 14px; box-shadow: 0 4px 0 var(--edge); }
.name-grid .pr-avatar { width: 44px; height: 44px; font-size: 1.15rem; }
/* Name and last visit STACKED. At opposite ends of the row they read as two
   unrelated facts with a hand's width of nothing between them; together they
   read as one child. (A button also centres its text by default, which is why
   the name used to float away from its avatar.) */
.pr-text { display: flex; flex-direction: column; min-width: 0; text-align: left; }
.pr-text b { font-size: 1.1rem; color: var(--ink); line-height: 1.25;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pr-text small { font-size: .8rem; color: var(--muted-2); font-weight: 800; }
/* Guest is a name on the list, so it looks like one, with a face rather than
   an initial. Pinned last by the render, never sorted. */
.guest-row .pr-avatar.guest { background: var(--surface-2); font-size: 1.2rem; }
.guest-row .pr-text b { color: var(--ink); }

/* Adding a child is one of the choices, so it sits IN the grid with them
   rather than beside it at a different size and weight. Dashed and quiet:
   still a choice, plainly not a child. */
.add-row { background: transparent; border: 2px dashed var(--line); box-shadow: none; }
.add-row .pr-text b { color: var(--muted); }
.add-row .pr-avatar.add { background: var(--surface-2); color: var(--muted); font-size: 1.3rem; }
/* Centred on the same axis as the name-grid above it, not flush against the
   shared column's left edge — a 340px form flush left while the grid it
   replaces sits centred read as its own drift all over again. */
#screen-who .player-new { max-width: 340px; margin: 14px auto 0; }

/* One colour per name, derived from the name itself, so four rows are a list
   you recognise rather than one you have to read. */
.pr-avatar.t-blue { background: #4a90d9; }
.pr-avatar.t-green { background: #3fb56b; }
.pr-avatar.t-amber { background: #e08a1e; }
.pr-avatar.t-plum { background: #8a6bd9; }
.pr-avatar.t-teal { background: #1f9e93; }
.link-btn {
  background: none; border: none; color: var(--muted); font-family: inherit; font-weight: 800;
  font-size: .95rem; cursor: pointer; padding: 10px 0; text-decoration: underline;
}

/* The chip over the shelves says who this sitting belongs to. Unset it asks:
   a quiet grey chip reading "add a name" reads as a label, not as a task. */


/* A wide screen buys MORE columns, never longer rows. Everything a finger
   aims at stays the size it is on a phone; the extra width goes back to the
   park, which is what the scenery is there for. */
@media (min-width: 900px) {
  /* ONE shared column, made by padding the screen itself. The first version
     of this rule centred every child separately, so the 1000px shelves, the
     520px chip and a 340px form each picked their own left edge and the page
     read as scattered. Padding the screen gives every element the same
     column; narrow things sit flush on its left edge like everything else. */
  .screen.themed { padding-inline: max(clamp(16px, 4vw, 44px), calc((100% - 1000px) / 2)); }
}

/* Whose settings these are, said once at the top. Changing child is a menu
   job, so this states a fact rather than offering a second way to do it. */
.settings-who { margin: 0 0 16px; color: var(--muted); font-size: .95rem; }
.settings-who b { color: var(--ink); }

/* The header greeting. Takes the place the app name used to hold: on the
   screen where a game is chosen, who is playing is the more useful fact. */
.greet {
  margin: 0; font-family: 'Fredoka One', 'Nunito', sans-serif; font-weight: 400;
  font-size: clamp(1.3rem, 4vw, 1.9rem); color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
}

/* One row per thing a grown-up can do. Big targets: this is often reached
   one-handed with a child on your lap. */
.menu-row {
  display: flex; align-items: center; gap: 14px; width: 100%; text-align: left;
  padding: 14px; margin-bottom: 10px; cursor: pointer;
  background: var(--surface-2); border: 2px solid transparent; border-radius: 18px;
  font-family: inherit; font-weight: 800; color: var(--ink);
}
.menu-row:active { transform: translateY(2px); }
.menu-row .mr-icon { font-size: 1.5rem; flex: 0 0 auto; width: 34px; text-align: center; }
.mr-text { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.mr-text b { font-size: 1.05rem; }
.mr-text small { font-size: .85rem; color: var(--muted); font-weight: 700; }


/* =================================================================
   ICON TOKENS — one drawing, many modes.

   Every fill and stroke in js/icons.js reads from a variable here, so high
   contrast, CVI and night re-colour the same artwork instead of shipping
   parallel sets that drift apart the first time one is edited.

   The per-asset colours arrive as inline --b / --b2 / --a / --cvi on the svg.
   The accent is --a and NOT --acc on purpose: an inline custom property beats a
   stylesheet one, so an --acc written on the element could never be re-coloured
   by a mode. test/icons/icons-drift.js fails if one creeps back.
   ================================================================= */
.ic { --line:#2A2118; --line-w:4; --lite:#FFFFFF; --spark:#FFFFFF;
      --mark:#2A2118; --shade:rgba(0,0,0,.10);
      /* the per-asset colours arrive inline as --b / --b2 / --a / --cvi, and an
         inline custom property beats a stylesheet one — so the names a mode
         needs to override are derived here rather than set inline */
      --body:var(--b); --body2:var(--b2,var(--b)); --acc:var(--a); }
.ic .ln    { stroke:var(--line); stroke-width:var(--line-w);
             stroke-linejoin:round; stroke-linecap:round; fill:none }
.ic .thin  { stroke-width:calc(var(--line-w) * .68) }
.ic .body  { fill:var(--body) }
.ic .body2 { fill:var(--body2) }
.ic .acc   { fill:var(--acc) }
.ic .lite  { fill:var(--lite) }
.ic .ink   { fill:var(--mark) }
.ic .spark { fill:var(--spark) }
.ic .shade { fill:var(--shade) }
/* a limb drawn as a stroke: the outline is the same path run fatter underneath,
   so it thickens with --line-w like every closed shape does */
.ic .fat   { stroke-width:calc(9px + var(--line-w) * 2px) }
.ic .tube  { stroke:var(--body);  stroke-width:9px; fill:none;
             stroke-linecap:round; stroke-linejoin:round }
.ic .tube2 { stroke:var(--body2); stroke-width:9px; fill:none;
             stroke-linecap:round; stroke-linejoin:round }
.ic .conf  { stroke:none }
.ic .glow  { fill:#FFD34D; opacity:.34 }
.ic .glow2 { fill:#FFE9A8; opacity:.5 }
.ic .beamedge { stroke:#FFE9A8; stroke-width:2; fill:none; opacity:.55; stroke-linecap:round }

/* HIGH CONTRAST.
   The first attempt only thickened the outline, and that was the wrong idea.
   Measured against the grass the characters stand on, seven of the eight sit
   under 3:1 — the lion at 1.02:1, which is no separation at all — and nothing
   about a heavier line changes that. Contrast is a relationship between a
   figure and its GROUND, so the ground has to move.
   This is the lineage of the old PCS High Contrast sets: bold black line art on
   a plain, near-white field. Black on #F4F4F4 measures 19:1, against 11.5:1 on
   grass and 1.02:1 for the lion's own body. The scenery goes with it, because
   texture behind a figure is the other half of the problem. */
[data-contrast="high"] .ic {
  --line: #000; --line-w: 6.5; --shade: transparent;
  --lite: #fff; --spark: transparent; --mark: #000;
}
/* the field flattens to near-white so the black line has something to be black
   against, and every object gains an edge it did not have on grass */
[data-contrast="high"]:not([data-cvi="on"]) {
  --scene-sky-1: #FAFAFA; --scene-sky-2: #F4F4F4; --scene-sky-3: #EFEFEF;
  --scene-grd-1: #E6E6E6; --scene-grd-2: #DCDCDC; --ground-lip: #D2D2D2;
  --stage-back: #E6E6E6; --stage-lip: #DCDCDC; --curtain: #C8C8C8;
}
[data-contrast="high"]:not([data-cvi="on"]) .scene { background: #F4F4F4; }
/* High contrast used to hide the park outright, whatever Background said —
   "texture behind a figure is the other half of the problem". Measured, that
   was not what was happening: the scenery never sits behind the target. Five
   of the six games overlap it by 0%, and Catch Me clips a corner at 8%. The
   tent and the wheel live in the sky band; the crate and the booths stand on
   the ground. So a parent who asked for the park and for high contrast was
   having one of the two answers thrown away, silently.
   Background and Contrast are separate axes and stay separate. What IS true is
   that the everyday scenery goes pale-on-pale once the field flattens to
   near-white, so it gets this mode's treatment like everything else: the fill
   goes grey and recedes, and a black line carries the shape. */
[data-contrast="high"]:not([data-cvi="on"]) .backdrop .t-scenery {
  filter: grayscale(1) brightness(1.08) contrast(.9) opacity(.92);
}
/* One weight for the whole park, including the shapes that carry their own.
   Letting the ferris wheel keep its stroke-width of 7 made its rim the boldest
   line on the screen — bolder than the crate, which is the thing to press.
   Scenery gets a thin line and a grey fill; the target keeps a thick line and
   a colour. That is the hierarchy, stated in two properties. */
[data-contrast="high"] .backdrop .t-scenery * { stroke: #000; stroke-width: 2.5; }
/* Clouds are the quietest tier and the only one with no edge of its own: they
   are two overlapping ellipses, so outlining them draws the seam where the two
   cross and they stop reading as a cloud. Grey fill, no line. */
[data-contrast="high"] .backdrop .day-clouds * { fill: #E9E9E9; stroke: none; }
/* Stars stay gone. Not a clutter decision — high contrast keeps the field
   near-white even at night, and a white star on it is nothing at all. */
[data-contrast="high"] .backdrop .stars { display: none; }
/* Anything that was pale-on-blue becomes pale-on-near-white, which is worse.
   The song sheet and its notes have to darken with the field. */
[data-contrast="high"] {
  --panel-bg: #FFFFFF; --panel-edge: #000000;
  --panel-line: rgba(0,0,0,.55); --panel-mark: #000000;
}
[data-contrast="high"] .backdrop .note-head { stroke: #000; stroke-width: 3; }
[data-contrast="high"] .backdrop .note-stem { fill: #000; }
[data-contrast="high"] .backdrop .staff-line { stroke-width: 2; }
/* pastel confetti on a white field is confetti nobody can see */
[data-contrast="high"] .particle { outline: 1.5px solid #000; outline-offset: -1px; }
/* The interior hairlines — valve stems, fur, ridges — are the first thing to
   merge into the outline once it thickens. Dropping them keeps the silhouette
   sharp instead of filling the shape in. */
[data-contrast="high"] .ic .thin { display: none }
/* the app's own chrome has to move too, or only the drawings change */
[data-contrast="high"] {
  /* This used to name two navy hexes. On a white page they were the darkest
     text in the app; on the night surface they were the SAME navy as the panel
     behind them, and the whole settings screen measured 1:1 — a mode meant to
     help low vision made the app unreadable for anyone using it after dark.
     Fading the current surface's ink instead is right on either surface, and
     there is nothing left to get wrong. */
  --line: #7c88a8; --edge: rgba(0,0,0,.55);
  --muted:   rgb(var(--on) / 1);
  --muted-2: rgb(var(--on) / .88);
}
[data-contrast="high"] .activity-card,
[data-contrast="high"] .settings-group,
[data-contrast="high"] .seg { border: 2px solid var(--ink); }
[data-contrast="high"] .seg button.on { outline: 2px solid var(--ink); outline-offset: -2px; }

/* CVI — the opposite instinct. Saturated colour on black beats black-and-white
   line art, and every extra line and dot is one more component to process. So
   this drops the outline entirely, hides fine detail, and collapses each shape
   to one saturated mass. Not high contrast with the brightness turned up. */
[data-cvi="on"] .ic { --line:transparent; --line-w:0; --shade:transparent;
                      --spark:transparent; --mark:#000; --lite:#000;
                      --body:var(--cvi,var(--b)); --body2:var(--cvi,var(--b));
                      --acc:var(--cvi,var(--b)) }
[data-cvi="on"] .ic .detail,
[data-cvi="on"] .ic .decor { display:none }
/* one preferred colour for the whole set — the child's, not the object's */
[data-cvi="on"][data-cvi-colour="one"] .ic { --body:var(--pref,#FFD400);
                                             --body2:var(--pref,#FFD400);
                                             --acc:var(--pref,#FFD400) }


/* ---- vision settings: the colour swatches ---- */
.sw-row { display: flex; gap: 7px; flex: 0 0 auto; }
.sw {
  width: 32px; height: 32px; border-radius: 9px; background: var(--sw);
  cursor: pointer; border: 2px solid transparent;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,.12);
}
.sw.on { border-color: var(--ink); }

/* =================================================================
   SCENE HIERARCHY — four tiers, loudest first.

     target    the one thing to look at and press right now
     prop      what holds or houses it — present, never competing
     distractor the other choices in Spotlight: the task, not atmosphere
     scenery   tent, wheel, bunting, curtains — must recede
     backdrop  sky and ground: a surface, not an object

   Before this the tent was painted at the same strength as the crate, so the
   loudest thing on screen was often the thing that did not matter.
   ================================================================= */
.backdrop .t-scenery { filter: saturate(.45) opacity(.72); }
.crate, .booth-svg   { filter: saturate(.88); }

/* ---- reduced clutter: its own axis, not a corner of CVI ---- */
[data-clutter="plain"] .backdrop .t-scenery { display: none; }
[data-clutter="plain"]  .backdrop .day-clouds,
[data-clutter="plain"]  .backdrop .stars,
[data-clutter="plain"]  .backdrop .sun,
[data-clutter="plain"]  .backdrop .sun-glow,
[data-clutter="plain"]  .backdrop .moon { display: none; }
/* the stage's own furniture is scenery too */
/* The stage's own furniture is scenery too. The footlights are .wheel-bulb —
   the class is shared with the ferris wheel, and guessing `.bulb` here meant the
   rule matched nothing and the lights stayed lit above Music Box. */
[data-clutter="plain"]  .backdrop .curtain,
[data-clutter="plain"]  .backdrop .light-rail,
[data-clutter="plain"]  .backdrop .wheel-bulb { display: none; }

/* ---- CVI takes the background with it ----
   The guidance is to isolate the target from a busy background, and a dimmed
   busy background is still a busy background. So this removes rather than
   fades, and the field goes black behind everything. */
[data-cvi="on"] {
  --scene-sky-1: #000; --scene-sky-2: #000; --scene-sky-3: #000;
  --scene-grd-1: #000; --scene-grd-2: #000; --ground-lip: #000;
  --stage-back: #000; --stage-lip: #000; --curtain: #000;
  --panel-bg: transparent; --panel-edge: transparent;
}
[data-cvi="on"] .scene { background: #000; }
[data-cvi="on"] .backdrop .t-scenery,
[data-cvi="on"] .backdrop .buntings,
[data-cvi="on"] .backdrop .ferris,
[data-cvi="on"] .backdrop .tent,
[data-cvi="on"] .backdrop .day-clouds,
[data-cvi="on"] .backdrop .sun,
[data-cvi="on"] .backdrop .sun-glow,
[data-cvi="on"] .backdrop .stars,
[data-cvi="on"] .backdrop .moon,
[data-cvi="on"] .backdrop .sheet-panel,
[data-cvi="on"] .backdrop .staff-line,
[data-cvi="on"] .backdrop .note-head,
[data-cvi="on"] .backdrop .note-stem,
[data-cvi="on"] .backdrop .note,
[data-cvi="on"] .backdrop .light-rail,
[data-cvi="on"] .backdrop .wheel-bulb,
[data-cvi="on"] .backdrop .curtain { display: none; }
/* props go neutral, never saturated: a bright crate competes with the target
   for exactly the attention the target needs */
[data-cvi="on"] .crate-svg .body,
[data-cvi="on"] .crate-svg .body2,
[data-cvi="on"] .crate-svg .acc,
[data-cvi="on"] .booth-svg .body,
[data-cvi="on"] .booth-svg .body2,
[data-cvi="on"] .booth-svg .acc { fill: #4C4C4C; }


/* Celebration is the one moment the screen deliberately gets busy, so it is the
   one moment CVI has to be loudest about cutting back: the five confetti
   colours collapse to the child's colour, and most of the pieces never appear. */
[data-cvi="on"] .particle { background: var(--pref, #FFD400) !important; }
[data-cvi="on"] .particle:nth-child(2n) { display: none; }


/* =================================================================
   MOTION — the target is marked by moving, not by a shape drawn round it.

   The app already had popup, rustle, bob and .booth.lit. What was missing was
   the reduced-motion contract: with motion off, the LIFT has to stay. Losing
   the animation should cost the flourish, never the cue — otherwise a child
   who needs less movement is left with no way to tell which one to press.
   ================================================================= */
@media (prefers-reduced-motion: reduce) {
  /* the lit booth keeps its lift, glow and spotlight; only the pulsing stops */
  .booth.lit { transform: translateY(-14px) scale(1.14); }
  /* and it ARRIVES at that lift rather than springing into it. The .22s
     cubic-bezier(.34,1.56,.64,1) on .booth overshoots the lit size by about 1%
     and settles back — which is the flourish, not the cue. Reduced motion asked
     for the cue without it, so the transform snaps and only the filter fades.
     This was invisible for as long as the rustle animation happened to still be
     running when the stall lit, because a running animation suppresses the
     transition; it is not something to leave depending on that. */
  .booth { transition: filter .22s ease; }
  .booth.lit::before { animation: none; opacity: .95; }
  .booth.lit .spotlight { opacity: 1; }
  /* an animal that pops still ends up outside the crate, it just gets there
     without the arc */
  .mode-explore .booth.popped .booth-item,
  .mode-warmup .booth.popped .booth-item {
    animation: none; transform: translateY(-46%) scale(1);
  }
  .particle, .balloon { display: none; }
}

/* CVI keeps movement — attraction to movement is among the more reliably
   preserved channels — but drops the decorative motion around it, which is
   complexity without a message. */
[data-cvi="on"] .flag,
[data-cvi="on"] .star,
[data-cvi="on"] .wheel-bulb,
[data-cvi="on"] .cabin { animation: none !important; }
[data-cvi="on"] .booth.lit::before { animation: none; }


/* a setting the app is overriding should look overridden, not merely unchosen */
.toggle-row.row-locked .seg { opacity: .45; pointer-events: none; }


/* ---- update pill ----
   Sits above everything except the game (it is never shown there — app.js
   decides per screen). Looks like the app's own pills, reads as an adult
   control: small words, no bounce, no sound. */
#update-banner {
  position: fixed; left: 50%; bottom: 18px; transform: translateX(-50%);
  z-index: 60; display: flex; align-items: center; gap: 6px;
  padding: 10px 18px; border: none; border-radius: 999px; cursor: pointer;
  background: #fff; color: #2b3a67; font: inherit; font-size: .95rem;
  box-shadow: 0 4px 14px rgba(0,0,0,.18);
}
#update-banner:active { transform: translateX(-50%) scale(.97); }


/* ---- version line, Settings only: adult-facing, quiet ----
   Quiet through SIZE and placement, never through low contrast: the colour is
   the theme's own muted token, which follows day/night and clears AA — a
   hardcoded translucent navy read 1.25:1 on the night theme. */
.settings-version {
  text-align: center; padding: 14px 16px 22px; font-size: .85rem;
  color: var(--muted);
}
