/* ============================================================
   AMBIENT DEPTH (experimental) — spec 2026-07-17-ambient-depth-design.md
   Everything in this file is inert unless <html data-ambient="1">
   (set pre-paint by theme-loader.js from localStorage 'gystAmbientDepth'
   or ?ambient=1|0; live-toggled by window.setAmbientDepth) — EXCEPT the
   background-picker chrome at the bottom of this file (category
   accordions, strip, mixer pane), which is Profile UI and must render
   identically regardless of data-ambient.
   Layer: #ambientLayer (fixed, z-index:-1, behind transparent <main>).
   Lights are theme-token colored — the recipe below is the Midnight-dark
   pilot; other themes recolor automatically via their own token values.
   iOS parity: AmbientBackgroundView.swift (same geometry + tempo).
   ============================================================ */

#ambientLayer { display: none; }

[data-ambient="1"] #ambientLayer {
  display: block;
  position: fixed;
  inset: 0;
  z-index: -1;            /* behind all in-flow content; body bg is beneath it */
  pointer-events: none;
  background: var(--bg);  /* base coat — lets surfaces go transparent safely */
  overflow: hidden;
}

/* ── STYLE SWITCH ────────────────────────────────────────────────────────
   The ambient layer hosts one recipe at a time; only the wrapper matching
   <html data-ambient-style> renders. Crossfade via opacity. Grain +
   vignette live OUTSIDE these wrappers (style-agnostic).
   iOS parity: activeStyle() switch in AmbientBackgroundView.

   Blend-stack note (round-4.1 fix preserved): at rest the active wrapper is
   opacity:1, which does NOT create a stacking context (only opacity<1 does),
   and position:absolute alone (z-index:auto) doesn't either — so
   .ambient-goo-wrap's mix-blend-mode:screen still composites against
   #ambientLayer's --bg backdrop exactly as before wrapping. During the 0.6s
   crossfade the transient opacity<1 briefly isolates the group; that only
   affects the fade instant, not the steady-state look. Verified visually:
   liquid renders identical to pre-wrap (soft molten gold/teal, no fringe). */
#ambientLayer .ambient-style-liquid,
#ambientLayer .ambient-style-aurora,
#ambientLayer .ambient-style-breathe,
#ambientLayer .ambient-style-lava {
  position: absolute; inset: 0;
  opacity: 0;
  transition: opacity 0.6s ease;
  pointer-events: none;
}
[data-ambient-style="liquid"]  #ambientLayer .ambient-style-liquid  { opacity: 1; }
[data-ambient-style="aurora"]  #ambientLayer .ambient-style-aurora  { opacity: 1; }
[data-ambient-style="breathe"] #ambientLayer .ambient-style-breathe { opacity: 1; }
[data-ambient-style="lava"]    #ambientLayer .ambient-style-lava    { opacity: 1; }

/* ── ANIME PIVOT (2026-07-21) ────────────────────────────────────────────
   The procedural style wrappers + canvas are retired — the background is now
   the per-theme graded still (.ambient-still) with the video crossfading over
   it. Hidden (not removed from the DOM) so ambient-depth.js / ambient-canvas.js
   querying these elements never throws. */
#ambientLayer .ambient-style-liquid,
#ambientLayer .ambient-style-aurora,
#ambientLayer .ambient-style-breathe,
#ambientLayer .ambient-style-lava,
#ambientLayer #ambientCanvas { display: none !important; }

/* Per-theme graded still — the animated background pre-blitz and the base the
   video crossfades over. background-image set by ambient-video.js, keyed on
   (style, effective grade, mode). Sits under the top-level video + grain +
   vignette (DOM order). */
.ambient-still {
  position: absolute; inset: 0;
  background-size: cover;
  background-position: center;
  pointer-events: none;
}

/* ── CANVAS STYLES (drift / ink) ─────────────────────────────────────────
   drift + ink are rAF-painted into <canvas id="ambientCanvas"> by
   ambient-canvas.js (iOS parity: DriftStyle.swift / InkStyle.swift). Only
   this CSS drives show/hide (0→1 crossfade) + the blend/blur treatment; the
   engine owns the per-style opacity BUDGET via ctx.globalAlpha so the two
   never fight. Blend modes mirror the iOS .blendMode() per style/mode:
   drift plus-lighter, ink screen (dark) → both multiply (light). Ink's
   softness comes from filter:blur — it draws crisp radial blooms that the
   blur melts into wet edges (iOS uses the Canvas alphaThreshold+blur). */
#ambientCanvas {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  opacity: 0;
  transition: opacity 0.6s ease;
  pointer-events: none;
}
[data-ambient-style="drift"] #ambientCanvas,
[data-ambient-style="ink"]   #ambientCanvas { opacity: 1; }
[data-ambient-style="drift"] #ambientCanvas { mix-blend-mode: plus-lighter; }
[data-ambient-style="ink"]   #ambientCanvas { mix-blend-mode: screen; filter: blur(10px); }
:root[data-mode="light"][data-ambient-style="drift"] #ambientCanvas,
:root[data-mode="light"][data-ambient-style="ink"]   #ambientCanvas { mix-blend-mode: multiply; }

/* Lights: soft radial falloff baked into the gradient (NO filter:blur —
   the transparent stop IS the softness; animation is compositor-only). */
.ambient-light {
  position: absolute;
  border-radius: 50%;
  will-change: transform, opacity;
}
/* Round-4 (Jeff): "psychedelic, physics, water, waves, gelatinous —
   something wonderful." LIQUID LIGHT: a goo field — blobs of gold and
   teal that drift on independent orbits and visibly MERGE AND SPLIT
   like immiscible liquids, each slowly morphing shape. The classic
   gooey trick: a black-backed container with blur+contrast so blob
   alpha edges fuse; screen-blended over the app so only light shows.
   Brightness budget matches round-3 (fun to watch, quiet at a glance).
   iOS parity: Canvas alphaThreshold metaballs in AmbientBackgroundView. */
/* Outer wrap melts the goo's thresholded edges into wet light and owns
   the screen blend (blend must apply AFTER the final blur, or contrast
   fringing shows — the round-4.1 fix: contrast(14) on gradient fills
   clipped channels at different radii → hard discs with rainbow rims). */
.ambient-goo-wrap {
  position: absolute; inset: 0;
  mix-blend-mode: screen;
  filter: blur(16px);
}
.ambient-goo {
  position: absolute; inset: -10%;
  background: #000;                 /* knocked out by the wrap's screen blend */
  filter: blur(36px) contrast(7);   /* the goo: edges fuse where blobs meet */
  opacity: .5;
}
.goo-blob {
  position: absolute;
  border-radius: 46% 54% 52% 48% / 55% 45% 57% 43%;
}
.goo-blob.gold { background: var(--accent); }
.goo-blob.teal { background: var(--accent-secondary); }

