/* =============================================================
   FOREST — scroll-driven descent + landing + floor scene.

   Ported from the Claude Design prototype (forest v4). The layout
   model is all parallax-on-a-fixed-camera:

   - .descent : tall (720vh). A fixed "camera" looks into it; canopy
     layers translate up at tier-specific speeds. Each layer's tier
     governs its speed, blur, scale, and when it fades.
   - .landing : short (180vh). The floor rises into view, the
     overhead canopy slides down to meet it, descent-trees keep
     drifting upward so there's no frozen pause.
   - .floor   : 100vh. Fixed after landing. Interactive cartoony
     trees, ground plane, grass, foreground frame trees, apple/leaf
     interactions.

   The effect is sold by PARALLAX SPEED + BLUR, not by clever art.
   ============================================================= */

:root {
  --ink: #17201a;
  --trunk: #7a4a24;
  --leaf-base: hsl(100, 36%, 42%);

  /* Atmosphere progression — written by JS on scroll. */
  --atm-top: #3a5238;
  --atm-bot: #1c2c1f;
  --dapple: 0;              /* 0 = no sun dapples, 1 = bright dapples on the floor */
  --descent-p: 0;           /* 0 at start of descent, 1 at end */
  --landing-p: 0;           /* 0 before landing, 1 after */

  /* --landing-eased is a smoothstep of --landing-p for dolly motion. */
  --landing-eased: 0;
  --canopy-density: 1;      /* 0.5 sparse → 1.8 dense */
  --fg-intensity: 1;        /* controls leaves falling + motes */
}

* { box-sizing: border-box; }
html, body {
  margin: 0;
  padding: 0;
  background: #0a1210;
  color: #e8dcc8;
  font-family: 'Source Serif 4', serif;
  overflow-x: hidden;
}
html { scroll-behavior: auto; }

/* -------------------------------------------------------------
   STAGE: wraps everything. The atmosphere gradient is a FIXED layer
   pinned to the viewport — color-interpolated as --descent-p and
   --landing-p change.
   ------------------------------------------------------------- */
.stage { position: relative; width: 100%; }

.atmosphere {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    /* soft vignette so the center of attention is preserved */
    radial-gradient(ellipse at 50% 60%, transparent 0%, rgba(0,0,0,0.35) 90%),
    /* the sky/canopy gradient */
    linear-gradient(to bottom, var(--atm-top) 0%, var(--atm-bot) 100%);
  transition: background 300ms ease;
}

/* Dappled sunlight overlay — only visible on the floor. Multiple
   radial gradients fake shafts of light filtering through leaves. */
.dapples {
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: var(--dapple);
  mix-blend-mode: screen;
  background:
    radial-gradient(ellipse 200px 120px at 22% 68%, rgba(220,240,180,0.35), transparent 70%),
    radial-gradient(ellipse 180px 100px at 68% 58%, rgba(220,240,180,0.28), transparent 70%),
    radial-gradient(ellipse 240px 140px at 84% 72%, rgba(220,240,180,0.22), transparent 70%),
    radial-gradient(ellipse 120px 80px  at 45% 82%, rgba(220,240,180,0.30), transparent 70%);
  transition: opacity 400ms ease;
}

/* -------------------------------------------------------------
   DESCENT / LANDING / FLOOR — scroll-driving sections.

   Only these three contribute to document height; everything the
   user sees is position:fixed inside .stage. The section heights
   define the duration of each phase.
   ------------------------------------------------------------- */
.descent { position: relative; height: 720vh; z-index: 2; }
.landing { position: relative; height: 180vh; z-index: 3; }
.floor   { position: relative; height: 100vh; z-index: 4; }

/* Each canopy layer is a full-width fixed strip. It starts with its
   top near the viewport top and translates UP over the descent so
   new blobs stream in from below. Each layer is 4 viewports tall so
   blobs can be distributed densely enough that the viewport is always
   covered. */
.canopy-layer {
  position: fixed;
  left: 0;
  right: 0;
  pointer-events: none;
  will-change: transform, opacity;
  height: 400vh;
  /* JS sets --layer-y in vh each frame. */
  transform: translateY(var(--layer-y, 0vh));
  /* Default: canopy fades fully as the floor arrives. */
  opacity: clamp(0, calc(1 - var(--landing-p, 0) * 1.25), 1);
  transition: opacity 400ms ease;
}

