/* ============ TOKENS ============ */
:root,
[data-theme='light'] {
  /* Cool, desaturated teal/lavender base — proven calming without reading
     clinical (research: blue/teal reliably lowers measured arousal; saturation
     matters more than hue, so we keep it soft rather than avoiding color). */
  --color-bg: #EFF5F3;
  --color-surface: #FFFFFF;
  --color-surface-2: #E2EFEA;
  --color-border: #CFE2DC;
  --color-text: #1F2E2A;
  --color-text-muted: #5B6E68;
  --color-text-faint: #93A49D;
  --color-text-inverse: #EFF5F3;

  /* Single warm accent, reserved for the "release" moment itself — the one
     spot of warmth in an otherwise cool, soothing palette. */
  --color-coral: #FF6F5E;
  --color-coral-hover: #E85847;
  --color-coral-highlight: #FFE1DB;

  --color-lavender: #8779C9;
  --color-lavender-hover: #6F5FB0;
  --color-lavender-highlight: #E7E1F6;

  --color-sage: #4FA48F;
  --color-sage-hover: #3D8974;
  --color-sage-highlight: #D8EDE6;

  --radius-sm: 0.5rem;
  --radius-md: 0.9rem;
  --radius-lg: 1.4rem;
  --radius-full: 9999px;

  --shadow-sm: 0 1px 3px rgba(20,32,29,0.07);
  --shadow-md: 0 6px 20px rgba(20,32,29,0.09);
  --shadow-lg: 0 16px 40px rgba(20,32,29,0.15);

  --transition-interactive: 180ms cubic-bezier(0.16,1,0.3,1);
  --spring: 480ms cubic-bezier(0.34, 1.56, 0.64, 1);

  /* Spring-physics easing curves, sampled from real damped-oscillator math
     (mass/stiffness/damping) and baked into CSS linear() stops — true spring
     feel with zero JS runtime cost. Neutralized globally under
     prefers-reduced-motion (see block below). */
  --spring-pop: linear(0, 0.136, 0.4235, 0.7224, 0.9538, 1.0913, 1.1436, 1.1366, 1.0991, 1.0542, 1.0163, 0.9917, 0.9803, 0.979, 0.9834, 0.99, 0.996, 1.0003, 1.0025, 1.0031, 1.0027, 1.0018, 1.0009, 1);
  --spring-screen: linear(0, 0.0779, 0.242, 0.424, 0.5892, 0.7236, 0.825, 0.8969, 0.9449, 0.9751, 0.9927, 1.002, 1.0061, 1.0072, 1.0067, 1.0055, 1.0042, 1.003, 1.002, 1.0013, 1.0008, 1);
  --spring-micro: linear(0, 0.2343, 0.6513, 0.9765, 1.128, 1.1414, 1.0891, 1.0297, 0.9916, 0.9784, 0.9816, 0.9905, 0.9983, 1.0024, 1.0032, 1.0022, 1.0009, 1);

  /* Resonance-breathing cadence: ~6 breaths/min (0.1Hz), the frequency with
     the strongest cited evidence for HRV/vagal effect. Visual cue and ambient
     tone below are both locked to this same duration so they stay in phase. */
  --breath-cycle: 10s;

  --space-1: 0.25rem; --space-2: 0.5rem; --space-3: 0.75rem; --space-4: 1rem;
  --space-5: 1.25rem; --space-6: 1.5rem; --space-8: 2rem; --space-10: 2.5rem;
  --space-12: 3rem; --space-16: 4rem; --space-20: 5rem; --space-24: 6rem;

  /* iOS safe areas. The viewport is declared viewport-fit=cover so the
     background wash reaches the screen edges; these insets hand the space
     back to content so nothing sits under the Dynamic Island, status bar,
     or home indicator. Fall back to 0px everywhere else. */
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-right: env(safe-area-inset-right, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-left: env(safe-area-inset-left, 0px);

  --text-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem);
  --text-sm: clamp(0.875rem, 0.8rem + 0.35vw, 1rem);
  --text-base: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
  --text-lg: clamp(1.125rem, 1rem + 0.75vw, 1.5rem);
  --text-xl: clamp(1.5rem, 1.2rem + 1.25vw, 2.25rem);
  --text-2xl: clamp(2rem, 1.2rem + 2.5vw, 3.25rem);

  --font-display: 'Chillax', 'Segoe UI', sans-serif;
  --font-body: 'General Sans', 'Segoe UI', sans-serif;
}

