/* Web fonts moved to a NON-BLOCKING <link> in index.html — an @import here serialized the fetch after
   styles.css AND render-blocked first paint until fonts.googleapis.com answered (seconds-to-timeout on
   filtered/flaky networks). display=swap already covers the fallback-then-swap behavior. */

:root{
  --green:#5fae5f; --green-d:#3f8a47; --green-l:#e7f6e2;
  --cream:#fbf7ed; --ink:#33402f; --muted:#7c8a73;
  --amber:#f2b134; --amber-d:#d6920f;
  /* soft warm off-white cards + gentler shadow = less glare for tired eyes (low-contrast calm) */
  --card:#fffdf7; --line:rgba(60,80,50,.11);
  --sky1:#bfe3f5; --sky2:#dff3e8;
  /* the one high-emphasis element: the center Play button. Mid-greens; the white glyph also carries a
     drop-shadow so it clears the 3:1 non-text floor on every gradient stop (not just the dark end). */
  --fab1:#53a455; --fab2:#3f8a47;
  --r:16px; --r-lg:22px;
  --shadow:0 2px 8px rgba(50,70,40,.10);   /* tighter blur = far cheaper to raster on weak GPUs; calm look unchanged */
  --iris-bg:#0b120c;   /* warm green-black for the menu<->level scene transition (matches --ink tonality) */
  /* F18 — design-token scale. One vocabulary for the polish pass; F19/F13/F15 consume these.
     Every value aliases a size/hex ALREADY in use — nothing new is invented, so adding these
     changes no pixels until a rule references them. */
  --r-sm:10px; --r-md:14px;                                                      /* --r(16)/--r-lg(22) already above */
  --fs-xs:11px; --fs-sm:12px; --fs-md:14px; --fs-lg:16px; --fs-xl:20px; --fs-2xl:24px; --fs-3xl:30px;
  --sp-1:4px; --sp-2:8px; --sp-3:12px; --sp-4:16px; --sp-5:20px; --sp-6:24px;
  /* semantic scene colours — the EXACT hex already scattered through the scene, just named for reuse */
  --c-error:#e8607a; --c-time:#46b6c9; --c-combo:#9a8fe0; --c-critter:#9ec27a; --c-coin:#e6a32e; --c-gem:#3b9ae8;
}
*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{margin:0;padding:0;height:100%}
body{
  font-family:'Fredoka',system-ui,-apple-system,Segoe UI,Roboto,sans-serif;
  color:var(--ink);
  background:linear-gradient(180deg,#bfe3f5 0%,#dff3e8 38%,#eef7df 100%);
  overscroll-behavior:none;
  touch-action:manipulation;   /* kill the double-tap-zoom tap delay in webviews that ignore user-scalable=no (TMA/iOS) — pan/scroll unaffected; .board's touch-action:none still wins there */
}
button{font-family:inherit;cursor:pointer;border:none;background:none;color:inherit}
.hidden{display:none !important}

#app{
  max-width:480px;margin:0 auto;height:100dvh;display:flex;flex-direction:column;
  position:relative;overflow:hidden;
}

/* ---------- HUD ---------- */
/* F16: tightened so all five currency pills + settings fit 360px without clipping the gem/settings.
   lives/coins/gems are the always-visible priority; qp/lvl are compact meta chips. */
#hud{display:flex;align-items:center;justify-content:space-between;padding:8px 6px;gap:5px}
.hud-left,.hud-right{display:flex;align-items:center;gap:6px;min-width:0}
.hud-pill{
  display:inline-flex;align-items:center;gap:5px;background:rgba(255,255,255,.85);
  border:1px solid var(--line);border-radius:999px;padding:4px 8px;font-weight:600;font-size:14px;
  box-shadow:0 2px 6px rgba(50,70,40,.08);white-space:nowrap;
}
.hud-pill i{font-size:17px}
.hud-pill span{font-variant-numeric:tabular-nums}   /* steady width while coins/gems count up */
/* I1: Tokens (the play-gate) recoloured OFF amber → teal "energy", so it no longer reads as coins.
   Roles are now visually distinct: coins = amber (soft) · gems = blue + premium ring · Tokens = teal. */
