/* ── Custom properties ─────────────────────────────────────────────────── */
:root {
  --bark:       #3c2018;
  --bark-mid:   #8b5e4a;
  --bark-light: #c4956a;
  --cream:      #fdf5ec;
  --cream-90:   rgba(253, 245, 236, 0.90);
  --cream-75:   rgba(253, 245, 236, 0.75);
  --line:       rgba(60, 32, 24, 0.12);
  --rose:       #ef6f8d;
  --teal:       #158f92;
  --mint:       #d9f3ea;
  --gold:       #f0bb4f;
  --danger:     #dc4c64;
  /* scene colours kept for theme variants */
  --tea:    #c98555;
  --taro:   #ad83d6;
  --matcha: #76a86a;
  --wood:   #a66c55;
  --floor:  #e8b77f;
  --shadow: 0 24px 70px rgba(60, 32, 24, 0.22);
}

* { box-sizing: border-box; }
body, h1, h2, h3, p { margin: 0; }

button, input { font: inherit; }
button { cursor: pointer; }

/* ── Native-app feel on phones ───────────────────────────────────────────── */
html {
  -webkit-text-size-adjust: 100%;   /* no surprise text inflation on rotate */
  text-size-adjust: 100%;
  height: 100%;
}
body {
  overscroll-behavior: none;        /* no pull-to-refresh / rubber-band bounce */
  overflow: hidden;                 /* the page itself never scrolls; sheets scroll internally */
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;      /* no long-press menu on the UI */
  touch-action: manipulation;       /* removes the 300ms double-tap-zoom delay */
}
/* App chrome isn't selectable text; keep real copy areas selectable below */
.scene, .top-hud, .focus-controls, .break-panel, .bottom-bar, .sheet-head,
.maker-wrap, .work-counter, .size-picker, .daily-goal {
  -webkit-user-select: none;
  user-select: none;
}
img { -webkit-user-drag: none; user-select: none; }

/* ── PWA install banner (slides down from the top) ───────────────────────── */
.install-banner {
  position: absolute;
  top: 0; left: 0; right: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: calc(env(safe-area-inset-top) + 10px) 14px 11px;
  background: var(--bark);
  color: #fff;
  box-shadow: 0 6px 22px rgba(60, 32, 24, 0.3);
  transform: translateY(-110%);
  transition: transform 320ms cubic-bezier(0.2, 0.8, 0.3, 1);
}
.install-banner.show { transform: translateY(0); }
.install-banner[hidden] { display: none; }
.install-icon { font-size: 1.3rem; }
.install-text { flex: 1; font-size: 0.8rem; font-weight: 800; line-height: 1.25; }
.install-btn {
  flex: 0 0 auto;
  background: var(--mint);
  color: var(--bark);
  border: none;
  border-radius: 999px;
  font-weight: 950;
  font-size: 0.8rem;
  padding: 8px 15px;
}
.install-x {
  flex: 0 0 auto;
  background: transparent;
  border: none;
  color: rgba(255, 255, 255, 0.8);
  font-size: 1rem;
  padding: 4px 6px;
  min-width: 30px;
}

/* Visible keyboard focus for accessibility (WCAG 2.4.7) */
:focus-visible {
  outline: 3px solid var(--teal);
  outline-offset: 2px;
  border-radius: 8px;
}

/* ── Outer shell: centres the experience on wide screens ──────────────── */
body {
  min-height: 100svh;
  background: url('assets/Background.png') repeat center / 280px;
  display: flex;
  justify-content: center;
  align-items: center;
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, sans-serif;
}

.scene-wrap {
  position: relative;
  width: 100%;
  max-width: 460px;
  height: 100dvh;            /* fill the whole phone, incl. under the status bar */
  overflow: hidden;
}

/* Only frame it as a floating "phone" (with a height cap) when the window is big
   enough that the wrap doesn't fill the screen. On an actual phone it stays
   edge-to-edge with NO max-height, so there's no white strip top or bottom. */
@media (min-width: 480px) and (min-height: 940px) {
  .scene-wrap {
    max-height: 920px;
    border-radius: 28px;
    box-shadow: var(--shadow);
  }
}

/* ── Desktop marketing rails ─────────────────────────────────────────────
   On wide screens the app floats alone on the wallpaper, so the dead space
   on either side becomes the landing page: pitch + App Store button left,
   scan-to-download QR + links right. The phone in the middle stays the
   live demo. Hidden below 1180px so tablets, phones and the native app
   never see them. */
.site-rail { display: none; }
@media (min-width: 1180px) and (min-height: 700px) {
  body { gap: 56px; }
  .site-rail {
    display: flex; flex-direction: column; align-items: center;
    width: 312px; flex: 0 0 auto; text-align: center;
    /* Card treatment: plain text on the boba wallpaper was unreadable. */
    background: var(--ui-paper);
    border: 2px solid var(--ui-edge);
    border-radius: 28px;
    padding: 30px 26px;
    box-shadow: var(--ui-sheen), 0 1px 0 rgba(96,58,36,0.07), 0 5px 12px -6px rgba(60,32,24,0.24);
  }
  .rail-mascot { width: 132px; height: 132px; filter: drop-shadow(0 8px 12px rgba(60,32,24,0.2)); }
  .rail-title { font-size: 44px; font-weight: 900; color: var(--bark); letter-spacing: -0.5px; margin-top: 6px; }
  .rail-tagline { font-size: 17px; font-weight: 700; color: var(--bark-mid); margin-top: 2px; }
  .rail-pitch { font-size: 14px; font-weight: 600; line-height: 1.55; color: var(--bark); opacity: 0.82; margin-top: 16px; }
  .rail-store-btn {
    display: inline-block; margin-top: 20px; padding: 14px 26px;
    background: var(--bark); color: var(--cream); text-decoration: none;
    border-radius: 999px; font-size: 15px; font-weight: 800;
    box-shadow: 0 8px 18px rgba(60,32,24,0.28);
    transition: transform 140ms, box-shadow 140ms;
  }
  .rail-store-btn:hover { transform: translateY(-2px); box-shadow: 0 12px 24px rgba(60,32,24,0.32); }
  .rail-note { font-size: 12px; font-weight: 600; color: var(--bark-mid); margin-top: 10px; }
  .rail-qr-card {
    display: flex; flex-direction: column; align-items: center; gap: 10px;
  }
  .rail-qr { width: 168px; height: 168px; border-radius: 12px; }
  .rail-qr-card span { font-size: 12.5px; font-weight: 700; color: var(--bark-mid); }
  .rail-links { display: flex; gap: 16px; margin-top: 18px; }
  .rail-links a { font-size: 13px; font-weight: 700; color: var(--bark-mid); text-decoration: none; }
  .rail-links a:hover { color: var(--bark); text-decoration: underline; }
  .rail-made { font-size: 12.5px; font-weight: 700; color: var(--bark-mid); margin-top: 14px; }
}

/* Registering --floor-y makes it an animatable <length> instead of an opaque
   string, so the floor STRIPE (a gradient stop) glides with the counter and the
   character when a mode changes it. Without this the counter would slide while
   the floor behind it jumped. Verified the responsive clamp below still tracks
   the viewport once registered; browsers without @property just skip the
   animation and keep the correct value. */
@property --floor-y {
  syntax: "<length>";
  inherits: true;
  initial-value: 470px;
}