[data-theme='dark'] {
  --color-bg: #121C1A;
  --color-surface: #17221F;
  --color-surface-2: #1D2B27;
  --color-border: #2C3B37;
  --color-text: #EAF2EF;
  --color-text-muted: #9EB2AC;
  --color-text-faint: #5D6E68;
  --color-text-inverse: #121C1A;

  --color-coral: #FF8A7B;
  --color-coral-hover: #FFA396;
  --color-coral-highlight: #3E2924;

  --color-lavender: #B0A3E4;
  --color-lavender-hover: #C4BAEB;
  --color-lavender-highlight: #2E2A47;

  --color-sage: #7BC6AE;
  --color-sage-hover: #93D3BE;
  --color-sage-highlight: #1F332D;

  --shadow-sm: 0 1px 3px rgba(0,0,0,0.32);
  --shadow-md: 0 6px 20px rgba(0,0,0,0.38);
  --shadow-lg: 0 16px 40px rgba(0,0,0,0.48);
}

/* ============ BASE ============ */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html {
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  scroll-behavior: smooth;
  /* Kill double-tap-to-zoom app-wide. Popping is a fast, repeated tap, and any
     tap landing just off a bubble was being read as a double-tap on the page
     and zooming the whole view. `manipulation` removes double-tap zoom while
     keeping panning and pinch-to-zoom, so accessibility zoom still works.
     The bubbles and their sheet keep the stricter `touch-action: none` they
     need for the drag-to-cascade gesture. */
  touch-action: manipulation;
}

/* touch-action does not inherit, so it is also set on the surfaces a stray
   tap actually lands on. The bubbles and sheet keep `none` further down. */
body, #stage, .screen, .screen-inner, button { touch-action: manipulation; }

body {
  min-height: 100dvh;
  min-height: 100vh;
  font-family: var(--font-body);
  font-size: var(--text-base);
  color: var(--color-text);
  /* Soft two-tone wash within the restrained cool palette — keeps the base
     calming while still feeling alive rather than flat, per teen-preference
     research favoring subtle gradients over monochrome. */
  background:
    radial-gradient(1100px 760px at 10% -8%, rgba(135,121,201,0.11), transparent 58%),
    radial-gradient(1000px 720px at 92% 108%, rgba(79,164,143,0.13), transparent 58%),
    var(--color-bg);
  line-height: 1.55;
  overflow-x: hidden;
  transition: background var(--transition-interactive), color var(--transition-interactive);
}

h1, h2, h3 { font-family: var(--font-display); text-wrap: balance; line-height: 1.1; }
p { text-wrap: pretty; }
button, input { font: inherit; color: inherit; }
button { cursor: pointer; background: none; border: none; }

:focus-visible {
  outline: 2px solid var(--color-coral);
  outline-offset: 3px;
  border-radius: var(--radius-sm);
}


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

/* ============ INTRO OVERLAY ============ */
/* One-time settling-in of the brand mark on load: coral → lavender → the sage
   "pop" strokes land last as the resolution beat, hold a breath, then the
   overlay cross-fades into home. Under prefers-reduced-motion the global
   block above collapses every duration here to ~0ms, so reduced-motion users
   get an effectively instant fade (JS also skips the hold entirely). */
.intro-overlay {
  position: fixed; inset: 0; z-index: 200;
  display: flex; align-items: center; justify-content: center;
  /* Same wash as the body background so the cross-fade lands seamlessly,
     in light and dark themes alike. */
  background:
    radial-gradient(1100px 760px at 10% -8%, rgba(135,121,201,0.11), transparent 58%),
    radial-gradient(1000px 720px at 92% 108%, rgba(79,164,143,0.13), transparent 58%),
    var(--color-bg);
}
.intro-mark { width: 88px; height: 88px; }
.intro-mark .mark-coral,
.intro-mark .mark-lavender,
.intro-mark .mark-sage {
  transform-box: fill-box;
  transform-origin: center;
}
/* Shapes only hide/animate once JS arms the overlay — if scripting ever
   fails, the static mark simply sits there rather than a blank screen. */