/* TIER: FAR  — distant canopy, highly blurred, small blobs, dim  */
.canopy-far    { z-index: 3; filter: blur(6px) brightness(0.6) saturate(0.65); }
/* TIER: MID  — closer canopy, mid blur, medium blobs              */
.canopy-mid    { z-index: 4; filter: blur(3px) brightness(0.8) saturate(0.8); }
/* TIER: NEAR — big dark leaf silhouettes close to viewer          */
.canopy-near   { z-index: 5; filter: blur(1.5px) brightness(0.55); }
/* TIER: FG   — extremely close blurred leaves. Rises during descent,
   gone by landing end. */
.canopy-fg     { z-index: 6; filter: blur(14px) brightness(0.3) saturate(0.7);
                 opacity: clamp(0, calc(var(--descent-p, 0) * 0.95 * (1 - var(--landing-p, 0) * 1.4)), 1); }

/* -------------------------------------------------------------
   OVERHEAD CANOPY — dedicated "branches hanging over you" layer
   for the forest floor. Starts fully above the viewport at
   translateY(-50vh). During landing it slides DOWN to -30vh so
   ~20vh peeks into the top of the viewport.

   Blobs cluster densely at the BOTTOM of the 50vh layer. That puts
   the visual mass above the viewport, with the bottom edge of the
   cluster hanging into the top ~10-15vh of screen — reads as
   foliage anchored off-screen.
   ------------------------------------------------------------- */
.canopy-overhead {
  position: fixed;
  left: 0;
  right: 0;
  top: 0;
  width: 100%;
  height: 50vh;
  z-index: 9; /* above floor-stage (8) */
  pointer-events: none;
  filter: blur(2px) brightness(0.55) saturate(0.9);
  transform: translateY(calc(-50vh + var(--landing-eased, 0) * 20vh));
  opacity: clamp(0, calc(var(--landing-p, 0) * 3), 1);
  will-change: transform, opacity;
}
.canopy-overhead .leaf-blob {
  /* Override: anchor by BOTTOM so blobs hang from a common baseline
     at the layer's lower edge — no blobs spilling below into the
     viewport. */
  top: auto;
  bottom: var(--y);
  /* Each overhead blob carries a stable baked --op (no rank-gate). */
  opacity: var(--op, 0.8);
}

/* Each canopy blob is an SVG shape at an absolute position set
   inline by JS. --rank (0..1) is a stable random per-blob value;
   the blob shows when --canopy-visible (scroll-driven) exceeds its
   rank. Lets us build ONCE at peak density and gate visibility
   smoothly via a CSS compare — no DOM rebuilds on scroll. */
.leaf-blob {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: var(--w);
  height: var(--h);
  transform: rotate(var(--r, 0deg));
  /* Soft cross-fade: each blob gets ~0.4 of a unit of --canopy-visible
     to fade in around its --rank, so the canopy thickens gradually
     instead of in discrete layers. */
  opacity: clamp(0, calc((var(--canopy-visible, 1) - var(--rank, 0)) * 2.5), 1);
}

/* Motes — tiny pollen dots drifting UP (reinforces descent feel). */
.motes-layer {
  position: fixed;
  inset: 0;
  z-index: 5;
  pointer-events: none;
  opacity: calc(var(--fg-intensity) * (1 - var(--landing-p, 0) * 0.6));
}
.mote {
  position: absolute;
  left: var(--mx);
  top: var(--my);
  width: var(--ms, 3px);
  height: var(--ms, 3px);
  border-radius: 50%;
  background: radial-gradient(circle, rgba(240,240,200,0.85) 0%, rgba(240,240,200,0) 70%);
  animation: mote-drift var(--mdur, 8s) linear infinite;
  animation-delay: var(--mdelay, 0s);
  will-change: transform, opacity;
}
@keyframes mote-drift {
  0%   { transform: translate(0, 0);                               opacity: 0; }
  15%  {                                                            opacity: 1; }
  85%  {                                                            opacity: 1; }
  100% { transform: translate(var(--mdx, 20px), -120vh);            opacity: 0; }
}

/* Falling leaves — built ONCE at max density. A per-leaf --rank
   gates visibility; --leaves-active (JS, 0..1) is compared against
   it so density ramps smoothly as you descend (no DOM rebuild, no
   pop-in). On the floor the animations are paused + the layer is
   hidden via .is-idle — removes 88 infinite animations from the
   compositor during the final phase. */