/* Five masses: sizes/orbits/morphs all incommensurate so the composite
   never visibly repeats. Morph (border-radius squish, 9-16s) is the
   gelatinous life; orbit (26-50s) is the journey. */
.goo-blob:nth-child(1) { width: 46vmax; height: 44vmax; left: 4vmax;  bottom: -6vmax;
  animation: goo-orbit-1 40s ease-in-out infinite, goo-morph-a 11s ease-in-out infinite; }
.goo-blob:nth-child(2) { width: 34vmax; height: 36vmax; left: 30vmax; bottom: 8vmax;
  animation: goo-orbit-2 31s ease-in-out infinite, goo-morph-b 9s  ease-in-out infinite; }
.goo-blob:nth-child(3) { width: 40vmax; height: 38vmax; right: 2vmax; top: -4vmax;
  animation: goo-orbit-3 47s ease-in-out infinite, goo-morph-a 13s ease-in-out infinite reverse; }
.goo-blob:nth-child(4) { width: 26vmax; height: 28vmax; right: 24vmax; top: 16vmax;
  animation: goo-orbit-4 26s ease-in-out infinite, goo-morph-b 16s ease-in-out infinite; }
.goo-blob:nth-child(5) { width: 30vmax; height: 30vmax; left: 42vmax; top: 40vmax;
  animation: goo-orbit-5 37s ease-in-out infinite, goo-morph-a 10s ease-in-out infinite reverse; }

@keyframes goo-orbit-1 {
  0%, 100% { transform: translate(0, 0) rotate(0deg); }
  30%      { transform: translate(26vmax, -10vmax) rotate(40deg); }
  60%      { transform: translate(40vmax, 4vmax) rotate(-20deg); }
  80%      { transform: translate(12vmax, 10vmax) rotate(15deg); }
}
@keyframes goo-orbit-2 {
  0%, 100% { transform: translate(0, 0) rotate(0deg); }
  35%      { transform: translate(-16vmax, -18vmax) rotate(-50deg); }
  70%      { transform: translate(10vmax, -30vmax) rotate(30deg); }
}
@keyframes goo-orbit-3 {
  0%, 100% { transform: translate(0, 0) rotate(0deg); }
  25%      { transform: translate(-28vmax, 14vmax) rotate(25deg); }
  55%      { transform: translate(-44vmax, 26vmax) rotate(-35deg); }
  80%      { transform: translate(-14vmax, 8vmax) rotate(10deg); }
}
@keyframes goo-orbit-4 {
  0%, 100% { transform: translate(0, 0) rotate(0deg); }
  40%      { transform: translate(-20vmax, 22vmax) rotate(60deg); }
  75%      { transform: translate(8vmax, 34vmax) rotate(-40deg); }
}
@keyframes goo-orbit-5 {
  0%, 100% { transform: translate(0, 0) rotate(0deg); }
  33%      { transform: translate(-30vmax, -12vmax) rotate(-30deg); }
  66%      { transform: translate(-8vmax, -26vmax) rotate(45deg); }
}
/* Gelatinous shape morphs — asymmetric radii rolling around the blob */
@keyframes goo-morph-a {
  0%, 100% { border-radius: 46% 54% 52% 48% / 55% 45% 57% 43%; }
  33%      { border-radius: 58% 42% 44% 56% / 42% 60% 40% 58%; }
  66%      { border-radius: 40% 60% 62% 38% / 58% 40% 55% 45%; }
}
@keyframes goo-morph-b {
  0%, 100% { border-radius: 52% 48% 44% 56% / 48% 54% 46% 52%; }
  50%      { border-radius: 38% 62% 58% 42% / 60% 38% 62% 40%; }
}

/* ═══ STYLE: AURORA — braided light curtains ══════════════════════
   2-3 vertical curtains undulating horizontally (60-90s traversals)
   with slow brightness swells. Dark: glow (screen). Light: dilute
   pigment banners (multiply). iOS parity: AuroraStyle.swift. */
.ambient-style-aurora { mix-blend-mode: screen; filter: blur(46px); }
:root[data-mode="light"] .ambient-style-aurora { mix-blend-mode: multiply; }
.aurora-curtain {
  position: absolute;
  top: -15%; height: 130%;
  border-radius: 50%;
  /* Frozen-state fallback, NOT dead code: while the swell animation runs,
     this static value is overridden every frame by the keyframe fill (see
     aurora-swell-1/2 below). But prefers-reduced-motion kills the
     animation entirely (animation: none !important, further down this
     file) — without a static value here the element falls back to the
     UA default opacity:1, i.e. full-blast frozen curtains. Mid-swell
     value, damped the same way the running animation is. */
  opacity: calc(.45 * var(--ambient-damp, 1));
  will-change: transform, opacity;
}
.aurora-curtain.c1 { left: 8%;  width: 34%; background: linear-gradient(175deg, transparent 0%, var(--accent) 35%, color-mix(in srgb, var(--accent) 55%, transparent) 70%, transparent 100%);
  animation: aurora-sweep-1 74s ease-in-out infinite, aurora-swell-1 41s ease-in-out infinite; }
.aurora-curtain.c2 { left: 40%; width: 28%; background: linear-gradient(185deg, transparent 0%, var(--accent-secondary) 35%, color-mix(in srgb, var(--accent-secondary) 55%, transparent) 70%, transparent 100%);
  animation: aurora-sweep-2 89s ease-in-out infinite, aurora-swell-2 53s ease-in-out infinite; }
.aurora-curtain.c3 { left: 66%; width: 20%; background: linear-gradient(170deg, transparent 0%, var(--accent) 30%, transparent 100%);
  animation: aurora-sweep-3 61s ease-in-out infinite, aurora-swell-1 33s ease-in-out infinite reverse; }
@keyframes aurora-sweep-1 { 0%,100% { transform: translateX(-24vw) skewX(-4deg); } 50% { transform: translateX(30vw) skewX(5deg); } }
@keyframes aurora-sweep-2 { 0%,100% { transform: translateX(18vw) skewX(3deg); }  50% { transform: translateX(-26vw) skewX(-6deg); } }
@keyframes aurora-sweep-3 { 0%,100% { transform: translateX(10vw) skewX(-3deg); } 50% { transform: translateX(-16vw) skewX(4deg); } }
@keyframes aurora-swell-1 { 0%,100% { opacity: calc(.28 * var(--ambient-damp, 1)); } 50% { opacity: calc(.62 * var(--ambient-damp, 1)); } }
@keyframes aurora-swell-2 { 0%,100% { opacity: calc(.55 * var(--ambient-damp, 1)); } 50% { opacity: calc(.22 * var(--ambient-damp, 1)); } }
/* Daylight calibration: pigment banners quieter on paper. --ambient-damp
   multiplies inside the swell keyframes above (animations fill opacity,
   so a static opacity here would be dead — keyframes stay single-source). */
