/* 糖果贴纸花园 (v6 batch 4). Interface surfaces are flat candy blocks: a 3px border in their own colour (--edge, or --line
   when neutral), a top highlight and a coloured lip underneath. Only illustrations carry the ink outline: the bunny PNGs,
   the sticker nav icons, the red packet and coin, the tone slides, the flag. Depth comes from colour and lips, not lines.
   Loaded after styles.css (reset and page frame). Children's text is at least 16px; the parent area may use 13–14px. */
@font-face{font-family:'Andika';src:url('./assets/fonts/andika-400.woff2') format('woff2');font-weight:400;font-display:swap}
@font-face{font-family:'Andika';src:url('./assets/fonts/andika-700.woff2') format('woff2');font-weight:700;font-display:swap}

/* Tokens. */
:root{
 --ink:#4A3B58;--ink-soft:#6E6386;--paper:#FFFBF2;--cream:#FFF4DE;--line:#EADCC4;--line-lip:#DFCBA8;
 --sun:#FFD43B;--sun-deep:#E8A800;--sun-fill:#FFF3C2;--sun-ink:#8A5A00;
 --good:#2F9E35;--good-lip:#1F7A27;--good-fill:#DDF6D3;
 --oops:#FF8A3D;--oops-lip:#E0661A;--oops-fill:#FFE6D6;
 --red:#EF4B3F;--red-lip:#C4342A;
 --sky-top:#8FD3FF;--sky-mid:#CDEEFF;--sky-low:#EEF9FF;--hill-far:#C9EDB2;--hill-mid:#A6DE84;--hill-near:#84CC5E;
 --hi:inset 0 4px 0 #ffffffb3;--amb:0 14px 24px -12px #4a3b5833;
 --rad-panel:30px;--rad-card:22px;--rad-chip:16px;
 --font-ui:'Yuanti SC','YuanTi SC','PingFang SC','Microsoft YaHei',sans-serif;--font-py:'Andika','Avenir Next',sans-serif;
 --tone1:#3B9BE0;--tone1-lip:#2378B8;--focus:#FF9F1C;
 --ease-back:cubic-bezier(.34,1.56,.64,1);--ease-out:cubic-bezier(.22,1,.36,1);--ease-in:cubic-bezier(.55,0,1,.45);--ease-spring:var(--ease-back);
 font-family:var(--font-ui);color:var(--ink);background:var(--sky-low)}