/* ── Full-screen boba shop scene ───────────────────────────────────────── */
.scene {
  /* --floor-y = distance from viewport bottom where the shop's floor sits.
     Everything in the scene (counter, character, cup) is anchored to this so
     the focal point stays above the focus controls overlay. Retuned 2026-07
     after the controls stack grew (Custom/Goal pills + Customize row): the
     maker's box (floor + 16 + 230) must clear the controls top (~448px from
     the bottom with a paused session's Reset visible) on ≥736px phones; the
     smallest iPhones tolerate a few masked-feet pixels of overlap.

     --floor-rest is the idle value; --floor-y is what everything reads. A mode
     that shows a SHORTER controls stack lowers --floor-y by exactly the height
     the stack lost, which keeps the gap between the counter and the top control
     identical in every mode instead of leaving a dead band. */
  /* Distance from the viewport bottom to the shop floor. Was clamp(408px, 56vh,
     470px), which on a 812px screen put the floor 455px up while the control
     stack only reaches ~306px — a ~150px dead band of flat colour between the
     counter and the timer card, and the main reason the scene felt starved and
     the background barely visible. Lowered so the scene gets that height back;
     Don't lower it further: --floor-lift already raises the counter 53px during
     a session, and at 46vh the counter overlapped the timer card by 10px on a
     812px screen. Verified clear at 667 / 812 / 900. */
  /* The whole tableau — character, cup, counter, props — scales together off
     one factor, so a taller screen gets a BIGGER scene rather than the same
     small furniture floating in more space (what made desktop look sparse).
     Stepped by media query, NOT calc: a ratio needs to be unitless, and CSS
     can't divide a length by a length, so clamp(1, calc(1 + (100vh - 780px) /
     1400), 1.16) is invalid — it silently poisoned every size that used it and
     collapsed the counter to 0 height. */
  --fig: 1;
  /* Was clamp(400px, 52vh, 450px), which put the control band at 45% of the
     screen and only ~11% of that band was empty, so the shop was being hidden by
     controls rather than by dead space. The stack itself is now ~35px shorter
     (idle progress bar hidden, tighter card and gap), and that height goes back
     to the scene rather than to more padding. */
  /* Second pass, after the empty idle .session-row stopped reserving 38px:
     the stack now sits ~47px lower, so the floor line follows it down and the
     shop gains that height. Capped at 330 so the tall desktop frame does not
     just grow the band back. */
  --floor-rest: clamp(310px, 42vh, 350px);
  --floor-y: var(--floor-rest);
  /* WAS 53px, the height of the size picker, on the assumption that the stack
     is exactly that much shorter once a session starts and the picker leaves.
     That invariant no longer holds. Idle now ALSO hides the 0% progress bar and
     collapses the empty .session-row, and both of those come back during a
     session, so the session stack is not shorter at all. Measured at 375x812:
     idle 203px, running 215px, i.e. the session stack is 12px TALLER. Dropping
     the floor 53px on top of that put the timer card 51px INTO the counter and
     it never came back on pause or resume.

     Now 0: the floor line does not move between idle and a session at all. The
     band below is sized for the TALLER (session) stack, so neither state
     overlaps and the shop never jumps when you press Start. If the stacks are
     ever re-tuned, re-measure both rather than assuming a constant. */
  --floor-lift: 0px;
  transition: --floor-y 420ms cubic-bezier(0.32, 0.72, 0.28, 1);
  /* Every theme MUST keep a floor under the counter. The themed rules below
     layer this gradient over their art — without it, tall windows crop the
     art's baked-in floor away and the counter looks like it floats mid-air. */
  --floor-a: #cf9d7b;   /* floor stripe (front edge) */
  --floor-b: #e6c8a0;   /* floor fill */
  /* Theme wash laid OVER the drawn boards, same trick as --counter-tint: one
     neutral wood texture serves all ten themes and each one just tints it to
     its own light. Alpha is tuned per theme below — warm themes barely need
     any, cool and dark ones need enough to pull the wood off warm. */
  --floor-tint: rgba(230,200,160,0.40);
  /* Three layers, front to back:
       1. the theme wash (semi-transparent, floor band only)
       2. the drawn boards, mapped onto EXACTLY the band code owns
       3. the original flat gradient, still fully opaque
     Layer 3 is deliberate: if floor-boards.png ever fails to load, the floor
     is still solid and the counter still has something to stand on, which is
     the one thing this variable must never stop doing. */
  --floor-grad:
      linear-gradient(180deg,
        transparent 0 calc(100% - var(--floor-y)),
        var(--floor-tint) calc(100% - var(--floor-y)) 100%) no-repeat,
      url('assets/floor-boards.png')
        bottom center / 100% var(--floor-y) no-repeat,
      linear-gradient(180deg,
        transparent 0 calc(100% - var(--floor-y)),
        var(--floor-a) calc(100% - var(--floor-y)) calc(100% - var(--floor-y) + 26px),
        var(--floor-b) calc(100% - var(--floor-y) + 26px) 100%);
  position: absolute;
  inset: 0;
  overflow: hidden;
  background-color: #fdf0e0;
  background-image:
    /* sky cap, softly fading into the wall instead of a hard line */
    linear-gradient(180deg, #d8ede7 0 18%, rgba(216,237,231,0.4) 22%, transparent 26%),
    /* floor stripe + colour at bottom (softer, less orange) */
    linear-gradient(180deg, transparent 0 calc(100% - var(--floor-y)),
                            #cf9d7b calc(100% - var(--floor-y)) calc(100% - var(--floor-y) + 26px),
                            #e6c8a0 calc(100% - var(--floor-y) + 26px) 100%);
  background-repeat: no-repeat, no-repeat;
  background-position: top, bottom;
}

/* Cozy (default) theme: the illustrated boba-shop backdrop (shelf, window,
   lights) instead of the flat gradient. Character/counter/cup sit on top. */
.scene[data-theme="cozy"] {
  background: var(--floor-grad) no-repeat,
              #f3e4cf url('assets/Shop Background.png') top center / cover no-repeat;
}
/* .cafe-glow is off by default — every theme has an illustrated backdrop now, so
   the old generic light orbs are redundant. It is reused below as the PREMIUM
   ambience layer: the four paid backgrounds get a second animated layer on top
   of .theme-fx, which is what makes them feel worth paying for. */
.cafe-glow { display: none; }

.scene[data-theme="night"] {
  --floor-a: #3a3450;
  --floor-b: #4a4364;
  --floor-tint: rgba(74,67,100,0.80);   /* darkest wash: the boards must read as night */
  background: var(--floor-grad) no-repeat,
              #2e3b57 url('assets/Shop Background Night.png') top center / cover no-repeat;
}
.scene[data-theme="night"] .speech-bubble { background: #fff0be; }
.scene[data-theme="night"] .work-counter {
  --counter-tint: rgba(40,34,66,0.30);
  border-color: rgba(20,14,30,0.22);
  border-top-color: rgba(255,255,255,0.6);
  box-shadow:
    inset 0 1.5px 0 rgba(255,255,255,0.55),
    0 12px 26px rgba(10,8,24,0.35),
    0 2px 0 rgba(10,8,24,0.12);
}
.scene[data-theme="night"] .timer-card {
  border-color: rgba(255,255,255,0.30);
  box-shadow: 0 6px 24px rgba(0,0,0,0.34);
}
.scene[data-theme="night"] .daily-goal {
  border-color: rgba(255,255,255,0.28);
  box-shadow: 0 2px 12px rgba(0,0,0,0.30);
}

.scene[data-theme="sakura"] {
  --floor-a: #d9a3ae;
  --floor-b: #ecc6cd;
  --floor-tint: rgba(236,198,205,0.55);
  background: var(--floor-grad) no-repeat,
              #f6e0e6 url('assets/Shop Background Sakura.png') top center / cover no-repeat;
}
.scene[data-theme="sakura"] .work-counter {
  --counter-tint: rgba(255,190,205,0.16);
  border-color: rgba(120,50,70,0.12);
  border-top-color: rgba(255,255,255,0.85);
}

.scene[data-theme="autumn"] {
  --floor-a: #c08b5a;
  --floor-b: #dcae7e;
  --floor-tint: rgba(220,174,126,0.42);
  background: var(--floor-grad) no-repeat,
              #f0dcb8 url('assets/Shop Background Autumn.png') top center / cover no-repeat;
}
.scene[data-theme="autumn"] .work-counter {
  --counter-tint: rgba(190,110,40,0.14);
  border-color: rgba(90,55,25,0.14);
  border-top-color: rgba(255,255,255,0.85);
}
.scene[data-theme="autumn"] .speech-bubble { background: #fff8e8; }

.scene[data-theme="rainy"] {
  --floor-a: #9aa5ae;
  --floor-b: #b9c2c9;
  --floor-tint: rgba(185,194,201,0.62);   /* cool themes need more wash to pull the wood off warm */
  background: var(--floor-grad) no-repeat,
              #d6dee6 url('assets/Shop Background Rainy.png') top center / cover no-repeat;
}
.scene[data-theme="rainy"] .work-counter {
  --counter-tint: rgba(90,110,130,0.20);
  border-color: rgba(40,55,70,0.14);
  border-top-color: rgba(255,255,255,0.8);
}
.scene[data-theme="rainy"] .speech-bubble { background: #f0f6ff; }

.scene[data-theme="winter"] {
  --floor-a: #aebfc7;
  --floor-b: #cfdce2;
  --floor-tint: rgba(207,220,226,0.62);
  background: var(--floor-grad) no-repeat,
              #ece3d4 url('assets/Shop Background Winter.png') top center / cover no-repeat;
}
.scene[data-theme="winter"] .work-counter {
  --counter-tint: rgba(150,185,210,0.22);
  border-color: rgba(60,70,90,0.13);
  border-top-color: rgba(255,255,255,0.85);
}
.scene[data-theme="winter"] .speech-bubble { background: #eef6ff; }

.scene[data-theme="galaxy"] {
  --floor-a: #b9a8d6;
  --floor-b: #cfc2e6;
  --floor-tint: rgba(207,194,230,0.55);
  background: var(--floor-grad) no-repeat,
              #efe4d2 url('assets/Shop Background Galaxy.png') top center / cover no-repeat;
}
.scene[data-theme="galaxy"] .work-counter {
  --counter-tint: rgba(120,95,180,0.24);
  border-color: rgba(80,60,120,0.13);
  border-top-color: rgba(255,255,255,0.85);
}
.scene[data-theme="galaxy"] .speech-bubble { background: #f3eeff; }

/* ── Premium-theme effects (justify the $1.99): stars + snow ──────────────── */
/* Confined to the upper "sky/window" zone so effects don't clutter the counter,
   character, or controls in the lower half. */
.theme-fx { position: absolute; left: 0; right: 0; top: 0; height: 54%; z-index: 1; pointer-events: none; overflow: hidden; }
.theme-fx::before, .theme-fx::after { content: ""; position: absolute; inset: 0; }

/* GALAXY ($1.99) — glowing twinkling stars + a drifting multi-colour nebula +
   an occasional shooting star. All confined to the sky zone (.theme-fx). */
.scene[data-theme="galaxy"] .theme-fx::before {
  background-repeat: no-repeat;
  background-image:
    radial-gradient(2.5px 2.5px at 12% 16%, #fff, transparent),
    radial-gradient(2px 2px at 27% 40%, #fff, transparent),
    radial-gradient(1.5px 1.5px at 46% 11%, #fff, transparent),
    radial-gradient(2.5px 2.5px at 62% 30%, #e7d8ff, transparent),
    radial-gradient(1.5px 1.5px at 77% 20%, #fff, transparent),
    radial-gradient(2px 2px at 89% 44%, #d7ecff, transparent),
    radial-gradient(1.5px 1.5px at 37% 26%, #fff, transparent),
    radial-gradient(2px 2px at 8% 38%, #fff, transparent),
    radial-gradient(1.5px 1.5px at 55% 50%, #fff, transparent),
    radial-gradient(3px 3px at 18% 31%, #fff, transparent),
    radial-gradient(2.5px 2.5px at 70% 8%, #fff, transparent),
    radial-gradient(1.5px 1.5px at 33% 47%, #ffe9f6, transparent),
    radial-gradient(2px 2px at 93% 27%, #fff, transparent),
    radial-gradient(1.5px 1.5px at 5% 12%, #fff, transparent);
  filter: drop-shadow(0 0 3px rgba(255,255,255,0.7));
  animation: star-twinkle 3.4s ease-in-out infinite;
}
@keyframes star-twinkle { 0%, 100% { opacity: 0.4; } 50% { opacity: 1; } }
.scene[data-theme="galaxy"] .theme-fx::after {
  background-repeat: no-repeat;
  background-image:
    radial-gradient(60% 46% at 22% 32%, rgba(149,94,224,0.42), transparent 70%),
    radial-gradient(55% 42% at 80% 20%, rgba(78,138,232,0.38), transparent 70%),
    radial-gradient(52% 44% at 56% 60%, rgba(228,118,201,0.30), transparent 72%);
  mix-blend-mode: screen;
  animation: nebula-drift 16s ease-in-out infinite alternate;
}
@keyframes nebula-drift {
  from { transform: translate3d(-2%, 0, 0) scale(1.03); opacity: 0.75; }
  to   { transform: translate3d(3%, 1.5%, 0) scale(1.12); opacity: 1; }
}
/* Shooting star — parked off-screen most of the cycle, then streaks across.
   Animates background-POSITION only (no opacity) so the stars/nebula aren't faded. */
.scene[data-theme="galaxy"] .theme-fx {
  background-repeat: no-repeat;
  background-image: linear-gradient(118deg, transparent 0 42%, rgba(255,255,255,0.95) 49% 51%, transparent 58%);
  background-size: 92px 2.5px;
  animation: shooting-star 7s ease-in infinite;
}
@keyframes shooting-star {
  0%, 72% { background-position: -130px 10%; }
  100%    { background-position: 118% 52%; }
}

/* WINTER ($1.99) — denser falling snow (two drifting layers) + a soft aurora. */
.scene[data-theme="winter"] .theme-fx::before,
.scene[data-theme="winter"] .theme-fx::after {
  background-repeat: repeat;
  background-size: 100% 200px;
  background-image:
    radial-gradient(2.6px 2.6px at 20% 8px, rgba(255,255,255,0.95), transparent),
    radial-gradient(2px 2px at 50% 40px, rgba(255,255,255,0.9), transparent),
    radial-gradient(2.4px 2.4px at 80% 18px, rgba(255,255,255,0.95), transparent),
    radial-gradient(2px 2px at 35% 90px, rgba(255,255,255,0.85), transparent),
    radial-gradient(2px 2px at 66% 130px, rgba(255,255,255,0.9), transparent),
    radial-gradient(1.8px 1.8px at 10% 160px, rgba(255,255,255,0.85), transparent),
    radial-gradient(2.2px 2.2px at 90% 70px, rgba(255,255,255,0.9), transparent),
    radial-gradient(1.6px 1.6px at 45% 178px, rgba(255,255,255,0.8), transparent);
  animation: snow-fall 8s linear infinite;
}
.scene[data-theme="winter"] .theme-fx::after { animation-duration: 13s; opacity: 0.7; background-position-x: 45px; }
@keyframes snow-fall { from { background-position-y: -200px; } to { background-position-y: 0; } }
/* Soft shimmering aurora across the top of the sky. background-POSITION shimmer
   only (no opacity anim — that would fade the snow layers above it). */
.scene[data-theme="winter"] .theme-fx {
  background-repeat: no-repeat;
  background-image:
    linear-gradient(180deg, rgba(150,232,205,0.20), transparent 42%),
    radial-gradient(70% 55% at 32% -8%, rgba(132,205,250,0.26), transparent 70%),
    radial-gradient(60% 48% at 76% -4%, rgba(201,162,240,0.20), transparent 72%);
  background-size: 150% 100%, 100% 100%, 100% 100%;
  animation: aurora-shimmer 11s ease-in-out infinite alternate;
}
@keyframes aurora-shimmer {
  from { background-position: 0% 0%, 0 0, 0 0; }
  to   { background-position: 45% 0%, 0 0, 0 0; }
}

/* Break mode is the one place that used to skip var(--floor-grad), and the
   .scene comment above warns exactly what happens without it: the art's own
   floor gets cropped away and things anchored to --floor-y look like they float
   mid-air. They did. The break art paints its floor at 82% of image height,
   which lands at screen y=667 after cover-scaling, while --floor-y puts the
   floor at y=422 — so the bed sat 245px up the wall, inside the window arch,
   with nothing under it. Restoring the gradient (with the art's own wood
   colours, sampled from the background) puts a real floor back under him. */
/* ⚠️ The doubled .is-on-break.is-on-break is DELIBERATE, do not "tidy" it away.
   `.scene.is-on-break` and `.scene[data-theme="x"]` have identical specificity
   (0,2,0), so on a tie the LAST rule in the file wins. Honey Library and Seaside
   Sunset were appended at the bottom in Jul 2026, after these rules, which meant
   break mode silently showed the SHOP backdrop in those two themes instead of the
   bedroom — no error, just the wrong room. Doubling the class makes break mode
   (0,3,0) so it outranks every theme no matter where a future theme is added. */
.scene.is-on-break.is-on-break {
  --floor-a: #d2a878;   /* wood, matches the room's floorboards */
  --floor-b: #dcb98f;   /* wood, matches the room's floorboards */
  --floor-tint: rgba(220,185,143,0.40);   /* the drawn boards already match the bedroom, so barely wash them */
  /* The room is authored SQUARE to match the STAGE — the part of .scene above
     the controls — and is sized to the stage explicitly rather than with cover.
     cover sizes against .scene, which is the full viewport height, so a square
     source was scaled to 812 and lost 218px off EACH side. Sizing the height to
     (100% - --floor-y) maps the art onto exactly the band the user can see, and
     crops about 27px per side instead. The +8px keeps it meeting the floor line
     as --floor-y shifts between modes; background-color backs it up. */
  /* Soften the join. Everywhere else the board texture's darker top edge lands
     under the counter and reads as its contact shadow, which is what we want.
     Here there is no counter: the bedroom art's own floor runs down to the cut
     at rgb(214,169,138), so that same dark edge lands mid-floor and reads as a
     false seam instead. Fade the art's exact floor tone over the first 54px of
     the texture so the two floors meet as one surface. */
  background: linear-gradient(180deg,
                transparent 0 calc(100% - var(--floor-y)),
                rgba(214,169,138,0.95) calc(100% - var(--floor-y)),
                rgba(214,169,138,0) calc(100% - var(--floor-y) + 54px)) no-repeat,
              var(--floor-grad) no-repeat,
              #c9bfe0 url('assets/Bedroom Background.png') top center /
                auto calc(100% - var(--floor-y) + 8px) no-repeat;
}
.scene.is-on-break.is-on-break .work-counter {
  background: linear-gradient(180deg, #fbf7ff 0, #f1ecf8 16px, #e3daf0 100%);
  border-color: rgba(70,55,110,0.13);
  border-top-color: rgba(255,255,255,0.85);
}
.scene.is-on-break.is-on-break .speech-bubble { background: #f5f0ff; }
/* Break = rest: no drink + no shop counter, just a cozy nap nook. */
.scene.is-on-break.is-on-break .cup-stage,
.scene.is-on-break.is-on-break .work-counter { display: none; }

/* Not a rug anymore — a soft contact SHADOW tucked directly under the bed.
   (The old freestanding lavender disc read as a random puddle hovering below
   the background's arch frame.) This quietly grounds the bed to the ledge. */
.break-rug {
  position: absolute;
  left: calc(50% - (var(--bed-w) / 2) + 6px);
  bottom: calc(var(--floor-y) + 1px);
  width: calc(var(--bed-w) - 12px); height: 20px;
  border-radius: 50%;
  background: radial-gradient(ellipse at 50% 50%,
    rgba(88, 58, 108, 0.30) 0%, rgba(88, 58, 108, 0.16) 55%, transparent 76%);
  z-index: 3; display: none; pointer-events: none;
}
.scene.is-on-break.is-on-break .break-rug { display: block; }

/* Cozy nap bed, in two layers with the character sandwiched between them.
   One --bed-w drives both halves and they were drawn in a single render at
   matching widths, so they stay registered at any size.

   The old single bed was 240px wide against a 205px character — 1.17x — so he
   could only ever sit ON it, and his art started 107px above its top edge.
   --bed-w is now wide enough to read as furniture at the character's real size,
   so he is never resized between focus and break. */
.scene { --bed-w: 292px; --blanket-w: 292px; }
.break-bed-back,
.break-bed-front {
  position: absolute;
  left: calc(50% - (var(--bed-w) / 2));
  width: var(--bed-w); height: auto;
  display: none;
  pointer-events: none;
}
.break-bed-back {
  bottom: calc(var(--floor-y) + 6px);
  z-index: 4;                       /* headboard + pillow, BEHIND him */
  filter: drop-shadow(0 3px 3px rgba(60,32,24,0.22));
}
.break-bed-front {
  /* Slightly narrower than the mattress, which is both how a duvet actually
     sits and what drops its folded cuff far enough down the bed to leave his
     face clear. At full bed width the cuff cut straight across his eyes. */
  left: calc(50% - (var(--blanket-w) / 2));
  width: var(--blanket-w);
  bottom: calc(var(--floor-y) + 6px);
  z-index: 10;                      /* blanket, IN FRONT of him = tucked in */
}
.scene.is-on-break.is-on-break .break-bed-back,
.scene.is-on-break.is-on-break .break-bed-front { display: block; }

/* ── Foreground desk: the near plane, break mode only ──────────────────────
   Everything above --floor-y is the room. This is an OBJECT in front of it.
   Its TOP edge sits --desk-rise ABOVE the code-owned floor line, so it crosses
   the bed's base and hides the bedroom art's own drawn floorboards. That is the
   actual fix for the seam: the bedroom paints a converging plank floor with a
   skirting board, it was guillotined at the floor line, and a flat
   non-perspective texture started immediately below. Two contradicting
   perspectives meeting at a hard full-width line is what the eye catches, and
   tinting cannot fix geometry. Occlusion can.

   It NEVER declares a floor line of its own, it only crosses the one --floor-y
   already owns, so it inherits that registered property's transition for free.

   Break mode always paints the bedroom (.is-on-break.is-on-break is (0,3,0) and
   outranks every .scene[data-theme] at (0,2,0)), so this needs NO per-theme tint
   and NO per-theme asset. It is deliberately ABSENT on the focus screen, where
   .work-counter is already a lit wooden ledge with its own bright lip and
   grained front face. Two lit ledges 12px apart is worse than one.

   The gradient is BOTH the fallback and the continuation: the PNG only draws the
   top of the surface, and the flat --desk-mid band below it runs to the bottom of
   the frame at any height, so there is never a gap and never a squash. */
.scene {
  --desk-rise: 46px;
  --desk-lip:  #d6a882;   /* far edge catching the light */
  /* Sampled from the SHIPPED break-desk.png bottom row, not picked by eye. The
     art only draws the top of the desk and this gradient continues it to the
     bottom of the frame, so any mismatch here is a full-width seam. */
  --desk-mid:  #d4945b;
  /* Desaturated as they darken, not just dimmed. Carrying --desk-mid's
     saturation down to the near edge turned the bottom third into a hot orange
     slab at desktop, where the desk is ~55% of the frame. Wood falling away
     from a lamp loses chroma as well as value. */
  --desk-low:  #c08b64;
  --desk-deep: #a87a56;   /* darkest; below this it reads as a bar, not a desk */
}
@media (min-width: 480px)  { .scene { --desk-rise: 54px; } }
@media (max-height: 720px) { .scene { --desk-rise: 34px; } }

.fg-desk {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: calc(var(--floor-y) + var(--desk-rise));
  z-index: 10;
  pointer-events: none;
  display: none;
  background: linear-gradient(180deg,
    rgba(60,32,24,0.34) 0 2px,
    var(--desk-lip)     2px 10px,
    var(--desk-mid)     10px 58%,
    var(--desk-low)     86%,
    var(--desk-deep)    100%);
}
.scene.is-on-break.is-on-break .fg-desk { display: block; }

/* The drawn surface. aspect-ratio derives height from the frame WIDTH, so the
   near plane gets BIGGER on the wider desktop frame instead of being the same
   small furniture in more space, and nothing is ever resampled non-uniformly.
   margin-top is a PERCENTAGE, which resolves against the containing block's
   WIDTH, so the transparent headroom above the desk's drawn far edge scales
   with the art at every width from ONE constant.
   -19.401% = the measured far-edge row (298) / asset width (1536). Both numbers
   were read off the shipped file, not assumed. */
.fg-desk::before {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 0;
  aspect-ratio: 1536 / 1024;
  margin-top: -19.401%;
  background: url('assets/break-desk.png') center / 100% 100% no-repeat;
}

/* Lamp pool + the recessed strip the nav pills sit in. Above the surface so the
   art and the CSS read as ONE lit object. This strip is what gives the pills
   their contrast in break, which is why .bottom-bar drops its own scrim there. */
.fg-desk::after {
  content: "";
  position: absolute; inset: 0;
  pointer-events: none;
  background:
    linear-gradient(180deg, transparent 0 calc(100% - 102px),
      rgba(46,26,18,0.28) calc(100% - 102px) calc(100% - 100px),
      rgba(46,26,18,0.12) calc(100% - 100px) 100%),
    radial-gradient(130% 54% at 50% 22%,
      rgba(255,240,214,0.30), rgba(255,240,214,0) 76%);
}

/* Character naps IN the bed on break: centred on the mattress and seated so the
   blanket layer crosses his chest.
   He is deliberately NOT scaled here. An earlier version shrank him to 0.75 in
   break, which read as the character shrinking rather than as depth. It also
   never actually took effect — .maker-wrap carries transform: translateX(--walk)
   with a 1050ms transition for the walk choreography, and that transition owns
   the transform property, so a scale declared here is silently dropped. Size the
   bed instead; --bed-w is the knob. */
.scene.is-on-break.is-on-break .maker-wrap {
  left: calc(50% - 102px);
  bottom: calc(var(--floor-y) + 122px);
}

/* ── Premium background ambience ───────────────────────────────────────────
   The four paid themes (winter, galaxy, library, sunset) each get a SECOND
   animated layer here, on top of the .theme-fx effects every theme has. Before
   this, library and sunset shipped with fewer effects than the free night
   theme, which is a poor argument for buying them.

   Unlike .theme-fx (capped at 54% so it stays in the sky), this layer spans the
   full scene, so premium effects can reach the floor. Its three spans are the
   moving parts; each theme repurposes them. All are low-contrast and slow on
   purpose — this is ambience behind a character you stare at for 30 minutes,
   not a screensaver. */
.scene[data-theme="winter"] .cafe-glow,
.scene[data-theme="galaxy"] .cafe-glow,
.scene[data-theme="library"] .cafe-glow,
.scene[data-theme="sunset"] .cafe-glow {
  display: block;
  inset: 0;
  height: auto;
  z-index: 2;
  overflow: hidden;
}
.scene[data-theme="winter"] .cafe-glow span,
.scene[data-theme="galaxy"] .cafe-glow span,
.scene[data-theme="library"] .cafe-glow span,
.scene[data-theme="sunset"] .cafe-glow span {
  border-radius: 0;
  filter: none;
  opacity: 1;
  animation: none;
}

/* WINTER — fairy lights twinkling above, cold fog drifting along the floor. */
.scene[data-theme="winter"] .cafe-glow span:nth-child(1),
.scene[data-theme="winter"] .cafe-glow span:nth-child(2) {
  left: -20%; right: -20%; width: 140%;
  bottom: calc(var(--floor-y) - 14px);
  height: 76px;
  background: radial-gradient(60% 100% at 30% 100%, rgba(226,240,248,0.5), transparent 70%),
              radial-gradient(50% 100% at 74% 100%, rgba(210,232,246,0.42), transparent 72%);
  animation: prem-drift 26s ease-in-out infinite alternate;
}
.scene[data-theme="winter"] .cafe-glow span:nth-child(2) {
  animation-duration: 37s; animation-direction: alternate-reverse; opacity: 0.7;
}
.scene[data-theme="winter"] .cafe-glow span:nth-child(3) {
  left: 0; right: 0; top: 0; height: 90px;
  background:
    radial-gradient(3px 3px at 12% 26px, rgba(255,246,214,0.95), transparent 60%),
    radial-gradient(3px 3px at 33% 40px, rgba(255,240,200,0.9), transparent 60%),
    radial-gradient(3px 3px at 54% 22px, rgba(255,248,220,0.95), transparent 60%),
    radial-gradient(3px 3px at 74% 38px, rgba(255,240,200,0.9), transparent 60%),
    radial-gradient(3px 3px at 91% 24px, rgba(255,246,214,0.95), transparent 60%);
  animation: prem-twinkle 3.6s ease-in-out infinite alternate;
}

/* GALAXY — nebula clouds drifting slowly behind the existing starfield. */
.scene[data-theme="galaxy"] .cafe-glow span:nth-child(1),
.scene[data-theme="galaxy"] .cafe-glow span:nth-child(2) {
  left: -25%; right: -25%; width: 150%; top: 0; height: 62%;
  background: radial-gradient(42% 46% at 26% 34%, rgba(178,142,232,0.30), transparent 72%),
              radial-gradient(36% 40% at 72% 22%, rgba(140,170,240,0.24), transparent 72%);
  animation: prem-drift 34s ease-in-out infinite alternate;
}
.scene[data-theme="galaxy"] .cafe-glow span:nth-child(2) {
  animation-duration: 48s; animation-direction: alternate-reverse; opacity: 0.72;
}
.scene[data-theme="galaxy"] .cafe-glow span:nth-child(3) {
  left: 0; right: 0; top: 0; height: 54%;
  background: radial-gradient(52% 44% at 50% 8%, rgba(232,214,255,0.20), transparent 74%);
  animation: prem-pulse 9s ease-in-out infinite alternate;
}

/* LIBRARY — warm godrays leaning through the window, and lamplight breathing. */
.scene[data-theme="library"] .cafe-glow span:nth-child(1),
.scene[data-theme="library"] .cafe-glow span:nth-child(2) {
  left: 8%; top: -6%; width: 34%; height: 74%;
  transform: rotate(14deg);
  transform-origin: 50% 0;
  background: linear-gradient(180deg, rgba(255,222,150,0.26), rgba(255,222,150,0.06) 62%, transparent);
  animation: prem-pulse 11s ease-in-out infinite alternate;
}
.scene[data-theme="library"] .cafe-glow span:nth-child(2) {
  left: 44%; width: 22%; transform: rotate(11deg);
  animation-duration: 15s; animation-direction: alternate-reverse; opacity: 0.7;
}
.scene[data-theme="library"] .cafe-glow span:nth-child(3) {
  right: 0; left: 40%; bottom: calc(var(--floor-y) - 20px); height: 150px;
  background: radial-gradient(50% 70% at 72% 100%, rgba(255,198,110,0.26), transparent 72%);
  animation: prem-pulse 7s ease-in-out infinite alternate;
}

/* SUNSET — clouds crossing the sky, with golden-hour light on the room. */
.scene[data-theme="sunset"] .cafe-glow span:nth-child(1),
.scene[data-theme="sunset"] .cafe-glow span:nth-child(2) {
  left: -30%; right: -30%; width: 160%; top: 4%; height: 30%;
  background:
    radial-gradient(15% 42% at 22% 50%, rgba(255,240,228,0.5), transparent 70%),
    radial-gradient(11% 34% at 48% 62%, rgba(255,232,214,0.42), transparent 70%),
    radial-gradient(13% 38% at 76% 44%, rgba(255,244,232,0.46), transparent 70%);
  animation: prem-crossing 58s linear infinite;
}
.scene[data-theme="sunset"] .cafe-glow span:nth-child(2) {
  top: 16%; height: 24%; opacity: 0.6; animation-duration: 86s;
}
.scene[data-theme="sunset"] .cafe-glow span:nth-child(3) {
  left: 0; right: 0; top: 0; bottom: 0;
  background: radial-gradient(60% 40% at 50% 6%, rgba(255,186,120,0.24), transparent 72%);
  animation: prem-pulse 8s ease-in-out infinite alternate;
}

@keyframes prem-drift    { from { transform: translateX(-3%); } to { transform: translateX(3%); } }
@keyframes prem-crossing { from { transform: translateX(-14%); } to { transform: translateX(14%); } }
@keyframes prem-pulse    { from { opacity: 0.55; } to { opacity: 1; } }
@keyframes prem-twinkle  { from { opacity: 0.45; } to { opacity: 1; } }

/* Calm mode turns all of it off, same as every other ambient effect here. */
@media (prefers-reduced-motion: reduce) {
  .cafe-glow span { animation: none !important; }
}

/* ── Scene ambiance ────────────────────────────────────────────────────── */
/* Soft pastel light orbs drifting in the wall zone — gives the empty wall
   gentle depth without the old crude jar/window/light blocks. Purely
   decorative, low-contrast, never competes with the character or cup. */
.cafe-glow {
  position: absolute;
  inset: 56px 0 auto 0;
  height: clamp(180px, 26vh, 240px);
  z-index: 1;
  pointer-events: none;
}
.cafe-glow span {
  position: absolute;
  border-radius: 50%;
  filter: blur(2px);
  opacity: 0.5;
  animation: glow 5s ease-in-out infinite;
}
.cafe-glow span:nth-child(1) {
  top: 14px; left: 30px;
  width: 64px; height: 64px;
  background: radial-gradient(circle at 35% 35%, rgba(255,255,255,0.85), rgba(255,224,180,0.45) 55%, transparent 72%);
}
.cafe-glow span:nth-child(2) {
  top: 70px; right: 34px;
  width: 86px; height: 86px;
  background: radial-gradient(circle at 35% 35%, rgba(255,255,255,0.7), rgba(190,235,225,0.4) 55%, transparent 72%);
  animation-delay: 1.2s;
}
.cafe-glow span:nth-child(3) {
  top: 6px; left: 48%;
  width: 52px; height: 52px;
  background: radial-gradient(circle at 35% 35%, rgba(255,255,255,0.75), rgba(255,205,222,0.4) 55%, transparent 72%);
  animation-delay: 2.4s;
}

.speech-bubble {
  position: absolute;
  z-index: 9;
  /* Sits just above Mr. Tapioca's head, pointing down at him. Positioned relative
     to .maker-wrap (its parent) so it WALKS with him via the wrap's translateX. */
  left: 18px;
  bottom: 188px;
  max-width: 178px;
  padding: 9px 13px;
  border: 3px solid #67473d;
  border-radius: 18px;
  background: rgba(255,253,248,0.96);
  font-size: 0.78rem;
  font-weight: 850;
  line-height: 1.25;
  text-align: center;
  box-shadow: 4px 4px 0 rgba(45,36,40,0.1);
  color: var(--bark);
  /* hidden until he speaks (tap-to-talk); pops in/out */
  opacity: 0;
  transform: scale(0.6) translateY(8px);
  transform-origin: 30% 130%;
  pointer-events: none;
  transition: opacity 160ms ease, transform 240ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
.speech-bubble.show {
  opacity: 1;
  transform: scale(1) translateY(0);
}
.speech-bubble::after {
  content: "";
  position: absolute;
  left: 60px; bottom: -13px;
  width: 18px; height: 18px;
  border-right: 3px solid #67473d;
  border-bottom: 3px solid #67473d;
  background: rgba(255,253,248,0.96);
  transform: rotate(45deg);
}

/* ── Work counter and cup ───────────────────────────────────────────────── */
/* The shop FLOOR. Below --floor-y the floor gradient paints a flat colour over
   the background art, and on a tall screen that is half the app reading as a
   blank pastel block — the single biggest reason the backgrounds "can barely be
   seen". This layer turns that block into a floor: boards running away from the
   viewer, spaced wider as they come forward, a shadow gathering under the
   counter, and the corners falling off so the eye stays on the character.
   Purely decorative and behind everything (z 0). */
.scene::after {
  content: "";
  position: absolute;
  left: 0; right: 0;
  bottom: 0;
  height: var(--floor-y);
  z-index: 0;
  pointer-events: none;
  background:
    /* shadow pooling where the floor meets the counter */
    linear-gradient(180deg, rgba(60,32,24,0.20) 0, rgba(60,32,24,0.06) 34px, transparent 96px),
    /* board seams, wider apart nearer the viewer = depth */
    linear-gradient(180deg,
      transparent 0 16px, rgba(60,32,24,0.07) 16px 17px,
      transparent 17px 44px, rgba(60,32,24,0.06) 44px 45px,
      transparent 45px 88px, rgba(60,32,24,0.05) 88px 89px,
      transparent 89px 152px, rgba(60,32,24,0.04) 152px 153px,
      transparent 153px),
    /* (The "plank joins" layer that used to sit here was a repeating 90deg
       gradient at a CONSTANT 74px pitch. The board seams above it converge
       16/44/88/152px, which implies a specific camera height, so a constant
       vertical pitch crossing them contradicted that perspective and turned the
       floor into a tile grid. Removing it is the single biggest free win on the
       focus screen.) */
    /* corners fall away */
    radial-gradient(120% 90% at 50% 0%, transparent 55%, rgba(60,32,24,0.16) 100%);
}

/* The shop counter — real illustrated wood, not CSS gradients.
   It used to be built from linear-gradients, which is why it read as a bright
   plastic bar: on the galaxy theme its top colour was #fbf8ff, making the
   largest object in the scene the brightest thing on screen, in a scene that is
   otherwise entirely illustrated. Now it is drawn art in the same style as the
   backgrounds.

   The art is cropped so its edge line lands at 88% of its height, because the
   cup's base contacts at 56px and his feet at 68px of the 84px counter — both
   must land ON the top surface, not the front panel. Stretching horizontally is
   safe: the grain runs horizontally, so width changes are invisible, and the
   vertical proportion is fixed by the crop.

   --counter-tint lets a theme wash the wood toward its own light (cool at
   night, lilac in galaxy) without ever turning it back into a white slab. */
.work-counter {
  --counter-tint: transparent;
  position: absolute;
  left: 16px; right: 16px;
  bottom: var(--floor-y);
  height: calc(84px * var(--fig));
  z-index: 8;
  border-radius: 22px 22px 18px 18px;
  background:
    linear-gradient(var(--counter-tint), var(--counter-tint)),
    url('assets/counter-wood.png') center / 100% 100% no-repeat;
  box-shadow:
    0 12px 26px rgba(60,32,24,0.22),
    0 2px 0 rgba(60,32,24,0.06);
}

/* Counter props. z-index 1 keeps them behind the cup (5) and the character
   (9, a sibling of the counter), so they read as sitting further back on the
   surface. bottom is set from the 62px surface line, not the counter's base. */
.counter-prop {
  position: absolute;
  bottom: calc(24px * var(--fig));
  z-index: 1;
  pointer-events: none;
  height: auto;
  filter: drop-shadow(0 2px 2px rgba(60,32,24,0.16));
}
.prop-straws { left: 1px;  width: calc(23px * var(--fig)); }
.prop-plant  { right: 2px; width: calc(25px * var(--fig)); }
/* The counter is hidden on break, and so are its props (inherited), but be
   explicit so a future layout change can't leave them floating in the bedroom. */
.scene.is-on-break.is-on-break .counter-prop { display: none; }

/* Wood grain on the front face only. Very low contrast on purpose: it should
   read as material at a glance and never compete with the character. */
.work-counter::before {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 62px; bottom: 0;
  border-radius: 0 0 17px 17px;
  pointer-events: none;
  opacity: 0.5;
  background: repeating-linear-gradient(90deg,
    transparent 0 17px, rgba(120,80,50,0.05) 17px 18px,
    transparent 18px 41px, rgba(120,80,50,0.035) 41px 42px);
}
.work-counter::after {
  content: "";
  position: absolute;
  left: 14px; right: 14px; top: 9px;
  height: 10px;
  border-radius: 999px;
  background: linear-gradient(180deg, rgba(255,255,255,0.9), rgba(255,255,255,0));
  pointer-events: none;
}
/* The maker (z-index:9) always stands IN FRONT of the counter, fully visible —
   at rest AND while mixing. He walks over and leans into the cup's edge to stir
   (cup stays visible beside him); he does not tuck behind the counter. */



/* ── Mr. Tapioca character image ────────────────────────────────────────── */
/* Wrapper owns POSITION + the left/right "walk" (translateX). The inner img
   owns the looping idle/mixing/etc. animation. Splitting them lets the maker
   walk to the cup while still bobbing/wiggling — the two transforms compose. */
.maker-wrap {
  position: absolute;
  left: 18px;
  /* z-index ABOVE the counter (8) so he stands AT it, ~85% visible; his feet
     fade behind the counter lip via the mask on .maker-img */
  bottom: calc(var(--floor-y) + 16px);
  width: calc(205px * var(--fig));
  height: calc(230px * var(--fig));
  z-index: 9;
  --walk: 0px;
  transform: translateX(var(--walk));
  transition: transform 1050ms cubic-bezier(0.45, 0.05, 0.35, 1);
  cursor: pointer;   /* tap him for a personality line */
}

.maker-img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: bottom center;
  transform-origin: center bottom;
  -webkit-mask-image: linear-gradient(180deg, #000 0 86%, transparent 100%);
          mask-image: linear-gradient(180deg, #000 0 86%, transparent 100%);
}

/* These animate the whole sprite (transform), so they apply to EVERY skin,
   not just the base art. transform-origin is center-bottom for squash/stretch. */
.maker-img { will-change: transform; }
.maker-img[data-state="idle"]    { animation: maker-idle  4s   ease-in-out infinite; }
.maker-img[data-state="walking"] { animation: maker-walk  0.34s ease-in-out infinite; }
.maker-img[data-state="mixing"]  { animation: maker-mixing 0.9s ease-in-out infinite; }
.maker-img[data-state="sleeping"]{ animation: maker-sleep  3.6s ease-in-out infinite; }
.maker-img[data-state="drinking"]{ animation: maker-drink  1.4s ease-in-out infinite; }
.maker-img[data-state="shocked"] { animation: maker-shock  0.5s ease-out forwards; }

/* ── Sprite mode (real frame animation; see SpriteEngine + assets/SPRITES.md) ──
   When a sprite strip is active the engine blanks the <img> bitmap and paints the
   strip as a background; this rule + an INLINE `animation` (set in JS with a literal
   frame count) scroll one whole frame at a time. The inline animation overrides the
   transform keyframes above, so the strip alone carries the motion. background-size
   is also set inline (N×100%). The 86% mask still applies → feet fade into the lip. */

/* The "dozing" fake (lean + dim) lived here because skins only had an awake
   portrait, so a nap had to be implied. Every skin now has real eyes-closed art,
   so the nap is drawn rather than suggested and maker-sleep supplies the
   breathing. Tilting and dimming a character who is already visibly asleep read
   as doing the job twice. */

/* Idle: soft breathing, then a little hop with squash-and-stretch — reads as
   "alive" even though each skin is a single still image. */
@keyframes maker-idle {
  0%, 100% { transform: translateY(0)    scaleX(1)    scaleY(1); }
  18%      { transform: translateY(-4px) scaleX(0.99) scaleY(1.02); }  /* breathe in */
  36%      { transform: translateY(0)    scaleX(1)    scaleY(1); }      /* breathe out */
  68%      { transform: translateY(0)    scaleX(1.04) scaleY(0.96); }   /* crouch */
  80%      { transform: translateY(-13px) scaleX(0.97) scaleY(1.05); }  /* hop */
  90%      { transform: translateY(0)    scaleX(1.05) scaleY(0.95); }   /* land squash */
}
/* Walking: quick waddle (bob + tilt) while he glides toward/away from the cup */
@keyframes maker-walk {
  0%, 100% { transform: translateY(0)    rotate(-5deg); }
  25%      { transform: translateY(-5px) rotate(4deg); }
  50%      { transform: translateY(0)    rotate(-5deg); }
  75%      { transform: translateY(-5px) rotate(4deg); }
}

/* Mixing: leans toward the cup on the right and bobs as if working */
@keyframes maker-mixing {
  0%, 100% { transform: translate(0, 0)     rotate(-2deg); }
  25%      { transform: translate(5px, -3px) rotate(4deg); }
  50%      { transform: translate(7px, 0)    rotate(3deg); }
  75%      { transform: translate(5px, -3px) rotate(4deg); }
}

/* One-shot reactions (added/removed by JS; placed after the looping state
   rules so they win while present, then the loop resumes). Work on any skin. */
.maker-img.pop       { animation: maker-pop       0.42s ease-out; }
.maker-img.celebrate { animation: maker-celebrate 1.1s  ease-in-out; }
.maker-img.wiggle    { animation: maker-wiggle    0.8s  ease-in-out; }

/* Gentle look-around sway used for idle "fidgets" so he feels alive untouched */
@keyframes maker-wiggle {
  0%, 100% { transform: rotate(0deg)   translateY(0); }
  20%      { transform: rotate(-5deg)  translateY(-2px); }
  45%      { transform: rotate(5deg)   translateY(-3px); }
  70%      { transform: rotate(-3deg)  translateY(-1px); }
  88%      { transform: rotate(2deg)   translateY(0); }
}

@keyframes maker-pop {
  0%   { transform: translateY(0)    scale(1); }
  40%  { transform: translateY(-10px) scale(1.12, 0.92); }
  70%  { transform: translateY(0)    scale(0.96, 1.04); }
  100% { transform: translateY(0)    scale(1); }
}
@keyframes maker-celebrate {
  0%, 100% { transform: translateY(0)    rotate(0deg)  scale(1); }
  20%      { transform: translateY(-16px) rotate(-6deg) scale(1.06); }
  45%      { transform: translateY(0)    rotate(0deg)  scale(0.97, 1.05); }
  65%      { transform: translateY(-12px) rotate(6deg)  scale(1.05); }
  85%      { transform: translateY(0)    rotate(0deg)  scale(0.98, 1.03); }
}
@keyframes maker-sleep {
  0%, 100% { transform: translateY(0)   rotate(-3deg) scale(1); }
  50%      { transform: translateY(2px) rotate(-3deg) scale(1.03); }
}
@keyframes maker-drink {
  0%, 100% { transform: translateY(0)   rotate(-2deg); }
  50%      { transform: translateY(-4px) rotate(2deg); }
}
@keyframes maker-shock {
  0%   { transform: translateY(0)    scale(1); }
  20%  { transform: translateY(-9px) scale(1.08) rotate(2deg); }
  100% { transform: translateY(-3px) scale(1.04) rotate(-1deg); }
}

/* ── Ambient effects: focus sparkles, break zzz, completion burst ─────────── */
.focus-fx, .break-zzz, .celebrate-fx {
  position: absolute;
  left: 0; right: 0;
  bottom: var(--floor-y);
  pointer-events: none;
  opacity: 0;
}

/* Soft glowing bubbles drifting up while focusing */
.focus-fx {
  height: 230px;
  z-index: 9;
  transition: opacity 400ms ease;
}
.scene.is-focusing .focus-fx { opacity: 1; }
.focus-fx span {
  position: absolute;
  bottom: 70px;
  width: 11px; height: 11px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #ffffff, #ffd98a 70%);
  box-shadow: 0 0 6px rgba(255, 215, 130, 0.7);
  opacity: 0;
  animation: fx-rise 3.6s ease-in-out infinite;
}
.focus-fx span:nth-child(1) { left: 24%; animation-delay: 0s;    }
.focus-fx span:nth-child(2) { left: 38%; animation-delay: 0.6s;  }
.focus-fx span:nth-child(3) { left: 30%; animation-delay: 1.2s;  }
.focus-fx span:nth-child(4) { left: 62%; animation-delay: 0.3s;  }
.focus-fx span:nth-child(5) { left: 72%; animation-delay: 1.5s;  }
.focus-fx span:nth-child(6) { left: 55%; animation-delay: 2.1s;  }
@keyframes fx-rise {
  0%   { transform: translateY(0)     scale(0.5); opacity: 0; }
  15%  { opacity: 0.95; }
  100% { transform: translateY(-160px) scale(1);   opacity: 0; }
}

/* Sleepy z's near the maker's head during a break */
.break-zzz {
  right: auto;
  left: calc(50% + 62px);          /* clear of his head, which now sits higher in the bed */
  bottom: calc(var(--floor-y) + 214px);
  display: flex;
  gap: 4px;
  z-index: 9;                      /* above the mascot, below the break panel (z-index:10) */
}
/* Floating zzz while napping. Every character needs it now: the old base nap art
   had zzz drawn into the picture, the new generated set deliberately has no text
   baked in, so this is the only source. */
.scene.is-napping .break-zzz { opacity: 1; }
.break-zzz span {
  font-style: italic;
  font-weight: 950;
  color: #b0a2d8;
  animation: zzz-float 2.6s ease-in-out infinite;
}
.break-zzz span:nth-child(1) { font-size: 1.0rem; animation-delay: 0s;   }
.break-zzz span:nth-child(2) { font-size: 1.3rem; animation-delay: 0.6s; }
.break-zzz span:nth-child(3) { font-size: 1.6rem; animation-delay: 1.2s; }
@keyframes zzz-float {
  0%   { transform: translateY(0)    rotate(-6deg); opacity: 0; }
  30%  { opacity: 1; }
  100% { transform: translateY(-34px) rotate(10deg); opacity: 0; }
}

/* Confetti-ish burst of treats when a drink is completed */
.celebrate-fx {
  height: 210px;
  z-index: 12;
}
.celebrate-fx span {
  position: absolute;
  left: 50%;
  bottom: 50px;
  font-size: 1.5rem;
  opacity: 0;
}
.scene.celebrating .celebrate-fx span { animation: fx-burst 1.3s ease-out forwards; }
.celebrate-fx span:nth-child(1) { --tx: -120px; --rot: -40deg; animation-delay: 0s;    }
.celebrate-fx span:nth-child(2) { --tx:  -80px; --rot:  30deg; animation-delay: 0.05s; }
.celebrate-fx span:nth-child(3) { --tx:  -40px; --rot: -20deg; animation-delay: 0.1s;  }
.celebrate-fx span:nth-child(4) { --tx:  -10px; --rot:  15deg; animation-delay: 0.02s; }
.celebrate-fx span:nth-child(5) { --tx:   30px; --rot: -25deg; animation-delay: 0.08s; }
.celebrate-fx span:nth-child(6) { --tx:   70px; --rot:  35deg; animation-delay: 0.04s; }
.celebrate-fx span:nth-child(7) { --tx:  110px; --rot: -30deg; animation-delay: 0.12s; }
.celebrate-fx span:nth-child(8) { --tx:  140px; --rot:  20deg; animation-delay: 0.06s; }
@keyframes fx-burst {
  0%   { transform: translate(-50%, 0)   scale(0.4) rotate(0deg);                 opacity: 0; }
  20%  { opacity: 1; }
  100% { transform: translate(calc(-50% + var(--tx)), -150px) scale(1.1) rotate(var(--rot)); opacity: 0; }
}

.pearl-icon {
  width: 20px; height: 20px;
  object-fit: contain;
  vertical-align: middle;
  flex-shrink: 0;
}

/* ── Boba cup (image + liquid fill) ─────────────────────────────────────── */
.cup-stage {
  position: absolute;
  right: calc(22px * var(--fig)); top: calc(-120px * var(--fig));
  width: calc(130px * var(--fig)); height: calc(174px * var(--fig));
  z-index: 5;
  pointer-events: none;
}

.cup-frame {
  --fill: 0%;
  --drink-color: var(--tea);
  position: relative;
  width: 100%;
  height: 100%;
}
.cup-frame.spilling {
  transform-origin: bottom center;
  animation: cup-spill 1.0s cubic-bezier(0.3,0,0.6,0.8) forwards;
}

/* ── Crafted SVG boba cup ──────────────────────────────────────────────────
   The liquid (#liquid rect) is clipped to the cup's exact interior path, so the
   fill hugs the tapered walls instead of being a rectangle behind a PNG. */
.cup-svg { width: 100%; height: 100%; display: block; overflow: visible; }

.cup-body-fill { fill: rgba(255,255,255,0.34); }
.cup-outline  { stroke: var(--bark); stroke-width: 2.6; stroke-linejoin: round; stroke-linecap: round; }

/* Liquid + surface: geometry (y/height/fill/cy) is set every tick in updateCup. */
#liquid       { transition: y 480ms ease, height 480ms ease, fill 300ms ease; }
.liq-surface  { fill: #fff; opacity: 0.22; transition: cy 480ms ease, opacity 300ms ease; }
.cup-sheen    { stroke: #fff; stroke-width: 5; opacity: 0.16; stroke-linecap: round; }

/* ── Making the drink feel brewed rather than filled ───────────────────────
   The liquid was a rect whose y/height slid on a 480ms transition, and nothing
   else in the cup ever moved. Three cheap additions give it life, all confined
   to the existing clip path so they can never escape the cup:
     1. the surface gently swells and settles, like liquid finding its level
     2. bubbles rise through it while a session is actually running
     3. the pearls shift very slightly, so the bottom isn't frozen
   Everything here is opt-in on .is-focusing, so a paused or idle cup is calm. */
.cup-bubbles circle {
  fill: #fff;
  opacity: 0;
  transform-box: fill-box;
  transform-origin: 50% 50%;
}
.scene.is-focusing .cup-frame.has-fill .cup-bubbles circle {
  animation: bubble-rise 3.4s ease-in infinite;
}
.cup-bubbles circle:nth-child(2) { animation-duration: 4.2s; animation-delay: 0.7s; }
.cup-bubbles circle:nth-child(3) { animation-duration: 3.0s; animation-delay: 1.4s; }
.cup-bubbles circle:nth-child(4) { animation-duration: 4.8s; animation-delay: 2.1s; }
.cup-bubbles circle:nth-child(5) { animation-duration: 3.8s; animation-delay: 0.3s; }
@keyframes bubble-rise {
  0%   { opacity: 0;    transform: translateY(0)     scale(0.6); }
  12%  { opacity: 0.34; }
  70%  { opacity: 0.26; }
  100% { opacity: 0;    transform: translateY(-46px) scale(1); }
}

/* The meniscus breathes instead of sitting perfectly still. */
.scene.is-focusing .cup-frame.has-fill .liq-surface {
  animation: liq-settle 2.8s ease-in-out infinite;
}
@keyframes liq-settle {
  0%, 100% { ry: 3.4; opacity: 0.22; }
  50%      { ry: 4.4; opacity: 0.3; }
}

/* Pearls resettle very slightly — enough to read as suspended in liquid. */
.scene.is-focusing .cup-frame.has-fill .cup-pearls,
.scene.is-focusing .cup-frame.has-fill .cup-pearl-shine {
  animation: pearl-settle 5.2s ease-in-out infinite;
}
@keyframes pearl-settle {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-1.2px); }
}

/* Toppings sit at the bottom; recolor the cluster per choice. */
.cup-pearls circle      { fill: #2a1d22; }
.cup-pearl-shine circle { fill: #fff; opacity: 0.4; }
.cup-frame[data-topping="jelly"]   .cup-pearls circle { fill: rgba(238,91,127,0.82); }
.cup-frame[data-topping="pudding"] .cup-pearls circle { fill: #f7cb59; }
.cup-frame[data-topping="coconut"] .cup-pearls circle { fill: rgba(228,248,228,0.95); }
.cup-frame[data-topping="jelly"]   .cup-pearl-shine,
.cup-frame[data-topping="pudding"] .cup-pearl-shine,
.cup-frame[data-topping="coconut"] .cup-pearl-shine { display: none; }

/* Cheese/cream foam cap — a band riding the liquid surface, only for foam. */
.foam-band { fill: #fff3d6; display: none; }
.cup-frame[data-topping="foam"].has-fill .foam-band { display: block; }

/* Straw + lid + dome */
.cup-straw    { fill: var(--rose); stroke: var(--bark); stroke-width: 2.2; stroke-linejoin: round; }
.cup-straw-hl { fill: #fff; opacity: 0.5; }
.cup-lid      { fill: var(--cream); stroke: var(--bark); stroke-width: 2.4; stroke-linejoin: round; }
.cup-dome     { fill: rgba(255,255,255,0.5); stroke: var(--bark); stroke-width: 2.4; stroke-linejoin: round; }
.cup-dome-hl  { fill: #fff; opacity: 0.55; }

.cup-sticker {
  position: absolute;
  left: 50%; top: 55%;
  z-index: 4;
  min-width: 38px; max-width: 50px;
  padding: 2px 5px;
  transform: translate(-50%, -50%) rotate(-5deg);
  border: 2px solid var(--bark);
  background: rgba(255,255,255,0.94);
  font-size: 0.52rem;
  font-weight: 950;
  text-align: center;
  overflow-wrap: anywhere;
  color: var(--bark);
}

.steam {
  position: absolute;
  left: 60px; top: -8px;
  display: flex; gap: 9px;
  z-index: 4;
  opacity: 0;
  transition: opacity 200ms ease;
}
.scene.is-focusing .steam { opacity: 1; }
.steam span {
  width: 8px; height: 28px;
  border-radius: 999px;
  border-left: 3px solid rgba(255,255,255,0.82);
  animation: steam-rise 1.8s ease-in-out infinite;
}
.steam span:nth-child(2) { animation-delay: 0.32s; }
.steam span:nth-child(3) { animation-delay: 0.64s; }

/* ── Top HUD overlay ────────────────────────────────────────────────────── */
.top-hud {
  position: absolute;
  top: 0; left: 0; right: 0;
  z-index: 10;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  padding: 52px 18px 16px;
  pointer-events: none;
}
.top-hud > * { pointer-events: auto; }
.hud-chips { display: flex; align-items: center; gap: 8px; }
/* HUD chips match the pearl chip's box (20px icon + 6px padding + 2px borders).
   The streak chip leads with its flame the way the pearl chip leads with its
   pearl; the icon-less name chip centers with symmetric padding. Compound
   selectors outrank the base .pearl-chip regardless of file order. */
.pearl-chip.streak-chip, .pearl-chip.hud-name { min-height: 42px; }   /* = the pearl chip's measured box */
.pearl-chip.streak-chip .flame { font-size: 1.05rem; line-height: 1; }
.pearl-chip.hud-name { justify-content: center; padding: 6px 12px; }
/* Inline Customize chip in the timer card (replaces the old percent label) */
.customize-inline-btn {
  border: 2px solid var(--ui-edge);
  background: var(--ui-paper);
  box-shadow: var(--ui-sheen), 0 1px 0 rgba(96,58,36,0.07), 0 5px 12px -6px rgba(60,32,24,0.24);
  color: var(--bark);
  font-weight: 900;
  font-size: 0.68rem;
  padding: 4px 10px;
  border-radius: 999px;
  white-space: nowrap;
}
.customize-inline-btn:active { transform: scale(0.95); }
.hud-name { font-weight: 950; max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Declutter (2026-07): the drink-name pill moved INTO the timer card
   (#sessionLabel). The top HUD now holds only the pearl chip, so the cozy
   scene gets the whole top of the screen. Markup kept for easy revert. */
.drink-label { display: none; }

/* The drink name in the timer card: one tidy ellipsized line, tappable. */
#sessionLabel {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 70%;
}

.pearl-chip {
  display: flex;
  align-items: center;
  gap: 6px;
  background: var(--ui-paper);
  border: 2px solid var(--ui-edge);
  border-radius: 999px;
  padding: 6px 14px 6px 6px;
  font-size: 1.02rem;
  font-weight: 950;
  color: var(--bark);
  box-shadow: var(--ui-sheen), var(--ui-lift);
}
.pearl-chip .pearl-icon { width: 26px; height: 26px; }
.pearl-chip.small {
  font-size: 0.82rem;
  padding: 5px 12px 5px 6px;
}
.pearl-chip.small .pearl-icon { width: 20px; height: 20px; }
.pearl-chip.pearl-pop {
  animation: pearlPop 0.45s ease;
}
@keyframes pearlPop {
  0%   { transform: scale(1); }
  35%  { transform: scale(1.22); box-shadow: 0 4px 16px rgba(72,160,150,0.5); }
  100% { transform: scale(1); }
}

/* ── Daily goal progress pill (under the top HUD) ───────────────────────── */
/* Declutter: hidden while a focus session runs or during a break (it otherwise
   collides with the speech bubble on short phones). Returns when idle/paused. */
.scene-wrap.is-session .daily-goal { display: none; }
.daily-goal {
  position: absolute;
  top: 108px;   /* clears the top-HUD pills; env()-aware override below for notches */
  left: 50%;
  transform: translateX(-50%);
  z-index: 10;
  display: flex;
  align-items: center;
  gap: 8px;
  max-width: 210px;
  width: max-content;
  padding: 6px 12px;
  border-radius: 999px;
  background: var(--ui-paper);
  border: 2px solid var(--ui-edge);
  box-shadow: var(--ui-sheen), var(--ui-lift);
}
.dg-icon { font-size: 0.9rem; line-height: 1; }
.dg-track {
  width: 92px;
  height: 8px;
  border-radius: 999px;
  background: rgba(60,32,24,0.12);
  overflow: hidden;
}
.dg-fill {
  display: block;
  width: 0%;
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, #f0bb4f, #ef8aa0);
  transition: width 500ms ease;
}
.dg-label {
  font-size: 0.72rem;
  font-weight: 950;
  color: var(--bark);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.daily-goal.met {
  border-color: var(--teal);
  background: var(--mint);
}
.daily-goal.met .dg-fill { background: linear-gradient(90deg, #75c8bd, #158f92); }

.goal-stepper {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* Stacked settings row (label on top, control row below) */
.settings-row.stack {
  flex-direction: column;
  align-items: stretch;
  gap: 10px;
}

/* ── Volume sliders (music / sound effects) ─────────────────────────────── */
.volume-control {
  display: flex;
  align-items: center;
  gap: 12px;
}
.volume-val {
  flex: 0 0 34px;
  text-align: right;
  font-size: 0.82rem;
  font-weight: 900;
  color: var(--bark-mid);
  font-variant-numeric: tabular-nums;
}
.volume-slider {
  -webkit-appearance: none;
  appearance: none;
  flex: 1;
  height: 8px;
  border-radius: 999px;
  background: rgba(60,32,24,0.14);
  outline: none;
  cursor: pointer;
}
.volume-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 24px; height: 24px;
  border-radius: 50%;
  background: var(--teal);
  border: 3px solid #fff;
  box-shadow: 0 2px 6px rgba(60,32,24,0.3);
  cursor: pointer;
}
.volume-slider::-moz-range-thumb {
  width: 22px; height: 22px;
  border-radius: 50%;
  background: var(--teal);
  border: 3px solid #fff;
  box-shadow: 0 2px 6px rgba(60,32,24,0.3);
  cursor: pointer;
}
.volume-slider:focus-visible { box-shadow: 0 0 0 3px rgba(21,143,146,0.3); }

.goal-display {
  min-width: 56px;
  text-align: center;
  font-size: 0.82rem;
  font-weight: 950;
  color: var(--bark);
  font-variant-numeric: tabular-nums;
}

/* ── Focus controls overlay ─────────────────────────────────────────────── */
/* Once a session is underway the size picker is dead weight (switching modes
   needs a reset anyway), so it leaves the flow entirely. It used to be merely
   `visibility: hidden`, which reserved its space so the stack could never
   shift, but that left a blank strip between the counter and the timer that
   read as a hole in the layout. Now the row is removed and the shop floor
   used to drop by the same amount (see --floor-lift), so the counter followed
   the controls down. --floor-lift is now 0 because the session stack is no
   longer shorter than idle, so the floor stays put and only the stack grows
   upward into the gap that was reserved for it.

   Nothing below the picker moves: the stack is anchored from the bottom, so
   removing its top row leaves the timer, the button and the pill in place. */
.focus-controls.session-on .size-picker { display: none; }
/* The whole scene (counter, character, cup, floor stripe) rides --floor-y. */
.scene.is-brewing { --floor-y: calc(var(--floor-rest) - var(--floor-lift)); }
/* One fixed-height row for the shield pill + Reset: the pill stays centered,
   Reset docks at the right edge, and toggling either never moves the stack. */
.session-row { position: relative; width: 100%; min-height: 38px; display: flex; align-items: center; justify-content: center; }
/* Idle, both of this row's children are display:none, but min-height still
   reserved 38px of bare floor directly under Start Focus. The min-height is
   only needed once the row has content, because .reset-link is absolutely
   positioned and contributes no height of its own. Collapsing it while idle
   lets the whole stack sit lower and the floor line come down with it.
   Gated rather than removed: the block pill is an in-flow child, so a visible
   pill still sizes the row on its own. */
.focus-controls:not(.session-on) .session-row { min-height: 0; }
.session-row .block-pill { margin: 0; }
.session-row .reset-link { position: absolute; right: 0; top: 50%; transform: translateY(-50%); }
.focus-controls {
  position: absolute;
  /* add the home-indicator inset so the Reset link clears the bottom nav (which
     grows with safe-area) on notched iPhones — otherwise the nav covered it. */
  bottom: calc(108px + env(safe-area-inset-bottom)); left: 0; right: 0;
  z-index: 10;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 9px;
  padding: 0 20px;
}

.size-picker {
  display: flex;
  gap: 0;
  width: 100%;
  background: var(--ui-paper);
  border: 2px solid var(--ui-edge);
  border-radius: 999px;
  padding: 3px;
  box-shadow: var(--ui-sheen), var(--ui-lift);
}

.size-btn {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  padding: 5px 6px;
  border: none;
  background: none;
  border-radius: 999px;
  font-size: 0.8rem;
  font-weight: 900;
  color: var(--bark-mid);
  transition: background 180ms, color 180ms, box-shadow 180ms;
}
.size-btn small { font-size: 0.56rem; font-weight: 700; opacity: 0.85; }
.size-btn.active {
  background: linear-gradient(180deg, #f6ddc4, #efcead);
  color: var(--bark);
  box-shadow: inset 0 0 0 1.5px rgba(60,32,24,0.16), 0 2px 6px rgba(60,32,24,0.12);
}

.adj-btn.small-adj {
  width: 40px; height: 40px;
  font-size: 1.2rem;
  border-width: 2px;
}

/* ── Touch target floor ───────────────────────────────────────────────────
   44 controls measured under the 44x44 minimum, including the timer's -/+
   (40x40), every sheet close (40x40) and the shop buttons (32 tall). Fixing
   that by growing them visually would wreck a deliberately dense layout, so
   the hit area is extended past the visual bounds instead — which is what
   Apple's HIG and Material both actually call for.

   The pseudo-element is centred on the control and sized to the shortfall, so
   a 40px button gets a 44px target and a 32px one gets 44 without either
   moving a pixel. Parents need position; these all already have it or get it
   here. Pointer-events stay on the ::after only, so it can't swallow taps
   meant for anything drawn above it. */
.time-adj, .adj-btn, .sheet-close, .install-x,
.shop-buy-btn, .shop-equip-btn, .shop-unequip-btn, .shop-preview-btn,
.game-quit-btn, .settings-action-btn, .squad-remove, #resetBtn, #customizeDrinkBtn,
.size-btn {
  position: relative;
}
.time-adj::after, .adj-btn::after, .sheet-close::after, .install-x::after,
.shop-buy-btn::after, .shop-equip-btn::after, .shop-unequip-btn::after,
.shop-preview-btn::after, .game-quit-btn::after, .settings-action-btn::after,
.squad-remove::after, #resetBtn::after, #customizeDrinkBtn::after,
.size-btn::after {
  content: "";
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: max(100%, 44px);
  height: max(100%, 44px);
  border-radius: inherit;
}

/* ── Icons ────────────────────────────────────────────────────────────────
   Inline SVG replaces the emoji that were doing structural work (every sheet
   close, the streak counter, the app-blocking shield). Emoji render from a
   font the platform chooses, so they shift shape between iOS versions and
   Android, ignore currentColor, and can't take a stroke weight — they were the
   one part of the UI that couldn't be art-directed at all. These inherit
   colour and sit on the text baseline. */
.ico { width: 1em; height: 1em; display: block; flex-shrink: 0; }
.sheet-close .ico, .install-x .ico { width: 15px; height: 15px; }
.ico-flame { width: 1.05em; height: 1.05em; }
/* The emoji this replaced was orange, and that colour carried the meaning of
   "streak" — a monochrome glyph in body-text brown reads as decoration. Keeping
   the warmth is the point, not a flourish. */
.flame { color: #ef8a3c; display: flex; align-items: center; }
.block-pill-ico .ico { width: 15px; height: 15px; }
.ico-lg { width: 44px; height: 44px; display: block; margin: 0 auto; }
.block-shield { color: var(--teal); }
/* Icon slots that previously held an emoji glyph and were sized by font-size. */
.g-emoji { display: inline-flex; align-items: center; }
.g-emoji .ico { width: 1.15em; height: 1.15em; }
#onboardEmoji .ico { width: 62px; height: 62px; margin: 0 auto; color: var(--teal); }
.boba-pin .ico { width: 19px; height: 19px; margin: 0 auto; }
.map-shop-emoji .ico { width: 17px; height: 17px; }

/* ── UI material system ────────────────────────────────────────────────────
   Every control was a cream rounded rect with the same flat blur and a generic
   soft shadow, which is why the chrome read as a stock component kit sitting on
   top of an illustrated game. These tokens give the whole UI one material and
   ONE light direction (from above, matching the shop window), so cards, pickers
   and buttons look like objects on the counter rather than floating panels.

   --ui-lift is deliberately three-part: a hairline contact edge, a tight close
   shadow, and a wide ambient one. A single blurred shadow is what makes UI look
   flat and cheap. */
/* On :root, NOT .scene-wrap — the controls and the nav live outside the scene
   wrapper, so scoping these there left them with no background at all. */
:root {
  --ui-paper: linear-gradient(180deg, #fffbf3 0%, #fdf4e6 58%, #f8ecda 100%);
  --ui-edge:  rgba(96,58,36,0.20);
  --ui-sheen: inset 0 1.5px 0 rgba(255,255,255,0.95);
  --ui-lift:  0 1px 0 rgba(96,58,36,0.10),
              0 6px 14px -6px rgba(60,32,24,0.26),
              0 16px 32px -20px rgba(60,32,24,0.34);
}

.timer-card {
  width: 100%;
  background: var(--ui-paper);
  border: 2px solid var(--ui-edge);
  border-radius: 22px;
  padding: 7px 18px 6px;
  box-shadow: var(--ui-sheen), var(--ui-lift);
}
.timer-card.compact {
  width: auto;
  min-width: 124px;
  text-align: center;
  padding: 8px 18px 6px;
}

.timer-time {
  font-size: 1.95rem;
  font-weight: 950;
  color: var(--bark);
  line-height: 1;
  text-align: center;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}
/* In-card −/+ for Custom mode (replaces the old separate stepper row).
   Scoped under .timer-card so these beat the later, lower-specificity .adj-btn. */
.timer-time-row { display: flex; align-items: center; justify-content: center; gap: 16px; }
.timer-card .time-adj { display: none; width: 40px; height: 40px; font-size: 1.3rem; }
.timer-card.custom-adjust .time-adj { display: flex; }

.timer-meta {
  display: flex;
  justify-content: space-between;
  align-items: center;
  color: var(--bark-mid);
  font-size: 0.78rem;
  font-weight: 900;
  margin-top: 3px;
}

.timer-sub {
  color: var(--bark-mid);
  font-size: 0.72rem;
  font-weight: 850;
  text-align: center;
  margin-top: 3px;
}

/* Before a session there is nothing to show: the bar sits at 0% and costs 18px
   of the control stack (8px track + 10px margin) to say nothing. Hiding it while
   idle is the single cheapest way to give the shop back some screen. */
/* .focus-controls is a SIBLING of .scene inside .scene-wrap, and it carries its
   own session-on class, so gate on that. (.scene-wrap never gets is-session and
   .scene gets is-brewing, so neither of those works from here.) */
.focus-controls:not(.session-on) .progress-track { display: none; }

.progress-track {
  height: 8px;
  margin-top: 10px;
  overflow: hidden;
  border: 1.5px solid rgba(60,32,24,0.14);
  border-radius: 999px;
  background: rgba(60,32,24,0.07);
}
.progress-track span {
  display: block;
  width: 0%;
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, #f08aa0, #f0bb4f, #75c8bd);
  transition: width 420ms ease;
}
.break-track span { background: linear-gradient(90deg, #c4b5e8, #e8b5d8); }

.start-btn {
  width: 100%;
  padding: 16px 32px;
  /* transparent border matches .is-running's 2px solid one, so the button
     (and the whole stack above it) never changes height when pausing */
  border: 2px solid transparent;
  border-radius: 999px;
  background: linear-gradient(180deg, #4a2a20, #3c2018);
  color: var(--cream);
  font-size: 1.05rem;
  font-weight: 950;
  letter-spacing: 0.01em;
  /* The main CTA is the most-tapped thing in the app, so it gets real
     tactility: a bright top edge, a solid lip beneath it, and ambient shadow.
     Pressing depresses it onto the lip rather than only dimming it. */
  box-shadow:
    inset 0 1.5px 0 rgba(255,255,255,0.20),
    inset 0 -2px 0 rgba(0,0,0,0.20),
    0 5px 0 #2a1610,
    0 12px 22px -8px rgba(60,32,24,0.48);
  transition: transform 70ms ease, box-shadow 70ms ease, background 150ms;
}
.start-btn:active {
  transform: translateY(4px);
  box-shadow:
    inset 0 1.5px 0 rgba(255,255,255,0.14),
    inset 0 -2px 0 rgba(0,0,0,0.20),
    0 1px 0 #2a1610,
    0 5px 12px -6px rgba(60,32,24,0.44);
}

/* While the focus session is running, the Start button becomes the Pause
   button — softer cream colour so it reads as "tap to pause" not "primary CTA" */
/* Paused: same key, in paper instead of chocolate — its lip switches to the
   bark colour so the object keeps its thickness rather than flattening. */
#startPauseBtn.is-running {
  background: var(--ui-paper);
  color: var(--bark);
  border: 2px solid var(--bark);
  box-shadow:
    inset 0 1.5px 0 rgba(255,255,255,0.9),
    0 5px 0 rgba(60,32,24,0.55),
    0 12px 22px -8px rgba(60,32,24,0.34);
}
#startPauseBtn.is-running:active {
  transform: translateY(4px);
  box-shadow:
    inset 0 1.5px 0 rgba(255,255,255,0.9),
    0 1px 0 rgba(60,32,24,0.55),
    0 5px 12px -6px rgba(60,32,24,0.30);
}

.reset-link {
  background: rgba(60, 32, 24, 0.55);
  color: var(--cream);
  border: 1.5px solid rgba(253, 245, 236, 0.3);
  border-radius: 999px;
  font-size: 0.74rem;
  font-weight: 900;
  padding: 5px 14px;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  letter-spacing: 0.02em;
}

/* ── Break panels ───────────────────────────────────────────────────────── */
/* bottom clears the nav rather than guessing at it. .bottom-bar measures 102px
   tall on phone (62px pills + 12/28 padding), so the old flat 96px let Skip's
   last 6px sit UNDER the nav: hit-testing the pill at 80% and 95% of its height
   returned .bottom-bar, not the button, so the bottom fifth of Skip was dead.
   132px also closes the 81.6px of bare floor that used to sit between the floor
   line and the panel top, which was pure layout: the break-RUNNING panel is
   283.7px tall in the identical band and left only 10.3px of the same gap. */
.break-panel {
  position: absolute;
  bottom: 132px; left: 0; right: 0;
  z-index: 10;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 0 20px;
}
/* Skip was 27px tall, the only control in the app under the 44px floor that the
   hit-target system at the top of this file exists to enforce. */
.break-panel .reset-link {
  min-height: 44px;
  padding: 0 24px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.break-panel.hidden { display: none; }

/* Compact running break panel so the cozy bed/rug nook stays fully visible and
   GROUNDED above it. (It used to be a tall 3-button column that blanketed the
   bed's base + the floor, so the mascot read as floating.) */
#breakRunningPanel { gap: 7px; }
#breakRunningPanel .timer-card { padding: 8px 18px 7px; }
#breakRunningPanel .timer-time { font-size: 1.7rem; }
#breakRunningPanel .break-games-note { margin: 0; }
.break-games-row { display: flex; gap: 8px; width: 100%; }
.break-games-row .game-launch-btn {
  flex: 1; min-width: 0;
  padding: 9px 4px;
  font-size: 0.82rem;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  line-height: 1.1;
}
.break-games-row .g-emoji { font-size: 1.15rem; line-height: 1; }
/* Lift the whole nook (rug + bed + mascot all share --floor-y) clear of the
   compacted panel so the bed visibly rests on the rug instead of floating.

   A flat value, not a viewport clamp: the break panel is anchored to the BOTTOM
   of the screen, so the distance between the rug and the panel works out to
   (--floor-y - panel height - 92px) regardless of how tall the phone is. The old
   44vh clamp made that distance grow with the screen, which opened a ~104px band
   under the nook on a Pro Max while a 390-wide phone sat at 74px. Pinning it
   keeps the nook the same distance above the panel on every size. */
.scene.is-on-break.is-on-break { --floor-y: 390px; }
/* Desktop windows (no phone is 480px wide): the break arch is cover art,
   top-anchored and height-fit, so its baked-in ledge sits at 47% of the
   frame height, while the flat 390px floor above was tuned for an 844px
   phone (ledge 397px there). On the taller desktop frame (capped at 920px
   near the top of this file) the bed floated below the ledge over a dead
   lavender band. Seat the nook on the ledge and let the break panel follow,
   so the extra height goes to the art's wood floor instead of empty wall.
   47dvh tracks the frame below the cap (frame = 100dvh there); 426px/433px
   are the ledge at the 920px cap. The @media (max-height: 720px) rules
   further down still win for short windows (later cascade, same
   specificity). Derivation: ledge = 1 - 712/1344 of frame height from the
   bottom; 301px = panel height 223px + the phone's 78px ledge-to-pill band. */
@media (min-width: 480px) {
  .scene.is-on-break.is-on-break { --floor-y: min(47dvh - 7px, 426px); }
  /* 291px, not 301px: the old constant was derived from "panel height 223px",
     which matches neither real panel (break-offer measures 212.4, break-running
     283.7), so desktop opened a +20px gap where phone had a -6px overlap. */
  .break-panel { bottom: max(112px, min(47dvh, 433px) - 291px); }
}

/* On the material system, not a fifth material. This was a dark translucent
   glass pill (rgba(60,32,24,0.55) + blur, radius 999px) in a band that already
   carried four different surfaces. As a cream paper card it becomes an object
   lying on the desk, matching the timer card and the nav pills. */
.break-title {
  color: var(--bark);
  font-size: 1rem;
  font-weight: 950;
  align-self: center;
  background: var(--ui-paper);
  border: 2px solid var(--ui-edge);
  box-shadow: var(--ui-sheen), var(--ui-lift);
  padding: 7px 18px;
  border-radius: 14px;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

/* The row declared width:100% but its content only spanned x 56.8..318.2,
   leaving 36.8px of dead gutter inside it on each side while #startBreakBtn
   used the full 20px rail. space-between + a growing card puts the stepper on
   the same rail as everything else. */
.break-adjust-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
}
#breakOffer .timer-card.compact { flex: 1; width: auto; min-width: 0; }

/* The stepper did not look attached to its card because it was made of
   different stuff: frosted cream circles flanking a paper card. Scoped to the
   break steppers only; the Custom/Goal steppers sit INSIDE a .timer-card where
   the frosted look is still correct. */
#breakMinus, #breakPlus {
  background: var(--ui-paper);
  border: 2px solid var(--ui-edge);
  border-radius: 16px;
  box-shadow: var(--ui-sheen), var(--ui-lift);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

.adj-btn {
  width: 44px; height: 44px;
  border: 2px solid var(--cream-90);
  border-radius: 50%;
  background: var(--cream-75);
  color: var(--bark);
  font-size: 1.4rem;
  font-weight: 950;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.game-launch-btn {
  background: #8272aa;
  color: var(--cream);
  box-shadow: 0 6px 20px rgba(130,114,170,0.4);
}

/* ── Bottom navigation bar ──────────────────────────────────────────────── */
.bottom-bar {
  position: absolute;
  bottom: 0; left: 0; right: 0;
  z-index: 10;
  display: flex;
  justify-content: space-around;
  align-items: center;
  gap: 6px;
  /* 20px side padding, not 12px: the nav rail was 12/363 while every control
     above it used 20/355, an 8px misalignment on both sides. */
  padding: 12px 20px 28px;
  background: linear-gradient(0deg, rgba(60,32,24,0.18) 0%, transparent 100%);
}
/* In break the desk's ::after draws a proper recessed strip with a dark lip for
   the pills to sit in, so the generic scrim would double up on it. */
.scene.is-on-break.is-on-break ~ .bottom-bar { background: none; }

.icon-pill {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  flex: 1;
  max-width: 84px;
  background: var(--ui-paper);
  border: 2px solid var(--ui-edge);
  border-radius: 18px;
  padding: 9px 4px;
  box-shadow: var(--ui-sheen), var(--ui-lift);
  transition: transform 90ms ease, box-shadow 90ms ease;
}
.icon-pill:active {
  transform: translateY(2px);
  box-shadow: var(--ui-sheen), 0 1px 0 rgba(96,58,36,0.12), 0 3px 8px -3px rgba(60,32,24,0.30);
}
/* (A second `.icon-pill:active { transform: scale(0.95) }` used to sit here.
   Same selector, later in the file, so it silently overrode the translateY(2px)
   above and the pills never performed the press-onto-the-lip motion the
   box-shadow beside it was written for.) */
.pill-label { font-size: 0.58rem; font-weight: 950; color: var(--bark); letter-spacing: 0.01em; }
.icon-pill { position: relative; }

/* Daily-quests nav icon: a little clipboard with a check */
/* Count badge on the Quests pill (remaining quests today) */
.nav-badge {
  position: absolute; top: 1px; right: 5px;
  min-width: 17px; height: 17px; padding: 0 4px;
  border-radius: 999px; background: var(--rose); color: #fff;
  font-size: 0.62rem; font-weight: 950;
  display: flex; align-items: center; justify-content: center;
  border: 1.5px solid var(--cream);
  box-shadow: 0 1px 4px rgba(60,32,24,0.3);
}
.nav-badge.hidden { display: none; }

/* Cute CSS icons for the bottom bar */
/* The five nav icons were each hand-drawn from borders and pseudo-elements —
   five different constructions with five different stroke weights, which is why
   they never read as one set. They are a single SVG family now: one 24 grid,
   one 2px stroke, round caps and joins. */
.nav-svg { width: 100%; height: 100%; display: block; }
.nav-icon {
  position: relative;
  width: 25px;
  height: 25px;          /* square, so the 24-grid icons aren't squashed */
  display: block;
  color: var(--bark);
}

/* Shopping bag: rectangle body + handle on top */

/* Painter's palette: oval with thumb hole + four colour dots */

/* Settings gear — crisp SVG silhouette tinted with the nav color.
   URL-encoded data URI (encoded <,>,# ) so the mask works in every browser. */

/* Map location pin */

/* ── Slide-up sheets ────────────────────────────────────────────────────── */
.sheet {
  position: absolute;
  bottom: 0; left: 0; right: 0;
  z-index: 50;
  /* Same paper as the rest of the chrome, with a lip along the top edge so the
     sheet reads as a physical card sliding up over the scene. */
  background: var(--ui-paper);
  border-top: 2px solid var(--ui-edge);
  border-radius: 28px 28px 0 0;
  max-height: 88svh;
  display: flex;
  flex-direction: column;
  transform: translateY(0);
  transition: transform 320ms cubic-bezier(0.32, 0.72, 0, 1);
  box-shadow: 0 -8px 40px rgba(60,32,24,0.2);
}
.sheet.hidden {
  /* display:flex overrides the global `.hidden { display:none }` (2773), which
     otherwise yanks the sheet out of layout instantly and kills the slide-out.
     visibility (delayed until the transform finishes) still takes it out of the
     a11y tree and hit-testing once it is offscreen. */
  display: flex;
  visibility: hidden;
  transform: translateY(110%);
  pointer-events: none;
  transition: transform 320ms cubic-bezier(0.32, 0.72, 0, 1),
              visibility 0s linear 320ms;
}

.sheet-handle {
  width: 44px; height: 5px;
  background: rgba(60,32,24,0.18);
  border-radius: 999px;
  margin: 12px auto 0;
  flex-shrink: 0;
  position: relative;
  touch-action: none;   /* we own the vertical drag; don't let iOS scroll it */
  cursor: grab;
}
/* A 5px pill is untappable. Give it an invisible 80x33 hit area. */
.sheet-handle::before {
  content: "";
  position: absolute;
  left: -18px; right: -18px; top: -14px; bottom: -14px;
}
/* While a finger is down the sheet tracks 1:1 instead of easing. */
.sheet.dragging { transition: none; }

.sheet-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 20px 12px;
  border-bottom: 1.5px solid var(--line);
  flex-shrink: 0;
}
.sheet-head h2 {
  font-size: 1.1rem;
  font-weight: 950;
  color: var(--bark);
  flex: 1;
}

.sheet-close {
  width: 40px; height: 40px;
  border-radius: 50%;
  border: 2px solid var(--ui-edge);
  background: var(--ui-paper);
  box-shadow: var(--ui-sheen), 0 2px 6px -3px rgba(60,32,24,0.3);
  color: var(--bark-mid);
  font-size: 0.9rem;
  font-weight: 950;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.sheet-body {
  overflow-y: auto;
  padding: 16px 20px 40px;
  flex: 1;
  -webkit-overflow-scrolling: touch;
}

.sheet-backdrop {
  position: absolute;
  inset: 0;
  z-index: 40;
  background: rgba(60,32,24,0.45);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
}
.sheet-backdrop.hidden { display: none; }

/* ── Headings inside sheets ─────────────────────────────────────────────── */
h2 { font-size: 1.08rem; }
h3 {
  font-size: 0.86rem;
  font-weight: 950;
  color: var(--bark);
  margin: 0 0 10px;
}
.section-gap { margin-top: 20px; }

.eyebrow {
  margin: 0 0 3px;
  color: var(--teal);
  font-size: 0.72rem;
  font-weight: 900;
  text-transform: uppercase;
}

/* ── Shared form controls (used inside sheets) ──────────────────────────── */
/* Customize pickers: rendered as literal shop cards (.shop-card etc.), so
   they inherit the shop's fonts and colors exactly. These few rules only
   reset the fact that the card is a <button> here, and add selected state. */
.option-list { display: flex; flex-direction: column; gap: 8px; }
button.option-row { width: 100%; font: inherit; text-align: left; cursor: pointer; }
.option-row.active { border-color: var(--teal); background: var(--mint); }
.option-row.locked { opacity: 0.82; }
.shop-buy-btn.as-price { display: inline-flex; align-items: center; }

.break-games-note {
  margin: 4px 0 -2px;
  text-align: center;
  font-size: 0.72rem;
  font-weight: 800;
  color: var(--bark-mid);
  opacity: 0.85;
}

.section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.section-head.compact { margin-bottom: 8px; }

.badge {
  padding: 5px 9px;
  border: 2px solid rgba(60,32,24,0.18);
  border-radius: 999px;
  background: rgba(60,32,24,0.06);
  color: var(--bark-mid);
  font-size: 0.72rem;
  font-weight: 900;
}

.icon-btn {
  min-height: 40px;
  width: 40px;
  border: 2px solid rgba(60,32,24,0.18);
  border-radius: 10px;
  background: rgba(60,32,24,0.05);
  color: var(--bark);
  font-weight: 950;
}

/* ── Stats dashboard (Settings → Your Progress) ─────────────────────────── */
.stats-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 14px;
}
.stat-streak {
  background: linear-gradient(135deg, #ffe7c2 0%, #ffd2a6 100%);
  border: 2px solid var(--line);
}
.stat-block {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 14px 8px;
  border-radius: 16px;
  background: var(--ui-paper);
  box-shadow: var(--ui-sheen), 0 1px 0 rgba(96,58,36,0.07), 0 5px 12px -6px rgba(60,32,24,0.24);
  border: 2px solid var(--ui-edge);
}
.stats-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
}
.stat-value {
  font-size: 1.5rem;
  font-weight: 950;
  color: var(--bark);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.stat-streak .stat-value { font-size: 2.1rem; }
.stat-label {
  font-size: 0.68rem;
  font-weight: 850;
  color: var(--bark-mid);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* ── Settings row + toggle ──────────────────────────────────────────────── */
.settings-section-title {
  margin: 26px 0 4px;
  padding-top: 18px;
  border-top: 2px dashed var(--line);
  font-size: 1.05rem;
  color: var(--bark);
}
.settings-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 14px;
  padding: 12px 14px;
  border: 2px solid var(--ui-edge);
  border-radius: 16px;
  background: var(--ui-paper);
  box-shadow: var(--ui-sheen), 0 1px 0 rgba(96,58,36,0.07), 0 4px 10px -6px rgba(60,32,24,0.2);
}
.settings-row-label {
  font-size: 0.86rem;
  font-weight: 900;
  color: var(--bark);
}
.settings-row-label small {
  display: block;
  font-size: 0.66rem;
  font-weight: 700;
  color: var(--bark-mid);
  margin-top: 1px;
}
/* Little maker's mark at the very bottom of Settings. */
.made-by {
  text-align: center;
  font-size: 0.74rem;
  font-weight: 700;
  color: var(--bark-mid);
  margin: 20px 0 6px;
}
.toggle {
  position: relative;
  width: 52px;
  height: 30px;
  border-radius: 999px;
  border: 2px solid var(--line);
  background: rgba(60,32,24,0.14);
  transition: background 180ms;
  flex-shrink: 0;
}
.toggle.on { background: var(--teal); }
.toggle-knob {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(60,32,24,0.3);
  transition: transform 180ms;
}
.toggle.on .toggle-knob { transform: translateX(22px); }

/* ── Weekly focus chart ─────────────────────────────────────────────────── */
.week-chart {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 6px;
  height: 110px;
  padding: 10px 8px 0;
  border: 2px solid var(--ui-edge);
  border-radius: 16px;
  background: var(--ui-paper);
  box-shadow: var(--ui-sheen), 0 1px 0 rgba(96,58,36,0.07), 0 5px 12px -6px rgba(60,32,24,0.24);
}
.week-col {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  height: 100%;
  gap: 4px;
}
.week-bar-wrap {
  flex: 1;
  width: 100%;
  display: flex;
  align-items: flex-end;
  justify-content: center;
}
.week-bar {
  width: 62%;
  min-height: 4px;
  border-radius: 6px 6px 3px 3px;
  background: linear-gradient(180deg, #f0bb4f, #ef8aa0);
  transition: height 400ms ease;
}
.week-bar.empty { background: rgba(60,32,24,0.12); }
.week-bar.today {
  background: linear-gradient(180deg, #75c8bd, #158f92);
  box-shadow: 0 0 0 2px rgba(21,143,146,0.25);
}
.week-day {
  font-size: 0.62rem;
  font-weight: 900;
  color: var(--bark-mid);
}
.week-col.is-today .week-day { color: var(--teal); }

/* ── Achievements / badges ──────────────────────────────────────────────── */
/* ── Toast ──────────────────────────────────────────────────────────────── */
.toast {
  position: absolute;
  left: 50%;
  bottom: 120px;
  transform: translateX(-50%) translateY(20px);
  z-index: 120;
  max-width: 84%;
  padding: 11px 18px;
  border-radius: 999px;
  background: var(--bark);
  color: var(--cream);
  font-size: 0.84rem;
  font-weight: 900;
  box-shadow: 0 8px 24px rgba(60,32,24,0.35);
  opacity: 0;
  pointer-events: none;
  transition: opacity 280ms ease, transform 280ms cubic-bezier(0.34,1.56,0.64,1);
  text-align: center;
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
.toast.hidden { display: none; }

/* ── First-run onboarding ───────────────────────────────────────────────── */
.onboarding {
  position: absolute;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: linear-gradient(160deg, #fff2dd 0%, #ffe6ea 55%, #e8f3ee 100%);
}
.onboarding.hidden { display: none; }

.onboard-card {
  position: relative;
  width: 100%;
  max-width: 360px;
  padding: 26px 22px 22px;
  border-radius: 28px;
  background: var(--cream);
  border: 3px solid var(--bark);
  box-shadow: 0 16px 50px rgba(60,32,24,0.28);
  text-align: center;
}

.onboard-skip {
  position: absolute;
  top: 8px; right: 8px;
  min-height: 40px;
  padding: 8px 14px;
  border: none;
  background: none;
  color: var(--bark-mid);
  font-size: 0.8rem;
  font-weight: 850;
}

.onboard-visual {
  height: 150px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 6px;
}
.onboard-img {
  max-height: 150px;
  max-width: 70%;
  object-fit: contain;
  animation: onboard-pop 0.5s ease-out;
}
.onboard-emoji {
  font-size: 5rem;
  line-height: 1;
  animation: onboard-pop 0.5s ease-out;
}
.onboard-emoji.hidden, .onboard-img.hidden { display: none; }
@keyframes onboard-pop {
  0%   { transform: scale(0.6) translateY(10px); opacity: 0; }
  60%  { transform: scale(1.06); }
  100% { transform: scale(1) translateY(0); opacity: 1; }
}

.onboard-card h2 {
  font-size: 1.4rem;
  color: var(--bark);
  margin-bottom: 8px;
}
.onboard-card p {
  color: var(--bark-mid);
  font-size: 0.92rem;
  font-weight: 700;
  line-height: 1.4;
  min-height: 66px;
  margin: 0 6px;
}

.onboard-name-input {
  display: block;
  width: 100%;
  margin: 2px 0 6px;
  padding: 12px 16px;
  border: 2px solid var(--line);
  border-radius: 14px;
  background: rgba(255,255,255,0.8);
  font-size: 1.05rem;
  font-weight: 850;
  color: var(--bark);
  text-align: center;
  outline: none;
}
.onboard-name-input:focus { border-color: var(--teal); background: #fff; }

.onboard-dots {
  display: flex;
  justify-content: center;
  gap: 7px;
  margin: 16px 0 18px;
}
.onboard-dots span {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: rgba(60,32,24,0.2);
  transition: width 200ms, background 200ms;
}
.onboard-dots span.on {
  width: 22px;
  border-radius: 999px;
  background: var(--bark);
}

.onboard-actions {
  display: flex;
  gap: 10px;
  align-items: center;
}
.onboard-next { flex: 1; }
.onboard-back {
  min-width: 76px;
}
.onboard-back.hidden { display: none; }

.settings-action-btn {
  min-height: 34px;
  padding: 0 16px;
  border: 2px solid var(--teal);
  border-radius: 999px;
  background: var(--mint);
  color: var(--bark);
  font-size: 0.78rem;
  font-weight: 950;
  flex-shrink: 0;
}
/* Name button: action label left, pearl cost right */
.settings-action-btn.name-btn {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
}
.settings-action-btn.danger {
  border-color: var(--danger);
  background: #fff0f3;
  color: var(--danger);
}

/* ── Boba map sheet ─────────────────────────────────────────────────────── */
.map-body {
  display: flex;
  flex-direction: column;
  padding-bottom: 20px;
}
.map-intro {
  margin: 0 0 10px;
  font-size: 0.8rem;
  font-weight: 800;
  color: var(--bark-mid);
  line-height: 1.35;
}
.map-intro strong { color: var(--bark); }

.map-perk-banner {
  margin-bottom: 10px;
  padding: 11px 13px;
  border: 2px solid var(--teal);
  border-radius: 14px;
  background: var(--mint);
  color: var(--bark);
  font-size: 0.84rem;
  font-weight: 900;
}
.map-perk-banner.hidden { display: none; }

.map-canvas {
  position: relative;
  height: 42vh;
  min-height: 240px;
  flex: none;
  border-radius: 18px;
  overflow: hidden;
  border: 2px solid var(--line);
  background: #e8eef0;
}
/* Warm + soften the raw OpenStreetMap tiles so the map reads cozy/on-brand. */
.map-canvas .leaflet-tile {
  filter: sepia(0.22) saturate(0.78) brightness(1.05) contrast(0.93);
}
/* Floating status card over the map — no longer a full cover, so an error or
   "no shops" note never hides the (still useful) map tiles underneath. */
.map-status {
  position: absolute;
  left: 12px; right: 12px; top: 12px;
  z-index: 500;   /* above Leaflet tiles/markers, below its popups & controls */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  text-align: center;
  padding: 12px 16px;
  background: rgba(251, 246, 239, 0.95);
  border: 1.5px solid var(--line);
  border-radius: 14px;
  box-shadow: 0 4px 12px rgba(60, 32, 24, 0.12);
  color: var(--bark-mid);
  font-size: 0.86rem;
  font-weight: 850;
}
.map-status.hidden { display: none; }
.map-retry {
  border: none;
  border-radius: 999px;
  padding: 8px 18px;
  background: var(--boba, #c98a5e);
  color: #fff;
  font: inherit;
  font-weight: 900;
  cursor: pointer;
  min-height: 36px;
}

/* Leaflet marker pins drawn as emoji bubbles (no marker image assets needed) */
.boba-pin {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px; height: 34px;
  border-radius: 50% 50% 50% 0;
  background: var(--cream);
  border: 2.5px solid var(--bark);
  transform: rotate(-45deg);
  box-shadow: 0 2px 6px rgba(60,32,24,0.3);
  font-size: 1rem;
}
.boba-pin > span { transform: rotate(45deg); }
.boba-pin.partner { background: var(--gold); border-color: var(--bark); }
.boba-pin.me { background: var(--teal); border-color: var(--cream); }

.leaflet-popup-content { font-family: inherit; color: var(--bark); }
.map-pop-name { font-weight: 950; font-size: 0.9rem; }
.map-pop-meta { color: var(--bark-mid); font-size: 0.76rem; margin-top: 2px; }

/* Nearby-shops list under the map */
.map-shop-list { margin-top: 12px; display: flex; flex-direction: column; gap: 8px; }
.map-shop-list.hidden { display: none; }
.map-list-head {
  font-size: 0.74rem;
  font-weight: 950;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--teal);
  margin: 2px 2px 2px;
}
.map-shop-item {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  padding: 11px 13px;
  border: 2px solid var(--ui-edge);
  border-radius: 14px;
  background: var(--ui-paper);
  box-shadow: var(--ui-sheen), 0 1px 0 rgba(96,58,36,0.06), 0 3px 9px -6px rgba(60,32,24,0.2);
  text-align: left;
  transition: transform 90ms ease, background 160ms, border-color 160ms;
}
.map-shop-item:active { transform: scale(0.98); background: var(--mint); }
.map-shop-emoji {
  flex-shrink: 0;
  width: 34px; height: 34px;
  display: flex; align-items: center; justify-content: center;
  font-size: 1.05rem;
  border-radius: 50%;
  background: #fff;
  border: 2px solid rgba(60,32,24,0.14);
}
.map-shop-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
.map-shop-name {
  font-weight: 900;
  font-size: 0.92rem;
  color: var(--bark);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.map-shop-dist { font-size: 0.74rem; font-weight: 800; color: var(--bark-mid); }
.map-shop-go { flex-shrink: 0; font-size: 1.3rem; font-weight: 900; color: var(--bark-mid); opacity: 0.6; }
.map-pop-perk {
  margin-top: 6px;
  padding: 5px 8px;
  border-radius: 8px;
  background: var(--mint);
  border: 1.5px dashed var(--teal);
  font-size: 0.76rem;
  font-weight: 850;
}
.map-pop-perk.locked { background: rgba(60,32,24,0.05); border-color: var(--line); color: var(--bark-mid); }

/* ── Pearl economy info card (Settings sheet) ───────────────────────────── */
.pearl-info-card {
  padding: 16px 16px 14px;
  border: 2px solid var(--line);
  border-radius: 18px;
  background: linear-gradient(135deg, #fff7ec 0%, #fdeed8 100%);
  box-shadow: 0 3px 0 rgba(60,32,24,0.06);
}
.pearl-info-card h3 {
  margin: 0 0 10px;
  color: var(--bark);
  font-size: 0.86rem;
}
.pearl-info-rows {
  display: grid;
  gap: 8px;
  margin-bottom: 10px;
}
.pearl-info-row {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 0.82rem;
  font-weight: 850;
  color: var(--bark);
}
.pearl-info-pearl {
  width: 24px;
  height: 24px;
  object-fit: contain;
  flex-shrink: 0;
}
.pearl-info-row strong { color: var(--bark); font-weight: 950; }

/* ── Blocked apps / restriction preview ─────────────────────────────────── */
.blocked-panel {
  padding: 12px;
  border: 2px solid var(--ui-edge);
  border-radius: 16px;
  background: var(--ui-paper);
  box-shadow: var(--ui-sheen), 0 1px 0 rgba(96,58,36,0.07), 0 5px 12px -6px rgba(60,32,24,0.24);
}
.app-locks {
  display: grid;
  grid-template-columns: repeat(2, minmax(0,1fr));
  gap: 7px;
}
.app-locks label {
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 34px;
  padding: 6px 8px;
  border-radius: 10px;
  background: rgba(60,32,24,0.05);
  color: var(--bark-mid);
  font-size: 0.78rem;
  font-weight: 850;
}
.app-locks input { accent-color: var(--teal); }
.soon-badge {
  font-size: 0.6rem;
  font-weight: 900;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: #fff;
  background: var(--teal);
  padding: 3px 8px;
  border-radius: 999px;
}
.app-locks input:disabled { opacity: 0.8; }

/* ── Shelf & reward lists ───────────────────────────────────────────────── */
/* Drinks + treats are compact one-row horizontal scrollers so they don't
   dominate the Settings sheet as the collection grows. */

/* ── Shop skin cards ────────────────────────────────────────────────────── */
.shop-grid { display: grid; gap: 6px; }

.shop-category-head {
  margin: 14px 0 6px;
  font-size: 0.7rem;
  font-weight: 950;
  color: var(--teal);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.shop-card {
  display: grid;
  grid-template-columns: 48px 1fr auto;
  gap: 10px;
  align-items: center;
  padding: 10px 12px;
  border: 2px solid var(--ui-edge);
  border-radius: 14px;
  background: var(--ui-paper);
  box-shadow: var(--ui-sheen), 0 1px 0 rgba(96,58,36,0.08), 0 4px 10px -5px rgba(60,32,24,0.24);
  transition: transform 90ms ease, box-shadow 90ms ease;
}
.shop-card:active {
  transform: translateY(2px);
  box-shadow: var(--ui-sheen), 0 1px 4px -2px rgba(60,32,24,0.28);
}

.shop-preview {
  width: 48px; height: 48px;
  border-radius: 12px;
  border: 2px solid var(--line);
  overflow: hidden;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.shop-skin-preview {
  width: 100%; height: 100%;
  object-fit: cover;
  border-radius: 10px;
}

/* Solid colour swatch for Backgrounds shop cards */
.shop-theme-preview {
  width: 100%;
  height: 100%;
  border-radius: 10px;
  box-shadow: inset 0 -10px 0 rgba(60,32,24,0.08), inset 0 2px 0 rgba(255,255,255,0.18);
}

.shop-card strong { display: block; margin-bottom: 2px; font-size: 0.82rem; color: var(--bark); }
.shop-card small  { color: var(--bark-mid); font-size: 0.7rem; }

.shop-card-action {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 4px;
}

.shop-buy-btn {
  min-height: 32px;
  padding: 0 12px;
  border: 2px solid var(--bark);
  border-radius: 999px;
  background: var(--bark);
  color: var(--cream);
  font-size: 0.76rem;
  font-weight: 950;
  white-space: nowrap;
  /* Same key as the main CTA, scaled down — the offset-square shadow it had was
     a different depth language from everything else on screen. */
  background: linear-gradient(180deg, #55362a 0%, #3c2018 100%);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.18),
    0 3px 0 #2a1610,
    0 6px 12px -6px rgba(60,32,24,0.45);
  transition: transform 70ms ease, box-shadow 70ms ease;
}
.shop-buy-btn:active {
  transform: translateY(2px);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.14),
    0 1px 0 #2a1610,
    0 3px 8px -5px rgba(60,32,24,0.4);
}
.shop-buy-btn:disabled { opacity: 0.36; cursor: not-allowed; box-shadow: none; }

.shop-equip-btn {
  min-height: 32px;
  padding: 0 12px;
  border: 2px solid var(--teal);
  border-radius: 999px;
  background: var(--mint);
  color: var(--bark);
  font-size: 0.76rem;
  font-weight: 950;
  white-space: nowrap;
}

.shop-equipped-badge {
  padding: 3px 9px;
  border: 2px solid var(--teal);
  border-radius: 999px;
  background: var(--mint);
  color: var(--teal);
  font-size: 0.64rem;
  font-weight: 950;
  white-space: nowrap;
}

.shop-unequip-btn {
  padding: 2px 8px;
  border: 1.5px solid var(--line);
  border-radius: 8px;
  background: transparent;
  color: var(--bark-mid);
  font-size: 0.64rem;
  font-weight: 850;
}

.shop-preview-btn {
  min-height: 32px;
  padding: 0 12px;
  border: 2px solid var(--gold);
  border-radius: 999px;
  background: #fff4cf;
  color: #7a4e0e;
  font-size: 0.76rem;
  font-weight: 950;
  white-space: nowrap;
  box-shadow: 2px 2px 0 rgba(60,32,24,0.1);
}

.shop-preview { position: relative; }
.shop-premium-flag {
  position: absolute;
  top: -6px;
  right: -6px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--gold);
  color: var(--bark);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.62rem;
  font-weight: 950;
  border: 1.5px solid var(--bark);
  box-shadow: 0 1px 3px rgba(60,32,24,0.25);
}

/* ── Dialogs ────────────────────────────────────────────────────────────── */
dialog {
  border: 0;
  padding: 0;
  background: transparent;
}
dialog::backdrop { background: rgba(60,32,24,0.45); }

.reward-card {
  position: relative;
  width: min(380px, calc(100vw - 32px));
  padding: 22px 24px 24px;
  border: 3px solid var(--bark);
  border-radius: 26px;
  background: linear-gradient(180deg, #fff6e6 0%, var(--cream) 46%);
  /* Keeps its chunky offset (this is the celebration moment) but gains the same
     ambient falloff everything else has, so it sits in the same room. */
  box-shadow:
    inset 0 2px 0 rgba(255,255,255,0.9),
    8px 8px 0 rgba(60,32,24,0.14),
    0 18px 40px -18px rgba(60,32,24,0.45);
  color: var(--bark);
  text-align: center;
  overflow: hidden;
}
.reward-card .eyebrow { text-align: center; }
.reward-card h2 {
  margin-bottom: 6px;
  font-size: 1.35rem;
  color: var(--bark);
}
.reward-card #rewardCopy {
  margin: 0 0 4px;
  color: var(--bark-mid);
  font-size: 0.86rem;
  font-weight: 800;
}

/* The finished drink, presented proudly at the top */
.reward-drink {
  --drink-color: var(--tea);
  position: relative;
  width: 108px;
  height: 130px;
  margin: 6px auto 12px;
}
/* Warm celebratory glow behind the cup */
.reward-drink::before {
  content: "";
  position: absolute;
  left: 50%; top: 54%;
  width: 140px; height: 140px;
  transform: translate(-50%, -50%);
  background: radial-gradient(circle, rgba(255,206,112,0.55) 0%, rgba(255,206,112,0) 68%);
  z-index: 0;
  pointer-events: none;
}
/* Crafted SVG cup, shown full — matches the focus-screen cup. */
.reward-cup-svg { position: relative; z-index: 1; width: 100%; height: 100%; display: block; overflow: visible; }
.reward-liquid { fill: var(--drink-color, #c98a5e); }
/* Proud little pop-in when the celebration opens */
dialog[open] .reward-drink { animation: reward-drink-pop 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) both; }
@keyframes reward-drink-pop {
  0%   { transform: scale(0.5) translateY(10px); opacity: 0; }
  60%  { transform: scale(1.08) translateY(0); opacity: 1; }
  100% { transform: scale(1); }
}

/* Confetti that pops when the dialog opens */
.reward-confetti {
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 0;
  text-align: center;
  pointer-events: none;
  z-index: 5;
}
.reward-confetti span {
  position: absolute;
  top: 8px;
  font-size: 1.2rem;
  opacity: 0;
}
.reward-confetti span:nth-child(1) { left: 8%;  }
.reward-confetti span:nth-child(2) { left: 24%; }
.reward-confetti span:nth-child(3) { left: 40%; }
.reward-confetti span:nth-child(4) { left: 56%; }
.reward-confetti span:nth-child(5) { left: 72%; }
.reward-confetti span:nth-child(6) { left: 86%; }
.reward-confetti span:nth-child(7) { left: 50%; }
dialog[open] .reward-confetti span { animation: reward-confetti-fall 1.2s ease-out forwards; }
dialog[open] .reward-confetti span:nth-child(2) { animation-delay: 0.08s; }
dialog[open] .reward-confetti span:nth-child(3) { animation-delay: 0.04s; }
dialog[open] .reward-confetti span:nth-child(4) { animation-delay: 0.12s; }
dialog[open] .reward-confetti span:nth-child(5) { animation-delay: 0.06s; }
dialog[open] .reward-confetti span:nth-child(6) { animation-delay: 0.10s; }
dialog[open] .reward-confetti span:nth-child(7) { animation-delay: 0.02s; }
@keyframes reward-confetti-fall {
  0%   { opacity: 0; transform: translateY(-14px) rotate(0deg) scale(0.6); }
  25%  { opacity: 1; }
  100% { opacity: 0; transform: translateY(64px) rotate(40deg) scale(1); }
}

/* Pearls earned */
.reward-earned {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 4px 0 2px;
  padding: 6px 14px;
  border-radius: 999px;
  background: #fff4cf;
  border: 2px solid var(--gold);
  font-weight: 950;
  font-size: 0.92rem;
  color: #7a4e0e;
}
.reward-earned .reward-pearl { width: 22px; height: 22px; object-fit: contain; }

.partner {
  margin: 14px 0 4px;
  padding: 13px;
  border: 2px dashed var(--line);
  border-radius: 14px;
  background: rgba(60,32,24,0.04);
  font-weight: 850;
  color: var(--bark-mid);
  font-size: 0.88rem;
}
/* When a real partner perk was earned, present it like a coupon */
.partner.has-perk {
  border-color: var(--teal);
  background: var(--mint);
  color: var(--bark);
  font-weight: 950;
}

.dialog-actions {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: center;
  gap: 10px;
  margin-top: 16px;
}
.dialog-actions form { display: contents; }

/* Share = the hero action on the reward card (caramel), Save = quieter. */
.reward-card .secondary {
  min-height: 52px;
  padding: 0 20px;
  border: 2px solid var(--bark);
  border-radius: 999px;
  background: var(--gold, #d99e5c);
  color: var(--bark);
  font-weight: 950;
  font-size: 1rem;
  box-shadow: 3px 3px 0 rgba(60,32,24,0.16);
  transition: transform 90ms ease, box-shadow 180ms;
}
.reward-card .secondary:active { transform: scale(0.95); }
.reward-card .secondary:disabled { opacity: 0.7; }
.reward-card .primary {
  background: transparent;
  color: var(--bark-mid);
  border-color: rgba(60,32,24,0.22);
  box-shadow: none;
}

.primary {
  min-height: 44px;
  padding: 0 20px;
  border: 2px solid var(--bark);
  border-radius: 999px;
  background: linear-gradient(180deg, #55362a 0%, #40241b 62%, #361e15 100%);
  color: var(--cream);
  font-weight: 950;
  font-size: 0.88rem;
  /* A real key: bright top edge, a solid lip underneath, and ambient shadow.
     It depresses onto its lip when pressed instead of just dimming. */
  box-shadow:
    inset 0 1.5px 0 rgba(255,255,255,0.20),
    inset 0 -2px 0 rgba(0,0,0,0.18),
    0 4px 0 #2a1610,
    0 10px 18px -8px rgba(60,32,24,0.48);
  transition: transform 70ms ease, box-shadow 70ms ease;
}
.primary:active {
  transform: translateY(3px);
  box-shadow:
    inset 0 1.5px 0 rgba(255,255,255,0.14),
    inset 0 -2px 0 rgba(0,0,0,0.18),
    0 1px 0 #2a1610,
    0 4px 10px -6px rgba(60,32,24,0.44);
}
.quiet {
  min-height: 44px;
  padding: 0 16px;
  border: 2px solid rgba(60,32,24,0.2);
  border-radius: 999px;
  background: var(--cream);
  color: var(--bark-mid);
  font-weight: 850;
  font-size: 0.88rem;
}

/* ── Tactile press feedback (springy squish on tap) ─────────────────────── */
.size-btn, .choice, .base-option, .adj-btn, .primary, .quiet,
.shop-buy-btn, .shop-equip-btn, .shop-unequip-btn, .shop-preview-btn,
.sheet-close, .settings-action-btn, .game-quit-btn, .toggle {
  transition: transform 90ms ease, background 180ms, color 180ms, box-shadow 180ms;
}
/* Squish is for flat controls only. .primary and .shop-buy-btn are KEYS with a
   lip beneath them and depress with translateY instead — they were listed here
   too, and since this rule comes later it won the transform while their own
   rule still removed the lip, so a press both squished AND flattened them. Two
   depth metaphors at once reads as a glitch. */
.size-btn:active, .choice:active, .base-option:active, .adj-btn:active,
.quiet:active, .shop-equip-btn:active,
.shop-unequip-btn:active, .shop-preview-btn:active, .sheet-close:active,
.settings-action-btn:active, .game-quit-btn:active {
  transform: scale(0.93);
}

/* ── Game overlays ──────────────────────────────────────────────────────── */
.pearl-game {
  position: absolute;
  inset: 0;
  z-index: 20;
  flex-direction: column;
  /* Same fix as pong. The old backdrop was over-scaled badly enough that
     cropped clouds carried heavier outlines than the catcher itself. */
  background: #f7ece0 url('assets/catch-board.jpg') center / 100% 100% no-repeat;
  border-radius: 0;
}

.game-hud {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 14px 8px;
  background: rgba(255,255,255,0.65);
  border-bottom: 2px solid rgba(180,150,230,0.25);
  flex-shrink: 0;
}
.game-score, .game-timer { font-size: 1.05rem; font-weight: 950; color: var(--bark); }
.game-timer { font-variant-numeric: tabular-nums; }
.game-quit-btn {
  min-height: 44px;            /* Apple 44px min tap target */
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0 16px;
  border: 2px solid rgba(60,32,24,0.3);
  border-radius: 999px;
  background: rgba(253,245,236,0.9);
  color: var(--bark-mid);
  font-size: 0.72rem;
  font-weight: 900;
}

.game-area {
  position: relative;
  flex: 1;
  overflow: hidden;
}

.falling-pearl {
  position: absolute;
  width: 26px; height: 26px;
  border-radius: 50%;
  background:
    radial-gradient(circle at 35% 30%, rgba(255,255,255,0.55), transparent 55%),
    #3a2a20;
  box-shadow: inset -4px -4px 0 rgba(0,0,0,0.28), 0 2px 3px rgba(60,32,24,0.25);
  pointer-events: none;
}
/* Golden pearl — worth 3, glows so it reads as the "go for it" target. */
.falling-golden {
  background:
    radial-gradient(circle at 35% 28%, rgba(255,255,255,0.85), transparent 55%),
    radial-gradient(circle at 60% 70%, #e8a93a, #c47f1e);
  box-shadow: inset -3px -3px 0 rgba(140,80,0,0.35),
              0 0 10px rgba(245,190,90,0.85), 0 2px 3px rgba(60,32,24,0.25);
  animation: pearl-glow 0.9s ease-in-out infinite alternate;
}
@keyframes pearl-glow { from { filter: brightness(1); } to { filter: brightness(1.25); } }
/* Ice cube — junk to DODGE. Cool blue, square-ish so it's unmistakable. */
.falling-ice {
  border-radius: 7px;
  background:
    linear-gradient(135deg, rgba(255,255,255,0.7), transparent 45%),
    linear-gradient(160deg, #cfedefde, #9fd4e6);
  box-shadow: inset -3px -3px 0 rgba(90,150,180,0.4), 0 2px 3px rgba(60,90,110,0.3);
  border: 2px solid rgba(255,255,255,0.6);
}

/* ── The catcher: a cute, wide, code-drawn boba cup (no more tiny PNG) ─── */
.game-cup {
  position: absolute;
  bottom: 12px;
  width: 104px; height: 76px;
  filter: drop-shadow(0 5px 6px rgba(60,32,24,0.28));
  transform-origin: 50% 100%;
  will-change: transform;
}
/* The open mouth (an oval rim) — visually where pearls drop in. */
.cup-mouth {
  position: absolute;
  top: 0; left: 4px; right: 4px;
  height: 22px;
  border-radius: 50%;
  background: linear-gradient(180deg, #8a5a3c, #6e4226);
  box-shadow: inset 0 -4px 0 rgba(0,0,0,0.18);
  border: 3px solid var(--bark);
  z-index: 2;
}
/* The tapering cup body, milk-tea coloured with a thick rounded outline. */
.cup-body {
  position: absolute;
  top: 11px; left: 0; right: 0; bottom: 0;
  background: linear-gradient(180deg, #f3d9b8, #e8c39a 60%, #dcae84);
  border: 3px solid var(--bark);
  border-top: none;
  border-radius: 14px 14px 26px 26px / 14px 14px 40px 40px;
  /* trapezoid taper toward the base */
  clip-path: polygon(0 0, 100% 0, 88% 100%, 12% 100%);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  overflow: hidden;
}
.cup-face {
  font-size: 0.9rem;
  color: var(--bark);
  margin-bottom: 14px;
  opacity: 0.65;
  letter-spacing: 1px;
}
/* Catch squash-pop. */
.game-cup.cup-bump { animation: cup-bump 0.22s ease-out; }
@keyframes cup-bump {
  0%   { transform: scale(1, 1); }
  35%  { transform: scale(1.14, 0.86); }
  70%  { transform: scale(0.96, 1.05); }
  100% { transform: scale(1, 1); }
}

/* Floating "+1"/"+3"/"brr!" tag at the catch point. */
.catch-float {
  position: absolute;
  transform: translate(-50%, -50%);
  font-weight: 950;
  font-size: 1rem;
  color: var(--bark);
  text-shadow: 0 1px 0 rgba(255,255,255,0.7);
  pointer-events: none;
  z-index: 4;
  animation: catch-float 0.65s ease-out forwards;
}
.catch-float-golden { color: #c47f1e; font-size: 1.2rem; }
.catch-float-ice    { color: #4a90b8; }
@keyframes catch-float {
  0%   { opacity: 0; transform: translate(-50%, -40%) scale(0.6); }
  25%  { opacity: 1; transform: translate(-50%, -70%) scale(1.1); }
  100% { opacity: 0; transform: translate(-50%, -150%) scale(1); }
}

/* Live combo banner. */
.game-combo {
  position: absolute;
  top: 12px; left: 50%;
  transform: translateX(-50%) scale(0.7);
  padding: 4px 14px;
  border-radius: 999px;
  background: var(--teal);
  color: #fff;
  font-weight: 950;
  font-size: 0.95rem;
  box-shadow: 0 3px 0 rgba(60,32,24,0.25);
  opacity: 0;
  pointer-events: none;
  z-index: 4;
}
.game-combo.show { animation: combo-pop 0.7s ease-out; }
@keyframes combo-pop {
  0%   { opacity: 0; transform: translateX(-50%) scale(0.5); }
  20%  { opacity: 1; transform: translateX(-50%) scale(1.15); }
  40%  { transform: translateX(-50%) scale(1); }
  80%  { opacity: 1; }
  100% { opacity: 0; transform: translateX(-50%) scale(1); }
}

/* Bomb — DODGE it (catching costs points). Rendered as a 💣 emoji (no pearl bg). */
.falling-bomb {
  background: none;
  box-shadow: none;
  border: none;
  display: flex; align-items: center; justify-content: center;
  font-size: 26px; line-height: 1;
  filter: drop-shadow(0 2px 2px rgba(60,32,24,0.3));
}
.catch-float-bomb { color: #d8453a; font-size: 1.3rem; }

/* On-screen press-and-hold steering arrows (bottom corners, thumb-friendly). */
.catch-arrow {
  position: absolute;
  bottom: 16px;
  width: 66px; height: 66px;
  border-radius: 50%;
  border: 2px solid var(--ui-edge);
  background: var(--ui-paper);
  color: var(--bark);
  font-size: 2.1rem; font-weight: 950; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  /* A key, like every other button — it depresses onto its lip on press. */
  box-shadow:
    var(--ui-sheen),
    0 4px 0 rgba(96,58,36,0.35),
    0 10px 18px -8px rgba(60,32,24,0.4);
  touch-action: none; user-select: none; -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
  z-index: 6;
  opacity: 0.9;
}
.catch-arrow:active {
  transform: translateY(3px);
  background: var(--mint);
  border-color: var(--teal);
  box-shadow: var(--ui-sheen), 0 1px 0 rgba(96,58,36,0.35), 0 4px 10px -6px rgba(60,32,24,0.36);
}
.catch-arrow-l { left: 16px; padding-right: 4px; }
.catch-arrow-r { right: 16px; padding-left: 4px; }

/* Red vignette flash when ice is caught. */
.game-area.area-flash::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 3;
  box-shadow: inset 0 0 60px 10px rgba(220,80,80,0.55);
  animation: area-flash 0.4s ease-out forwards;
}
@keyframes area-flash { from { opacity: 1; } to { opacity: 0; } }

.game-result {
  position: absolute;
  inset: 0;
  z-index: 5;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 24px;
  background: rgba(253,245,236,0.82);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  text-align: center;
}
.game-result h3 { margin: 0 0 10px; color: var(--bark); }

/* ── Plinko game overlay ────────────────────────────────────────────────── */
.plinko-game {
  position: absolute;
  inset: 0;
  z-index: 20;
  flex-direction: column;
  /* A purpose-drawn cabinet, authored 9:19 so it fills the game area without
     cropping. The old background was the shop backdrop cover-scaled into a
     different aspect, which is where the hard seam across the middle and the
     cropped plant bleeding in from the corner came from. */
  background: #f6e0c8 url('assets/plinko-board.png') center / 100% 100% no-repeat;
  overflow: hidden;
}
.plinko-canvas {
  flex: 1;
  width: 100%;
  display: block;
  min-height: 0;
}
.plinko-controls {
  padding: 8px 16px 12px;
  flex-shrink: 0;
}
.plinko-controls .primary { width: 100%; border-radius: 999px; }

.plinko-result {
  position: absolute;
  inset: 0;
  z-index: 5;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 24px;
  background: rgba(253,245,236,0.82);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border-radius: 0;
  text-align: center;
}
.plinko-result h3 { margin: 0 0 6px; font-size: 1.8rem; color: var(--bark); }
.plinko-result-actions { display: flex; gap: 10px; }

/* ── Cup Pong game ──────────────────────────────────────────────────────── */
.pong-game {
  position: absolute;
  inset: 0;
  z-index: 20;
  flex-direction: column;
  /* Authored at the game area's own aspect and sized 100% 100%, so nothing is
     cropped and the composition is identical on every screen. The old backdrop
     was the shop background cover-scaled into a different shape, which left the
     bottom 45% a dead colour void. */
  background: #f3e6d6 url('assets/pong-board.jpg') center / 100% 100% no-repeat;
  overflow: hidden;
}
.pong-canvas {
  flex: 1;
  width: 100%;
  display: block;
  min-height: 0;
  touch-action: none;   /* let us handle drag without the page scrolling */
}
.pong-hint {
  flex-shrink: 0;
  text-align: center;
  padding: 8px 16px 14px;
  font-size: 0.78rem;
  font-weight: 850;
  color: var(--bark-mid);
}
.pong-result {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 24px;
  background: rgba(253,245,236,0.82);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  text-align: center;
}
.pong-result h3 { margin: 0 0 6px; font-size: 1.8rem; color: var(--bark); }

.shop-sticker-preview {
  font-size: 0.44rem;
  font-weight: 950;
  text-align: center;
  line-height: 1.2;
  padding: 2px;
  word-break: break-word;
  color: var(--bark);
}

/* ── Utility ────────────────────────────────────────────────────────────── */
.hidden { display: none; }

/* ── Keyframes ──────────────────────────────────────────────────────────── */
@keyframes glow {
  0%, 100% { opacity: 0.75; }
  50%       { opacity: 1; }
}
@keyframes topping-bob {
  0%, 100% { translate: 0 0; }
  50%       { translate: 0 -5px; }
}
@keyframes tea-shine {
  0%, 100% { transform: translateX(-68%); }
  50%       { transform: translateX(68%); }
}
@keyframes steam-rise {
  0%   { transform: translateY(10px) scaleY(0.78); opacity: 0; }
  45%  { opacity: 1; }
  100% { transform: translateY(-14px) scaleY(1.15); opacity: 0; }
}
@keyframes cup-spill {
  0%   { transform: rotate(0deg); opacity: 1; }
  20%  { transform: rotate(12deg); }
  75%  { transform: rotate(88deg) translateX(10px); opacity: 1; }
  90%  { transform: rotate(90deg) translateX(12px); opacity: 0.5; }
  100% { transform: rotate(90deg) translateX(12px); opacity: 0; }
}

/* ── Media: safe-area insets on iPhone ─────────────────────────────────── */
@supports (padding-top: env(safe-area-inset-top)) {
  .top-hud { padding-top: max(52px, env(safe-area-inset-top) + 12px); }
  /* Keep the goal pill BELOW the safe-area-pushed top HUD (notch / Dynamic Island)
     so they don't overlap — the bug was a fixed top:92px ignoring the inset. */
  .daily-goal { top: max(108px, calc(env(safe-area-inset-top) + 68px)); }
  .bottom-bar { padding-bottom: max(28px, env(safe-area-inset-bottom) + 12px); }
  /* Keep game HUD/controls clear of the notch + home indicator. */
  .game-hud { padding-top: max(10px, calc(env(safe-area-inset-top) + 6px)); }
  .game-cup { bottom: max(12px, calc(env(safe-area-inset-bottom) + 6px)); }
  .plinko-controls { padding-bottom: max(12px, calc(env(safe-area-inset-bottom) + 8px)); }
  .pong-hint { padding-bottom: max(14px, calc(env(safe-area-inset-bottom) + 8px)); }
  .sheet-body { padding-bottom: max(40px, calc(env(safe-area-inset-bottom) + 24px)); }
  /* Catch's on-screen arrows must clear the home indicator gesture zone. */
  .catch-arrow { bottom: max(16px, calc(env(safe-area-inset-bottom) + 8px)); }
}

/* Bottom-nav "Squad" people icon (matches the other CSS-mask icons) */

/* ── Study Squad (friends leaderboard) ──────────────────────────────────────── */
.squad-me {
  display: flex; align-items: center; gap: 14px;
  padding: 14px; margin-bottom: 12px;
  border-radius: 20px;
  background: linear-gradient(180deg, #fff6e6, var(--cream));
  border: 2px solid var(--line);
}
.squad-avatar {
  width: 60px; height: 60px; flex-shrink: 0;
  object-fit: contain;
  border-radius: 50%; background: #fff;
  border: 2px solid rgba(60,32,24,0.14);
}
.squad-me-info { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.squad-name-btn {
  display: flex; align-items: center; gap: 6px;
  background: none; border: none; padding: 0; color: var(--bark);
  max-width: 100%; min-width: 0;
}
.squad-name { font-size: 1.1rem; font-weight: 950; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.squad-edit { font-size: 0.8rem; opacity: 0.7; }
.squad-stats { font-size: 0.8rem; font-weight: 850; color: var(--bark-mid); }

.squad-share {
  width: 100%; padding: 14px; margin-bottom: 8px;
  border: none; border-radius: 999px;
  background: var(--bark); color: var(--cream);
  font-size: 0.98rem; font-weight: 950;
  box-shadow: 0 6px 18px rgba(60,32,24,0.25);
  transition: transform 80ms;
}
.squad-share:active { transform: scale(0.97); }

/* Your own friend code, readable so you can say it out loud or type it in
   manually. Before this it existed only inside the share sheet's message. */
.squad-mycode.hidden { display: none; }   /* same trap as .block-pill.hidden */
.squad-mycode {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin: 0 2px 10px; padding: 10px 14px;
  border-radius: 16px; background: rgba(60,32,24,0.06);
}
.squad-mycode-label { font-size: 0.76rem; font-weight: 850; color: var(--bark-mid); }
.squad-mycode-value {
  border: none; background: none; padding: 4px 6px;
  font-size: 1.15rem; font-weight: 950; letter-spacing: 0.16em;
  color: var(--bark); font-variant-numeric: tabular-nums;
}
.squad-mycode-value:active { transform: scale(0.96); }
.squad-hint { font-size: 0.76rem; font-weight: 750; color: var(--bark-mid); line-height: 1.45; margin: 0 2px 12px; }

.squad-add { display: flex; gap: 8px; margin-bottom: 18px; }
.squad-input {
  flex: 1; min-width: 0; padding: 12px 14px;
  border: 2px solid var(--ui-edge); border-radius: 14px;
  /* Inputs are the inverse of a key: recessed rather than raised, so the one
     control you type into never looks like something you press. */
  background: rgba(255,252,246,0.9); color: var(--bark);
  box-shadow: inset 0 2px 4px rgba(96,58,36,0.16), inset 0 -1px 0 rgba(255,255,255,0.8);
  font-size: 0.85rem; font-weight: 700; font-family: inherit;
}
.squad-input:focus-visible { outline: none; border-color: var(--teal); }
.squad-add-btn {
  flex-shrink: 0; padding: 0 18px;
  border: 2px solid var(--teal); border-radius: 14px;
  background: var(--mint); color: var(--bark);
  font-size: 0.9rem; font-weight: 950;
}

.squad-board-head {
  font-size: 0.74rem; font-weight: 950; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--teal); margin: 0 2px 8px;
}
.squad-board { display: flex; flex-direction: column; gap: 8px; }
.squad-row {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px;
  border: 2px solid var(--ui-edge); border-radius: 16px;
  background: var(--ui-paper);
  box-shadow: var(--ui-sheen), 0 1px 0 rgba(96,58,36,0.06), 0 3px 9px -6px rgba(60,32,24,0.2);
}
.squad-row.me { border-color: var(--teal); background: var(--mint); }
.squad-rank { flex-shrink: 0; width: 26px; text-align: center; font-size: 1.1rem; }
.squad-rank-num { font-size: 0.85rem; font-weight: 950; color: var(--bark-mid); }
.squad-row-avatar {
  width: 38px; height: 38px; flex-shrink: 0; object-fit: contain;
  border-radius: 50%; background: #fff; border: 2px solid rgba(60,32,24,0.12);
}
.squad-row-info { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
.squad-row-name {
  font-size: 0.92rem; font-weight: 900; color: var(--bark);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  display: flex; align-items: center; gap: 6px;
}
.squad-you {
  font-size: 0.56rem; font-weight: 950; letter-spacing: 0.06em;
  background: var(--teal); color: #fff; padding: 1px 6px; border-radius: 999px; flex-shrink: 0;
}
.squad-row-sub { font-size: 0.72rem; font-weight: 800; color: var(--bark-mid); }

/* ── Daily Quests panel ────────────────────────────────────────────────── */
.quests-intro { font-size: 0.82rem; font-weight: 800; color: var(--bark-mid); line-height: 1.45; margin: 0 0 14px; }
.quests-list { display: flex; flex-direction: column; gap: 12px; }
.quest-card {
  display: flex; align-items: center; gap: 12px;
  padding: 13px 14px;
  border: 2px solid var(--ui-edge); border-radius: 18px;
  background: var(--ui-paper);
  box-shadow: var(--ui-sheen), 0 1px 0 rgba(96,58,36,0.08), 0 4px 10px -5px rgba(60,32,24,0.22);
  transition: background 240ms ease, border-color 240ms ease;
}
.quest-card.done { background: var(--mint); border-color: var(--teal); }
.quest-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 5px; }
.quest-title { font-size: 0.9rem; font-weight: 900; color: var(--bark); }
.quest-track { height: 8px; border: 1.5px solid var(--line); border-radius: 999px; background: rgba(60,32,24,0.07); overflow: hidden; }
.quest-fill { display: block; height: 100%; border-radius: 999px; background: linear-gradient(90deg,#f08aa0,#f0bb4f,#75c8bd); transition: width 420ms ease; }
.quest-card.done .quest-fill { background: var(--teal); }
.quest-sub { font-size: 0.68rem; font-weight: 850; color: var(--bark-mid); }
.quest-reward { flex: none; font-size: 0.82rem; font-weight: 950; color: var(--bark); background: var(--gold); padding: 5px 9px; border-radius: 999px; white-space: nowrap; }
.quest-reward.claimed { background: var(--teal); color: #fff; }

/* ── Streak Freeze (consumable) ─────────────────────────────────────────── */
.streak-freeze-note { display: block; margin-top: 6px; font-size: 0.68rem; font-weight: 850; color: var(--teal); }
.streak-freeze-note.hidden { display: none; }
.shop-boost-preview { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; font-size: 2.6rem; }

/* ── Weekly insights recap ─────────────────────────────────────────────── */
/* Shrink-wrap to the content and center it under the chart */
.insights-card { width: fit-content; max-width: 100%; margin: 12px auto 4px; padding: 14px; border-radius: 18px; background: var(--mint); border: 2px solid var(--line); }
.insight-head { display: flex; justify-content: space-between; align-items: center; gap: 8px; margin-bottom: 10px; }
.insight-title { font-size: 0.92rem; font-weight: 950; color: var(--bark); }
.insight-delta { font-size: 0.64rem; font-weight: 900; padding: 3px 8px; border-radius: 999px; white-space: nowrap; }
.insight-delta.up      { background: rgba(47,158,110,0.18); color: #2f9e6e; }
.insight-delta.down    { background: rgba(220,76,100,0.16); color: var(--danger); }
.insight-delta.neutral { background: rgba(60,32,24,0.08); color: var(--bark-mid); }
.insight-stats { display: flex; gap: 8px; }
.insight-stat { flex: 1; text-align: center; background: rgba(255,255,255,0.62); border-radius: 12px; padding: 8px 4px; }
.is-val { display: block; font-size: 1.1rem; font-weight: 950; color: var(--bark); }
.is-lab { display: block; font-size: 0.58rem; font-weight: 850; color: var(--bark-mid); text-transform: uppercase; letter-spacing: 0.02em; }
.insight-line { margin: 9px 0 0; font-size: 0.76rem; font-weight: 800; color: var(--bark-mid); line-height: 1.4; }
.insight-line strong { color: var(--bark); }
.squad-row-stats { flex-shrink: 0; font-size: 0.82rem; font-weight: 950; color: var(--bark); }
.squad-remove {
  flex-shrink: 0; width: 26px; height: 26px; margin-left: 2px;
  border: none; border-radius: 50%; background: rgba(60,32,24,0.08);
  color: var(--bark-mid); font-size: 0.8rem; font-weight: 900;
}

/* Taller screens get a proportionally bigger tableau (see --fig above). */
@media (min-height: 850px)  { .scene { --fig: 1.07; } }
@media (min-height: 960px)  { .scene { --fig: 1.14; } }

/* Short phones (e.g. iPhone SE, 375x667): the full-size scene + control stack
   don't both fit, so the mascot poked into the daily-goal pill and the counter
   crowded the size-picker. Shrink the scene + controls together so everything
   fits with breathing room. (Taller phones keep the full-size layout.) */
@media (max-height: 720px) {
  /* --floor-rest, not --floor-y: .scene sets --floor-y from it, and a :root
     value would lose to that rule and leave short phones on the tall layout.
     The lift shrinks too because this breakpoint tightens the stack gap to 7px. */
  .scene { --floor-rest: 290px; --floor-lift: 48px; }
  /* Keep the break nook lifted clear of the compact panel, but lower than on tall
     phones so the bed doesn't crowd the top HUD on short screens. */
  .scene.is-on-break.is-on-break { --floor-y: clamp(280px, 46vh, 360px); }
  .maker-wrap { width: 176px; height: 188px; bottom: calc(var(--floor-y) + 12px); }
  .cup-stage { width: 112px; height: 150px; top: -100px; }
  .work-counter { height: 72px; }
  .daily-goal { top: max(104px, calc(env(safe-area-inset-top) + 64px)); }
  .focus-controls { bottom: calc(90px + env(safe-area-inset-bottom)); gap: 7px; }
  .timer-card { padding: 10px 18px 8px; }
  .timer-time { font-size: 2.4rem; }
  .start-btn { padding: 13px 28px; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
  }
}

/* ── Feature tour (spotlight coach marks) ─────────────────────────────────── */
.coach-overlay {
  position: absolute;
  inset: 0;
  z-index: 210;           /* above every sheet/panel (max elsewhere is 120) */
  overflow: hidden;
}
.coach-overlay.hidden { display: none; }
/* The spotlight: a transparent window whose enormous shadow dims the rest. */
.coach-hole {
  position: absolute;
  border-radius: 18px;
  box-shadow: 0 0 0 9999px rgba(43, 26, 18, 0.62);
  transition: left 380ms cubic-bezier(0.4, 0, 0.2, 1),
              top 380ms cubic-bezier(0.4, 0, 0.2, 1),
              width 380ms cubic-bezier(0.4, 0, 0.2, 1),
              height 380ms cubic-bezier(0.4, 0, 0.2, 1);
  pointer-events: none;
}
.coach-hole.lit {
  border: 2.5px solid rgba(255, 244, 224, 0.9);
  animation: coach-pulse 1.8s ease-in-out infinite;
}
@keyframes coach-pulse {
  0%, 100% { box-shadow: 0 0 0 9999px rgba(43, 26, 18, 0.62), 0 0 0 0 rgba(255, 244, 224, 0.45); }
  50%      { box-shadow: 0 0 0 9999px rgba(43, 26, 18, 0.62), 0 0 0 9px rgba(255, 244, 224, 0); }
}
.coach-tip {
  position: absolute;
  width: min(320px, calc(100% - 24px));
  background: var(--cream, #fbf6ef);
  border: 2px solid var(--line, #ead9c7);
  border-radius: 20px;
  padding: 16px 18px 14px;
  box-shadow: 0 12px 34px rgba(43, 26, 18, 0.35);
  transition: top 380ms cubic-bezier(0.4, 0, 0.2, 1);
}
.coach-tip h3 { margin: 0 0 6px; font-size: 1.05rem; color: var(--bark, #3d2117); }
.coach-tip p  { margin: 0 0 12px; font-size: 0.88rem; line-height: 1.5; color: var(--bark-mid, #6b4a3a); }
.coach-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.coach-dots { display: flex; gap: 4px; flex-wrap: wrap; }
.coach-dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: rgba(107, 74, 58, 0.25);
}
.coach-dot.on { background: var(--boba, #c98a5e); }
.coach-btns { display: flex; gap: 8px; }
.coach-skip {
  border: none; background: none; color: var(--bark-mid, #6b4a3a);
  font: inherit; font-weight: 800; font-size: 0.85rem;
  padding: 9px 10px; cursor: pointer; min-height: 40px;
}
.coach-next {
  border: none; border-radius: 999px;
  background: var(--boba, #c98a5e); color: #fff;
  font: inherit; font-weight: 900; font-size: 0.9rem;
  padding: 9px 22px; cursor: pointer; min-height: 40px;
}


/* ── One-shot reactions on the WRAP (audit fix, 2026-07) ────────────────────
   The sprite engine sets the img's animation INLINE, which silently killed
   every class-based one-shot (tap pop, celebrate hop, idle wiggle) for sprite
   users. Playing them on the wrapper composes with the img's frame loop —
   every frame repeats translateX(var(--walk)) so he doesn't teleport. */
.maker-wrap.pop       { animation: wrap-pop       0.42s ease-out; }
.maker-wrap.celebrate { animation: wrap-celebrate 1.1s  ease-in-out; }
.maker-wrap.wiggle    { animation: wrap-wiggle    0.8s  ease-in-out; }
@keyframes wrap-pop {
  0%   { transform: translateX(var(--walk)) translateY(0)     scale(1); }
  40%  { transform: translateX(var(--walk)) translateY(-10px) scale(1.12, 0.92); }
  70%  { transform: translateX(var(--walk)) translateY(0)     scale(0.96, 1.04); }
  100% { transform: translateX(var(--walk)) translateY(0)     scale(1); }
}
@keyframes wrap-celebrate {
  0%, 100% { transform: translateX(var(--walk)) translateY(0)     rotate(0deg)  scale(1); }
  20%      { transform: translateX(var(--walk)) translateY(-16px) rotate(-6deg) scale(1.06); }
  45%      { transform: translateX(var(--walk)) translateY(0)     rotate(0deg)  scale(0.97, 1.05); }
  65%      { transform: translateX(var(--walk)) translateY(-12px) rotate(6deg)  scale(1.05); }
  85%      { transform: translateX(var(--walk)) translateY(0)     rotate(0deg)  scale(0.98, 1.03); }
}
@keyframes wrap-wiggle {
  0%, 100% { transform: translateX(var(--walk)) rotate(0deg); }
  25%      { transform: translateX(var(--walk)) rotate(-4deg); }
  50%      { transform: translateX(var(--walk)) rotate(3deg); }
  75%      { transform: translateX(var(--walk)) rotate(-2deg); }
}

/* Daily-goal pill removed from the scene (2026-07, owner request — it read as
   clutter). The goal itself still works: set it in Settings, progress lives in
   the stats dashboard, and the "goal reached" toast still fires. Element +
   update code kept so this is a one-line revert. */
#dailyGoal { display: none !important; }

/* ── Honey Library theme (Higgsfield-generated backdrop, 2026-07) ─────────── */
.scene[data-theme="library"] {
  --floor-a: #c08b5a;
  --floor-b: #dcae7e;
  --floor-tint: rgba(220,174,126,0.42);
  background: var(--floor-grad) no-repeat,
              #f0e0c2 url('assets/Shop Background Library.png') top center / cover no-repeat;
}
.scene[data-theme="library"] .work-counter {
  --counter-tint: rgba(190,140,60,0.12);
  border-color: rgba(90, 60, 25, 0.14);
  border-top-color: rgba(255, 255, 255, 0.85);
}
.scene[data-theme="library"] .speech-bubble { background: #fff8e4; }

/* ── Seaside Sunset theme (Higgsfield-generated backdrop, 2026-07) ────────── */
.scene[data-theme="sunset"] {
  --floor-a: #d0906c;
  --floor-b: #ecc0a0;
  --floor-tint: rgba(236,192,160,0.42);
  background: var(--floor-grad) no-repeat,
              #f7e3d3 url('assets/Shop Background Sunset.png') top center / cover no-repeat;
}
.scene[data-theme="sunset"] .work-counter {
  --counter-tint: rgba(255,150,90,0.16);
  border-color: rgba(120, 60, 40, 0.13);
  border-top-color: rgba(255, 255, 255, 0.85);
}
.scene[data-theme="sunset"] .speech-bubble { background: #fff2e8; }

/* ══ Ambient life for every theme (2026-07) ═══════════════════════════════
   Every backdrop gets motion native to its world, same infra as galaxy/
   winter: layers inside .theme-fx (sky zone), gradient sprites, position or
   opacity animation only. Paid themes stay the flashiest. */

/* COZY (default) — the softest touch: warm dust motes drifting up the window
   light, barely-there. The default theme must whisper, not shout. */
.scene[data-theme="cozy"] .theme-fx::before {
  background-repeat: repeat;
  background-size: 100% 240px;
  background-image:
    radial-gradient(2px 2px at 30% 30px, rgba(255,226,170,0.55), transparent),
    radial-gradient(1.6px 1.6px at 55% 110px, rgba(255,236,190,0.45), transparent),
    radial-gradient(1.8px 1.8px at 44% 190px, rgba(255,226,170,0.5), transparent),
    radial-gradient(1.4px 1.4px at 66% 60px, rgba(255,240,200,0.4), transparent);
  animation: mote-rise 16s linear infinite;
  opacity: 0.8;
}
@keyframes mote-rise { from { background-position-y: 240px; } to { background-position-y: 0; } }

/* NIGHT — fireflies: tiny warm dots that pulse and wander, plus faint stars. */
.scene[data-theme="night"] .theme-fx::before {
  background-repeat: no-repeat;
  background-image:
    radial-gradient(2.6px 2.6px at 16% 34%, rgba(255,244,150,0.9), transparent),
    radial-gradient(2px 2px at 38% 52%, rgba(230,255,160,0.8), transparent),
    radial-gradient(2.4px 2.4px at 64% 40%, rgba(255,244,150,0.85), transparent),
    radial-gradient(2px 2px at 84% 55%, rgba(230,255,160,0.75), transparent),
    radial-gradient(1.8px 1.8px at 50% 28%, rgba(255,250,180,0.7), transparent);
  filter: drop-shadow(0 0 4px rgba(255,244,150,0.8));
  animation: firefly-pulse 4.2s ease-in-out infinite;
}
.scene[data-theme="night"] .theme-fx::after {
  background-repeat: no-repeat;
  background-image:
    radial-gradient(1.6px 1.6px at 22% 12%, #fff, transparent),
    radial-gradient(1.4px 1.4px at 48% 8%, #fff, transparent),
    radial-gradient(1.8px 1.8px at 72% 15%, #fff, transparent),
    radial-gradient(1.4px 1.4px at 90% 10%, #fff, transparent);
  animation: star-twinkle 4.8s ease-in-out infinite;
  opacity: 0.8;
}
@keyframes firefly-pulse {
  0%, 100% { opacity: 0.25; transform: translate3d(0, 0, 0); }
  50%      { opacity: 1;    transform: translate3d(1.5%, -2%, 0); }
}

/* SAKURA — falling cherry petals, two layers drifting at different speeds. */
.scene[data-theme="sakura"] .theme-fx::before,
.scene[data-theme="sakura"] .theme-fx::after {
  background-repeat: repeat;
  background-size: 100% 220px;
  background-image:
    radial-gradient(4px 2.6px at 22% 20px, rgba(255,183,205,0.9), transparent),
    radial-gradient(3.4px 2.2px at 56% 70px, rgba(255,199,216,0.85), transparent),
    radial-gradient(4px 2.4px at 80% 40px, rgba(250,170,196,0.9), transparent),
    radial-gradient(3px 2px at 38% 140px, rgba(255,199,216,0.8), transparent),
    radial-gradient(3.6px 2.4px at 68% 180px, rgba(255,183,205,0.85), transparent),
    radial-gradient(3px 2px at 10% 100px, rgba(255,210,224,0.75), transparent);
  animation: petal-fall 9s linear infinite;
}
.scene[data-theme="sakura"] .theme-fx::after {
  animation-duration: 14s; opacity: 0.65; background-position-x: 60px;
}
@keyframes petal-fall {
  from { background-position-y: -220px; background-position-x: 0; }
  to   { background-position-y: 0;      background-position-x: 26px; }
}

/* AUTUMN — drifting falling leaves in amber and rust. */
.scene[data-theme="autumn"] .theme-fx::before,
.scene[data-theme="autumn"] .theme-fx::after {
  background-repeat: repeat;
  background-size: 100% 260px;
  background-image:
    radial-gradient(4.4px 3px at 18% 30px, rgba(224,141,60,0.9), transparent),
    radial-gradient(3.6px 2.6px at 52% 90px, rgba(233,169,86,0.85), transparent),
    radial-gradient(4px 2.8px at 78% 50px, rgba(196,108,44,0.85), transparent),
    radial-gradient(3.4px 2.4px at 34% 170px, rgba(233,169,86,0.8), transparent),
    radial-gradient(4px 2.8px at 64% 220px, rgba(224,141,60,0.85), transparent);
  animation: leaf-fall 11s linear infinite;
}
.scene[data-theme="autumn"] .theme-fx::after {
  animation-duration: 17s; opacity: 0.6; background-position-x: 70px;
}
@keyframes leaf-fall {
  from { background-position-y: -260px; background-position-x: 0; }
  to   { background-position-y: 0;      background-position-x: -38px; }
}

/* RAINY — two layers of rain streaks + a slow fog breath on the glass. */
.scene[data-theme="rainy"] .theme-fx::before,
.scene[data-theme="rainy"] .theme-fx::after {
  background-repeat: repeat;
  background-size: 90px 160px;
  background-image:
    linear-gradient(168deg, transparent 0 46%, rgba(174,199,220,0.55) 48% 52%, transparent 54% 100%);
  animation: rain-fall 0.9s linear infinite;
}
.scene[data-theme="rainy"] .theme-fx::after {
  background-size: 130px 220px;
  animation-duration: 1.4s; opacity: 0.5; background-position-x: 40px;
}
@keyframes rain-fall { from { background-position-y: -160px; } to { background-position-y: 0; } }
.scene[data-theme="rainy"] .theme-fx {
  background-repeat: no-repeat;
  background-image: radial-gradient(80% 60% at 50% 30%, rgba(214,226,235,0.35), transparent 75%);
  animation: fog-breathe 9s ease-in-out infinite alternate;
}
@keyframes fog-breathe { from { opacity: 0.35; } to { opacity: 0.8; } }

/* HONEY LIBRARY — golden dust motes rising through lamplight + a soft lamp
   glow that breathes near the shelves. */
.scene[data-theme="library"] .theme-fx::before {
  background-repeat: repeat;
  background-size: 100% 220px;
  background-image:
    radial-gradient(2.2px 2.2px at 28% 40px, rgba(255,214,140,0.75), transparent),
    radial-gradient(1.8px 1.8px at 52% 120px, rgba(255,226,170,0.65), transparent),
    radial-gradient(2px 2px at 72% 70px, rgba(255,206,120,0.7), transparent),
    radial-gradient(1.6px 1.6px at 40% 190px, rgba(255,226,170,0.6), transparent),
    radial-gradient(1.8px 1.8px at 86% 150px, rgba(255,214,140,0.6), transparent);
  animation: mote-rise 13s linear infinite;
}
.scene[data-theme="library"] .theme-fx::after {
  background-repeat: no-repeat;
  background-image:
    radial-gradient(45% 40% at 82% 62%, rgba(255,196,110,0.32), transparent 72%),
    radial-gradient(38% 34% at 12% 58%, rgba(255,206,130,0.22), transparent 70%);
  animation: lamp-breathe 6s ease-in-out infinite alternate;
}
@keyframes lamp-breathe { from { opacity: 0.55; } to { opacity: 1; } }

/* SEASIDE SUNSET — golden glints dancing on the water + a warm sun haze that
   slowly breathes, like the last hour of golden light. */
.scene[data-theme="sunset"] .theme-fx::before {
  background-repeat: no-repeat;
  background-image:
    radial-gradient(2.6px 1.6px at 34% 62%, rgba(255,235,180,0.95), transparent),
    radial-gradient(2px 1.4px at 46% 68%, rgba(255,220,160,0.85), transparent),
    radial-gradient(2.4px 1.5px at 56% 64%, rgba(255,235,180,0.9), transparent),
    radial-gradient(2px 1.3px at 64% 70%, rgba(255,225,170,0.8), transparent),
    radial-gradient(2.2px 1.4px at 42% 74%, rgba(255,235,180,0.85), transparent),
    radial-gradient(1.8px 1.2px at 52% 78%, rgba(255,220,160,0.8), transparent);
  animation: water-glint 3.2s ease-in-out infinite;
}
@keyframes water-glint {
  0%, 100% { opacity: 0.35; transform: translate3d(0, 0, 0); }
  50%      { opacity: 1;    transform: translate3d(0.8%, 0, 0); }
}
.scene[data-theme="sunset"] .theme-fx::after {
  background-repeat: no-repeat;
  background-image:
    radial-gradient(60% 50% at 50% 45%, rgba(255,176,120,0.30), transparent 72%),
    radial-gradient(45% 38% at 30% 30%, rgba(255,200,150,0.22), transparent 70%);
  mix-blend-mode: screen;
  animation: sun-haze 12s ease-in-out infinite alternate;
}
@keyframes sun-haze {
  from { opacity: 0.5;  transform: scale(1); }
  to   { opacity: 0.95; transform: scale(1.06); }
}

/* Stillness for those who ask for it */
@media (prefers-reduced-motion: reduce) {
  .theme-fx, .theme-fx::before, .theme-fx::after { animation: none !important; }
}

/* ── App-blocking discoverability: shield pill + start-focus prompt (2026-07) ─ */
.block-pill.hidden {
  /* the global `.hidden { display:none }` at ~3255 loses to the `display:inline-flex`
     below it, so this pill stayed visible on web even though it is native-only.
     It sat next to Reset in .session-row and pushed it out of alignment. */
  display: none;
}
.block-pill {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin: 10px auto 0;
  padding: 7px 14px;
  border-radius: 999px;
  border: 2px solid rgba(60, 32, 24, 0.14);
  background: var(--cream-90, rgba(253, 245, 236, 0.92));
  color: var(--bark, #3d2117);
  font-weight: 800;
  font-size: 0.9rem;
  cursor: pointer;
  transition: transform 100ms ease, border-color 150ms ease, background 150ms ease;
  -webkit-backdrop-filter: blur(8px);
          backdrop-filter: blur(8px);
}
.block-pill:active { transform: scale(0.96); }
.block-pill .block-pill-chev { color: rgba(60, 32, 24, 0.4); font-weight: 900; }
.block-pill.is-on {
  border-color: #6fceb6;
  background: linear-gradient(180deg, #eafaf4, #d6f2e8);
  color: #1f7a63;
}
.block-pill.is-on .block-pill-chev { color: #4aa98e; }

/* Start-focus prompt (reuses .reward-card shell) */
.block-card { text-align: center; }
.block-card .block-shield {
  font-size: 2.8rem;
  filter: drop-shadow(0 4px 8px rgba(60, 32, 24, 0.18));
  animation: block-shield-pop 0.5s cubic-bezier(0.2, 0.9, 0.3, 1.2);
}
@keyframes block-shield-pop {
  0% { transform: scale(0.4); opacity: 0; }
  100% { transform: scale(1); opacity: 1; }
}
.block-actions {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 18px;
}
.block-actions .primary {
  width: 100%;
  background: var(--bark, #3d2117);
  color: #fff7ee;
  border-color: var(--bark, #3d2117);
  box-shadow: 0 8px 20px rgba(60, 32, 24, 0.24);
}
.block-actions .quiet {
  background: transparent;
  border: 2px solid rgba(60, 32, 24, 0.16);
  color: var(--bark, #3d2117);
  font-weight: 800;
  border-radius: 999px;
  padding: 12px 18px;
}
.block-actions .block-never {
  background: none; border: none;
  color: rgba(60, 32, 24, 0.5);
  font-weight: 700; font-size: 0.85rem;
  padding: 4px; cursor: pointer;
}
.block-actions .block-never:hover { color: rgba(60, 32, 24, 0.75); }
@media (prefers-reduced-motion: reduce) {
  .block-card .block-shield { animation: none; }
}

/* Break-games gate (iPhone build): visible-but-locked teaches the feature */
.games-lock-note {
  margin: 2px 0 6px;
  text-align: center;
  font-size: 0.85rem;
  font-weight: 800;
  color: rgba(60, 32, 24, 0.55);
}
.game-launch-btn:disabled { opacity: 0.55; }

/* ── inline icon sizing for the icons that replaced emoji ────────────── */
.dg-icon { display: inline-flex; align-items: center; color: var(--bark-mid, #6b4a3a); }
.dg-icon .ico { width: 15px; height: 15px; }
.game-score { display: inline-flex; align-items: center; gap: 5px; }
.game-score .ico { width: 15px; height: 15px; color: var(--boba, #3d2117); }
.games-lock-note { display: flex; align-items: center; justify-content: center; gap: 6px; }
.games-lock-note .ico { width: 14px; height: 14px; flex-shrink: 0; }
.squad-board-head { display: flex; align-items: center; gap: 7px; }
.squad-board-head .ico { width: 17px; height: 17px; }
.install-icon .ico { width: 26px; height: 26px; }
.settings-action-btn .ico { width: 15px; height: 15px; display: inline-block; vertical-align: -2px; }
.squad-stats, .squad-row-stats { display: inline-flex; align-items: center; gap: 1px; }
.squad-stats .ico, .squad-row-stats .ico { width: 13px; height: 13px; color: #ef8a3c; }
.g-label { display: inline; }

.shop-boost-preview .ico { width: 54px; height: 54px; color: #7fb7bd; margin: 0 auto; }

/* ── In-world confirm / prompt / alert ─────────────────────────────────────
   Replaces the ten window.confirm / prompt / alert calls. Those rendered as
   stock OS alert boxes, and on the Capacitor build as UIAlertController, which
   is the single hardest break of the app's world. They also fired on the most
   trafficked paths: Reset mid-session, switching drink or cup size, and every
   pearl purchase in Customize. Built from the existing .reward-card shell so it
   inherits the app's material rather than inventing a fifth one. */
.ask-card { max-width: 320px; text-align: center; }
.ask-card h2 { margin: 2px 0 6px; font-size: 1.35rem; color: var(--bark); }
.ask-card p  { color: var(--bark-mid); font-size: 0.92rem; line-height: 1.45; margin: 0 0 4px; white-space: pre-line; }
.ask-input {
  width: 100%;
  margin: 10px 0 2px;
  padding: 11px 14px;
  min-height: 44px;
  font: inherit;
  font-weight: 850;
  color: var(--bark);
  text-align: center;
  background: var(--ui-paper);
  border: 2px solid var(--ui-edge);
  border-radius: 14px;
  box-shadow: var(--ui-sheen);
}
.ask-input:focus { outline: 2px solid var(--bark-mid); outline-offset: 1px; }
/* .hidden sits ABOVE this file's component rules, so any component that sets its
   own display needs its own .hidden guard or the later display wins. */
.ask-input.hidden { display: none; }
.ask-card .block-actions { margin-top: 12px; }
/* Destructive asks (clear progress, delete cloud account) read as destructive. */
.ask-card.is-danger #askConfirmBtn { background: linear-gradient(180deg, #c2544f, #a8433f); }
/* .block-actions children set their own display, so they need this guard. */
.ask-card .block-actions button.hidden { display: none; }