:root[data-mode="light"] .aurora-curtain { --ambient-damp: .55; }

/* ═══ STYLE: BREATHE — one glow, breathing ════════════════════════
   ~104s breath + very slow migration; counter-phased faint echo.
   iOS parity: BreatheStyle.swift. */
.ambient-style-breathe { mix-blend-mode: screen; }
:root[data-mode="light"] .ambient-style-breathe { mix-blend-mode: multiply; }
.breathe-bloom {
  position: absolute;
  border-radius: 50%;
  will-change: transform, opacity;
}
.breathe-bloom.primary {
  width: 90vmax; height: 90vmax; left: 5vw; top: 30vh;
  background: radial-gradient(circle, color-mix(in srgb, var(--accent) 55%, transparent) 0%, transparent 60%);
  animation: breathe-swell 104s ease-in-out infinite, breathe-migrate 540s ease-in-out infinite;
  /* Frozen-state fallback (same reasoning as .aurora-curtain above): the
     running animation overrides this every frame, but prefers-reduced-
     motion strips the animation and this is what's left on screen. */
  opacity: calc(.6 * var(--ambient-damp, 1));
}
.breathe-bloom.echo {
  width: 60vmax; height: 60vmax; right: -10vw; top: -10vh;
  background: radial-gradient(circle, color-mix(in srgb, var(--accent-secondary) 35%, transparent) 0%, transparent 60%);
  animation: breathe-swell 104s ease-in-out infinite reverse;
  /* echo fainter than primary via damp (multiplies inside breathe-swell
     while animating). Static opacity below is the frozen-state fallback
     for prefers-reduced-motion — same .6 mid-swell base as primary, but
     --ambient-damp: .5 here makes the frozen echo fainter too; light-mode
     damps compose via the explicit :root[data-mode="light"] overrides
     further down. */
  --ambient-damp: .5;
  opacity: calc(.6 * var(--ambient-damp, 1));
}
@keyframes breathe-swell   { 0%,100% { transform: scale(.82); opacity: calc(.38 * var(--ambient-damp, 1)); } 50% { transform: scale(1.05); opacity: calc(.85 * var(--ambient-damp, 1)); } }
@keyframes breathe-migrate { 0%,100% { translate: 0 0; } 50% { translate: 14vw -10vh; } }
:root[data-mode="light"] .breathe-bloom { --ambient-damp: .5; }
:root[data-mode="light"] .breathe-bloom.echo { --ambient-damp: .25; }  /* custom props don't compose across rules — combined value set explicitly */

/* Grain: ~1.5% turbulence noise over the lights breaks the 8-bit radial
   banding that surfaces once bloom opacity is high enough to see
   (grader finding 2026-07-17). Pure data-URI SVG — no asset, no JS. */
.ambient-grain {
  position: absolute; inset: 0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='1'/%3E%3C/svg%3E");
  opacity: .035;
  mix-blend-mode: overlay;
}

/* Vignette: pulls edges back toward --bg so global luminance stays near
   today's — the readability scrim from the spec. */
.ambient-vignette {
  position: absolute; inset: 0;
  background: radial-gradient(ellipse at 50% 38%, transparent 42%, var(--bg) 100%);
  opacity: .55;
}

@keyframes ambient-journey-a {
  0%, 100% { transform: translate(0, 0) scale(1); }
  25%      { transform: translate(30vmax, -14vmax) scale(1.08); }
  50%      { transform: translate(46vmax, -6vmax) scale(1.14); }
  75%      { transform: translate(16vmax, 6vmax) scale(1.04); }
}
@keyframes ambient-journey-b {
  0%, 100% { transform: translate(0, 0) scale(1); }
  30%      { transform: translate(-34vmax, 12vmax) scale(1.06); }
  60%      { transform: translate(-52vmax, 4vmax) scale(.96); }
  85%      { transform: translate(-18vmax, 16vmax) scale(1.03); }
}
@keyframes ambient-journey-c {
  0%, 100% { transform: translate(0, 0) scale(1); }
  33%      { transform: translate(-22vmax, 16vmax) scale(1.1); }
  66%      { transform: translate(14vmax, 24vmax) scale(.95); }
}
@keyframes ambient-chameleon-gold {
  0%, 100% { opacity: .14; }
  50%      { opacity: .02; }
}
@keyframes ambient-chameleon-teal {
  0%, 100% { opacity: .02; }
  50%      { opacity: .14; }
}

@media (prefers-reduced-motion: reduce) {
  .ambient-light, .goo-blob, .aurora-curtain, .breathe-bloom, .lava-blob { animation: none !important; }
}

/* ── Frost (sheets + floating chrome only; cards stay solid) ──
   Recipe: 78% surface-panel over 18px saturated blur + 1px top
   catch-light. Extends the .topnav house vocabulary (style.css:192).
   :root anchor lifts specificity to (0,3,0) so this beats the
   per-theme topnav overrides in briefing.css ([data-theme][data-mode]
   .topnav) via load order. iOS parity: AmbientFrostModifier. */
:root[data-ambient="1"] .topnav,
:root[data-ambient="1"] .destination-tabs,
:root[data-ambient="1"] .modal .modal-content,
:root[data-ambient="1"] .gyst-top-sheet,
:root[data-ambient="1"] .gyst-confirm-sheet,
:root[data-ambient="1"] .gyst-popover,
:root[data-ambient="1"] .gyst-toast {
  background: color-mix(in srgb, var(--surface-panel) var(--frost-tint), transparent);
  -webkit-backdrop-filter: blur(var(--frost-blur)) saturate(1.2);
  backdrop-filter: blur(var(--frost-blur)) saturate(1.2);
  /* glass catch-light: 1px top inner highlight */
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--white) 8%, transparent);
}

/* Timeline week-nav strips — the [This Week / ← → / DATE / Filter] rows
   that sit above AND below the day tiles (plus the month strip). Brings
   them into the frost vocabulary as a floating rounded glass BAR so the
   small 11–12px controls read cleanly over the animated atmosphere
   instead of raw transparent chrome. A touch more tint than floating
   chrome (82% vs 78%) for legibility; glass catch-light + a soft
   elevation lift + a rounded float margin. Flag-off (no data-ambient)
   keeps the bare transparent strip, pixel-identical.
   iOS parity: AmbientChromeBarModifier (ambientChromeBar()). */