.hud-pill.lives i{color:#2ba39b}
.hud-pill.lives{position:relative;overflow:hidden}
.hud-pill.lives::before{content:'';position:absolute;left:0;top:0;bottom:0;width:calc(var(--regen,0)*100%);background:linear-gradient(90deg,rgba(43,163,155,.10),rgba(43,163,155,.30));transition:width .9s linear;pointer-events:none}   /* time-to-next-token fill — a touch richer so the countdown reads at a glance */
/* wallet chips (hud-right): amber coins with a soft accent · blue gems with a faint premium ring */
.hud-pill.coin i{color:#e6a32e}
.hud-pill.gem i{color:#3b9ae8}
.hud-pill.coin{box-shadow:0 2px 6px rgba(50,70,40,.08),inset 0 0 0 1.5px rgba(230,163,46,.16)}
.hud-pill.gem{box-shadow:0 2px 6px rgba(50,70,40,.08),inset 0 0 0 1.5px rgba(59,154,232,.32)}
.hud-pill.lives.full::before{width:0}
.hud-pill.lives>*{position:relative}
/* narrow-phone fit: tighten the 6-item HUD so nothing clips on small screens (coins/gems/level run long) */
@media (max-width:400px){
  #hud{padding:8px 6px;gap:4px}
  .hud-left,.hud-right{gap:4px}
  .hud-pill{padding:3px 7px;font-size:12px;gap:3px}
  .hud-pill i{font-size:15px}
  .hud-timer{display:none}   /* drop the regen/"full" text on narrow — the number + fill already carry the state */
}
/* smallest phones: also fold away the quest-score pill (it's mirrored in the Home quest card) so nothing clips */
@media (max-width:360px){ #qpPill{display:none} }
.hud-pill.lives.beat i{animation:lifeBeat .5s ease}
@keyframes lifeBeat{0%,100%{transform:scale(1)}40%{transform:scale(1.22)}}
.hud-timer{font-size:11px;color:#2ba39b;font-weight:700;letter-spacing:.2px;opacity:.92;display:inline-block;max-width:8ch;overflow:hidden;white-space:nowrap;transition:max-width .3s ease,opacity .3s ease}   /* 8ch: the VIP unlimited window renders '1440:00' */   /* F16 kept: an empty timer (lives full) still frees its ~30px — but eased, so the coin/gem pills glide instead of snapping */
.hud-timer:empty{max-width:0;opacity:0}
.hud-icon{width:38px;height:38px;border-radius:50%;background:rgba(255,255,255,.85);border:1px solid var(--line);display:grid;place-items:center;font-size:19px;box-shadow:0 2px 6px rgba(50,70,40,.08)}
/* ---------- Offline chip (OFFLINE.md §4.1): a MUTED state, never a red error ---------- */
.hud-pill.link{position:relative;cursor:pointer;color:var(--muted);background:rgba(124,138,115,.10);border-color:rgba(124,138,115,.28);padding:4px 9px;max-width:72px;transition:max-width .28s ease,opacity .28s ease,padding .28s ease,margin .28s ease}
.hud-pill.link.chip-off{max-width:0;opacity:0;padding-left:0;padding-right:0;border-width:0;pointer-events:none;overflow:hidden;margin-inline-start:-5px}   /* clip only while collapsed (the Mailbox count badge overflows by design when visible); the negative margin swallows the flex gap a zero-width item would leave */
@media (max-width:400px){ .hud-pill.link.chip-off{margin-inline-start:-4px} }
.hud-pill.link i{font-size:16px;color:var(--muted)}
.hud-pill.link .link-count{position:absolute;top:-5px;inset-inline-end:-5px;min-width:15px;height:15px;padding:0 3px;border-radius:8px;background:var(--amber);color:#fff;font-size:10px;font-weight:800;line-height:15px;text-align:center;box-shadow:0 0 0 2px var(--card,#fffdf7);font-variant-numeric:tabular-nums}
.hud-pill.link.tappable:active,.hud-pill.link:active{transform:scale(.96)}
@media (max-width:400px){ .hud-pill.link{padding:3px 7px} .hud-pill.link i{font-size:14px} }
/* the small ti-cloud-off suffix glyph a gated-but-tappable claim button carries (§4.4) */
.off-suffix{font-size:13px;color:var(--muted);margin-inline-start:5px;vertical-align:-1px}

/* ---------- Screen ---------- */
#screen{flex:1;overflow-y:auto;overflow-x:hidden;padding:6px 16px 18px;-webkit-overflow-scrolling:touch}
.screen-title{font-size:24px;font-weight:700;margin:6px 0 2px}
.screen-sub{color:var(--muted);font-weight:500;margin:0 0 14px}

/* ---------- Nav (6 tabs + a Play FAB docked on the center seam) ---------- */
#nav{position:relative;z-index:5;display:flex;align-items:flex-end;background:rgba(255,253,247,.94);border-top:1px solid var(--line);padding:6px 4px max(6px,env(safe-area-inset-bottom));box-shadow:0 -2px 10px rgba(50,70,40,.05)}
.nav-btn{flex:1;min-width:0;position:relative;display:flex;flex-direction:column;align-items:center;gap:2px;padding:7px 0;border-radius:13px;color:var(--muted);font-weight:600;transition:background .14s ease}
.nav-btn i{font-size:21px;transition:font-size .14s ease}
.nav-btn span{max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-size:10px;line-height:1.15;padding:0 3px}
.nav-btn.active{color:var(--green-d);background:var(--green-l)}
.nav-btn.active i{font-size:25px}                       /* enlarged active tab — "you are here" (owner req 2026-07-06) */
.nav-btn.active span{font-size:11px;font-weight:700}
.nav-btn.locked{opacity:.5}
.nav-btn.locked .nav-lock{position:absolute;top:3px;inset-inline-start:calc(50% + 4px);width:15px;height:15px;border-radius:50%;background:var(--card,#fffdf7);box-shadow:0 1px 3px rgba(40,50,30,.28);display:grid;place-items:center}
.nav-btn.locked .nav-lock i{font-size:10px;color:#9aa191}
.nav-btn.has-dot::after{content:'';position:absolute;top:4px;inset-inline-start:calc(50% + 7px);width:8px;height:8px;border-radius:50%;background:#e8607a;box-shadow:0 0 0 2px rgba(255,253,247,.94)}
/* Critters-tab arrivals badge — "where my progress goes": a live count of rescued critters not yet viewed */
.nav-btn .nav-count{position:absolute;top:1px;inset-inline-start:calc(50% + 5px);min-width:15px;height:15px;padding:0 3px;border-radius:8px;background:#5fae5f;color:#fff;font-size:10px;font-weight:800;line-height:15px;text-align:center;box-shadow:0 0 0 2px rgba(255,253,247,.94)}
/* the two tabs flanking the seam: nudge content/badges to their OUTER edge so the raised Play never covers an icon */
.nav-btn--seam{padding-top:8px}
.nav-btn[data-screen="events"]{padding-inline-end:16px}
.nav-btn[data-screen="sanctuary"]{padding-inline-start:16px}
.nav-btn[data-screen="events"].has-dot::after{inset-inline-start:calc(50% - 13px)}
.nav-btn[data-screen="sanctuary"] .nav-count{inset-inline-start:calc(50% - 13px)}
@keyframes arrivalsPop{0%{transform:scale(1)}42%{transform:scale(1.06)}100%{transform:scale(1)}}   /* scale 1.06 = the calm ceiling, no higher */
.nav-btn.arrivals-pop{animation:arrivalsPop .5s ease}
@media (prefers-reduced-motion: reduce){ .nav-btn.arrivals-pop{animation:none} .nav-btn i{transition:none} }
body.lowfx .nav-btn.arrivals-pop{animation:none}

/* center Play — raised round action button docked on the seam, absolute-centered (independent of the six tabs'
   flex so all six keep an exact 1/6 width). Stays a CHILD of #nav so body.in-game #nav{display:none} hides it. */
.nav-fab{position:absolute;left:50%;bottom:calc(env(safe-area-inset-bottom) + 30px);transform:translateX(-50%);z-index:6;display:flex;flex-direction:column;align-items:center;gap:1px;background:none;padding:0}
.nav-fab .fab-circle{display:grid;place-items:center;width:54px;height:54px;border-radius:50%;
  background:linear-gradient(155deg,var(--fab1),var(--fab2));border:4px solid var(--card);
  box-shadow:0 8px 18px rgba(63,138,71,.36);animation:fabBreathe 2.8s ease-in-out infinite}
.nav-fab .fab-circle svg{width:25px;height:25px;fill:#fff;margin-left:3px;filter:drop-shadow(0 1px 1.5px rgba(35,60,35,.28))}  /* optical-center the triangle; shadow guarantees glyph separation */
.nav-fab .fab-label{font-size:10px;font-weight:700;color:var(--green-d)}
.nav-fab .fab-sub{font-size:8.5px;font-weight:800;line-height:1;margin-top:1px;padding:1px 6px;border-radius:8px;background:var(--green-d);color:#fff;letter-spacing:.3px;box-shadow:0 1px 3px rgba(63,138,71,.4);font-variant-numeric:tabular-nums}  /* level the Play button resumes into */
.nav-fab .fab-sub:empty{display:none}
.nav-fab.fab-hidden{display:none}  /* hidden on screens that own a primary play action (e.g. Daily) — see FAB_HIDE in ui.js */
.nav-fab:active .fab-circle{animation:none;transform:scale(.94);box-shadow:0 4px 10px rgba(63,138,71,.34)}
#navPlay:focus-visible{outline:3px solid var(--green-d);outline-offset:2px;border-radius:16px}
@keyframes fabBreathe{0%,100%{transform:scale(1)}50%{transform:scale(1.04)}}  /* gentle breathe, within calm ceiling (≤1.06) */
body.calm .nav-fab .fab-circle{animation:none}  /* honor the in-app Calm feel too — matches qReduced() */
@media (prefers-reduced-motion: reduce){.nav-fab .fab-circle{animation:none}}

/* ---------- Buttons ---------- */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;border-radius:14px;font-weight:600;font-size:16px;padding:13px 18px;transition:transform .08s}
.btn:active{transform:scale(.97)}
.btn-primary{background:var(--green);color:#fff;box-shadow:0 4px 0 var(--green-d)}
.btn-primary:active{box-shadow:0 1px 0 var(--green-d);transform:translateY(3px)}
.btn-amber{background:var(--amber);color:#5a3d00;box-shadow:0 4px 0 var(--amber-d)}
.btn-amber:active{box-shadow:0 1px 0 var(--amber-d);transform:translateY(3px)}
.btn-ghost{background:var(--card);border:1px solid var(--line);color:var(--ink);box-shadow:var(--shadow)}
.btn-block{width:100%}
.btn-sm{padding:9px 13px;font-size:14px;border-radius:12px}
.btn[disabled]{opacity:.5;pointer-events:none;filter:grayscale(.3)}
/* "can't afford yet" — a deactivated (not-green) look that STAYS tappable, so a tap
   opens the universal top-up modal instead of silently failing. Used on any currency
   buy button (.btn / .price-btn / .ds-buy / .ld-buy) when the wallet is short. */
.cant{background:#e8e6df!important;color:#9aa49a!important;border:1px solid rgba(60,80,50,.10)!important;box-shadow:none!important;filter:none!important;text-shadow:none!important;cursor:pointer!important}
.cant i{color:#9aa49a!important}
/* F19 — four-tier button grammar for the in-level flow, so a paid action can never masquerade
   as the free one: primary=recommended free action (green) · paid=costs currency (amber + a
   price chip that names the cost) · ghost=free alternative · quiet=low-stakes dismiss. The
   .btn-price chip is tier-agnostic (also rides a quiet ghost, e.g. the de-emphasised $0.99). */
.btn-paid{background:linear-gradient(180deg,#f6bd45,var(--amber));color:#5a3d00;box-shadow:0 4px 0 var(--amber-d);font-weight:700}
.btn-paid:active{box-shadow:0 1px 0 var(--amber-d);transform:translateY(3px)}
.btn-quiet{background:none;color:var(--muted);box-shadow:none;font-weight:600;padding:10px 14px}
.btn-quiet:active{transform:scale(.97)}
.btn .btn-price{display:inline-flex;align-items:center;gap:3px;margin-left:2px;padding:1px 8px;border-radius:999px;background:rgba(60,50,20,.13);font-weight:800;font-variant-numeric:tabular-nums;font-size:.92em}
.btn .btn-price i{font-size:1.05em}
.btn-paid .btn-price i{color:var(--c-gem)}   /* the gem keeps its brand blue on the gold button — reads as "this costs gems" */
/* soft "here's where to top up" highlight after a deep-link into the Shop (calm: no scale, <1Hz) */
@keyframes shopFocusPulse{0%{box-shadow:0 0 0 0 rgba(95,174,95,0)}25%{box-shadow:0 0 0 4px rgba(95,174,95,.32)}100%{box-shadow:0 0 0 0 rgba(95,174,95,0)}}
.shop-focus{animation:shopFocusPulse 1.2s ease-out 2;border-radius:14px}

/* ---------- Cards ---------- */
.card{background:var(--card);border:1px solid var(--line);border-radius:var(--r-lg);padding:16px;box-shadow:var(--shadow)}
.row{display:flex;align-items:center;gap:12px}
.spacer{flex:1}
.tag{font-size:12px;font-weight:600;padding:3px 9px;border-radius:999px;background:var(--green-l);color:var(--green-d)}

/* ---------- Level map ---------- */
.map-wrap{position:relative}
.map-nodes{position:relative;z-index:2;display:flex;flex-direction:column;align-items:center;gap:14px;padding:8px 0 4px}
.lvl-node{position:relative;width:66px;height:66px;border-radius:50%;display:grid;place-items:center;font-weight:700;font-size:21px;color:#fff;box-shadow:var(--shadow);border:3px solid #fff;transition:transform .12s ease}
.lvl-node.done,.lvl-node.current{cursor:pointer}
.lvl-node.done{background:var(--green)}
.lvl-node.current{background:var(--amber);color:#5a3d00;animation:bob 1.6s ease-in-out infinite}
.lvl-node.current::after{content:'';position:absolute;inset:-3px;border-radius:50%;pointer-events:none;border:3px solid rgba(242,177,52,.55);opacity:0;animation:nodeReady 2.4s ease-out infinite}  /* a soft amber "you're here — play me" expanding ring; transform+opacity so the always-on map anim composites instead of repainting a box-shadow every frame */
@keyframes nodeReady{0%{transform:scale(.9);opacity:.75}70%{transform:scale(1.32);opacity:0}100%{transform:scale(1.32);opacity:0}}
body.calm .lvl-node.current::after{animation:none}
@media (prefers-reduced-motion:reduce){.lvl-node.current::after{animation:none}}
.lvl-node.locked{background:#c8d2c0;color:#fff}
.lvl-node.done:active,.lvl-node.current:active{transform:scale(.93)}
.lvl-node.pop{animation:nodePop .34s ease}
@keyframes nodePop{0%{transform:scale(1)}45%{transform:scale(1.06)}100%{transform:scale(1)}}
/* level-unlock reveal on the map (cosmetic; soft green ring, no scale beyond the node pop). Relocated
   from ftue.js so it also fires for returning players (the FTUE's injected CSS only mounts when it plays). */
.lvl-node.ftue-unlocked{animation:ftueUnlockGlow 1.5s ease-out 1}
@keyframes ftueUnlockGlow{0%{box-shadow:0 0 0 0 rgba(95,174,95,.55)}70%{box-shadow:0 0 0 13px rgba(95,174,95,0)}100%{box-shadow:0 0 0 0 rgba(95,174,95,0)}}
@media (prefers-reduced-motion:reduce){.lvl-node.ftue-unlocked{animation:none;box-shadow:0 0 0 3px rgba(95,174,95,.5)}}
.lvl-stars{position:absolute;bottom:-9px;left:50%;transform:translateX(-50%);display:flex;gap:2px;background:#fff;border-radius:999px;padding:2px 6px;box-shadow:0 1px 3px rgba(0,0,0,.15)}
.star{width:13px;height:13px;display:inline-block;vertical-align:middle}
.star path{fill:none;stroke:#c4c9bd;stroke-width:1.5;stroke-linejoin:round}   /* not won: hollow outline */
.star.on path{fill:var(--amber);stroke:var(--amber-d);stroke-width:1}          /* won: solid gold */
.lvl-stars .star{width:12px;height:12px}
.ev-badge{position:absolute;top:-7px;right:-7px;width:24px;height:24px;border-radius:50%;border:2px solid #fff;overflow:hidden;pointer-events:none;box-shadow:0 1px 4px rgba(50,70,40,.22);z-index:3}
.ev-badge svg{width:100%;height:100%;display:block}
@keyframes bob{0%,100%{transform:translateY(0)}50%{transform:translateY(-6px)}}

/* ---------- Living map scene (ambient critters, trail, scenery) ---------- */
.map-bg{position:absolute;inset:0;z-index:1;pointer-events:none;overflow:hidden}
.map-trail{position:absolute;left:0;top:0}
.map-tree{position:absolute;pointer-events:none;filter:drop-shadow(0 3px 3px rgba(40,55,30,.14))}
.map-bush{position:absolute;pointer-events:auto;cursor:pointer;filter:drop-shadow(0 2px 2px rgba(40,55,30,.14))}
.map-bush.rustle{animation:bushRustle .5s ease}
@keyframes bushRustle{0%,100%{transform:rotate(0)}25%{transform:rotate(-3deg)}60%{transform:rotate(2deg)}}
.map-firefly{position:absolute;width:7px;height:7px;border-radius:50%;background:radial-gradient(circle,#fff6c0 0%,rgba(255,224,120,.5) 45%,rgba(255,224,120,0) 70%);animation:ffPulse 3.2s ease-in-out infinite}
@keyframes ffPulse{0%,100%{opacity:.22;transform:translateY(0)}50%{opacity:.9;transform:translateY(-7px)}}
.map-actors{position:absolute;inset:0;z-index:4;pointer-events:none;overflow:hidden}
.map-critter{position:absolute;left:0;top:0;width:46px;height:46px;pointer-events:auto;cursor:pointer;will-change:transform}
.mc-face{width:100%;height:100%;transform-origin:50% 85%;transition:transform .2s ease}
.mc-body{width:100%;height:100%;transform-origin:50% 92%;transition:transform .5s cubic-bezier(.4,0,.2,1),opacity .5s}
.mc-body>svg{width:100%;height:100%;display:block;filter:drop-shadow(0 3px 3px rgba(40,55,30,.18))}
.map-critter.duck .mc-body{transform:scale(.5) translateY(11px);opacity:.4}
.map-critter.startle .mc-body{animation:mcHop .45s ease}
@keyframes mcHop{0%{transform:translateY(0)}35%{transform:translateY(-12px)}70%{transform:translateY(-1px)}100%{transform:translateY(0)}}
.map-emote{position:absolute;left:50%;top:-10px;transform:translateX(-50%);font-size:18px;line-height:1;pointer-events:none;filter:drop-shadow(0 1px 2px rgba(40,55,30,.25));animation:mcEmote 1.6s ease forwards}
@keyframes mcEmote{0%{opacity:0;transform:translate(-50%,6px) scale(.5)}22%{opacity:1;transform:translate(-50%,-6px) scale(1)}80%{opacity:1;transform:translate(-50%,-12px) scale(1)}100%{opacity:0;transform:translate(-50%,-22px) scale(.9)}}
.map-shadow{position:absolute;width:120px;height:54px;border-radius:50%;background:radial-gradient(closest-side,rgba(45,55,35,.20),rgba(45,55,35,0));pointer-events:none;will-change:transform,opacity}

/* ── Living-map LANDSCAPE (CE.Sky-driven): a SKY header above a GRASS meadow.
   The time-of-day tint is motion-free and stays on in calm mode; star twinkle,
   cloud + bird drift are gated by mapAlive(). ── */
/* the grass pattern is a COSMETIC seam (CE.Cosmetic): --cos-grass-img/size swap the texture,
   falling back to the built-in procedural look when no art-set is equipped. --grass (the base
   colour) stays owned by the day/night sky system, so terrain sets never fight the time-of-day. */
.map-ground{position:absolute;inset:0;z-index:0;pointer-events:none;
  background-color:var(--grass,#bfe096);
  background-image:var(--cos-grass-img, repeating-linear-gradient(114deg, rgba(20,60,20,.045) 0 1px, transparent 1px 7px),
    radial-gradient(120% 60% at 50% 0%, rgba(255,255,255,.06), transparent 60%));
  background-size:var(--cos-grass-size, auto);
  transition:background-color 1.2s linear}
.map-sky-header{position:absolute;top:0;left:0;width:100%;z-index:0;pointer-events:none;overflow:hidden;
  background:linear-gradient(180deg,var(--sky-top,#8fc7f0) 0%,var(--sky-mid,#bfe0f5) 58%,var(--sky-horizon,#e6f2e0) 100%);
  transition:background 1.2s linear}
.map-sky-header::after{content:'';position:absolute;left:-6%;bottom:-1px;width:112%;height:40px;
  background:var(--grass,#bfe096);border-radius:50% 50% 0 0/100% 100% 0 0}
.sky-sun,.sky-moon,.sky-cloud,.sky-bird{position:absolute;left:0;top:0;will-change:transform}
.sky-moon.is-day{opacity:.72}
/* ── Moonstruck secret (moonlit.js) — discoverable glow + tappable moon ── */
.sky-moon.moonstruck{pointer-events:auto;cursor:pointer}
.sky-moon.moonstruck svg{filter:drop-shadow(0 0 5px rgba(220,228,255,.6))}                                   /* subtle standing halo (static = calm-safe) */
.sky-moon.moonstruck.is-full svg{filter:drop-shadow(0 0 9px rgba(232,238,255,.92)) drop-shadow(0 0 18px rgba(170,190,255,.55))}  /* real full moon = brighter */
.sky-moon.moonBreath svg{transform-origin:50% 50%;animation:moonBreath 2.6s ease-in-out 2}                   /* one-shot breathing nudge */
.sky-moon.moon-poke svg{animation:moonPoke .4s ease}
@keyframes moonBreath{0%,100%{transform:scale(1);opacity:.92}50%{transform:scale(1.06);opacity:1}}           /* scale capped at the CALM ceiling (1.06) */
@keyframes moonPoke{0%,100%{transform:scale(1)}40%{transform:scale(1.06)}}
.moon-chip{position:fixed;top:66px;right:12px;z-index:40;width:38px;height:38px;border:none;border-radius:50%;
  background:rgba(34,42,78,.5);color:#fff;font-size:18px;line-height:1;cursor:pointer;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);
  box-shadow:0 2px 8px rgba(20,28,60,.3);opacity:.7;transition:opacity .2s,box-shadow .2s}
.moon-chip:hover{opacity:1}
.moon-chip.on{background:rgba(90,108,170,.9);opacity:1;box-shadow:0 0 12px rgba(180,200,255,.6)}
/* the secret cosmetic coat — parent-class keyed off S.d.moonlit; CE.critterSVG is never touched */
.collect-grid.moonlit svg{filter:drop-shadow(0 0 5px #cfd8f0) drop-shadow(0 2px 2px rgba(40,55,30,.18))}
.collect-grid.moonlit.moonlit-full svg{filter:drop-shadow(0 0 7px #e6ecff) drop-shadow(0 0 13px rgba(180,200,255,.55)) drop-shadow(0 2px 2px rgba(40,55,30,.18))}
.pc-av.moonlit svg{filter:drop-shadow(0 0 5px #cfd8f0)}
.pc-av.moonlit.moonlit-full svg{filter:drop-shadow(0 0 8px #e6ecff) drop-shadow(0 0 13px rgba(180,200,255,.5))}
/* unlock celebration card */
.ml-card{text-align:center;padding:4px 4px 2px}
.ml-card-moon{display:flex;justify-content:center;margin:2px 0 6px}
.ml-card-h{margin:2px 0 6px;font-size:20px}
.ml-card-p{margin:0 0 14px;font-size:14px;line-height:1.5;color:#5a6452}
.ml-card-btns{display:flex;flex-direction:column;gap:8px}
@media (prefers-reduced-motion:reduce){.sky-moon.moonBreath svg,.sky-moon.moon-poke svg{animation:none}}
body.calm .sky-moon.moonBreath svg,body.calm .sky-moon.moon-poke svg{animation:none}
.sky-bird{opacity:.72}
.sky-star{position:absolute;border-radius:50%;opacity:var(--so,.7);
  background:radial-gradient(circle,#fff 0%,rgba(255,255,255,.55) 50%,rgba(255,255,255,0) 75%)}
.sky-star.tw{animation:skyTwinkle 4s ease-in-out infinite}
@keyframes skyTwinkle{0%,100%{opacity:calc(var(--so,.7)*.35)}50%{opacity:var(--so,.7)}}
.sky-cloud.drift{animation:skyDrift 46s ease-in-out infinite alternate}
@keyframes skyDrift{from{transform:translateX(-12%)}to{transform:translateX(12%)}}
.sky-bird.fly{animation:birdFly 30s linear infinite}
@keyframes birdFly{0%{transform:translate(-16%,0)}50%{transform:translate(52%,-9px)}100%{transform:translate(116%,0)}}
/* ---- map juice pass: grounded critters, walk-cycle life, ambient visitors, mastery blooms, depth ---- */
.mc-shadow{position:absolute;left:50%;bottom:1px;width:26px;height:7px;margin-left:-13px;border-radius:50%;background:rgba(40,60,30,.16);filter:blur(1px);pointer-events:none}
.map-critter.duck .mc-shadow{opacity:.35}
.map-critter.walking .mc-body{animation:mcWalk .48s ease-in-out infinite alternate}   /* ambient tier: tiny 1.5px bob + 2° sway, silently loops only while moving */
@keyframes mcWalk{from{transform:translateY(0) rotate(-1.6deg)}to{transform:translateY(-1.5px) rotate(1.6deg)}}
.map-butterfly{position:absolute;left:0;top:0;font-size:17px;line-height:1;pointer-events:auto;cursor:pointer;will-change:transform;filter:drop-shadow(0 1px 2px rgba(40,55,30,.2));z-index:5}
.map-drift{position:absolute;font-size:13px;line-height:1;pointer-events:none;will-change:transform;opacity:0}
/* 3★ mastery bloom: perfected levels flower on the map — static, earned, theirs */
.lvl-node.bloom::before{content:'🌼';position:absolute;left:-7px;bottom:-3px;font-size:12px;line-height:1;pointer-events:none;filter:drop-shadow(0 1px 1px rgba(40,55,30,.25))}
.lvl-node.bloom::after{content:'🌷';position:absolute;right:-6px;bottom:-4px;font-size:11px;line-height:1;pointer-events:none;filter:drop-shadow(0 1px 1px rgba(40,55,30,.25))}
/* distant hills: two soft bands tinted from the live sky palette; parallax drifts them at 0.25× scroll */
.map-hills{position:absolute;left:0;right:0;height:60px;z-index:1;pointer-events:none;will-change:transform}
.map-hills svg{position:absolute;inset:0;width:100%;height:100%}
.map-hills .mh-far path{fill:var(--sky-horizon,#dcebc8);opacity:.65}
.map-hills .mh-near path{fill:var(--grass,#bfe096);opacity:.55;filter:brightness(.94)}
/* night lanterns: a static warm glow lifts nodes off the dim meadow (calm-safe) */
.map-wrap.night .lvl-node:not(.locked){box-shadow:var(--shadow),0 0 16px 3px rgba(255,206,120,.42)}
.map-wrap.night .lvl-node{text-shadow:0 1px 2px rgba(20,24,40,.32)}

@media (prefers-reduced-motion:reduce){
  .map-firefly,.map-emote,.map-bush.rustle,.lvl-node.pop,.map-critter.startle .mc-body,
  .map-critter.walking .mc-body,
  .lvl-node.current,.sky-star.tw,.sky-cloud.drift,.sky-bird.fly{animation:none}   /* bob was escaping the guard */
}

/* ---------- Low-FX performance tier (body.lowfx, set by config.js on weak devices) ----------
   Quiets the always-on decorative layer so the calm core scrolls at 60fps on a low-end WebView.
   Pure presentation: never changes game state, the board, or readability. The living-map ambient
   sim (JS) is also skipped in ui.js via CE.lowFx, so these elements mostly aren't even built. */
body.lowfx .fab-attract{display:none}
body.lowfx .nav-fab .fab-circle,
body.lowfx .lvl-node.current,
body.lowfx .sky-star.tw,
body.lowfx .sky-cloud.drift,
body.lowfx .sky-bird.fly,
body.lowfx .map-firefly,
body.lowfx .map-critter.walking .mc-body,
body.lowfx .ltier.cur .lt-ico{animation:none}
/* drop-shadow = an offscreen blur pass re-run on every composite/scroll — drop it on weak GPUs */
body.lowfx .mc-body>svg,
body.lowfx .map-tree,
body.lowfx .map-bush,
body.lowfx .map-emote,
body.lowfx .map-butterfly,
body.lowfx .mc-shadow,
body.lowfx .lvl-node.bloom::before,
body.lowfx .lvl-node.bloom::after,
body.lowfx .critter svg,
body.lowfx .hab-fly svg{filter:none}
body.lowfx .map-wrap.night .lvl-node:not(.locked){box-shadow:var(--shadow)}   /* drop the 16px per-node glow */
/* flatten card/list paint: a hairline shadow rasters far cheaper across a scrolling list than a blurred one */
body.lowfx .card,body.lowfx .tcard,body.lowfx .lb-row,body.lowfx .tshort,body.lowfx .hud-pill{box-shadow:0 1px 2px rgba(50,70,40,.12)}
/* in-level: the combo glow (filter:brightness), pipe wave (stroke) + drop-shadow, and tool-target
   pulse (filter:drop-shadow) are PAINT-bound loops that re-rasterize every frame during play — quiet
   them on weak GPUs (gameplay + readability unchanged; only the decorative pulse is dropped) */
body.lowfx .combo-rail.beating .combo-chip,
body.lowfx .combo-rail.beating .combo-fill,
body.lowfx .pipe-liquid,
body.lowfx .critter.tool-target{animation:none}
body.lowfx .pipe-liquid{filter:none}
/* lowfx coverage audit (2026-07-18): infinite animations that ESCAPED the tier gate. The node-ready
   ring alone held the Levels map at ~40fps / ~27% CPU on an SD660 with Reduce effects ON — under
   lowfx every infinite loop must stop. (nodeReady's base opacity is 0, so animation:none hides it.) */
body.lowfx .lvl-node.current::after,
body.lowfx .tool.booster.charged,
body.lowfx .tc-ico.tc-chest,
body.lowfx .win-critters .wc,
body.lowfx .win-critters.st1.go .wc,
body.lowfx .win-critters.st2.go .wc,
body.lowfx .win-critters.st3.go .wc-v0,
body.lowfx .win-critters.st3.go .wc-v1,
body.lowfx .win-critters.st3.go .wc-v2,
body.lowfx .coach-hand,
body.lowfx .coach-glow,
body.lowfx .coach-tapring,
body.lowfx .coach-hand2,
body.lowfx .coach-ripple,
body.lowfx .type-intro.ready .lt-aff{animation:none}
body.lowfx .coach-glow{box-shadow:0 0 0 3px rgba(242,177,52,.55)}   /* static highlight — mirrors the reduced-motion fallback */
/* in-level low-FX: drop every per-frame blur/shadow pass on the board (critter drop-shadow, mis-tap
   pip glow) and halt board spark particles — JS already caps them to 3, this stops the residual
   animate() repaints on weak GPUs. Gameplay + readability unchanged; only decorative paint is cut. */
body.lowfx.in-game .critter svg,
body.lowfx.in-game .mistakes .mh.used{filter:none}
body.lowfx.in-game .spark{display:none}

/* F7: aligned one-row header. Nav (back+pause) left, title+chip center (title ellipsizes, chip never wraps),
   hearts right — all on one 44px baseline. Truncation is scoped to .puzzle-top so the reused .lvl-label
   leaderboard headers ("You"/"Friends") are untouched. */
.puzzle-top .pz-nav{flex:0 0 auto;display:flex;align-items:center;gap:6px}
/* HUD stability (owner 2026-07-14): the title sits DEAD-CENTRE on every level — a 3-track grid keeps it
   there while the type chip lives in the right-hand well (reserved even when empty, so "Level 16" lands on
   the same pixel with or without a chip). Grid auto-placement follows dir, so RTL mirrors for free. */
.puzzle-top .lvl-mid{flex:1 1 auto;min-width:0;display:grid;grid-template-columns:1fr auto 1fr;align-items:center}
.puzzle-top .lvl-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;grid-column:2}
.puzzle-top .lm-well{min-width:0;display:flex;align-items:center;overflow:hidden}
.puzzle-top .ltype-chip{flex:0 0 auto;max-width:112px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.puzzle-top .mistakes{flex:0 0 auto}
/* fixed tool anchors: scout stays far-left, Rewind+Rush far-right — an obstacle tool joining the middle
   never slides the constants (F11 relevance rule unchanged; only geometry is pinned) */
.puzzle-tools .tool-gap{flex:1 1 0;min-width:0;padding:0;border:0;background:none;box-shadow:none;pointer-events:none}
/* F8B pause sheet: sound/music pill toggles + a calm board blur behind the sheet (content zones only, so the
   fixed pipe-frame isn't caught by the filter; off under reduced-motion). */
.pause-togs{display:flex;gap:8px;justify-content:center;margin:2px 0 14px}
.pause-tog{display:inline-flex;align-items:center;gap:6px;padding:8px 14px;border-radius:999px;border:1.5px solid var(--line);background:#fff;color:var(--muted);font-weight:600;font-size:14px}
.pause-tog.on{border-color:var(--green);color:var(--green-d);background:var(--green-l)}
.pause-tog i{font-size:17px}
body.paused .pz-stage,body.paused .pz-dock{filter:blur(2.5px);transition:filter .18s ease}
@media (prefers-reduced-motion:reduce){body.paused .pz-stage,body.paused .pz-dock{filter:none}}
/* ---------- Puzzle ---------- */
/* F1B/F5B scene tokens (shared so the cell math and the scene padding can never desync — one source each).
   --cell is ONE tile size for EVERY level: computed for the 6-col worst case, clamped, applied to all boards,
   so a 4×4 and a 6×6 render at the same tile size (smaller boards just take a smaller centered footprint). */
body.in-game{
  --scene-pad-x:20px;    /* = #screen in-game horizontal padding */
  --board-pad-x:10px;    /* = .board-wrap.has-pipe horizontal padding */
  --board-gap:7px;       /* = board grid gap */
  --cell:clamp(42px, calc((100vw - 2*var(--scene-pad-x) - 2*var(--board-pad-x) - 5*var(--board-gap)) / 6), 58px);
}
.puzzle-top{display:flex;align-items:center;gap:8px;margin:2px 0 8px;min-height:44px}
/* Roll Call quota strip — calm "bring these home" list above the board (cosmetic; never gates state) */
.quota-bar{display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:8px;width:100%;max-width:430px;margin:0 auto 8px}
.quota-bar .quota-lbl{font-size:12px;font-weight:600;color:var(--muted);margin-right:2px}
.quota-chip{display:inline-flex;align-items:center;gap:5px;padding:3px 9px 3px 5px;border-radius:999px;background:rgba(70,90,55,.07);box-shadow:inset 0 0 0 1px rgba(70,90,55,.08);transition:background .25s ease,box-shadow .25s ease,opacity .25s ease}
.quota-chip .qc-art{width:24px;height:24px;display:inline-flex;align-items:center;justify-content:center}
.quota-chip .qc-art svg{width:24px;height:24px;display:block}
.quota-chip .qc-n{font-size:13px;font-weight:700;color:var(--ink,#3c4a35);font-variant-numeric:tabular-nums}
.quota-chip.done{background:rgba(158,194,122,.22);box-shadow:inset 0 0 0 1px rgba(120,170,90,.35)}
.quota-chip.done .qc-n{color:#5a8a3a}

/* ===== Baby Rescue: Survival (BABY_RESCUE.md) — cubs, sleepers, the goal pips + juice (≤ CALM ceiling) ===== */
.cub-bar .cub-pips{display:inline-flex;gap:5px;align-items:center}
.cub-pip{display:inline-flex;color:rgba(150,150,160,.42);font-size:16px}
.cub-pip.saved{color:#e8749a}
.cub-pip.saved i{animation:qpulse .4s ease}
.cub-bar .qc-n{font-size:13px;font-weight:700;color:var(--ink,#3c4a35);font-variant-numeric:tabular-nums;margin-left:3px}
/* the CUB on the board: a soft objective glow (calm opacity pulse — no scale) */
.critter.baby{position:relative}
.critter.baby::after{content:"";position:absolute;inset:-7%;border-radius:26%;pointer-events:none;z-index:-1;
  background:radial-gradient(circle at 50% 46%, rgba(255,182,120,.55), rgba(255,182,120,0) 68%);animation:cubGlow 2.6s ease-in-out infinite}
@keyframes cubGlow{0%,100%{opacity:.32}50%{opacity:.7}}
/* the SLEEPER: gently dimmed + a cool hush until woken */
.critter.sleepy{filter:saturate(.72) brightness(.98)}
.critter.sleepy::after{content:"";position:absolute;inset:-7%;border-radius:26%;pointer-events:none;z-index:-1;
  background:radial-gradient(circle at 50% 46%, rgba(150,170,210,.38), rgba(150,170,210,0) 66%)}
/* R9 SNAIL (R9_SPECIALS_PLAN.md §3.1): a slow-exit critter. A small shell badge marks it; on rescue it
   dashes home at a snail's pace and holds its lane a beat longer. Calm: the badge is static, the slow dash
   is a gentle ease with no strobe, the slime trail is ≤4 soft motes. */
.critter.slow{position:relative}
.critter.slow::after{content:"🐌";position:absolute;right:-7%;bottom:-9%;font-size:42%;line-height:1;
  pointer-events:none;z-index:6;filter:drop-shadow(0 1px 1px rgba(0,0,0,.18));transform:rotate(-4deg)}
.critter.slow-exit{transition:transform .9s cubic-bezier(.33,0,.15,1),opacity .9s cubic-bezier(.4,0,.5,1)!important}
.slime{position:absolute;left:50%;top:54%;width:6px;height:6px;border-radius:50%;pointer-events:none;z-index:4}
/* wake beats — max scale 1.05, within the ceiling */
.critter.yawn{animation:cubYawn .42s ease}
.critter.wakeup{animation:cubWake .46s ease}
@keyframes cubYawn{0%{transform:scaleY(1)}35%{transform:scale(1.05,.94)}70%{transform:scale(.99,1.03)}100%{transform:scale(1)}}
@keyframes cubWake{0%{transform:scale(1)}45%{transform:scale(1.05)}100%{transform:scale(1)}}
/* a heart rises on a cub rescue + the z-puff particle */
.cub-heart{position:absolute;left:50%;top:34%;transform:translate(-50%,0);color:#e8749a;font-size:18px;pointer-events:none;z-index:7;text-shadow:0 1px 2px rgba(0,0,0,.12)}
.zpuff{position:absolute;left:50%;top:42%;width:7px;height:7px;border-radius:50%;background:#cfd6dd;pointer-events:none;z-index:6}
/* all cubs safe: a calm warm wash over the board (opacity only) */
.board.cubs-safe{position:relative}
.board.cubs-safe::after{content:"";position:absolute;inset:-4%;pointer-events:none;z-index:8;border-radius:18px;
  background:radial-gradient(circle at 50% 45%, rgba(255,201,140,.5), rgba(255,182,190,.22) 45%, rgba(255,182,190,0) 72%);animation:cubsSafeWash 1.2s ease-out}
@keyframes cubsSafeWash{0%{opacity:0}28%{opacity:1}100%{opacity:0}}
@media (prefers-reduced-motion:reduce){.critter.baby::after{animation:none}.critter.yawn,.critter.wakeup{animation:none}}
body.calm .critter.baby::after,body.lowfx .critter.baby::after{animation:none}
/* P3: Baby Rescue send-off — the freed cub waves goodbye (art rock only, no scale → 0% impulse), a 👋 emoji rises,
   then the wave-clear "Cubs safe!" banner (scale .96→1 = 4% ≤1.06, fade, no translate). All calm-gated. */
.critter svg.cub-wave{transform-origin:50% 78%;animation:cubWaveBye .42s ease}
@keyframes cubWaveBye{0%{transform:rotate(0)}22%{transform:rotate(-7deg)}52%{transform:rotate(6deg)}78%{transform:rotate(-3deg)}100%{transform:rotate(0)}}
.cub-emoji{position:absolute;left:50%;top:26%;transform:translate(-50%,0);font-size:18px;pointer-events:none;z-index:7}
.cub-wave-banner{position:absolute;left:50%;top:38%;transform:translate(-50%,-50%) scale(.96);display:inline-flex;align-items:center;gap:8px;padding:9px 16px;border-radius:999px;background:linear-gradient(180deg,rgba(255,245,232,.97),rgba(255,232,214,.97));box-shadow:0 6px 20px rgba(180,120,60,.22),inset 0 0 0 1px rgba(220,150,90,.35);color:#7a4a1e;font-weight:800;pointer-events:none;z-index:9;opacity:0;white-space:nowrap}
.cub-wave-banner .cwb-s{font-weight:600;opacity:.72;font-size:.86em}
.cub-wave-banner.in{opacity:1;transform:translate(-50%,-50%) scale(1);transition:opacity .22s ease,transform .28s cubic-bezier(.2,1.2,.3,1)}
.cub-wave-banner.out{opacity:0;transition:opacity .3s ease}
@media (prefers-reduced-motion:reduce){.critter svg.cub-wave{animation:none}.cub-wave-banner,.cub-wave-banner.in{transition:none}}
body.calm .critter svg.cub-wave,body.lowfx .critter svg.cub-wave{animation:none}
/* ENDLESS TAIL (ENDLESS_TAIL.md) ------------------------------------------------ */
/* the GOLDEN CUB (§1.6): a static soft ring from the theme.golden token (--tail-gold/--tail-gold-glow are
   set inline from theme.js — no color constant here); .shimmer adds a slow opacity-only glow (calm-safe:
   no scale, no flash — 2.6s, opacity .10→.18), stripped under reduced-motion/calm like the cub glow */
.critter.golden{position:relative}
.critter.golden::before{content:"";position:absolute;inset:-9%;border-radius:30%;pointer-events:none;z-index:-1;
  box-shadow:0 0 0 2.5px var(--tail-gold);opacity:.55}
.critter.golden.shimmer::after{content:"";position:absolute;inset:-14%;border-radius:34%;pointer-events:none;z-index:-1;
  background:radial-gradient(circle at 50% 46%, var(--tail-gold-glow), transparent 70%);animation:tailGoldShimmer 2.6s ease-in-out infinite}
@keyframes tailGoldShimmer{0%,100%{opacity:.10}50%{opacity:.18}}
@media (prefers-reduced-motion:reduce){.critter.golden.shimmer::after{animation:none;opacity:.12}}
body.calm .critter.golden.shimmer::after,body.lowfx .critter.golden.shimmer::after{animation:none;opacity:.12}
/* win-card opt-in strip (§1.1): one quiet ghost-tone row ≤64px, before .popup-actions — no pulse, no countdown */
.tail-strip{display:flex;align-items:center;gap:10px;max-height:64px;margin:2px 0 10px;padding:9px 12px;border-radius:14px;
  background:rgba(70,90,55,.06);box-shadow:inset 0 0 0 1px rgba(70,90,55,.09);text-align:left}
.tail-strip .ts-ico{font-size:22px;flex:0 0 auto}
.tail-strip .ts-txt{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
.tail-strip .ts-txt b{font-size:14px;font-weight:800;color:var(--ink,#3c4a35);line-height:1.15}
.tail-strip .ts-sub{font-size:11.5px;font-weight:600;color:#7c8a73;line-height:1.2}
.tail-strip .ts-go{flex:0 0 auto;padding:8px 13px;font-size:13px;white-space:nowrap}
[dir=rtl] .tail-strip{text-align:right}
/* bank interstitial / bank card bits: the banked chip + the personal-best sparkle line */
.tail-bank{font-variant-numeric:tabular-nums}
.tail-pb{color:var(--amber,#e6a32e);font-weight:800;white-space:nowrap}
/* golden-wave pre-level announce: the amber accent on the shipped intro shell */
.si-lvl.golden{color:var(--amber,#e6a32e)}
/* Par Path — calm "moves vs par" chip above the board (cosmetic; never gates state) */
.par-bar{display:flex;align-items:center;justify-content:center;gap:10px;width:100%;max-width:430px;margin:0 auto 8px}
.par-bar .par-lbl{display:inline-flex;align-items:center;gap:4px;font-size:13px;font-weight:700;color:var(--ink,#3c4a35);padding:3px 10px;border-radius:999px;background:rgba(70,90,55,.07);box-shadow:inset 0 0 0 1px rgba(70,90,55,.08)}
.par-bar .par-moves{font-size:13px;font-weight:600;color:var(--muted);font-variant-numeric:tabular-nums;transition:color .25s ease}
.par-bar .par-moves.over{color:#c88a3a}   /* gently warm (not red) once you pass par — no loss-aversion */
/* Stacked critters — a friend hidden on top. A soft peek-card behind hints at the one beneath, so the
   depth is fair (known, not hidden information); revealing on top-clear is a calm scale-in (<=1.06). */
.critter.stack-over{z-index:3}
.critter.stack-over::before{content:'';position:absolute;left:7px;right:-6px;top:9px;bottom:-6px;border-radius:24%;background:rgba(150,170,120,.32);box-shadow:inset 0 0 0 1.5px rgba(70,90,55,.18);z-index:-1}
.critter.reveal-under{animation:revealUnder .34s cubic-bezier(.3,1.3,.5,1)}
@keyframes revealUnder{0%{transform:scale(.62);opacity:0}60%{transform:scale(1.05)}100%{transform:scale(1);opacity:1}}
@media (prefers-reduced-motion:reduce){.critter.reveal-under{animation:none}}
/* in-level MIS-TAP pips: remaining = calm green dot, spent = a red ✕ (distinct from the play-gate hearts) */
.mistakes{display:flex;gap:6px;align-items:center}
.mistakes .mh{width:24px;height:24px;display:block}   /* F15: hearts read a touch smaller than the old dots; header title/chip still fit one row */
.mistakes .mh.on path{fill:var(--c-critter)}                                           /* F15 remaining mis-tap: a calm green heart */
.mistakes .mh.bonus.on path{fill:var(--c-coin)}                                        /* Lucky Acorn's extra mis-tap: a golden heart */
.mistakes .mh.bonus.on{filter:drop-shadow(0 1px 2px rgba(230,163,46,.4))}
.mistakes .mh.used path{fill:#efe7df;stroke:var(--c-error);stroke-width:1.4;opacity:.8} /* spent: a soft hollow heart (calm — not a hard ✕) */
.mistakes .mh.lost-beat{animation:heartLost .55s ease}
@keyframes heartLost{0%{transform:scale(1.06)}55%{transform:scale(.94)}100%{transform:scale(1)}}
.mistakes .mh-wrap{position:relative;display:block}                                     /* Bear's gift pip wears its 🐻 tag (attribution ⑨) */
.mistakes .mh-wrap .mh-tag{position:absolute;bottom:-4px;inset-inline-end:-5px;font-size:9px;font-style:normal;pointer-events:none}
.lvl-label{font-weight:700;font-size:18px}
.board-wrap{display:flex;justify-content:center;margin:6px 0 12px}
.board{display:grid;gap:var(--board-gap,7px);width:100%;max-width:430px;touch-action:none}   /* board never scrolls → drag gestures become deterministic pointer streams for the swipe-coach tracker (COACH.md §v2a) */
/* F1B pre-set play area: width/height are computed from --cell × the deterministic col/row counts, so the
   board's footprint is fixed the instant the stage mounts (no reflow when critters/gate-bars/particles paint)
   AND every level's tile is the SAME size. grid repeat(cols,1fr) then makes each column exactly --cell wide. */
.board[style*="--bcols"]{
  width:calc(var(--cell) * var(--bcols) + var(--board-gap) * (var(--bcols) - 1));
  height:calc(var(--cell) * var(--brows) + var(--board-gap) * (var(--brows) - 1));
  max-width:100%;
}
/* Safety net for boards WIDER than the 6-col --cell design max (the F1B formula above assumes ≤6 cols):
   scale the tile down so the board always fits the scene instead of its slots painting past the viewport
   (aspect-ratio:1/1 slots take the row-track height, so a width-squeezed board still overflowed
   horizontally — the L15 3×9 Rapids bug). For every ≤6-col board --cell-fit ≥ --cell, so min() keeps
   them byte-identical. Shaped boards (.tri-board/.hex-board) self-size in their renderers and stay
   pinned to --cell. */
.board[style*="--bcols"]:not(.tri-board):not(.hex-board){
  --cell-fit:min(var(--cell), calc((100vw - 2*var(--scene-pad-x,20px) - 2*var(--board-pad-x,10px) - (var(--bcols) - 1)*var(--board-gap,7px)) / var(--bcols)));
  width:calc(var(--cell-fit) * var(--bcols) + var(--board-gap,7px) * (var(--bcols) - 1));
  height:calc(var(--cell-fit) * var(--brows) + var(--board-gap,7px) * (var(--brows) - 1));
}
.slot{position:relative;aspect-ratio:1/1;border-radius:13px;background:rgba(255,255,255,.45);box-shadow:inset 0 0 0 1px rgba(70,90,55,.06)}
.critter{position:absolute;inset:0;cursor:pointer;transition:transform .36s cubic-bezier(.5,0,.2,1),opacity .36s}   /* no blanket will-change: it force-promoted 20-28 filtered compositor layers per board; the exit transition promotes on its own for its .36s */
.critter svg{width:100%;height:100%;display:block;filter:drop-shadow(0 2px 2px rgba(40,55,30,.18))}
.critter:active{transform:scale(.93)}
.critter.exit-U{transform:translateY(-900%) scaleY(1.15) scaleX(.82);opacity:0}
.critter.exit-D{transform:translateY(900%) scaleY(1.15) scaleX(.82);opacity:0}
.critter.exit-L{transform:translateX(-900%) scaleX(1.15) scaleY(.82);opacity:0}
.critter.exit-R{transform:translateX(900%) scaleX(1.15) scaleY(.82);opacity:0}
.critter.shake{animation:shk .42s}
.critter.pulse{animation:pulse .6s ease 2}
/* ===== TRUE triangle board (POLYGON_BOARDS.md §5) — absolute triangular tile layer ===== */
/* ONE clean big triangle (POLYGON_BOARDS.md §5). The board IS the triangle's bounding box; a single SVG
   plate fills it edge-to-edge, so there's NO competing rectangular frame or per-tile square. */
.board.tri-board{display:block;gap:0;position:relative;margin:0 auto;background:none!important;box-shadow:none!important}
/* suppress the rectangular play-area frame + texture + watermark + foliage so only the shaped board reads
   (shared by the triangle and hexagon true-geometry boards) */
.board-wrap.has-pipe.shaped-wrap{background:none!important;box-shadow:none!important;padding-top:4px}
.board-wrap.shaped-wrap::before{display:none!important}
.board-wrap.shaped-wrap .lt-watermark,.board-wrap.shaped-wrap .lvl-foliage{display:none!important}
/* the big-triangle plate + subtle tessellation grid + a bold outer border (the "allowed" boundary) */
.tri-plate-wrap{position:absolute;inset:0;z-index:0;pointer-events:none}
.tri-plate-wrap svg{width:100%;height:100%;display:block;overflow:visible}
.tri-plate{fill:var(--lt-l,#e7f0d8);fill-opacity:.5}
.tri-grid polygon{fill:none;stroke:var(--lt-k,#5a6b52);stroke-width:1.1;stroke-opacity:.13;stroke-linejoin:round}
.tri-edge{fill:none;stroke:var(--lt-b,#8FBF52);stroke-width:4.5;stroke-opacity:.55;stroke-linejoin:round}
/* tri-slots: transparent hit-targets that hold critters (clip = the triangle so taps tessellate cleanly) */
.tri-slot{position:absolute;aspect-ratio:auto;border-radius:0!important;background:none!important;box-shadow:none!important;pointer-events:none;z-index:1}
.tri-slot::before{display:none!important}
.tri-slot .critter{pointer-events:auto}
.tri-slot.tri-up .critter{clip-path:polygon(50% 0,100% 100%,0 100%)}
.tri-slot.tri-down .critter{clip-path:polygon(0 0,100% 0,50% 100%)}
/* exit fling — the critter slides STRAIGHT out its edge's outward normal (clip removed so it flies free) */
.critter.exit-D,.critter.exit-U,.critter.exit-UL,.critter.exit-UR,.critter.exit-DL,.critter.exit-DR{clip-path:none!important;opacity:0}
.critter.exit-D{transform:translateY(760%) scale(.82)}                 /* down (up-tile bottom edge) */
.critter.exit-U{transform:translateY(-760%) scale(.82)}                /* up (down-tile top edge) */
.critter.exit-UL{transform:translate(-640%,-370%) scale(.82)}          /* up-left */
.critter.exit-UR{transform:translate(640%,-370%) scale(.82)}           /* up-right */
.critter.exit-DL{transform:translate(-640%,370%) scale(.82)}           /* down-left */
.critter.exit-DR{transform:translate(640%,370%) scale(.82)}            /* down-right */

/* ===== TRUE hexagon board (POLYGON_BOARDS.md §6) — pointy-top honeycomb tile layer ===== */
/* The board IS the honeycomb's bounding box; one SVG draws a faint per-cell fill + a BOLD outer silhouette
   (boundary edges only), so there's NO competing rectangular frame. */
.board.hex-board{display:block;gap:0;position:relative;margin:0 auto;background:none!important;box-shadow:none!important}
.hex-plate-wrap{position:absolute;inset:0;z-index:0;pointer-events:none}
.hex-plate-wrap svg{width:100%;height:100%;display:block;overflow:visible}
.hex-cells polygon{fill:var(--lt-l,#e7f0d8);fill-opacity:.42;stroke:var(--lt-k,#5a6b52);stroke-width:1.1;stroke-opacity:.16;stroke-linejoin:round}
.hex-edge line{fill:none;stroke:var(--lt-b,#8FBF52);stroke-width:5;stroke-opacity:.6;stroke-linecap:round}
/* hex-slots: transparent hit-targets that hold critters (clip = the hexagon so taps tessellate cleanly) */
.hex-slot{position:absolute;aspect-ratio:auto;border-radius:0!important;background:none!important;box-shadow:none!important;pointer-events:none;z-index:1}
.hex-slot::before{display:none!important}
.hex-slot .critter{pointer-events:auto;clip-path:polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%)}
/* exit fling — the critter slides STRAIGHT out its edge's outward normal (clip removed so it flies free) */
.critter.exit-E,.critter.exit-W,.critter.exit-NE,.critter.exit-NW,.critter.exit-SE,.critter.exit-SW{clip-path:none!important;opacity:0}
.critter.exit-E{transform:translateX(720%) scale(.82)}                 /* east (right edge) */
.critter.exit-W{transform:translateX(-720%) scale(.82)}                /* west (left edge) */
.critter.exit-NE{transform:translate(600%,-500%) scale(.82)}           /* up-right */
.critter.exit-NW{transform:translate(-600%,-500%) scale(.82)}          /* up-left */
.critter.exit-SE{transform:translate(600%,500%) scale(.82)}            /* down-right */
.critter.exit-SW{transform:translate(-600%,500%) scale(.82)}           /* down-left */
@keyframes shk{0%,100%{transform:scale(1)}30%{transform:scale(1.05,.93)}60%{transform:scale(.98,1.02)}}
@keyframes pulse{0%,100%{transform:scale(1)}50%{transform:scale(1.12)}}
/* F11: tray fits the in-game content width even in the 6-tool worst case (scout+spin+pick+key+rewind+rush).
   Budget = the #screen in-game content width: 360px floor − 40px (#screen 20px×2 pad, styles.css:435) = 320px.
   6×48 min-width + 5×6 gap = 318 ≤ 320 → never clips or scrolls (tools/quality-gate.mjs asserts this). */
.puzzle-tools{display:flex;gap:6px;justify-content:center;margin-top:4px;min-height:64px;align-items:stretch;flex-wrap:nowrap}
/* fixed tool slots: >=44px tap target + a reserved-width count badge so the row's geometry is set
   ONCE — a count going 1->99 (or the booster 0%->100%) never re-flows the rail under the board. */
.tool{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;background:#fff;border:1px solid var(--line);border-radius:14px;padding:6px 4px;box-shadow:var(--shadow);min-width:48px;min-height:44px}
.tool i{font-size:22px;color:var(--green-d)}
.tool .cnt{font-size:12px;font-weight:600;color:var(--muted);text-align:center;font-variant-numeric:tabular-nums}
.tool .lbl{font-size:11px;line-height:1;font-weight:600;color:var(--muted);max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}   /* F11: ellipsize, never wrap — survives long i18n labels */
.tool.armed{box-shadow:0 0 0 2px var(--green),var(--shadow)}
.tool.armed i{color:var(--green)}
.critter.tool-target{animation:toolTargetPulse 1.2s ease-in-out infinite}
@keyframes toolTargetPulse{0%,100%{filter:none}50%{filter:drop-shadow(0 0 6px var(--green))}}
@media (prefers-reduced-motion:reduce){.critter.tool-target{animation:none;outline:2px dashed var(--green);outline-offset:2px}}
body.calm .critter.tool-target{animation:none;outline:2px dashed var(--green);outline-offset:2px}
.tool:active{transform:scale(.96)}
.float-coin{position:absolute;font-weight:600;color:var(--amber-d);pointer-events:none;animation:fup .8s ease forwards;z-index:5;font-size:13px;opacity:.9}   /* P2/D2: de-emphasized (was 17px/700) so the growing combo is the hero; +5 stays honest but quiet */
@keyframes fup{from{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-40px)}}

/* ---------- Sanctuary ---------- */
.sanc-hero{border-radius:var(--r-lg);padding:18px;background:linear-gradient(160deg,#cbeccf,#aee0c0);box-shadow:var(--shadow);margin-bottom:14px;position:relative;overflow:hidden}
.sanc-hero h3{margin:0 0 2px;font-size:20px}
.sanc-critters{display:flex;flex-wrap:wrap;gap:6px;margin-top:10px}
.sanc-critters .mini{width:42px;height:42px;background:rgba(255,255,255,.6);border-radius:11px;padding:3px}
.task{display:flex;align-items:center;gap:12px;padding:12px 4px;border-bottom:1px solid var(--line)}
.task:last-child{border-bottom:none}
.task .ti-circle-check{color:var(--green)}
.collect-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(60px,1fr));gap:10px;margin-top:8px}
.collect-cell{background:#fff;border:1px solid var(--line);border-radius:14px;padding:6px;text-align:center;box-shadow:var(--shadow)}
.collect-cell svg{width:100%;height:auto}
.collect-cell.unseen{filter:grayscale(1);opacity:.32}
.collect-cell .cn{font-size:11px;color:var(--muted);font-weight:600;margin-top:2px}

/* ---------- Shop / Daily ---------- */
.shop-card{display:flex;align-items:center;gap:12px;padding:14px;margin-bottom:11px}
.shop-icon{width:50px;height:50px;border-radius:14px;display:grid;place-items:center;font-size:27px;color:#fff;flex-shrink:0}
.shop-card h4{margin:0 0 2px;font-size:16px}
.shop-card p{margin:0;font-size:13px;color:var(--muted);font-weight:500}
.price-btn{background:var(--green);color:#fff;border-radius:12px;padding:9px 14px;font-weight:700;font-size:14px;box-shadow:0 3px 0 var(--green-d)}
.price-btn:active{transform:translateY(2px);box-shadow:0 1px 0 var(--green-d)}
.badge-best{position:absolute;top:10px;right:10px;background:var(--amber);color:#5a3d00;font-size:11px;font-weight:700;padding:3px 9px;border-radius:999px}
.bundle-head{font-weight:700;font-size:15px;margin:6px 2px 9px}
.bundle-card.picked{outline:2px solid var(--green);outline-offset:1px}
.proto-note{font-size:12px;color:var(--muted);text-align:center;margin:8px 0 2px;font-weight:500;min-height:30px}

/* ---------- Overlay popups ---------- */
/* F14: backdrop snaps to full dim in <=120ms so the board never ghosts through a mid-fade card/text */
#overlay{position:absolute;inset:0;background:rgba(35,45,30,.5);display:grid;place-items:center;z-index:50;padding:max(22px,env(safe-area-inset-top)) max(16px,env(safe-area-inset-right)) max(22px,env(safe-area-inset-bottom)) max(16px,env(safe-area-inset-left));animation:fade .12s ease-out}
@keyframes fade{from{opacity:0}to{opacity:1}}
/* .popup is its own scroll container: max-height fits the safe-area-padded overlay cell, overscroll-behavior
   contains the gesture (no background scroll-chaining, item #8), internal scroll keeps header+close reachable
   on short/notched screens (item #10a). body.popup-open freezes the layer behind the dim. */
/* F14: card settles in 200ms with a NON-overshoot curve (peak scale = 1.0, within the 1.06 calm ceiling — the old .2,1.3,.4,1 could overshoot) */
.popup{position:relative;background:#fff;border-radius:24px;padding:22px 20px;width:100%;max-width:330px;max-height:100%;overflow-y:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;text-align:center;box-shadow:0 20px 50px rgba(0,0,0,.3);animation:pop .2s cubic-bezier(.2,.9,.3,1)}
body.popup-open,body.popup-open #screen{overflow:hidden}
@media (max-height:560px){.popup{padding:14px 16px}}
@keyframes pop{from{transform:scale(.9);opacity:0}to{transform:scale(1);opacity:1}}
/* Outside-tap on a GUARDED popup (continue/fail/win flows): one gentle "make a choice" pulse — a single
   rise+decay, scale 1.03 (calm ceiling 1.06), never a shake, no sound. */
.popup.need-choice{animation:needChoice .32s ease-out}
@keyframes needChoice{0%{transform:scale(1)}45%{transform:scale(1.03)}100%{transform:scale(1)}}
/* F14: reduced-motion → no entrance animation, card appears instantly opaque (no ghost, no scale) */
@media (prefers-reduced-motion:reduce){#overlay{animation:none}.popup{animation:none}.popup.need-choice{animation:none}}
/* R6 (fail sheet shows the board): a fail/continue popup becomes a bottom SHEET. #overlay.sheet-mode thins the
   scrim to a bottom-weighted gradient (the board reads through the top) and pins the card to the bottom; the card
   goes full-width with a rounded top and slides up. Buttons/flows are unchanged — only the frame moves. */
#overlay.sheet-mode{align-items:end;padding:0;background:linear-gradient(to bottom,rgba(35,45,30,.10) 0%,rgba(35,45,30,.30) 55%,rgba(35,45,30,.6) 100%)}
.popup.popup-sheet{align-self:end;width:100%;max-width:560px;max-height:80%;border-radius:22px 22px 0 0;
  padding:22px 20px calc(20px + env(safe-area-inset-bottom));box-shadow:0 -12px 40px rgba(0,0,0,.28);animation:sheetUp .3s cubic-bezier(.2,.9,.3,1)}
@keyframes sheetUp{from{transform:translateY(100%);opacity:.7}to{transform:translateY(0);opacity:1}}
@media (prefers-reduced-motion:reduce){.popup.popup-sheet{animation:none}}
body.calm .popup.popup-sheet{animation:none}
.popup h2{margin:6px 0 4px;font-size:23px}
.popup p{margin:0 0 14px;color:var(--muted);font-weight:500}
.popup .big-ico{font-size:48px;line-height:1}
.popup .stuck-ico{animation:stuckIn .45s ease-out}   /* "A Critter's Stuck!" — one rise+decay entrance, calm ceiling ≤1.05, no loop */
@keyframes stuckIn{0%{transform:scale(.82);opacity:0}60%{transform:scale(1.05)}100%{transform:scale(1);opacity:1}}
@media (prefers-reduced-motion:reduce){.popup .stuck-ico{animation:none}}
.lpromo{display:flex;align-items:center;gap:10px;background:linear-gradient(135deg,#fef6e4,#fdeccb);border:1.5px solid #f2d79a;border-radius:14px;padding:10px 12px;margin:0 0 12px}   /* League promotion chest banner */
.lpromo-ico{font-size:26px;line-height:1}
.lpromo-body{flex:1;text-align:left;min-width:0}
.lpromo-t{font-weight:800;color:#8a5a12}
.lpromo-r{font-size:13px;color:#a07a2e;display:flex;gap:4px;flex-wrap:wrap;margin-top:2px}
.popup .stars{display:flex;justify-content:center;gap:10px;margin:10px 0}
.popup .stars .star{width:42px;height:42px}
/* §8.2 plump star: a 10-vertex polygon inflated by a same-colour round stroke (paint-order:stroke). ONE style —
   no flawless gradient variant. Unfilled = a soft ghost (no dark outline); filled = gold with a darker gold rim. */
.popup .stars .star polygon{fill:rgba(122,160,106,.18);stroke:none}
.popup .stars .star.on polygon{fill:var(--amber);stroke:var(--amber-d);stroke-width:2.6;stroke-linejoin:round;paint-order:stroke}
.popup .stars .star.on{animation:starPop .42s cubic-bezier(.2,1.45,.4,1) backwards}   /* R1: JS staggers the fill one-by-one */
.popup .stars .star.on polygon{filter:drop-shadow(0 2px 4px rgba(214,146,15,.4))}
@keyframes starPop{0%{transform:scale(.3) rotate(-22deg);opacity:0}60%{transform:scale(1.06) rotate(4deg)}100%{transform:scale(1) rotate(0);opacity:1}}
.win-critters{display:flex;justify-content:center;gap:6px;margin:2px 0 4px}
/* §9.3 phases: entrance pop → gentle idle bob → star-tier verdict move once .go lands (ui.js, last-star beat).
   All ≤ CALM ceiling: travel ≤5px on a 46px sprite, rotate ≤6°, scale ≤1.06. Inline animation-delay staggers
   both the entrance and the loops (the st3 dance troupe moves out of phase on purpose). */
.win-critters .wc{width:46px;height:46px;display:block;transform-origin:50% 80%;animation:wcIn .45s cubic-bezier(.2,1.4,.4,1) both, wcBob 2.6s ease-in-out .45s infinite}
@keyframes wcIn{from{opacity:0;transform:translateY(7px) scale(.86)}to{opacity:1;transform:translateY(0) scale(1)}}
@keyframes wcBob{0%,100%{transform:translateY(0) rotate(-1.5deg)}50%{transform:translateY(-2px) rotate(1.5deg)}}
.win-critters.go .wc{opacity:1}
.win-critters.st1.go .wc{animation:wcSway 2.3s ease-in-out infinite}
@keyframes wcSway{0%,100%{transform:rotate(-4deg)}50%{transform:rotate(4deg) translateY(-2px)}}
.win-critters.st2.go .wc{animation:wcCheer 1.05s cubic-bezier(.3,1.2,.4,1) infinite}
@keyframes wcCheer{0%,100%{transform:translateY(0) scaleY(1)}18%{transform:translateY(0) scaleY(.94)}46%{transform:translateY(-5px) scaleY(1.04)}74%{transform:translateY(0) scaleY(1)}}
.win-critters.st3.go .wc-v0{animation:wcDanceA 1.15s ease-in-out infinite}
.win-critters.st3.go .wc-v1{animation:wcDanceB 1.15s ease-in-out infinite}
.win-critters.st3.go .wc-v2{animation:wcDanceC 1.15s ease-in-out infinite}
@keyframes wcDanceA{0%,100%{transform:translateX(-3px) rotate(-6deg)}50%{transform:translateX(3px) rotate(6deg) translateY(-3px)}}
@keyframes wcDanceB{0%,100%{transform:translateY(0) rotate(4deg)}25%{transform:translateY(-4px) rotate(-2deg)}50%{transform:translateY(0) rotate(-5deg)}75%{transform:translateY(-4px) rotate(2deg)}}
@keyframes wcDanceC{0%,100%{transform:translateY(-3px) scale(1.03)}50%{transform:translateY(1px) scale(.97) rotate(-4deg)}}
body.calm .win-critters .wc{animation:none !important}
/* §9.2 verdict line — a static plain-English sentence (information is never motion-gated) */
.win-sub{font-size:13.5px;font-weight:600;color:#6b7a5c;line-height:1.45;margin:0 14px 10px;text-align:center}
.reward-row{display:flex;justify-content:center;gap:16px;margin:10px 0 16px;font-weight:600}
.reward-row .it{display:flex;align-items:center;gap:5px}
/* F1: itemized "earned → where it went" ledger — a calm receipt (icon · amount · muted destination pill) */
.reward-ledger{flex-direction:column;gap:7px;align-items:stretch;max-width:280px;margin-left:auto;margin-right:auto}
.reward-ledger .rl-item{display:flex;align-items:center;gap:8px;font-weight:800;font-size:15px;color:#54634a}
.reward-ledger .rl-item .ti{font-size:19px;flex:none}
.reward-ledger .rl-dest{margin-left:auto;font-weight:700;font-size:11px;color:#8aa07d;background:rgba(122,160,106,.13);padding:2px 9px;border-radius:99px;white-space:nowrap}
.place-strip{display:flex;align-items:center;justify-content:center;gap:6px;margin:-4px 0 12px;font-size:12.5px;font-weight:700;color:#6b7a5c}
.place-strip i{font-size:15px}
/* Endgame completion: live-ops progress fold · gentle "remaining to next" line · placement-strip reveal juice. All calm-ceiling safe (≤7px rise, no scale/flash/particle/haptic). */
.res-progress{display:flex;flex-direction:column;gap:6px;max-width:290px;margin:0 auto 12px}
.res-progress .rp-item{display:flex;align-items:center;gap:8px;font-size:13px;font-weight:700;color:#5f6f52;background:rgba(122,160,106,.10);border-radius:12px;padding:6px 11px;animation:rpIn .42s cubic-bezier(.2,1.1,.4,1) both}
.res-progress .rp-item i{font-size:16px;flex:none;color:#7bbe6a}
.res-progress .rp-next{background:none;color:#8aa07d;font-weight:700;justify-content:center;padding:1px 0 0;font-size:12.5px}
.res-progress .rp-next i{color:#8bbf6f;font-size:15px}
@keyframes rpIn{from{transform:translateY(7px);opacity:0}to{transform:translateY(0);opacity:1}}
.place-strip{animation:rpIn .5s cubic-bezier(.2,1.1,.4,1) both}
/* calm + reduced-motion → render the endgame progress cluster + placement strip STATIC (information is never motion-gated) */
@media (prefers-reduced-motion:reduce){ .res-progress .rp-item,.place-strip{animation:none !important} }
body.calm .res-progress .rp-item,body.calm .place-strip{animation:none !important}
/* ===== Iconic end-game: stat tiles · progress card · rank chip — low-text, count-up + meter (§9.4: heart-pips removed) ===== */
.win-stats{display:flex;gap:8px;justify-content:center;flex-wrap:wrap;margin:8px auto 14px;max-width:320px}
.stat-tile{position:relative;flex:1 1 0;min-width:56px;max-width:100px;background:var(--card);border:1px solid var(--line);border-radius:var(--r);padding:9px 6px 8px;display:flex;flex-direction:column;align-items:center;gap:3px;box-shadow:var(--shadow);animation:rpIn .4s cubic-bezier(.2,1.1,.4,1) both}
.stat-tile>.ti:first-child{font-size:20px}
.stat-tile .stat-n{font-size:20px;font-weight:800;color:var(--ink);line-height:1;font-variant-numeric:tabular-nums}
.stat-tile .stat-max{font-size:10px;color:var(--muted);opacity:.7;margin-top:-2px}
.stat-tile .stat-dest{position:absolute;right:5px;bottom:4px;font-size:12px;color:var(--muted);opacity:.5}
/* A4 Golden Rush: gold-tinted coin tile + the ×2 chip; gold-hit pops on the doubling beat (scale ≤ CALM 1.06) */
.stat-tile.golden{background:linear-gradient(160deg,#fff7e0,#ffedbd);border-color:#ecd28e}
.stat-tile .gold-x2{font-size:10px;font-weight:800;color:#a06e00;background:#ffe9b3;border-radius:999px;padding:1px 6px;line-height:1.4;white-space:nowrap}
.stat-tile.gold-hit{animation:goldHit .42s cubic-bezier(.2,1.2,.4,1)}
@keyframes goldHit{50%{transform:scale(1.06)}}
/* A1: win-card mini wallet — the VISIBLE landing target for the reward flight (the HUD is hidden in-game) */
.win-wallet{position:absolute;top:10px;right:12px;display:flex;gap:6px;z-index:2}
.win-wallet .ww{display:inline-flex;align-items:center;gap:4px;background:var(--card);border:1px solid var(--line);border-radius:999px;padding:4px 9px;box-shadow:var(--shadow);font-weight:800;font-size:13px;color:var(--ink);font-variant-numeric:tabular-nums}
.win-wallet .ww .ti{font-size:14px;color:#e6a32e}
.win-wallet .ww.gem .ti{color:#3b9ae8}
[dir=rtl] .win-wallet{right:auto;left:12px}
/* ===== §8.1 grouped Progress Card — one bordered card folds pass/rxp/event/streak (where earnings ACCRUE);
   the stat tiles above are what you EARNED this run. Rings → linear meters (RTL-safe via inset-inline). ===== */
.win-prog{width:min(320px,100%);margin:8px auto 12px;background:var(--card);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);padding:2px 12px}
.wp-row{display:grid;grid-template-columns:24px 1fr auto;column-gap:10px;align-items:center;min-height:38px}
.wp-row+.wp-row{border-top:1px solid var(--line)}
.wp-ico{width:24px;height:24px;border-radius:8px;display:flex;align-items:center;justify-content:center;flex:none}
.wp-ico .ti{font-size:15px}
.wp-ico.t-pass{background:rgba(95,174,95,.14)} .wp-ico.t-pass .ti{color:var(--green-d,#3f8a47)}
.wp-ico.t-rxp{background:rgba(242,177,52,.16)} .wp-ico.t-rxp .ti{color:var(--amber-d,#d6920f)}
.wp-ico.t-event{background:rgba(63,138,71,.14)} .wp-ico.t-event .ti{color:var(--green-d,#3f8a47)}
.wp-ico.t-streak{background:rgba(242,177,52,.16)} .wp-ico.t-streak .ti{color:var(--amber,#f2b134)}
.wp-meter{height:8px;border-radius:999px;background:rgba(122,160,106,.15);position:relative;overflow:hidden}
.wp-fill{position:absolute;inset-block:0;inset-inline-start:0;border-radius:999px;width:0}
.wp-pips{display:flex;gap:4px}
.wp-pips .pip{flex:1;height:8px;border-radius:3px;background:rgba(242,177,52,.22)}
.wp-pips .pip.lit{background:var(--amber,#f2b134)}
.wp-pips .pip.next{box-shadow:inset 0 0 0 2px rgba(242,177,52,.5)}
.wp-val{min-width:48px;display:inline-flex;align-items:center;justify-content:flex-end;font-size:14px;font-weight:800;color:var(--ink);font-variant-numeric:tabular-nums}
.wp-val .num{color:var(--green-d,#3f8a47)}
.wp-val .den{font-size:12px;font-weight:800;color:var(--muted)}
.wp-val .rxp-gain{margin-inline-start:5px}
/* ===== §8.3 ONE pill grammar + the chip shelf (rank chip · Power Hour · refHook · mural · mission · streak gift) ===== */
.win-chips{display:flex;flex-wrap:wrap;justify-content:center;gap:6px;max-width:320px;margin:6px auto 12px}
.win-chip{border:0;border-radius:999px;padding:5px 11px;font-size:12px;font-weight:800;font-variant-numeric:tabular-nums;display:inline-flex;align-items:center;gap:5px}
.win-chip>.ti{font-size:14px}
.win-chip.sm{padding:1px 7px;font-size:11px}   /* the small delta pill (+41, gift chips) */
.win-chip.wc-green{background:var(--green-l,#e7f6e2);color:var(--green-d,#3f8a47)}
.win-chip.wc-amber{background:#fff2cf;color:#8a5a00}
.win-chip.wc-purple{background:rgba(154,143,224,.14);color:#6a4bb0}
.win-chip.done-stamp{cursor:pointer}
.win-chip.done-stamp .mck{color:var(--green,#5fae5f);animation:discBloom .42s ease-out both}
.win-chip .mbc-paw{font-size:14px}
/* Win-card micro-derby (B1/B4): the rank chip becomes a 3-5 row mini-ladder — your row FLIP-slides up
   past the rival you just passed (transforms set inline by runWinDerby; ≤4 rows, ~120ms/row).
   Calm/reduced-motion renders the FINAL order statically with the ± chip as the tell. */
.win-derby{margin:2px auto 12px;max-width:300px;animation:rpIn .42s cubic-bezier(.2,1.1,.4,1) both}
.win-derby .dby-chip{display:inline-flex;align-items:center;gap:4px;justify-content:center;margin:0 auto 6px;font-weight:800;font-size:15px;color:var(--ink);font-variant-numeric:tabular-nums}
.win-derby .dby-chip>.ti{color:var(--amber,#f2b134);font-size:16px}
.win-derby .dby-pop{opacity:0;font-weight:800;font-size:11px;color:#3d8b3d;background:rgba(95,174,95,.18);padding:2px 7px;border-radius:999px}
.win-derby .dby-pop.on{opacity:1;animation:dbyPop .38s cubic-bezier(.2,1.05,.4,1) both}
@keyframes dbyPop{0%{transform:scale(.7);opacity:0}100%{transform:scale(1);opacity:1}}
.win-derby .dby-rows{display:flex;flex-direction:column;gap:2px;overflow:hidden;border-radius:11px}
.win-derby .dby-row{display:grid;grid-template-columns:38px 30px 1fr auto;align-items:center;gap:8px;height:34px;padding:0 8px;border-radius:9px;background:var(--card-2,#f3f1e8);will-change:transform}
.win-derby .dby-row.you{background:rgba(242,177,52,.14);box-shadow:inset 0 0 0 2px var(--amber,#f2b134);position:relative;z-index:1}
.win-derby .dby-rk{font-weight:800;font-size:12.5px;color:#9a9a9a;font-variant-numeric:tabular-nums;text-align:left}
.win-derby .dby-row.you .dby-rk{color:var(--amber-d,#d6920f)}
.win-derby .dby-av{width:26px;height:26px;border-radius:50%;overflow:hidden;background:var(--card,#fff);border:1.5px solid var(--line)}
.win-derby .dby-av svg{width:23px;height:23px;display:block}
.win-derby .dby-nm{min-width:0;text-align:left;font-weight:700;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.win-derby .dby-row.you .dby-nm{color:var(--amber-d,#d6920f)}
.win-derby .dby-sc{font-weight:800;font-size:12px;color:var(--muted);font-variant-numeric:tabular-nums}
.win-derby .dby-sub{margin-top:5px;font-size:11.5px;color:var(--muted)}
/* rankUpBeat owns the FLIP + chime here; its row ±chip is redundant with the header pop → hide it */
.win-derby .dby-row .rank-delta,.win-derby .dby-row .lb-delta{display:none}
[dir=rtl] .win-derby .dby-rk,[dir=rtl] .win-derby .dby-nm{text-align:right}
/* B5: the dust the water-jar just banked lands here (tap → the daily Dust board); motes fly in, then the chip
   fades up. §8.3: it keeps its pot anchoring + flight but adopts the one pill grammar (padding/gap/size). */
.jar-dust-chip{display:inline-flex;align-items:center;gap:5px;margin:6px auto 0;padding:5px 11px;border:0;border-radius:999px;cursor:pointer;
  font-weight:800;font-size:12px;color:#8a5a00;background:linear-gradient(180deg,#fff2cf,#ffe6a6);box-shadow:inset 0 0 0 1px rgba(230,163,46,.28);
  opacity:0;transform:translateY(4px);transition:opacity .34s ease,transform .34s cubic-bezier(.2,1.05,.4,1)}
.jar-dust-chip.in{opacity:1;transform:translateY(0)}
@media (prefers-reduced-motion:reduce){.jar-dust-chip{transition:none}}
body.calm .jar-dust-chip{transition:none}
[dir=rtl] .stat-tile .stat-dest{right:auto;left:5px}
/* calm + reduced-motion: everything final + static (no count-up roll, no meter fill, no reveal) — info never motion-gated */
@media (prefers-reduced-motion:reduce){ .stat-tile,.win-derby,.win-derby .dby-pop.on,.win-chip .mck{animation:none !important} .wp-fill{transition:none !important} .win-derby .dby-pop{opacity:1} }
body.calm .stat-tile,body.calm .win-derby,body.calm .win-derby .dby-pop.on,body.calm .win-chip .mck{animation:none !important}
body.calm .win-derby .dby-pop{opacity:1}
body.calm .wp-fill{transition:none !important}
/* starPop calm compliance: add the missing body.calm guard (was reduced-motion-only) so calm users get static stars */
body.calm .popup .stars .star.on{animation:none}
/* P1.5 v2 (Fixed Stage, owner Option B 2026-07-14): the "pow" backdrop covers the WHOLE card — a soft green
   ray fan slowly turning (70s, 0 flash) behind a full-card glow that breathes (6s opacity, ≤.18 alpha).
   Present from mount (t0), aria-hidden, canonical green only. body.calm / reduced-motion pin both layers
   static — information never rides the motion, the pattern itself stays. Rays are a FIXED 680px square
   (covers the 330×584 card's diagonal) so weak compositors rasterize one bounded tile, never a %-of-page. */
.popup.win-card{position:relative}
.win-pow{position:absolute;inset:0;min-height:100%;border-radius:24px;pointer-events:none;z-index:0;overflow:hidden;opacity:0;transition:opacity .5s ease-out}
.win-pow::before{content:'';position:absolute;left:50%;top:50%;width:680px;height:680px;margin:-340px 0 0 -340px;background:repeating-conic-gradient(from 0deg at 50% 50%,rgba(95,174,95,.09) 0deg 6deg,transparent 6deg 15deg);animation:powSpin 70s linear infinite}
.win-pow::after{content:'';position:absolute;inset:-14%;background:radial-gradient(circle at 50% 32%,rgba(95,174,95,.18),rgba(95,174,95,.05) 58%,rgba(95,174,95,0) 80%);animation:powBreathe 6s ease-in-out infinite}
@keyframes powSpin{to{transform:rotate(360deg)}}
@keyframes powBreathe{0%,100%{opacity:.6}50%{opacity:1}}
.win-pow.show{opacity:1}
.popup.win-card > *:not(.win-pow){position:relative;z-index:1}
@media (prefers-reduced-motion:reduce){.win-pow{transition:none}.win-pow::before,.win-pow::after{animation:none}}
body.calm .win-pow{transition:none}
body.calm .win-pow::before,body.calm .win-pow::after{animation:none}
body.lowfx .win-pow::before,body.lowfx .win-pow::after{animation:none}
/* Heading juice: "Safe with you!" pops in, then crossfades to a paw (the Critters-tab symbol) whose SHAPE fills up bottom→top = the destination, iconic */
.win-head{position:relative;height:52px;margin:2px 0 6px;display:flex;align-items:center;justify-content:center}
.win-head .wh-text{position:absolute;font-family:Fredoka,system-ui,-apple-system,sans-serif;font-size:25px;font-weight:800;color:var(--green-d,#3f8a47);white-space:nowrap;animation:whPop .5s cubic-bezier(.2,1.4,.4,1) backwards;transition:opacity .38s ease,transform .38s ease}
.win-head .wh-icon{position:absolute;width:46px;height:46px;opacity:0;transform:scale(.85);transition:opacity .38s ease,transform .45s cubic-bezier(.2,1.4,.4,1)}
.win-head.to-icon .wh-text{opacity:0;transform:scale(.9)}
.win-head.to-icon .wh-icon{opacity:1;transform:scale(1)}
.fill-icon{width:46px;height:46px;display:block}
.fill-icon .paw-ghost{fill:rgba(95,174,95,.2)}
.fill-icon .fill-rect{fill:var(--green,#5fae5f);transform:scaleY(0);transform-box:fill-box;transform-origin:center bottom;transition:transform .7s cubic-bezier(.3,1,.4,1)}
.win-head.filled .fill-rect{transform:scaleY(1)}
@keyframes whPop{0%{transform:scale(.85);opacity:0}60%{transform:scale(1.05)}100%{transform:scale(1);opacity:1}}
/* calm + reduced-motion: no pop, no crossfade, no fill — the text simply stays (legible, static) */
@media (prefers-reduced-motion:reduce){ .win-head .wh-text{animation:none} .win-head .wh-icon,.fill-icon .fill-rect{transition:none} }
body.calm .win-head .wh-text{animation:none}
body.calm .win-head .wh-icon,body.calm .fill-icon .fill-rect{transition:none}
.place-strip.refhook{cursor:pointer;color:#3F6A1F;background:#EDF6E0;border:1px solid #cfe6b3;border-radius:11px;padding:7px 10px;margin:2px 0 12px}
.place-strip.refhook:active{transform:scale(.98)}
@media (prefers-reduced-motion:reduce){.place-strip.refhook:active{transform:none}}
/* ===== §3 win-info bottom sheet (tap-for-meaning) + F4 progression bits (§9.4: info-dots removed) ===== */
/* every carrier is a keyboard-focusable button; keep the calm look, add a clear focus ring */
[data-info]{cursor:pointer}
[data-info]:focus-visible{outline:2px solid var(--amber,#f2b134);outline-offset:2px;border-radius:12px}
/* F4/§8.3: the +N XP delta pill on the rxp row — shape/tint come from .win-chip.sm.wc-green; .rxp-gain only
   carries the fade-in (visible from mount at calm). The next-unlock badge moved OFF the row (the sheet names it). */
.rxp-gain{opacity:0;transition:opacity .34s ease}
.rxp-gain.in{opacity:1}
@media (prefers-reduced-motion:reduce){.rxp-gain{transition:none;opacity:1}}
body.calm .rxp-gain{transition:none;opacity:1}
/* the transparent scrim (closes + swallows the tap) + the viewport-anchored bottom sheet (never reflows the card) */
.win-info-scrim{position:fixed;inset:0;z-index:70;background:transparent}
.win-info{position:fixed;left:50%;bottom:max(12px,env(safe-area-inset-bottom));transform:translateX(-50%);z-index:71;
  width:min(330px,calc(100% - 16px));background:var(--card,#fff);border:1px solid var(--line);border-radius:20px;
  padding:16px 16px 14px;box-shadow:0 16px 44px rgba(0,0,0,.28);text-align:center;animation:wiIn .22s cubic-bezier(.2,.9,.3,1)}
@keyframes wiIn{from{transform:translateX(-50%) translateY(14px);opacity:0}to{transform:translateX(-50%) translateY(0);opacity:1}}
.win-info.rebump{animation:wiBump .28s cubic-bezier(.2,1.05,.4,1)}
@keyframes wiBump{0%{transform:translateX(-50%) scale(.96)}60%{transform:translateX(-50%) scale(1.04)}100%{transform:translateX(-50%) scale(1)}}
.win-info .wi-x{position:absolute;top:8px;right:10px;border:0;background:none;font-size:16px;line-height:1;color:var(--muted);cursor:pointer;padding:4px}
[dir=rtl] .win-info .wi-x{right:auto;left:10px}
.win-info .wi-ico{font-size:30px;line-height:1;color:var(--green-d,#3f8a47)}
.win-info .wi-title{font-weight:800;font-size:17px;margin:4px 0 2px;color:var(--ink)}
.win-info .wi-line{margin:2px 0;font-size:13.5px;font-weight:600;color:var(--muted);line-height:1.4}
.win-info .wi-meter{margin:8px auto 2px;font-weight:800;font-size:14px;color:var(--ink);font-variant-numeric:tabular-nums}
.win-info .wi-nu{margin:6px auto 0;font-size:12px;font-weight:700;color:var(--muted);display:flex;gap:5px;justify-content:center;flex-wrap:wrap;align-items:center}
.win-info .wi-nu .ti{font-size:13px}
.win-info .wi-act{margin:12px auto 0;display:inline-flex;align-items:center;justify-content:center;gap:6px;border:0;border-radius:12px;
  padding:9px 16px;font-weight:800;font-size:14px;color:#fff;background:var(--green,#5fae5f);cursor:pointer}
@media (prefers-reduced-motion:reduce){.win-info,.win-info.rebump{animation:none}}
body.calm .win-info,body.calm .win-info.rebump{animation:none}
.popup-actions{display:flex;flex-direction:column;gap:9px}
/* ===== Fixed Stage win card (owner Option B, 2026-07-14) — the FRAME owns its geometry. Six slots in a
   fixed order (header · hero · earned · progress · moment · actions), each with a reserved height; content
   changes INSIDE a slot (cross-fade / ghost placeholders), so the card border and every slot edge sit in
   the same place on every single level.
   Height is a MIN, not a fixed cap: 584px is the resting size (keeps the frame consistent on the common
   level), but the card GROWS to fit when the hero (tall "New friends!" discover banner) and a rich moment
   slot both run large on the same clear — a fixed height forced the flex-grow hero to shrink and CLIP the
   critter portraits + verdict (the "half-loaded card" bug). Capped at the overlay cell; overflow-y:auto is
   the last-resort safety on a genuinely tiny viewport so the Next CTA can never end up clipped off-screen. */
.popup.win-card.wc2{min-height:min(584px,100%);max-height:100%;display:flex;flex-direction:column;overflow-y:auto;overflow-x:hidden;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;padding:14px 16px 16px}
.wc-head{flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;gap:8px;min-height:28px}
.wc-lvl{display:inline-flex;align-items:center;gap:5px;min-width:0;background:#f4f2e6;color:var(--muted);border-radius:999px;padding:3px 10px;font-size:12px;font-weight:800;white-space:nowrap;overflow:hidden}
.wc-lvl .ti{font-size:13px;flex:0 0 auto}
.wc-lvl .wc-sep{opacity:.55}
.wc2 .win-wallet{position:static;flex:0 0 auto}   /* the wallet gets a real slot — never absolute over content, never mirrored by a stale dir */
/* flex:1 0 auto — the hero GROWS to fill the resting frame (celebration) but never SHRINKS below its own
   content. Shrinking is what collapsed the abs-positioned paw heading onto the verdict on content-rich
   clears; now the card grows (min-height above) instead of the hero clipping. */
.wc-hero{flex:1 0 auto;min-height:128px;display:flex;flex-direction:column;align-items:center;justify-content:center;overflow:hidden}
.wc-hero .win-critters{margin:0 0 2px}
.wc-hero .stars{margin:4px 0}
/* flex:0 0 auto reserves the heading's own box (its base height:52px, or 40px on short screens) so the
   absolutely-positioned paw can't be squeezed to ~8px and overflow down onto the verdict line. */
.wc-hero .win-head{margin:0;flex:0 0 auto}
.wc-hero .win-sub{flex:none;margin:2px 8px 0;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
.wc-hero .discover-banner{margin:0}
.wc-earn{flex:0 0 auto;min-height:72px}
.wc2 .wc-earn .win-stats{display:grid;grid-template-columns:repeat(4,1fr);gap:7px;margin:0 auto;max-width:302px;flex-wrap:initial}
.wc2 .stat-tile{animation:none;max-width:none}   /* the frame enters as ONE piece — no per-tile "row by row" reveal; the count-ups carry the beat */
.stat-tile.ghost{opacity:.35;box-shadow:none;border-style:dashed;pointer-events:none}
.stat-tile.ghost .stat-n{color:var(--muted)}
.wc-prog{flex:0 0 auto;min-height:66px;margin-top:8px}
/* first-steps levels (idx≤3) render an EMPTY progress slot (progHtml is gated off) — collapse it so its
   reserved band doesn't sit as a hollow rays-only void AND so the freed height flows to the flex-grow hero,
   which otherwise clips the taller "New friends!" discover banner (portraits + verdict cut off). */
.wc-prog:empty{display:none}
.wc-prog .win-prog{margin:0 auto;width:100%}
.wc-prog .reward-row{margin:4px 0 6px}
/* the MOMENT slot: every "sometimes" surface (level-up · rank ladder · Baby-Rescue offer · unlock · drops)
   takes turns here — one at a time, cross-fade, never stacking. Dots = manual nav (info never motion-gated). */
.wc-moment{flex:0 0 auto;position:relative;min-height:108px;margin-top:8px;border-radius:14px;background:rgba(122,160,106,.07);display:grid;align-items:stretch}
.wc-moment .mo{grid-area:1/1;min-width:0;opacity:0;visibility:hidden;transition:opacity .38s ease;display:flex;flex-direction:column;justify-content:center;gap:4px;padding:8px 10px}
.wc-moment .mo.on{opacity:1;visibility:visible}
.wc-moment .win-derby{margin:0;animation:none}
.wc-moment .win-derby .dby-rows{gap:2px}
.wc-moment .win-derby .dby-row{height:28px}
.wc-moment .lvlup-banner{margin-bottom:2px}
.wc-moment .tail-strip{margin:0;background:none;box-shadow:none;padding:4px 2px}
.wc-moment .win-chips{margin:0 auto}
.wc-moment .unlock-banner{margin:0 0 4px}
.wc-moment .mo-rw{margin:0;justify-content:center}
.mo-passed{font-size:11.5px;font-weight:700;color:#3d8b3d;opacity:0;transition:opacity .3s ease;display:flex;gap:4px;justify-content:center}
.mo-ph{font-size:11px;font-weight:700;color:#8a5a00;opacity:0;transition:opacity .3s ease;display:flex;gap:4px;justify-content:center;align-items:center}
.mo-ph .ti{font-size:12px}
.mo-passed.in,.mo-ph.in{opacity:1}
.mo-rank-big{display:flex;align-items:center;justify-content:center;gap:8px;font-weight:800;font-size:18px;color:var(--ink)}
.mo-rank-big .ti{font-size:20px;color:var(--amber)}
.mo-dots{position:absolute;top:5px;right:7px;display:flex;gap:2px;z-index:2}
.mo-dots button{width:16px;height:16px;border-radius:50%;padding:0;display:grid;place-items:center;cursor:pointer}
.mo-dots button::after{content:'';width:6px;height:6px;border-radius:50%;background:rgba(63,138,71,.3)}
.mo-dots button.on::after{background:var(--green-d,#3f8a47)}
.mo-dots button:focus-visible{outline:2px solid var(--amber);outline-offset:-2px}
[dir=rtl] .mo-dots{right:auto;left:7px}
.wc-cta{flex:0 0 auto;margin-top:10px}
.wc-cta .popup-actions{gap:8px}
.wc-cta .cta-row{display:flex;gap:8px}
.wc-cta .cta-row .btn{flex:1 1 0;margin:0}
/* short screens: shrink the hero + moment; the card scrolls only if even the shrunk layout can't fit */
@media (max-height:600px){
  .popup.win-card.wc2{padding:10px 14px 12px}
  .wc-hero{min-height:106px}
  .wc-hero .win-critters .wc{width:38px;height:38px}
  .popup.win-card.wc2 .stars .star{width:32px;height:32px}
  .wc-hero .win-head{height:40px}
  .wc-hero .win-head .wh-icon,.wc-hero .win-head .fill-icon{width:36px;height:36px}   /* fit the paw inside the 40px head so it can't touch the verdict */
  .wc-hero .win-head .wh-text{font-size:21px}
  .wc-hero .win-sub{font-size:12px}
  .wc-moment{min-height:92px}
  .wc-earn{min-height:64px}
  .wc2 .stat-tile{padding:6px 4px 5px}
}
/* calm/reduced-motion: moment cross-fades become instant swaps (manual dots still navigate) */
@media (prefers-reduced-motion:reduce){.wc-moment .mo{transition:none}.mo-passed,.mo-ph{transition:none}}
body.calm .wc-moment .mo{transition:none}
body.calm .mo-passed,body.calm .mo-ph{transition:none}

/* ---------- Toast ---------- */
#toast{position:absolute;left:50%;bottom:78px;transform:translateX(-50%);z-index:60;display:flex;flex-direction:column;gap:6px;align-items:center;pointer-events:none}
.toast-item{background:rgba(40,50,35,.92);color:#fff;font-weight:600;font-size:14px;padding:9px 16px;border-radius:999px;box-shadow:0 4px 12px rgba(0,0,0,.25);animation:tin .3s}
@keyframes tin{from{transform:translateY(10px);opacity:0}to{transform:translateY(0);opacity:1}}

/* ---------- sliding tab transitions ----------
   The new screen glides in by nav direction (forward = from the right, back = from the left).
   Calm: one-shot .28s, ~26px impulse (<12% of viewport), gentle fade; ui.js skips it under the
   calm feel / reduced-motion (mul<=0) and the media query hard-disables it as a backstop. */
#screen.slide-fwd{animation:scrSlideFwd .28s cubic-bezier(.22,1,.36,1)}
#screen.slide-back{animation:scrSlideBack .28s cubic-bezier(.22,1,.36,1)}
@keyframes scrSlideFwd{from{transform:translateX(26px);opacity:.35}to{transform:translateX(0);opacity:1}}
@keyframes scrSlideBack{from{transform:translateX(-26px);opacity:.35}to{transform:translateX(0);opacity:1}}
@media (prefers-reduced-motion:reduce){#screen.slide-fwd,#screen.slide-back{animation:none}}

/* ---------- iris scene transition (menu <-> in-level) ----------
   A black overlay (z 108, in the free 60–140 gap above toast/coach, below FTUE) whose
   transparent hole grows from the centre. JS (CE.juice.iris) drives --ir 150%→0 (cover) then
   0→150% (reveal) with rAF — no @keyframes, no scale, no flash, so it is inherently calm and
   never mounts under calm/reduced-motion (gated in juice.js). The masked area still blocks
   input, locking the board during the swap. */
/* No will-change here: `mask` is not a Blink direct-compositing reason, so declaring it
   promotes nothing and only adds a hint the compositor cannot act on. (Removing it is
   hygiene, not a VRAM saving — the mask animation's real cost is the per-frame re-raster.) */
.iris{position:absolute;inset:0;z-index:108;background:var(--iris-bg);
  -webkit-mask-image:radial-gradient(circle at 50% 50%, transparent var(--ir,0%), #000 calc(var(--ir,0%) + .8%));
  mask-image:radial-gradient(circle at 50% 50%, transparent var(--ir,0%), #000 calc(var(--ir,0%) + .8%));}

/* ---------- in-level chrome: the menu retracts so the board reads as its own world ----------
   Hiding the top HUD + bottom nav reclaims their flex space, letting #screen(flex:1) fill the
   shell — the level feels like a separate part of the game, not a tab. The swap happens behind
   the iris (or instantly under calm), so the layout change is never seen mid-flight. */
body.in-game #hud,
body.in-game #nav{display:none}
/* inset the play content so it sits INSIDE the screen-framing pipe (top run clears the header,
   bottom run clears the tools — the timer number lives on that bottom run) */
/* F5B: in-game screen is a flex column — header (auto) · pz-stage (flex:1, centers the board) · pz-dock (auto).
   Bottom pad clears the fixed TIME BONUS pod (~66px + safe-area). Horizontal pad is the shared cell-math token. */
body.in-game #screen{display:flex;flex-direction:column;padding:42px var(--scene-pad-x) calc(88px + env(safe-area-inset-bottom))}
.pz-stage{flex:1 1 auto;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:0;width:100%}
.pz-dock{flex:0 0 auto;display:flex;flex-direction:column;align-items:center;width:100%}

/* ---------- Play-button attract ----------
   The center Play FAB earns the eye instead of a floating pill: a soft green "ping" ring
   breathes out of its safe area and two small chevrons nod down toward it — a calm, always-on
   "tap here". Pure chrome (never touches state); only renders on the menu (hidden in-game) and
   stands down entirely in Calm / reduced-motion. The ring sits BEHIND the circle (z below). */
.fab-circle{position:relative;z-index:2}
/* attract ping — concentric with the 54px docked circle (top:0 = over the circle, which is the fab's first child) */
.fab-attract{position:absolute;left:50%;top:0;width:54px;height:54px;transform:translateX(-50%);
  pointer-events:none;z-index:1}
.fab-ping{position:absolute;inset:0;border-radius:50%;border:2px solid var(--green);
  opacity:0;animation:fabPing 2.2s ease-out infinite}
@keyframes fabPing{0%{transform:scale(.82);opacity:.45}70%{opacity:0}100%{transform:scale(1.5);opacity:0}}
body.calm .fab-attract,body.in-game .fab-attract{display:none}
@media (prefers-reduced-motion:reduce){.fab-attract{display:none}}

/* ---------- walls + juice ---------- */
.slot.wall{background:#c4b59a;box-shadow:inset 0 -4px 0 rgba(90,70,40,.18),inset 0 0 0 1px rgba(90,70,40,.12)}
.slot.wall svg{position:absolute;inset:16%;opacity:.9}
.critter.spawn{animation:spawnIn .42s cubic-bezier(.3,1.4,.5,1) backwards}
@keyframes spawnIn{from{transform:scale(0);opacity:0}to{transform:scale(1);opacity:1}}

/* "eager run" — each critter hops toward the way it's pointed (its exit dir).
   Runs on the inner <svg> so it never fights the critter's spawn/exit/pulse transforms. */
/* Critters stay STILL on their signpost by default — the continuous "run" hop read as too busy.
   The only motion now is the occasional ~2s idle beat below. (run-* classes are kept because they
   encode the facing direction; their hop animation is intentionally disabled.) */
.critter[class*="run-"] svg{animation:none}

/* ── idle "living scene": every 5–10s ONE critter shows a mood FACE for ~2s ──
   The critter stays perfectly still in its tile (no body travel / no go-and-return). The
   expression is carried entirely by the face swap (heart-eyes, wink, star-eyes, …) in
   theme.js + its species voice; ui.js restores the neutral face when the beat ends. */
.slot.blocker-flash{animation:blkFlash .55s}
.slot.tap-ring::after{content:'';position:absolute;inset:0;border-radius:inherit;pointer-events:none;animation:tapRing .5s ease-out}  /* a clean green pulse from the tapped cell as a critter runs home — layers under the spark burst for a juicier rescue (box-shadow only, calm ceiling) */
@keyframes tapRing{0%{box-shadow:0 0 0 0 rgba(120,190,106,.5)}100%{box-shadow:0 0 0 12px rgba(120,190,106,0)}}
body.calm .slot.tap-ring::after{animation:none}
@media (prefers-reduced-motion:reduce){.slot.tap-ring::after{animation:none}}
.critter.blocker-flash svg{animation:blkWobble .55s}
@keyframes blkFlash{0%{box-shadow:0 0 0 0 rgba(242,177,52,0)}45%{box-shadow:0 0 0 4px rgba(242,177,52,.5)}100%{box-shadow:0 0 0 0 rgba(242,177,52,0)}}
@keyframes blkWobble{0%,100%{transform:rotate(0)}40%{transform:rotate(-4deg)}70%{transform:rotate(3deg)}}
.spark{position:absolute;left:50%;top:45%;width:8px;height:8px;border-radius:50%;background:#ffd76b;pointer-events:none;z-index:6}
/* ── juice: reward motes + HUD count-up + species discovery ── */
.reward-mote{position:fixed;width:11px;height:11px;border-radius:50%;pointer-events:none;z-index:200;box-shadow:0 0 6px rgba(242,177,52,.55)}
.hud-pulse{animation:hudPulse .42s ease}
@keyframes hudPulse{0%,100%{transform:scale(1)}45%{transform:scale(1.18)}}
.discover-banner{display:flex;flex-direction:column;align-items:center;gap:6px;margin:2px 0 12px;animation:discBloom .5s cubic-bezier(.2,1.5,.4,1)}
.discover-banner .disc-portrait{width:64px;height:64px;border-radius:50%;background:radial-gradient(circle,#fff7e0,#ffe6a8);padding:6px;box-shadow:0 0 0 4px rgba(242,177,52,.25),0 4px 12px rgba(0,0,0,.12)}
.discover-banner .disc-portrait svg{width:100%;height:100%}
.discover-banner .disc-label{font-weight:700;color:var(--green-d,#3f8a47);font-size:15px;text-align:center;line-height:1.3}
@keyframes discBloom{from{transform:scale(.4);opacity:0}to{transform:scale(1);opacity:1}}
/* win-popup unlock beat — fires regardless of which CTA the player picks (calm bloom, reuses discBloom) */
.unlock-banner{display:flex;align-items:center;justify-content:center;gap:7px;margin:2px 0 12px;padding:7px 15px;border-radius:999px;background:linear-gradient(180deg,#eaf7e3,#d6eec8);color:var(--green-d,#3f8a47);font-weight:800;font-size:15px;animation:discBloom .5s cubic-bezier(.2,1.5,.4,1)}
.unlock-banner i{font-size:18px}
/* ── juice: full reduced-motion coverage (fade/shrink in place, no fling) ── */
@media (prefers-reduced-motion:reduce){
  .critter{transition:opacity .2s !important}
  .critter.exit-U,.critter.exit-D,.critter.exit-L,.critter.exit-R{transform:scale(.6) !important;opacity:0}
  .critter.shake,.critter.spawn,.board.win-zoom,.discover-banner,.unlock-banner,.hud-pulse,.win-critters .wc{animation:none !important}
  .float-coin{animation:fupRM .5s ease forwards}
}
@keyframes fupRM{from{opacity:1}to{opacity:0;transform:translateY(-12px)}}
@keyframes sparkFly{from{transform:translate(-50%,-50%) scale(1);opacity:1}to{opacity:0}}
/* P2: escalating combo pop — ⚡×N, anchored ABOVE the rail (never over the board). NUMBER is the true count
   (font-size set inline, 17→28px); INTENSITY is the purple→amber --heat wash (0 at ×2 → amber at ×8). Entrance
   peaks at scale 1.06 (calm max) then settles. Always pointer-events:none. direction:ltr keeps the badge stable in RTL. */
.combo-pop{position:absolute;left:50%;top:-4px;transform:translateX(-50%);pointer-events:none;direction:ltr;display:inline-flex;align-items:center;gap:3px;background:var(--c-combo);background:linear-gradient(rgba(242,177,52,var(--heat,0)),rgba(242,177,52,var(--heat,0))),var(--c-combo);color:#fff;font-weight:800;font-size:16px;line-height:1;padding:3px 12px;border-radius:999px;box-shadow:var(--shadow);z-index:9;white-space:nowrap;animation:comboPop .42s cubic-bezier(.2,1.5,.4,1)}
.combo-pop .ti{font-size:.82em;opacity:.95}
@keyframes comboPop{0%{transform:translateX(-50%) scale(.5);opacity:0}70%{transform:translateX(-50%) scale(1.06);opacity:1}100%{transform:translateX(-50%) scale(1);opacity:1}}
@media (prefers-reduced-motion:reduce){.combo-pop{animation:none}}
body.calm .combo-pop{animation:none}

/* ===================================================================================
   P9 game-scene juice pack (RELEASE_REVIEW_PLAN §9). Every beat obeys the CALM ceiling:
   scale ≤1.06 · translate impulse ≤12% · one rise+decay (no oscillation) · particles ≤8 ·
   no >3Hz flash · no pure-white/saturated-red full-cell flash. All are decoration only.
   =================================================================================== */

/* R1 (piece pop / contrast discipline): every board skin is fundamentally LIGHT (playmat + ≤.17-opacity
   type tints), so a colored critter body reads at only ~1.4–3.5:1 against it — the dark P.k OUTLINE (8–13:1)
   + this drop-shadow carry the pop. In-game we deepen the contact shadow + add a faint 1px dark rim so pale
   critters (lemon / golden cub) lift off any skin. lowfx (2-class specificity) still wins → filter:none. */
body.in-game .critter svg{filter:drop-shadow(0 2px 2px rgba(40,55,30,.34))}   /* ONE shadow pass (slightly deepened to keep pale critters lifted) — the second 1px-rim drop-shadow doubled the per-critter blur passes on every repaint for a near-invisible gain */

/* R3 blocked lean — handled by CE.juice._leanArt (WAAPI on the SVG); no keyframe needed here. */

/* R4 escalating praise ladder — one warm word on the combo rail at ×3/×5/×8. Legibility tier: the WORD
   shows even at calm (only the entrance motion is dropped). Sits above the ⚡×N combo pop. */
.praise-pop{position:absolute;left:50%;top:-22px;transform:translateX(-50%);pointer-events:none;direction:ltr;white-space:nowrap;z-index:10;
  font-weight:800;font-size:15px;color:var(--amber-d,#d6920f);text-shadow:0 1px 3px rgba(255,255,255,.75),0 1px 2px rgba(0,0,0,.12);animation:praisePop .5s cubic-bezier(.2,1.4,.4,1)}
@keyframes praisePop{0%{opacity:0;transform:translateX(-50%) translateY(6px) scale(.82)}60%{opacity:1;transform:translateX(-50%) translateY(0) scale(1.04)}100%{opacity:1;transform:translateX(-50%) translateY(0) scale(1)}}
@media (prefers-reduced-motion:reduce){.praise-pop{animation:none}}
body.calm .praise-pop{animation:none}

/* R5 last-heart tension — the remaining pip rises+decays once (scale ≤1.06). No red, no shake. */
.mistakes .tension-pulse{animation:heartTension .6s ease}
@keyframes heartTension{0%,100%{transform:scale(1)}45%{transform:scale(1.06)}}
@media (prefers-reduced-motion:reduce){.mistakes .tension-pulse{animation:none}}

/* R9 distinct tool signatures (~300ms each) — prefs()-gated in JS (calm → the class is never added). */
.critter.sig-spin::after{content:'';position:absolute;inset:-2px;border-radius:50%;pointer-events:none;z-index:4;
  border:2px solid transparent;border-top-color:var(--green,#5fae5f);border-right-color:var(--green,#5fae5f);animation:sigSpin .34s ease-out}
@keyframes sigSpin{0%{transform:rotate(0);opacity:.9}100%{transform:rotate(300deg);opacity:0}}
.critter.sig-pick svg{animation:sigPick .34s ease}
@keyframes sigPick{0%{transform:scale(1)}42%{transform:scale(1.06)}100%{transform:scale(1)}}
.critter.sig-key svg{animation:sigKeyTurn .34s ease}
@keyframes sigKeyTurn{0%{transform:rotate(0)}45%{transform:rotate(11deg)}100%{transform:rotate(0)}}
.critter.sig-key::after{content:'';position:absolute;inset:0;border-radius:12px;pointer-events:none;z-index:4;overflow:hidden;
  background:linear-gradient(115deg,transparent 38%,rgba(255,255,255,.7) 50%,transparent 62%);animation:sigGlint .34s ease-out}
@keyframes sigGlint{0%{transform:translateX(-110%);opacity:0}30%{opacity:1}100%{transform:translateX(110%);opacity:0}}
.critter.sig-scout::after{content:'';position:absolute;inset:0;border-radius:50%;pointer-events:none;z-index:4;animation:sigScout .36s ease-out}
@keyframes sigScout{0%{box-shadow:0 0 0 0 rgba(95,174,95,.6)}100%{box-shadow:0 0 0 12px rgba(95,174,95,0)}}
@media (prefers-reduced-motion:reduce){.critter.sig-spin::after,.critter.sig-pick svg,.critter.sig-key svg,.critter.sig-key::after,.critter.sig-scout::after{animation:none}}

/* I1 warm run-trail (combo ≥4) — a 200ms warm opacity wake behind the exiting critter, fading toward its
   travel axis. Opacity only (no particles); sits behind the art (z-index:-1 inside the critter's own context). */
.critter.warm-trail::after{content:'';position:absolute;inset:0;border-radius:12px;pointer-events:none;z-index:-1;
  background:radial-gradient(circle,rgba(242,177,52,.42),transparent 70%);animation:warmTrail .2s ease-out}
.critter.warm-trail.exit-R::after{background:linear-gradient(to left,rgba(242,177,52,.5),transparent 72%)}
.critter.warm-trail.exit-L::after{background:linear-gradient(to right,rgba(242,177,52,.5),transparent 72%)}
.critter.warm-trail.exit-U::after{background:linear-gradient(to bottom,rgba(242,177,52,.5),transparent 72%)}
.critter.warm-trail.exit-D::after{background:linear-gradient(to top,rgba(242,177,52,.5),transparent 72%)}
@keyframes warmTrail{0%{opacity:.5}100%{opacity:0}}
@media (prefers-reduced-motion:reduce){.critter.warm-trail::after{animation:none;opacity:0}}

/* I2 board settle-in — tiles fade-drop (≤8px) staggered from centre, once per stage (JS-gated; --sd per tile). */
.slot.tile-settle{animation:tileSettle .16s ease both;animation-delay:var(--sd,0ms)}
@keyframes tileSettle{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}
@media (prefers-reduced-motion:reduce){.slot.tile-settle{animation:none}}

/* I3 goodbye wave — the last critter's ART waves (rotate) inside EXIT_MS while the cell dashes home. */
.critter svg.goodbye-wave{transform-origin:50% 80%;animation:goodbyeWave .25s ease}
@keyframes goodbyeWave{0%{transform:rotate(0)}30%{transform:rotate(-9deg)}62%{transform:rotate(7deg)}100%{transform:rotate(0)}}
@media (prefers-reduced-motion:reduce){.critter svg.goodbye-wave{animation:none}}

/* I4 friends watching — two rescued-species friends idle-hop at the finale board edge (JS gates calm/lowfx). */
.board-watcher{position:absolute;bottom:2px;width:30px;height:30px;z-index:1;pointer-events:none;opacity:.92;animation:watcherHop 6s ease-in-out infinite}
.board-watcher svg{width:100%;height:100%;display:block}
.board-watcher.watch-left{inset-inline-start:-7px}
.board-watcher.watch-right{inset-inline-end:-7px}
@keyframes watcherHop{0%,84%,100%{transform:translateY(0)}90%{transform:translateY(-6px)}95%{transform:translateY(0)}}
@media (prefers-reduced-motion:reduce){.board-watcher{animation:none}}
body.calm .board-watcher,body.lowfx .board-watcher{display:none}

/* I5 pot anticipation — the TIME-BONUS gauge tips 3° in the last fifth of the window (static; composes with the centering translate). */
.pipe-time.par-near{transform:translateX(-50%) rotate(3deg);transition:transform .4s ease}

/* I8 lane-clear ripple — a soft light sweep across the now-clear lane when a cage opens (one rise+decay). */
.lane-ripple{position:absolute;inset:0;border-radius:inherit;pointer-events:none;z-index:3;opacity:0;
  background:radial-gradient(circle,rgba(255,240,190,.55),rgba(255,240,190,.16) 68%,transparent);animation:laneRipple .35s ease-out both}
@keyframes laneRipple{0%{opacity:0}42%{opacity:.6}100%{opacity:0}}
@media (prefers-reduced-motion:reduce){.lane-ripple{display:none}}

/* I9 flawless sheen — a static rainbow film on the courier jar after a zero-mistake stage (screen-blended, subtle). */
.tpot.sheen .tp-jar::after{content:'';position:absolute;left:6%;right:6%;top:14%;bottom:4%;pointer-events:none;z-index:3;
  border-radius:44% 44% 50% 50%/38% 38% 62% 62%;mix-blend-mode:screen;
  background:linear-gradient(125deg,rgba(255,120,170,.16),rgba(255,215,90,.16) 38%,rgba(120,225,160,.16) 66%,rgba(130,175,245,.18))}

/* combo "fast-next-action" charge bar — reverse countdown + tightening heartbeat.
   Obeys the JUICE.md calm ceiling: pulse scale <=1.06, soft brightness pulse (not a luminance strobe),
   green->amber warmth capped at Min(8), break is a silent fade. */
/* height + margin are CONSTANT so the rail never reflows the board; only opacity/slide animate on appear */
.combo-rail{position:relative;display:flex;align-items:center;gap:8px;width:100%;max-width:430px;height:18px;margin:0 auto 8px;opacity:0;transform:translateY(-3px);transition:opacity .25s ease,transform .25s ease;pointer-events:none}
.combo-rail.on{opacity:1;transform:translateY(0)}
.combo-bar{position:relative;flex:1;height:9px;border-radius:999px;background:rgba(70,90,55,.14);box-shadow:inset 0 0 0 1px rgba(70,90,55,.06);overflow:hidden;transition:box-shadow .3s}
.combo-rail.on .combo-bar{box-shadow:inset 0 0 0 1px rgba(70,90,55,.06),0 0 calc(2px + var(--warm,0)*9px) rgba(242,177,52,calc(.12 + var(--warm,0)*.42))}
/* Drained via transform:scaleX from comboTick(), never width — width is a LAYOUT property, so a
   width transition costs layout+paint every frame for the whole 0.8-2.0s window and re-arms on each
   rescue. transform-origin pins the drain to the left edge; .combo-bar's overflow:hidden + pill
   radius clip the shape, so nothing about the look changes. */
.combo-fill{position:absolute;inset:0 auto 0 0;width:100%;border-radius:999px;background:linear-gradient(90deg,var(--green),#7fc97f);transform-origin:left center;will-change:transform}
.combo-fill::after{content:'';position:absolute;inset:0;border-radius:999px;background:linear-gradient(90deg,var(--amber),#ffcf5a);opacity:var(--warm,0);transition:opacity .3s}
/* P2/D3: combo identity = purple --c-combo + ti-bolt (matches the win-card combo tile). Dark-plum text keeps ≥4.5:1 on the lilac. */
.combo-chip{flex-shrink:0;display:inline-flex;align-items:center;gap:3px;direction:ltr;background:var(--c-combo);color:#2b2352;font-weight:800;font-size:12px;line-height:1;padding:3px 9px;border-radius:999px;box-shadow:var(--shadow)}
.combo-chip .ti{font-size:13px}
/* P2: live combo-SCORE readout (the sess.score points climbing on the rail — the win-card combo number, live). */
.combo-score{flex-shrink:0;direction:ltr;font-weight:800;font-size:11px;line-height:1;color:var(--c-combo);font-variant-numeric:tabular-nums;letter-spacing:.02em;min-width:0}
.combo-score:empty{display:none}
.combo-rail.beating .combo-chip{animation:comboBeat var(--beat,.8s) ease-in-out infinite}
.combo-rail.beating .combo-fill{animation:comboGlow var(--beat,.8s) ease-in-out infinite}
@keyframes comboBeat{0%{transform:scale(1)}9%{transform:scale(1.06)}20%{transform:scale(1)}30%{transform:scale(1.035)}42%{transform:scale(1)}100%{transform:scale(1)}}
@keyframes comboGlow{0%,100%{filter:brightness(1)}12%{filter:brightness(1.13)}34%{filter:brightness(1.06)}}
@media (prefers-reduced-motion:reduce){.combo-rail.beating .combo-chip,.combo-rail.beating .combo-fill{animation:none}}
/* Magic-Dust timer — a no-fail liquid "pipe" hugging the board (left·bottom·right, open top).
   Drains as the level runs; finishing with time left earns a leaderboard bonus — empty is never a
   loss (JUICE.md loss-aversion ban). Calm / reduced-motion: stepwise 1Hz drain, no wave shimmer. */
/* SIMPLE board: a soft calm card (the busy photo playmat squeezed the tiles). Small padding → bigger tiles.
   The meadow photo stays available as an opt-in cosmetic set (--cos-playmat). */
/* F1B: the card hugs the board (fit-content) so a smaller board gets a smaller centered card, not a wide
   card with a small board floating in it. Horizontal padding uses the shared --board-pad-x (cell-math source). */
.board-wrap.has-pipe{position:relative;display:block;width:fit-content;max-width:100%;margin:6px auto 12px;padding:12px var(--board-pad-x,10px) 14px;
  background:var(--cos-playmat, linear-gradient(170deg,rgba(255,255,255,.5),rgba(238,245,228,.62)));border-radius:22px;
  box-shadow:inset 0 0 0 1px rgba(120,140,100,.10)}
.board-wrap.has-pipe .board{position:relative;z-index:2;margin:0 auto}
.pipe-frame{position:fixed;inset:0;pointer-events:none;z-index:30;overflow:visible}
.pipe-timer{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;overflow:visible}
.pipe-timer path{fill:none;stroke-linecap:round;stroke-linejoin:round}   /* viewBox is now in PX (buildPipe) → uniform, connected drain */
.pipe-casing{stroke:rgba(70,90,55,.14);stroke-width:17;stroke-linecap:round}
.pipe-liquid{stroke:#46b6c9;stroke-width:13;stroke-linecap:round;stroke-dasharray:1000 1000;stroke-dashoffset:0;filter:drop-shadow(0 0 3px rgba(70,182,201,.45));animation:pipeWave 2.6s ease-in-out 1}   /* JUICE.md §1: one rise+decay on mount, NOT an oscillating/repeating glow */
.pipe-timer.rs-empty .pipe-liquid{opacity:.22;animation:none}
/* the countdown lives in a FAT bulged section of the bottom pipe run (a water-filled gauge pod) */
.pipe-time{position:absolute;left:50%;bottom:calc(10px + env(safe-area-inset-bottom));transform:translateX(-50%);pointer-events:none;z-index:2;
  display:flex;flex-direction:column;align-items:center;gap:0;padding:4px 26px 6px;min-width:92px;
  background:linear-gradient(#ecfbff,#c9ecf4);border:5px solid #3fb2c6;border-radius:34px;
  box-shadow:0 3px 10px rgba(30,80,95,.22),inset 0 2px 5px rgba(255,255,255,.78),inset 0 -4px 7px rgba(63,178,198,.30)}
.pipe-time-lbl{font-weight:800;font-size:9px;line-height:1.4;letter-spacing:1.6px;color:#2b8298;text-transform:uppercase;opacity:.92}
.pipe-time-val{font-weight:900;font-size:38px;line-height:1;color:#0e4d60;       /* high-contrast fill + white inner stroke + blue outer stroke */
  -webkit-text-stroke:3px #fff;paint-order:stroke fill;
  text-shadow:-1.5px -1.5px 0 #1f8fb3,1.5px -1.5px 0 #1f8fb3,-1.5px 1.5px 0 #1f8fb3,1.5px 1.5px 0 #1f8fb3,0 3px 4px rgba(20,60,75,.32);
  font-variant-numeric:tabular-nums;letter-spacing:1px}
/* D1 professional timer: a quiet readout that just drops in place (no per-digit motion; the gentle
   countdown motion is the liquid drain). The game font's digits are PROPORTIONAL and it has no tabular
   figures, so we lock each digit into a fixed-width cell — every digit keeps a fixed column, no jitter. */
.pipe-time-val{display:inline-block;width:2.1em;text-align:right;letter-spacing:0;white-space:nowrap}
.pipe-time-val .ptd{display:inline-block;width:.7em;text-align:center}
.pipe-stamp{animation:pipeStamp .42s cubic-bezier(.2,1.2,.4,1)}   /* keyframes keep translateX(-50%) so the gauge stays centred */
@keyframes pipeStamp{0%{transform:translateX(-50%) scale(1.06);opacity:.55}60%{opacity:1}100%{transform:translateX(-50%) scale(1)}}
@media (prefers-reduced-motion:reduce){.pipe-stamp{animation:none}}
.pipe-time.rs-empty{border-color:#9cc2cb;background:linear-gradient(#f1f6f7,#dde9ec)}
.pipe-time.rs-empty .pipe-time-val{color:#5f8b97;text-shadow:0 1px 2px rgba(0,0,0,.15)}
/* napping critter: a slow opacity "breathing" (opacity leaves transform free for the pose tilt) */
/* the daily-rank status BUTTON on the time row (tap → opens the daily time board) */
.rank-btn{display:inline-flex;align-items:center;gap:6px;margin-top:5px;padding:5px 8px 5px 10px;border:none;border-radius:11px;cursor:pointer;background:linear-gradient(#fff6df,#ffedc2);box-shadow:0 1px 4px rgba(150,110,20,.18),inset 0 0 0 1.5px #f0d79a;font:800 12.5px/1 "Fredoka",sans-serif;color:#8a6a1e}
.rank-btn:active{transform:scale(.97)}
.rank-btn .rank-lbl{display:inline-flex;align-items:center;gap:4px}
.rank-btn .rank-lbl i{color:#e6b422}
.rank-btn .rank-n{font-size:15px;font-weight:900;color:#5a4a1e;font-variant-numeric:tabular-nums}
.rank-btn .rank-delta{display:inline-flex;align-items:center;gap:2px;font-size:11px;font-weight:800;color:#8a9088}
.rank-btn.up .rank-delta{color:#2f9e52}.rank-btn.down .rank-delta{color:#c67a3a}
.rank-btn .rank-delta i{font-size:12px}
.rank-btn .rank-go{margin-left:2px;color:#c2a86a;font-size:14px}
/* TIME-JAR COURIER — a glass jar hops in on a stage clear (squash landing), its copper lid pops open,
   the stage's remaining time sloshes in (gulps + badge pops + sparkle burst), the lid slams with a
   wiggle and it zips off carrying the bank; it returns docked on the win card. Pure decoration —
   the time is banked before it runs. Calm/reduced-motion-gated off. */
.tpot{position:fixed;left:50%;bottom:24%;width:136px;height:150px;margin-left:-68px;z-index:59;transform:translateY(calc(24vh + 110%));transform-origin:50% 100%}   /* parked FULLY below the viewport on any screen (24vh gap + own height) — must match tpHopIn's 0% */
/* D2: stage-loss stakes row — the banked time-jar + the critters it holds, shown as what a Continue keeps */
.loss-stakes{display:flex;align-items:center;justify-content:center;gap:14px;margin:6px auto 12px;max-width:320px}
.loss-stakes .ls-side{text-align:left;min-width:0}
.loss-stakes .ls-cap{font-size:12.5px;font-weight:700;color:var(--muted);margin-bottom:6px}
.loss-stakes .ls-critters{display:flex;flex-wrap:wrap;gap:4px}
.loss-stakes .ls-cr{width:30px;height:30px;border-radius:50%;overflow:hidden;background:var(--card-2,#f3f1e8);border:2px solid var(--line);display:grid;place-items:center}
.loss-stakes .ls-cr svg{width:26px;height:26px;display:block}
[dir=rtl] .loss-stakes .ls-side{text-align:right}
.tpot-dim{position:fixed;inset:0;z-index:58;background:rgba(46,74,52,.28);-webkit-backdrop-filter:blur(1.5px);backdrop-filter:blur(1.5px);opacity:0;transition:opacity .3s}
.tpot-dim.on{opacity:1}
.tpot .tp-glow{position:absolute;left:50%;top:55%;width:190px;height:190px;margin:-95px 0 0 -95px;border-radius:50%;background:radial-gradient(circle,rgba(140,230,245,.65),rgba(140,230,245,0) 62%);opacity:0;transition:opacity .45s;pointer-events:none}
.tpot.lit .tp-glow{opacity:1}
.tpot .tp-jar{position:relative;width:100%;height:100%;transform-origin:50% 96%;filter:drop-shadow(0 10px 12px rgba(20,70,85,.3))}
.tpot .tp-svg{position:absolute;inset:0;width:100%;height:100%;overflow:visible}
.tpot .tp-sand{transition:transform .42s cubic-bezier(.3,1.2,.4,1)}   /* tracks the per-drop pour steps tightly */
.tpot .tp-surf{transform-box:fill-box;transform-origin:center}
.tpot .tp-surf.slosh{animation:tpSlosh .55s ease-out}
.tpot .tp-surf.splash{animation:tpSplash .34s cubic-bezier(.3,1.3,.5,1)}   /* crown bulge as a drop lands */
.tpot .tp-rip{transform-box:fill-box;transform-origin:center;opacity:0}
.tpot .tp-rip.rip{animation:tpRip .5s ease-out}                            /* ring spreads across the surface on impact */
/* a falling time-droplet (WAAPI-arced from the gauge into the mouth) */
.tp-drop{position:fixed;width:13px;height:16px;margin:0;z-index:60;pointer-events:none;border-radius:50%;
  background:radial-gradient(120% 95% at 36% 26%,#e6fbff,#5fcadd 52%,#2f9db4);box-shadow:0 0 7px rgba(95,202,221,.7),inset 0 -2px 3px rgba(20,90,110,.35)}
.tpot .tp-bub{animation:tpBub 2.6s ease-in-out infinite}
.tpot .tp-bub.b2{animation-delay:.6s}.tpot .tp-bub.b3{animation-delay:1.3s}
.tpot .tp-lidg{transform-origin:60px 27px}
.tpot.open .tp-lidg{animation:tpLidO .4s cubic-bezier(.3,1.4,.5,1) forwards,tpLidFloat 1.5s ease-in-out .42s infinite alternate}
.tpot.shut .tp-lidg{animation:tpLidC .3s cubic-bezier(.4,0,.6,1.4) forwards}
.tpot .tp-num-wrap{position:absolute;left:50%;bottom:26px;transform:translateX(-50%);display:inline-flex;gap:5px;align-items:center;background:rgba(10,74,90,.85);color:#fff;border-radius:999px;padding:4px 13px;font:900 21px/1 "Fredoka",sans-serif;text-shadow:0 2px 3px rgba(5,40,50,.4);white-space:nowrap;z-index:2}
.tpot .tp-num-wrap i{font-size:16px;color:#9fe6f2}
.tpot .tp-num-wrap.pop{animation:tpNumPop .3s cubic-bezier(.3,1.5,.5,1)}
.tpot .tp-num-wrap.punch{animation:tpNumPunch .42s cubic-bezier(.3,1.7,.5,1)}   /* PHASE 4 · the full-jar number PUNCH (bigger than the per-drop pop) */
.tpot .tp-spark{position:absolute;left:50%;top:24px;width:7px;height:7px;margin-left:-3px;border-radius:50%;background:#c8f2fa;box-shadow:0 0 8px #7ddeee;opacity:0;pointer-events:none}
.tpot.burst .tp-spark.s1{animation:tpSpark1 .65s ease-out .02s}
.tpot.burst .tp-spark.s2{animation:tpSpark2 .7s ease-out .12s}
.tpot.burst .tp-spark.s3{animation:tpSpark3 .6s ease-out .22s}
/* PHASE 1 · ARRIVE — twin dust puffs kick out sideways as the jar slams down */
.tpot .tp-dust{position:absolute;left:50%;bottom:8%;width:30px;height:11px;border-radius:50%;background:radial-gradient(circle,rgba(206,228,236,.6),rgba(206,228,236,0) 70%);opacity:0;pointer-events:none}
.tpot.landed .tp-dust.d1{animation:tpDustL .5s ease-out}
.tpot.landed .tp-dust.d2{animation:tpDustR .5s ease-out}
/* PHASE 2/5 · UNCORK pop-ring at the mouth (reused, pushed DOWNWARD, for the SEAL slam) */
.tpot .tp-cork{position:absolute;left:50%;top:22px;width:30px;height:12px;margin-left:-15px;border-radius:50%;border:2.5px solid rgba(206,246,255,.9);opacity:0;pointer-events:none}
.tpot.uncork .tp-cork{animation:tpCork .5s ease-out}
.tpot.seal .tp-cork{animation:tpSeal .4s ease-out}
/* PHASE 4 · SETTLE shockwave — a ring blasts outward from the full jar */
.tpot .tp-shock{position:absolute;left:50%;top:52%;width:88px;height:88px;margin:-44px 0 0 -44px;border-radius:50%;border:3px solid rgba(150,235,250,.7);opacity:0;pointer-events:none}
.tpot.full .tp-shock,.tpot.dock.burst .tp-shock{animation:tpShock .6s cubic-bezier(.2,.7,.3,1)}
.tpot.full .tp-jar{animation:tpFull .5s cubic-bezier(.3,1.4,.5,1)}   /* satisfied bounce (scale ≤1.05, calm-ceiling-safe) */
/* PHASE 6 · DEPART speed-lines — motion streaks trail the jar as it rockets off */
.tpot .tp-speed{position:absolute;left:-34px;top:38%;width:44px;height:34px;opacity:0;pointer-events:none;background:repeating-linear-gradient(0deg,transparent 0 5px,rgba(185,236,250,.55) 5px 7px);-webkit-mask-image:linear-gradient(90deg,transparent,#000);mask-image:linear-gradient(90deg,transparent,#000)}
.tpot.off .tp-speed{animation:tpSpeed .42s ease-out}
.tpot.in{animation:tpHopIn .52s cubic-bezier(.34,1.2,.5,1) forwards}
.tpot.off{animation:tpZipOff .46s cubic-bezier(.5,.05,.85,.4) forwards}
.tpot .tp-jar.gulp{animation:tpGulp .3s ease-out}
.tpot .tp-jar.wig{animation:tpWig .45s ease-in-out}
@keyframes tpHopIn{0%{transform:translateY(calc(24vh + 110%))}58%{transform:translateY(-5%)}74%{transform:translateY(2.5%) scale(1.07,.88)}88%{transform:translateY(-1.5%) scale(.98,1.03)}100%{transform:translateY(0) scale(1,1)}}
@keyframes tpZipOff{0%{transform:translateY(0)}28%{transform:translate(-10px,5px) scale(1.04,.9) rotate(-4deg)}100%{transform:translate(74vw,-26px) scale(1.12,.92) rotate(9deg)}}
@keyframes tpGulp{0%{transform:scale(1,1)}45%{transform:scale(1.05,.93)}100%{transform:scale(1,1)}}
@keyframes tpWig{0%{transform:rotate(0)}30%{transform:rotate(2.4deg)}62%{transform:rotate(-1.8deg)}100%{transform:rotate(0)}}
@keyframes tpLidO{0%{transform:translateY(0) rotate(0)}60%{transform:translateY(-31px) rotate(-11deg)}80%{transform:translateY(-21px) rotate(-7deg)}100%{transform:translateY(-25px) rotate(-9deg)}}
@keyframes tpLidFloat{0%{transform:translateY(-25px) rotate(-9deg)}100%{transform:translateY(-29px) rotate(-7deg)}}
@keyframes tpLidC{0%{transform:translateY(-25px) rotate(-9deg)}55%{transform:translateY(2.5px) rotate(0)}78%{transform:translateY(-2px)}100%{transform:translateY(0) rotate(0)}}
@keyframes tpNumPop{0%{transform:translateX(-50%) scale(1)}45%{transform:translateX(-50%) scale(1.22)}100%{transform:translateX(-50%) scale(1)}}
@keyframes tpNumPunch{0%{transform:translateX(-50%) scale(1)}40%{transform:translateX(-50%) scale(1.35)}70%{transform:translateX(-50%) scale(.96)}100%{transform:translateX(-50%) scale(1)}}
@keyframes tpDustL{0%{opacity:.55;transform:translateX(-50%) scale(.3)}100%{opacity:0;transform:translate(-150%,3px) scale(1.35)}}
@keyframes tpDustR{0%{opacity:.55;transform:translateX(-50%) scale(.3)}100%{opacity:0;transform:translate(50%,3px) scale(1.35)}}
@keyframes tpCork{0%{opacity:.9;transform:translateY(0) scale(.5)}100%{opacity:0;transform:translateY(-15px) scale(1.6)}}
@keyframes tpSeal{0%{opacity:.85;transform:translateY(-8px) scale(1.35)}100%{opacity:0;transform:translateY(3px) scale(.65)}}
@keyframes tpShock{0%{opacity:.75;transform:scale(.38)}100%{opacity:0;transform:scale(1.5)}}
@keyframes tpFull{0%{transform:scale(1,1)}30%{transform:scale(1.05,.95)}62%{transform:scale(.98,1.03)}100%{transform:scale(1,1)}}
@keyframes tpSpeed{0%{opacity:0;transform:translateX(24px) scaleX(.5)}35%{opacity:.6}100%{opacity:0;transform:translateX(-26px) scaleX(1.4)}}
@keyframes tpSlosh{0%{transform:translateY(0) scaleX(1)}30%{transform:translateY(-4px) scaleX(1.05)}60%{transform:translateY(2px) scaleX(.97)}100%{transform:translateY(0) scaleX(1)}}
@keyframes tpSplash{0%{transform:scaleY(1)}35%{transform:translateY(-2px) scaleY(1.85)}100%{transform:scaleY(1)}}
@keyframes tpRip{0%{transform:scale(.32);opacity:.85}100%{transform:scale(1.16);opacity:0}}
@keyframes tpBub{0%,100%{transform:translateY(0);opacity:.4}50%{transform:translateY(-7px);opacity:.7}}
@keyframes tpBob{0%,100%{transform:translateY(0)}50%{transform:translateY(-2.5px)}}
@keyframes tpSpark1{0%{opacity:0;transform:translate(0,0) scale(1)}18%{opacity:1}100%{opacity:0;transform:translate(-17px,-30px) scale(.4)}}
@keyframes tpSpark2{0%{opacity:0;transform:translate(0,0) scale(1)}18%{opacity:1}100%{opacity:0;transform:translate(2px,-38px) scale(.45)}}
@keyframes tpSpark3{0%{opacity:0;transform:translate(0,0) scale(1)}18%{opacity:1}100%{opacity:0;transform:translate(18px,-28px) scale(.4)}}
/* docked on the win card (inline, centered, drops in with a bounce) */
.tpot.dock{position:relative;left:auto;bottom:auto;width:104px;height:114px;margin:0 auto 4px;transform:none;opacity:0;z-index:auto}
.tpot.dock.in{animation:tpDrop .6s cubic-bezier(.34,1.25,.5,1) forwards}
.tpot.dock .tp-spark{top:20px}
.tpot.dock.settled .tp-jar{animation:tpBob 3.4s ease-in-out infinite}   /* alive-on-the-card idle bob (±2.5px) */
@keyframes tpDrop{0%{opacity:0;transform:translateY(-48px) scale(.9)}55%{opacity:1;transform:translateY(4px)}74%{transform:translateY(0) scale(1.06,.92)}100%{opacity:1;transform:translateY(0) scale(1,1)}}
@media (prefers-reduced-motion:reduce){
  .tpot,.tpot *,.tpot-dim,.tp-drop{animation:none!important;transition:none!important}
  .tpot.in{transform:translateY(0)}.tpot.dock{opacity:1}
  .tpot.open .tp-lidg{transform:translateY(-25px) rotate(-9deg)}
  .tpot.dock.settled .tp-jar{animation:none!important}
}
body.calm .tpot *,body.lowfx .tpot *{animation-duration:.01s!important;transition-duration:.01s!important}
body.calm .tpot .tp-bub,body.lowfx .tpot .tp-bub{animation:none!important}
body.calm .tpot.open .tp-lidg,body.lowfx .tpot.open .tp-lidg{animation:none!important;transform:translateY(-25px) rotate(-9deg)}   /* no .01s infinite float — a >3Hz flicker */
body.calm .tpot.dock.settled .tp-jar,body.lowfx .tpot.dock.settled .tp-jar{animation:none!important}   /* no .01s infinite bob */
/* stage intro — "Stage k/3 · 3·2·1·GO" overlay (calm: backdrop alpha .34<.6, pop scale 1.06) */
.stage-intro{position:fixed;inset:0;z-index:60;display:grid;place-items:center;background:rgba(58,90,55,.34);-webkit-backdrop-filter:blur(1.5px);backdrop-filter:blur(1.5px);animation:siIn .2s ease}
@keyframes siIn{from{opacity:0}to{opacity:1}}
.si-card{text-align:center;color:#fff;padding:0 24px}
.si-lvl{font-size:15px;font-weight:800;letter-spacing:1.4px;text-transform:uppercase;opacity:.9}
.si-stage{font-size:27px;font-weight:900;margin:2px 0 16px;text-shadow:0 2px 6px rgba(0,0,0,.3)}
.si-bar{width:210px;height:13px;border-radius:99px;background:rgba(255,255,255,.25);margin:0 auto 16px;overflow:hidden}
.si-fill{display:block;height:100%;width:0;background:linear-gradient(90deg,#46b6c9,#7fd0dc);border-radius:99px;animation:siFill 2.45s ease forwards}
@keyframes siFill{to{width:100%}}
.si-count{font-size:66px;font-weight:900;color:#fff;text-shadow:0 3px 12px rgba(0,0,0,.35);min-height:74px;line-height:74px}
.si-count.pop{animation:siPop .42s ease}
@keyframes siPop{0%{transform:scale(.62);opacity:.35}55%{transform:scale(1.06)}100%{transform:scale(1);opacity:1}}
@media (prefers-reduced-motion:reduce){.stage-intro{animation:none}.si-fill{animation:none;width:100%}.si-count.pop{animation:none}}
/* one type-identity banner — reused on the pack screen, the loading beat, the result card. Tinted per
   type via --ltb/--ltbl/--ltbk (the type PAL). The loading banner flies to the .ltype-chip on Go. */
.ltype-banner{display:flex;align-items:center;gap:10px;width:max-content;max-width:100%;margin:0 auto;padding:8px 16px 8px 10px;border-radius:999px;background:var(--ltbl,#e7f3d6);color:var(--ltbk,#2c4a20);box-shadow:0 2px 8px rgba(50,70,40,.14)}
.ltype-banner .ltb-ic{flex:0 0 auto;width:30px;height:30px;border-radius:50%;display:grid;place-items:center;background:var(--ltb,#8FBF52);color:#fff;box-shadow:0 1px 4px rgba(0,0,0,.16)}
.ltype-banner .ltb-ic i{font-size:18px}
.ltype-banner .ltb-tx{display:flex;flex-direction:column;text-align:left;line-height:1.2;min-width:0}
.ltype-banner .ltb-tx b{font-size:15px;font-weight:800}
.ltype-banner .ltb-cap{font-size:12px;opacity:.72;font-weight:600}
.ltb-pack{margin:0 auto 12px}.ltb-result{margin:2px auto 8px}
.si-card .ltype-banner{margin:2px auto 14px;background:rgba(255,255,255,.95);animation:ltbIn .34s cubic-bezier(.2,1.3,.4,1)}
@keyframes ltbIn{from{transform:scale(.82);opacity:0}to{transform:scale(1);opacity:1}}
.ltype-chip.chip-land{animation:chipLand .5s cubic-bezier(.2,1.5,.4,1)}
@keyframes chipLand{0%{transform:scale(.62)}55%{transform:scale(1.06)}100%{transform:scale(1)}}
@media (prefers-reduced-motion:reduce){.si-card .ltype-banner{animation:none}.ltype-chip.chip-land{animation:none}}
/* napping critter: a slow opacity "breathing" (opacity leaves transform free for the pose tilt) */
.critter.dozing{filter:saturate(.82) brightness(1.02);animation:critterDoze 3.4s ease-in-out infinite}
@keyframes critterDoze{0%,100%{opacity:1}50%{opacity:.9}}
.critter.dozing.doze-curl{transform:rotate(-3.5deg)}    /* nap-pose variety */
.critter.dozing.doze-lean{transform:rotate(3deg)}
.critter.drowsy{animation:critterDrowsy .44s ease}       /* fall-asleep settle */
.critter.waking{animation:critterWake .36s cubic-bezier(.2,1.4,.4,1)}  /* perk-up on wake (scale<=1.05, calm) */
@keyframes critterDrowsy{0%{transform:scale(1)}60%{transform:scale(.97) translateY(2px)}100%{transform:scale(1)}}
@keyframes critterWake{0%{transform:scale(.96)}55%{transform:scale(1.05)}100%{transform:scale(1)}}
body.tab-hidden .critter.dozing,body.tab-hidden .pipe-liquid{animation-play-state:paused}   /* battery: freeze ambient motion when backgrounded */
@media (prefers-reduced-motion:reduce){.critter.dozing,.critter.drowsy,.critter.waking{animation:none}}
body.calm .critter.dozing,body.lowfx .critter.dozing{animation:none}
@keyframes pipeWave{0%,100%{stroke:#46b6c9}50%{stroke:#58c2d3}}
/* procedural ambient leaves behind the board — calm theme juice (≤6, slow drift, low opacity) */
.lvl-foliage{position:absolute;inset:-10px;z-index:0;pointer-events:none;overflow:hidden;border-radius:22px}
.lvl-foliage .leaf{position:absolute;width:26px;height:26px;opacity:.22;color:#7fae5f;transform-origin:center;
  animation:leafDrift 11s ease-in-out infinite}
.lvl-foliage .leaf svg{width:100%;height:100%;display:block;fill:currentColor}
@keyframes leafDrift{0%,100%{translate:0 0}50%{translate:0 7px}}
body.lowfx .lvl-foliage,body.calm .lvl-foliage{display:none}
@media (prefers-reduced-motion:reduce){.lvl-foliage{display:none}}
@media (prefers-reduced-motion:reduce){.pipe-liquid{animation:none;filter:none}}

/* combo auto-booster — "Rainbow Rush" tool + chain banner */
/* classic boosters read as a distinct (warmer) system vs the green obstacle-tools */
.tool.boost i{color:#e0902f}
.tool.off{opacity:.42;filter:grayscale(.45)}
/* pre-level "Pack your bag" boost-pick cards */
.pack-wrap{text-align:center}
.pack-cards{display:flex;flex-direction:column;gap:9px;margin:2px 0 4px;text-align:left}
.pack-card{display:flex;align-items:center;gap:11px;width:100%;background:#fff;border:1px solid var(--line);border-radius:16px;padding:10px 12px;box-shadow:var(--shadow);cursor:pointer}
.pack-card.equipped{box-shadow:0 0 0 2px var(--amber),var(--shadow);background:linear-gradient(120deg,#fff,#fef6e3)}
.pack-card:active{transform:scale(.99)}
.pc-ic{width:42px;height:42px;flex-shrink:0;border-radius:13px;background:#fdf3e0;display:grid;place-items:center}
.pc-ic i{font-size:23px;color:#e0902f}
.pc-tx{display:flex;flex-direction:column;line-height:1.25;min-width:0;flex:1}.pc-tx b{font-size:14px}.pc-tx .muted{font-size:11.5px}
.pc-state{flex-shrink:0;font-size:12px;font-weight:800;color:var(--muted);display:inline-flex;align-items:center;gap:4px;white-space:nowrap}
.pc-state.on{color:var(--green-d)}
.pc-state.buy{color:#2c5a8c;background:#eaf2fb;border:1px solid #c9def5;border-radius:999px;padding:4px 10px}
.pc-own{opacity:.7;font-weight:700}
.tool.booster{position:relative;overflow:hidden}
.tool.booster i{color:#9a8fe0}
.tool.booster::after{content:'';position:absolute;left:0;bottom:0;height:3px;width:calc(var(--chg,0)*100%);background:linear-gradient(90deg,#9a8fe0,#7fc97f);transition:width .25s}
.tool.booster.charged{box-shadow:0 0 0 2px #9a8fe0,var(--shadow);animation:boostReady 1.1s ease-in-out infinite}
.tool.booster.charged i,.tool.booster.charged .cnt{color:#7c5fae}
@keyframes boostReady{0%,100%{transform:scale(1)}50%{transform:scale(1.05)}}
.rainbow-banner{position:absolute;left:50%;top:8px;transform:translateX(-50%);z-index:8;font-weight:800;font-size:16px;padding:5px 16px;border-radius:999px;color:#fff;background:linear-gradient(90deg,#ED93B1,#F4C842,#8FBF52,#85B7EB,#AFA9EC);box-shadow:var(--shadow);animation:comboPop .5s cubic-bezier(.2,1.5,.4,1)}
@media (prefers-reduced-motion:reduce){.tool.booster.charged{animation:none}}
body.calm .tool.booster.charged{animation:none}
.board.win-zoom{animation:winZoom .5s ease}
@keyframes winZoom{0%,100%{transform:scale(1)}40%{transform:scale(1.04)}}
.diff-row{display:flex;gap:8px;justify-content:center;margin:6px 0 2px}
.diff-chip{padding:7px 14px;border-radius:999px;border:1px solid var(--line);background:#fff;font-weight:600;font-size:14px}
.diff-chip.on{background:var(--green);color:#fff;border-color:var(--green)}

/* utility */
.center{text-align:center}
.mt8{margin-top:8px}.mt12{margin-top:12px}.mt16{margin-top:16px}
.muted{color:var(--muted);font-weight:500}
.big{font-size:18px;font-weight:700}

/* ---------- Today hub (audience-segmented retention) ---------- */
.today-head{margin-bottom:4px}
.seg-chip{display:inline-flex;align-items:center;gap:6px;background:var(--green-l);color:var(--green-d);font-weight:700;font-size:13px;padding:4px 11px;border-radius:999px}
.seg-chip i{font-size:16px}
.seg-prev{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 12px}
.segx{font-size:12px;font-weight:600;padding:5px 11px;border-radius:999px;border:1px solid var(--line);background:#fff;color:var(--muted)}
.segx.on{background:var(--ink);color:#fff;border-color:var(--ink)}
.tcard{margin-bottom:12px}
.tcard.spot{border:1.5px solid var(--green);box-shadow:0 8px 20px rgba(95,174,95,.20)}
.tc-ico{width:42px;height:42px;border-radius:13px;display:grid;place-items:center;font-size:22px;color:#fff;flex-shrink:0}
.tc-ico.green{background:var(--green)}.tc-ico.amber{background:var(--amber)}.tc-ico.flame{background:#ef7a3a}
.tc-ico.emj{background:var(--green-l)}   /* Events-hub module cards: emoji icon on a soft neutral pad */
.tc-ico.sky{background:#5aa9e8}.tc-ico.gold{background:#e6b234}.tc-ico.lilac{background:#9a8fe0}
.tc-ev{width:44px;height:44px;border-radius:13px;background:var(--green-l);display:grid;place-items:center;flex-shrink:0;padding:4px}
.tc-ev .av{width:100%;height:100%}
.qrow{display:flex;align-items:center;gap:9px;padding:7px 2px;font-weight:600;font-size:14px}
.qrow .ti{font-size:20px}
.av{display:inline-block;width:28px;height:28px;vertical-align:middle}
.av svg{width:100%;height:100%;display:block}

/* progress bars */
.prog{height:8px;border-radius:999px;background:rgba(70,90,55,.12);overflow:hidden;margin-top:7px}
.prog-fill{height:100%;border-radius:999px;background:linear-gradient(90deg,var(--green),#7fc97f);transition:width .4s}

/* login calendar */
.logrow{display:flex;gap:5px}
.logcell{flex:1;min-width:0;border-radius:11px;border:1px solid var(--line);background:#fff;padding:7px 3px;text-align:center;display:flex;flex-direction:column;align-items:center;gap:2px;position:relative}
.logcell .d{font-size:11px;font-weight:700;color:var(--muted)}
.logcell .lr{font-size:9px;line-height:1.05;font-weight:600;color:var(--ink)}
.logcell .ti{position:absolute;top:3px;right:4px;font-size:12px;color:var(--green)}
.logcell.got{background:var(--green-l);border-color:transparent;opacity:.85}
.logcell.now{border-color:var(--amber);box-shadow:0 0 0 2px rgba(242,177,52,.35);background:#fffaf0}
.logcell.soon{opacity:.6}

/* missions */
.mrow{display:flex;align-items:center;gap:10px;padding:9px 0;border-bottom:1px solid var(--line)}
.mrow:last-child{border-bottom:none}
.mtxt{flex:1;min-width:0;font-weight:600;font-size:14px}
.mrw{display:flex;gap:6px;align-items:center}
.rw{display:inline-flex;align-items:center;gap:3px;font-size:13px;font-weight:700;color:var(--ink)}
.rw i{font-size:15px}

/* league (rows now render through the canonical .lbrow renderLBRow — the old .lstand/.lrow markup is retired) */
/* B7: daily board keeps time as a muted sub inside the value cell */
.lbrow .lb-n .lb-time{opacity:.6;font-weight:600;font-size:11px;margin-left:4px}
/* event-fill invite CTA + "enroll us both" toggle on the cohort card */
.ce-fill-cta{margin-top:8px}
.ce-arr{display:flex;align-items:center;gap:7px;margin-top:8px;font-size:12.5px;color:var(--muted);cursor:pointer}
.ce-arr input{width:15px;height:15px;accent-color:#5FA84F;flex:none}
/* winners' rewards strip — mirrors the canonical CE.Cohort.REWARD per tier */
.winstrip{display:flex;flex-wrap:wrap;gap:6px;justify-content:center;margin:8px 0 2px}
.wschip{display:inline-flex;align-items:center;gap:3px;font-size:11px;font-weight:700;color:#6a6450;background:var(--card-2,#f3f1e8);border-radius:999px;padding:3px 9px}
.wschip .wsm{font-size:12px}

/* ===== renderLBRow — ONE canonical leaderboard row (cohort · league · referral-ready) ===== */
.lbrow{display:grid;grid-template-columns:34px 40px 1fr auto 30px 40px;align-items:center;gap:8px;height:50px;padding:0 6px;border-radius:11px;position:relative}
.lbrow + .lbrow{margin-top:2px}
.lbrow-wt{grid-template-columns:34px 40px 1fr auto 30px minmax(64px,auto)}
.lbrow .lb-rk{width:32px;height:32px;border-radius:50%;display:grid;place-items:center;font-weight:800;font-size:14px;color:#9a9a9a;background:var(--card-2,#f3f1e8);font-variant-numeric:tabular-nums}
.lbrow.pod .lb-rk{color:#5a3d05;font-size:15px;box-shadow:inset 0 1px 1px rgba(255,255,255,.55),0 1px 3px rgba(0,0,0,.12)}
.lbrow.r1 .lb-rk{background:radial-gradient(circle at 35% 30%,#ffe08a,#f2b134)}
.lbrow.r2 .lb-rk{background:radial-gradient(circle at 35% 30%,#eef2f6,#c2ccd6)}
.lbrow.r3 .lb-rk{background:radial-gradient(circle at 35% 30%,#e7c39c,#cf9b6a)}
.lbrow .lb-av{width:38px;height:38px;border-radius:50%;display:grid;place-items:center;background:var(--card-2,#f3f1e8);border:2px solid var(--ring,var(--line));overflow:hidden}
.lbrow .lb-av .av,.lbrow .lb-av svg{width:34px;height:34px;display:block}
.lbrow .lb-nm{min-width:0;font-weight:700;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:flex;align-items:center;gap:4px}
.lbrow .lb-fr{color:#5FA84F;font-size:13px;flex:none}
.lbrow .lb-val{display:flex;flex-direction:column;align-items:flex-end;gap:3px;min-width:52px}
.lbrow .lb-n{font-weight:800;font-size:13px;font-variant-numeric:tabular-nums;line-height:1}
.lbrow .lb-bar{width:60px;height:6px;border-radius:4px;background:var(--card-2,#eee);overflow:hidden}
.lbrow .lb-bar i{display:block;height:100%;border-radius:4px;background:linear-gradient(90deg,var(--green,#5fae5f),var(--green-d,#3f8a47))}
.lbrow .lb-dl{font-weight:800;font-size:11.5px;display:flex;align-items:center;justify-content:center}
.lbrow .lb-dl.up{color:#57b26a}.lbrow .lb-dl.down{color:#9aa7b0}
.lbrow .lb-rw{display:grid;place-items:center}
.lbrow .lb-tail{display:flex;justify-content:flex-end;align-items:center}
.lbrow .lb-up{color:#57b26a;font-size:18px}.lbrow .lb-dn{color:#c1a24a;font-size:18px}
.lbrow.you{background:rgba(242,177,52,.14);box-shadow:inset 0 0 0 2px var(--amber,#f2b134)}
.lbrow.you .lb-nm{color:var(--amber-d,#d6920f)}
.lbrow.promo{background:rgba(95,174,95,.10)}
.lbrow.releg{background:rgba(232,96,122,.09)}
.lbrow.lb-friend{background:rgba(95,174,95,.08)}
.lbrow .lb-chest{vertical-align:-3px}
.lbrow .lb-delta{position:absolute;right:44px;top:50%;transform:translateY(-50%);display:inline-flex;align-items:center;gap:1px;font-size:11px;font-weight:800;padding:1px 6px;border-radius:999px;line-height:1.5;pointer-events:none}
.lbrow .lb-delta.up{color:#3d8b3d;background:rgba(95,174,95,.18)}
.lbrow .lb-delta.down{color:#b06a86;background:rgba(176,106,134,.14)}
.lbrow.moved{animation:lbMoved 1.1s ease-out 1}
@keyframes lbMoved{0%{background:rgba(242,177,52,.22)}100%{background:transparent}}
.lb-gap{text-align:center;color:#bcbcbc;font-weight:800;letter-spacing:3px;font-size:13px;padding:1px 0}
.lb-pin{display:flex;align-items:center;gap:7px;margin:5px 2px;padding:5px 9px;border-radius:9px;border:1px dashed var(--amber,#f2b134);background:repeating-linear-gradient(45deg,rgba(242,177,52,.12),rgba(242,177,52,.12) 8px,transparent 8px,transparent 16px);font-weight:800;font-size:11.5px;color:var(--amber-d,#d6920f)}
.lb-pin .lb-pin-f{display:grid;place-items:center;flex:none}
@media (prefers-reduced-motion:reduce){.lbrow.moved{animation:none}.lbrow .lb-delta{position:static;transform:none}}

/* ===== chest-open ceremony (settled-cohort claim) ===== */
.chest-stage{display:grid;place-items:center;height:130px;margin:2px 0 4px}
.chest-big{position:relative;width:120px;height:112px;cursor:pointer;filter:drop-shadow(0 8px 12px rgba(40,55,30,.22))}
.chest-big .chest-big-svg{position:relative;width:120px;height:112px}
.chest-big .cb-lid{position:absolute;left:0;top:6px;transform-origin:50% 100%;transition:transform .28s cubic-bezier(.3,.7,.3,1.2);z-index:2}
.chest-big .cb-body{position:absolute;left:0;bottom:0;z-index:1}
.chest-big.chest-pop{animation:chestPop .3s ease}
@keyframes chestPop{30%{transform:scale(.97)}62%{transform:scale(1.05)}100%{transform:scale(1)}}
/* PRE-OPEN anticipation (ceremony v2): two gentle wobbles, ~190ms each. Transform-only (rotate ≤±3°,
   scale ≤1.04 — CALM ceiling ≤1.06), single run (not infinite), zero luminance flash. Unscoped so the
   non-chest lanes (mailbox gift icon / tail chip) wobble too and degrade safely. */
.chest-wobble{animation:chestWobble .38s ease-in-out 1}
@keyframes chestWobble{0%{transform:rotate(0) scale(1)}22%{transform:rotate(3deg) scale(1.04)}50%{transform:rotate(0) scale(1)}72%{transform:rotate(-3deg) scale(1.04)}100%{transform:rotate(0) scale(1)}}
/* TIER LIGHT: opacity-only radial glow behind the chest (JS animates 0→.7→0 over ~600ms — one
   rise+decay, no flash). z-index:-1 sits it behind the art in the .chest-big filter stacking context. */
.chest-bloom{position:absolute;left:50%;top:50%;width:150%;height:150%;transform:translate(-50%,-50%);border-radius:50%;pointer-events:none;z-index:-1;opacity:0;background:radial-gradient(circle,var(--bc,#ffd76b) 0%,rgba(255,255,255,0) 66%)}
/* raster basket/nest sprite (theme.chestArt seam) — swaps closed→open at the reveal beat; sits above the bloom, replaces the SVG when the art file loads */
.chest-big .chest-raster{position:absolute;inset:0;width:100%;height:100%;object-fit:contain}
.chest-big.open .cb-lid{transform:rotate(-104deg)}
.chest-pay{display:flex;justify-content:center;gap:9px;margin:6px 0 2px;min-height:56px;flex-wrap:wrap}
/* SEQUENTIAL DOLE-OUT: chips render one at a time (JS timers, 140ms apart) — .in pops the just-doled
   chip (no --i stagger delay); .shown reveals instantly (skip / calm path). Base state is hidden. */
.chest-pay-chip{background:var(--card,#fffdf7);border:1px solid var(--line);border-radius:12px;padding:8px 11px;display:flex;flex-direction:column;align-items:center;gap:3px;min-width:56px;box-shadow:var(--shadow);opacity:0;transform:scale(.85)}
.chest-pay-chip.in{animation:cpIn .34s cubic-bezier(.2,.85,.25,1) forwards}
.chest-pay-chip.shown{opacity:1;transform:none}
.chest-pay-chip .cpv{font-size:22px}.chest-pay-chip .cpn{font-weight:800;font-size:13px}
@keyframes cpIn{100%{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){.chest-big.chest-pop,.chest-wobble{animation:none}.chest-big .cb-lid{transition:none}.chest-pay-chip{animation:none;opacity:1;transform:none}}
/* RESULTS card — a settled cohort's chest, breathing gently until you open it */
.tc-ico.tc-chest{background:transparent;display:grid;place-items:center;animation:tcChestBreathe 2.6s ease-in-out infinite}
.tc-ico.tc-chest .lb-chest{width:34px;height:34px;filter:drop-shadow(0 2px 5px rgba(40,55,30,.25))}
@keyframes tcChestBreathe{50%{transform:scale(1.04)}}
@media (prefers-reduced-motion:reduce){.tc-ico.tc-chest{animation:none}}

/* ===== P4: Rescuer-rank status badge (HUD) + status sheet ===== */
.hud-pill.status{padding-left:5px;padding-right:5px}
.hud-pill.status .status-ring{position:relative;width:24px;height:24px;border-radius:50%;display:grid;place-items:center;border:2px solid var(--tier,#6bbf59);color:var(--tier,#6bbf59);transition:border-color .5s ease,color .5s ease}
.hud-pill.status .status-ring i{font-size:14px;line-height:1}
.hud-pill.status .status-medals{position:absolute;bottom:-4px;right:-6px;min-width:14px;height:14px;padding:0 3px;border-radius:8px;background:var(--amber,#f2b134);color:#fff;font-weight:800;font-size:9px;line-height:1;display:grid;place-items:center;border:2px solid var(--card,#fffdf7);font-variant-numeric:tabular-nums}
.hud-pill.status.promote{animation:stPromote .7s cubic-bezier(.3,.85,.25,1)}
@keyframes stPromote{35%{transform:scale(1.06)}70%{transform:scale(.99)}100%{transform:scale(1)}}
.hud-pill.status .status-ring.cross{animation:stCross .6s ease}
@keyframes stCross{0%{filter:brightness(1.45)}100%{filter:brightness(1)}}
.hud-pill.status .status-medals.bump{animation:stBump .5s ease}
@keyframes stBump{40%{transform:scale(1.12)}100%{transform:scale(1)}}
.status-hero{text-align:center}
.status-hero-ring{width:64px;height:64px;border-radius:50%;display:inline-grid;place-items:center;border:3px solid var(--tier,#6bbf59);color:var(--tier,#6bbf59);margin:4px 0 0}
.status-hero-ring i{font-size:32px}
.status-sub{text-align:center;font-size:12.5px;margin:8px 0 12px}
.status-medals-row{display:flex;justify-content:center;gap:8px;flex-wrap:wrap}
.status-medals-row .sm-chip{background:var(--card-2,#f3f1e8);border:1px solid var(--line);border-radius:999px;padding:5px 12px;font-weight:800;font-size:13px;font-variant-numeric:tabular-nums}
@media (prefers-reduced-motion:reduce){.hud-pill.status.promote,.hud-pill.status .status-ring.cross,.hud-pill.status #statusMedals.bump{animation:none}}
/* narrow phones (≤400px): tighten HUD gaps/padding so the 7th (status) pill fits without clipping settings */
@media (max-width:400px){ #hud{gap:3px;padding-left:4px;padding-right:4px} #hud .hud-left,#hud .hud-right{gap:3px} #hud .hud-pill{padding-left:4px;padding-right:4px} }
/* live "Settles in …" countdown */
.bcd-line{font-size:12px;color:var(--muted);margin:6px 0 2px}
.bcd-line i{font-size:13px;vertical-align:-2px}
.bcd-line b{color:var(--green-d);font-variant-numeric:tabular-nums}
/* Magic Dust board — daily/weekly/monthly cadence tabs */
.md-tabs{display:flex;gap:6px;justify-content:center;margin:6px 0 4px}
.md-tab{border:1px solid rgba(120,120,120,.22);background:transparent;border-radius:999px;padding:4px 14px;font-weight:700;font-size:13px;color:#7a7a7a;cursor:pointer}
.md-tab.on{background:linear-gradient(90deg,#f6d36b,#ffe9a8);border-color:transparent;color:#6a4f12}
.md-sub{font-size:13px;margin:4px 0}
.md-climb{color:#3d8b3d;font-weight:700;font-size:13px;margin:4px 0}
/* ===== Event lifecycle card (.evcard) + shared popup hero (.ev-hero) — Gardenscapes-register list card.
   Calm: static background-image crop (no motion/particles/flash), 180deg vertical scrim = RTL-safe.
   Legibility (adversarial-panel R3): main scrim + a body backing plate + shadow-all-text keep white text
   >=4.5:1 at the TOP body line over the brightest banner crop (not only at the 100% stop). ===== */
.evcard{position:relative;overflow:hidden;min-height:118px;padding:0;border-radius:18px;
  display:flex;flex-direction:column;justify-content:flex-end;isolation:isolate}
/* E1 (tap card = open detail): pointer ONLY when the body tap actually opens something — a whole-card
   data-coh (cohort routing) or data-open (todayAction routing). A Join-state card reads static. */
.evcard[data-coh],.evcard[data-open]{cursor:pointer}
.ev-art{position:absolute;inset:0;z-index:0;
  /* layered: banner over the deep-night fallback gradient — a 404/gitignored asset degrades to the gradient */
  background-image:var(--ev-img,none),linear-gradient(180deg,#0a0e28 0%,#1b2350 60%,#2b326a 100%);
  background-size:cover,cover;background-position:var(--ev-pos,50% 60%),center;background-repeat:no-repeat}
.ev-scrim{position:absolute;inset:0;z-index:1;background:linear-gradient(180deg,
  rgba(8,10,24,0) 0%,rgba(8,10,24,.12) 34%,rgba(8,10,24,.60) 66%,rgba(8,10,24,.90) 100%)}
/* animated comet overlay on the 'before' starter — a transparent Codex FX layer that slowly drifts along
   the comet's travel axis + fades (the "simple animation, image quality" ask). CALM: transform/opacity only,
   ≤7% impulse, ~9s period (no flash); static under calm/reduced-motion. */
.ev-fx{position:absolute;inset:0;z-index:1;pointer-events:none;background-image:var(--ev-fx);
  background-size:cover;background-repeat:no-repeat;opacity:0;will-change:transform,opacity;
  animation:evComet 8.5s ease-in-out 1.2s infinite}
.evcard .ev-fx{background-position:var(--ev-pos,50% 60%)}
.ev-hero .ev-fx{background-position:var(--ev-hero-pos,50% 32%);animation-duration:9.5s}
@keyframes evComet{0%{opacity:0;transform:translate(3.5%,-2%)}15%{opacity:1}70%{opacity:1}100%{opacity:0;transform:translate(-3.5%,2%)}}
body.calm .ev-fx,body.lowfx .ev-fx{animation:none;opacity:.92;transform:none}
@media (prefers-reduced-motion:reduce){.ev-fx{animation:none;opacity:.92;transform:none}}
/* fx-sky (§6) — the alpha sky plate drifting VERY slowly across the popup hero, under the comet fx +
   scrim. CALM: transform/opacity only, ≤2.5% travel over 14s (no flash); static under calm/reduced. */
.ev-fx-sky{position:absolute;inset:-3% -4%;z-index:1;pointer-events:none;background-image:var(--ev-fx-sky);
  background-size:cover;background-repeat:no-repeat;background-position:var(--ev-hero-pos,50% 32%);
  opacity:.9;will-change:transform;animation:evSkyDrift 14s ease-in-out infinite alternate}
@keyframes evSkyDrift{0%{transform:translateX(-1.2%)}100%{transform:translateX(1.2%)}}
body.calm .ev-fx-sky,body.lowfx .ev-fx-sky{animation:none;transform:none}
@media (prefers-reduced-motion:reduce){.ev-fx-sky{animation:none;transform:none}}
.ev-body{position:relative;z-index:2;padding:16px 13px 12px;text-align:left;
  background:linear-gradient(180deg,rgba(8,10,24,0),rgba(8,10,24,.48))}
.ev-title{color:#fff;font-weight:800;font-size:16px;line-height:1.15;text-shadow:0 1px 2px rgba(0,0,0,.72),0 1px 10px rgba(0,0,0,.4)}
.ev-chips{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:7px}
.ev-chips .tag{font-size:11px;padding:3px 9px}
.ev-chips .ev-medal{font-size:14px;filter:drop-shadow(0 1px 2px rgba(0,0,0,.5))}
.ev-meta{color:#fff;font-size:12px;margin-top:7px;text-shadow:0 1px 2px rgba(0,0,0,.6)}
.ev-meta .hub-cd{font-weight:700;font-variant-numeric:tabular-nums}
.ev-claim{position:absolute;top:12px;right:12px;z-index:3;box-shadow:0 2px 8px rgba(0,0,0,.35)}
[dir="rtl"] .ev-claim{right:auto;left:12px}
.ev-chip-btn{border:0;cursor:pointer}
/* popup detail hero header — same plate, sky/comet crop; full-bleed inside .popup's 20px/22px padding */
.ev-hero{position:relative;overflow:hidden;margin:-22px -20px 12px;min-height:150px;border-radius:0 0 20px 20px;
  display:flex;flex-direction:column;justify-content:flex-end;isolation:isolate}
.ev-hero.ev-hero-sm{min-height:92px}
.ev-hero .ev-art{background-position:var(--ev-hero-pos,50% 32%),center}
.ev-hero .ev-body{padding:18px 18px 14px;background:linear-gradient(180deg,rgba(8,10,24,0),rgba(8,10,24,.60))}
.ev-hero .ev-title{font-size:20px}
/* compact stat row for the popup DURING state */
.ev-stats{display:flex;gap:8px;justify-content:center;flex-wrap:wrap;margin:8px 0 2px}
.ev-stat{background:var(--green-l);color:var(--green-d);border-radius:999px;padding:4px 11px;font-size:12px;font-weight:700}
.ev-howto{text-align:left;font-size:13px;color:var(--muted,#6a6a6a);margin:8px 0;padding-left:2px}
/* controls hosted INSIDE an evcard (cohort/daily/event cards rolled onto the pilot pattern) —
   ghost buttons, the enroll-both checkbox and the progress track must stay legible over art */
.evcard .btn-ghost{color:#fff;border-color:rgba(255,255,255,.45);background:rgba(8,10,24,.30)}
.evcard .ce-arr{color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.6)}
.evcard .rw{color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.6)}   /* E5a reward chips stay legible over art */
/* E3: league podium rows hosted on the grove evcard — white ink over art (the .you name keeps its
   own amber rule, which is more specific and already reads on the dark plate) */
.evcard .lbrow{color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.6)}
.evcard .prog{background:rgba(255,255,255,.30)}
.evcard .ev-chips .tc-ev{box-shadow:0 1px 6px rgba(0,0,0,.35)}
.ev-howto li{margin:3px 0}
/* keep the hero full-bleed in lockstep with .popup's reduced padding on short viewports (styles.css:562) */
@media (max-height:560px){.ev-hero{margin:-14px -16px 12px}}
/* Town Gallery header — the same hero worn as a SCREEN banner (gallery skin): no popup negative
   margins, all-corner radius, back button overlaid top-start (inset-inline = RTL-safe) */
.gal-hero{position:relative}
.gal-hero .ev-hero{margin:0 0 10px;border-radius:20px}
.gal-hero .gal-back{position:absolute;top:10px;inset-inline-start:10px;z-index:3}
/* Critter Dash — race-track header + Dash Pot milestone track (calm: .5s eases, no flash/haptic) */
.race-track{position:relative;height:34px;margin:8px 0 2px;border-radius:999px;background:rgba(120,150,90,.12);box-shadow:inset 0 0 0 1px rgba(120,150,90,.14);overflow:visible}
.race-track .rt-fill{position:absolute;inset:0 auto 0 0;border-radius:999px;background:linear-gradient(90deg,#bfe39a,#9fcf72);transition:width .5s ease}
.race-track .rt-runner{position:absolute;top:50%;transform:translate(-50%,-50%);transition:left .5s cubic-bezier(.2,.8,.2,1)}
.race-track .rt-runner .av,.race-track .rt-runner svg{width:28px;height:28px;display:block}
.race-track .rt-flag{position:absolute;right:5px;top:50%;transform:translateY(-50%);font-size:16px}
/* B2: rival faces on the strip (Magic Dust daily) — score-proportional spots; a tiny startled hop
   (≤12% of the 22px face — calm impulse ceiling) as your runner passes. Runner rides above rivals. */
.race-track .rt-runner{z-index:2}
.race-track .rt-rival{position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1;opacity:.92}
.race-track .rt-rival .av,.race-track .rt-rival svg{width:22px;height:22px;display:block}
.race-track .rt-rival.hop{animation:rtHop .42s cubic-bezier(.3,1.05,.4,1)}
@keyframes rtHop{0%{transform:translate(-50%,-50%)}40%{transform:translate(-50%,calc(-50% - 2.5px)) rotate(-4deg)}100%{transform:translate(-50%,-50%)}}
@media (prefers-reduced-motion:reduce){.race-track .rt-rival.hop{animation:none}}
body.calm .race-track .rt-rival.hop{animation:none}
.dash-pot{margin:8px 0;text-align:left}
.dp-bar{position:relative;height:10px;border-radius:999px;background:rgba(230,163,46,.12);box-shadow:inset 0 0 0 1px rgba(230,163,46,.12);margin-top:5px}
.dp-fill{position:absolute;inset:0 auto 0 0;border-radius:999px;background:linear-gradient(90deg,#f6d36b,#ffe9a8);transition:width .5s ease}
.dp-node{position:absolute;top:50%;width:8px;height:8px;border-radius:50%;background:#fff;box-shadow:0 0 0 2px rgba(180,150,60,.5);transform:translate(-50%,-50%)}
.dp-node.ready{box-shadow:0 0 0 2px #e6a32e,0 0 6px rgba(230,163,46,.6)}
.dp-node.done{background:#7cc06a;box-shadow:0 0 0 2px #5fae5f}
@media (prefers-reduced-motion:reduce){.race-track .rt-fill,.race-track .rt-runner,.dp-fill{transition:none}}

/* ---- Event race track (race.js CE.Race) — the shared competitive-event hero: sky header · up to 5
   lanes (name/avatar chip + runway + score-anchored mover) · checkered finish strip with reward-band
   chest pins. Structure + calm-gated motion only; palette/prop/sky/pin art all come from CE.theme.race.
   Pinned LTR like .board/.map-wrap; ALL motion is transform/opacity-only, gated by .race-calm + RM. ---- */
.race-shell{position:relative;border-radius:14px;overflow:hidden;margin:2px 0 10px;border:1px solid var(--line);background:var(--card-2,#f3f1e8)}
.race-sky{position:relative;display:flex;align-items:center;justify-content:space-between;gap:8px;padding:7px 12px;min-height:34px;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.30)}
.race-goal{font-weight:800;font-size:12.5px;line-height:1.25;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.race-cd{flex:none;font-size:12px;font-weight:700;display:inline-flex;align-items:center;gap:4px;background:rgba(0,0,0,.20);border-radius:999px;padding:2px 9px}
.race-cd i{font-size:12px}
.race-cd b{font-variant-numeric:tabular-nums}
.race-grid{display:grid;grid-template-columns:1fr 30px;background:var(--card,#fffdf7)}
.race-lanes{grid-column:1;min-width:0;padding:3px 0 3px 6px}
.race-lane{position:relative;display:grid;grid-template-columns:96px 1fr;align-items:center;gap:8px;height:44px;border-bottom:1px dashed var(--line)}
.race-lane:last-child{border-bottom:0}
.race-lane-you{background:rgba(242,177,52,.14)}
.race-chip{display:flex;align-items:center;gap:6px;min-width:0}
.race-av{width:30px;height:30px;flex:none;border-radius:50%;display:grid;place-items:center;background:var(--card-2,#f3f1e8);border:2px solid var(--line);overflow:hidden}
.race-av svg{width:26px;height:26px;display:block}
.race-lane-you .race-av{border-color:var(--amber,#f2b134)}
.race-who{min-width:0;display:flex;flex-direction:column;line-height:1.12}
.race-nm{font-weight:700;font-size:12.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:flex;align-items:center;gap:3px}
.race-lane-you .race-nm{color:var(--amber-d,#d6920f)}
.race-fr{color:#5FA84F;font-size:12px;flex:none}
.race-sc{font-size:10.5px;color:var(--muted);font-variant-numeric:tabular-nums}
.race-runway{position:relative;height:100%;min-width:0}
.race-mover{position:absolute;top:50%;left:5%;transform:translate(-50%,-50%);z-index:2;will-change:transform}
.race-bob{display:flex;flex-direction:column;align-items:center}
.race-prop{display:block;width:15px;margin-bottom:-4px;filter:drop-shadow(0 0 5px rgba(250,199,117,.65))}
.race-prop svg{display:block}
.race-crit{width:30px;height:30px;display:block}
.race-crit svg{width:30px;height:30px;display:block}
.race-flag-mk{position:absolute;top:50%;transform:translateY(-50%);z-index:1;color:var(--muted);font-size:13px;opacity:.5}
.race-finish{grid-column:2;position:relative;overflow:hidden}
.race-checks{position:absolute;inset:0;background:repeating-conic-gradient(#e9e6db 0 25%,#cfcabb 0 50%) 0 0/10px 10px;opacity:.6}
.race-pin{position:absolute;right:2px;transform:translateY(50%);z-index:2;filter:drop-shadow(0 1px 2px rgba(40,50,30,.35))}
.race-pin svg{display:block}
/* gentle idle bob (≤2px, 2.4s ease alternate) — a calm ambient, not a flash; killed under calm/RM */
@keyframes raceBob{from{transform:translateY(0)}to{transform:translateY(-2px)}}
.race-shell:not(.race-calm) .race-bob{animation:raceBob 2.4s ease-in-out infinite alternate}
/* glide-in on mount — transform/opacity only, ~1.4s both fill (centering preserved in the keyframes) */
@keyframes raceGlide{from{transform:translate(-50%,-50%) translateX(-16px);opacity:0}to{transform:translate(-50%,-50%);opacity:1}}
.race-shell:not(.race-calm) .race-mover{animation:raceGlide 1.4s cubic-bezier(.2,.8,.25,1) both}
/* finishBeat crossing nudge — the runner glides the last stretch to the line (transform/left only) */
.race-mover.race-crossed{transition:left .5s cubic-bezier(.2,.8,.2,1);left:96%}
[dir="rtl"] .race-shell{direction:ltr}
.race-calm .race-bob,.race-calm .race-mover{animation:none}
@media (prefers-reduced-motion:reduce){.race-bob,.race-mover{animation:none}.race-mover.race-crossed{transition:none}}

/* ---- CE.Race.mountIntro — the first-open-per-window story beat (race.js P2). A fixed full-scene
   overlay over the board/popup: dusk back-plate (pure-CSS gradient today; .ri-plate is the P11 image
   seam) + the skin's floating prop + story caption + reward preview + a Join/Continue affordance.
   Opacity/transform only, within the calm ceiling; calm / reduced-motion never mounts it (race.js
   returns instant). Pinned LTR like the track. ---- */
.race-intro{position:fixed;inset:0;z-index:110;display:flex;flex-direction:column;align-items:center;justify-content:center;overflow:hidden;padding:26px;opacity:0;transition:opacity .3s ease}
.race-intro.ri-go{opacity:1}
.race-intro.ri-out{opacity:0}
[dir="rtl"] .race-intro{direction:ltr}
.ri-sky{position:absolute;inset:0;z-index:0}
.ri-plate{position:absolute;inset:0;background-size:cover;background-position:center}   /* P11: pre-baked scene image; absent → the .ri-sky gradient shows */
.ri-scrim{position:absolute;inset:0;z-index:1;background:radial-gradient(120% 82% at 50% 32%,transparent 42%,rgba(18,20,38,.42) 100%)}
/* over a real photo plate the text needs more backing: keep the top sky clear, darken from the caption zone down */
.race-intro.has-plate .ri-scrim{background:linear-gradient(180deg,transparent 0%,transparent 24%,rgba(8,10,24,.34) 48%,rgba(8,10,24,.52) 100%)}
.ri-stage{position:relative;z-index:2;display:flex;flex-direction:column;align-items:center;gap:11px;max-width:340px;text-align:center;color:#fff;text-shadow:0 1px 3px rgba(0,0,0,.42)}
.race-intro.ri-go .ri-stage{animation:riRise .42s cubic-bezier(.2,.9,.3,1) both}
@keyframes riRise{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}
.ri-prop{width:64px;filter:drop-shadow(0 0 15px rgba(250,199,117,.7))}
.ri-prop svg{width:64px;height:auto;display:block;margin:0 auto}
.race-intro:not(.ri-out) .ri-prop{animation:riProp 2.6s ease-in-out infinite alternate}
@keyframes riProp{from{transform:translateY(0)}to{transform:translateY(calc(-2px * var(--ri-mul,1)))}}   /* ≤2px, calm ceiling */
.ri-goal{font-weight:800;font-size:16px;line-height:1.3}
.ri-cap{font-size:14px;font-weight:600;line-height:1.42;opacity:.96}
.ri-reward{display:inline-flex;align-items:center;gap:6px;font-size:12.5px;font-weight:700;background:rgba(0,0,0,.30);border-radius:999px;padding:5px 13px}
.ri-reward i{color:var(--amber,#f2b134);font-size:14px}
.ri-cta{margin-top:5px;display:inline-flex;align-items:center;gap:6px;border:none;border-radius:999px;padding:11px 22px;font-weight:800;font-size:15px;color:#3a2a10;background:linear-gradient(180deg,#ffd874,#f2b134);box-shadow:0 4px 14px rgba(0,0,0,.28);cursor:pointer;text-shadow:none}
.ri-cta i{font-size:16px}
body.calm .race-intro .ri-prop,body.lowfx .race-intro .ri-prop{animation:none}
@media (prefers-reduced-motion:reduce){.race-intro,.race-intro.ri-go .ri-stage{transition:none;animation:none}.race-intro .ri-prop{animation:none}}

/* ---- Daily Challenge hub (daily.js / renderDaily) ---- */
.daily-zone{margin-bottom:12px}
.dz-emoji{font-size:30px;line-height:1;width:44px;height:44px;display:flex;align-items:center;justify-content:center;background:var(--card-2,#f3f1e8);border-radius:12px}
.dz-h{font-weight:700;font-size:15px;margin-bottom:2px}
.dz-stats{display:flex;gap:8px;margin-top:8px}
.dz-stat{flex:1;background:var(--card-2,#f3f1e8);border-radius:10px;padding:8px 6px;text-align:center}
.dz-stat span{display:block;font-size:11px;color:var(--muted);margin-bottom:2px}
.dz-stat b{font-size:16px}
.dz-toggle{display:flex;align-items:center;gap:7px;font-size:13px;color:var(--muted);cursor:pointer}
.dz-toggle input{width:16px;height:16px}

/* ---- Today hub: shortcut row + collapsible category buckets ---- */
.today-shortcuts{display:flex;gap:8px;margin:0 0 12px}
.tshort{flex:1;position:relative;display:flex;flex-direction:column;align-items:center;gap:3px;padding:9px 4px;border:none;border-radius:14px;background:var(--card,#fff);box-shadow:0 1px 3px rgba(60,70,50,.08);font-weight:700;font-size:13px;color:var(--ink,#3a4030);cursor:pointer}
.tshort i{font-size:20px}
.tshort:active{transform:scale(.97)}
.tshort.locked{opacity:.45}
.tshort-dot{position:absolute;top:7px;right:14px;width:8px;height:8px;border-radius:50%;background:#e8607a;box-shadow:0 0 0 2px var(--card,#fff)}
.tbucket{margin-bottom:10px}
.tbucket>summary{list-style:none;cursor:pointer;font-weight:700;font-size:13px;color:var(--muted);padding:4px 2px 8px;display:flex;align-items:center;gap:6px;user-select:none}
.tbucket>summary::-webkit-details-marker{display:none}
.tbucket>summary::before{content:'›';display:inline-block;transition:transform .15s;font-size:18px;line-height:1;color:var(--muted)}
.tbucket[open]>summary::before{transform:rotate(90deg)}
/* layout-stability: animated fold (see ui.js animateBuckets). The row eases 1fr↔0fr; the chevron keys
   off .folded so it rotates with the slide, not at the deferred native close. */
.tbucket .tb-body{display:grid;grid-template-rows:1fr;transition:grid-template-rows .26s ease}
.tbucket.folded .tb-body{grid-template-rows:0fr}
.tbucket .tb-inner{min-height:0;overflow:hidden}
.tbucket._anim[open]>summary::before, .tbucket>summary::before{transition:transform .15s}
.tbucket.folded[open]>summary::before{transform:rotate(0deg)}

/* ---- Critter Quiz (daily quiz mechanic) ---- */
.quiz-dots{display:flex;gap:5px;margin-left:auto}
.quiz-dots .qd{width:8px;height:8px;border-radius:50%;background:#d7ddcf}
.quiz-dots .qd.done{background:var(--green,#7cc36b)}
.quiz-dots .qd.cur{background:var(--green-d,#4e9a4e);transform:scale(1.06)}
.quiz-card{background:var(--card,#fff);border-radius:18px;padding:20px 16px;box-shadow:0 2px 8px rgba(60,70,50,.08);text-align:center}
.quiz-emoji{font-size:60px;line-height:1;margin:4px 0 10px;display:inline-block}
.quiz-q{font-weight:700;font-size:18px;margin-bottom:16px}
.quiz-opts{display:flex;flex-direction:column;gap:9px}
.quiz-opt{padding:13px 14px;border:2px solid #e6e9df;border-radius:13px;background:#fbfcf9;font-weight:600;font-size:15px;color:var(--ink,#3a4030);cursor:pointer;transition:transform .12s,border-color .15s,opacity .15s,background .15s}
.quiz-opt:active{transform:scale(.98)}
.quiz-opt.dim{opacity:.4;pointer-events:none}
.quiz-opt.correct{border-color:var(--green,#7cc36b);background:#eef7e9}
.quiz-opt.reveal{border-color:var(--green,#7cc36b);animation:qpulse 1s ease}
.quiz-opt.wrong{border-color:#e8607a;background:#fbeef0;animation:qshake .3s ease}
@keyframes qpulse{0%,100%{transform:scale(1)}50%{transform:scale(1.04)}}
@keyframes qshake{0%,100%{transform:translateX(0)}25%{transform:translateX(-5px)}75%{transform:translateX(5px)}}
.quiz-hintbar{display:flex;align-items:center;gap:10px;margin-top:14px;text-align:left}
.quiz-hintbar .muted{font-size:13px;flex:1}
@media (prefers-reduced-motion:reduce){ .quiz-opt.reveal,.quiz-opt.wrong{animation:none} }
.count-num{min-width:54px;flex:0 0 auto}
.count-grid{display:flex;flex-wrap:wrap;gap:8px;justify-content:center;margin:6px 0 16px;font-size:30px;line-height:1}
.count-grid .cc{transition:transform .15s,filter .15s}
.count-grid .cc-on{filter:drop-shadow(0 0 6px var(--green,#7cc36b));transform:scale(1.06)}
@media (prefers-reduced-motion:reduce){ .count-grid .cc{transition:none} }
.odd-grid .cc-btn{border:none;background:transparent;cursor:pointer;border-radius:12px;padding:6px;transition:transform .12s,opacity .15s,background .15s}
.odd-grid .cc-btn:active{transform:scale(.92)}
.odd-grid .cc-btn.dim{opacity:.3;pointer-events:none}
.odd-grid .cc-btn.correct{background:#eef7e9}
.odd-grid .cc-btn.reveal{background:#eef7e9;animation:qpulse 1s ease}
.odd-grid .cc-btn.wrong{animation:qshake .3s ease}
@media (prefers-reduced-motion:reduce){ .odd-grid .cc-btn.reveal,.odd-grid .cc-btn.wrong{animation:none} }
.lboard{max-height:46vh;overflow-y:auto;margin-top:6px}

/* star chase */
.starrow{display:flex;gap:8px;flex-wrap:wrap}
.starmile{display:flex;flex-direction:column;align-items:center;gap:2px;border:1px solid var(--line);border-radius:13px;padding:8px 10px;background:#fff;font-weight:700;font-size:14px;min-width:62px}
.starmile .ti{font-size:18px;color:#cdd5c6}
.starmile .sm-rw{font-size:11px;font-weight:600}
.starmile.ready{border-color:var(--amber);box-shadow:0 0 0 2px rgba(242,177,52,.3)}
.starmile.ready .ti{color:var(--amber)}
.starmile.got{background:var(--green-l);opacity:.8}
.starmile.got .ti{color:var(--green)}
.starmile.soon{opacity:.55}

/* pass popup */
.phead{display:grid;grid-template-columns:42px 1fr 1fr;gap:6px;font-size:12px;font-weight:700;color:var(--muted);padding:0 2px 4px;text-align:center}
.pboard{max-height:46vh;overflow-y:auto}
.prow{display:grid;grid-template-columns:42px 1fr 1fr;gap:6px;align-items:center;padding:6px 0;border-bottom:1px solid var(--line)}
.prow .ptier{width:30px;height:30px;border-radius:50%;background:#eef2ea;display:grid;place-items:center;font-weight:700;margin:0 auto;font-size:14px}
.prow.reached .ptier{background:var(--green);color:#fff}
.pcell{display:flex;align-items:center;justify-content:center;gap:6px;min-height:34px;border-radius:10px;background:#faf8f1;flex-wrap:wrap}
.pcell.prem{background:#f3f0fb}
.pclaim{background:var(--amber);color:#5a3d00;font-weight:700;font-size:12px;padding:5px 10px;border-radius:9px}
.pcell .ok{color:var(--green);font-size:18px}
.tag.gold{background:var(--amber);color:#5a3d00}

/* ---------- Habitats (routing construction) ---------- */
.hab-res{display:flex;gap:8px;margin-bottom:12px}
.hab-res .res{flex:1;display:flex;align-items:center;gap:6px;background:#fff;border:1px solid var(--line);border-radius:14px;padding:9px 10px;box-shadow:var(--shadow);font-weight:700;font-size:15px}
.hab-res .res i{font-size:19px}
.hab-pen{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px;min-height:10px}
.pen-chip{position:relative;flex:0 0 50px;width:50px;height:50px;border-radius:13px;background:var(--green-l);padding:4px;box-shadow:var(--shadow);cursor:pointer;transition:transform .1s}
.pen-chip:active{transform:scale(.9)}
.pen-chip svg{width:100%;height:100%;display:block}
.pen-chip .home-dot{position:absolute;bottom:-5px;right:-5px;width:20px;height:20px;border-radius:50%;display:grid;place-items:center;font-size:11px;color:#fff;border:2px solid #fff;box-shadow:0 1px 3px rgba(0,0,0,.18)}
.hab-card{position:relative;margin-bottom:12px}
.hab-head{display:flex;align-items:center;gap:11px}
.hab-ico{width:44px;height:44px;border-radius:13px;display:grid;place-items:center;font-size:23px;color:#fff;flex-shrink:0}
.hab-name{font-weight:700;font-size:17px}
.hab-stage{font-size:13px;color:var(--muted);font-weight:600}
/* stage-ladder pips (SANCTUARY spec P3) — replaces the single stage label + "Task N/9" counter */
.hab-pips{display:flex;align-items:center;gap:4px;flex-wrap:wrap;margin-top:2px}
.hab-pip{display:inline-flex;align-items:center;justify-content:center;gap:3px;border:0;padding:2px 7px;border-radius:999px;
  font-size:11px;font-weight:700;line-height:1.3;font-family:inherit;background:rgba(120,120,110,.12);color:var(--muted);cursor:pointer}
.hab-pip .ti{font-size:11px}
.hab-pip.done{background:#dff0d2;color:#4c8a3f;padding:2px 5px}
.hab-pip.cur{background:#fdeec9;color:#8a6a15}
.hab-pip.todo{padding:2px 4px;opacity:.55}
.hab-pip.todo .ti{font-size:9px}
.hab-card.complete .hab-pip.cur{background:#f5e3b0;color:#7a5a10}
.hab-reward{white-space:nowrap}
/* beds full/max trade line (SANCTUARY spec P5) */
.rpke-note{font-size:12px;color:var(--muted);margin:5px 2px 2px;line-height:1.45}
.hab-spec{display:flex;gap:4px;margin-left:auto}
.hab-spec .mini{flex:0 0 30px;width:30px;height:30px;background:rgba(255,255,255,.6);border-radius:8px;padding:2px}
.hab-spec .mini svg{width:100%;height:100%;display:block}
.hab-barwrap{margin-top:11px}
.hab-barlbl{display:flex;justify-content:space-between;font-size:12px;font-weight:600;color:var(--muted);margin-top:6px}
.hab-actions{display:flex;gap:8px;margin-top:11px}
.hab-actions .btn{flex:1}
.hab-btn2{display:flex;flex-direction:column;align-items:flex-start;gap:1px;text-align:start}   /* cost → outcome, two lines (redesign §2) */
.hab-btn2 .hb-l1{display:flex;align-items:center;gap:5px;font-weight:800}
.hab-btn2 .hb-out{font-size:10.5px;font-weight:600;opacity:.8;line-height:1.25}
.hab-card.complete{background:linear-gradient(160deg,#ffffff,#f1f8e6)}
.hab-card.hab-bloom{animation:habBloom .5s ease}
@keyframes habBloom{0%,100%{transform:scale(1)}45%{transform:scale(1.045)}}
.hab-pop{position:absolute;font-weight:800;color:var(--green-d);pointer-events:none;animation:fup .9s ease forwards;z-index:9;font-size:18px}
.hab-fly{position:fixed;z-index:80;pointer-events:none;will-change:transform,opacity}
.hab-fly svg{width:100%;height:100%;display:block;filter:drop-shadow(0 3px 4px rgba(40,55,30,.25))}
.hab-spark{position:fixed;width:9px;height:9px;border-radius:50%;background:#ffd76b;pointer-events:none;z-index:79}
@media (prefers-reduced-motion:reduce){.hab-card.hab-bloom{animation:none}.prog-fill{transition:none}}

/* ===================== Quest layer ("Ranger's Path") =====================
   All motion obeys the JUICE.md calm ceiling: scale <=1.06, single rise+decay,
   no oscillation, warm green/amber only (no white, no red), reduced-motion safe.
   NOTE: never reuse @keyframes pulse (scale 1.12 x2) — it breaches the ceiling. */
:root{ --q-accent:#5fae5f; --q-gold:#f2b134; --q-glow:rgba(95,174,95,.30); }

/* the host (Pip) chip + card head */
.q-host{display:flex;align-items:center;gap:9px;margin-bottom:6px}
.q-host .av{width:34px;height:34px}
.q-host .qh-name{font-weight:700;font-size:13px;color:var(--green-d)}
.q-host .qh-sub{font-size:12px;color:var(--muted);font-weight:500}
.q-track{display:inline-flex;align-items:center;gap:4px;font-size:11px;font-weight:700;padding:2px 8px;border-radius:999px}
.q-track.skill{background:var(--green-l);color:var(--green-d)}
.q-track.open{background:#fdeccb;color:#9a6a05}

/* a quest row */
.qrow{display:flex;align-items:center;gap:10px;padding:9px 0;border-top:1px solid var(--line)}
.qrow:first-of-type{border-top:none}
.qrow .qr-ico{width:30px;height:30px;border-radius:9px;display:grid;place-items:center;flex-shrink:0;background:var(--green-l);color:var(--green-d);font-size:17px}
.qrow.worth .qr-ico{background:#fdeccb;color:#c4860a}
.qrow .qr-body{flex:1;min-width:0}
.qrow .qr-title{font-weight:600;font-size:14px;line-height:1.2}
.qrow .qr-pip{font-size:12px;color:var(--muted);font-weight:500;margin-top:1px}
.qrow .qr-right{display:flex;align-items:center;gap:6px;flex-shrink:0}
.q-modebadge{font-size:10px;font-weight:800;padding:2px 6px;border-radius:6px;background:var(--amber);color:#5a3d00}
.q-modebadge.worth{background:#c4860a;color:#fff}
.qrow.claimed .qr-title{opacity:.5;text-decoration:line-through}

/* the one calm row-level spotlight — single rise+decay, runs ONCE */
@keyframes qspot{0%{transform:scale(1)}45%{transform:scale(1.05)}100%{transform:scale(1)}}
.qrow.qspot{animation:qspot .9s ease 1;border-radius:12px;box-shadow:0 0 0 1.5px var(--q-accent),0 6px 16px var(--q-glow);padding-left:8px;padding-right:8px}

/* QP score pill in the HUD */
.hud-pill.qp i{color:var(--green-d)}
@keyframes qArrive{0%,100%{transform:none}45%{transform:scale(1.05)}}
.hud-pill.q-arrive,#qpVal.q-arrive{animation:qArrive .42s cubic-bezier(.2,1.3,.4,1)}

/* QP milestone chips (reuse the star-chase look) */
.qmiles{display:flex;gap:7px;overflow-x:auto;padding:2px 0 2px;margin-top:8px}
.qmile{flex:0 0 auto;display:flex;flex-direction:column;align-items:center;gap:2px;min-width:62px;padding:7px 8px;border-radius:12px;border:1px solid var(--line);background:#fff;font-weight:700;font-size:12px}
.qmile.got{background:var(--green-l);border-color:transparent;opacity:.85}
.qmile.ready{border-color:var(--amber);box-shadow:0 0 0 2px rgba(242,177,52,.35);background:#fffaf0;cursor:pointer}
.qmile .qm-rw{font-size:10px;color:var(--muted);font-weight:600}

/* reward motes flying to the HUD */
.q-mote{position:fixed;z-index:80;pointer-events:none;will-change:transform,opacity;font-size:16px;font-weight:800}

/* one-time worth "treasure" reveal */
@keyframes qTreasure{0%{transform:scale(.3) translateY(10px);opacity:0}55%{transform:scale(1.06);opacity:1}100%{transform:scale(1)}}
.q-treasure{animation:qTreasure .6s cubic-bezier(.2,1.5,.4,1)}

@media (prefers-reduced-motion:reduce){
  .qrow.qspot{animation:none}
  .hud-pill.q-arrive,#qpVal.q-arrive{animation:none}
  .q-mote{display:none}
  .q-treasure{animation:none}
}

/* ---- Guided coachmark ("Show me" — leads the player to the exact place to act) ----
   A soft spotlight (un-dims the target), Pip's pointing hand, and a caption bubble.
   Whole layer is pointer-events:none so the target stays tappable; only the bubble
   captures taps. Calm: the only loop is a gentle hand bob; off under reduced-motion. */
#coach{position:fixed;inset:0;z-index:58;pointer-events:none}
.coach-halo{position:fixed;border-radius:16px;border:3px solid var(--amber);
  box-shadow:0 0 0 4px rgba(242,177,52,.35), 0 0 0 9999px rgba(22,30,16,.46);
  pointer-events:none;transition:left .25s ease,top .25s ease,width .25s ease,height .25s ease;animation:coachIn .35s ease}
@keyframes coachIn{from{opacity:0;transform:scale(.94)}to{opacity:1;transform:scale(1)}}
.coach-hand{position:fixed;font-size:30px;color:var(--amber-d);pointer-events:none;z-index:59;
  filter:drop-shadow(0 2px 3px rgba(40,55,30,.3));animation:coachBob 1.15s ease-in-out infinite}
@keyframes coachBob{0%,100%{transform:translateY(0)}50%{transform:translateY(-7px)}}
/* guided coachmark bubble — DISTINCT class from the in-board .coach-bubble (below) so the
   board-coach rule can't clobber it (it was overriding pointer-events:auto -> none = dead button,
   and #fff -> --ink = dark/cramped). Keep these two coaches on separate class names. */
.guide-bubble{position:fixed;max-width:268px;background:#fff;border-radius:16px;padding:12px 14px;z-index:60;
  box-shadow:0 14px 34px rgba(40,55,30,.30);pointer-events:auto;animation:coachIn .3s ease;display:flex;gap:10px;align-items:flex-start}
.guide-bubble .av{width:36px;height:36px;flex-shrink:0}
.guide-bubble .cb-name{font-weight:700;color:var(--green-d);font-size:12px;margin-bottom:2px}
.guide-bubble .cb-text{font-size:13.5px;font-weight:500;line-height:1.35;color:var(--ink)}
.guide-bubble .cb-ok{margin-top:10px;background:var(--green);color:#fff;border-radius:10px;font-weight:700;font-size:13px;padding:7px 15px;box-shadow:0 3px 0 var(--green-d)}
.guide-bubble .cb-ok:active{transform:translateY(2px);box-shadow:0 1px 0 var(--green-d)}
@media (prefers-reduced-motion:reduce){ .coach-halo,.coach-hand,.guide-bubble{animation:none} }

/* =====================================================================
   Interactive meta-layer — "tap any item to learn what it means / do its action"
   Critterpedia fact card + generic info card + league how-to.
   All motion obeys the JUICE.md calm ceiling: scale <=1.06, gentle shine,
   single rise+decay, no red, no flash, reduced-motion safe.
   ===================================================================== */

/* affordance: anything inspectable gets a soft press + pointer */
.tappable{cursor:pointer;transition:transform .12s ease}
.tappable:active{transform:scale(.95)}
.hud-pill.tappable:active{transform:scale(.96)}
/* hold-to-inspect targets: suppress the native long-press callout / text selection */
.tappable,.critter,.tool{-webkit-touch-callout:none;-webkit-user-select:none;user-select:none}

/* one-time "hold to learn" coach-mark on the first special piece (calm: soft ring pulse, no flash) */
.coach-glow{border-radius:14px;animation:coachGlow 1.6s ease-in-out infinite}
@keyframes coachGlow{0%,100%{box-shadow:0 0 0 2px rgba(242,177,52,.38)}50%{box-shadow:0 0 0 5px rgba(242,177,52,.72)}}
.coach-bubble{position:absolute;z-index:30;display:inline-flex;align-items:center;gap:7px;max-width:188px;
  background:var(--ink);color:#fff;font-weight:600;font-size:12.5px;line-height:1.25;padding:8px 12px;border-radius:13px;
  box-shadow:0 6px 18px rgba(0,0,0,.28);pointer-events:none;animation:coachFade .3s ease}
.coach-bubble i{font-size:17px;flex:0 0 auto;color:var(--amber)}
.coach-bubble::after{content:'';position:absolute;left:50%;transform:translateX(-50%);border:6px solid transparent}
.coach-bubble.up{transform:translate(-50%,-100%)}
.coach-bubble.up::after{top:100%;border-top-color:var(--ink)}
.coach-bubble.down{transform:translate(-50%,0)}
.coach-bubble.down::after{bottom:100%;border-bottom-color:var(--ink)}
@keyframes coachFade{from{opacity:0}to{opacity:1}}
@media (prefers-reduced-motion:reduce){
  .coach-glow{animation:none;box-shadow:0 0 0 3px rgba(242,177,52,.55)}
  .coach-bubble{animation:none}
}

/* ---- First-tap coach KIT (v3, COACH.md §v3): spotlight + white-glove hand + ghost demo ----
   Guidance, not juice — it shows even at the calm feel; calm/reduced-motion collapses to the
   static variant (no hand loop / ripple / ghost — spotlight + ring + bubble only). The whole
   kit is pointer-events:none; the input gate lives in tap(). z 31: above board art + HUD/dock
   (both dimmed by the spotlight's 9999px shadow), below every real overlay (tpot-dim 58+). */
.coach-kit{position:absolute;left:0;top:0;width:100%;height:100%;z-index:31;pointer-events:none}
.coach-kit>*{position:absolute}
.coach-spot{border-radius:16px;box-shadow:0 0 0 9999px rgba(22,30,16,.45)}
.coach-tapring{border-radius:16px;border:3px solid var(--amber);animation:coachRingPulse 1.6s ease-in-out infinite}
@keyframes coachRingPulse{0%,100%{transform:scale(1);opacity:.8}50%{transform:scale(1.05);opacity:1}}   /* ≤1.06 calm ceiling */
.coach-lane{border-radius:12px;background:rgba(255,240,170,.28);box-shadow:inset 0 0 0 2px rgba(242,177,52,.4)}
.coach-ghost{opacity:0;animation:coachGhostRun 3.2s ease-in 3}   /* 3 demo runs, then rests (motion budget) */
.coach-ghost svg{width:100%;height:100%;display:block}
@keyframes coachGhostRun{0%,16%{opacity:0;transform:none}22%{opacity:.5}30%{opacity:.44}55%,100%{opacity:0;transform:var(--run)}}
.coach-hand2{transform-origin:51% 5%;filter:drop-shadow(0 3px 4px rgba(40,55,30,.35));animation:coachHandTap 3.2s ease-in-out infinite}
.coach-hand2 svg{width:100%;height:100%;display:block}
.coach-hand2.still{animation:none;transform:translateY(2px)}
@keyframes coachHandTap{0%,14%,100%{transform:translateY(7px)}5%,9%{transform:translateY(0) scale(.97)}}   /* rest = lifted; 5-9% = fingertip contact */
.coach-ripple{width:38px;height:38px;border-radius:50%;border:3px solid #fff;box-shadow:0 0 0 1px rgba(242,177,52,.6);opacity:0;animation:coachRipple 3.2s ease-out infinite}
@keyframes coachRipple{0%,4%{opacity:0;transform:scale(.4)}9%{opacity:.85;transform:scale(.75)}22%{opacity:0;transform:scale(1.5)}100%{opacity:0}}
.coach-bubble2{transform:translate(-50%,-100%);background:var(--ink);color:#fff;border-radius:15px;padding:10px 16px 9px;text-align:center;
  box-shadow:0 8px 22px rgba(0,0,0,.3);animation:coachFade .3s ease;max-width:240px}
.coach-bubble2 b{display:block;font-size:18px;font-weight:800;letter-spacing:.2px}
.coach-bubble2 span{display:block;font-size:12.5px;font-weight:600;opacity:.88;margin-top:2px}
.coach-bubble2::after{content:'';position:absolute;left:var(--tx,50%);top:100%;transform:translateX(-50%);border:8px solid transparent;border-top-color:var(--ink)}
/* drag-coach wordless strip: [drag ✗] → [tap ✓] inside the standard .coach-bubble */
.cb-col{display:flex;flex-direction:column;align-items:center;gap:6px}
.coach-strip{display:flex;align-items:center;gap:12px;background:rgba(255,255,255,.12);border-radius:9px;padding:4px 9px}
.coach-strip svg{width:40px;height:24px;display:block}
@media (prefers-reduced-motion:reduce){
  .coach-tapring,.coach-hand2,.coach-bubble2{animation:none}
  .coach-ripple,.coach-ghost{display:none}
}
body.calm .coach-tapring,body.calm .coach-hand2,body.calm .coach-bubble2{animation:none}
body.calm .coach-ripple,body.calm .coach-ghost{display:none}

/* ---- Critterpedia fact card (the animal popup) ---- */
.cpedia .cp-halo{width:118px;height:118px;margin:2px auto 6px;border-radius:50%;
  background:radial-gradient(circle,#fff7e0,#ffe6a8);padding:14px;
  box-shadow:0 0 0 5px rgba(242,177,52,.22),0 6px 16px rgba(0,0,0,.12);animation:discBloom .5s cubic-bezier(.2,1.5,.4,1)}
.cpedia .cp-halo svg{width:100%;height:100%;display:block}
.cpedia .cp-name{font-size:23px;font-weight:700;margin:0;line-height:1.1}
.cpedia .cp-tag{display:inline-flex;align-items:center;gap:5px;margin:7px 0 14px;
  background:var(--green-l);color:var(--green-d);font-weight:700;font-size:13px;padding:4px 12px;border-radius:999px}
.cp-facts{display:flex;flex-direction:column;gap:9px;text-align:left;margin:0 2px 6px}
.cp-fact{display:flex;align-items:center;gap:10px;background:#faf8f1;border:1px solid var(--line);
  border-radius:14px;padding:11px 13px;font-weight:600;font-size:15px;color:var(--ink);
  opacity:0;transform:translateY(6px);animation:cpFactIn .42s ease forwards}
.cp-fact:nth-child(1){animation-delay:.06s}
.cp-fact:nth-child(2){animation-delay:.15s}
.cp-fact:nth-child(3){animation-delay:.24s}
.cp-fact .cp-bul{flex:0 0 26px;width:26px;height:26px;border-radius:9px;display:grid;place-items:center;
  background:var(--green-l);color:var(--green-d);font-size:16px}
@keyframes cpFactIn{to{opacity:1;transform:translateY(0)}}
.cp-home{display:flex;align-items:center;justify-content:center;gap:6px;margin:12px 0 4px;
  font-size:13px;font-weight:600;color:var(--muted)}
.cp-home i{font-size:16px;color:var(--green-d)}
.cp-meta{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:600;color:var(--muted);
  background:#fff;border:1px solid var(--line);border-radius:999px;padding:3px 11px;margin:2px 0 2px}
.cp-meta.seen{color:var(--green-d);background:var(--green-l);border-color:transparent}

/* ---- generic info card (currencies / boosters / resources) ---- */
.infocard .ic-disc{width:58px;height:58px;margin:0 auto 8px;border-radius:17px;display:grid;place-items:center;
  font-size:30px;color:#fff;box-shadow:0 4px 12px rgba(50,70,40,.18);animation:discBloom .42s cubic-bezier(.2,1.4,.4,1)}
.infocard .ic-mean{font-size:15px;font-weight:600;color:var(--ink);margin:0 0 12px}
.ic-facts{display:flex;flex-direction:column;gap:7px;text-align:left;margin:0 0 4px}
.ic-facts .icf{display:flex;align-items:flex-start;gap:9px;font-size:13.5px;font-weight:500;color:var(--ink)}
.ic-facts .icf i{flex:0 0 auto;font-size:16px;color:var(--green-d);margin-top:1px}

/* ---- league: how-to-play ladder + per-tier hint ---- */
.league-howto{display:flex;flex-direction:column;gap:7px;text-align:left;margin:2px 0 12px}
.league-howto .lh{display:flex;align-items:flex-start;gap:9px;font-size:13.5px;font-weight:500;color:var(--ink)}
.league-howto .lh i{flex:0 0 auto;font-size:17px;color:var(--green-d);margin-top:1px}
.ladder{display:flex;flex-direction:column;gap:7px;margin:2px 0 6px}

/* ===================== Social: Critter Town ===================== */
.town-head{margin-bottom:6px}
.town-h3{margin:16px 0 8px;font-size:15px;font-weight:700}
.town-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:10px}
.pc{background:var(--card);border:1px solid var(--line);border-radius:var(--r-lg);padding:12px 10px;box-shadow:var(--shadow);display:flex;flex-direction:column;align-items:center;gap:6px;text-align:center;position:relative}
.pc.house{cursor:pointer;transition:transform .1s}
.pc.house:active{transform:scale(.97)}
.pc-av{position:relative;width:62px;height:62px;border-radius:50%;background:var(--pcbg,#e7f6e2);display:grid;place-items:center;padding:6px;overflow:hidden;flex-shrink:0}
.pc-av svg{width:100%;height:100%;position:relative;z-index:2}
.pc-av.big{width:88px;height:88px;padding:9px}
.pc-banner{position:absolute;inset:0;display:grid;place-items:center;opacity:.16;z-index:1}
.pc-banner svg{width:135%;height:135%}
.pc-av.frame-leaf{box-shadow:0 0 0 3px #8fbf52}
.pc-av.frame-flame{box-shadow:0 0 0 3px #ef7a3a}
.pc-av.frame-gold{box-shadow:0 0 0 3px var(--amber)}
.pc-av.frame-crown{box-shadow:0 0 0 3px #9a8fe0}
.pc-name{font-weight:700;font-size:13px;line-height:1.15;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pc-name.big{font-size:17px;white-space:normal}
.pc-lvl{display:inline-flex;align-items:center;gap:4px;font-size:12px;font-weight:700;color:var(--green-d);background:var(--green-l);border-radius:999px;padding:3px 10px}
.pc-lvl:active{transform:scale(.95)}
.pc-like{display:inline-flex;align-items:center;gap:4px;font-size:13px;font-weight:700;color:#b54368;background:#fdeef2;border:1px solid #f6d3dd;border-radius:999px;padding:4px 11px}
.pc-like .ti{font-size:15px}
.pc-like.on{background:#f6c9d6;color:#9c2f4f;border-color:#eeb4c5}
.pc-like.static{cursor:default}
@keyframes likedPulse{0%,100%{transform:scale(1)}45%{transform:scale(1.06)}}
.pc-like.liked-pulse{animation:likedPulse .4s ease}
.town-me{align-items:center;margin-bottom:6px}
.town-me-stats{display:flex;gap:8px;align-items:center;margin:2px 0}
.town-me-actions{display:flex;gap:8px;margin-top:6px}

/* profile popup */
.prof{display:flex;flex-direction:column;align-items:center}
.prof .pc-av{margin:0 auto}
.pc-flair{display:inline-flex;align-items:center;gap:5px;font-size:12px;font-weight:700;color:var(--amber-d);background:#fdeccb;border-radius:999px;padding:3px 11px;margin-bottom:4px}
.prof-stats{display:flex;gap:6px;margin:12px 0 14px;width:100%;justify-content:center}
.pst{flex:1;background:#faf8f1;border-radius:13px;padding:9px 4px;display:flex;flex-direction:column;align-items:center;gap:1px}
.pst .ti{font-size:18px;color:var(--green-d)}
.pst-v{font-weight:800;font-size:16px}
.pst-l{font-size:11px;color:var(--muted);font-weight:600}

/* visit / sub-screen toolbar */
.visit-top{display:flex;align-items:center;gap:8px;margin:2px 0 4px}
.visit-title{flex:1;text-align:center;font-weight:700;font-size:16px;display:flex;align-items:center;justify-content:center;gap:6px;min-width:0}
.visit-title .av{width:26px;height:26px;flex-shrink:0}
.visit-jumps{display:flex;gap:6px}
.vmap{display:flex;flex-direction:column;align-items:center;gap:14px;padding:8px 0 6px}
.lvl-node.ro{cursor:default}

/* daily gallery */
.gal-podium{display:flex;gap:8px;align-items:flex-end;justify-content:center;margin:6px 0 12px}
.gal-pod{flex:1;background:var(--card);border:1px solid var(--line);border-radius:var(--r-lg);box-shadow:var(--shadow);padding:12px 6px 10px;display:flex;flex-direction:column;align-items:center;gap:4px;position:relative;cursor:pointer}
.gal-pod.p1{transform:scale(1.06);border-color:var(--amber);box-shadow:0 8px 20px rgba(242,177,52,.25)}
.gal-pod .pc-av{width:54px;height:54px}
.gal-pod.p1 .pc-av{width:64px;height:64px}
.gal-rank{position:absolute;top:-9px;left:50%;transform:translateX(-50%);width:24px;height:24px;border-radius:50%;background:var(--green);color:#fff;font-weight:800;font-size:13px;display:grid;place-items:center;border:2px solid #fff;box-shadow:0 1px 3px rgba(0,0,0,.2)}
.gal-pod.p1 .gal-rank{background:var(--amber);color:#5a3d00}
.gal-likes{font-weight:800;font-size:13px;color:#b54368;display:inline-flex;align-items:center;gap:3px}
.gal-list{display:flex;flex-direction:column;gap:2px}
.gal-row{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:12px;font-weight:600;font-size:14px;cursor:pointer}
.gal-row:active{background:rgba(95,174,95,.08)}
.gal-row.you{outline:2px solid var(--green);outline-offset:-2px;background:var(--green-l)}
.gal-pos{width:22px;text-align:center;color:var(--muted);font-weight:700}
.gal-row .av{width:34px;height:34px;flex-shrink:0}
.gal-nm{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.gal-lk{font-weight:800;color:#b54368;display:inline-flex;align-items:center;gap:3px}

/* customize editor */
.cust-sec{margin:12px 0}
.cust-lbl{font-size:13px;font-weight:700;color:var(--muted);margin-bottom:6px}
.cust-row{display:flex;gap:8px;flex-wrap:wrap;align-items:center}
.cust-preview{margin-bottom:4px}
.sw{width:30px;height:30px;border-radius:50%;border:2px solid #fff;box-shadow:0 0 0 1px var(--line);display:grid;place-items:center;color:#fff}
.sw.on{box-shadow:0 0 0 2px var(--ink)}
.cust-sp{width:48px;height:48px;border-radius:14px;border:1px solid var(--line);background:#fff;padding:4px;display:grid;place-items:center;box-shadow:var(--shadow);color:var(--muted)}
.cust-sp svg{width:100%;height:100%}
.cust-sp.on{border-color:var(--green);box-shadow:0 0 0 2px rgba(95,174,95,.4)}
.cust-name{flex:1;min-width:120px;border:1px solid var(--line);border-radius:12px;padding:10px 12px;font-family:inherit;font-size:15px;font-weight:600;color:var(--ink);background:#fff}
.cust-frame{width:36px;height:36px;border-radius:50%;background:#fff;border:1px solid var(--line);display:grid;place-items:center;color:var(--muted)}
.cust-frame.frame-leaf{box-shadow:inset 0 0 0 3px #8fbf52}
.cust-frame.frame-flame{box-shadow:inset 0 0 0 3px #ef7a3a}
.cust-frame.frame-gold{box-shadow:inset 0 0 0 3px var(--amber)}
.cust-frame.frame-crown{box-shadow:inset 0 0 0 3px #9a8fe0}
.cust-frame.on{outline:2px solid var(--ink);outline-offset:1px}
.cust-frame.locked{opacity:.45}
.cust-flairs{flex-direction:column;align-items:stretch}
.cust-flair{display:flex;align-items:center;gap:8px;padding:9px 12px;border-radius:12px;border:1px solid var(--line);background:#fff;font-weight:600;font-size:14px;text-align:left}
.cust-flair .ti{font-size:18px;color:var(--green-d)}
.cust-flair.on{border-color:var(--green);background:var(--green-l);color:var(--green-d)}
@media (prefers-reduced-motion:reduce){.pc-like.liked-pulse{animation:none}.gal-pod.p1{transform:none}}
.ltier{display:flex;align-items:center;gap:11px;padding:9px 11px;border-radius:14px;border:1px solid var(--line);
  background:#fff;text-align:left}
.ltier .lt-ico{flex:0 0 38px;width:38px;height:38px;border-radius:11px;display:grid;place-items:center;
  font-size:21px;color:#fff;background:var(--green)}
.ltier .lt-body{flex:1;min-width:0}
.ltier .lt-name{font-weight:700;font-size:15px;line-height:1.1}
.ltier .lt-hint{font-size:12px;color:var(--muted);font-weight:500;margin-top:1px}
.ltier.cur{border-color:var(--amber);box-shadow:0 0 0 2px rgba(242,177,52,.32);background:#fffaf0}
.ltier.cur .lt-ico{background:var(--amber);color:#5a3d00;animation:tierShine 2.4s ease-in-out infinite}
.ltier.locked{opacity:.6}
.ltier.locked .lt-ico{background:#c8d2c0}
@keyframes tierShine{0%,100%{transform:scale(1);filter:brightness(1)}50%{transform:scale(1.05);filter:brightness(1.08)}}

/* league card: the tier badge becomes a tappable "how to play" hint chip */
.league-tierbadge{display:inline-flex;align-items:center;gap:6px;cursor:pointer}
.league-hintchip{display:inline-flex;align-items:center;gap:5px;font-size:12px;font-weight:600;
  color:var(--green-d);background:var(--green-l);border-radius:999px;padding:3px 10px;margin-top:8px}
.league-hintchip i{font-size:14px}

@media (prefers-reduced-motion:reduce){
  .cpedia .cp-halo,.cp-fact,.infocard .ic-disc{animation:none;opacity:1;transform:none}
  .ltier.cur .lt-ico{animation:none}
  .tappable:active,.hud-pill.tappable:active{transform:none}
}

/* ---------- Rescuer Level: HUD pill · locked meta cards · level-up · Profile ---------- */
.hud-pill.lvl i{color:#e6a32e}
.tcard.locked{opacity:.72;filter:saturate(.55)}
.tcard.locked .big{color:var(--muted)}
.tc-ico.locked{background:#c8d2c0}
.lock-chip{display:inline-flex;align-items:center;gap:4px;font-size:12px;font-weight:700;color:var(--muted);background:#eef2ea;border-radius:999px;padding:4px 10px}
/* ---------- Universal offline card state (OFFLINE.md §4.3): mirrors .locked — MUTED, never red ---------- */
.card.offline{border-style:dashed;border-color:rgba(124,138,115,.32);background:rgba(124,138,115,.05)}
.card.offline .big{color:var(--muted)}
.tc-ico.offline{background:rgba(124,138,115,.16);color:var(--muted)}
.tc-ico.offline i{font-size:20px}
.card.offline .btn[data-offline-connect]{color:var(--muted)}
/* ---------- Connection sheet (OFFLINE.md §4.2) — a calm bottom-sheet, symmetric actions ---------- */
.conn-sec-h{font-size:12px;font-weight:800;color:var(--muted);text-align:start;margin:10px 2px 4px}
.conn-item{display:flex;align-items:center;gap:9px;padding:7px 0;border-top:1px solid var(--line);text-align:start}
.conn-item:first-of-type{border-top:none}
.conn-ico{width:30px;height:30px;flex:0 0 auto;border-radius:9px;background:var(--green-l);color:var(--green-d);display:grid;place-items:center;font-size:16px}
.conn-note{font-size:13px;font-weight:600;color:var(--ink)}
.conn-rw{font-size:13px;font-weight:700;white-space:nowrap}
.conn-beacon,.conn-streak{display:flex;align-items:center;gap:7px;font-size:13px;color:var(--muted);margin:10px 2px 2px;text-align:start}
.conn-beacon i{color:#ef7a3a}.conn-streak i{color:#ef7a3a}
.conn-beacon b{color:var(--ink)}
.popup-actions .conn-act{flex:1 1 0;min-width:120px}   /* symmetric Retry / Keep-playing (no dark-pattern emphasis) */
.lvlup-banner{display:flex;align-items:center;gap:7px;justify-content:center;background:linear-gradient(90deg,var(--amber),#ffcf5a);color:#5a3d00;font-weight:800;font-size:14px;padding:9px 12px;border-radius:14px;margin-bottom:10px;box-shadow:var(--shadow)}
.lvlup-banner i{font-size:18px}
.lvlup-banner .lvlup-unl{font-weight:600;font-size:12px}
.lvl-unlocks{display:flex;flex-direction:column;gap:6px;margin:2px 0 12px}
.lvl-unl{display:flex;align-items:center;gap:7px;justify-content:center;font-size:14px;font-weight:600;color:var(--green-d)}
.ff-claim{display:flex;flex-direction:column;align-items:center;gap:2px;margin:6px 0 14px}
.ff-claim .ff-h{font-size:30px;font-weight:800;color:#7c5fae;line-height:1}
.ff-claim .ff-xp{font-size:13px;font-weight:600;color:var(--muted)}
.rxp-bar{display:flex;align-items:center;justify-content:space-between;margin:2px 0 10px}
.rxp-hero{margin-bottom:12px;background:linear-gradient(160deg,#ffffff,#f1f8e6)}
/* top-level Town <-> Friends switch (both are one tap from the always-available Town nav tab) */
.social-switch{display:flex;gap:6px;max-width:300px;margin:0 auto 14px}
.social-switch .ss-tab{flex:1;background:#fff;border:1px solid var(--line);border-radius:999px;padding:8px 0;font-weight:700;font-size:14px;color:var(--muted);box-shadow:var(--shadow)}
.social-switch .ss-tab.on{background:var(--green-l);color:var(--green-d);border-color:var(--green)}
.ref-tabs{display:flex;gap:8px;margin:16px 0 10px}
.ref-tab{flex:1;background:#fff;border:1px solid var(--line);border-radius:12px;padding:9px 0;font-weight:700;font-size:14px;color:var(--muted);box-shadow:var(--shadow)}
.ref-tab.on{background:var(--green-l);color:var(--green-d);border-color:var(--green)}
.lb-list{display:flex;flex-direction:column;gap:7px}
.lb-row{display:flex;align-items:center;gap:10px;background:#fff;border:1px solid var(--line);border-radius:14px;padding:9px 12px;box-shadow:var(--shadow)}
.lb-row.you{outline:2px solid var(--green);outline-offset:1px}
.lb-rank{width:22px;text-align:center;font-weight:800;color:var(--muted)}
.lb-row .av{width:34px;height:34px;flex-shrink:0}
.lb-name{font-weight:700;flex:1}
.lb-meta{font-size:13px;color:var(--muted);font-weight:600}
.lb-row .lb-act{flex-shrink:0;margin-left:4px;padding:5px 10px;font-size:12px}
.lb-row.asks{outline:2px solid #f6b8c6;outline-offset:1px;background:#fff6f8}
.lb-row .tag{flex-shrink:0;font-size:11px;font-weight:700;color:var(--green-d,#3f8a47);background:var(--green-l);border-radius:999px;padding:3px 9px}
.hearts-panel{margin:14px 0 4px;background:#fff;border:1px solid var(--line);border-radius:14px;padding:11px 13px;box-shadow:var(--shadow)}
.hearts-panel .hp-row{display:flex;align-items:center;gap:10px;justify-content:space-between;flex-wrap:wrap}
.hearts-panel .hp-txt{font-size:14px}
.hearts-panel .hp-note{font-size:12px;margin-top:7px}
.rxp-top{display:flex;align-items:center;gap:14px}
.rxp-av{position:relative;width:64px;height:64px;border-radius:50%;background:radial-gradient(circle,#fff7e0,#ffe6a8);padding:8px;box-shadow:0 0 0 4px rgba(242,177,52,.22),var(--shadow);flex-shrink:0}
.rxp-av svg{width:100%;height:100%;display:block}
.rxp-badge{position:absolute;right:-4px;bottom:-4px;min-width:24px;height:24px;border-radius:999px;background:var(--green);color:#fff;font-size:13px;font-weight:800;display:grid;place-items:center;border:2px solid #fff;padding:0 5px}
.rxp-name{font-size:20px;font-weight:700}
.rxp-xprow{display:flex;justify-content:space-between;align-items:center}
.rxp-h3{margin:18px 0 8px;font-size:16px;font-weight:700}
.rxp-stats{display:grid;grid-template-columns:repeat(4,1fr);gap:8px}
.rxp-stat{background:#fff;border:1px solid var(--line);border-radius:14px;padding:10px 4px;text-align:center;box-shadow:var(--shadow)}
.rxp-stat i{font-size:18px;color:var(--green-d)}
.rxp-stat .rxp-v{font-size:16px;font-weight:800;margin-top:2px}
.rxp-stat .rxp-l{font-size:10px;color:var(--muted);font-weight:600;line-height:1.1;margin-top:1px}
.rxp-ladder{display:flex;flex-direction:column;gap:8px}
.rxp-row{display:flex;align-items:center;gap:11px;padding:10px 12px;border-radius:14px;border:1px solid var(--line);background:#fff;box-shadow:var(--shadow)}
.rxp-row .rxp-rico{flex:0 0 38px;width:38px;height:38px;border-radius:11px;display:grid;place-items:center;font-size:20px;color:#fff;background:var(--green)}
.rxp-row.off{opacity:.72}
.rxp-row.off .rxp-rico{background:#c8d2c0}
.rxp-row .rxp-rbody{flex:1;min-width:0}
.rxp-row .rxp-rname{font-weight:700;font-size:15px}
.rxp-row .rxp-rsub{font-size:12px;color:var(--muted);font-weight:600;margin-top:1px}
.rxp-row .rxp-rlvl{font-weight:800;font-size:14px;color:var(--green-d)}
.rxp-row.off .rxp-rlvl{color:var(--muted)}
.rxp-row .rxp-rlvl .ti{font-size:20px;color:var(--green)}
@media (max-width:360px){ .rxp-stats{grid-template-columns:repeat(3,1fr)} }

/* =======================================================================
   Localization fonts + RTL  (CE.i18n sets <html lang="xx" dir="..">).
   Persian/Arabic use the bundled Vazirmatn (Arabic-script) face; CJK / Thai /
   Cyrillic use platform-native faces (instant, no extra web-font download).
   Latin languages keep the brand Fredoka. The layout mirrors under RTL EXCEPT
   the spatial gameplay surfaces, which stay LTR.
   ======================================================================= */
html[lang="fa"] body,
html[lang="ar"] body{
  font-family:'Vazirmatn','Fredoka',system-ui,-apple-system,'Segoe UI',Tahoma,sans-serif;
  line-height:1.65;
}
html[lang="ru"] body{ font-family:'Fredoka',system-ui,-apple-system,'Segoe UI',Roboto,sans-serif; }
html[lang="th"] body{ font-family:'Thonburi','Leelawadee UI','Noto Sans Thai',system-ui,sans-serif; line-height:1.6; }
html[lang="ko"] body{ font-family:'Apple SD Gothic Neo','Malgun Gothic','Noto Sans KR',system-ui,sans-serif; }
html[lang="ja"] body{ font-family:'Hiragino Sans','Hiragino Kaku Gothic ProN','Yu Gothic','Meiryo','Noto Sans JP',system-ui,sans-serif; }
html[lang="zh-Hans"] body{ font-family:'PingFang SC','Hiragino Sans GB','Microsoft YaHei','Noto Sans SC',system-ui,sans-serif; }
html[lang="zh-Hant"] body{ font-family:'PingFang TC','Microsoft JhengHei','Noto Sans TC',system-ui,sans-serif; }
/* the language picker holds 17 options — wrap + scroll so it never dominates the Settings popup */
.lang-row{ flex-wrap:wrap; gap:6px; max-height:138px; overflow-y:auto; padding:2px 2px 4px; }
/* GAMEPLAY STAYS LTR: the board grid + level map are spatial — critters face
   arrow directions tied to geometry, so mirroring them would contradict the
   puzzle logic. Pin them to LTR even inside the RTL document. */
[dir="rtl"] .board-wrap,
[dir="rtl"] .board,
[dir="rtl"] .pipe-time,
[dir="rtl"] .map-wrap{ direction:ltr; }   /* .pipe-time is a fixed overlay OUTSIDE the board flow, so the board's LTR pin never reached it — under RTL its per-digit cells laid the time bonus out right-to-left ("100"→"001"). Pin the gauge LTR. */

/* right-align the text blocks that were hardcoded text-align:left */
[dir="rtl"] .quiz-hintbar,
[dir="rtl"] .cp-facts,
[dir="rtl"] .ic-facts,
[dir="rtl"] .league-howto,
[dir="rtl"] .cust-flair{ text-align:right; }
[dir="rtl"] .ev-body,[dir="rtl"] .ev-howto{ text-align:right; }
[dir="rtl"] .ev-howto{ padding-left:0; padding-right:2px; }

/* margin-left:auto right-pins were LTR tricks — flip to margin-right:auto */
[dir="rtl"] .quiz-dots,
[dir="rtl"] .hab-spec{ margin-left:0; margin-right:auto; }

/* corner indicators mirror to the opposite horizontal edge */
[dir="rtl"] .badge-best{ right:auto; left:10px; }
[dir="rtl"] .logcell .ti{ right:auto; left:4px; }
[dir="rtl"] .tshort-dot{ right:auto; left:14px; }
[dir="rtl"] .pen-chip .home-dot{ right:auto; left:-5px; }
[dir="rtl"] .rxp-badge{ right:auto; left:-4px; }

/* back-chevrons point the RTL way */
[dir="rtl"] .hud-icon .ti-chevron-left{ display:inline-block; transform:scaleX(-1); }

/* screen slide-in mirrors so "forward" still enters from the leading edge */
[dir="rtl"] #screen.slide-fwd{ animation-name:scrSlideBack; }
[dir="rtl"] #screen.slide-back{ animation-name:scrSlideFwd; }

/* ---- identity / juicy profile: name + flag, edit, avatar frames, medals ---- */
.rxp-top{position:relative}
.rxp-edit{position:absolute;top:0;right:0;color:#7c8a73}
[dir="rtl"] .rxp-edit{right:auto;left:0}
.rxp-name{unicode-bidi:plaintext}
.rxp-av.frame-leaf{box-shadow:0 0 0 4px rgba(95,174,95,.5),var(--shadow)}
.rxp-av.frame-flame{box-shadow:0 0 0 4px rgba(242,120,52,.55),var(--shadow)}
.rxp-av.frame-gold{box-shadow:0 0 0 4px rgba(241,196,15,.6),var(--shadow)}
.rxp-av.frame-crown{box-shadow:0 0 0 4px rgba(155,120,230,.55),var(--shadow)}
.rxp-h3c{font-size:13px;font-weight:600;color:#9aa890;margin-inline-start:6px}
.rxp-medals{display:grid;grid-template-columns:repeat(5,1fr);gap:8px}
@media (max-width:420px){ .rxp-medals{grid-template-columns:repeat(4,1fr)} }
.rxp-medal{display:flex;flex-direction:column;align-items:center;gap:5px;padding:10px 4px;border-radius:14px;background:#f4f7f1;text-align:center;opacity:.55;filter:grayscale(.6);cursor:pointer}   /* P4: tappable → heroic detail popup */
.rxp-medal:active{transform:scale(.97)}
@media (prefers-reduced-motion:reduce){.rxp-medal:active{transform:none}}
.rxp-medal.on{opacity:1;filter:none;background:linear-gradient(160deg,#fff,#f1f8e6);box-shadow:var(--shadow)}
.rxp-mico{width:38px;height:38px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:#e7ece2;color:#9aa890;font-size:20px}
.rxp-medal.on .rxp-mico{background:radial-gradient(circle,#fff7e0,#ffe6a8);color:#e0962b}
.rxp-ml{font-size:11px;font-weight:600;line-height:1.2;color:#5a6b50}
.rxp-medal.on .rxp-ml{color:#3b4a33}

/* ---- settings: Simple/Detailed segmented control + profile card + section headers + language flags ---- */
.set-seg{display:flex;gap:8px;justify-content:center;margin:12px 0 2px}
.set-profile{display:flex;align-items:center;gap:12px;padding:10px 12px;margin:6px 0 10px;border-radius:16px;background:linear-gradient(160deg,#fff,#f1f8e6);box-shadow:var(--shadow)}
.set-pav{width:46px;height:46px;border-radius:50%;background:radial-gradient(circle,#fff7e0,#ffe6a8);padding:5px;flex-shrink:0}
.set-pav svg{width:100%;height:100%}
.set-pinfo{flex:1;min-width:0}
.set-pname{font-size:16px;font-weight:800;unicode-bidi:plaintext;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.set-h{font-size:11.5px;font-weight:800;text-transform:uppercase;letter-spacing:1.1px;color:#9aa890;margin:15px 4px 6px}
.lang-chip .flag-emoji{font-size:1.05em}
/* grouped settings cards + rows (Settings / Developer tools redesign) */
.set-card{background:#fff;border:1px solid var(--line);border-radius:16px;overflow:hidden;box-shadow:var(--shadow)}
.set-row{display:flex;align-items:center;justify-content:space-between;gap:10px;width:100%;padding:12px 14px;background:#fff;border:0;border-top:1px solid var(--line);font-size:15px;font-weight:600;text-align:left;color:inherit;font-family:inherit;cursor:pointer}
.set-card .set-row:first-child{border-top:0}
.set-row:active{background:#f6faef}
.set-row .lbl{display:flex;align-items:center;gap:10px;min-width:0;flex:1}
.set-row .lbl i{font-size:18px;color:#8a9a80;flex-shrink:0}
.set-row .val{display:flex;align-items:center;gap:6px;font-weight:700;color:#9aa890;font-size:13.5px;flex-shrink:0;white-space:nowrap}
.set-row .val.on{color:var(--green)}
.set-row .val .ti-chevron-right{font-size:17px;color:#c7d0bd;margin-right:-3px}
.set-row.inner{flex-direction:column;align-items:stretch;gap:9px}
.set-row.inner .lbl{flex:none}
.set-row.inner .diff-row{justify-content:flex-start;flex-wrap:wrap;margin:0}
/* A4 audio mixer — per-layer volume sliders. Calm palette (green accent), comfortable touch target. */
.set-row.srow{cursor:default;gap:14px}
.set-row.srow .lbl{flex:0 0 auto}
.volr{flex:1;min-width:110px;max-width:210px;height:26px;margin:-3px 0 -3px auto;accent-color:var(--green);cursor:pointer;background:transparent}
.volr:focus{outline:none}
.dev-badge{background:#eef3e6;color:#7a8a6f;border-radius:6px;padding:1px 7px;font-size:11px;font-weight:800;text-transform:lowercase;letter-spacing:.3px}
.lang-list{display:flex;flex-direction:column;max-height:52vh;overflow:auto;border:1px solid var(--line);border-radius:14px;margin:6px 0}
.lang-opt{display:flex;align-items:center;gap:11px;padding:11px 13px;border:0;border-top:1px solid var(--line);background:#fff;width:100%;text-align:left;font-weight:700;font-size:15px;font-family:inherit;cursor:pointer}
.lang-list .lang-opt:first-child{border-top:0}
.lang-opt:active{background:#f6faef}
.lang-opt.on{background:linear-gradient(160deg,#fff,#eef7e0)}
.lang-opt .lo-flag{font-size:21px;flex-shrink:0;line-height:1}
.lang-opt .lo-name{flex-shrink:0}
.lang-opt .lo-en{flex:1;color:#9aa890;font-weight:600;font-size:12px}
.lang-opt .ti-check{color:var(--green);font-size:19px;flex-shrink:0}

/* reduced-motion / calm: keep the star+heart reward feedback (fill stays) but drop the pulses */
@media (prefers-reduced-motion:reduce){
  .hud-pill.lives.beat i,.mistakes .mh.lost-beat,.mistakes .mh.lost-beat path,.popup .stars .star.on{animation:none}
}

/* ===== Level types (leveltypes.js / LEVEL_TYPES.md) — SKIN + framing only, static (calm ceiling) ===== */
.board.ltype-reversed{box-shadow:inset 0 0 0 3px rgba(143,111,208,.30),0 0 22px rgba(143,111,208,.16)}
.board.ltype-boss{box-shadow:inset 0 0 0 3px rgba(224,168,58,.34),0 0 22px rgba(224,168,58,.15)}
.board.ltype-zen{box-shadow:inset 0 0 0 3px rgba(110,195,175,.32),inset 0 0 42px rgba(150,220,200,.20)}
.board.ltype-overgrown{box-shadow:inset 0 0 0 4px rgba(60,120,60,.38),inset 0 0 42px rgba(48,108,52,.20)}
/* the type chip beside the level label */
.ltype-chip{display:inline-flex;align-items:center;gap:4px;margin-left:8px;padding:2px 9px;border-radius:999px;font-size:12px;font-weight:600;vertical-align:middle;line-height:1.4}
.ltype-chip i{font-size:13px}
.ltype-chip.ltype-reversed{background:rgba(143,111,208,.16);color:#6b52a8}
.ltype-chip.ltype-boss{background:rgba(224,168,58,.20);color:#946a12}
.ltype-chip.ltype-zen{background:rgba(120,180,150,.18);color:#3f7d63}
.ltype-chip.ltype-overgrown{background:rgba(74,120,70,.16);color:#3c6a38}
/* map node badge (top-left corner; mirrors .ev-badge on the right) */
.ltype-badge{position:absolute;top:-7px;left:-7px;width:24px;height:24px;border-radius:50%;border:2px solid #fff;display:flex;align-items:center;justify-content:center;pointer-events:none;box-shadow:0 1px 4px rgba(50,70,40,.22);z-index:3;font-size:13px;color:#fff}
.ltype-badge.ltype-boss{background:#e0a83a}
.ltype-badge.ltype-reversed{background:#8f6fd0}
.lvl-node.locked .ltype-badge{opacity:.72}
/* curse / boss intro card accents */
.curse-card h2{color:#6b52a8}
.boss-card h2{color:#946a12}
/* v2 curses (triangle/hexagon/fog) + standard variants (menagerie/rescue) */
.slot.void{visibility:hidden}   /* off-platform: no tile at all, so the remaining tiles read as the platform shape */
.board.ltype-triangle,.board.ltype-hexagon,.board.ltype-fog{box-shadow:inset 0 0 0 3px rgba(143,111,208,.28),0 0 22px rgba(143,111,208,.14)}
.board.ltype-menagerie{box-shadow:inset 0 0 0 3px rgba(240,140,175,.32),inset 0 0 42px rgba(250,190,120,.18)}
.board.ltype-rescue{box-shadow:inset 0 0 0 3px rgba(240,160,90,.34),inset 0 0 42px rgba(250,180,110,.20)}
.ltype-chip.ltype-triangle,.ltype-chip.ltype-hexagon,.ltype-chip.ltype-fog{background:rgba(143,111,208,.16);color:#6b52a8}
.ltype-chip.ltype-menagerie{background:rgba(90,160,210,.18);color:#2f6a94}
.ltype-chip.ltype-rescue{background:rgba(230,140,160,.18);color:#a84f66}
.ltype-badge.ltype-triangle,.ltype-badge.ltype-hexagon,.ltype-badge.ltype-fog{background:#8f6fd0}

/* ============ STAGE-0 DEEP TYPE IDENTITY (LEVELTYPE_OVERHAUL.md) ============
   Each level type is its own PLACE, not a colored ring on the meadow. ALL color derives from the type
   PAL via --lt-* vars set on .board-wrap (ui.js applyBoardAssets); scoped under body.tp-on so flag-OFF is
   byte-identical. Calm ceiling: textures are STATIC (no animation/flash), element opacity ≤ .17. The
   texture ::before sits behind the board (board z-index:2; translucent cells let it read through); slot
   treatments layer OVER the habitat _skinCSS via .slot::before (stays BELOW the critter). Reskin-safe:
   a Reef/Space swap changes only typePresentation — these selectors follow the tokens. */
/* token-driven ring — supersedes the legacy hardcoded per-type rings when the flag is on (same
   specificity, later in file → wins), so fog reads sky-grey not purple, boss/rescue read amber, etc. */
.board.lt-skinned{box-shadow:inset 0 0 0 3px var(--lt-ring,rgba(143,111,208,.3)),0 0 22px var(--lt-glow,rgba(143,111,208,.14))}
/* type-tinted play-area FRAME — the whole card reads as the type's color family. Respects a purchased
   playmat cosmetic (--cos-playmat wins; the type tint is only the fallback). */
.tp-on .board-wrap[data-ltbg]{background:var(--cos-playmat,linear-gradient(170deg,var(--lt-soft,rgba(255,255,255,.5)),rgba(255,255,255,.55)))}
/* per-type texture layer — one static ::before, bounded to the rounded frame (no overflow:hidden, so the
   run-off exit fling is never clipped) */
.tp-on .board-wrap[data-ltbg]::before{content:'';position:absolute;inset:0;z-index:1;pointer-events:none;border-radius:22px}
.tp-on .board-wrap[data-ltbg='den']::before{opacity:.16;background:radial-gradient(120% 74% at 50% 116%,var(--lt-b),transparent 66%),linear-gradient(180deg,var(--lt-k) 0%,transparent 34%)}                                    /* boss — grand golden hall */
.tp-on .board-wrap[data-ltbg='mirror']::before{opacity:.15;background:linear-gradient(125deg,var(--lt-l) 0%,transparent 44%,transparent 56%,var(--lt-b) 100%)}                                                                 /* reversed — mirror world sheen */
.tp-on .board-wrap[data-ltbg='shard']::before{opacity:.13;background:repeating-linear-gradient(60deg,var(--lt-b) 0 3px,transparent 3px 26px),repeating-linear-gradient(-60deg,var(--lt-l) 0 3px,transparent 3px 26px)}          /* triangle — angular facets */
.tp-on .board-wrap[data-ltbg='comb']::before{opacity:.14;background:radial-gradient(circle at 50% 50%,var(--lt-b) 0 12%,transparent 13%);background-size:26px 30px}                                                             /* hexagon — crystal-comb lattice */
.tp-on .board-wrap[data-ltbg='haze']::before{opacity:.17;background:linear-gradient(180deg,transparent,var(--lt-l) 30%,transparent 46%,var(--lt-l) 66%,transparent 84%)}                                                        /* fog — grey haze bands */
.tp-on .board-wrap[data-ltbg='ripple']::before{opacity:.14;background:repeating-radial-gradient(circle at 50% 46%,var(--lt-b) 0 2px,transparent 2px 22px)}                                                                      /* zen — dawn ripple rings */
.tp-on .board-wrap[data-ltbg='thicket']::before{opacity:.16;background:radial-gradient(70% 60% at 0% 0%,var(--lt-k),transparent 60%),radial-gradient(70% 60% at 100% 100%,var(--lt-k),transparent 60%),radial-gradient(60% 50% at 100% 0%,var(--lt-b),transparent 62%)}   /* overgrown — deep thicket corners */
.tp-on .board-wrap[data-ltbg='confetti']::before{opacity:.16;background:radial-gradient(circle at 18% 22%,var(--lt-b) 0 3px,transparent 4px),radial-gradient(circle at 72% 34%,var(--lt-l) 0 3px,transparent 4px),radial-gradient(circle at 42% 68%,var(--lt-b) 0 2.5px,transparent 4px),radial-gradient(circle at 84% 78%,var(--lt-l) 0 3px,transparent 4px),radial-gradient(circle at 30% 88%,var(--lt-b) 0 2px,transparent 4px)}   /* menagerie — festive specks */
.tp-on .board-wrap[data-ltbg='warmwash']::before{opacity:.15;background:linear-gradient(135deg,var(--lt-l) 0%,transparent 40%,transparent 60%,var(--lt-b) 100%)}                                                                /* rescue — warm sweep */
.tp-on .board-wrap[data-ltbg='rapids']::before{opacity:.16;background:repeating-linear-gradient(0deg,var(--lt-b) 0 2px,transparent 2px 15px),linear-gradient(90deg,var(--lt-l),transparent 60%)}                                  /* linear — horizontal speed lines */
/* per-type SLOT treatment — additive over the habitat skin; ::before keeps it below the critter */
.tp-on .board[data-ltslot] .slot::before{content:'';position:absolute;inset:0;border-radius:inherit;pointer-events:none;background:var(--lt-b);opacity:.07}
.tp-on .board[data-ltslot='warm'] .slot::before{opacity:.10}
.tp-on .board[data-ltslot='frost'] .slot::before{background:var(--lt-l);opacity:.14}
.tp-on .board[data-ltslot='leafy'] .slot::before{opacity:.09}
.tp-on .board[data-ltslot='runic'] .slot{box-shadow:inset 0 0 0 1px var(--lt-ring),inset 0 0 0 1px rgba(70,90,55,.06)}
.tp-on .board[data-ltslot='stone'] .slot{border-radius:5px}
.tp-on .board[data-ltslot='crystal'] .slot{border-radius:5px 13px 5px 13px}
.tp-on .board[data-ltslot='lily'] .slot{border-radius:17px}
/* motif watermark — big faint type silhouette behind the grid (static, opacity ≤ .07 → calm) */
.lt-watermark{position:absolute;inset:0;z-index:1;display:grid;place-items:center;pointer-events:none}
.tp-on .lt-watermark i{font-size:clamp(120px,42vw,210px);line-height:1;color:var(--lt-b,#8FBF52);opacity:.11}

/* ============ TYPE PRESENTATION (TYPE_PRESENTATION.md) — token-driven via --lt-b/l/k ============ */
/* one-time in-view wiggle on special map nodes (§4.6) + filled trophy badge */
.ltype-badge.trophy{border-color:#fff;box-shadow:0 1px 5px rgba(50,70,40,.3)}
.lvl-node.ltype-hero{transform:scale(1.12)}
.lvl-node.ltype-hero .ltype-badge{width:28px;height:28px;font-size:15px;top:-9px;left:-9px}
@keyframes ltWiggle{0%{transform:scale(1) rotate(0)}30%{transform:scale(1.06) rotate(-5deg)}60%{transform:scale(1.03) rotate(4deg)}100%{transform:scale(1) rotate(0)}}
.ltype-badge.wiggle{animation:ltWiggle .55s ease-out}
body.calm .ltype-badge.wiggle,body.lowfx .ltype-badge.wiggle{animation:none}

/* iris hole-edge tint (§3.1) — a faint colored ring following the growing hole */
.iris.tinted::after{content:'';position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(circle at 50% 50%, transparent var(--ir,0%), var(--iris-tint,transparent) calc(var(--ir,0%) + 1.4%), transparent calc(var(--ir,0%) + 4%));opacity:.55}

/* the in-world intro overlay */
.type-intro{position:fixed;inset:0;z-index:107;pointer-events:none;overflow:hidden;display:flex;align-items:center;justify-content:center;flex-direction:column}
.lt-wash{position:absolute;inset:-12%;opacity:0;background:linear-gradient(135deg,transparent 0%,var(--lt-l) 46%,transparent 72%)}
.type-intro.go .lt-wash{animation:ltWash .25s ease-out forwards}
@keyframes ltWash{0%{opacity:0;transform:translate(-30%,-30%)}45%{opacity:.28}100%{opacity:0;transform:translate(18%,18%)}}
.lt-motif{position:absolute;width:40%;max-width:170px;opacity:0}
.type-intro.go .lt-motif{animation:ltMotif .55s cubic-bezier(.3,.7,.3,1) .1s forwards}
@keyframes ltMotif{0%{opacity:0;transform:translate(-22%,-16%) scale(.9)}42%{opacity:.92;transform:translate(0,0) scale(1.04)}100%{opacity:0;transform:translate(18%,14%) scale(1.06)}}
.lt-banner{position:relative;display:inline-flex;align-items:center;gap:8px;padding:8px 18px;border-radius:999px;background:var(--lt-l);color:var(--lt-k);font-weight:700;font-size:20px;box-shadow:0 3px 12px rgba(40,40,60,.18);opacity:0}
.lt-banner i{font-size:22px;color:var(--lt-b)}
.type-intro.go .lt-banner{animation:ltPill .4s ease-out .25s forwards}
.type-intro.ti-static .lt-banner{opacity:1}
@keyframes ltPill{0%{opacity:0;transform:translateY(12px)}100%{opacity:1;transform:translateY(0)}}
.lt-cap{margin-top:10px;color:var(--lt-k);background:rgba(255,255,255,.85);padding:4px 13px;border-radius:12px;font-size:15px;font-weight:600;opacity:0}
.type-intro.teach.go .lt-cap,.type-intro.ti-static .lt-cap{animation:ltFadeIn .4s ease-out .9s forwards}
.type-intro.ti-static .lt-cap{animation-delay:.15s}
@keyframes ltFadeIn{to{opacity:1}}
.lt-aff{position:absolute;bottom:11%;display:flex;align-items:center;gap:1px;color:var(--lt-k);font-size:22px;opacity:0}
.type-intro.ready .lt-aff{animation:ltAff 1.4s ease-in-out infinite}
@keyframes ltAff{0%,100%{transform:translateX(0);opacity:.55}50%{transform:translateX(5px);opacity:1}}
.type-intro.ti-static{animation:ltStatic 1.2s ease-in-out forwards}
@keyframes ltStatic{0%{opacity:0}16%{opacity:1}80%{opacity:1}100%{opacity:0}}
.type-intro.out{animation:ltFadeOut .25s ease-in forwards}
@keyframes ltFadeOut{to{opacity:0}}

/* demo primitives (§3.2) */
.lt-ghost{position:fixed;opacity:.5;z-index:107;pointer-events:none;transition:transform .36s ease-in,opacity .36s ease-in}
/* D1 teach caption — pinned under the board during a first-of-type demo (transform/opacity only) */
.lt-democap{position:fixed;transform:translate(-50%,4px);z-index:108;pointer-events:none;max-width:78vw;text-align:center;
  padding:6px 14px;border-radius:14px;background:rgba(40,40,60,.86);color:#fff;font-weight:700;font-size:14px;
  box-shadow:0 3px 12px rgba(20,20,35,.28);opacity:0;transition:opacity .32s ease,transform .32s cubic-bezier(.2,1.05,.4,1)}
.lt-democap.in{opacity:1;transform:translate(-50%,0)}
@media (prefers-reduced-motion:reduce){.lt-democap{transition:none}}
body.calm .lt-democap{transition:none}
.lt-peek{position:fixed;transform:translate(-50%,-50%);z-index:107;pointer-events:none;font-size:26px;opacity:0;animation:ltPeek 1s ease-in-out forwards}
@keyframes ltPeek{0%{opacity:0;transform:translate(-50%,-50%) scale(.6)}30%{opacity:1;transform:translate(-50%,-50%) scale(1)}70%{opacity:1;transform:translate(-50%,-50%) scale(1)}100%{opacity:0;transform:translate(-50%,-50%) scale(.7)}}
.critter.lt-stomp{animation:ltStomp .6s ease-out}
@keyframes ltStomp{0%{transform:scale(1)}30%{transform:scale(1.03) translateY(-2px)}62%{transform:scale(.99)}100%{transform:scale(1)}}
.lt-trace{position:fixed;border:3px solid;border-radius:16px;pointer-events:none;z-index:107;opacity:0;box-sizing:border-box}
.lt-trace.go{animation:ltTrace .8s ease-out forwards}
@keyframes ltTrace{0%{opacity:0;transform:scale(1.04)}30%{opacity:.85}100%{opacity:0;transform:scale(1)}}
/* P5-A: reversed CURSE cast — a wand sweeps the board diagonal; each critter card-flips (scaleX shrink → 0%
   positional impulse) as its arrow reverses in the wand's wake. Calm/reduced-motion skip both (wandCurse flips
   instantly). Wand peak scale 1.04 (≤1.06); one diagonal pass, no strobe. */
.critter.lt-flip{animation:ltFlip 250ms cubic-bezier(.5,0,.5,1)}
@keyframes ltFlip{0%{transform:scaleX(1)}48%{transform:scaleX(.06)}52%{transform:scaleX(.06)}100%{transform:scaleX(1)}}
.lt-wand{position:fixed;pointer-events:none;z-index:108;opacity:0}
.lt-wand.go{animation:ltWand 900ms ease forwards}
@keyframes ltWand{0%{opacity:0;transform:translate(-16%,-16%) rotate(-6deg) scale(.9)}30%{opacity:1}100%{opacity:0;transform:translate(16%,16%) rotate(-6deg) scale(1.04)}}
@media (prefers-reduced-motion:reduce){.critter.lt-flip{animation:none}.lt-wand.go{animation:none}}
body.calm .critter.lt-flip,body.calm .lt-wand.go{animation:none}

/* type-win beats (§4.5) — curse-break wash drain (cursed); boss handled by sparks + hop in JS */
.type-win.drain{position:fixed;inset:0;z-index:106;pointer-events:none;background:linear-gradient(0deg,transparent 0%,var(--lt-l) 60%,var(--lt-b) 100%);opacity:0}
.type-win.drain.go{animation:ltDrain .55s ease-in forwards}
@keyframes ltDrain{0%{opacity:.32;transform:translateY(0)}100%{opacity:0;transform:translateY(-24%)}}

/* living-board ambient layer (§4) — all transform/opacity, ≤2 concurrent enforced in JS */
.amb{position:absolute;pointer-events:none;z-index:2}
.amb-leaf{top:-8%;width:13px;height:13px;border-radius:0 60% 0 60%;background:var(--lt-l);opacity:.5;animation:ambFall 11s linear forwards}
.amb-petal{top:-8%;width:12px;height:16px;border-radius:60% 0 60% 0;background:var(--lt-l);opacity:.42;animation:ambFall 13s linear forwards}
@keyframes ambFall{0%{transform:translateY(-14px) rotate(0);opacity:0}12%{opacity:.5}90%{opacity:.42}100%{transform:translateY(360px) rotate(210deg);opacity:0}}
.amb-mote2{bottom:-6%;width:8px;height:8px;border-radius:50%;background:var(--lt-b);opacity:.5;box-shadow:0 0 6px var(--lt-l);animation:ambRise 9s ease-in forwards}
@keyframes ambRise{0%{transform:translateY(0);opacity:0}16%{opacity:.6}100%{transform:translateY(-330px);opacity:0}}
.amb-sparkle{top:20%;width:7px;height:7px;background:var(--lt-b);opacity:.5;transform:rotate(45deg);animation:ambDrift 9s ease-in-out forwards}
@keyframes ambDrift{0%{opacity:0;transform:translate(0,0) rotate(45deg)}22%{opacity:.6}100%{opacity:0;transform:translate(26px,42px) rotate(45deg)}}
.amb-mist{top:32%;left:-42%!important;width:60%;height:22px;border-radius:14px;background:var(--lt-l);opacity:0;filter:blur(3px);animation:ambMist 9s linear forwards}
@keyframes ambMist{0%{transform:translateX(0);opacity:0}25%{opacity:.28}100%{transform:translateX(190%);opacity:0}}
/* persistent-class ambients: shape edge-glow breathe + overgrown corner vine (opacity-only) */
.amb-edgeglow{animation:ambEdge 6s ease-in-out infinite}
@keyframes ambEdge{0%,100%{box-shadow:inset 0 0 0 3px var(--lt-glow-lo,rgba(143,111,208,.22)),0 0 8px var(--lt-glow-lo,rgba(143,111,208,.22))}50%{box-shadow:inset 0 0 0 3px var(--lt-glow-hi,rgba(143,111,208,.4)),0 0 18px var(--lt-glow-hi,rgba(143,111,208,.32))}}
.amb-vine::before{content:'';position:absolute;left:-2px;bottom:-2px;width:40px;height:40px;border-radius:0 60% 0 60%;background:var(--lt-l,rgba(120,180,90,.5));opacity:.4;pointer-events:none;transform-origin:bottom left;animation:ambVine 10s ease-in-out infinite;z-index:1}
@keyframes ambVine{0%,100%{transform:rotate(0)}50%{transform:rotate(4deg)}}

/* boss "breathing" idle — big piece only, released the instant it exits so the fling still works */
.board.ltype-boss .critter.big:not(.exit-R):not(.exit-L):not(.exit-U):not(.exit-D){animation:bossBreathe 4s ease-in-out infinite}
@keyframes bossBreathe{0%,100%{transform:scale(1)}50%{transform:scale(1.03)}}
body.calm .board.ltype-boss .critter.big,body.lowfx .board.ltype-boss .critter.big{animation:none}

/* reversed rescue afterglow — a brief purple halo on the exiting critter (opacity-only) */
.critter.lt-afterglow{box-shadow:0 0 12px 3px var(--lt-b,rgba(143,111,208,.6))}

@media (prefers-reduced-motion:reduce){
  .type-intro.go .lt-wash,.type-intro.go .lt-motif,.type-intro.ready .lt-aff,.lt-peek,.lt-trace.go,
  .amb,.amb-edgeglow,.amb-vine::before,.board.ltype-boss .critter.big,.critter.lt-stomp,.type-win.drain.go{animation:none}
  .ltype-badge.wiggle{animation:none}
}
/* Foggy curse: a hidden critter shows a calm "?" until its path is clear (static — calm ceiling) */
.critter.fogged::after{content:'?';position:absolute;inset:2px;background:rgba(206,212,224,.92);border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:20px;font-weight:700;color:#7b8598;z-index:6}
/* Friends on the map: faces of your friends who cleared a level (added/invited only) — tap to visit their map */
.lvl-friends{position:absolute;bottom:-13px;left:50%;transform:translateX(-50%);display:flex;align-items:center;z-index:4}
.lf-face{width:22px;height:22px;border-radius:50%;border:2px solid #fff;background:#eef3ea;overflow:hidden;padding:0;display:block;box-shadow:0 1px 3px rgba(50,70,40,.28);margin-left:-6px;cursor:pointer;transition:transform .12s ease}
.lf-face:first-child{margin-left:0}
.lf-face:hover{transform:translateY(-2px)}
.lf-face .lf-av,.lf-face .lf-av svg{display:block;width:100%;height:100%}
.lf-more{font-size:10px;font-weight:700;color:#6b7a63;background:#fff;border-radius:9px;padding:1px 5px;margin-left:3px;box-shadow:0 1px 3px rgba(50,70,40,.2)}

/* ============================================================================
   P8 — MENU-SCENE JUICE (RELEASE_REVIEW_PLAN §8). Within the CALM ceiling: scale
   ≤1.06 · small impulse · no flash >3Hz · one rise+decay (except I8's slow ambient
   "breathe", which JUICE.md §4 explicitly permits). Every beat is prefs()-gated in
   JS or guarded here by body.calm / prefers-reduced-motion. "Static" items = colour only.
   ============================================================================ */

/* Home hero (R3 mood · R5 CTA · I4 sign · I6 hold-to-pet) */
.home-hero{display:flex;align-items:center;gap:12px}
.hh-pet{flex:0 0 auto;width:58px;height:58px;position:relative;cursor:pointer;-webkit-user-select:none;user-select:none;touch-action:manipulation}
.hh-pet svg{display:block;width:100%;height:100%}
.hh-copy{min-width:0;flex:1}
.hh-pet.wave svg{transform-origin:50% 82%;animation:cubWaveBye .9s ease-in-out}   /* R3 wave — reuses the calm cub-wave keyframe */
.hh-pet.pet-bounce{animation:hhPet .34s cubic-bezier(.3,1.4,.4,1)}                /* I6 tap bounce (scale ≤1.05) */
@keyframes hhPet{0%,100%{transform:scale(1)}40%{transform:scale(1.05)}}
.pet-heart{position:absolute;left:50%;top:4px;transform:translateX(-50%);font-size:16px;color:#e36a86;pointer-events:none;z-index:3}
.hh-cta{display:inline-flex;align-items:center;gap:6px;padding:6px 13px;border-radius:999px;border:none;font-weight:800;font-size:13px;cursor:pointer;color:#3a2a12;background:var(--amber,#f2b134);box-shadow:0 2px 7px rgba(180,120,40,.25)}
.hh-cta i{font-size:15px}
.hh-cta-play{background:#5fae5f;color:#fff;box-shadow:0 2px 7px rgba(60,130,60,.28)}
.hh-cta-claim{background:#e6a32e;color:#4a3300}
.hh-cta:active{transform:scale(.96)}
@media (prefers-reduced-motion:reduce){.hh-pet.wave svg,.hh-pet.pet-bounce{animation:none}}
body.calm .hh-pet.wave svg{animation:none}
/* I4 welcome-back sign — static; motion lives only in the fade-in */
.hh-welcome{position:absolute;left:50%;bottom:100%;transform:translate(-50%,4px);white-space:nowrap;background:linear-gradient(180deg,rgba(255,248,236,.98),rgba(255,236,214,.98));color:#7a4a1e;font-weight:800;font-size:12px;padding:4px 10px;border-radius:999px;box-shadow:0 4px 12px rgba(180,120,60,.24),inset 0 0 0 1px rgba(220,150,90,.35);opacity:0;pointer-events:none;z-index:4;transition:opacity .3s ease,transform .3s cubic-bezier(.2,1.2,.3,1)}
.hh-welcome.in{opacity:1;transform:translate(-50%,-4px)}
.hh-welcome.out{opacity:0}
.hh-welcome i{font-size:13px;color:#e6a32e}
@media (prefers-reduced-motion:reduce){.hh-welcome{transition:none}}

/* I5 time-of-day header tint — a static var swap on .today-head */
.today-head[data-tod]{border-radius:14px;padding:6px 8px 2px;transition:background .6s ease}
.today-head[data-tod="dawn"]{background:linear-gradient(180deg,rgba(241,200,154,.20),transparent)}
.today-head[data-tod="day"]{background:linear-gradient(180deg,rgba(143,199,240,.13),transparent)}
.today-head[data-tod="dusk"]{background:linear-gradient(180deg,rgba(176,106,146,.18),transparent)}
.today-head[data-tod="night"]{background:linear-gradient(180deg,rgba(70,80,126,.20),transparent)}

/* I1 critter peek-in — motion only, below popups (#overlay z=50) */
.critter-peek{position:fixed;bottom:66px;width:70px;height:70px;z-index:30;pointer-events:none;opacity:.96;transition:transform .5s cubic-bezier(.2,.9,.3,1)}
.critter-peek svg{display:block;width:100%;height:100%;filter:drop-shadow(0 3px 6px rgba(50,70,40,.25))}
.critter-peek.left{left:0;transform:translateX(-105%)}
.critter-peek.right{right:0;transform:translateX(105%)}
.critter-peek.left.in{transform:translateX(-30%)}
.critter-peek.right.in{transform:translateX(30%)}
.critter-peek.left.out{transform:translateX(-105%)}
.critter-peek.right.out{transform:translateX(105%)}
@media (prefers-reduced-motion:reduce){.critter-peek{display:none}}

/* I2 paw-trail nav — opacity only, below popups */
.paw-nav{position:fixed;transform:translate(-50%,-50%);z-index:30;pointer-events:none;color:var(--amber,#f2b134);opacity:0;font-size:20px}
.paw-nav i{display:block}
.paw-nav.go{animation:pawFade .4s ease}
@keyframes pawFade{0%{opacity:0}40%{opacity:.5}100%{opacity:0}}
@media (prefers-reduced-motion:reduce){.paw-nav{display:none}}

/* I3 shooting star — one element, opacity + translate; tap = one sparkle (JS) */
.shooting-star{position:absolute;width:3px;height:3px;border-radius:50%;background:#fff;box-shadow:0 0 6px 1px rgba(255,255,255,.9);opacity:0;z-index:3;cursor:pointer}
.shooting-star::after{content:'';position:absolute;top:50%;width:32px;height:1.5px;transform:translateY(-50%)}
.shooting-star.from-right{right:8%}
.shooting-star.from-right::after{right:3px;background:linear-gradient(90deg,rgba(255,255,255,0),rgba(255,255,255,.8))}
.shooting-star.from-left{left:8%}
.shooting-star.from-left::after{left:3px;background:linear-gradient(90deg,rgba(255,255,255,.8),rgba(255,255,255,0))}
.shooting-star.from-right.go{animation:shootL 1.3s ease-in forwards}
.shooting-star.from-left.go{animation:shootR 1.3s ease-in forwards}
@keyframes shootL{0%{opacity:0;transform:translate(0,0)}14%{opacity:.95}100%{opacity:0;transform:translate(-240px,110px)}}
@keyframes shootR{0%{opacity:0;transform:translate(0,0)}14%{opacity:.95}100%{opacity:0;transform:translate(240px,110px)}}
@media (prefers-reduced-motion:reduce){.shooting-star{display:none}}

/* I9 seasonal map dressing — static */
.season-decor{position:absolute;transform:translate(-50%,-50%);font-size:26px;z-index:1;pointer-events:none;filter:drop-shadow(0 2px 3px rgba(50,70,40,.28))}

/* R8 level-node lock-dissolve (opacity + scale-down, one decay) */
.node-lock-dissolve{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:#8a94a6;font-size:20px;pointer-events:none;z-index:3;animation:lockDissolve .6s ease-out forwards}
@keyframes lockDissolve{0%{opacity:.9;transform:scale(1)}100%{opacity:0;transform:scale(.4)}}
@media (prefers-reduced-motion:reduce){.node-lock-dissolve{display:none}}

/* R2 shop-purchase card confirm pulse (soft glow, no scale/flash) */
.shop-card.buy-pulse{animation:buyPulse .5s ease}
@keyframes buyPulse{0%,100%{box-shadow:0 2px 6px rgba(50,70,40,.10)}45%{box-shadow:0 0 0 3px rgba(95,174,95,.5),0 4px 14px rgba(95,174,95,.28)}}
@media (prefers-reduced-motion:reduce){.shop-card.buy-pulse{animation:none}}

/* R10 park Care-level-up dance — reuses the calm win-card cheer keyframe */
.rpk-dance{position:absolute;left:10px;bottom:8px;width:42px;height:42px;z-index:2;transform-origin:50% 82%;pointer-events:auto;cursor:pointer;animation:rpkDanceIn .3s cubic-bezier(.2,1.4,.4,1) both,wcCheer 1.05s cubic-bezier(.3,1.2,.4,1) .3s 2}
.rpk-dance svg{display:block;width:100%;height:100%;filter:drop-shadow(0 2px 4px rgba(180,90,110,.28))}
@keyframes rpkDanceIn{from{opacity:0;transform:translateY(8px) scale(.8)}to{opacity:1;transform:translateY(0) scale(1)}}
@media (prefers-reduced-motion:reduce){.rpk-dance{animation:none}}
body.calm .rpk-dance{animation:none}

/* I7 streak flame — colour warms by tier (static) */
.tc-ico.flame.st0{background:#a9b0a2}
.tc-ico.flame.st1{background:#f0a83a}
.tc-ico.flame.st2{background:#ef7a3a}
.tc-ico.flame.st3{background:#e8563a}

/* I8 breathing lives pill when FULL — ambient "board breathe" (JUICE.md §4); ~0.25Hz ≪ 3Hz, soft glow,
   gated off calm/lowfx/reduced-motion. (Box-shadow glow rather than an opacity pseudo because
   .hud-pill.lives has overflow:hidden, which would clip a pseudo-element glow.) */
.hud-pill.lives.full{animation:livesBreathe 4s ease-in-out infinite}
@keyframes livesBreathe{0%,100%{box-shadow:0 2px 6px rgba(50,70,40,.08),inset 0 0 0 1.5px rgba(43,163,155,.16)}50%{box-shadow:0 2px 11px rgba(43,163,155,.34),inset 0 0 0 1.5px rgba(43,163,155,.34)}}
body.calm .hud-pill.lives.full,body.lowfx .hud-pill.lives.full{animation:none}
@media (prefers-reduced-motion:reduce){.hud-pill.lives.full{animation:none}}

/* I10 badge-dot settle — appears via a soft drop+fade instead of popping in */
.nav-btn.has-dot::after,.nav-btn .nav-count,.hud-pill.link .link-count{animation:dotSettle .24s ease}
@keyframes dotSettle{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}
@media (prefers-reduced-motion:reduce){.nav-btn.has-dot::after,.nav-btn .nav-count,.hud-pill.link .link-count{animation:none}}

/* R6 docked event strip — one quiet row: ≤3 live-event icons + countdown chips */
.event-strip{display:flex;align-items:center;gap:8px;padding:8px 12px;margin:0 0 12px;border-radius:14px;background:var(--card,#fffdf7);box-shadow:0 2px 6px rgba(50,70,40,.08),inset 0 0 0 1.5px rgba(230,163,46,.14)}
.event-strip .es-label{flex:0 0 auto;font-weight:800;font-size:13px;color:#8a6a1e;display:inline-flex;align-items:center;gap:5px;white-space:nowrap}
.event-strip .es-label>i{font-size:16px;color:#e6a32e}
.es-chips{flex:1;min-width:0;display:flex;align-items:center;gap:6px;overflow:hidden}
.es-chip{flex:0 0 auto;display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border-radius:999px;border:none;background:rgba(124,138,115,.10);cursor:pointer;font-size:11px;font-weight:700;color:#5a6a52}
.es-chip.ready{background:rgba(230,163,46,.18);color:#8a6a1e;box-shadow:inset 0 0 0 1.5px rgba(230,163,46,.4)}
.es-chip .es-emj{font-size:15px;line-height:1}
.es-chip .es-cd{display:inline-flex;align-items:center;gap:2px;font-variant-numeric:tabular-nums}
.es-chip .es-cd i{font-size:11px}
.es-chip:active{transform:scale(.95)}
.es-empty{font-size:12px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.es-more{flex:0 0 auto;font-size:18px}

/* R4 quest replay — the just-completed quest checks off once, then collapses */
.quest-replay{display:flex;align-items:center;gap:8px;margin:8px 0 2px;padding:8px 10px;border-radius:12px;background:rgba(95,174,95,.12);max-height:0;opacity:0;overflow:hidden;transition:max-height .3s ease,opacity .3s ease,padding .3s ease,margin .3s ease}
.quest-replay.go{max-height:48px;opacity:1}
.quest-replay.out{max-height:0;opacity:0;padding-top:0;padding-bottom:0;margin:0}
.qr-check{flex:0 0 auto;width:22px;height:22px;border-radius:50%;background:#5fae5f;color:#fff;display:flex;align-items:center;justify-content:center}
.qr-check i{font-size:14px;animation:qrCheck .5s cubic-bezier(.2,1.4,.4,1)}
.qr-txt{font-weight:700;font-size:13px;color:#3f6b3f}
@keyframes qrCheck{0%{transform:scale(0);opacity:0}60%{transform:scale(1.15)}100%{transform:scale(1);opacity:1}}
@media (prefers-reduced-motion:reduce){.quest-replay{transition:none}.qr-check i{animation:none}}

/* ===== v3 core-loop (CORE_LOOP_PLAN.md): telegraph badges · Rest Stop · Willow's Basket · Rescue Road ===== */
.ltype-badge.ltype-hard{background:#e8a33a}
.lvl-node.wall-node .ltype-badge.ltype-hard{background:#d96f3f}
.lvl-node.hard-node{box-shadow:0 0 0 3px rgba(232,163,58,.40),0 2px 8px rgba(50,70,40,.18)}
.up-ahead .rr-mini{display:flex;align-items:center;gap:6px;color:#b07c2e;font-weight:700}
.rr-pips{display:flex;gap:3px}
.rr-pip{width:8px;height:8px;border-radius:50%;background:#d8dbd0;display:inline-block}
.rr-pip.on{background:#e8a33a}
.hard-chip{display:flex;align-items:center;justify-content:center;gap:6px;margin:0 auto 8px;padding:4px 12px;border-radius:999px;background:#f6e3c0;color:#8a5a17;font-weight:800;font-size:13px;width:max-content}
.hard-chip.wall{background:#f3d3c2;color:#8a3d17}
.pack-basket{display:flex;align-items:center;gap:10px;background:#eef4e4;border:1px solid #d5e3c2;border-radius:12px;padding:8px 12px;margin:0 0 10px;text-align:left}
.pack-basket .pb-ic{font-size:20px}
.pack-basket .pb-tx{flex:1;display:flex;flex-direction:column;line-height:1.25}
.pack-basket .pb-sub{font-size:12px;color:#6b7760}
.pack-basket .pb-gifts{display:flex;gap:6px}
.pack-basket .pb-gift{display:flex;align-items:center;gap:2px;background:#fff;border-radius:8px;padding:3px 7px;font-size:12px;font-weight:800;color:#3f7d4e}
.ls-streak{display:flex;align-items:center;gap:6px;justify-content:center;font-size:13px;color:#a3721f;margin:6px 0 0}
.rest-preview{display:flex;align-items:center;justify-content:center;gap:10px;margin:10px 0 2px;font-weight:700;color:#556052}
.rest-preview .rp-lbl{font-size:12px;color:#8a948a;font-weight:600;text-transform:uppercase;letter-spacing:.04em}
.rest-preview .rp-ct{display:flex;align-items:center;gap:3px;background:#eef1e6;border-radius:8px;padding:3px 8px;font-size:13px}
.next-tease{display:inline-flex;align-items:center;gap:4px;margin-left:8px;padding:2px 8px;border-radius:999px;background:rgba(255,255,255,.25);font-size:12px;font-weight:800}
.next-tease.hard{background:rgba(232,163,58,.30)}

/* ── Watchful Owl (class A gaze texture — engine._applyOwls / ui.js owlWatches) ──
   Calm contract: the cone is an opacity-only breathe at ≈0.4Hz (far under the 3Hz flash ceiling),
   the hoot peaks at scale 1.06 (the ceiling), and everything is pointer-events:none decoration. */
.slot.wall.owl-slot{cursor:pointer}
.slot.wall.owl-slot svg{inset:8%;opacity:1}
.owl-cone{position:absolute;z-index:1;pointer-events:none;border-radius:10px;
  background:linear-gradient(180deg,rgba(240,196,90,.30),rgba(240,196,90,.10));
  animation:owlBreathe 2.4s ease-in-out infinite;
  transition:left .45s ease,top .45s ease,width .45s ease,height .45s ease}
.owl-cone.dir-U{background:linear-gradient(0deg,rgba(240,196,90,.30),rgba(240,196,90,.10))}
.owl-cone.dir-L{background:linear-gradient(270deg,rgba(240,196,90,.30),rgba(240,196,90,.10))}
.owl-cone.dir-R{background:linear-gradient(90deg,rgba(240,196,90,.30),rgba(240,196,90,.10))}
@keyframes owlBreathe{0%,100%{opacity:.72}50%{opacity:1}}
.owl-cone.owl-pulse{animation:owlPulse .5s ease}
@keyframes owlPulse{35%{opacity:1;filter:brightness(1.12)}}
body.lowfx .owl-cone{animation:none}
@media (prefers-reduced-motion:reduce){.owl-cone{animation:none;transition:none}}
.slot.wall.owl-slot.tool-target{outline:3px solid #ffd76a;outline-offset:-3px;border-radius:12px;
  box-shadow:0 0 12px rgba(255,205,90,.5)}
.owl-hoot{animation:owlHoot .36s ease}
@keyframes owlHoot{35%{transform:scale(1.06) rotate(-3deg)}70%{transform:scale(1.02) rotate(2deg)}}

/* ═══════════════════════════════════════════════════════════════════════════════
   LOW-FX GAME-FLOW PASS (2026-07-19) — measured on a Nokia 7.1 / Adreno 509.
   Ground truth from `dumpsys gfxinfo` on the shipped WebView build (not a desktop
   proxy): hub scrolling is fine (1% janky, p90 11ms), but in-level play ran 91%
   janky with a p50 of 48ms, and level entry hit a p99 of 550ms. Cost is paint and
   composite, not JS — screen rebuilds measured 0-7ms.

   Everything here is DECORATION ONLY under body.lowfx (the device tier: native
   WebView or <=4GB RAM, i.e. every Capacitor/store player). No gameplay signal, no
   affordance and no readability is removed — where a cue was carried by motion, a
   static equivalent replaces it rather than leaving nothing behind.

   These live at the END of the sheet deliberately: several gates tie on specificity
   with their source rule, so source order is what makes them win. Do not relocate
   them into the earlier lowfx block without re-checking each one.
   ═══════════════════════════════════════════════════════════════════════════════ */

/* --- in-play, per-frame or per-tap (the frames the player actually feels) --- */

/* charged booster: an infinite scale loop in the dock for most of every stage (with
   the Fox companion, from frame 0). The ring + recoloured count still read "ready". */
body.calm .tool.booster.charged,body.lowfx .tool.booster.charged{animation:none}

/* tap-ring: box-shadow SPREAD keyframes on every single rescue — spread is paint,
   never composited, on the hottest path in the game. With the animation off the box-shadow
   rests at its 0% state (0 0 0 0) and paints nothing visible, so removing the pseudo-element
   outright is visually identical AND one fewer paint box per rescue. Measured on the Nokia 7.1
   as part of the tap-overlay set below. */
body.lowfx .slot.tap-ring::after{animation:none;display:none}

/* Tap-time decorative overlays, gated on the DEVICE tier only.
   On-device A/B (Adreno 509, 3 rescues per condition, identical fresh 24-critter board):
       overlays on : p90 85ms · p95 150ms · p99 300ms
       overlays off: p90 73ms · p95  93ms · p99 150ms
   The p99 halves. These are the discrete tail spikes — the resting board is free (4 frames/12s)
   and the mounted scene composites at 5ms/frame, so everything felt in play is spawned by a tap.
   warm-trail is a gradient ::after painted behind a moving element; zpuff is a particle. Both are
   pure decoration, so the low tier loses nothing legible.
   NOT gated here, deliberately: .float-coin (that is REWARD FEEDBACK — the reward has to visibly
   land, per the reward-delivery rule) and the critter run animation itself (that is the mechanic). */
body.lowfx .critter.warm-trail::after{display:none}
body.lowfx.in-game .zpuff{display:none}
/* NB: .slot.blocker-flash is deliberately NOT gated. It is the only feedback for a
   mis-tap — a failure signal, not decoration — and it is a one-shot on an error path. */

/* overgrown boards: an infinite rotate mounted for the whole level. */
body.lowfx .amb-vine::before,body.calm .amb-vine::before{animation:none}

/* --- bracketing play (thermal load carried INTO the board) --- */

/* stage intro: a full-viewport backdrop-filter over a viewport-sized animating SVG,
   ~2.6s before every staged entry. The raised alpha keeps text contrast identical —
   nobody perceives a 1.5px blur at that opacity. */
body.lowfx .stage-intro{-webkit-backdrop-filter:none;backdrop-filter:none;background:rgba(58,90,55,.62)}
body.lowfx .si-fill{animation:none;width:100%}
body.lowfx .si-count.pop{animation:none}
body.lowfx .si-card .ltype-banner{animation:none}   /* source is (0,2,0): a bare body.lowfx .ltype-banner would win only on a tiebreak */

/* time-pot courier: a SECOND ungated full-viewport backdrop-filter, twice per level. */
body.lowfx .tpot-dim{-webkit-backdrop-filter:none!important;backdrop-filter:none!important;background:rgba(46,74,52,.42)}
body.lowfx .tpot .tp-jar{filter:none}
body.lowfx .tp-drop{box-shadow:none}   /* .tp-drop mounts on document.body, so the existing `body.lowfx .tpot *` descendant rule never reaches it */
body.lowfx .tpot{animation-duration:.18s!important}   /* the existing `body.lowfx .tpot *` uses a DESCENDANT combinator and so misses .tpot's own hop-in/zip-off */
/* Do NOT write animation:none on .tpot — it is parked off-screen at translateY(24vh + 110%)
   and tpHopIn/tpZipOff are `forwards`; killing the animation strands the jar off-screen. */

/* pause: an animated filter:blur across the whole board + dock on every pause open. */
body.lowfx.paused .pz-stage,body.lowfx.paused .pz-dock,
body.calm.paused .pz-stage,body.calm.paused .pz-dock{filter:none!important;transition:none!important;opacity:.55}

/* --- post-play and the resume screen (battery + thermal, not felt in-frame) --- */

/* win card: up to FIVE infinite dances on the most-shown card in the game.
   The !important is LOAD-BEARING — source .win-critters.st3.go .wc-v0 is (0,4,0) and
   this gate is (0,3,1), so it loses on class count without it. Do not "clean it up". */
body.lowfx .win-critters .wc{animation:none!important}

/* "you are here" map ring: the existing gate at the top of this sheet kills the
   animation, but the base rule sets opacity:0 and the keyframe is what makes the ring
   visible — so on the device tier the affordance vanished entirely while every desktop
   tester still saw it. Restore it statically. */
body.lowfx .lvl-node.current::after{opacity:.55}

/* first-special coach: infinite box-shadow spread, ~6.5s during play. The static ring
   matters — a dozing critter carrying .coach-glow otherwise gets no ring at all. */
body.lowfx .coach-glow{animation:none;box-shadow:0 0 0 3px rgba(154,143,224,.85)}

/* --- phantom compositor layers promoted for animations lowfx already killed --- */
body.lowfx .sky-sun,body.lowfx .sky-moon,body.lowfx .sky-cloud,
body.lowfx .sky-bird,body.lowfx .map-hills{will-change:auto}
body.calm .ev-fx,body.lowfx .ev-fx{animation:none;opacity:.92;transform:none;will-change:auto}
body.calm .ev-fx-sky,body.lowfx .ev-fx-sky{animation:none;transform:none;will-change:auto}

/* Combo rail: the warmth glow is a box-shadow whose BLUR RADIUS is driven by --warm, so every
   combo step repaints it over a .3s transition. Keep the colour shift (it carries the warmth
   read) but drop the blur ramp on the device tier — a blurred shadow is paint, never composited. */
body.lowfx .combo-rail.on .combo-bar{box-shadow:inset 0 0 0 1px rgba(70,90,55,.06),0 0 3px rgba(242,177,52,calc(.12 + var(--warm,0)*.42));transition:none}
