:root {
  /* Forest palette */
  --forest-ink:    #14302A;
  --forest-deep:   #234A3E;
  --forest-mid:    #3A6B5A;
  --forest-soft:   #7FA396;
  --forest-mist:   #C8D8D0;

  /* Paper / surfaces */
  --paper:         #F4F1EA;
  --paper-card:    #FBF9F4;
  --paper-pure:    #FFFFFF;

  /* Earth + accent */
  --bark:          #6B5240;
  --ember:         #B8552E;
  --ember-soft:    #D87B4F;

  /* Text */
  --ink:           #1B2A24;
  --ink-soft:      #4D6359;
  --ink-faint:     #5C7066;

  /* Text-only variants of --forest-soft/--ember: those two tokens are also
     used as borders/backgrounds/icons (which only need 3:1 contrast), so
     they stay at their original, lighter values for that — these darker
     siblings exist for the handful of spots using --forest-soft/--ember as
     small (<18px) text color, which needs 4.5:1. Verified against every
     paper tone this app uses (--paper #F4F1EA, --paper-card #FBF9F4,
     --paper-pure #FFFFFF): --ink-faint 4.70–5.30:1, --forest-soft-text
     5.03:1, --ember-text 5.61–6.32:1 — all pass WCAG AA. (--ink-faint was
     text-only already, so it's darkened in place rather than split.) */
  --forest-soft-text: #4C6E5F;
  --ember-text:    #9A481F;

  /* Safety / observed states (contraindication warnings, outcome indicators) */
  --safety-ink:    #8F3F1F;
  --safety-bg:     #FBEDE6;
  --observed-ink:  #2B5346;

  --r-sm: 6px;
  --r-md: 10px;
  --r-lg: 14px;
  --r-pill: 999px;

  --shadow-sm: 0 1px 2px rgba(20,48,42,0.04);
  --shadow-md: 0 4px 16px rgba(20,48,42,0.06);
  --shadow-lg: 0 12px 32px rgba(20,48,42,0.10);

  /* Spacing scale (px-based rem so it still respects user font-size prefs) */
  --space-1: 0.25rem;   /* 4px */
  --space-2: 0.5rem;    /* 8px */
  --space-3: 0.75rem;   /* 12px */
  --space-4: 1rem;      /* 16px */
  --space-5: 1.5rem;    /* 24px */
  --space-6: 2rem;      /* 32px */
  --space-7: 2.5rem;    /* 40px */
  --space-8: 3rem;      /* 48px */

  /* Breakpoints (documented here; CSS custom properties can't drive
     @media conditions directly, so @media rules still hardcode these
     same values — keep the two in sync if either changes):
       --bp-sm: 640px   (compact detail/table layouts)
       --bp-md: 768px   (header scroll hide/show)
       --bp-lg: 1100px  (Session Builder becomes a mobile sheet + FAB) */

  /* Fluid type scale: replaces the fixed px sizes that used to need a
     manual per-breakpoint override to shrink on narrow viewports. */
  --fs-h1:    clamp(1.65rem, 1.5rem + 0.8vw, 2rem);        /* ~26.4px → 32px, section headings */
  --fs-h3:    clamp(0.9rem, 0.87rem + 0.15vw, 0.9375rem);  /* ~14.4px → 15px, card/prose subheads */
  --fs-body:  clamp(0.8125rem, 0.79rem + 0.1vw, 0.875rem); /* 13px → 14px, running prose */
  --fs-sub:   clamp(0.8125rem, 0.79rem + 0.15vw, 0.90625rem); /* 13px → 14.5px, section intros */

  /* Idle-motion grammar (Walk the Forest). One shared beat and its
     harmonics, so every ambient loop in the scene shares one rhythm
     instead of each inventing its own duration — the scene previously had
     20 distinct durations across 4 easing families for its ~22 idle
     loops. --wf-beat is 6.5s because that was already the scene's most
     common individual value (wf-bob/wf-lift/wf-peg all used it
     independently before this token existed) — harmonics are simple
     ratios of it so a frame-energy autocorrelation resolves to one
     dominant period, not scattered incommensurate peaks. Variety between
     loops comes from animation-delay (phase), never from picking a
     different duration off this scale — see styles-walk-forest.css. */
  --wf-beat:      6.5s;
  --wf-beat-050:  3.25s;
  --wf-beat-100:  6.5s;
  --wf-beat-150:  9.75s;
  --wf-beat-200:  13s;
  --wf-beat-300:  19.5s;
  --wf-beat-600:  39s;
  --wf-beat-1200: 78s;
  /* One deliberate off-ratio rung: the far cloud layer (.wf-drift2) needs
     to move slower than the near one (.wf-drift, --wf-beat-1200) as a
     parallax depth cue, not to match it — see styles-walk-forest.css. */
  --wf-beat-1400: 91s;

  /* Two easing cases, not one, because they're physically different
     motions: an oscillating loop (sway/bob/glow/breath) needs zero
     velocity at both ends of its swing so the wrap is invisible; a
     one-way travel loop (a mote or a wisp of smoke drifting off in one
     direction) needs constant velocity, and hides its own wrap with
     opacity ramps at both ends instead (see wfMote/wfSmoke). Applying
     --ease-idle to a travel loop would make it visibly decelerate into
     nothing rather than drift at a steady pace. */
  --ease-idle: cubic-bezier(.37, 0, .63, 1);
}