:root[data-ambient="1"] .strip-header {
  background: color-mix(in srgb, var(--surface-panel) var(--frost-tint), transparent);
  -webkit-backdrop-filter: blur(var(--frost-blur)) saturate(1.2);
  backdrop-filter: blur(var(--frost-blur)) saturate(1.2);
  border: 1px solid color-mix(in srgb, var(--chrome-border, var(--border)) 55%, transparent);
  border-radius: 14px;
  padding: 8px 12px;
  /* No float inset — the frosted bar's border sits on the same content
     edge as the day cards (Jeff 2026-08-17: the nav bar read narrower
     than everything else in the Today view). */
  margin-left: 0;
  margin-right: 0;
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--white) 16%, transparent), /* catch-light */
    0 10px 24px -12px rgba(0, 0, 0, 0.5);                            /* elevation */
}

/* Daily timeline cards — Jeff round-1 note: "a little frosted glass
   transparency, just enough to notice" — much lighter than chrome frost
   (62% solid, 14px blur — 88% and 76% both read opaque on device) so the ambient light barely breathes through
   without muddying the timeline. iOS parity: ambientCardFrost() on
   DayTileView. */
:root[data-ambient="1"] .day-tile {
  background: color-mix(in srgb, var(--surface-panel, var(--bg-2)) var(--frost-tint), transparent);
  -webkit-backdrop-filter: blur(var(--frost-blur)) saturate(1.1);
  backdrop-filter: blur(var(--frost-blur)) saturate(1.1);
}

/* ═══ PHASE-2 #2: ELEVATION SYSTEM ════════════════════════════════
   REVERT: localStorage.setItem('gystDepth-elev','0') or delete section.
   Layered shadows (soft ambient drop + tight contact) + gradient ring
   border (lit top edge, shaded bottom — beveled glass) + top catch-
   light. Today keeps its accent outline untouched.
   iOS parity: AmbientElevation modifier + gradient ring in DayTileView. */
[data-depth-elev] .day-tile {
  position: relative;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.07),
    0 1px 2px rgba(0, 0, 0, 0.55),
    0 12px 32px -8px rgba(0, 0, 0, 0.55);
}
[data-depth-elev] .day-tile:not(.today) { border-color: transparent; }
[data-depth-elev] .day-tile:not(.today)::after {
  content: '';
  position: absolute; inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: linear-gradient(180deg,
    rgba(255, 255, 255, 0.16),
    rgba(255, 255, 255, 0.03) 32%,
    rgba(0, 0, 0, 0.28));
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
  z-index: 3;
}

/* ═══ PHASE-2 #5: INTERIOR CARD DEPTH ═════════════════════════════
   REVERT: localStorage.setItem('gystDepth-int','0') or delete section.
   The card interior gets strata: the NOW line blooms (lit object, not
   printed line), the hour axis recedes. Event bars already carry depth
   shadows (calendar.css .track-event). iOS parity: ambientInteriorDepth
   flag on TimelineRenderInputs (drawNowLine glow). */
[data-depth-int] .track-now-line {
  box-shadow:
    0 0 10px 1px color-mix(in srgb, var(--danger) 65%, transparent),
    0 0 3px 0 color-mix(in srgb, var(--danger) 85%, transparent);
}
[data-depth-int] .track-hour { opacity: 0.55; }
[data-depth-int] .track-hour-label { opacity: 0.65; }

/* ═══ PHASE-2 #1: REACTIVE CARD LIGHTING ══════════════════════════
   REVERT: localStorage.setItem('gystDepth-light','0') or delete section.
   ambient-depth.js drives --al-x/--al-y/--al-i per tile from the gold
   liquid's real position — the room lights the furniture.
   iOS parity: AmbientReactiveLight modifier. */
[data-depth-light] .day-tile::before {
  content: '';
  position: absolute; inset: 0;
  border-radius: inherit;
  background: radial-gradient(130% 130% at var(--al-x, 50%) var(--al-y, -30%),
    rgba(255, 255, 255, var(--al-i, 0)), transparent 62%);
  pointer-events: none;
  z-index: 2;
  transition: background 0.25s linear;
}

/* ═══ PHASE-2 #3: PARALLAX ════════════════════════════════════════
   REVERT: localStorage.setItem('gystDepth-par','0') or delete section.
   ambient-depth.js lerps the liquid plane toward the pointer (iOS:
   gyro). The wrap scales slightly so shifted edges never expose. */
[data-depth-par] .ambient-goo-wrap { will-change: transform; }
[data-depth-par] .ambient-goo { inset: -14%; }

/* ═══ PHASE-2 #4: TOUCH PHYSICS ═══════════════════════════════════
   REVERT: localStorage.setItem('gystDepth-touch','0') or delete section.
   Hover lift (deeper shadow + rise), press settle, and a scroll sheen —
   a faint specular band that sweeps card surfaces as they move through
   the viewport (--sheen-y from ambient-depth.js).
   iOS parity: AmbientPressable (press tilt/scale). */
[data-depth-touch] .day-tile {
  transition: transform 0.22s cubic-bezier(0.25, 0.46, 0.45, 0.94),
              box-shadow 0.22s ease, border-color 0.2s;
}
[data-depth-touch] .day-tile:hover {
  transform: translateY(-2px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.09),
    0 2px 3px rgba(0, 0, 0, 0.55),
    0 18px 44px -8px rgba(0, 0, 0, 0.6);
}
[data-depth-touch] .day-tile:active { transform: translateY(-1px) scale(0.997); }
[data-depth-touch] .day-tile .day-tile-header {
  background-image: linear-gradient(180deg,
    rgba(255, 255, 255, 0.045) calc(var(--sheen-y, 50%) - 12%),
    rgba(255, 255, 255, 0.0) calc(var(--sheen-y, 50%) + 12%));
}

/* ═══ DAYLIGHT (LIGHT-MODE) RECIPE — liquid light becomes WATERCOLOR ══
   On dark, the liquids are luminous (screen blend adds light). On warm
   paper, added light is invisible — so the same masses become PIGMENT:
   multiply-blended terracotta + slate-blue that tint and pool on the
   paper like wet watercolor. Shadows carry more of the depth; the card
   sheen turns warm. Scoped by data-mode (theme-loader) so every light
   variant inherits the translation. iOS parity: colorScheme-aware
   branches in AmbientBackground.swift. */