/* A real spring for presses where linear() exists; elsewhere the back-out curve. */
@supports (transition-timing-function:linear(0,1)){:root{--ease-spring:linear(0,.35 6%,.88 15%,1.12 24%,1.17 30%,1.1 38%,1 48%,.97 58%,1 72%,1.005 84%,1)}}
/* Family colours: fill / edge / lip / ink. */
.green{--fill:#E2F7CF;--edge:#8ED36B;--lip:#62B043;--ink-f:#2E7A24}
.peach{--fill:#FFEBCC;--edge:#FFB65C;--lip:#EE8B1E;--ink-f:#A34A00}
.purple{--fill:#EFE5FF;--edge:#BC9BF5;--lip:#8F6AD8;--ink-f:#6338B8}
.blue{--fill:#DDF1FF;--edge:#7CC4F5;--lip:#3B9BE0;--ink-f:#1C6BA8}
.teal{--fill:#D4F7F0;--edge:#5ED3BE;--lip:#2FAE98;--ink-f:#137A69}
.redf{--fill:#FFE1DC;--edge:#FF8F80;--lip:#E0483A;--ink-f:#B42A1F}
.sunf{--fill:#FFF3C2;--edge:#FFD43B;--lip:#E8A800;--ink-f:#8A5A00}
/* Module colour, set by route() on body[data-page]. */
body{--m-fill:#FFF3C2;--m-edge:#FFD43B;--m-lip:#E8A800;--m-ink:#8A5A00}
body[data-page=learn]{--m-fill:#D4F7F0;--m-edge:#5ED3BE;--m-lip:#2FAE98;--m-ink:#137A69}
body[data-page=spell]{--m-fill:#FFEBCC;--m-edge:#FFB65C;--m-lip:#EE8B1E;--m-ink:#A34A00}
body[data-page=tones]{--m-fill:#DDF1FF;--m-edge:#7CC4F5;--m-lip:#3B9BE0;--m-ink:#1C6BA8}
body[data-page=packet]{--m-fill:#FFE1DC;--m-edge:#FF8F80;--m-lip:#E0483A;--m-ink:#B42A1F}
/* Tone colours, wherever a tone shows. */
.t1{--tc:#3B9BE0;--ts:#E3F2FF;--tl:#2378B8}.t2{--tc:#2F9E35;--ts:#E0F6DD;--tl:#1F7A27}.t3{--tc:#F08A24;--ts:#FFEBD6;--tl:#C96A0E}.t4{--tc:#E5487A;--ts:#FFE2EC;--tl:#B8305A}
body{min-height:100vh;background:linear-gradient(180deg,var(--sky-top) 0,var(--sky-mid) 300px,var(--sky-low) 640px,#F2FBE8 100%) fixed;color:var(--ink)}
.pinyin{font-family:var(--font-py);font-weight:700;letter-spacing:.5px}
button:focus-visible,a:focus-visible,input:focus-visible{outline:4px solid var(--focus);outline-offset:3px}
#app{position:relative;z-index:1}

/* Scenery (outside #app, never redrawn): three drifting cloud stickers, a swaying sun, three SVG hills and two trees.
   Only transform moves; the hills are fixed behind the stage and take no room in the page. */
.scenery{position:fixed;inset:0;z-index:0;pointer-events:none;overflow:hidden;contain:strict}
.scenery img{position:absolute;display:block;height:auto;user-select:none}
.cloud{left:0;will-change:transform;animation:cloud linear infinite}
.cl1{width:120px;top:64px;animation-duration:90s;animation-delay:-55s}
.cl2{width:90px;top:150px;animation-duration:120s;animation-delay:-100s;opacity:.92}
.cl3{width:70px;top:28px;animation-duration:150s;animation-delay:-20s;opacity:.85}
@keyframes cloud{from{transform:translateX(-200px)}to{transform:translateX(calc(100vw + 40px))}}
.sun{width:110px;top:20px;right:300px;transform-origin:50% 50%;animation:sun-sway 20s ease-in-out infinite alternate}
@keyframes sun-sway{to{transform:rotate(12deg)}}
.hills{position:absolute;left:0;bottom:0;width:100%;height:200px;display:block}
.hill-far{fill:var(--hill-far)}.hill-mid{fill:var(--hill-mid)}.hill-near{fill:var(--hill-near)}
/* Two butterflies drift through the sky beside the title and a row of grass sways at the foot of the hills. Each is one
   slow loop (14–17s, 8s): the butterflies' wing beats (scaleX, in bursts) and their path share one keyframe list. With the
   three clouds and the sun that is seven loops, all paused while a sound plays. */
.scenery .bfly{position:absolute;width:40px;height:32px;overflow:visible;animation:bfly 14s ease-in-out infinite}
.scenery .bf1{top:96px;left:56%}
.scenery .bf2{top:170px;right:16%;width:32px;height:26px;animation-duration:17s;animation-delay:-6s;--bx:-1}
@keyframes bfly{0%,100%{translate:0 0;rotate:10deg}25%{translate:calc(var(--bx,1)*120px) -26px;rotate:20deg}50%{translate:calc(var(--bx,1)*220px) 8px;rotate:-6deg}75%{translate:calc(var(--bx,1)*90px) 30px;rotate:-20deg}2%,6%,10%,14%,36%,40%,44%,48%,70%,74%,78%,82%{scale:.3 1}0%,4%,8%,12%,16%,34%,38%,42%,46%,50%,68%,72%,76%,80%,84%,100%{scale:1}}
.scenery .grass{position:absolute;left:0;right:0;bottom:-4px;display:flex;justify-content:space-between;padding:0 6px 0 236px;transform-origin:50% 100%;animation:grass-sway 8s ease-in-out infinite alternate}
.scenery .grass svg{display:block;width:60px;height:48px}
@keyframes grass-sway{from{transform:skewX(-5deg)}to{transform:skewX(5deg)}}
.tree{width:80px;bottom:46px}.tree-l{left:244px}.tree-r{right:10px;bottom:60px}
/* Below 1500px the gutters beside the stage are under 60px, so a tree could only show as a sliver. */
@media(max-width:1500px){.scenery .tree{display:none}}
/* While a sound plays the scenery and the title bunny hold still. !important: the entry shorthand on the bunny
   (main.enter … img.bunny) resets play-state and is more specific. The bunny's one-shot entry (bunny-in, then idle) runs
   on while the page's greeting plays: only its idle part waits (not under the opening, where the whole entry waits). */
html[data-listening] .scenery *,html[data-listening] main .page-head .helper img.bunny{animation-play-state:paused!important}
html[data-listening]:not([data-opening]) main.enter[data-level=page] .page-head .helper img.bunny:not(.swapped){animation-play-state:running,paused!important}

/* Sidebar: warm paper, the bunny head and five sticker entries; the chosen one takes its module colour. */
.sidebar{padding:22px 18px 16px;background:#FFF8EC;box-shadow:inset -3px 0 0 #F1E2C8,12px 0 30px -18px #4a3b5833}
.brand{display:flex;align-items:center;gap:10px;min-height:48px;padding:0 4px;font:800 22px var(--font-ui);color:var(--ink)}
.brand img.bunny-head{width:auto;height:40px}
/* The head is cropped from bunny-wave.png; its bottom edge fades out instead of ending in a hard cut through the scarf. */
.bunny-head{display:block;width:auto;user-select:none;-webkit-user-drag:none;-webkit-mask-image:linear-gradient(#000 74%,transparent 96%);mask-image:linear-gradient(#000 74%,transparent 96%)}
.sidebar nav{display:grid;gap:14px;margin-top:28px}
.nav-item{position:relative;display:grid;grid-template-columns:56px 1fr;align-items:center;gap:8px;height:74px;padding:0 12px 0 8px;border-radius:var(--rad-card);background:#fff;border:3px solid #F1E2C8;box-shadow:var(--hi),0 5px 0 #E6D5B8;color:var(--ink);font:800 18px var(--font-ui);transition:transform 160ms var(--ease-back)}
.nav-item:hover{transform:translateY(-2px)}
.nav-item:active{box-shadow:var(--hi),0 1px 0 #E6D5B8}
.nav-ico{display:grid;place-items:center;width:56px;height:56px;border-radius:50%}
.nav-icon{display:block;width:48px;height:44px;object-fit:contain;transition:transform 200ms var(--ease-back)}
.nav-item:hover .nav-icon{transform:rotate(-6deg)}
.nav-item.active{background:var(--m-fill);border-color:var(--m-edge);box-shadow:var(--hi),0 5px 0 var(--m-lip);color:var(--m-ink)}
.nav-item.active .nav-ico{width:52px;height:52px;margin:2px;background:#fff;box-shadow:0 3px 0 var(--m-edge)}
.nav-item.active .nav-icon{width:42px;height:38px}
/* A little tab pointing at the page the entry opened. */
.nav-item.active::after{content:'';position:absolute;right:-12px;top:50%;width:16px;height:16px;margin-top:-8px;background:var(--m-fill);border:3px solid var(--m-edge);border-left:0;border-bottom:0;border-radius:0 4px 0 0;transform:rotate(45deg)}
.nl-short{display:none}
.parent-link{margin-top:auto;min-height:48px;display:flex;justify-content:center;align-items:center;gap:6px;padding:12px;background:none;color:var(--ink-soft);font-size:14px;font-weight:700}

/* Top bar: 继续 with a still flag and the stop number, the HUD (stars + today's flowers) and, on phones, the gear. */
/* Above main, so the title bunny's ear tips tuck behind 继续 instead of painting over its lip. */
.topbar{position:relative;z-index:2;height:64px;display:flex;align-items:center;justify-content:space-between;gap:12px}
.top-r{display:flex;align-items:center;gap:8px}
.go-btn{display:inline-flex;align-items:center;gap:8px;height:60px;padding:0 10px 0 14px;border-radius:30px;background:var(--sun);box-shadow:var(--hi),0 5px 0 var(--sun-deep),var(--amb);color:var(--ink);font:800 20px var(--font-ui);transition:transform 160ms var(--ease-back)}
.go-btn:hover{transform:translateY(-2px)}.go-btn:active{box-shadow:var(--hi),0 1px 0 var(--sun-deep)}
.go-btn.here{padding-right:10px}
.flag{width:26px;height:30px;overflow:visible}
.flag-pole{fill:none;stroke:var(--ink);stroke-width:3;stroke-linecap:round}
.flag-cloth{fill:var(--red);stroke:var(--ink);stroke-width:2.5;stroke-linejoin:round}
.go-num{display:inline-grid;place-items:center;min-width:38px;height:38px;padding:0 6px;border-radius:19px;background:#fff;font:700 20px var(--font-py);color:var(--ink)}
.hud{display:flex;align-items:center;gap:8px;padding:0;background:none;border-radius:25px}
.chip{display:inline-flex;align-items:center;gap:4px;height:50px;padding:0 14px;border-radius:25px;background:#fff;box-shadow:inset 0 0 0 3px var(--line),0 4px 0 var(--line-lip);font-size:20px}
.chip-star{padding-left:8px}
.hud-star{width:30px;height:30px;object-fit:contain}
.chip-star b{font:700 22px var(--font-py);color:var(--sun-ink)}
.hud-flower{width:28px;height:30px;object-fit:contain;filter:brightness(0) opacity(.12)}
.hud-flower.on{filter:none}
.gear{display:none;place-items:center;width:48px;height:48px;border-radius:50%;background:#fff;box-shadow:inset 0 0 0 3px var(--line),0 4px 0 var(--line-lip);font-size:22px;color:var(--ink-soft)}

/* Page head: the one talking bunny, the title bubble and the round 🐢 switch. */
.page-head{display:flex;align-items:center;gap:14px;height:84px}
.helper{position:relative;flex-shrink:0;height:84px;padding:0;background:none}
img.bunny{display:block;width:auto;height:100%;object-fit:contain;transform-origin:50% 100%;user-select:none;-webkit-user-drag:none}
.helper img.bunny{height:88px;margin-top:-4px;animation:idle 4s ease-in-out infinite}
/* A pose being replaced fades out on top of the new one (swapPose). */
.helper img.bunny.pose-out{position:absolute;left:0;top:0;pointer-events:none}
/* The one loop inside the page at rest: the title bunny bobs 3px every 4s. */
@keyframes idle{50%{transform:translateY(-3px)}}
.helper-speaker{position:absolute;right:-20px;bottom:0;top:auto;display:grid;place-items:center;width:30px;height:30px;border-radius:50%;background:#fff;box-shadow:inset 0 0 0 2px var(--line),0 3px 0 var(--line-lip);color:var(--ink)}
.helper-speaker .icon{width:17px;height:17px;stroke-width:2.6}
.helper.talking::after{content:'♪';position:absolute;right:-22px;top:-6px;font-size:22px;font-weight:900;color:var(--m-lip);animation:note-up .9s ease-out infinite}
@keyframes note-up{from{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-16px)}}
.bubble{position:relative;margin-left:10px;padding:6px 22px;border-radius:24px;background:#fff;border:3px solid var(--line);box-shadow:0 4px 0 var(--line-lip)}
.bubble::before{content:'';position:absolute;left:-11px;top:50%;width:16px;height:16px;margin-top:-8px;background:#fff;border-left:3px solid var(--line);border-bottom:3px solid var(--line);transform:rotate(45deg)}
.bubble h1{margin:0;font:800 30px/1.3 var(--font-ui);color:var(--ink)}
.speed{position:relative;margin-left:auto;display:grid;place-items:center;width:60px;height:60px;padding:0;border-radius:50%;background:#fff;box-shadow:inset 0 0 0 3px var(--line),0 4px 0 var(--line-lip);font-size:28px}
.speed[aria-pressed=true]{background:var(--good-fill);box-shadow:inset 0 0 0 3px var(--good),0 4px 0 var(--good-lip)}
.speed .on{position:absolute;right:-6px;top:-6px;display:grid;place-items:center;width:24px;height:24px;border-radius:50%;background:var(--good);color:#fff;font:900 14px/1 var(--font-ui);font-style:normal}

/* Bar (pick / where am I) and the stage card. The bar's controls are light (2px, see-through white); only the stage is heavy.
   main is a column and the stage takes what is left below the bar, so a bar on two rows (the 四声 shelf on a tablet) never
   pushes the dock under the fold. At one bar row this is the old stage height: 100dvh − topbar 64 − head 84 − bar 68 − 28. */
.bar{width:100%;max-width:1100px;min-height:56px;display:flex;align-items:center;gap:12px;margin:0 auto 12px;flex-wrap:wrap}
.bar>*{margin:0}
main{display:flex;flex-direction:column;min-height:calc(100dvh - 68px)}
.stage{position:relative;width:100%;max-width:1100px;margin:0 auto 24px;display:flex;flex-direction:column;flex:1 0 auto;border-radius:var(--rad-panel);background:var(--board,var(--paper));border:3px solid var(--m-edge);box-shadow:var(--hi),0 8px 0 var(--m-lip),var(--amb)}
.stage-body{flex:1;display:flex;flex-direction:column;justify-content:center;align-items:center;gap:12px;padding:20px 24px}
.dock{height:96px;display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:16px;padding:0 22px;border-top:3px dashed var(--line)}
.dock>.dock-l{justify-self:start;display:flex;align-items:center;gap:10px}.dock>.dock-r{justify-self:end}
.stage.in-game{--board:#FFFBE9}
.where,.map-count,.packet-bar,.task-chip,.coin-track,.map-bar>b{border:2px solid #fff;background:#F7FCFF;box-shadow:0 4px 0 #A9D3EE}

/* Buttons. Main actions are green with white 19–20px bold text; 去哪儿 (go) actions are sun with ink text; the second
   choice is white with a line edge. Lips change without a transition; only transform eases. */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:56px;padding:0 24px;border-radius:20px;background:var(--good);color:#fff;font:800 19px var(--font-ui);text-shadow:0 2px 0 var(--good-lip);box-shadow:var(--hi),0 6px 0 var(--good-lip),var(--amb);transition:transform 160ms var(--ease-back)}
.btn .icon{width:24px;height:24px}
.btn:hover:not(:disabled){transform:translateY(-2px)}
.btn:active:not(:disabled){box-shadow:var(--hi),0 1px 0 var(--good-lip)}
.btn.go{background:var(--sun);color:var(--ink);text-shadow:none;box-shadow:var(--hi),0 6px 0 var(--sun-deep),var(--amb)}
.btn.secondary{background:#fff;color:var(--ink);text-shadow:none;box-shadow:inset 0 0 0 3px var(--line),0 6px 0 var(--line-lip)}
.btn.secondary:active:not(:disabled){box-shadow:inset 0 0 0 3px var(--line),0 1px 0 var(--line-lip)}
/* Parent-area buttons: small text, so the darker green keeps white text readable. */
.btn.small{min-height:40px;padding:0 14px;border-radius:14px;background:var(--good-lip);font-size:14px;text-shadow:none;box-shadow:var(--hi),0 3px 0 #155C1C}
.btn.secondary.small{background:#fff;box-shadow:inset 0 0 0 2px var(--line),0 3px 0 var(--line-lip)}
.btn:disabled,.dock-main:disabled{opacity:.4;box-shadow:none}
.dock-main{display:inline-flex;align-items:center;justify-content:center;gap:10px;min-height:72px;min-width:200px;padding:0 28px;border-radius:36px;background:var(--good);color:#fff;font:800 20px var(--font-ui);text-shadow:0 2px 0 var(--good-lip);box-shadow:var(--hi),0 6px 0 var(--good-lip),var(--amb);transition:transform 160ms var(--ease-back)}
.dock-main:hover{transform:translateY(-2px)}
.dock-main:active{box-shadow:var(--hi),0 1px 0 var(--good-lip)}
.dock-main .icon{width:28px;height:28px;stroke-width:2.6}
.dock-main .pinyin{font-size:30px}
.dock-main.find-start.ready,.dock-main.go-next,.stage-map .dock-main{background:var(--sun);color:var(--ink);text-shadow:none;box-shadow:var(--hi),0 6px 0 var(--sun-deep),var(--amb)}
.dock-main.listen-again{background:var(--tone1);text-shadow:0 2px 0 var(--tone1-lip);box-shadow:var(--hi),0 6px 0 var(--tone1-lip),var(--amb)}
.dock-main.packet-go{background:var(--red);text-shadow:0 2px 0 var(--red-lip);box-shadow:var(--hi),0 6px 0 var(--red-lip),var(--amb)}
.dock-main.playing{box-shadow:var(--hi),0 6px 0 var(--good-lip),0 0 0 5px #ffd43b88}
.dest-icon{height:38px;width:auto}
/* An emoji on a coloured button sits on a white disc, so it never vanishes into the button colour. */
.btn-ico{display:grid;place-items:center;flex-shrink:0;width:40px;height:40px;margin-left:-10px;border-radius:50%;background:#fff;font-size:24px;line-height:1;text-shadow:none}
.neutral-chip .btn-ico{width:34px;height:34px;margin-left:-8px;background:var(--sun-fill);font-size:20px}
.stage-close{position:absolute;right:14px;top:14px;z-index:3;display:grid;place-items:center;width:48px;height:48px;padding:0;border-radius:50%;background:#fff;box-shadow:inset 0 0 0 3px var(--line),0 4px 0 var(--line-lip);color:var(--ink-soft)}
.stage-close .icon{width:24px;height:24px;stroke-width:3}
.feedback{min-height:25px;margin:0;font-size:18px;font-weight:800;color:var(--ink-soft);text-align:center}
.feedback.good{color:var(--good)}.feedback.oops{color:var(--oops-lip)}

/* Question navigator: ◀ 上一题 | dots | 下一题 ▶; the green ▶ carries the countdown ring. */
.qnav{display:flex;align-items:center;gap:14px;padding-bottom:22px}
.qnav-step{position:relative;display:grid;place-items:center;width:56px;height:56px;padding:0;border-radius:50%;background:#fff;box-shadow:inset 0 0 0 3px var(--line),0 4px 0 var(--line-lip);color:var(--ink)}
.qnav-step svg.chev{width:26px;height:26px;fill:none;stroke:currentColor;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round}
.qnav-step small{position:absolute;bottom:-25px;left:50%;transform:translateX(-50%);font-size:16px;font-weight:800;white-space:nowrap;color:var(--ink-soft)}
.qnav-step:disabled{opacity:.3;box-shadow:inset 0 0 0 3px var(--line)}
.qnav-step.go{width:72px;height:72px;background:var(--good);color:#fff;box-shadow:var(--hi),0 5px 0 var(--good-lip)}
.qnav-step.go small{bottom:-23px;color:var(--good-lip)}
.qnav-step.packet-step{background:var(--red);color:#fff;box-shadow:var(--hi),0 5px 0 var(--red-lip)}
.ring{position:absolute;inset:-6px;width:84px;height:84px;transform:rotate(-90deg);pointer-events:none}
.ring circle{fill:none;stroke:var(--sun);stroke-width:6;stroke-linecap:round;stroke-dasharray:100;stroke-dashoffset:100}
.qnav-dots{display:flex;gap:8px;margin:0;padding:0;list-style:none}
.qdot{display:grid;place-items:center;width:40px;height:40px;border-radius:50%;background:#fff;border:2.5px dashed var(--line-lip);font:700 16px var(--font-py);color:var(--ink-soft)}
.qdot.now{transform:scale(1.15);border:3px solid var(--sun-deep);color:var(--ink)}
.qdot.done{background:var(--good-fill);border:2.5px solid var(--good)}
.qdot.miss{background:var(--oops-fill);border:2.5px solid var(--oops)}
.qdot.got{position:relative;background:var(--sun-fill);border:2.5px solid var(--sun-deep)}
.qdot.got::after{content:'★';position:absolute;right:-6px;top:-8px;font-size:15px;color:var(--sun-deep);text-shadow:0 0 2px #fff}

/* Pinyin cards (点读板 keys, 识读 cards, game cards): family colour fill, edge and lip, a big letter with a white rim,
   a white emoji stamp, the word on wide cards, a highlight streak. While its sound plays a card gets a sun edge, a ring
   and an equaliser (only the playing card animates, and only as long as its sound). */
.sound-key{position:relative;display:flex;flex-direction:column;justify-content:center;align-items:center;gap:6px;padding:8px 6px;border-radius:var(--rad-card);background:var(--fill);border:3px solid var(--edge);box-shadow:var(--hi),0 6px 0 var(--lip);color:var(--ink-f);transition:transform 160ms var(--ease-back)}
.sound-key::before{content:'';position:absolute;left:12%;top:7px;width:30%;height:6px;border-radius:3px;background:#fff;opacity:.7}
.sound-key strong{display:block;font-size:40px;line-height:1;color:var(--ink-f);text-shadow:0 2px 0 #fff}
.key-pic{display:grid;place-items:center;flex-shrink:0;width:38px;height:38px;border-radius:50%;background:#fff;box-shadow:0 3px 0 var(--edge);font-size:24px;line-height:1}
.key-word{font-size:16px;font-weight:800;line-height:1.1;color:var(--ink-f);white-space:nowrap}
.key-check{position:absolute;right:-10px;top:-10px;z-index:2;display:grid;place-items:center;width:30px;height:30px;border-radius:50%;background:var(--good);box-shadow:0 0 0 3px #fff,0 3px 0 3px var(--good-lip);color:#fff}
.key-check .icon{width:18px;height:18px;stroke-width:3.4}
.key-eq{position:absolute;right:10px;top:12px;display:flex;align-items:flex-end;gap:2px;height:14px;opacity:0}
.key-eq i{display:block;width:4px;height:100%;border-radius:2px;background:var(--sun-deep);transform-origin:50% 100%;transform:scaleY(.4)}
.sound-key:hover{transform:translateY(-3px);box-shadow:var(--hi),0 9px 0 var(--lip)}
.sound-key:active{box-shadow:var(--hi),0 2px 0 var(--lip)}
.sound-key.playing{border-color:var(--sun-deep)}
.sound-key.playing::after{content:'';position:absolute;inset:-4px;border-radius:inherit;border:4px solid var(--sun);pointer-events:none;animation:key-ring 900ms ease-out infinite}
.sound-key.playing .key-eq{opacity:1}
.sound-key.playing .key-eq i{animation:eq 420ms ease-in-out infinite alternate}
.sound-key.playing .key-eq i:nth-child(2){animation-delay:-140ms}.sound-key.playing .key-eq i:nth-child(3){animation-delay:-280ms}
@keyframes key-ring{from{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(1.12)}}
@keyframes eq{to{transform:scaleY(1)}}
/* The word only fits on cards at least 110px wide (it is always in the label). */
@container key (max-width:109px){.key-word{display:none}}

/* 拼音点点乐: folder tabs in the bar join the board below; the board takes the chosen family's colours. */
/* Unchosen tabs tuck under the board's top edge; the chosen one lies over it, so the two read as one folder. */
.bar:has(.board-tabs){align-items:flex-end;min-height:58px;margin-bottom:-3px}
.stage-home{z-index:1}
.board-tabs{display:flex;align-items:flex-end;gap:6px}
.tab{display:inline-flex;align-items:center;gap:8px;height:52px;padding:0 20px;border:3px solid var(--line);border-bottom:0;border-radius:20px 20px 0 0;background:#fff;color:var(--ink);font:800 18px var(--font-ui);white-space:nowrap;position:relative;z-index:0;transform:translateY(8px);transition:transform 160ms var(--ease-back)}
.tab small{font-size:16px;color:var(--ink-f);opacity:.8}
.tab:hover:not(.active){transform:translateY(4px)}
.tab.active{z-index:2;height:58px;transform:none;background:var(--board-tab,var(--fill));border-color:var(--edge);color:var(--ink-f)}
.tab-dot{display:inline-block;width:12px;height:12px;border-radius:50%;background:var(--edge)}
.stage-home{border-top-left-radius:0}
.stage-home[data-family=all]{--m-edge:#FFD43B;--m-lip:#E8A800;--board:var(--paper)}
.stage-home[data-family=initials]{--m-edge:#8ED36B;--m-lip:#62B043;--board:#F3FBEA}
.stage-home[data-family=finals]{--m-edge:#FFB65C;--m-lip:#EE8B1E;--board:#FFF6E8}
.stage-home[data-family=whole]{--m-edge:#BC9BF5;--m-lip:#8F6AD8;--board:#F7F1FF}
.tab.all-tab.active{--board-tab:var(--paper)}.tab.green.active{--board-tab:#F3FBEA}.tab.peach.active{--board-tab:#FFF6E8}.tab.purple.active{--board-tab:#F7F1FF}
.stage-home .stage-body{align-items:stretch;justify-content:center;padding:14px 20px}
.sound-grid{display:grid;grid-template-columns:repeat(8,minmax(0,1fr));gap:10px;width:100%}
.sound-grid.board-all{grid-template-columns:repeat(12,minmax(0,1fr));gap:8px}
.family-head{grid-column:1/-1;display:flex;align-items:center;gap:8px;height:22px;margin-top:2px;font:800 16px/1 var(--font-ui);color:var(--ink-f)}
.family-head small{font:700 16px var(--font-py);color:var(--ink-soft)}
/* Board keys are size containers: a short key keeps its stamp in the corner, a tall one stacks letter, stamp and word. */
.stage-home .sound-key{container:key/size;height:clamp(60px,calc((100dvh - 380px)/3),110px);padding:4px;gap:4px;border-radius:18px;box-shadow:var(--hi),0 5px 0 var(--lip)}
.stage-home .board-all .sound-key{height:clamp(50px,calc((100dvh - 500px)/6),96px);border-radius:16px}
.stage-home .sound-key strong{font-size:40px;font-size:min(40px,46cqi,48cqh)}
.stage-home .key-pic{width:min(38px,36cqi,40cqh);height:min(38px,36cqi,40cqh);font-size:min(24px,22cqi,25cqh)}
.stage-home .sound-key:hover{box-shadow:var(--hi),0 8px 0 var(--lip)}
@container key (max-height:84px){
 .stage-home .key-pic{display:none}
 .stage-home .sound-key strong{margin:0;font-size:min(30px,80cqh,56cqi)}
 .stage-home .sound-key.long strong{margin:0;font-size:min(26px,72cqh,42cqi)}
 .stage-home .key-eq{top:-9px;right:-6px;z-index:2;height:18px;padding:3px 5px;border-radius:9px;background:#fff;box-shadow:0 2px 0 var(--sun-deep)}
 .stage-home .key-eq i{width:3px}
 .stage-home .sound-key::before{top:5px;height:4px}
}
@container key (max-height:109px){.key-word{display:none}}
.ears{display:flex;align-items:center;gap:6px}
.ear{display:grid;place-items:center;width:36px;height:36px;border-radius:50%;border:2.5px dashed var(--line-lip);font-style:normal;font-size:22px;line-height:1}
.ear span{filter:brightness(0) opacity(.12)}
.ear.on{border:0;background:var(--sun-fill);box-shadow:inset 0 0 0 2.5px var(--sun)}
.ear.on span{filter:none}
.ears-flower{width:40px;height:44px;margin-left:6px;object-fit:contain;filter:brightness(0) opacity(.12)}
.ears-flower.on{filter:none}
.ears.bloomed .ear.on{background:var(--good-fill);box-shadow:inset 0 0 0 2.5px var(--good)}

/* Stop sign: zone emoji + stop number + what it holds; tapping opens the map. */
.where{display:inline-flex;align-items:center;gap:12px;min-height:56px;padding:4px 10px 4px 16px;border-radius:20px;color:var(--ink);font:800 20px var(--font-ui);transition:transform 160ms var(--ease-back)}
.where:hover{transform:translateY(-2px)}
.zone-emoji{font-size:28px;line-height:1}
.sign-preview{display:inline-flex;align-items:center;gap:6px;font-size:24px}
.sign-preview b{font:700 22px var(--font-py);background:var(--m-fill);color:var(--m-ink);border-radius:10px;padding:0 7px}
.sign-preview small{font-size:16px;color:var(--ink-soft)}
.sign-preview i{font-style:normal}
.sign-bunny{display:block}.sign-star{font-style:normal;color:var(--sun-deep);font-size:22px}
.map-chip{display:grid;place-items:center;min-width:44px;height:40px;padding:0 8px;border-radius:14px;background:var(--sky-mid);font-size:22px}

/* 识读小课堂: big cards, listened dots, then the in-place 听音找一找 table.
   Tracks shrink with the stage (1024px laptops, tablets) and never spill under the sidebar; each card is a size container,
   so its letter, picture and word scale with its own width (px first for browsers without cqi). */
.stage-learn .lesson-cards{display:grid;grid-template-columns:repeat(var(--cols),minmax(0,172px));justify-content:center;justify-content:safe center;gap:clamp(10px,1.5vw,18px);width:100%;padding:0}
.stage-learn .lesson-cards .sound-key{container:key/inline-size;width:100%;height:auto;aspect-ratio:172/212;gap:10px;padding:10px 8px;border-radius:28px}
.stage-learn .lesson-cards .sound-key strong{font-size:64px;font-size:clamp(28px,37cqi,64px);line-height:1.2;text-shadow:0 3px 0 #fff}
.stage-learn .lesson-cards .key-pic{width:56px;height:56px;width:min(56px,32.5cqi);height:min(56px,32.5cqi);font-size:34px;font-size:min(34px,20cqi);box-shadow:0 4px 0 var(--edge)}
.stage-learn .lesson-cards .key-word{font-size:16px}
.stage-learn .lesson-cards.two-rows{grid-template-columns:repeat(var(--cols),minmax(0,148px));gap:clamp(10px,1.4vw,16px)}
.stage-learn .lesson-cards.two-rows .sound-key{aspect-ratio:148/176;gap:6px}
.stage-learn .lesson-cards.two-rows .sound-key strong{font-size:54px;font-size:clamp(26px,36.5cqi,54px)}
.stage-learn .lesson-cards.two-rows .key-pic{width:48px;height:48px;width:min(48px,32.5cqi);height:min(48px,32.5cqi);font-size:28px;font-size:min(28px,19cqi)}
.stage-learn .lesson-cards .key-eq{left:auto;right:16px;top:16px;height:18px}
.stage-learn .lesson-cards .key-eq i{width:5px}
/* The game table: same picture cards, a little smaller, in a fixed grid so a reshuffle only moves them. */
.stage-learn .lesson-cards.game{grid-template-columns:repeat(var(--cols),minmax(0,150px));gap:clamp(10px,1.5vw,18px)}
.stage-learn .lesson-cards.game .sound-key{aspect-ratio:150/168}
.stage-learn .lesson-cards.game .sound-key strong{font-size:56px;font-size:clamp(26px,37cqi,56px)}
.stage-learn .lesson-cards.game .key-pic{width:46px;height:46px;width:min(46px,30.7cqi);height:min(46px,30.7cqi);font-size:28px;font-size:min(28px,18.7cqi)}
/* Narrow tablets: at most 3 cards a row, so five cards never shrink below a tap-friendly size. */
@media(min-width:601px) and (max-width:760px){
 .stage-learn .lesson-cards{grid-template-columns:repeat(min(3,var(--cols)),minmax(0,172px))}
 .stage-learn .lesson-cards.two-rows{grid-template-columns:repeat(min(3,var(--cols)),minmax(0,148px))}
 .stage-learn .lesson-cards.game{grid-template-columns:repeat(min(3,var(--cols)),minmax(0,150px))}
}
.game-card{cursor:pointer}
.game-card.tried{opacity:.45;border-color:var(--oops);box-shadow:var(--hi),0 3px 0 var(--oops-lip)}
.game-card.got{background:var(--good-fill);border-color:var(--good);box-shadow:var(--hi),0 6px 0 var(--good-lip),0 0 0 5px #2f9e3533}
.game-card.hint{outline:4px solid var(--sun);outline-offset:3px}
/* A resting card lies face down: its family edge colour with white dots, a white Zz, pressed a little into the table. */
.card-back{background-color:var(--edge);background-image:radial-gradient(#ffffff73 2.5px,transparent 3px);background-size:18px 18px;border-color:var(--edge);box-shadow:0 2px 0 var(--lip);transform:translateY(4px)}
.card-back::before{display:none}
.card-back:hover,.card-back:active{transform:translateY(4px);box-shadow:0 2px 0 var(--lip)}
.card-back .zz{width:64px;height:54px;fill:none;stroke:#fff;stroke-width:4.5;stroke-linecap:round;stroke-linejoin:round}
.card-back .zz path+path{opacity:.7}
.ear-dots{display:flex;gap:8px}
.ear-dots i{display:grid;place-items:center;width:22px;height:22px;border-radius:50%;border:2.5px dashed var(--line-lip);font:900 13px/1 var(--font-ui);font-style:normal;color:#fff}
.ear-dots i.on{border:0;background:var(--good)}
.found-count{display:flex;gap:6px}
.found-count i{display:grid;place-items:center;width:36px;height:36px;border-radius:50%;border:2.5px dashed var(--line-lip);font-size:0;font-style:normal}
.found-count i.on{border:0;font-size:28px}
.listen-again{position:relative}
.listen-again.shuffled::after{content:'🔀';position:absolute;right:-8px;top:-12px;display:grid;place-items:center;width:34px;height:34px;border-radius:50%;background:#fff;box-shadow:inset 0 0 0 2px var(--line);font-size:18px;text-shadow:none}
/* Coach hand (in #fly) and its tap ripple; with reduced motion the target gets a still outline instead. */
.coach-hand{position:fixed;width:64px;height:76px;transform-origin:29px 4px;pointer-events:none;filter:drop-shadow(0 3px 0 #4a3b5833)}
.coach-hand svg{display:block}
.coach-ripple{position:fixed;width:54px;height:54px;border-radius:50%;border:5px solid var(--focus);opacity:0;pointer-events:none}
.coach-spot{outline:4px solid var(--focus)!important;outline-offset:4px}

/* Finish card: shown inside the stage when a stop is cleared. */
.finish-card{display:flex;flex-direction:column;align-items:center;gap:10px;width:min(520px,100%);text-align:center}
img.bunny.big{height:140px;width:auto;margin:0 auto}
.finish-card h2{margin:0;font:800 30px var(--font-ui)}
.finish-stars{display:flex;gap:6px;font-size:26px}.finish-stars i{font-style:normal}
.finish-actions{display:flex;gap:12px;justify-content:center;align-items:center;flex-wrap:wrap}

/* Map page: 出发 card, then a snaking trail of stones, 7 to a row. Learn stops are round, spelling stops square. */
.map-bar{display:flex;align-items:center;gap:10px;font:800 22px var(--font-ui)}
.map-bar>b{display:inline-flex;align-items:center;min-height:52px;padding:0 16px;border-radius:20px}
.map-count{display:inline-flex;align-items:center;min-height:52px;padding:0 16px;border-radius:20px;font-size:20px}
.stage-map .stage-body{align-items:stretch;justify-content:flex-start;gap:20px}
.map-hero{display:flex;align-items:center;gap:18px;padding:10px 18px;border-radius:24px;background:#fff;box-shadow:inset 0 0 0 3px var(--line),0 5px 0 var(--line-lip)}
.map-hero img.hero-bunny{height:80px}
.hero-info{flex:1;display:flex;align-items:center;gap:14px}
.hero-info>b{font-size:28px}
.trail{display:flex;flex-direction:column;gap:30px;padding:24px 8px 8px}
.trail-row{position:relative;display:flex;align-items:center;justify-content:space-between;gap:10px;padding:0 10px}
.trail-row.rev{flex-direction:row-reverse}
.trail-row::before{content:'';position:absolute;left:42px;right:42px;top:50%;border-top:5px dotted var(--line-lip);z-index:0}
.trail-row:not(:last-child)::after{content:'';position:absolute;right:42px;top:50%;height:calc(100% + 30px);border-right:5px dotted var(--line-lip);z-index:0}
.trail-row.rev:not(:last-child)::after{right:auto;left:42px}
.zone-mark{position:relative;z-index:1;flex-shrink:0;display:flex;flex-direction:column;align-items:center;font-size:48px;line-height:1}
.zone-mark small{margin-top:2px;padding:1px 6px;border-radius:8px;background:#fff;font-size:16px;font-weight:800}
/* Stones shrink only as whole circles (aspect-ratio), never below a 44px tap target. */
.stone2{position:relative;z-index:1;scroll-margin:110px 0 40px;display:grid;place-items:center;flex:0 1 64px;min-width:44px;aspect-ratio:1;border-radius:50%;background:var(--fill);border:3px solid var(--edge);box-shadow:var(--hi),0 5px 0 var(--lip);color:var(--ink-f);--fill:#D4F7F0;--edge:#5ED3BE;--lip:#2FAE98;--ink-f:#137A69;transition:transform 160ms var(--ease-back)}
.stone2:hover{transform:translateY(-3px)}
.stone2.spell{border-radius:18px;--fill:#FFEBCC;--edge:#FFB65C;--lip:#EE8B1E;--ink-f:#A34A00}
.stone2 .stone-face{font-size:28px;line-height:1}.stone2 .stone-face b{font:700 22px var(--font-py)}
.stone2>small{position:absolute;bottom:-25px;padding:0 6px;border-radius:8px;background:var(--paper);font-size:16px;line-height:1.2;font-weight:800;color:var(--ink-soft)}
.stone2 .brick{position:absolute;right:-8px;bottom:-6px;font-style:normal;font-size:16px}
.stone2.done{--fill:var(--good-fill);--edge:var(--good);--lip:var(--good-lip);--ink-f:var(--good-lip)}
.stone2.current{flex-basis:80px;min-width:56px;--fill:var(--sun);--edge:var(--sun-deep);--lip:var(--sun-deep);--ink-f:var(--ink)}
.stone2.last{outline:4px dashed var(--ink-soft);outline-offset:5px}
.stone2 .stone-head{position:absolute;top:-34px;left:50%;transform:translateX(-50%);font-style:normal}
.stone2 .stone-star{position:absolute;right:-6px;top:-8px;font-style:normal;color:var(--sun-deep);font-size:20px;text-shadow:0 0 0 #fff,0 0 3px #fff}

/* Spelling stage: the picture card with its 🔊 badge on the left, slots and the wooden tray on the right. */
.spell-scene{display:flex;align-items:center;justify-content:center;gap:44px;width:100%}
.word-pic{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;width:190px;height:220px;padding:0;border-radius:30px;background:var(--cream);border:3px solid var(--line);box-shadow:var(--hi),0 6px 0 var(--line-lip),var(--amb);color:var(--ink)}
.word-pic .pic{display:grid;place-items:center;width:124px;height:124px;border-radius:50%;background:radial-gradient(circle,#fff 62%,#ffffff00 72%);font-size:84px;line-height:1}
.word-pic b{font-size:30px}
.word-pic.playing{border-color:var(--sun-deep);box-shadow:var(--hi),0 6px 0 var(--sun-deep),0 0 0 5px #ffd43b66}
.spk-badge{position:absolute;right:-12px;bottom:-12px;display:grid;place-items:center;width:56px;height:56px;border-radius:50%;background:var(--tone1);box-shadow:0 0 0 3px #fff,0 4px 0 3px var(--tone1-lip);color:#fff}
.spk-badge .icon{width:28px;height:28px;stroke-width:2.6}
.spell-right{display:flex;flex-direction:column;align-items:center;gap:20px}
.slots{display:flex;align-items:center;justify-content:center;gap:12px}
.slots .plus{font:900 30px var(--font-ui);color:var(--ink-soft)}
.slot{position:relative;display:grid;place-items:center;width:112px;height:120px;padding:0;border-radius:24px;background:#fff;border:4px dashed var(--edge);color:var(--ink-f);font:700 48px var(--font-py)}
.slot.whole{width:190px}
.slot .q-mark{font:900 44px var(--font-ui);font-style:normal;color:var(--edge)}
.slot:disabled{opacity:1;cursor:default}
.slot.active{border:4px solid var(--focus);background:#FFFAF0}
.slot.active::before{content:'▼';position:absolute;top:-30px;left:50%;transform:translateX(-50%);font-size:20px;color:var(--focus)}
.slot.filled{background:var(--fill);border:3px solid var(--edge);box-shadow:var(--hi),0 6px 0 var(--lip)}
.slot.playing{transform:scale(1.14);border-color:var(--sun-deep);box-shadow:var(--hi),0 6px 0 var(--sun-deep)}
.slots.merged{gap:0}
.slots.merged .plus{display:none}
.slots.merged .slot:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0;border-left-width:1.5px}
.slots.merged .slot:has(~ .slot){border-top-right-radius:0;border-bottom-right-radius:0;border-right-width:1.5px}
.result-chip{display:inline-flex;align-items:center;gap:8px;margin-left:18px;min-height:96px;padding:0 22px;border-radius:26px;background:#fff;border:3px solid var(--good);box-shadow:var(--hi),0 6px 0 var(--good-lip);color:var(--good);font-size:60px;line-height:1}
.result-chip .icon{width:30px;height:30px;stroke-width:2.6;color:var(--ink)}
.result-chip.playing{background:var(--sun-fill)}
.rule-chip{margin:0;padding:8px 16px;border-radius:16px;background:var(--sun-fill);font-size:18px;font-weight:700}
.tray{display:flex;justify-content:center;gap:18px;padding:18px 24px 26px;border-radius:26px;background:var(--cream);box-shadow:inset 0 -10px 0 #EAD8B8}
.tray .piece{position:relative;display:grid;place-items:center;width:124px;height:112px;padding:0;border-radius:24px;background:var(--fill);border:3px solid var(--edge);box-shadow:var(--hi),0 6px 0 var(--lip);color:var(--ink-f);font-size:48px;text-shadow:0 2px 0 #fff;transition:transform 160ms var(--ease-back)}
.tray .piece:hover{transform:translateY(-3px)}
.tray .piece:active{box-shadow:var(--hi),0 2px 0 var(--lip)}
.tray .piece.tried{opacity:.4;border-color:var(--oops);box-shadow:0 2px 0 var(--oops-lip)}
.tray .piece.playing{border-color:var(--sun-deep);box-shadow:var(--hi),0 6px 0 var(--sun-deep),0 0 0 5px #ffd43b66}
.stage-spell .qdot{width:44px;height:44px;font-size:24px}

/* 四声变变变. The bar is one shelf of 16 sounds (three colour groups with an icon); the stage has a notch under the chosen
   sound and four slide cards whose ball rides the tone's curve while it plays. Nothing loops at rest: the ride is a
   one-shot that lasts as long as its sound; reduced motion shows the ball at the end and the slide lit while it plays.
   The chips size with the shelf (a container): 48px wide where there is room, down to 34px, so the shelf stays one row
   from 1280px laptops down to small ones (about 870px); 172px is what the icons, paddings and gaps take. */
.sound-shelf{container-type:inline-size;flex:1 1 100%;min-width:0;display:flex;flex-wrap:wrap;align-items:center;gap:6px}
/* Dimmed during 猜一猜 with muted colours, not opacity: the shelf stays opaque, so no cloud shows through it. */
.sound-shelf.dim .shelf-group{background:#F4F7FA}.sound-shelf.dim .shelf-ico{opacity:.5}
.sound-shelf.dim .shelf-chip{background:#EEF3F7;border-color:#fff;box-shadow:0 3px 0 #D5DEE6;color:#9AA7B4}
.sound-shelf.dim .shelf-chip.active{background:#FFF1C2;border-color:#F3D77E;box-shadow:0 4px 0 #E9CF7A;color:#8E8496}
.shelf-group{display:flex;align-items:center;gap:4px;padding:4px 5px;border-radius:22px;background:#F2FAFF}
.g-vowels{--g:#fff1d6}.g-finals{--g:#ffe2c4}.g-syllables{--g:#eadcff}
.shelf-ico{width:22px;font-size:20px;line-height:1;text-align:center}
.shelf-chip{position:relative;display:grid;place-items:center;width:48px;height:52px;width:clamp(34px,calc((100cqi - 176px)/16),48px);height:clamp(46px,calc((100cqi - 176px)/16 + 4px),52px);padding:0;border-radius:16px;background:var(--g);border:2px solid #fff;box-shadow:0 3px 0 #A9D3EE;color:var(--ink);font-size:24px;font-size:clamp(19px,calc((100cqi - 176px)/32),24px);line-height:1}
.shelf-chip.active{background:var(--sun);border-color:var(--sun-deep);box-shadow:var(--hi),0 4px 0 var(--sun-deep);transform:translateY(-4px)}
.stage-tones::before{content:'';position:absolute;top:-13px;left:calc(var(--notch-x,50%) - 12px);width:20px;height:20px;background:inherit;border:3px solid var(--m-edge);border-right:0;border-bottom:0;border-radius:6px 0 0 0;transform:rotate(45deg);z-index:2}
.tone-tracks{display:grid;grid-template-columns:repeat(4,minmax(0,220px));justify-content:center;justify-content:safe center;gap:clamp(10px,1.6vw,20px);width:100%}
.tone-track-card{container-type:inline-size;position:relative;display:flex;flex-direction:column;align-items:center;gap:0;width:100%;aspect-ratio:220/250;padding:12px 10px 10px;border-radius:26px;background:linear-gradient(var(--ts),#fff 70%);border:3px solid var(--tc);box-shadow:var(--hi),0 6px 0 var(--tl),var(--amb);color:var(--ink)}
.track-badge{position:absolute;left:10px;top:10px;z-index:1;display:grid;place-items:center;width:36px;height:36px;width:clamp(28px,16cqi,36px);height:clamp(28px,16cqi,36px);border-radius:50%;background:var(--tc);box-shadow:0 3px 0 var(--tl)}
.track-badge b{font:700 20px/1 var(--font-py);color:#fff}
.tone-track-card svg.track{display:block;width:84%;height:auto;margin-top:22px;margin-top:clamp(16px,11cqi,26px);overflow:visible}
.track-hill{fill:var(--hill-far)}
.track-edge,.track-road,.track-glow{fill:none;stroke-linecap:round;stroke-linejoin:round}
.track-edge{stroke:var(--ink);stroke-width:10}.track-road{stroke:#fff;stroke-width:7}
.track-glow{stroke:var(--tc);stroke-width:7;stroke-dasharray:1;stroke-dashoffset:1;opacity:0}
.track-start{fill:#fff;stroke:var(--ink);stroke-width:1.5}
.track-star{fill:var(--sun);stroke:var(--ink);stroke-width:1.2;stroke-linejoin:round;transform-box:fill-box;transform-origin:center}
.rider{transform-box:fill-box;transform-origin:center}
.rider .ball{fill:var(--tc);stroke:var(--ink);stroke-width:1.6}.rider .shine{fill:#fff;opacity:.8}.rider .eye{fill:var(--ink)}.rider .blush{fill:#ff8fab;opacity:.8}.rider .mouth{fill:none;stroke:var(--ink);stroke-width:.8;stroke-linecap:round}
.tone-track-card strong{margin-top:auto;font-size:46px;font-size:clamp(28px,21cqi,46px);line-height:1.1;color:var(--ink)}
.tone-track-card .tm{font-style:normal;color:var(--tc)}
.tone-track-card small{font-size:16px;font-weight:800;color:var(--ink-soft)}
.tone-track-card.playing{border-color:var(--sun-deep);box-shadow:var(--hi),0 6px 0 var(--sun-deep),0 0 0 5px #ffd43b66}
:is(.tone-track-card,.tone-choice).lit .track-glow{stroke-dashoffset:0;opacity:1}:is(.tone-track-card,.tone-choice).lit .rider{transform:var(--end)}
.tone-track-card.tried{opacity:.45;border-color:var(--oops);box-shadow:var(--hi),0 3px 0 var(--oops-lip)}
.tone-track-card.got{border-color:var(--good);box-shadow:var(--hi),0 6px 0 var(--good-lip),0 0 0 5px #2f9e3533}
.tone-track-card.hint{outline:4px solid var(--sun);outline-offset:3px}
.tone-check{position:absolute;right:-12px;top:-12px;display:grid;place-items:center;width:42px;height:42px;border-radius:50%;background:var(--good);border:3px solid #fff;box-shadow:0 3px 0 var(--good-lip);color:#fff;font:900 22px/1 var(--font-ui)}
.neutral-chip{display:inline-flex;align-items:center;gap:6px;min-height:56px;padding:0 18px;border-radius:28px;background:#fff;border:3px solid var(--line);box-shadow:var(--hi),0 4px 0 var(--line-lip);color:var(--ink);font:800 18px var(--font-ui)}
.neutral-chip .pinyin{font-size:20px}
.neutral-chip.playing{background:var(--sun-fill);border-color:var(--sun-deep)}
.stage-tones .qnav-dots .qdot{font-size:18px}
@media(max-width:1150px){.shelf-chip{border-radius:14px}}

/* Red packets (batch 6). The packets, coins and the little packet are art.js SVG illustrations (ink outline, shared
   gradients); everything around them follows the surface rules. */
.packet-bar,.task-chip,.coin-track{display:inline-flex;align-items:center;gap:10px;min-height:56px;padding:0 10px;border-radius:28px;font-size:18px;font-weight:800}
.coin-tray{display:flex;gap:6px}
.tray-slot{display:grid;place-items:center;width:48px;height:48px;font-style:normal}
.tray-slot svg{display:block;width:48px;height:48px;overflow:visible}
/* Empty coin slots are the same 2.5px --line-lip dashes as every other empty slot (CSS beats the SVG attributes). */
.coin-socket circle{stroke:var(--line-lip);stroke-width:2.5}
.tray-amount{position:relative;display:inline-grid;place-items:center;min-width:84px;height:44px;padding:0 14px;overflow:hidden;border-radius:22px;background:var(--red);box-shadow:inset 0 3px 0 #ffffff55,0 4px 0 var(--red-lip);color:#fff;font:900 22px/1 var(--font-ui);text-shadow:0 2px 0 var(--red-lip)}
.tray-amount>span{grid-area:1/1}
/* The packet's moving layers: the flap and its inside turn about the hinge; the seal and the coin about their middles. */
.pk-flap,.pk-flap-in{transform-box:view-box;transform-origin:60px 10px}
.pk.closed .pk-flap-in{transform:scaleY(0)}
.pk-seal,.pk-coin{transform-box:fill-box;transform-origin:center}
.mini-pk{display:block;overflow:visible}.mp-flap{transform-box:view-box;transform-origin:20px 5px}
/* Home: four packets hang on a rope by wooden pegs; the middle two hang lower with its sag. */
.stage-packet .stage-body{gap:10px;padding-top:8px}
.packet-rack{position:relative;width:100%;max-width:920px;margin-top:34px}
.packet-line{position:absolute;left:-14px;top:8px;width:calc(100% + 28px);height:60px;overflow:visible}
.packets{position:relative;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px}
.packet{position:relative;display:flex;flex-direction:column;align-items:center;gap:2px;padding:0 4px 4px;background:none;color:var(--ink)}
.packet:nth-child(2),.packet:nth-child(3){margin-top:18px}
.peg{position:relative;z-index:2;flex-shrink:0;margin:6px 0 -16px}
.packet-swing{display:block;transform-origin:50% 0;transition:transform 200ms var(--ease-back)}
.packet-swing .pk{display:block;width:132px;height:172px}
.packet:hover .packet-swing{transform:rotate(-3deg)}
.packet-label{display:flex;align-items:center;gap:6px;margin-top:6px;font-size:17px;font-weight:800;white-space:nowrap}
.packet-label .task-ico{flex-shrink:0}
.packet-state{display:flex;align-items:center;min-height:24px;font-size:16px;font-weight:700;color:var(--ink-soft)}
.packet.opened .packet-state{color:var(--good-lip)}
.best-dots{display:inline-flex;align-items:center;gap:4px}
.best-dots i{width:13px;height:13px;border-radius:50%;border:2.5px solid var(--sun-deep)}
.best-dots i.on{background:var(--sun)}
.best-dots .notch{width:3px;height:18px;margin:0 2px;border-radius:2px;background:var(--ink-soft)}
/* An opened packet on the rope shows only a short lip of its inner flap and no glow, so the flaps never cover the rope,
   the peg or the row above; the result page keeps the full flap and glow. */
.packets .pk.empty .pk-flap-in{transform:scaleY(.5)}.packets .pk.empty .pk-glow{opacity:0}
.next-hand{position:absolute;right:2px;top:138px;z-index:3;pointer-events:none;transform:rotate(-28deg)}
.next-hand svg{display:block;width:42px;height:50px}
/* Today's claim ticket: a white ticket with notched sides and a dashed tear line. */
.claim-ticket{position:relative;display:flex;align-items:center;gap:18px;min-height:88px;padding:6px 26px;border-radius:18px;background:#fff;border:3px solid var(--line);box-shadow:var(--hi),0 5px 0 var(--line-lip),var(--amb)}
.claim-ticket::before,.claim-ticket::after{content:'';position:absolute;top:50%;width:26px;height:26px;margin-top:-13px;border-radius:50%;background:var(--board,var(--paper));border:3px solid var(--line)}
.claim-ticket::before{left:-15px;clip-path:inset(0 0 0 50%)}.claim-ticket::after{right:-15px;clip-path:inset(0 50% 0 0)}
.ticket-coin svg{display:block}
.ticket-mid{display:flex;flex-direction:column;align-items:center;gap:8px;padding:0 20px;border-left:3px dashed var(--line);border-right:3px dashed var(--line)}
.ticket-amount{font:900 34px/1.1 var(--font-ui);color:var(--red)}
.ticket-pks{display:flex;gap:4px}.ticket-pks svg{width:24px;height:31px}
.ticket-show{font-size:18px;font-weight:800;color:var(--ink)}
.paid-stamp{display:grid;place-items:center;width:92px;height:92px;border-radius:50%;border:4px solid var(--red);box-shadow:inset 0 0 0 3px #fff,inset 0 0 0 6px #ef4b3f66;color:var(--red);font:900 19px/1.1 var(--font-ui);text-align:center;transform:rotate(-12deg)}
/* Like a real stamp it overhangs the ticket a little (the ticket keeps its height), with ✓ on its own line. */
.paid-stamp{margin-block:-12px;padding:0 14px;font-size:18px;line-height:1.15}
/* A question: the task chip and the coin track in the bar; the prompt (a big speaker, a photo or a wooden sign) with its
   verdict stamp beside it; big choices. */
.task-chip{padding:0 18px 0 12px;font-size:20px}
.task-chip .task-ico{flex-shrink:0}
.coin-track{gap:6px}
.track-cell{display:grid;place-items:center;width:40px;height:40px;border-radius:50%;border:2.5px dashed var(--line-lip);font-style:normal}
.track-cell.now{border:3px dashed var(--sun-deep);background:var(--sun-fill)}
.track-cell.done,.track-cell.miss{border:0}
.track-cell svg{display:block;width:40px;height:40px;overflow:visible}
.pebble{position:relative;width:30px;height:23px;border-radius:50% 50% 44% 44%;background:#D3CADB;box-shadow:inset 0 -4px 0 #B6ABC2}
.pebble::after{content:'✕';position:absolute;right:-9px;top:-11px;font:900 16px/1 var(--font-ui);color:var(--oops-lip)}
.track-pk{display:grid;margin-left:4px}
.stage-packet.in-task .stage-body{gap:16px;text-align:center}
.task-top{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:22px;width:100%}
.task-prompt{display:grid;place-items:center}
.task-prompt .listen-big{width:150px;height:150px;margin:0}
.task-prompt .listen-big .spk{width:84px;height:84px}
.polaroid{position:relative;display:flex;flex-direction:column;align-items:center;gap:4px;width:170px;height:190px;padding:12px 12px 0;border-radius:10px;background:#fff;box-shadow:0 0 0 3px var(--line),0 7px 0 var(--line-lip),var(--amb);rotate:-3deg}
.pol-pic{display:grid;place-items:center;width:146px;height:126px;border-radius:6px;background:linear-gradient(var(--sky-mid),#F2FBE8);font-size:96px;line-height:1}
.polaroid b{font-size:26px;color:var(--ink)}
.polaroid .spk-badge{right:-14px;top:-14px;bottom:auto;width:48px;height:48px}
.polaroid.playing{box-shadow:0 0 0 3px var(--sun-deep),0 7px 0 var(--sun-deep),0 0 0 9px #ffd43b66}
.read-sign{position:relative;display:grid;place-items:center;min-width:230px;height:132px;padding:0 36px;border-radius:22px;background:linear-gradient(#EDC183,#DDA058);box-shadow:inset 0 4px 0 #ffffff66,inset 0 -6px 0 #C98A44,0 7px 0 #A8692B,var(--amb)}
.read-sign::before,.read-sign::after{content:'';position:absolute;top:14px;width:12px;height:12px;border-radius:50%;background:#A8692B;box-shadow:inset 0 2px 0 #7A4B2A}
.read-sign::before{left:14px}.read-sign::after{right:14px}
.read-sign b{font-size:84px;line-height:1;color:var(--ink);text-shadow:0 2px 0 #F8DDB2}
.task-feedback{justify-self:start;display:flex;align-items:center;gap:10px;min-height:56px;font-size:24px;font-weight:900}
.fb-stamp{display:grid;place-items:center;width:56px;height:56px;border-radius:50%;color:#fff;rotate:-8deg}
.fb-stamp .icon{width:32px;height:32px;stroke-width:3.2}
.task-feedback.good .fb-stamp{background:var(--good);box-shadow:0 0 0 3px #fff,0 4px 0 3px var(--good-lip)}.task-feedback.good b{color:var(--good)}
.task-feedback.oops .fb-stamp{background:var(--oops);box-shadow:0 0 0 3px #fff,0 4px 0 3px var(--oops-lip)}.task-feedback.oops b{color:var(--oops-lip)}
.task-choices{display:flex;justify-content:center;gap:20px;flex-wrap:wrap;margin:0}
.task-choice{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;width:150px;height:130px;border-radius:var(--rad-card);background:#fff;border:3px solid var(--line);box-shadow:var(--hi),0 6px 0 var(--line-lip);font-size:52px;line-height:1.1;color:var(--ink)}
.task-choice.fam{background:var(--fill);border-color:var(--edge);box-shadow:var(--hi),0 6px 0 var(--lip);color:var(--ink-f);text-shadow:0 2px 0 #fff}
.task-choice.fam::before{content:'';position:absolute;left:12%;top:7px;width:30%;height:6px;border-radius:3px;background:#fff;opacity:.7}
.task-choice:hover:not(:disabled){transform:translateY(-3px)}
.task-choice.picture{width:160px;height:160px}
.task-choice .pic{font-size:80px;line-height:1}
.task-choice.picture small{font-size:18px;font-weight:800}
.tone-choice{width:140px;height:140px;gap:0;background:var(--ts);border-color:var(--tc);box-shadow:var(--hi),0 6px 0 var(--tl)}
.tone-choice svg.track{display:block;width:92px;height:auto;overflow:visible}
.tone-choice b{font-size:36px}.tone-choice .tm{font-style:normal;color:var(--tc)}
.task-choice.correct{background:var(--good-fill);border-color:var(--good);box-shadow:var(--hi),0 6px 0 var(--good-lip);color:var(--good-lip)}
.task-choice.wrong{background:var(--oops-fill);border-color:var(--oops);box-shadow:var(--hi),0 6px 0 var(--oops-lip);color:var(--oops-lip)}
.task-choice.dim{opacity:.45}
/* On a tone slide the tone colour gives way to the verdict, so a green tone 2 never reads as right or an orange tone 3 as wrong. */
.tone-choice.correct{--tc:var(--good-lip)}.tone-choice.wrong{--tc:var(--oops-lip)}.tone-choice.dim{--ts:#fff;--tc:#BCAFCB;--tl:var(--line-lip)}
.task-choice.playing{border-color:var(--sun-deep)}
.pick-stamp{position:absolute;right:-14px;top:-14px;display:grid;place-items:center;width:42px;height:42px;border-radius:50%;background:var(--good);box-shadow:0 0 0 3px #fff,0 3px 0 3px var(--good-lip);color:#fff}
.pick-stamp .icon{width:24px;height:24px;stroke-width:3.4}
.cue-ring{position:absolute;inset:-9px;border-radius:30px;border:4px solid var(--good);opacity:0;pointer-events:none}
.cue-hand{position:absolute;left:50%;top:-48px;font-size:34px;font-style:normal;line-height:1;opacity:0;transform:translate(-50%,0);pointer-events:none}
/* The big round speaker for listening questions; its waves pulse only while the question sounds. */
.listen-big{position:relative;display:grid;place-items:center;flex-shrink:0;width:96px;height:96px;margin:0 auto 8px;padding:0;border-radius:50%;background:var(--tone1);color:#fff;box-shadow:var(--hi),0 6px 0 var(--tone1-lip),var(--amb);transition:transform 160ms var(--ease-back)}
.listen-big:hover{transform:translateY(-2px)}.listen-big:active{box-shadow:var(--hi),0 1px 0 var(--tone1-lip)}
.listen-big .spk{width:58px;height:58px;fill:none;stroke:currentColor;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round}
.listen-big.playing{box-shadow:var(--hi),0 6px 0 var(--tone1-lip),0 0 0 6px #ffd43b88}
.listen-big.playing .w1,.listen-big.playing .w2{animation:wave 600ms ease-in-out infinite alternate}
.listen-big.playing .w2{animation-delay:150ms}
@keyframes wave{from{opacity:.2}to{opacity:1}}
/* The result: packet and bunny on the left; title, tray (or the key bar and 还差 N 个) and the five review chips. */
.stage-packet.in-result .stage-body{padding-block:10px}
.result-body{display:flex;align-items:center;justify-content:center;gap:40px;width:100%}
.result-art{position:relative;display:flex;align-items:flex-end;gap:0}
.result-pk{position:relative;display:block;width:200px;height:260px;margin-top:60px}
.result-pk .pk{display:block;width:200px;height:260px;transform-origin:50% 90%}
.pk-star{position:absolute;left:50%;top:-30px;width:64px;height:64px;margin-left:-32px;object-fit:contain}
.result-art img.result-bunny{height:190px;margin-left:-18px}
.result-info{display:flex;flex-direction:column;align-items:flex-start;gap:14px;text-align:left}
.result-info h2{margin:0;font-size:34px}
.result-info p{margin:-6px 0 0;font-size:18px;font-weight:700;color:var(--ink-soft)}
.key-bar{display:flex;align-items:center;gap:16px}
.key-cells{display:flex;gap:6px;padding:4px 10px;border-radius:30px;border:2px solid #fff;background:#F7FCFF;box-shadow:0 4px 0 #A9D3EE}
.key-cells i{display:grid;place-items:center;width:44px;height:44px;font-style:normal}.key-cells svg{display:block;width:44px;height:44px;overflow:visible}
.need-bubble{position:relative;padding:4px 18px;border-radius:24px;background:var(--sun-fill);border:3px solid var(--sun);box-shadow:0 4px 0 var(--sun-deep);font-size:20px;font-weight:800}
.need-bubble::before{content:'';position:absolute;left:-11px;top:50%;width:14px;height:14px;margin-top:-7px;background:var(--sun-fill);border-left:3px solid var(--sun);border-bottom:3px solid var(--sun);transform:rotate(45deg)}
.need-bubble b{font:900 36px/1 var(--font-py);color:var(--oops-lip)}
.review{display:flex;flex-wrap:wrap;gap:10px;margin:0;padding:0;list-style:none}
.review-chip{display:inline-flex;align-items:center;gap:6px;height:48px;min-width:56px;padding:0 14px 0 8px;border-radius:24px;background:#fff;font-size:20px}
.review-chip i{display:grid;place-items:center;width:28px;height:28px;border-radius:50%;font:900 16px/1 var(--font-ui);font-style:normal;color:#fff}.review-chip i .icon{width:17px;height:17px;stroke-width:2.8}
.review-chip.done{box-shadow:inset 0 0 0 3px var(--good),0 4px 0 var(--good-lip);color:var(--good-lip)}.review-chip.done i{background:var(--good)}
.review-chip.miss{box-shadow:inset 0 0 0 3px var(--oops),0 4px 0 var(--oops-lip);color:var(--oops-lip)}.review-chip.miss i{background:var(--oops)}
.review-chip.playing{background:var(--sun-fill)}
.dock-main.try-again{background:var(--sun);color:var(--ink);text-shadow:none;box-shadow:var(--hi),0 6px 0 var(--sun-deep),var(--amb)}

/* Dialogs: rewards (我的小花园) and the parent area. */
dialog{max-width:640px;padding:26px 28px;border:0;border-radius:var(--rad-panel);background:var(--paper);color:var(--ink);box-shadow:var(--hi),0 8px 0 var(--line-lip),0 20px 80px #2b3b6044}
dialog[data-kind=panel]{max-width:640px}
dialog::backdrop{background:#3b4a7a55;backdrop-filter:blur(3px)}
/* A dialog pops up only when it really opens (@starting-style); new content in an open dialog does not pop again. */
dialog{transition:opacity 200ms var(--ease-out),scale 300ms var(--ease-back),translate 300ms var(--ease-back),display 300ms allow-discrete,overlay 300ms allow-discrete}
dialog:not([open]){opacity:0;scale:.92}
@starting-style{dialog[open]{opacity:0;scale:.85;translate:0 30px}}
dialog::backdrop{transition:opacity 200ms var(--ease-out),display 300ms allow-discrete,overlay 300ms allow-discrete}
dialog:not([open])::backdrop{opacity:0}
@starting-style{dialog[open]::backdrop{opacity:0}}
.dialog-head{display:flex;justify-content:space-between;align-items:center;gap:15px}
.dialog-head h2{margin:0 0 8px;font-size:26px}
.dialog-head button{display:grid;place-items:center;flex-shrink:0;width:48px;height:48px;border-radius:50%;background:#fff;box-shadow:inset 0 0 0 3px var(--line),0 3px 0 var(--line-lip);font-size:26px;color:var(--ink-soft)}
.dialog-head button .icon{width:24px;height:24px;stroke-width:3}
.dialog-actions{display:flex;gap:10px;flex-wrap:wrap;margin-top:20px}
dialog p{font-size:16px;line-height:1.7;color:var(--ink-soft)}
dialog h3{margin:0 0 8px;font-size:18px}
dialog a{text-decoration:underline}
.danger{padding:12px 16px;border-radius:14px;background:var(--oops-fill);color:var(--oops-lip);font-size:14px;font-weight:700}
.muted{color:var(--ink-soft)}
.parent-box{padding:12px 16px;border-radius:18px;background:#fff;border:2px solid var(--line)}
.parent-box h3{display:flex;justify-content:space-between;align-items:center;gap:8px;flex-wrap:wrap}
.balance{padding:2px 10px;border-radius:10px;background:var(--sun-fill);font-size:14px}
/* 我的小花园: two columns (today across the top, tree | week, the shop across the bottom); one column under 760px. */
dialog[data-kind=garden]{max-width:880px;max-height:94dvh;padding:18px 24px 20px}
.g-balance{display:inline-flex;align-items:center;gap:4px;margin-right:auto;height:44px;padding:0 16px 0 8px;border-radius:22px;background:var(--sun-fill);box-shadow:inset 0 0 0 2px var(--sun),0 3px 0 var(--sun-deep);font:700 22px var(--font-py);color:var(--sun-ink)}
.g-balance img{width:30px;height:32px;object-fit:contain}
.garden{display:grid;grid-template-columns:minmax(0,1.1fr) minmax(0,1fr);grid-template-areas:'today today' 'tree week' 'shop shop';gap:12px;margin-top:4px}
.garden section{padding:10px 14px;border-radius:20px;background:#fff;border:2px solid var(--line);box-shadow:0 4px 0 var(--line-lip)}
.garden h3{display:flex;align-items:baseline;gap:12px;margin:0 0 4px;font-size:18px}
.garden h3 small{font-size:16px;font-weight:700;color:var(--ink-soft)}
.g-today{grid-area:today}.g-tree{grid-area:tree}.g-week{grid-area:week}.g-shop{grid-area:shop}
.pots{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}
.pot{display:flex;flex-direction:column;align-items:center;gap:2px;padding:2px;border-radius:16px;background:none;color:var(--ink-soft)}
.pot-art{position:relative;display:block;width:96px;height:110px}
.pot-svg{position:absolute;left:0;bottom:0;width:96px;height:60px}
.pot-top{position:absolute;left:50%;bottom:46px;display:grid;place-items:end center;width:70px;height:66px;margin-left:-35px}
.pot-flower{display:block;width:56px;height:auto;transform-origin:50% 100%}
.bud-svg{width:52px;height:56px}
.pot-rule{display:flex;align-items:center;gap:6px;font-size:16px;font-weight:700;line-height:1.25;text-align:left}
.pot-rule i{flex-shrink:0;display:grid;place-items:center;width:30px;height:30px;border-radius:50%;background:var(--cream);font-size:17px;font-style:normal}
.pot.on{color:var(--ink)}.pot.on .pot-rule i{background:var(--sun-fill)}
.g-tree{display:grid;grid-template-columns:120px minmax(0,1fr);grid-template-rows:1fr auto;column-gap:14px;align-items:center}
.tree-art{grid-row:1/3;position:relative;display:grid;place-items:end center;width:120px;height:150px}
.mound{position:absolute;left:4px;right:4px;bottom:0;height:24px;border-radius:50% 50% 10px 10px/100% 100% 10px 10px;background:#B07A4A;box-shadow:inset 0 5px 0 #C99363}
.tree-img{position:relative;max-width:112px;max-height:128px;margin-bottom:14px;object-fit:contain;transform-origin:50% 100%}
.tree-info{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px}
.lv-name{font-size:24px}
.lv-stars{display:inline-flex;align-items:center;gap:4px;font:700 20px var(--font-py);color:var(--sun-ink)}.lv-stars img{width:26px;height:26px}
.tree-info small{flex-basis:100%;font-size:16px;font-weight:700;color:var(--good-lip)}
.vine{position:relative;padding-bottom:30px}
.vine-line{position:absolute;left:0;top:0;width:100%;height:36px}
.vine-nodes{position:relative;display:flex;justify-content:space-between;height:36px;margin:0;padding:0;list-style:none}
.vine-nodes li{position:relative;display:grid;place-items:center;width:30px}
.vine-nodes img{max-width:30px;max-height:34px;object-fit:contain;filter:brightness(0) opacity(.12)}
.vine-nodes li.reached img{filter:none}
.vine-tip{position:absolute;top:100%;left:50%;margin-top:2px;padding:1px 8px;border-radius:12px;background:var(--sun-fill);box-shadow:inset 0 0 0 2px var(--sun);font-size:16px;font-weight:800;white-space:nowrap;transform:translateX(-50%)}
.vine-nodes li:last-child .vine-tip{left:auto;right:0;transform:none}
.stamps{display:flex;justify-content:space-between;gap:4px}
.stamp-cell{position:relative;display:grid;place-items:end center;width:46px;height:62px;padding-bottom:3px;border-radius:14px;background:var(--cream);border:2px dashed var(--line-lip)}
.stamp-cell small{position:absolute;left:0;right:0;top:3px;font-size:16px;font-weight:800;line-height:1;text-align:center;color:var(--ink-soft)}
.stamp-cell.today{border:3px solid var(--sun);background:var(--sun-fill)}
.stamp{display:block;width:34px;height:34px;rotate:var(--r,0deg)}.stamp svg{display:block;width:100%;height:100%}
.medal-row{display:flex;align-items:center;gap:10px;margin-top:10px}
/* Five dots on a dotted path that runs on to the medal. */
.medal-dots{display:flex;gap:8px;flex:1;align-items:center;justify-content:space-between;margin-right:6px;background:linear-gradient(90deg,var(--line-lip) 50%,transparent 0) 0 50%/8px 2.5px repeat-x}
.medal-dots i{width:16px;height:16px;border-radius:50%;border:2.5px dashed var(--line-lip);background:#fff}.medal-dots i.on{background:var(--sun);border:2.5px solid var(--sun-deep)}
.medal{width:auto;height:58px;filter:brightness(0) opacity(.12)}.medal.got{filter:none}
.medal-row b{font:700 20px var(--font-py)}
/* The shop row scrolls under soft edges (a card fading out says there is more), out to the section's border. */
.prize-row{display:flex;gap:12px;overflow-x:auto;margin-inline:-14px;padding:4px 14px 6px;scroll-padding-inline:14px;scroll-snap-type:x mandatory;-webkit-mask-image:linear-gradient(90deg,transparent,#000 14px,#000 calc(100% - 40px),transparent);mask-image:linear-gradient(90deg,transparent,#000 14px,#000 calc(100% - 40px),transparent)}
.prize-card{flex:0 0 156px;display:flex;flex-direction:column;align-items:center;justify-content:space-between;gap:2px;height:184px;padding:10px 8px;border-radius:18px;background:var(--cream);border:2px solid var(--line);scroll-snap-align:start}
.prize-card.ok{background:var(--good-fill);border-color:#A8DE9A}
.prize-pic{position:relative;display:grid;place-items:center;width:62px;height:62px;flex-shrink:0}
.prize-ring{position:absolute;inset:0;width:62px;height:62px}
.prize{position:relative;font-size:30px;line-height:1}
.prize-card b{font-size:17px;text-align:center;line-height:1.2}
.prize-card small{font-size:16px;font-weight:700;color:var(--ink-soft)}
.prize-need{font-size:16px;font-weight:800;color:var(--oops-lip)}
.btn.get{min-height:48px;padding:0 18px;border-radius:16px;font-size:19px}
.garden h3 .pending{padding:0 10px;border-radius:12px;background:var(--sun-fill);color:var(--ink)}
/* Bursts inside a dialog play in its own layer, above the backdrop. */
.dialog-fx{position:absolute;left:50%;top:40%;width:0;height:0;z-index:5;pointer-events:none}
.confirm-prize{text-align:center}.confirm-prize span{display:block;font-size:70px}
.confirm-prize p{font-size:22px;font-weight:800;line-height:1.5;color:var(--ink)}.confirm-prize+.dialog-actions{justify-content:center}
.gate{display:flex;gap:12px;align-items:center;flex-wrap:wrap}
.gate label{display:flex;align-items:center;gap:10px}
.gate-q{font-size:26px;font-weight:800}
.gate input,.shop-form input{width:110px;padding:6px 10px;border:2px solid var(--line);border-radius:12px;background:#fff;color:var(--ink);font-size:20px}
.shop-form{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-top:10px}
.shop-form input{font-size:15px}
.shop-form #shop-emoji{width:58px;text-align:center}.shop-form #shop-name{flex:1;min-width:150px;width:auto}.shop-form #shop-cost{width:90px}
.ledger{width:100%;margin-bottom:8px;border-collapse:collapse;font-size:14px}
.ledger th,.ledger td{padding:5px 4px;border-bottom:1px dashed var(--line);text-align:left}
.ledger small{color:var(--ink-soft)}
.redeem-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:5px 0;border-bottom:1px dashed var(--line)}
.redeem-row span{flex:1;min-width:160px;font-size:14px}
.parent-box{margin-top:12px}
.parent-box p{font-size:14px;line-height:1.7}

/* Effects: stars and confetti burst from the middle (#fx); flyers, sparkles and the coach hand live in #fly. */
.fx{position:fixed;left:50%;top:45%;width:0;height:0;z-index:200;pointer-events:none}
.fx-bit{position:absolute;left:-15px;top:-15px;font-size:30px;animation:burst 1.2s cubic-bezier(.15,.8,.3,1) var(--d) both}
@keyframes burst{0%{transform:translate(0,0) scale(.2);opacity:1}70%{opacity:1}100%{transform:translate(var(--x),calc(var(--y) + 60px)) scale(1.1) rotate(200deg);opacity:0}}
.fly{position:fixed;inset:0;z-index:210;pointer-events:none;overflow:hidden}
.fly-bit{position:fixed;font-size:22px;line-height:1;pointer-events:none}
.fly-star{display:block;width:40px;height:40px}
/* The toast: the bunny peeks over its top edge (bunny-peek.png). */
#toast{padding:14px 22px;border-radius:20px;background:var(--sun-fill);border:3px solid var(--sun);box-shadow:var(--hi),0 5px 0 var(--sun-deep),var(--amb);color:var(--ink);font-size:18px;font-weight:800}
#toast .bunny-peek{position:absolute;left:12px;bottom:calc(100% - 6px);width:auto;height:56px;pointer-events:none}
/* 正在读 … is for screen readers only: the playing control itself lights up. */
.audio-status{width:1px;height:1px;overflow:hidden;white-space:nowrap;clip-path:inset(50%);font-size:16px}
.error-banner{padding:12px 18px;border-radius:16px;background:var(--oops-fill);border:3px solid var(--oops);box-shadow:0 4px 0 var(--oops-lip);color:#8A3A00;font-size:16px;font-weight:700}
.error-banner button{background:none;color:inherit;font-size:24px}

/* Press spring: every control squashes a little under the finger and springs back. The individual scale and translate
   properties compose with hover lifts, entry animations and resting transforms (a sleeping card's offset). */
:is(.btn,.dock-main,.sound-key,.piece,.slot.filled,.task-choice,.tone-track-card,.shelf-chip,.packet,.stone2,.nav-item,.go-btn,.hud,.gear,.listen-big,.word-pic,.helper,.qnav-step,.stage-close,.speed,.tab,.where,.neutral-chip,.result-chip,.dialog-head button,.polaroid,.review-chip,.pot){transition:scale 320ms var(--ease-spring),translate 320ms var(--ease-spring),transform 160ms var(--ease-back)}
:is(.btn,.dock-main,.sound-key,.piece,.slot.filled,.task-choice,.tone-track-card,.shelf-chip,.packet,.stone2,.nav-item,.go-btn,.hud,.gear,.listen-big,.word-pic,.helper,.qnav-step,.stage-close,.speed,.tab,.where,.neutral-chip,.result-chip,.dialog-head button,.polaroid,.review-chip,.pot):active:not(:disabled){scale:1.05 .92;translate:0 4px;transition-duration:90ms}

/* A wrong card shakes in full colour and only then fades to its tried look (after the 380ms wobble); reduced motion
   forces every transition to 1ms. */
:is(.game-card,.tray .piece,.tone-track-card){transition:scale 320ms var(--ease-spring),translate 320ms var(--ease-spring),transform 160ms var(--ease-back),opacity 220ms ease-out 300ms,border-color 160ms}

/* Page transitions (View Transitions, ≤380ms; app.js picks the kind and a tap skips it). Only the page content moves:
   the sidebar and top bar keep their own names and cross-fade briefly, the root (sky and scenery, unchanged) swaps at
   once, and #fly has its own layer so the sticker stars of a module change sit above the iris. The iris animates
   clip-path, the one approved exception to "transform and opacity only". Hits pass through to the new page. */
::view-transition{pointer-events:none}
#main{view-transition-name:stage-main}
.sidebar{view-transition-name:sidebar}
.topbar{view-transition-name:topbar}
.fly{view-transition-name:fly}
::view-transition-old(root),::view-transition-old(fly){animation:none;opacity:0}
::view-transition-new(root),::view-transition-new(fly),::view-transition-group(fly),::view-transition-group(stage-main){animation:none}
::view-transition-group(sidebar),::view-transition-group(topbar),::view-transition-old(sidebar),::view-transition-new(sidebar),::view-transition-old(topbar),::view-transition-new(topbar){animation-duration:200ms}
::view-transition-old(stage-main),::view-transition-new(stage-main){mix-blend-mode:normal;transform-origin:50% 40vh}
/* The old page image is drawn where the new #main is (its group does not move); --vt-shift puts it back where it was on
   screen, so a scrolled page does not jump to its top as it leaves. */
html[data-vt]::view-transition-old(stage-main),html[data-vt]::view-transition-old(stage-body){translate:0 var(--vt-shift,0px)}
/* The iris is a wipe: one registered radius clips the new page in and masks the same circle out of the old one, so the
   old page never shows through the see-through gaps of the new. Each image has the tap in its own coordinates. */
@property --iris{syntax:'<length>';inherits:false;initial-value:0px}
html[data-vt=iris]::view-transition-new(stage-main),html[data-vt=zoom]::view-transition-new(stage-main){clip-path:circle(var(--iris) at var(--vt-x,50%) var(--vt-y,40vh))}
html[data-vt=iris]::view-transition-old(stage-main),html[data-vt=zoom]::view-transition-old(stage-main){-webkit-mask-image:radial-gradient(circle at var(--vt-xo,50%) var(--vt-yo,40vh),transparent var(--iris),#000 calc(var(--iris) + 1px));mask-image:radial-gradient(circle at var(--vt-xo,50%) var(--vt-yo,40vh),transparent var(--iris),#000 calc(var(--iris) + 1px))}
html[data-vt=iris]::view-transition-new(stage-main),html[data-vt=iris]::view-transition-old(stage-main){animation:vt-iris 360ms cubic-bezier(.22,.9,.3,1) both}
/* Zoom (into a stop from the map, back out to it): the same wipe, both pages scaling about the tap, so they never show
   through each other. Into a stop the new page grows from the stone while the map flies past; out, the reverse. */
html[data-dir=fwd]{--dx:56px;--z-old:1.15;--z-new:.8}html[data-dir=back]{--dx:-56px;--z-old:.85;--z-new:1.15}
html[data-vt=zoom]::view-transition-new(stage-main){transform-origin:var(--vt-x,50%) var(--vt-y,40vh);animation:vt-iris 340ms cubic-bezier(.22,.9,.3,1) both,vt-zoom-in 340ms var(--ease-out) both}
html[data-vt=zoom]::view-transition-old(stage-main){transform-origin:var(--vt-xo,50%) var(--vt-yo,40vh);animation:vt-iris 340ms cubic-bezier(.22,.9,.3,1) both,vt-zoom-out 340ms var(--ease-out) both}
/* Slide (another stop of the same module): the page head, bar, stage panel and dock swap in place; only the stage body
   slides, over the new panel's opaque cream, so nothing is doubled. */
html[data-vt=slide] .stage-body{view-transition-name:stage-body}
::view-transition-group(stage-body){animation:none}
html[data-vt=slide]::view-transition-old(stage-main){animation:none;opacity:0}
html[data-vt=slide]::view-transition-new(stage-main){animation:none}
html[data-vt=slide]::view-transition-old(stage-body){animation:vt-slide-out 140ms var(--ease-in) both}
html[data-vt=slide]::view-transition-new(stage-body){animation:vt-slide-in 300ms var(--ease-back) 60ms both}
@keyframes vt-iris{from{--iris:0px}to{--iris:150vmax}}
@keyframes vt-slide-out{to{transform:translateX(calc(var(--dx) * -1));opacity:0}}
@keyframes vt-slide-in{from{transform:translateX(var(--dx));opacity:0}}
@keyframes vt-zoom-out{to{transform:scale(var(--z-old))}}
@keyframes vt-zoom-in{from{transform:scale(var(--z-new))}}
.fly-bit.sticker img{display:block;width:36px;height:36px;object-fit:contain}
.fx-bit .fx-img{display:block;width:34px;height:34px;object-fit:contain}
/* Ceremony layers in #fly: seal sparks, the flower that blooms to the HUD, the level-up and the four-packet finale. */
.spark4 svg{display:block;width:20px;height:20px;fill:#FFD34D;stroke:#E8A600;stroke-width:1.2}
.flyer .coin-svg{display:block}
.bloom{position:fixed;display:grid;place-items:center;transform:translate(-50%,-50%);pointer-events:none}
.bloom::before,.levelup::before{content:'';position:absolute;left:50%;top:50%;width:220px;height:220px;margin:-110px 0 0 -110px;border-radius:50%;background:radial-gradient(circle,#fff 0,#fffbe9ee 45%,#fff3c200 70%)}
.bloom-flower{position:relative;display:block;width:120px;height:auto}
.bloom-plus{position:absolute;right:-30px;top:-4px;display:grid;place-items:center;height:40px;padding:0 12px;border-radius:20px;background:var(--sun);box-shadow:0 0 0 3px #fff,0 4px 0 3px var(--sun-deep);font:900 22px/1 var(--font-py);color:var(--ink)}
.bloom-rule{position:absolute;left:-30px;bottom:4px;display:grid;place-items:center;width:48px;height:48px;border-radius:50%;background:#fff;box-shadow:0 0 0 3px var(--line),0 4px 0 3px var(--line-lip);font-size:26px}
.levelup{position:fixed;display:grid;place-items:center;width:200px;height:200px;transform:translate(-50%,-50%);pointer-events:none}
.levelup img{position:relative;width:auto;height:140px;object-fit:contain}
.lv-dot{position:absolute;left:50%;top:50%;width:14px;height:14px;border-radius:50%;background:var(--sun);box-shadow:0 0 0 2px #fff}
.lv-dot:nth-child(3n+2){background:#FF8FAB}.lv-dot:nth-child(3n){background:#8FD16A}
.lv-chip{position:absolute;bottom:-20px;padding:4px 16px;border-radius:20px;background:#fff;box-shadow:0 0 0 3px var(--good),0 4px 0 3px var(--good-lip);font-size:22px;white-space:nowrap;color:var(--good-lip)}
.finale{position:fixed;width:0;height:0;pointer-events:none}
.finale>*{position:absolute;left:0;top:0}
.fin-glow{width:600px;height:600px;border-radius:50%;background:radial-gradient(circle,#FFE58FDD 0,#FFE58F66 40%,#FFE58F00 70%)}
.fin-pk svg{width:84px;height:109px}
.fin-coin svg{display:block}
.confetti{width:8px;height:14px;border-radius:2px}

/* Entry choreography, keyed by the view (app.js viewKey): only a render that changes the view carries main.enter with
   its level. page: title bunny, bubble, bar, stage, cards, main button; stop: stage, cards, main button; view: only
   [data-enter] in the stage body. data-enter="stop" marks cards that a view change moves by itself (a deal, a new
   question sliding in, a new sound), so they enter only with their page or stop. Inside a page transition (data-vt) the
   stage leaves its own entry to the transition. Fills are backwards only: nothing lingers once an entry is over, and the
   last element is done by 660ms. Elements stay tappable throughout. */
main.enter[data-level=page] .page-head .helper img.bunny:not(.swapped){animation:bunny-in 420ms var(--ease-back) backwards,idle 4s ease-in-out 420ms infinite}
main.enter[data-level=page] .bubble{animation:pop-in 260ms var(--ease-back) 80ms backwards}
main.enter[data-level=page] .bar:not(:has(.board-tabs)){animation:drop-in 300ms var(--ease-out) 40ms backwards}
main.enter:is([data-level=page],[data-level=stop]):not([data-vt]) .stage{animation:panel-in 300ms var(--ease-out) 60ms backwards}
main.enter [data-enter]:not([data-enter=stop]),main.enter:not([data-level=view]) [data-enter=stop]{animation:pop-in 260ms var(--ease-back) backwards;animation-delay:calc(120ms + min(var(--i,0),8) * 40ms)}
main.enter .sound-grid [data-enter],main.enter .trail [data-enter]{animation-delay:calc(60ms + min(var(--i,0),24) * 12ms)}
main.enter:not([data-level=view]) .dock-main{animation:pop-in 260ms var(--ease-back) 300ms backwards}
/* The rules below share the generic rule's specificity, so they must come after it. A red-packet question: the prompt
   grows in and the choices rise one after another (50ms apart). */
main.enter .task-prompt[data-enter]{animation:prompt-in 300ms var(--ease-back) backwards}
main.enter .task-choices [data-enter]{animation:rise-in 300ms var(--ease-back) backwards;animation-delay:calc(80ms + var(--i,0) * 50ms)}
@keyframes prompt-in{from{transform:scale(.85);opacity:0}}
@keyframes rise-in{from{transform:translateY(24px) scale(.92);opacity:0}}
/* The first thing of a swapped stage shows at once, so the stage never stands empty: the result's packet (while its open
   ceremony is pending it has no entry at all: the shake is its entry) and the finish card, which pops from small. */
main.enter .result-art[data-enter]{animation-delay:0ms}
main.enter .finish-card[data-enter]{animation:card-pop 320ms var(--ease-back) backwards}
@keyframes card-pop{from{transform:scale(.6) translateY(20px);opacity:.2}}
/* Inside a page transition the wipe (or the slide) brings the new page in, so its content is there from the first frame
   and keeps only a small scale bounce; the map's trail fills in within the zoom; a slid stage body carries its cards. */
main.enter[data-vt] [data-enter],main.enter[data-vt]:not([data-level=view]) .dock-main,main.enter[data-vt][data-level=page] .bubble{animation-name:pop-scale;animation-delay:calc(min(var(--i,0),8) * 30ms)}
main.enter[data-vt] :is(.trail,.sound-grid) [data-enter]{animation-delay:calc(min(var(--i,0),24) * 8ms)}
main.enter[data-vt][data-level=page] .bar:not(:has(.board-tabs)){animation-name:drop-only}
main.enter[data-vt=slide] .stage-body [data-enter]{animation:none}
@keyframes pop-scale{from{transform:scale(.85)}}
@keyframes drop-only{from{transform:translateY(-14px)}}
/* The finish card pops, then its stars land one after another (80ms apart). */
main.enter .finish-stars [data-enter]{animation:star-in 300ms var(--ease-back) backwards;animation-delay:calc(220ms + var(--i,0) * 80ms)}
/* Under the opening curtain the page's entry waits; it starts as the curtain lifts. */
html[data-opening] main *{animation-play-state:paused!important}
@keyframes bunny-in{from{transform:translateY(26px) scale(.85);opacity:.5}}
@keyframes pop-in{from{transform:scale(.85);opacity:0}}
@keyframes drop-in{from{transform:translateY(-14px);opacity:0}}
@keyframes panel-in{from{transform:translateY(20px) scale(.97);opacity:0}}
@keyframes star-in{from{transform:translateY(-30px) scale(.3) rotate(-40deg);opacity:0}}

/* MG opening (app.js playOpening; WAAPI does the moving): sky, the cheering bunny, the title, flowers and stars. */
.opening{position:fixed;inset:0;z-index:230;display:grid;place-items:center;background:linear-gradient(180deg,var(--sky-top) 0,var(--sky-mid) 45%,#F2FBE8 100%);cursor:pointer}
.op-stage{position:relative;display:flex;flex-direction:column;align-items:center;gap:4px}
.opening img.op-bunny{position:relative;z-index:1;height:210px}
.opening .scenery{position:absolute}
.op-title{position:relative;z-index:1;margin:0;font:900 56px/1.2 var(--font-ui);color:var(--ink);letter-spacing:4px}
.op-title span{display:inline-block;text-shadow:0 3px 0 #fff,0 7px 0 var(--sun-deep)}
.op-bit{position:absolute;left:50%;top:44%;width:56px;height:56px;margin:-28px 0 0 -28px;object-fit:contain}
@media(max-width:600px){.opening img.op-bunny{height:150px}.op-title{font-size:44px}.op-bit{width:40px;height:40px;margin:-20px 0 0 -20px}}

/* Wider than a phone: room between a question's prompt and its choices (the photo's 🔊 badge, the sign's lip). */
@media(min-width:601px){.stage-packet.in-task .stage-body{gap:26px}}
/* Laptop heights up to 800px: the rope sits higher, so the claim ticket (and its stamp) fit without scrolling. */
@media(min-width:601px) and (max-height:800px){.packet-rack{margin-top:18px}.stage-packet .stage-body{gap:4px}}
/* Laptop heights: 我的小花园 fits without scrolling inside (the shop's 我要换 stays in view). */
@media(min-width:761px) and (max-height:780px){dialog[data-kind=garden]{padding:12px 22px 14px}.garden{gap:6px}.garden section{padding:8px 14px}.pot-art{height:92px}.pot-top{bottom:40px}.pot-flower{width:50px}.bud-svg{width:46px;height:50px}.tree-art{height:128px}.tree-img{max-height:108px}.prize-card{height:164px;padding:6px 8px}.prize-pic,.prize-ring{width:52px;height:52px}.prize{font-size:26px}}
/* Tall laptop screens: the packets on the rope grow into the room the stage has. */
@media(min-width:951px) and (min-height:780px){.packet-swing .pk{width:142px;height:185px}.next-hand{top:150px}}
/* Short screens. */
@media(min-width:760px) and (max-height:760px){
 .topbar{height:52px}.go-btn{height:48px}.chip{height:48px}
 .page-head{height:72px}.helper{height:72px}.helper img.bunny{height:76px}
 .dock{height:84px}.dock-main{min-height:60px}
 /* The 84px dock has no room under the arrows for 上一题/下一题 (phones hide them too); the arrows say it. */
 .qnav{padding-bottom:0}.qnav-step small{display:none}
 main{min-height:calc(100dvh - 56px)}
 .packet-swing .pk{width:112px;height:146px}.next-hand{top:116px}.claim-ticket{min-height:76px}.ticket-coin svg{width:60px;height:60px}.ticket-amount{font-size:28px}
 .task-prompt .listen-big{width:120px;height:120px}.polaroid{width:150px;height:166px}.pol-pic{width:126px;height:104px;font-size:80px}.read-sign{height:112px}.read-sign b{font-size:72px}.task-choice.picture{height:140px}
 .result-pk,.result-pk .pk{width:170px;height:221px}.result-pk{margin-top:50px}.result-art img.result-bunny{height:160px}
}
/* Laptops: the sidebar labels and the tone shelf stay on one row. */
@media(max-width:1150px){
 .sidebar{padding-inline:14px}
 .nav-item{grid-template-columns:48px 1fr;font-size:17px;padding-left:6px}
 .nav-ico,.nav-item.active .nav-ico{width:46px;height:46px}.nav-icon{width:42px;height:38px}
 .tab{padding:0 14px}.tab small{display:none}
 /* 全部 at 8 columns: twelve would shrink keys under 60px and letters under 20px; the board may scroll here instead. */
 .sound-grid.board-all{grid-template-columns:repeat(8,minmax(0,1fr))}
 .stage-home .board-all .sound-key{height:clamp(56px,calc((100dvh - 560px)/8),84px)}
}
/* Tablets: a 100px sidebar with the icon over a short label. */
@media(min-width:601px) and (max-width:950px){.scenery .grass{padding-left:104px}}
@media(max-width:950px){
 .sidebar{padding:18px 10px}
 .brand{justify-content:center}.brand>span{display:none}.brand img.bunny-head{height:44px}
 .sidebar nav{margin-top:24px;gap:12px}
 .nav-item{grid-template-columns:1fr;justify-items:center;align-content:center;gap:2px;height:84px;padding:4px 0;font-size:16px}
 .nav-ico,.nav-item.active .nav-ico{width:48px;height:48px;margin:0}.nav-icon,.nav-item.active .nav-icon{width:40px;height:36px}
 .nav-item.active::after{right:-13px}
 .parent-link>span{display:none}.parent-link{font-size:24px}
 .nl-long{display:none}.nl-short{display:inline}
}
@media(min-width:601px) and (max-width:950px){.packet-swing .pk{width:112px;height:146px}.next-hand{top:116px}.task-choices{gap:14px}}
@media(max-width:760px){.garden{grid-template-columns:minmax(0,1fr);grid-template-areas:'today' 'tree' 'week' 'shop'}.pots{gap:4px}.pot-rule{flex-direction:column;text-align:center}}
@media(min-width:601px) and (max-width:950px){.stage-map .stage-body{padding:16px}.trail{padding:24px 0 8px}.trail-row{gap:8px;padding:0 4px}.zone-mark{font-size:38px}}
@media(max-width:900px){
 .spell-scene{flex-direction:column;gap:16px}
 .word-pic{flex-direction:row;width:auto;height:84px;padding:0 26px 0 12px;gap:12px}
 .word-pic .pic{width:64px;height:64px;font-size:46px}.word-pic b{font-size:26px}
 .spk-badge{width:44px;height:44px;right:-10px;bottom:-10px}.spk-badge .icon{width:22px;height:22px}
 .slot{width:84px;height:84px;font-size:40px;border-radius:20px}.slot.whole{width:150px}.slot .q-mark{font-size:34px}
 .result-chip{min-height:72px;font-size:42px;margin-left:12px;padding:0 14px}
 .tray{gap:12px;padding:14px 16px 22px}.tray .piece{width:96px;height:92px;font-size:38px}
}
/* No 全部 under 760px (the board starts on 声母); six keys a row. */
@media(max-width:759px){
 .tab.all-tab{display:none}.tab{padding:0 16px}
 .sound-grid{grid-template-columns:repeat(6,minmax(0,1fr))}
 .stage-home .sound-key{height:clamp(64px,calc((100dvh - 400px)/4),100px)}
}
/* Tablets: two rows of cards, as wide as the height allows (two rows ≈ 2.27 × width), so the dock stays on screen. */
@media(max-width:760px){.tone-tracks{grid-template-columns:repeat(2,minmax(0,200px));grid-template-columns:repeat(2,minmax(0,clamp(140px,calc((100dvh - 464px)*.44),200px)))}}
/* Four 识读 cards on a narrow screen lie 2 × 2 rather than 3 + 1 (and stay wide enough to show their word). */
@media(max-width:760px){.stage-learn .lesson-cards:has(>:nth-child(4):last-child){grid-template-columns:repeat(2,minmax(0,172px))}}
/* Narrow screens: a game's ✕ stays in the dock's left slot instead of lying over the top-right card. */
@media(max-width:760px){.dock .stage-close{position:static;flex-shrink:0}}
/* Narrow tablets: ✕, ◀ dots ▶ and 再听 share one dock row, so the question navigator takes its phone size. */
@media(min-width:601px) and (max-width:760px){
 .stage.in-game .dock{gap:10px;padding:0 12px}.stage.in-game .dock-main{min-width:0;padding:0 16px}
 .stage.in-game .qnav{gap:6px}.stage.in-game .qnav-step{width:48px;height:48px}.stage.in-game .qnav-step.go{width:64px;height:64px}.stage.in-game .ring{width:76px;height:76px}
 .stage.in-game .qnav-dots{gap:4px}.stage.in-game .qdot{width:28px;height:28px}.stage.in-game .qnav-step small{display:none}
}
/* Phones: the sidebar becomes a five-tab bottom bar, the parent entry moves to a gear in the top bar, the hills shrink. */
@media(max-width:600px){
 .sidebar{flex-direction:row;padding:6px 8px max(6px,env(safe-area-inset-bottom));box-shadow:inset 0 3px 0 #F1E2C8,0 -10px 30px -18px #4a3b5833}
 .brand,.parent-link{display:none}
 .sidebar nav{flex:1;display:grid;grid-template-columns:repeat(5,1fr);gap:6px;margin:0}
 .nav-item{grid-template-columns:1fr;justify-items:center;align-content:center;gap:1px;height:72px;padding:2px 0;border-radius:18px;font-size:16px;box-shadow:var(--hi),0 3px 0 #E6D5B8}
 .nav-item.active{box-shadow:var(--hi),0 3px 0 var(--m-lip)}
 .nav-ico,.nav-item.active .nav-ico{width:40px;height:40px;margin:0;box-shadow:none}.nav-icon,.nav-item.active .nav-icon{width:36px;height:32px}
 .nav-item.active::after{display:none}
 .gear{display:grid}
 .hills{height:80px}.scenery .tree,.scenery .sun,.scenery .cloud,.scenery .bf2{display:none}
 /* One butterfly flies a shorter path; the grass stands on the bottom bar. */
 .scenery .bf1{top:74px;left:44%;--bx:.45}.scenery .grass{bottom:calc(80px + env(safe-area-inset-bottom));padding:0 4px}
 .packet-bar{gap:6px;padding:0 6px}.tray-slot,.tray-slot svg{width:38px;height:38px}.tray-amount{min-width:0;height:38px;padding:0 10px;font-size:18px}
 .task-chip{min-height:48px;padding:0 12px 0 8px;font-size:17px}.coin-track{min-height:48px;gap:3px;padding:0 6px}.track-cell,.track-cell svg{width:30px;height:30px}.pebble{width:22px;height:17px}.track-pk svg{width:30px;height:39px}
 .topbar{height:60px;gap:6px}.go-btn{height:48px;padding:0 6px 0 10px;font-size:17px}.go-num{min-width:32px;height:32px;font-size:17px}.flag{width:20px;height:24px}
 .chip{height:48px;padding:0 9px;font-size:16px}.chip-star{padding-left:5px}.hud-star{width:24px;height:24px}.hud-flower{width:22px;height:24px}.gear{width:48px;height:48px}
 .page-head{height:72px}.helper{height:72px}.helper img.bunny{height:76px}.bubble h1{font-size:24px}.speed{width:50px;height:50px;font-size:24px}
 main{min-height:0}
 .dock{position:sticky;bottom:calc(84px + env(safe-area-inset-bottom));display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px 10px;height:auto;min-height:80px;padding:8px 10px;background:var(--board,var(--paper));z-index:5;border-radius:0 0 26px 26px}
 .dock>.dock-c{order:-1;flex:1 1 100%;display:flex;justify-content:center;min-width:0}
 .dock>.dock-l:empty,.dock>.dock-r:empty,.dock>.dock-c:empty{display:none}
 .dock>.dock-r{margin-left:auto}
 .dock-main{min-height:56px;min-width:0;padding:0 16px;font-size:19px}
 .qnav{gap:6px;min-width:0}.qnav-step{width:48px;height:48px;flex-shrink:0}.qnav-step.go{width:64px;height:64px}.ring{width:76px;height:76px}.qnav-dots{gap:4px;min-width:0}.qdot{width:26px;height:26px;font-size:16px}.qdot.now{transform:scale(1.1)}.qnav-step small{display:none}
 .ears{gap:4px}.ear{width:30px;height:30px;font-size:18px}.ears-flower{width:32px;height:36px}
 .board-tabs{gap:4px}.tab{height:48px;padding:0 12px;font-size:17px}.tab.active{height:54px}
 .sound-grid{grid-template-columns:repeat(4,minmax(0,1fr));gap:8px}
 .stage-home .stage-body{padding:12px}
 .stage-home .sound-key{height:clamp(64px,calc((100dvh - 470px)/6),92px)}
 .where{font-size:16px;gap:8px}.sign-preview b{font-size:18px}
 .map-hero{flex-wrap:wrap}.stage-map .stage-body{padding:12px}.trail{padding:24px 0 8px}.trail-row{gap:6px;padding:0 2px}.zone-mark{font-size:30px}.stone2{min-width:44px}.stone2.current{flex-basis:64px;min-width:52px}
 .stage-learn .lesson-cards,.stage-learn .lesson-cards.two-rows,.stage-learn .lesson-cards.game{grid-template-columns:repeat(min(3,var(--cols)),minmax(0,1fr));gap:10px;width:100%}
 .stage-learn .lesson-cards .sound-key,.stage-learn .lesson-cards.two-rows .sound-key,.stage-learn .lesson-cards.game .sound-key{width:100%;height:150px;aspect-ratio:auto}
 .stage-learn .lesson-cards .sound-key strong{font-size:44px}
 .stage-learn .lesson-cards .key-pic{width:40px;height:40px;font-size:24px}
 .slots{gap:6px}.slot{width:68px;height:76px;font-size:34px}.slot.whole{width:120px}
 .result-chip{font-size:34px;min-height:64px}
 .tray .piece{width:84px;height:80px;font-size:32px}
 .stage-spell .qdot{width:28px;height:28px;font-size:16px}
 .sound-shelf{gap:6px}.shelf-group{flex-wrap:wrap}.shelf-chip{width:40px;height:44px;font-size:19px;border-radius:13px}
 .tone-tracks{grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}
 .tone-track-card{padding:8px 6px;border-radius:22px}.track-badge{left:7px;top:7px}.track-badge b{font-size:17px}
 .neutral-chip{min-height:48px;padding:0 12px;font-size:16px}
 /* In the game the cards are shorter (smaller slide, no tone name: the badge has the number), so both rows fit above the dock. */
 .tone-tracks.in-game .tone-track-card{aspect-ratio:auto}.tone-tracks.in-game .tone-track-card small{display:none}
 .tone-tracks.in-game .tone-track-card svg.track{width:70%;width:min(70%,92px);margin-top:14px}
 dialog[data-kind=garden]{padding:14px 12px 16px}.garden section{padding:8px 10px}
 .pot-art{width:80px;height:96px}.pot-svg{width:80px;height:50px}.pot-top{bottom:38px}.pot-flower{width:48px}.bud-svg{width:44px;height:48px}.pot-rule i{width:26px;height:26px;font-size:16px}
 .stamps{gap:3px}.stamp-cell{flex:1;width:auto;min-width:0}.stamp{width:28px;height:28px}
 .g-tree{grid-template-columns:96px minmax(0,1fr);column-gap:10px}.tree-art{width:96px;height:124px;grid-row:1}.tree-img{max-width:90px;max-height:104px}
 /* The vine takes the whole width under the tree, so its seven stages never overlap. */
 .vine{grid-column:1/-1;margin-top:8px}
 .prize-row{margin-inline:-10px;padding-inline:10px;scroll-padding-inline:10px;-webkit-mask-image:linear-gradient(90deg,transparent,#000 10px,#000 calc(100% - 32px),transparent);mask-image:linear-gradient(90deg,transparent,#000 10px,#000 calc(100% - 32px),transparent)}
 .packets{grid-template-columns:repeat(2,minmax(0,1fr));gap:14px 10px}.packet-line{display:none}.packet:nth-child(2),.packet:nth-child(3){margin-top:0}
 .packet-swing .pk{width:100px;height:130px}.next-hand{top:100px}.next-hand svg{width:34px;height:40px}
 /* The ticket stays one row: the amount is already over the little packets, so the coin goes. */
 .claim-ticket{gap:10px;padding:6px 18px;flex-wrap:nowrap;justify-content:center}.ticket-coin{display:none}.ticket-mid{padding:0 10px 0 0;border-left:0}.ticket-amount{font-size:26px}.ticket-show{font-size:16px;word-break:keep-all;text-align:center}.paid-stamp{width:74px;height:74px;margin-block:-6px;padding:0 6px;font-size:16px}
 .task-top{grid-template-columns:1fr;gap:8px}.task-top>span:first-child{display:none}.task-feedback{justify-self:center;min-height:48px}.fb-stamp{width:44px;height:44px}
 .task-prompt .listen-big{width:110px;height:110px}.task-prompt .listen-big .spk{width:64px;height:64px}
 .polaroid{width:130px;height:150px;padding:8px 8px 0}.pol-pic{width:114px;height:96px;font-size:70px}.polaroid b{font-size:22px}
 .read-sign{min-width:170px;height:100px}.read-sign b{font-size:60px}
 /* Three cards fit one row even at 360px (≤296px of 302px), four tone slides lie 2 × 2; stamps stay off the neighbours. */
 .stage-packet.in-task .stage-body{padding:16px 12px}
 .task-choices{gap:10px}.task-choice{width:92px;height:96px;font-size:36px}.task-choice.picture{width:92px;height:100px}.task-choice .pic{font-size:52px}
 .task-tone .task-choices{display:grid;grid-template-columns:repeat(2,120px);justify-content:center}.tone-choice{width:120px;height:104px}.tone-choice svg.track{width:64px}.tone-choice b{font-size:26px}
 .pick-stamp{width:34px;height:34px;right:-8px;top:-10px}.pick-stamp .icon{width:20px;height:20px}
 .pebble::after{right:auto;left:50%;top:50%;transform:translate(-50%,-50%)}
 /* The task's name is read out, not shown: its icon keeps the bar on one row for every task, so the stage never jumps. */
 .task-chip{padding:0 10px}.task-chip b{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}
 /* A failed result: the bubble sits under the coin sockets, its tail pointing up. */
 .key-bar{flex-direction:column;gap:12px}.need-bubble{white-space:nowrap}.need-bubble::before{left:50%;top:-10px;margin:0 0 0 -7px;transform:rotate(135deg)}
 .stage-packet.in-result .stage-body{padding-top:24px}
 .result-body{flex-direction:column;gap:12px}.result-pk,.result-pk .pk{width:130px;height:169px}.result-pk{margin-top:44px}.result-art img.result-bunny{height:120px}.result-info{align-items:center;text-align:center}.result-info h2{font-size:28px}.review{justify-content:center}
}
/* The narrowest phones: 继续 is in the flag button's label (继续闯关), so only the flag and the number show. */
@media(max-width:380px){.go-label{display:none}.go-btn{padding:0 6px 0 8px}}
/* Reduced motion: every end state shows still; sounds still play. The clouds rest in the sky, the playing ring is a
   still border and the equaliser bars stand full. */
@media(prefers-reduced-motion:reduce){
 *,*::before,*::after{animation:none!important;transition-duration:1ms!important}
 ::view-transition-group(*),::view-transition-old(*),::view-transition-new(*){animation:none!important}
 ::view-transition-old(*){opacity:0}
 :is(.btn,.dock-main,.sound-key,.piece,.slot.filled,.task-choice,.tone-track-card,.shelf-chip,.packet,.stone2,.nav-item,.go-btn,.hud,.gear,.listen-big,.word-pic,.helper,.qnav-step,.stage-close,.speed,.tab,.where,.neutral-chip,.result-chip,.dialog-head button,.polaroid,.review-chip,.pot):active:not(:disabled){scale:none;translate:none}
 .opening{display:none}
 .cl1{left:auto;right:12%}.cl2{left:auto;right:34%}.cl3{left:auto;right:56%}
 .sound-key.playing .key-eq i{transform:none}
 .fx{display:none}
 .ring circle{stroke-dashoffset:0}
}