.intro-overlay.intro-playing .mark-coral {
  animation: introShapeIn 520ms cubic-bezier(0.16,1,0.3,1) 0ms backwards;
}
.intro-overlay.intro-playing .mark-lavender {
  animation: introShapeIn 520ms cubic-bezier(0.16,1,0.3,1) 160ms backwards;
}
.intro-overlay.intro-playing .mark-sage {
  animation: introPopIn 480ms var(--spring-micro) 340ms backwards;
}
@keyframes introShapeIn {
  from { opacity: 0; transform: scale(0.85); }
  to { opacity: 1; transform: scale(1); }
}
@keyframes introPopIn {
  from { opacity: 0; transform: scale(0.5); }
  to { opacity: 1; transform: scale(1); }
}
.intro-overlay.intro-leaving {
  opacity: 0;
  pointer-events: none;
  transition: opacity 480ms cubic-bezier(0.16,1,0.3,1);
}

/* ============ AMBIENT BACKGROUND FIELD ============ */
.bg-field {
  position: fixed; inset: 0; z-index: 0; pointer-events: none; overflow: hidden;
}
.bg-field .drift {
  position: absolute;
  border-radius: 50%;
  opacity: 0.35;
  filter: blur(0px);
  animation: drift 18s ease-in-out infinite;
}

.particle-layer {
  position: fixed; inset: 0; z-index: 60; pointer-events: none;
}
.burst-particle {
  position: fixed;
  border-radius: 50%;
  pointer-events: none;
  will-change: transform, opacity;
}

@keyframes drift {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50% { transform: translate(12px, -18px) scale(1.05); }
}

/* ============ TOPBAR ============ */
.topbar {
  position: relative; z-index: 10;
  display: flex; align-items: center; justify-content: space-between;
  padding: calc(var(--space-5) + var(--safe-top))
           calc(var(--space-6) + var(--safe-right))
           var(--space-5)
           calc(var(--space-6) + var(--safe-left));
}
.brand {
  display: flex; align-items: center; gap: var(--space-2);
  text-decoration: none; color: var(--color-text);
}
.brand-mark { width: 30px; height: 30px; }
.mark-coral { color: var(--color-coral); }
.mark-lavender { color: var(--color-lavender); }
.mark-sage { color: var(--color-sage); }
.brand-name { font-family: var(--font-display); font-weight: 600; font-size: var(--text-lg); }

.topbar-actions { display: flex; align-items: center; gap: var(--space-2); }
.icon-btn {
  width: 40px; height: 40px; border-radius: var(--radius-full);
  display: flex; align-items: center; justify-content: center;
  color: var(--color-text-muted);
  transition: background var(--transition-interactive), color var(--transition-interactive);
}
.icon-btn:hover { background: var(--color-surface-2); color: var(--color-text); }
.icon-btn svg { width: 19px; height: 19px; }

.text-btn {
  font-size: var(--text-sm); color: var(--color-text-muted);
  padding: var(--space-2) var(--space-3); border-radius: var(--radius-full);
  transition: background var(--transition-interactive), color var(--transition-interactive);
}
.text-btn:hover { background: var(--color-surface-2); color: var(--color-text); }

/* ============ STAGE / SCREENS ============ */
#stage { position: relative; z-index: 1; min-height: calc(100dvh - 88px - var(--safe-top)); }

.screen {
  display: none;
  min-height: calc(100dvh - 88px - var(--safe-top));
  align-items: center; justify-content: center;
  padding: var(--space-6)
           calc(var(--space-6) + var(--safe-right))
           calc(var(--space-6) + var(--safe-bottom))
           calc(var(--space-6) + var(--safe-left));
}
.screen.is-active { display: flex; animation: screenIn 620ms var(--spring-screen); }