:root[data-ambient="1"][data-mode="light"] .ambient-goo-wrap {
  mix-blend-mode: multiply;
}
:root[data-ambient="1"][data-mode="light"] .ambient-goo {
  background: #fff;            /* multiply knocks WHITE out on light */
  opacity: .34;
  filter: blur(36px) contrast(5);
}
:root[data-ambient="1"][data-mode="light"] .ambient-grain { opacity: .02; }

/* Elevation: light-mode bevel — warm soft shadows; the ring flips to a
   bright top edge over a shaded base (paper lit from above). */
:root[data-mode="light"][data-depth-elev] .day-tile {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.65),
    0 1px 2px rgba(90, 70, 40, 0.18),
    0 12px 32px -8px rgba(90, 70, 40, 0.22);
}
:root[data-mode="light"][data-depth-elev] .day-tile:not(.today)::after {
  background: linear-gradient(180deg,
    rgba(255, 255, 255, 0.85),
    rgba(0, 0, 0, 0.05) 38%,
    rgba(0, 0, 0, 0.14));
}
:root[data-mode="light"][data-depth-touch] .day-tile:hover {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.7),
    0 2px 3px rgba(90, 70, 40, 0.2),
    0 18px 44px -8px rgba(90, 70, 40, 0.28);
}

/* Reactive lighting: a white sheen is invisible on paper — the passing
   gold mass instead warms the card with an accent-tinted wash. */
:root[data-mode="light"][data-depth-light] .day-tile::before {
  background: radial-gradient(130% 130% at var(--al-x, 50%) var(--al-y, -30%),
    color-mix(in srgb, var(--accent) 55%, transparent), transparent 62%);
  opacity: calc(var(--al-i, 0) * 4.5);
}

/* Scroll sheen: warm tint instead of white on the header band. */
:root[data-mode="light"][data-depth-touch] .day-tile .day-tile-header {
  background-image: linear-gradient(180deg,
    color-mix(in srgb, var(--accent) 5%, transparent) calc(var(--sheen-y, 50%) - 12%),
    transparent calc(var(--sheen-y, 50%) + 12%));
}

/* Daylight calibration: pigment at .34 washed the tile interiors too
   loudly (interiors read tinted, not frosted). Calmer paper: softer
   goo, more opaque tiles in light mode. */
:root[data-ambient="1"][data-mode="light"] .ambient-goo { opacity: .24; }
:root[data-mode="light"][data-ambient="1"] .day-tile {
  background: color-mix(in srgb, var(--surface-panel, var(--bg-2)) var(--frost-tint), transparent);
}

/* ═══ ROUND-5: CARD FROST CONSISTENCY ═════════════════════════════
   Jeff's Daylight device note: card frost existed only on timeline
   day tiles — missing from the Today peek (tasks/habits), Up Next,
   and the Habits + Month destinations. Same recipe as .day-tile:
   62% of the card's own solid color over blur(var(--frost-blur)) saturate(1.1),
   82% in light mode (Daylight calibration — pigment washes read
   louder than glow). --ambient-card-solidity carries that per-mode
   number so every card family + state variant calibrates from one
   knob. State variants (hover / overdue / routine-done / blank) are
   re-declared below because these :root-anchored rules out-rank the
   (0,2,0) state rules in tasks.css / habits.css / calendar.css.
   iOS parity: ambientCardFrost() on TaskRowView, HabitCardView,
   RoutineCardView, MonthDestination agenda rows, and
   FormGroupPanel(ambientCardFrosted:) for Up Next. */
:root[data-ambient="1"] {
  /* Jeff-settled frost defaults (2026-07-23), applied uniformly to EVERY frost
     surface for iOS parity (ThemeManager.frostTint 0.50 / frostBlur 0.40).
     --frost-blur ~8px ≈ 40% of the full 18px chrome blur. iOS keeps the two as
     live sliders; web pins the settled values here. */
  --frost-tint: 50%;
  --frost-blur: 8px;
  --ambient-card-solidity: var(--frost-tint);
}

/* bg-1 cards: task rows, peek rows, peek mini-month cells, habit/routine
   cards. (The Money peek's recent-charge cards were briefly here; they now
   nest inside the peek's frosted tile and stay solid — frost inside frost
   reads as fog.) */
:root[data-ambient="1"] .task-item,
:root[data-ambient="1"] .today-peek-row,
:root[data-ambient="1"] .today-peek-month-cell,
:root[data-ambient="1"] .habit-card {
  background: color-mix(in srgb, var(--bg-1) var(--ambient-card-solidity), transparent);
  -webkit-backdrop-filter: blur(var(--frost-blur)) saturate(1.1);
  backdrop-filter: blur(var(--frost-blur)) saturate(1.1);
}

/* surface-panel cards: Up Next (iOS FormGroupPanel ambientCardFrosted) and
   every panel in the Money destination — the net-worth hero, each institution
   card, the step-up gate, the passkey-offer banner, the Plaid consent panel,
   and the syncing skeletons. They are all a bare `.gyst-panel` inside
   #moneySection, so ONE descendant selector enrolls the whole destination and
   any Money panel added later is frosted the day it ships (the lesson the
   eyebrow-chip block below records). Money shipped with no ambient rules at
   all; this is the retrofit. iOS parity: FormGroupPanel(ambientCardFrosted:)
   in MoneyOverviewView + FinanceGateView. */
:root[data-ambient="1"] .gyst-up-next-panel,
:root[data-ambient="1"] .today-peek-money,
:root[data-ambient="1"] .money-section .gyst-panel {
  background: color-mix(in srgb, var(--surface-panel) var(--ambient-card-solidity), transparent);
  -webkit-backdrop-filter: blur(var(--frost-blur)) saturate(1.1);
  backdrop-filter: blur(var(--frost-blur)) saturate(1.1);
}

/* Sign-in card (login.html) — the auth page carries the same #ambientLayer
   markup as the app, so a returning user's remembered atmosphere greets them
   before they sign in (Jeff, 2026-08-14). The card paints var(--bg-1) in
   login.html's own styles, so it frosts from that token — the .list-card
   precedent. Flag off → the page is pixel-identical to the pre-ambient login.
   iOS parity: LoginView AmbientBackgroundView + AuthCard ambientCardFrost. */
:root[data-ambient="1"] .auth-card {
  background: color-mix(in srgb, var(--bg-1) var(--ambient-card-solidity), transparent);
  -webkit-backdrop-filter: blur(var(--frost-blur)) saturate(1.1);
  backdrop-filter: blur(var(--frost-blur)) saturate(1.1);
}