.falling-layer {
  position: fixed;
  inset: 0;
  z-index: 5;
  pointer-events: none;
}
.falling-layer.is-idle {
  display: none;
}
.falling-leaf {
  position: absolute;
  left: var(--fx);
  top: -30px;
  width: var(--fw, 16px);
  height: var(--fh, 20px);
  background: var(--leaf-color, #6a8a3a);
  border-radius: 60% 10% 60% 10%;
  /* Dropped per-leaf blur — it forced a raster on every composite.
     The visual softness wasn't worth the scroll-stutter cost. */
  filter: none;
  animation: leaf-fall var(--fdur, 12s) linear infinite;
  animation-delay: var(--fdelay, 0s);
  will-change: transform, opacity;
  /* Per-leaf visibility gate (JS-driven via --leaves-active). */
  --leaf-gate: clamp(0, calc((var(--leaves-active, 1) - var(--rank, 0)) * 3), 1);
}
@keyframes leaf-fall {
  /* Opacity fades over the LAST portion of each leaf's fall (bottom
     half of the trajectory). Each leaf fades based on where IT is in
     its own path. The --leaf-gate factor multiplies through so
     non-gated leaves simply stay invisible. */
  0%   { transform: translate(0, 0) rotate(0deg);
         opacity: calc(0.75 * var(--leaf-gate, 1)); }
  25%  { opacity: calc(0.75 * var(--leaf-gate, 1)); }
  70%  { opacity: 0; }
  100% { transform: translate(var(--fdx, 40px), 120vh) rotate(var(--frot, 400deg));
         opacity: 0; }
}

/* -------------------------------------------------------------
   LANDING: the v4 approach is "no fog band" — descent-trees stay
   visible through landing and the floor physically rises up. The
   old .landing-horizon fog gradient was removed because it created
   a foggy gap between descent-trees and floor-trees.
   ------------------------------------------------------------- */
.landing-horizon {
  display: none;
}

/* -------------------------------------------------------------
   DESCENT-TREES: fixed layer of silhouette trees BEHIND the canopy.
   Distributed vertically: sparse up high, dense near the bottom.
   Revealed as canopy layers thin out — this is what sells the
   arrival. The descent-trees keep drifting up smoothly through both
   descent and landing so they never feel frozen.
   ------------------------------------------------------------- */
.descent-trees {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  height: 100vh;
  z-index: 2; /* above atmosphere, below canopy layers */
  pointer-events: none;
  /* Vertical parallax continues through landing: descent-p 0→1 maps
     12vh → 0vh, then landing-p 0→1 continues 14vh → 0vh. Net: the
     background keeps moving up until the moment the floor lands. */
  transform: translateY(calc((1 - var(--descent-p, 0)) * 12vh + (1 - var(--landing-p, 0)) * 14vh));
  /* Visibility: fade in as the horizon reveals, stay visible
     through landing. The floor's ground covers the lower portion;
     the tops peek above the horizon as the backdrop. */
  opacity: clamp(0, calc(var(--horizon-p, 0) * 1.6), 1);
}
.descent-tree {
  position: absolute;
  left: var(--dtx);
  bottom: var(--dtb, 0);
  width: var(--dtw, 60px);
  height: var(--dth, 140px);
  filter: blur(var(--dtblur, 1px));
  opacity: var(--dtop, 0.8);
  transform: translateX(-50%);
}
.descent-tree svg { width: 100%; height: 100%; display: block; overflow: visible; }

/* -------------------------------------------------------------
   FLOOR: fixed, translates up from below during the landing phase.
   No opacity fade — the ground literally scrolls into view. Anchor
   at bottom, translateY(100vh) at landing-p=0 → translateY(0) at
   landing-p=1, eased by --landing-eased for a gentle settle.
   ------------------------------------------------------------- */
.floor-stage {
  position: fixed;
  inset: 0;
  z-index: 8;
  pointer-events: none;
  transform: translate3d(0, calc((1 - var(--landing-eased, 0)) * 100vh), 0);
  will-change: transform;
}
.floor-stage.is-visible { pointer-events: auto; }

/* Hidden in v4 — the background is now unified via .descent-trees. */
.floor-hills,
.floor-silhouettes {
  display: none;
}

/* The ground plane — where the cartoony trees are planted. A warm
   brown strip with soft inner detail + decorative grass ellipses
   along the top edge so it reads as solid ground. */
.ground {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 30%;
  background:
    /* grass-tuft noise, top edge */
    radial-gradient(ellipse 3px 10px at 3% 2%,  #4a6a32 0%, transparent 70%),
    radial-gradient(ellipse 3px  8px at 9% 1%,  #3e5a28 0%, transparent 70%),
    radial-gradient(ellipse 3px 10px at 15% 2%, #4a6a32 0%, transparent 70%),
    radial-gradient(ellipse 3px  8px at 21% 1%, #3e5a28 0%, transparent 70%),
    radial-gradient(ellipse 3px 10px at 27% 2%, #4a6a32 0%, transparent 70%),
    radial-gradient(ellipse 3px  8px at 33% 1%, #3e5a28 0%, transparent 70%),
    radial-gradient(ellipse 3px 10px at 39% 2%, #4a6a32 0%, transparent 70%),
    radial-gradient(ellipse 3px  8px at 45% 1%, #3e5a28 0%, transparent 70%),
    radial-gradient(ellipse 3px 10px at 51% 2%, #4a6a32 0%, transparent 70%),
    radial-gradient(ellipse 3px  8px at 57% 1%, #3e5a28 0%, transparent 70%),
    radial-gradient(ellipse 3px 10px at 63% 2%, #4a6a32 0%, transparent 70%),
    radial-gradient(ellipse 3px  8px at 69% 1%, #3e5a28 0%, transparent 70%),
    radial-gradient(ellipse 3px 10px at 75% 2%, #4a6a32 0%, transparent 70%),
    radial-gradient(ellipse 3px  8px at 81% 1%, #3e5a28 0%, transparent 70%),
    radial-gradient(ellipse 3px 10px at 87% 2%, #4a6a32 0%, transparent 70%),
    radial-gradient(ellipse 3px  8px at 93% 1%, #3e5a28 0%, transparent 70%),
    /* main ground gradient */
    linear-gradient(to bottom,
      #3a4a2a 0%,
      #2a3a22 14%,
      #1e2c1a 60%,
      #161f14 100%
    );
}

/* Grass tuft at the base of each interactive tree. Sits IN FRONT of
   the trunk so the grass clearly grounds the tree. Also used for
   the ambient scattered tufts (see .ground-tuft below). */
.tree-tuft {
  position: absolute;
  left: calc(var(--tx) + var(--tw, 160px) * 0.08);
  bottom: calc(var(--tbottom, 0px) - 18px);
  width: calc(var(--tw, 160px) * 0.84);
  height: 32px;
  pointer-events: none;
  /* +1 over the tree's tz so the tuft sits above THIS tree AND any
     tree behind it at the same tier. */
  z-index: calc(var(--tz, 3) + 1);
}
.tree-tuft svg { width: 100%; height: 100%; display: block; overflow: visible; }

/* Ambient grass tufts scattered across the floor (not tied to any
   tree). Smaller, tinted by distance. Always BEHIND trees so the
   trees remain the visual focus. */
.ground-tuft {
  position: absolute;
  left: var(--gx);
  bottom: var(--gy);
  width: var(--gw, 36px);
  height: calc(var(--gw, 36px) * 0.26);
  transform: translateX(-50%);
  pointer-events: none;
  filter: brightness(var(--gbright, 1)) saturate(var(--gsat, 1));
  z-index: 1;
  opacity: var(--gop, 1);
}
.ground-tuft svg { width: 100%; height: 100%; display: block; overflow: visible; }

/* ----- Grass size modes (data-grass-size on :root) -----
   "match"          (default) — tree-base grass is big, scattered is small.
   "big-scattered"  — scale scattered UP so they match tree-base size.
   "small-trees"    — scale tree-base DOWN so they match scattered size. */
:root[data-grass-size="big-scattered"] .ground-tuft {
  width: calc(var(--gw, 36px) * 3.8);
  height: calc(var(--gw, 36px) * 3.8 * 0.2);
}
:root[data-grass-size="small-trees"] .tree-tuft {
  left: calc(var(--tx) + var(--tw, 160px) * 0.18);
  width: calc(var(--tw, 160px) * 0.64);
  height: 12px;
  bottom: calc(var(--tbottom, 0px) - 6px);
}
/* Front-row (camera-closest) trees get slightly taller grass, sitting
   a bit lower, so perspective reads stronger. */
:root[data-grass-size="small-trees"] .tree-tuft[data-tz="6"] {
  height: 20px;
  bottom: calc(var(--tbottom, 0px) - 16px);
}

/* The INTERACTIVE trees live here — in front of ground, behind the
   foreground frame. */
.floor-trees {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 18%;
  height: 60%;
  pointer-events: none; /* children re-enable */
}
.floor-tree {
  position: absolute;
  left: var(--tx);
  bottom: var(--tbottom, 0px);
  width: var(--tw, 160px);
  aspect-ratio: 500 / 620;
  pointer-events: auto;
  cursor: pointer;
  z-index: var(--tz, 3);
  filter: drop-shadow(0 8px 12px rgba(0,0,0,0.4)) brightness(var(--tbright, 1)) saturate(var(--tsat, 1));
}
.floor-tree svg { width: 100%; height: 100%; display: block; overflow: visible; }
.floor-tree .g-tree-canopy {
  transform-box: view-box;
  transform-origin: 250px 380px;
  animation: canopy-sway 6s ease-in-out infinite alternate;
  animation-delay: var(--delay, 0s);
}
@keyframes canopy-sway {
  0%   { transform: rotate(-1deg); }
  100% { transform: rotate(1.5deg); }
}
.floor-tree.is-shaken .g-tree-canopy {
  animation: canopy-shake 0.7s cubic-bezier(.3,.6,.3,1);
}
@keyframes canopy-shake {
  0%,100% { transform: rotate(0deg); }
  20%     { transform: rotate(5deg); }
  40%     { transform: rotate(-4deg); }
  60%     { transform: rotate(3deg); }
  80%     { transform: rotate(-1deg); }
}
.floor-tree .g-tree-leaf {
  transform-box: fill-box;
  transform-origin: center;
  animation: leaf-drift calc(4.4s + var(--phase, 0) * 1.6s) ease-in-out infinite alternate;
  animation-delay: calc(var(--phase, 0) * -1.3s);
}
.floor-tree .g-tree-canopy .g-tree-leaf:nth-child(1) { --phase: 0; }
.floor-tree .g-tree-canopy .g-tree-leaf:nth-child(2) { --phase: 0.3; }
.floor-tree .g-tree-canopy .g-tree-leaf:nth-child(3) { --phase: 0.6; }
.floor-tree .g-tree-canopy .g-tree-leaf:nth-child(4) { --phase: 0.9; }
@keyframes leaf-drift {
  0%   { transform: rotate(-0.8deg) scale(0.998); }
  100% { transform: rotate(0.9deg) scale(1.005); }
}
.floor-tree .g-tree-leaf:hover {
  animation: leaf-rustle-hover 0.9s cubic-bezier(.3,.7,.3,1);
}
@keyframes leaf-rustle-hover {
  0%   { transform: rotate(0deg)   scale(1); }
  25%  { transform: rotate(5deg)   scale(1.04); }
  55%  { transform: rotate(-4deg)  scale(0.98); }
  80%  { transform: rotate(2deg)   scale(1.02); }
  100% { transform: rotate(0deg)   scale(1); }
}

/* Foreground frame trees — slight blur, very dark, sit over the
   interactive layer to sell "you are standing among these" feel.
   One on each edge, partially off-screen. */
.floor-foreground {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  top: 0;
  pointer-events: none;
  z-index: 10;
}
.fg-frame-tree {
  position: absolute;
  bottom: 0;
  width: var(--fgw, 340px);
  aspect-ratio: 500 / 620;
  filter: blur(3px) brightness(0.4) saturate(0.7);
}
.fg-frame-tree.left  { left: -8%; }
.fg-frame-tree.right { right: -8%; transform: scaleX(-1); }
.fg-frame-tree svg { width: 100%; height: 100%; display: block; }

/* Apple drop (click an interactive tree, it shakes + drops an apple). */
.apple {
  position: absolute;
  width: 18px;
  height: 18px;
  pointer-events: none;
  z-index: 11;
  will-change: transform;
}
.apple-body {
  width: 100%; height: 100%;
  background: radial-gradient(circle at 35% 30%, #c64838 0%, #8a2a20 60%, #5a1810 100%);
  border-radius: 50%;
  box-shadow: 0 2px 4px rgba(0,0,0,0.5);
}

/* Shed leaves on hover (hover an interactive tree, a few leaves
   gently detach and drift down). */
.shed-leaf {
  position: absolute;
  width: 10px; height: 14px;
  border-radius: 60% 10% 60% 10%;
  background: var(--sl-color, #6a8a3a);
  pointer-events: none;
  z-index: 11;
  will-change: transform, opacity;
}

/* -------------------------------------------------------------
   Scroll hint (initial) + "you're here" floor label + back link.
   ------------------------------------------------------------- */
.scroll-hint {
  position: fixed;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  z-index: 20;
  font-family: 'Caveat', cursive;
  font-size: 2rem;
  color: rgba(240, 230, 200, 0.85);
  text-align: center;
  pointer-events: none;
  transition: opacity 600ms ease;
}
.scroll-hint.is-hidden { opacity: 0; }
.scroll-hint small { display: block; font-size: 1rem; margin-top: 0.5rem; opacity: 0.7; letter-spacing: 0.04em; }

.floor-label {
  position: fixed;
  left: 50%;
  top: 12%;
  transform: translate(-50%, 0);
  z-index: 20;
  font-family: 'Caveat', cursive;
  font-size: 2.2rem;
  color: rgba(240, 230, 200, 0.9);
  text-align: center;
  pointer-events: none;
  opacity: calc(var(--landing-p, 0) * 0.9);
  transition: opacity 400ms ease;
  text-shadow: 0 2px 12px rgba(0,0,0,0.6);
}
.floor-label small {
  display: block;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.75rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: rgba(240, 230, 200, 0.5);
  margin-top: 0.4rem;
}

/* Back-to-projects link — fixed to the corner, subtle. Present from
   the start so the viewer can always leave. */
.forest-back {
  position: fixed;
  left: 1.25rem;
  top: 1.25rem;
  z-index: 25;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(240, 230, 200, 0.55);
  text-decoration: none;
  padding: 0.35rem 0.55rem;
  border-radius: 6px;
  transition: color 160ms ease, background 160ms ease;
}
.forest-back:hover {
  color: rgba(240, 230, 200, 0.9);
  background: rgba(18, 24, 20, 0.55);
}

/* -------------------------------------------------------------
   Tweaks panel — hidden by default, shown when the parent host
   toggles edit-mode (via postMessage). On the public page it stays
   invisible; internal dev/tuning turns it on.
   ------------------------------------------------------------- */
.tweaks {
  position: fixed;
  right: 1.25rem;
  bottom: 1.25rem;
  z-index: 100;
  width: 260px;
  padding: 1rem 1.1rem 1rem;
  background: rgba(18, 24, 20, 0.92);
  border: 1px solid rgba(240, 230, 200, 0.15);
  border-radius: 12px;
  backdrop-filter: blur(12px);
  color: #e8dcc8;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.78rem;
  display: none;
}
.tweaks.is-on { display: block; }
.tweaks h4 {
  margin: 0 0 0.8rem;
  font-family: 'Source Serif 4', serif;
  font-weight: 600;
  font-size: 0.95rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  opacity: 0.7;
}
.tweaks label {
  display: block;
  margin: 0.7rem 0 0.2rem;
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: 0.65;
}
.tweaks input[type="range"] {
  width: 100%;
  accent-color: #9ab868;
}
.tweaks .val { opacity: 0.5; font-size: 0.7rem; margin-left: 0.3rem; }

/* Grass-style + grass-size segmented picker */
.grass-picker {
  display: flex;
  gap: 4px;
  margin-top: 0.3rem;
}
.gs-btn {
  flex: 1;
  padding: 0.45rem 0.3rem;
  background: rgba(240, 230, 200, 0.06);
  border: 1px solid rgba(240, 230, 200, 0.15);
  color: #e8dcc8;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.7rem;
  cursor: pointer;
  border-radius: 4px;
  transition: background 160ms ease, border-color 160ms ease;
}
.gs-btn:hover { background: rgba(240, 230, 200, 0.12); }
.gs-btn.is-active {
  background: rgba(154, 184, 104, 0.25);
  border-color: rgba(154, 184, 104, 0.7);
  color: #e8f0cc;
}

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