@keyframes screenIn {
  from { opacity: 0; transform: translateY(14px) scale(0.985); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

.screen-inner { width: 100%; max-width: 640px; margin: 0 auto; text-align: center; }

/* ---- HOME ---- */
.eyebrow {
  font-size: var(--text-sm); color: var(--color-text-muted);
  letter-spacing: 0.06em; text-transform: uppercase; margin-bottom: var(--space-4);
}
.hero {
  font-size: var(--text-2xl); font-weight: 600; margin-bottom: var(--space-5);
}
.lede {
  font-size: var(--text-lg); color: var(--color-text-muted);
  max-width: 46ch; margin: 0 auto var(--space-10);
}
.mode-row {
  display: flex; gap: var(--space-4); flex-wrap: wrap; justify-content: center;
}
.mode-btn {
  flex: 1 1 220px; max-width: 280px;
  display: flex; flex-direction: column; gap: var(--space-1);
  padding: var(--space-6) var(--space-5);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  border: 2px solid var(--color-border);
  box-shadow: var(--shadow-sm);
  transition: transform 420ms var(--spring-micro), box-shadow var(--transition-interactive), border-color var(--transition-interactive);
}
.mode-btn:hover { transform: translateY(-4px); box-shadow: var(--shadow-md); }
.mode-btn:active { transform: translateY(0px) scale(0.97); transition: transform 200ms var(--spring-pop); }
.mode-feeling { border-color: var(--color-coral-highlight); }
.mode-feeling:hover { border-color: var(--color-coral); }
.mode-thought { border-color: var(--color-lavender-highlight); }
.mode-thought:hover { border-color: var(--color-lavender); }
.mode-label { font-family: var(--font-display); font-weight: 600; font-size: var(--text-lg); }
.mode-sub { font-size: var(--text-sm); color: var(--color-text-muted); }

.ambient-try { margin-top: var(--space-10); }
.ambient-try-label { font-size: var(--text-xs); color: var(--color-text-faint); margin-bottom: var(--space-3); letter-spacing: 0.02em; }
.ambient-sheet { min-height: auto; gap: var(--space-2); }
.ambient-sheet .bubble { --bsize: 34px; }

/* ---- NAME IT ---- */
.back-btn, .quiet-link {
  font-size: var(--text-sm); color: var(--color-text-muted);
  padding: var(--space-2) var(--space-1); margin-bottom: var(--space-4);
  /* Block with a fitted width, not inline-block: as an inline box this sat on
     the same line as the statement whenever the named feeling was short enough
     to fit beside it ("tender" collided, "overwhelmed" wrapped and looked
     fine). Its own line every time, centred by the fitted width. */
  display: block;
  width: fit-content;
  margin-inline: auto;
}
.back-btn:hover, .quiet-link:hover { color: var(--color-text); }

.step-title { font-size: var(--text-xl); font-weight: 600; margin-bottom: var(--space-6); }

.input-row { margin-bottom: var(--space-4); }
.name-input {
  width: 100%; max-width: 480px;
  padding: var(--space-4) var(--space-5);
  font-size: var(--text-lg);
  border-radius: var(--radius-md);
  border: 2px solid var(--color-border);
  background: var(--color-surface);
  text-align: center;
  transition: border-color var(--transition-interactive);
}
.name-input:focus { border-color: var(--color-coral); outline: none; }
body[data-mode='thought'] .name-input:focus { border-color: var(--color-lavender); }

.chip-row {
  display: flex; flex-wrap: wrap; gap: var(--space-2);
  justify-content: center; margin-bottom: var(--space-6);
}
.chip {
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-full);
  border: 1.5px solid var(--color-border);
  background: var(--color-surface);
  font-size: var(--text-sm); color: var(--color-text-muted);
  transition: all var(--transition-interactive);
}
.chip:hover { border-color: var(--color-coral); color: var(--color-text); }
body[data-mode='thought'] .chip:hover { border-color: var(--color-lavender); }
.chip.chip-active {
  background: var(--color-coral-highlight); border-color: var(--color-coral);
  color: var(--color-text);
}
body[data-mode='thought'] .chip.chip-active {
  background: var(--color-lavender-highlight); border-color: var(--color-lavender);
}

.phrase-preview {
  min-height: 2.4em;
  font-size: var(--text-lg); font-style: italic; color: var(--color-text-muted);
  margin-bottom: var(--space-8);
}
/* Optical space before the named word. The app loads General Sans in upright
   weights only (400/500/600/700, no italic face), so every italic here is a
   browser-synthesised oblique: it shears the upright glyphs without adjusting
   side bearings, and the leaning final letter encroaches on the following
   word space until it reads as no space at all. The real space is still
   there; this restores the space you can see. */
.phrase-preview strong { color: var(--color-text); font-style: normal; font-weight: 600; margin-left: 0.12em; }

.intensity-block { margin-bottom: var(--space-8); }
.intensity-label { font-size: var(--text-sm); color: var(--color-text-muted); margin-bottom: var(--space-3); }
.intensity-optional { color: var(--color-text-faint); }
.pebble-row { display: flex; gap: var(--space-2); justify-content: center; }
.pebble {
  width: 32px; height: 32px; border-radius: 50%;
  border: 2px solid var(--color-border);
  background: var(--color-surface);
  transition: all var(--transition-interactive);
}
.pebble:hover { border-color: var(--color-text-muted); }
.pebble.pebble-active { background: var(--color-sage); border-color: var(--color-sage); }

.cta-btn {
  padding: var(--space-4) var(--space-10);
  border-radius: var(--radius-full);
  font-family: var(--font-display); font-weight: 600; font-size: var(--text-lg);
  background: var(--color-coral); color: #fff;
  /* Transparent by default so the disabled state's visible edge does not
     change the button's height when it becomes enabled. */
  border: 1px solid transparent;
  box-shadow: var(--shadow-md);
  transition: transform 420ms var(--spring-micro), box-shadow var(--transition-interactive), opacity var(--transition-interactive), background var(--transition-interactive), border-color var(--transition-interactive), color var(--transition-interactive);
}
/* Scoped to the primary CTA only. As `body[data-mode='thought'] .cta-btn`
   this outscored .cta-secondary and .cta-ghost on specificity, so in
   thought mode the lavender fill overrode both: 'Name another' lost its
   sage, and 'Done for now' gained a fill it should never have, leaving
   muted text on lavender in both themes. */
body[data-mode='thought'] .cta-btn:not(.cta-secondary):not(.cta-ghost):not(:disabled) { background: var(--color-lavender); }
.cta-btn:hover:not(:disabled) { transform: translateY(-2px); box-shadow: var(--shadow-lg); }
.cta-btn:active:not(:disabled) { transform: translateY(0) scale(0.96); transition: transform 220ms var(--spring-pop); }
/* Disabled is a designed state, not a dimmed one. Dropping opacity took the
   coral/lavender fill to a washed lilac and the label to an accidental grey.
   Instead the button becomes an inert surface with a visible edge, at full
   opacity, so the shape still reads as a button that is deliberately not
   ready yet. Label contrast measures 4.6:1 on light and 6.6:1 on dark. */
.cta-btn:disabled,
body[data-mode='thought'] .cta-btn:disabled {
  background: var(--color-surface-2);
  color: var(--color-text-muted);
  border: 1px solid var(--color-border);
  opacity: 1;
  cursor: not-allowed;
  box-shadow: none;
  transform: none;
}
.cta-btn:not(:disabled) { animation: ctaAppear 480ms var(--spring-micro); }
@keyframes ctaAppear {
  from { transform: scale(0.9); }
  to { transform: scale(1); }
}
.cta-secondary,
body[data-mode='thought'] .cta-secondary { background: var(--color-sage); }
.cta-ghost,
body[data-mode='thought'] .cta-ghost { background: transparent; color: var(--color-text-muted); box-shadow: none; }
.cta-ghost:hover { color: var(--color-text); transform: none; box-shadow: none; }

/* ---- POP IT ---- */
.pop-inner { max-width: 720px; }
/* The pop screen carries the most vertical content, so its back control
   sits a step further from the statement than on lighter screens. */
.pop-inner .back-btn { margin-bottom: var(--space-6); }
.sticky-note {
  display: inline-block;
  font-size: var(--text-lg); font-style: italic;
  padding: var(--space-4) var(--space-6);
  background: var(--color-surface-2);
  border-radius: var(--radius-md);
  margin: 0 auto var(--space-10);
  box-shadow: var(--shadow-sm);
}
/* No optical margin here: the named word now sits on its own line, so a left
   margin would push the centred line off-centre. The phrase preview keeps
   its margin because the word stays inline there. */
.sticky-note strong { font-style: normal; font-weight: 600; }
.sticky-note { line-height: 1.4; }
/* Two lines: the lead-in holds one, the named word takes the next.
   The note also opts out of the `text-wrap: pretty` it inherits from p.
   Inside a shrink-to-fit box, pretty rebalances the lead-in into two
   short lines, which is how this became three cramped lines. Plain
   wrapping lets the box size to the lead-in and still fall back to
   wrapping rather than overflowing on a narrow screen. */
.sticky-note { text-wrap: wrap; }
.statement-lead { display: block; }

.progress-wrap { max-width: 320px; margin: 0 auto var(--space-8); }
.progress-track {
  height: 6px; border-radius: var(--radius-full);
  background: var(--color-surface-2); overflow: hidden;
}
.progress-fill {
  height: 100%; width: 0%; border-radius: var(--radius-full);
  background: var(--color-sage);
  transition: width 320ms cubic-bezier(0.16,1,0.3,1);
}

.bubble-sheet {
  display: flex; flex-wrap: wrap; gap: var(--space-3);
  justify-content: center; align-items: center;
  margin: 0 auto var(--space-6);
  max-width: 560px;
  min-height: 220px;
  touch-action: none;
}

/* Pop screen uses a computed radial layout instead of flex-wrap: bubbles sit
   in .bubble-slot children positioned by JS (see layoutRadialPositions), so
   the arrangement itself — calm circular spread vs. dense jittered cloud —
   can shift with the intensity that was picked. Home screen's ambient bubbles
   are unaffected and keep the plain flex-wrap layout above. */
.bubble-sheet.bubble-sheet-radial {
  display: block;
  position: relative;
  gap: 0;
}
.bubble-slot {
  position: absolute;
  transform: translate(-50%, -50%);
}

.bubble {
  --bsize: 62px;
  width: var(--bsize); height: var(--bsize);
  border-radius: 50%;
  position: relative;
  display: flex; align-items: center; justify-content: center;
  background: radial-gradient(circle at 35% 30%, rgba(255,255,255,0.55), transparent 60%), var(--bubble-color, var(--color-coral));
  box-shadow: inset 0 -6px 10px rgba(0,0,0,0.08), inset 0 4px 8px rgba(255,255,255,0.35), var(--shadow-sm);
  transition: transform 340ms var(--spring-pop);
  flex-shrink: 0;
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
}
.bubble.bubble-has-word {
  /* A touch deeper shadow only — no new hue — so word-carrying bubbles read
     as slightly more present without breaking the screen's 2-color discipline. */
  box-shadow: inset 0 -6px 10px rgba(0,0,0,0.1), inset 0 4px 8px rgba(255,255,255,0.4), var(--shadow-md);
}
.bubble-word {
  position: relative;
  z-index: 1;
  /* One line, never split. The size is measured to fit in JS (fitWordSize),
     so the word is scaled down rather than broken. Previously break-word was
     allowed, which chopped "Something" into "Somethin/g". */
  /* No max-width: the label must size to its own text so its rendered width
     can be measured and shrunk against the bubble (see shrinkWordToFit). */
  font-family: var(--font-body);
  font-weight: 500;
  letter-spacing: 0.01em;
  line-height: 1.1;
  font-size: 0.75rem; /* fallback; actual size is set inline per-word by length */
  color: rgba(255, 255, 255, 0.88);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.18);
  text-align: center;
  white-space: nowrap;
  overflow: visible;
  pointer-events: none;
  user-select: none;
}
.bubble:hover { transform: scale(1.06); }
.bubble:active { transform: scale(0.94); }
.bubble.is-pressed {
  /* Presses IN like a real pop-it dimple (compress + darken), not a flattened
     squash — the shadow flips to the near side to read as concave give. */
  transform: scale(0.84);
  transition: transform 90ms cubic-bezier(0.32, 0, 0.67, 0);
  box-shadow: inset 0 5px 9px rgba(0,0,0,0.28), inset 0 -3px 6px rgba(255,255,255,0.22), var(--shadow-sm);
}
.bubble::before {
  content: '';
  position: absolute; inset: 20%;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 42%, rgba(0,0,0,0.4), transparent 70%);
  opacity: 0;
  transform: scale(0.7);
  transition: opacity 100ms ease, transform 100ms ease;
  pointer-events: none;
}
.bubble.is-pressed::before {
  opacity: 1;
  transform: scale(1.1);
}
.bubble.bubble-enter {
  animation: bubbleEnter 480ms var(--spring-pop) backwards;
  animation-delay: var(--enter-delay, 0ms);
}
@keyframes bubbleEnter {
  from { opacity: 0; transform: scale(0.4); }
  to { opacity: 1; transform: scale(1); }
}