/* Money's desktop sub-nav chips sit on the atmosphere with their own bg-3
   surface, so they frost from that token rather than surface-panel — the
   .list-card precedent (a card frosted from the colour it already painted).
   Border and radius stay exactly as financial.css sets them. */
:root[data-ambient="1"] .money-subnav-chip {
  background: color-mix(in srgb, var(--bg-3) var(--ambient-card-solidity), transparent);
  -webkit-backdrop-filter: blur(var(--frost-blur)) saturate(1.1);
  backdrop-filter: blur(var(--frost-blur)) saturate(1.1);
}

/* bg-2 cards: month grid day cells (desktop grid + mobile spoke dials
   share .grid-day — calendar.css keeps the background on both) */
:root[data-ambient="1"] .grid-day {
  background: color-mix(in srgb, var(--bg-2) var(--ambient-card-solidity), transparent);
  -webkit-backdrop-filter: blur(var(--frost-blur)) saturate(1.1);
  backdrop-filter: blur(var(--frost-blur)) saturate(1.1);
}

/* bg-2 card: list cards — the Lists destination AND the Today Lists peek
   (both are iOS ListRowCard). Was missing from the frost scope (raw system
   material on iOS / plain --bg-2 on web) so it didn't frost with the rest.
   iOS parity: ambientCardFrost() on ListRowCard. */
:root[data-ambient="1"] .list-card {
  background: color-mix(in srgb, var(--bg-2) var(--ambient-card-solidity), transparent);
  -webkit-backdrop-filter: blur(var(--frost-blur)) saturate(1.1);
  backdrop-filter: blur(var(--frost-blur)) saturate(1.1);
}
:root[data-ambient="1"] .list-card:hover {
  background: color-mix(in srgb, var(--bg-3) var(--ambient-card-solidity), transparent);
}

/* Bare eyebrow label + link chips — eyebrows that sit directly on the
   atmosphere as dim text with no surface, which the animated background
   swallows. Hug each in a frosted-glass CHIP (app chip radius 8, deliberately
   not a full pill) at the settled frost values. Flag-off keeps the bare label.
   iOS parity: ambientLabelChip() — SectionLabel(ambientProtected:),
   MonthDaySectionLabel, TaskSectionHeader, HabitsPanelView HABITS/ROUTINES,
   MonthDestination agenda date, TasksDestination "Completed",
   ListsDestination "DORMANT", PeekHeader See-all, MoneyOverviewView
   "Net Worth" (SectionLabel ambientProtected:).

   ONE declaration for every eyebrow. It was duplicated per selector, which is
   how the newer destinations ended up with no chip at all — the recipe existed
   but adding a surface meant remembering to copy nine properties.

   .task-section-label is the shared eyebrow for TODAY / UPCOMING and the whole
   habits pane (MORNING / AFTERNOON / EVENING / HABITS / ROUTINES), so one
   selector covers both destinations. Two carve-outs, both flex rows that hold
   more than a label: the overdue header (danger-coloured with its own count
   capsule — already legible, and chipping it would box the icon too) and the
   active-reminders header (contains a DISMISS ALL button). */
:root[data-ambient="1"] .gyst-up-next .gyst-section-label,
:root[data-ambient="1"] .today-peek-see-all,
:root[data-ambient="1"] .mda-section-label,
:root[data-ambient="1"] .mda-titlewrap,
:root[data-ambient="1"] .task-section-label:not(.task-section-label-overdue):not(.task-section-label-bare),
:root[data-ambient="1"] .lists-dormant-toggle,
:root[data-ambient="1"] .money-hero-label-row .gyst-section-label,
:root[data-ambient="1"] .money-connections-section .gyst-section-label,
/* Every Money eyebrow, not just the two that were enrolled first: the
   Reports hero label, the Activity month headers, the Reports type-group
   headers and the due-soon overline all sit directly on the atmosphere and
   washed out against it (2026-08-17 parity audit). iOS passes
   ambientProtected: true on all of them. */
:root[data-ambient="1"] .money-section .gyst-section-label,
:root[data-ambient="1"] .money-txn-day-label,
:root[data-ambient="1"] .money-txn-filter-label {
  width: fit-content;
  padding: 3px 8px;
  border-radius: 8px;
  background: color-mix(in srgb, var(--surface-panel) var(--frost-tint), transparent);
  -webkit-backdrop-filter: blur(var(--frost-blur)) saturate(1.1);
  backdrop-filter: blur(var(--frost-blur)) saturate(1.1);
  border: 1px solid color-mix(in srgb, var(--chrome-border, var(--border)) 55%, transparent);
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--white) 12%, transparent);
}
/* Up Next's label is the only one that was a block with its own section gap.
   Zero side margins — the frosted chip's border shares the content edge with
   the panel below it (Jeff 2026-08-17: the eyebrow sat inset from
   everything else at scaled tiers, where --gyst-row-px grows). */
:root[data-ambient="1"] .gyst-up-next .gyst-section-label {
  display: block;
  margin: 16px 0 6px;  /* was 20px/6px padding — keep the section gap */
}
/* The chip's padding replaces `padding: 4px 0 6px`, which was carrying the gap
   between an eyebrow and its list. Re-state it as margin so the rhythm holds. */
:root[data-ambient="1"] .task-section-label:not(.task-section-label-overdue):not(.task-section-label-bare) {
  margin: 4px 0 6px;
}
/* State variants — same solid colors the base files use, frosted. */
:root[data-ambient="1"] .task-item:hover,
:root[data-ambient="1"] .habit-card:hover {
  background: color-mix(in srgb, var(--bg-2) var(--ambient-card-solidity), transparent);
}
:root[data-ambient="1"] .task-item.overdue {
  background: color-mix(in srgb, color-mix(in srgb, var(--danger) 6%, var(--bg-1)) var(--ambient-card-solidity), transparent);
}
:root[data-ambient="1"] .habit-card.routine-done {
  background: color-mix(in srgb, color-mix(in srgb, var(--success) 6%, var(--bg-1)) var(--ambient-card-solidity), transparent);
}
:root[data-ambient="1"] .habit-card.routine-done:hover {
  background: color-mix(in srgb, color-mix(in srgb, var(--success) 10%, var(--bg-1)) var(--ambient-card-solidity), transparent);
}
/* Blank mini-month cells stay invisible — frost would draw ghost tiles. */
:root[data-ambient="1"] .today-peek-month-cell.is-blank {
  background: transparent;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* Mobile month redesign (iOS MonthGridView): the month now lives in a frosted
   FRAME with frosted per-day CELLS and a frosted nav bar — no longer the old
   chrome-less strip. The per-cell frost comes from the :root[data-ambient]
   .grid-day rule above (it applies on mobile now); here we blur the frame + nav
   and keep today's accent gradient from being overwritten by the cell frost. */
@media (max-width: 768px),
       (pointer: coarse) and (hover: none),
       (orientation: landscape) and (max-height: 500px) {
  :root[data-ambient="1"] .month-frame,
  :root[data-ambient="1"] .cal-month-nav {
    -webkit-backdrop-filter: blur(var(--frost-blur)) saturate(1.05);
    backdrop-filter: blur(var(--frost-blur)) saturate(1.05);
  }
  /* Today's whole-cell accent gradient must win over the bg-2 cell frost. */
  :root[data-ambient="1"] .grid-day.today-cell {
    background: linear-gradient(180deg,
      color-mix(in srgb, var(--accent) 14%, transparent),
      color-mix(in srgb, var(--bg-2) var(--ambient-card-solidity), transparent));
  }
}

/* ═══ STYLE: LAVA LAMP — the vibrant pivot pilot ══════════════════
   Jeff 2026-07-18, reference video tmp/jeff-video-for-claude*.mp4:
   two huge OPAQUE saturated liquid masses (warm=--accent, cool=
   --accent-secondary) that rise, sink, stretch into necks and merge,
   plus satellite droplets. Deliberately LOUD in both modes — no
   translucent-glow budget, no multiply-pigment fade. 3D read comes
   from the per-blob radial gradient (lit core → saturated body →
   dark rim); necks/merges come from the SVG #gooLava filter (blur +
   ALPHA-only contrast — preserves interior gradients, unlike CSS
   contrast(), the round-4.1 fringe gotcha). Two fields keep the
   colors immiscible. iOS parity: LavaLampStyle.swift. */
.lava-field {
  position: absolute; inset: -8%;
  filter: url(#gooLava);
}
.lava-blob {
  position: absolute;
  border-radius: 48% 52% 46% 54% / 56% 44% 58% 42%;
  will-change: transform;
}
.lava-blob.warm {
  background: radial-gradient(circle at 40% 30%,
    color-mix(in srgb, var(--accent) 46%, white) 0%,
    var(--accent) 46%,
    color-mix(in srgb, var(--accent) 66%, black) 100%);
}
.lava-blob.cool {
  background: radial-gradient(circle at 42% 32%,
    color-mix(in srgb, var(--accent-secondary) 48%, white) 0%,
    var(--accent-secondary) 48%,
    color-mix(in srgb, var(--accent-secondary) 64%, black) 100%);
}
/* Daylight: liquids stay saturated on paper — only the lit core and
   rim soften a touch (glow reads less on white; ink-dark rims read
   harsher). Vibrancy is the point of this style in BOTH modes. */
:root[data-mode="light"] .lava-blob.warm {
  background: radial-gradient(circle at 40% 30%,
    color-mix(in srgb, var(--accent) 62%, white) 0%,
    var(--accent) 50%,
    color-mix(in srgb, var(--accent) 74%, black) 100%);
}
:root[data-mode="light"] .lava-blob.cool {
  background: radial-gradient(circle at 42% 32%,
    color-mix(in srgb, var(--accent-secondary) 64%, white) 0%,
    var(--accent-secondary) 52%,
    color-mix(in srgb, var(--accent-secondary) 72%, black) 100%);
}

/* Warm liquid: rises from lower-left (video: the orange). Main mass +
   trailing companion share the goo filter, so their vertical gap
   breathing forms the tapering neck. */
.lava-blob.warm.main {
  width: 52vmax; height: 60vmax; left: -4vmax; bottom: -14vmax;
  animation: lava-rise-main 36s ease-in-out infinite,
             lava-morph-a 13s ease-in-out infinite;
}
.lava-blob.warm.companion {
  width: 24vmax; height: 27vmax; left: 12vmax; bottom: 34vmax;
  animation: lava-fall-companion 27s ease-in-out infinite,
             lava-morph-b 11s ease-in-out infinite;
}
.lava-blob.warm.sat.s1 { width: 6vmax; height: 6.6vmax; left: 30vmax; top: 24vh;
  animation: lava-bob-1 17s ease-in-out infinite, lava-morph-b 9s ease-in-out infinite; }
.lava-blob.warm.sat.s2 { width: 4vmax; height: 4.4vmax; left: 8vmax; top: 40vh;
  animation: lava-bob-2 22s ease-in-out infinite reverse, lava-morph-a 8s ease-in-out infinite; }

/* Cool liquid: hangs from upper-right (video: the magenta). */
.lava-blob.cool.main {
  width: 48vmax; height: 56vmax; right: -8vmax; top: -16vmax;
  animation: lava-sink-main 31s ease-in-out infinite,
             lava-morph-b 15s ease-in-out infinite;
}
.lava-blob.cool.companion {
  width: 20vmax; height: 24vmax; right: 6vmax; top: 36vmax;
  animation: lava-rise-companion 24s ease-in-out infinite,
             lava-morph-a 10s ease-in-out infinite reverse;
}
.lava-blob.cool.sat.s1 { width: 5vmax; height: 5.6vmax; right: 26vmax; top: 56vh;
  animation: lava-bob-1 19s ease-in-out infinite reverse, lava-morph-a 7s ease-in-out infinite; }

/* Vertical thermal travel — stretch (scaleY) peaks mid-travel where
   speed is highest, exactly like hot wax. */
@keyframes lava-rise-main {
  0%, 100% { transform: translateY(6vmax) scaleY(1); }
  45%      { transform: translateY(-9vmax) scaleY(1.26); }
  60%      { transform: translateY(-11vmax) scaleY(1.08); }
}
@keyframes lava-fall-companion {
  0%, 100% { transform: translateY(0) scaleY(1.05); }
  40%      { transform: translateY(16vmax) scaleY(1.32); }
  55%      { transform: translateY(20vmax) scaleY(1.02); }
}
@keyframes lava-sink-main {
  0%, 100% { transform: translateY(-5vmax) scaleY(1); }
  50%      { transform: translateY(8vmax) scaleY(1.22); }
  65%      { transform: translateY(10vmax) scaleY(1.06); }
}
@keyframes lava-rise-companion {
  0%, 100% { transform: translateY(4vmax) scaleY(1.04); }
  45%      { transform: translateY(-14vmax) scaleY(1.3); }
  62%      { transform: translateY(-17vmax) scaleY(1.05); }
}
@keyframes lava-bob-1 {
  0%, 100% { transform: translate(0, 0); }
  50%      { transform: translate(2vmax, -12vmax); }
}
@keyframes lava-bob-2 {
  0%, 100% { transform: translate(0, 0); }
  50%      { transform: translate(-3vmax, 10vmax); }
}
/* Wax wobble — asymmetric radii rolling, same trick as the goo. */
@keyframes lava-morph-a {
  0%, 100% { border-radius: 48% 52% 46% 54% / 56% 44% 58% 42%; }
  50%      { border-radius: 42% 58% 56% 44% / 46% 56% 42% 58%; }
}
@keyframes lava-morph-b {
  0%, 100% { border-radius: 52% 48% 56% 44% / 44% 58% 46% 54%; }
  50%      { border-radius: 58% 42% 44% 56% / 58% 42% 56% 44%; }
}


/* ── VIDEO ENGINE (spec §8, lava pilot) ──────────────────────────────
   A style's master loop plays in <video class="ambient-video"> inside
   its wrapper, fading in over the procedural recipe once frames flow
   (ambient-video.js adds .playing). Procedural stays beneath as the
   instant-on fallback; reduced-motion never starts the video, so the
   frozen procedural still is what renders. iOS parity:
   LavaVideoOverlay (AVPlayerLooper). */
.ambient-video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 1.2s ease;
  pointer-events: none;
}
.ambient-video.playing { opacity: 1; }
/* Theme/mode swap crossfade: ambient-video.js paints the outgoing frame
   onto this canvas, then dissolves it once the new stream renders — the
   swap never drops to poster/procedural. */
.ambient-video-ghost {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;   /* applies to the poster <img> bridge */
  pointer-events: none;
  transition: opacity 0.35s ease;
}
.ambient-video-ghost.fading { opacity: 0; }

/* ── Background picker: category accordions + horizontal strip ───────────
   Spec: docs/superpowers/specs/2026-07-27-background-picker-categories-design.md
   iOS parity: GystUI.BackgroundCategoryRow + BackgroundMixerPane.
   NOT gated on [data-ambient] — this is the picker chrome inside Profile,
   which must look the same whether or not the background is currently on. */
.gyst-bg-category + .gyst-bg-category { border-top: 0.5px solid var(--border); }
.gyst-bg-category-head {
  display: flex; align-items: center; gap: 8px;
  width: 100%; padding: 12px 0; background: none; border: none;
  color: var(--text); font-size: 15px; font-weight: 600;
  cursor: pointer; text-align: left;
}
.gyst-bg-category-count { font-size: 12px; font-weight: 500; color: var(--text-dim); }
.gyst-bg-category-head .gyst-row-chevron { margin-left: auto; transition: transform 0.2s; }
.gyst-bg-category.is-open .gyst-row-chevron { transform: rotate(90deg); }
.gyst-bg-category-body { padding-bottom: 12px; }

/* Horizontal strip — ~2.5 tiles visible; the partial tile IS the affordance.
   clamp() mirrors the iOS min(150, max(100, ...)) so a wide desktop viewport
   shows MORE tiles rather than three giant ones. */
.gyst-bg-strip {
  display: flex; gap: 12px;
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
  padding: 2px 2px 4px;
  scrollbar-width: none;
}
.gyst-bg-strip::-webkit-scrollbar { display: none; }
.gyst-bg-strip .gyst-bg-tile {
  flex: 0 0 clamp(100px, calc((100% - 24px) / 2.5), 150px);
  scroll-snap-align: start;
}

/* Dice tile: same 9:16 footprint as a real tile so the strip's rhythm is intact,
   but a flat themed surface instead of artwork. */
.gyst-bg-tile-dice-frame {
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-3);
}
.gyst-bg-tile-dice-glyph { font-size: 30px; line-height: 1; }
.gyst-bg-tile-dice-sub { font-size: 10px; color: var(--text-dimmer); padding-left: 2px; }
.gyst-bg-tile-dice.is-selected .gyst-bg-tile-dice-sub { color: var(--text-dim); }