.bubble.is-popping {
  /* Continues from the pressed-in dimple (0.84), snaps past neutral as the
     membrane gives way — the actual "burst" moment — then collapses away.
     This reads as press → give → burst rather than a generic explode. */
  animation: bubblePop 230ms var(--spring-pop) forwards;
  pointer-events: none;
}
.bubble.is-popped {
  width: 0; height: 0; margin: 0; opacity: 0; pointer-events: none;
  transition: width 220ms ease, height 220ms ease, opacity 160ms ease, margin 220ms ease;
}

@keyframes bubblePop {
  0% { transform: scale(0.84); opacity: 1; }
  32% { transform: scale(1.28); opacity: 1; }
  62% { transform: scale(0.88); opacity: 0.85; }
  100% { transform: scale(0); opacity: 0; }
}

.bubble::after {
  content: '';
  position: absolute; inset: -14px;
  border-radius: 50%;
  border: 3px solid var(--bubble-color, var(--color-coral));
  opacity: 0;
  transform: scale(0.6);
}
.bubble.is-popping::after {
  animation: burstRing 260ms ease-out forwards;
}
@keyframes burstRing {
  0% { opacity: 0.8; transform: scale(0.5); }
  100% { opacity: 0; transform: scale(1.7); }
}

/* ---- RELEASED / DONE ---- */
.breath-circle {
  width: 120px; height: 120px; border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, var(--color-sage), var(--color-sage-hover));
  margin: 0 auto var(--space-6);
  /* Locked to the same ~6 breaths/min (0.1Hz) cadence as the ambient tone
     below, so the visual cue and sound stay in phase. */
  animation: breathe var(--breath-cycle) ease-in-out infinite;
  box-shadow: var(--shadow-lg);
}
.breath-circle-slow { opacity: 0.85; }
@keyframes breathe {
  0%, 100% { transform: scale(0.82); }
  50% { transform: scale(1.1); }
}
.breath-caption {
  font-size: var(--text-xs); color: var(--color-text-faint);
  letter-spacing: 0.02em; margin-top: calc(-1 * var(--space-4)); margin-bottom: var(--space-8);
}