/* Mixer pane: Animate at the far left behind a divider, chips scrolling right. */
.gyst-bg-mixer {
  display: flex; align-items: stretch; gap: 14px;
  margin-top: 12px; padding: 13px 16px;
  background: var(--surface-panel);
  border: 1px solid var(--border);
  border-radius: 12px;
}
.gyst-bg-mixer-motion {
  flex: 0 0 84px;
  display: flex; flex-direction: column; align-items: flex-start; gap: 4px;
}
.gyst-bg-mixer-motion.is-disabled { opacity: 0.5; }  /* = iOS Tokens.buttonDisabledOpacity */
/* Opacity does NOT compound here: .gyst-toggle.is-disabled (gyst-ui.css)
   already dims the switch itself by 0.4 on its own. If it were left as-is
   inside this 0.5-opacity container, the two would multiply to 0.2 —
   visibly more washed out than the 0.5 label right next to it (the exact
   bug hit twice on the iOS ThemedToggle). Cancel the toggle's own opacity
   here so only the container's 0.5 ever applies to it. */
.gyst-bg-mixer-motion .gyst-toggle.is-disabled { opacity: 1; }
.gyst-bg-mixer-motion-label { font-size: 11px; font-weight: 600; color: var(--text); }
.gyst-bg-mixer-motion.is-disabled .gyst-bg-mixer-motion-label { color: var(--text-dim); }
.gyst-bg-mixer-motion-reason { font-size: 9px; line-height: 1.25; color: var(--text-dimmer); }
/* Second toggle row stacked in the mixer's left slot (Favorite under Animate).
   The slot is already a flex column, so this only needs its own row layout. */
.gyst-bg-mixer-fav { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; }
/* The container's .is-disabled dims Animate for still-only styles. Favorite is
   never disabled, so it must NOT inherit that dimming — same class of
   compounding-opacity bug already fixed for the toggle itself above. */
.gyst-bg-mixer-motion.is-disabled .gyst-bg-mixer-fav { opacity: 1; }
.gyst-bg-mixer-motion.is-disabled .gyst-bg-mixer-fav .gyst-bg-mixer-motion-label { color: var(--text); }
.gyst-bg-mixer-divider { flex: 0 0 1px; background: var(--border); margin: 4px 0; }
.gyst-bg-mixer-chips {
  flex: 1 1 auto; display: flex; gap: 12px;
  overflow-x: auto; scrollbar-width: none;
  -webkit-mask-image: linear-gradient(to right, #000 0, #000 94%, transparent 100%);
          mask-image: linear-gradient(to right, #000 0, #000 94%, transparent 100%);
}
.gyst-bg-mixer-chips::-webkit-scrollbar { display: none; }