.released-title { font-size: var(--text-xl); font-weight: 600; margin-bottom: var(--space-4); }
.released-sub { font-size: var(--text-base); color: var(--color-text-muted); margin-bottom: var(--space-10); }
.released-actions { display: flex; gap: var(--space-4); justify-content: center; flex-wrap: wrap; }

.done-text { font-size: var(--text-xl); font-weight: 600; margin-bottom: var(--space-3); font-family: var(--font-display); }
.done-sub { color: var(--color-text-muted); margin-bottom: var(--space-8); }

/* ============ ABOUT PANEL ============ */
.about-overlay {
  position: fixed; inset: 0; z-index: 50;
  background: rgba(20,17,14,0.45);
  display: flex; align-items: center; justify-content: center;
  padding: calc(var(--space-6) + var(--safe-top))
           calc(var(--space-6) + var(--safe-right))
           calc(var(--space-6) + var(--safe-bottom))
           calc(var(--space-6) + var(--safe-left));
}
.about-overlay[hidden] { display: none; }
.about-panel {
  position: relative;
  max-width: 560px; width: 100%; max-height: 84vh; overflow-y: auto;
  background: var(--color-surface);
  border-radius: var(--radius-lg);
  padding: var(--space-8);
  box-shadow: var(--shadow-lg);
  text-align: left;
}
.about-panel h2 { font-size: var(--text-lg); margin-bottom: var(--space-4); }
.about-panel p { margin-bottom: var(--space-4); color: var(--color-text-muted); }
.about-list { list-style: none; display: flex; flex-direction: column; gap: var(--space-4); margin-bottom: var(--space-4); }
.about-list li { font-size: var(--text-sm); color: var(--color-text-muted); }
.about-list strong { color: var(--color-text); }
.about-list a { color: var(--color-coral); text-decoration: underline; }
.about-footer { font-size: var(--text-sm); font-style: italic; margin-bottom: 0; }
.about-close {
  position: absolute; top: var(--space-4); right: var(--space-4);
  width: 32px; height: 32px; border-radius: 50%;
  font-size: 1.4rem; color: var(--color-text-muted); line-height: 1;
  display: flex; align-items: center; justify-content: center;
}
.about-close:hover { background: var(--color-surface-2); color: var(--color-text); }

/* ============ RESPONSIVE ============ */
@media (max-width: 560px) {
  .topbar { padding: calc(var(--space-4) + var(--safe-top))
                     calc(var(--space-4) + var(--safe-right))
                     var(--space-4)
                     calc(var(--space-4) + var(--safe-left)); }
  .brand-name { display: none; }
  .mode-row { flex-direction: column; align-items: stretch; }
  .mode-btn { max-width: none; }
  .bubble { --bsize: 52px; }
  .cta-btn { width: 100%; }
  .released-actions { flex-direction: column; }
  .released-actions .cta-btn { width: 100%; }
}
