/* =============================================================
   Breathe. Balance. Become. — Visual System
   Soft, minimal, Apple-calm with a wellness undertone.
   ============================================================= */

:root {
  /* Palette — high-contrast night: deep charcoal + luminous sage + warm highlights */
  --bg:           #0E0E0C;     /* near-black with a hint of warm */
  --bg-tint:      #161614;
  --surface:      #1A1A17;     /* card surface */
  --surface-2:    #1F1F1C;     /* alt card surface */
  --border:       rgba(255, 248, 230, 0.07);
  --border-strong:rgba(255, 248, 230, 0.14);

  --ink:          #F5F1E6;     /* primary text — warm white */
  --ink-soft:     #B8B3A4;     /* secondary text */
  --ink-faint:    #7A766B;     /* tertiary */

  --sage:         #8FC0A9;     /* primary accent — luminous sage */
  --sage-deep:    #B6D9C4;     /* brighter sage for highlights on dark */
  --sage-soft:    rgba(143, 192, 169, 0.14);

  --dusk:         #A5B8D0;     /* secondary — moonlit sky */
  --dusk-soft:    rgba(165, 184, 208, 0.14);

  --honey:        #E8B97A;     /* warm accent — candlelight */
  --honey-soft:   rgba(232, 185, 122, 0.14);

  --rose:         #E0A39E;
  --rose-soft:    rgba(224, 163, 158, 0.14);

  /* Type */
  --font-display: 'Zodiak', 'Cormorant Garamond', Georgia, serif;
  --font-body:    'Satoshi', 'Inter', system-ui, -apple-system, sans-serif;

  /* Sizes */
  --text-xs:   12px;
  --text-sm:   14px;
  --text-base: 16px;
  --text-lg:   18px;
  --text-xl:   22px;
  --text-2xl:  30px;
  --text-3xl:  44px;
  --text-hero: clamp(48px, 8vw, 96px);

  /* Spacing */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;
  --space-8: 64px;
  --space-9: 96px;

  /* Motion */
  --transition-interactive: 220ms cubic-bezier(0.16, 1, 0.3, 1);
  --ease-out:  cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in:   cubic-bezier(0.4, 0, 1, 1);
  --ease-io:   cubic-bezier(0.4, 0, 0.2, 1);

  /* Shadow — on dark surfaces, shadows recede; we lean on borders + subtle inner glow */
  --shadow-1: 0 1px 0 rgba(255, 248, 230, 0.03) inset, 0 8px 24px rgba(0, 0, 0, 0.35);
  --shadow-2: 0 1px 0 rgba(255, 248, 230, 0.05) inset, 0 16px 48px rgba(0, 0, 0, 0.45);

  --radius-sm: 10px;
  --radius:    18px;
  --radius-lg: 28px;
  --radius-pill: 999px;

  /* Layout */
  /* Container scales with the viewport up to a wide-but-still-readable max.
     On a ~1440 laptop this fills the screen nicely; on ultra-wide displays
     we cap so line-lengths inside cards don't get uncomfortable. */
  --container: min(1600px, calc(100vw - 2 * var(--gutter)));
  --gutter: clamp(20px, 3vw, 56px);
}

/* ---------- Reset / Base ---------- */
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
html { scroll-behavior: smooth; }

/* ---------- Responsive scaling for small / dense / large-text viewports ----------
   The whole page is sized in px tokens, but font-sizes use `var(--text-*)` and
   spacing uses `var(--space-*)`. To shrink everything together when the
   viewport is narrow OR the user has bumped their system text size, we tone
   down the type-scale tokens at three breakpoints. Smaller screens get tighter
   spacing too. This is a global re-scale, not a font-size override on the
   root element — that would interact poorly with iOS text-zoom and zoomed
   browsers (which we want to respect at their literal pixel sizes). */
@media (max-width: 720px) {
  :root {
    --text-xs:   11px;
    --text-sm:   13px;
    --text-base: 15px;
    --text-lg:   16px;
    --text-xl:   19px;
    --text-2xl:  24px;
    --text-3xl:  34px;
    --text-hero: clamp(36px, 9vw, 64px);
    --space-5: 20px;
    --space-6: 26px;
    --space-7: 38px;
    --space-8: 52px;
    --space-9: 76px;
    --gutter: clamp(14px, 4vw, 28px);
  }
}
@media (max-width: 420px) {
  :root {
    --text-xs:   10.5px;
    --text-sm:   12px;
    --text-base: 14px;
    --text-lg:   15px;
    --text-xl:   17px;
    --text-2xl:  21px;
    --text-3xl:  28px;
    --text-hero: clamp(30px, 11vw, 48px);
    --space-4: 14px;
    --space-5: 18px;
    --space-6: 22px;
    --space-7: 32px;
    --gutter: clamp(12px, 4vw, 22px);
  }
}
@media (max-width: 340px) {
  /* Vintage / kiosk-sized screens or very large-text users on small phones. */
  :root {
    --text-base: 13px;
    --text-lg:   14px;
    --text-xl:   16px;
    --text-2xl:  19px;
    --text-3xl:  24px;
    --text-hero: clamp(26px, 12vw, 40px);
    --gutter: 10px;
  }
}
/* Prevent horizontal scrollbars from the photo card's 100vw full-bleed trick.
   `overflow-x: clip` is preferred over `hidden` because it doesn't establish
   a new scroll container (so position: sticky inside still works). */
html, body { overflow-x: clip; }

body {
  background: var(--bg);
  /* very subtle ambient glow — candle-in-a-quiet-room feeling */
  background-image:
    radial-gradient(ellipse 80% 60% at 85% -10%, rgba(232, 185, 122, 0.10), transparent 60%),
    radial-gradient(ellipse 70% 50% at 5% 110%, rgba(143, 192, 169, 0.12), transparent 60%);
  background-attachment: fixed;
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: 1.6;
  font-feature-settings: "ss01", "ss02";
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  min-height: 100vh;
}

img { max-width: 100%; display: block; }
button { font: inherit; color: inherit; background: transparent; border: 0; cursor: pointer; padding: 0; }

.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

.skip {
  position: absolute; left: -9999px; top: 0; padding: 8px 16px;
  background: var(--ink); color: var(--bg); border-radius: var(--radius-sm);
  font-weight: 500;
}
.skip:focus { left: 16px; top: 16px; z-index: 999; }

:focus-visible {
  outline: 2px solid var(--sage);
  outline-offset: 3px;
  border-radius: 6px;
}

.micro {
  font-size: var(--text-xs);
  color: var(--ink-faint);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* ---------- Header ---------- */
/* Single-row by design: brand (left) + meta (right). We `flex-wrap: nowrap`
   and give each side `min-width: 0` so flex children can shrink instead of
   pushing the row to two lines on narrow phones. Brand text truncates with
   ellipsis; the meta cluster hides the tagline + divider on very small
   viewports so the date alone remains. */
.site-header {
  max-width: var(--container);
  margin: 0 auto;
  padding: var(--space-6) var(--gutter) var(--space-4);
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  text-decoration: none;
  color: var(--ink);
  min-width: 0;            /* allow shrink inside the flex row */
  flex: 0 1 auto;
}
.brand-mark {
  width: 28px; height: 28px;
  color: var(--sage);
  flex: 0 0 auto;          /* keep the dot at full size; only text shrinks */
}
.brand-word {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  letter-spacing: 0.005em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.header-meta {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
  flex: 0 1 auto;
}
.header-meta .tagline,
.header-meta .divider {
  white-space: nowrap;
}
.today-label {
  font-size: var(--text-sm);
  color: var(--ink-soft);
  letter-spacing: 0.02em;
  white-space: nowrap;
}
.brand-mark { color: var(--sage); }

/* Mid breakpoint: shrink the brand text and hide the tagline+divider so the
   header stays one row even on iPhone-mini-class phones. The date is the
   functional element — keep that. */
@media (max-width: 640px) {
  .brand-word { font-size: var(--text-base); }
  .today-label { font-size: var(--text-xs); }
  .header-meta .tagline,
  .header-meta .divider { display: none; }
  .site-header { gap: var(--space-3); }
}
/* Narrowest phones (iPhone SE class, sub-360px): drop the brand-word.
   The dot mark is the brand; the date stays for context. iPhone mini
   (375px) and up keep the full brand word. */
@media (max-width: 359px) {
  .brand-word { display: none; }
  .today-label { font-size: var(--text-xs); }
}

/* ---------- Hero region (hero + quote, sized to one viewport) ---------- */
.hero-region {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  /* Fill exactly one viewport. No header anymore, so the hero owns the
     full screen until the user scrolls. svh handles iOS browser-chrome
     better than vh. dvh on supporting browsers tracks dynamic toolbars. */
  min-height: 100svh;
  min-height: 100dvh;
  height: 100svh;
  height: 100dvh;
  padding-bottom: var(--space-4);
  position: relative;
  isolation: isolate;
}

/* Hero gradient backdrop. The actual gradient layers come from
   --hero-grad set per palette below. Subtle by design — text stays the
   focus; always fades to page bg near the bottom for a clean handoff. */
.hero-region::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: var(--hero-grad, none),
              linear-gradient(180deg, transparent 72%, var(--bg) 100%);
}

/* ---- Palettes (data-palette on <html>) ---- */
/* Default — Cool Coast (ocean blue + teal surf). */
:root, :root[data-palette="cool-coast"] {
  --hero-grad:
    radial-gradient(ellipse 110% 55% at 50% 108%,
      rgba(120, 178, 178, 0.16) 0%, rgba(120, 178, 178, 0.06) 45%, transparent 78%),
    radial-gradient(ellipse 80% 30% at 50% 60%,
      rgba(143, 192, 169, 0.07) 0%, rgba(143, 192, 169, 0.02) 50%, transparent 80%),
    linear-gradient(180deg,
      rgba(140, 175, 215, 0.16) 0%, rgba(165, 184, 208, 0.07) 35%, transparent 65%);
}

:root[data-palette="beachy"] {
  --hero-grad:
    radial-gradient(ellipse 80% 35% at 50% 58%,
      rgba(224, 163, 158, 0.08) 0%, rgba(224, 163, 158, 0.03) 45%, transparent 75%),
    radial-gradient(ellipse 110% 55% at 50% 105%,
      rgba(232, 185, 122, 0.14) 0%, rgba(232, 185, 122, 0.05) 45%, transparent 75%),
    linear-gradient(180deg,
      rgba(165, 184, 208, 0.10) 0%, rgba(165, 184, 208, 0.04) 35%, transparent 65%);
}

:root[data-palette="sage-forest"] {
  --hero-grad:
    radial-gradient(ellipse 70% 55% at 22% 8%,
      rgba(143, 192, 169, 0.12) 0%, rgba(143, 192, 169, 0.05) 40%, transparent 70%),
    radial-gradient(ellipse 65% 50% at 82% 92%,
      rgba(165, 184, 208, 0.08) 0%, rgba(165, 184, 208, 0.03) 45%, transparent 75%),
    linear-gradient(180deg,
      rgba(255, 248, 230, 0.012) 0%, transparent 60%);
}

:root[data-palette="golden-hour"] {
  --hero-grad:
    radial-gradient(ellipse 95% 60% at 50% 105%,
      rgba(232, 185, 122, 0.20) 0%, rgba(232, 185, 122, 0.07) 45%, transparent 78%),
    radial-gradient(ellipse 70% 35% at 50% 50%,
      rgba(224, 163, 158, 0.12) 0%, rgba(224, 163, 158, 0.04) 50%, transparent 80%),
    linear-gradient(180deg,
      rgba(180, 120, 90, 0.08) 0%, rgba(180, 120, 90, 0.03) 40%, transparent 70%);
}

:root[data-palette="midnight"] {
  --hero-grad:
    radial-gradient(ellipse 90% 60% at 50% -5%,
      rgba(63, 74, 107, 0.28) 0%, rgba(63, 74, 107, 0.10) 45%, transparent 75%),
    radial-gradient(ellipse 60% 40% at 50% 95%,
      rgba(143, 192, 169, 0.07) 0%, rgba(143, 192, 169, 0.02) 50%, transparent 80%);
}

:root[data-palette="none"] {
  --hero-grad: none;
}

@media (prefers-reduced-motion: no-preference) {
  .hero-region::before { opacity: 0; animation: hero-bg-fade 1200ms var(--ease-out) 200ms forwards; }
}
/* When the palette swaps live (from the admin), don't replay the fade-in —
   just crossfade in place. */
:root[data-palette-swapping] .hero-region::before { animation: none; opacity: 1; transition: background 400ms var(--ease-out); }
@keyframes hero-bg-fade { to { opacity: 1; } }

/* ---------- Hero ---------- */
.hero {
  max-width: var(--container);
  /* Vertically center the hero cluster within .hero-region: paired with the
     existing `margin-top: auto` on .scroll-hint, this splits leftover space
     evenly above and below the content instead of dumping it all at the
     bottom. Keeps the "slide" feeling on desktop without the dead zone. */
  margin: auto auto 0;
  padding: clamp(20px, 4vh, 48px) var(--gutter) clamp(12px, 2.5vh, 28px);
  text-align: center;
  width: 100%;
  box-sizing: border-box;
}

.eyebrow {
  font-size: var(--text-xs);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--sage);
  margin: 0 0 var(--space-4);
  font-weight: 500;
  opacity: 0;
  animation: fade-soft 800ms var(--ease-out) 0ms forwards;
}

.hero-title {
  font-family: var(--font-display);
  font-weight: 400;
  /* Tighter clamp so the title doesn't dominate at small viewport heights. */
  font-size: clamp(40px, min(7vw, 8vh), 88px);
  line-height: 1.02;
  letter-spacing: -0.02em;
  margin: 0;
  color: var(--ink);
  /* Never let the title overflow the viewport, even during the 1.28x intro
     scale. On narrow phones the words wrap onto multiple lines instead of
     bleeding off the edges. JS in scheduleHeroIntro() additionally caps
     --hero-intro-scale so the scaled width never exceeds the screen. */
  max-width: 100%;
  overflow-wrap: break-word;
}
.hero-title .word {
  display: inline-block;
  margin: 0 0.08em;
  opacity: 0;
  transform: translateY(10px);
  filter: blur(4px);
  animation: word-rise 1400ms var(--ease-out) forwards;
}
/* Each word ~700ms apart — a clearly perceived but unhurried rhythm */
/* JS sets inline `animation-delay` on each .word in applyHeroCopy, so the
   stagger scales to any number of chunks. Color is driven entirely by the
   inline-formatting syntax (e.g. *Balance.*). */

@keyframes word-rise {
  to { opacity: 1; transform: translateY(0); filter: blur(0); }
}

/* ---------- Inline hero text formatting (Title / Eyebrow / Tagline) ----------
   Markdown-ish syntax in admin fields renders into these classes:
     **x** -> <strong>          *x* -> .hero-fx-accent
     _x_   -> <em>               ~x~ -> .hero-fx-underline
   Scoped to hero areas so site-wide <strong>/<em> stay untouched. */
.hero-title .hero-fx-accent,
.hero-tagline .hero-fx-accent,
.eyebrow .hero-fx-accent {
  color: var(--sage);
}
.hero-title strong,
.hero-tagline strong,
.eyebrow strong {
  font-weight: 700;
}
.hero-title em,
.hero-tagline em,
.eyebrow em {
  font-style: italic;
}
.hero-title .hero-fx-underline,
.hero-tagline .hero-fx-underline,
.eyebrow .hero-fx-underline {
  text-decoration: underline;
  text-decoration-thickness: 0.06em;
  text-underline-offset: 0.18em;
}

/* Admin live preview under each formatted hero field. */
.hero-fx-preview-wrap {
  margin-top: 6px;
  padding: 8px 10px;
  border: 1px dashed rgba(255,255,255,0.12);
  border-radius: 6px;
  background: rgba(255,255,255,0.02);
}
.hero-fx-preview-label {
  display: block;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: 0.55;
  margin-bottom: 4px;
}
.hero-fx-preview {
  font-size: 15px;
  line-height: 1.4;
}
.hero-fx-preview .hero-fx-accent { color: var(--sage); }
.hero-fx-preview .hero-fx-underline {
  text-decoration: underline;
  text-decoration-thickness: 0.06em;
  text-underline-offset: 0.18em;
}
/* Visualize animation steps (chunks) in the Title preview with subtle
   pill-style separators so the user can see where the animation breaks. */
.hero-fx-preview .hero-fx-chunk {
  display: inline-block;
  padding: 0 6px;
  margin: 0 1px;
  border: 1px dashed rgba(143, 192, 169, 0.25);
  border-radius: 4px;
}
.hero-fx-chunk-note {
  margin-top: 4px;
  font-size: 11px;
  opacity: 0.6;
}

/* Hero copy syntax guide \u2014 admin panel collapsible cheat sheet. */
.hero-syntax-guide {
  margin: 8px 0 14px;
  padding: 8px 12px;
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: 8px;
  background: rgba(255,255,255,0.02);
}
.hero-syntax-guide > summary {
  cursor: pointer;
  font-weight: 600;
  padding: 4px 0;
  list-style: revert;
}
.hero-syntax-guide[open] > summary { margin-bottom: 8px; }
.hero-syntax-guide p { margin: 6px 0; font-size: 13px; }
.hero-syntax-grid {
  display: grid;
  grid-template-columns: minmax(140px, max-content) auto minmax(120px, max-content) 1fr;
  gap: 6px 12px;
  align-items: center;
  margin: 6px 0;
  font-size: 13px;
}
.hero-syntax-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  padding: 2px 6px;
  background: rgba(255,255,255,0.06);
  border-radius: 4px;
  white-space: nowrap;
}
.hero-syntax-arrow { opacity: 0.5; }
.hero-syntax-preview {
  font-size: 14px;
  line-height: 1.3;
}
.hero-syntax-preview .hero-fx-accent { color: var(--sage); }
.hero-syntax-preview .hero-fx-underline {
  text-decoration: underline;
  text-decoration-thickness: 0.06em;
  text-underline-offset: 0.18em;
}
.hero-syntax-desc { font-size: 12px; }
/* On narrow screens the 4-column grid wraps to two lines per row. */
@media (max-width: 640px) {
  .hero-syntax-grid { grid-template-columns: 1fr; gap: 4px; }
  .hero-syntax-arrow { display: none; }
}

@keyframes fade-soft {
  to { opacity: 1; }
}

.hero-tagline {
  font-family: var(--font-display);
  font-size: clamp(17px, 2.2vh, 22px);
  font-style: italic;
  color: var(--sage);
  letter-spacing: 0.005em;
  margin: clamp(12px, 2.4vh, 28px) 0 0;
  opacity: 0;
  animation: fade-soft 900ms var(--ease-out) 2800ms forwards;
}

.hero-sub {
  max-width: 540px;
  margin: clamp(8px, 1.6vh, 18px) auto 0;
  color: var(--ink-soft);
  font-size: clamp(14.5px, 1.9vh, 18px);
  line-height: 1.5;
  opacity: 0;
  animation: fade-soft 900ms var(--ease-out) 3100ms forwards;
}

/* ---------- Hero intro: title centers large, then slides up ---------- */
/* On first paint, the title pretends to live at the vertical center of the
   hero-region, scaled up and dramatic. Everything else in the hero stays
   hidden. After the three words finish reveal + a 500ms pause, JS removes
   `data-hero-intro` from <html>, the title transitions back to its natural
   spot, and the other elements fade in on a staggered schedule. */
/* The title stays in document flow so its natural position remains correct
   after intro. During intro, we translate it by --hero-intro-offset (set in
   JS on each load + resize) so its visual center lands at viewport center.
   On exit, the offset goes to 0 and CSS transitions it smoothly back. */
:root .hero-title {
  transform: translateY(var(--hero-intro-offset, 0px)) scale(var(--hero-intro-scale, 1));
  transform-origin: 50% 50%;
  transition: transform 900ms var(--ease-out);
  will-change: transform;
}
:root[data-hero-intro] {
  --hero-intro-scale: 1.28;
}
:root:not([data-hero-intro]) {
  --hero-intro-offset: 0px;
  --hero-intro-scale: 1;
}
/* Hide every other hero-region element during intro. Uses !important so it
   beats the existing fade-soft animation rules on these elements. */
:root[data-hero-intro] .eyebrow,
:root[data-hero-intro] .hero-tagline,
:root[data-hero-intro] .hero-sub,
:root[data-hero-intro] .hero-quote,
:root[data-hero-intro] .scroll-hint {
  opacity: 0 !important;
  animation: none !important;
  transition: none;
}
/* When intro ends, fade these in on a staggered schedule. The selector uses
   `:not([data-hero-intro])` to engage only after JS removes the attribute. */
/* `both` keeps element invisible during delay AND retains opacity:1 after. */
:root:not([data-hero-intro]) .eyebrow {
  animation: fade-soft 600ms var(--ease-out) 200ms both;
}
:root:not([data-hero-intro]) .hero-tagline {
  animation: fade-soft 700ms var(--ease-out) 450ms both;
}
:root:not([data-hero-intro]) .hero-sub {
  animation: fade-soft 800ms var(--ease-out) 650ms both;
}
:root:not([data-hero-intro]) .hero-quote {
  animation: fade-soft 900ms var(--ease-out) 900ms both;
}
:root:not([data-hero-intro]) .scroll-hint {
  /* Two animations: fade-in (post-intro stagger) + ambient float (existing). */
  animation: fade-soft 700ms var(--ease-out) 1100ms both,
             scroll-hint-float 2.4s ease-in-out 1500ms infinite;
}

/* Intentionally NOT suppressing hero word reveal under prefers-reduced-motion.
   Many desktop users leave OS-level Reduce Motion on by default, which made the
   whole site feel dead. The reveal is gentle (opacity + small translate, ~700ms)
   and runs once on load. Users who truly need motion fully disabled can use the
   admin toggle that sets :root[data-no-hero-anim]. */

/* Replay state — suspends hero animations so JS can retrigger them on
   every page load (CSS animations otherwise run only once). */
.hero.hero--replay .eyebrow,
.hero.hero--replay .hero-title .word,
.hero.hero--replay .hero-tagline,
.hero.hero--replay .hero-sub {
  animation: none !important;
  opacity: 0;
}
.hero.hero--replay .hero-title .word {
  transform: translateY(10px);
  filter: blur(4px);
}
.hero.hero--replay ~ .hero-quote,
body:has(.hero.hero--replay) .hero-quote {
  animation: none !important;
  opacity: 0;
}

/* ---------- Hero Quote (featured below hero) ---------- */
.hero-quote {
  max-width: 760px;
  margin: 0 auto;
  padding: 0 var(--gutter);
  text-align: center;
  opacity: 0;
  animation: fade-soft 900ms var(--ease-out) 3400ms forwards;
  width: 100%;
  box-sizing: border-box;
}
.hero-quote-kicker {
  display: inline-block;
  font-size: var(--text-xs);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-faint);
  font-weight: 500;
  margin-bottom: clamp(10px, 1.6vh, 22px);
}
.hero-quote-block {
  margin: 0;
  position: relative;
}
.hero-quote-text {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(20px, min(3.6vw, 4.2vh), 42px);
  line-height: 1.22;
  letter-spacing: -0.012em;
  color: var(--ink);
  margin: 0 0 clamp(8px, 1.4vh, 18px);
  text-wrap: balance;
}
/* Flanking curly quote marks. Inline pseudos keep them aligned with the
   first/last line of the text even when it wraps. */
.hero-quote-text::before,
.hero-quote-text::after {
  font-family: var(--font-display);
  color: var(--sage);
  opacity: 0.6;
  font-size: 2.2em;
  line-height: 0;
  position: relative;
  top: 0.28em;
}
.hero-quote-text::before {
  content: "\201C"; /* “ */
  margin-right: 0.12em;
}
.hero-quote-text::after {
  content: "\201D"; /* ” */
  margin-left: 0.08em;
}
.hero-quote-attr {
  font-size: var(--text-sm);
  letter-spacing: 0.04em;
  color: var(--ink-soft);
  font-style: normal;
}
/* Hero quote fade kept under prefers-reduced-motion — it's a single 900ms
   opacity transition with no movement. Admin can disable via data-no-hero-anim. */

/* ---------- Scroll-down hint ---------- */
.scroll-hint {
  margin-top: auto;            /* pushes to bottom of .hero-region */
  padding: 16px 0 6px;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  align-self: center;
  gap: 4px;
  color: var(--ink-soft);
  text-decoration: none;
  opacity: 0;
  /* Show immediately — don't wait for the hero word reveal. */
  animation: fade-soft 700ms var(--ease-out) 0ms forwards,
             scroll-hint-float 2.4s ease-in-out 400ms infinite;
  transition: color 200ms var(--ease-out), transform 200ms var(--ease-out);
  -webkit-tap-highlight-color: transparent;
}
.scroll-hint:hover,
.scroll-hint:focus-visible {
  color: var(--sage);
}
.scroll-hint:focus-visible {
  outline: none;
}
.scroll-hint:focus-visible .scroll-hint-chevron {
  outline: 2px solid var(--sage);
  outline-offset: 4px;
  border-radius: 50%;
}
.scroll-hint-label {
  font-size: 11px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  font-weight: 500;
  opacity: 0.65;
}
.scroll-hint-chevron {
  display: block;
  color: currentColor;
}
@keyframes scroll-hint-float {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(4px); }
}
/* Scroll-hint float kept under prefers-reduced-motion — it's a 4px ambient bob
   that signals interactivity. The fade-in alone wasn't enough for the chevron to
   read as a clickable affordance. */

/* Admin toggles. data-no-hero-anim disables the per-word reveal entirely —
   everything is fully visible immediately with no staggered delay. data-no-hint-float
   removes the ambient bob on the chevron but keeps the fade-in. */
:root[data-no-hero-anim] .eyebrow,
:root[data-no-hero-anim] .hero-title .word,
:root[data-no-hero-anim] .hero-tagline,
:root[data-no-hero-anim] .hero-sub,
:root[data-no-hero-anim] .hero-quote,
:root[data-no-hero-anim] .hero-quote-kicker,
:root[data-no-hero-anim] .hero-quote-text,
:root[data-no-hero-anim] .hero-quote-attr,
:root[data-no-hero-anim] .hero-quote-block::before {
  animation: none !important;
  opacity: 1 !important;
  transform: none !important;
  filter: none !important;
}
:root[data-no-hint-float] .scroll-hint {
  animation: fade-soft 700ms var(--ease-out) 0ms forwards;
}
.hero.hero--replay ~ .scroll-hint,
body:has(.hero.hero--replay) .scroll-hint {
  animation: none !important;
  opacity: 0;
}

/* Header meta cluster */
.header-meta {
  display: inline-flex; align-items: center; gap: var(--space-3);
  font-size: var(--text-sm);
  color: var(--ink-soft);
}
.tagline {
  font-family: var(--font-display);
  font-style: italic;
  color: var(--sage);
  letter-spacing: 0.005em;
}
.divider { color: var(--ink-faint); }
@media (max-width: 640px) {
  .tagline, .divider { display: none; }
}

/* ---------- Grid ---------- */
/*
 * Default layout uses flexbox so visible cards always center in the viewport,
 * even when only a few sections are enabled. Each card sizes between
 * --card-min and --card-max; wrapped rows (including a short last row) center
 * horizontally. The wider 6-col bento layout is opt-in via `.grid--bento`
 * (set by app.js when enough cards are enabled to fill it cleanly).
 */
.grid {
  --card-min: 320px;
  --card-max: 520px;
  max-width: var(--container);
  margin: 0 auto;
  padding: var(--space-5) var(--gutter) var(--space-8);
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: stretch;
  gap: var(--space-5);
}
.grid > .card {
  flex: 1 1 var(--card-min);
  min-width: min(100%, var(--card-min));
  max-width: var(--card-max);
}

/* On wide viewports, let flex cards stretch further so we don't end up with
   a narrow centered column flanked by empty backdrop. The bento grid below
   takes over at >=1080px when enough sections are enabled; this rule fills
   the gap for non-bento layouts (3-card weeks, etc). */
@media (min-width: 1080px) {
  .grid:not(.grid--bento) {
    --card-max: 640px;
  }
}
@media (min-width: 1400px) {
  .grid:not(.grid--bento) {
    --card-max: 760px;
  }
}

/* Mobile: single full-width column */
@media (max-width: 719px) {
  .grid {
    --card-min: 0;
    --card-max: none;
  }
  .grid > .card { flex-basis: 100%; max-width: none; }
}

/* Desktop bento — only when the grid is `.grid--bento` (set by app when
 * enough sections are enabled to fill the 6-col layout cleanly). */
@media (min-width: 1080px) {
  .grid--bento {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
  }
  .grid--bento > .card {
    flex: none;
    max-width: none;
    min-width: 0;
  }
  .grid--bento .card--breath     { grid-column: span 3; grid-row: span 2; }
  .grid--bento .card--permission { grid-column: span 3; }
  /* Photo of the day is the wide hero band of the grid \u2014 always full width
     (6 cols) and panoramic so the landscape photo can breathe. Pinned to
     the top of the section list in admin so it always sits in the first row. */
  .grid--bento .card--sound      { grid-column: span 2; }
  .grid--bento .card--news       { grid-column: span 2; }
  .grid--bento .card--win        { grid-column: span 2; }
  .grid--bento .card--teaser     { grid-column: span 2; }
  .grid--bento .card--animal     { grid-column: span 2; }
}

/* Photo of the day \u2014 standalone full-bleed band sitting above the grid.
   The card lives OUTSIDE `.grid` in markup so the flex container's gutters
   and max-width don't constrain it. On mobile (default) it renders like any
   other card. On desktop (\u2265 1080px) it spans the full viewport edge-to-edge.
   `overflow-x: clip` on html/body prevents this from causing a horizontal
   scrollbar even if the page has a vertical scrollbar. */
.card--photo.card--photo-bleed {
  /* Mobile default: render inside the main container with normal card chrome.
     Match the container's gutter so it lines up with sibling cards. */
  margin: 0 var(--gutter) var(--space-5);
}
@media (min-width: 1080px) {
  /* `.card--photo.card--photo-bleed` doubles the specificity so we beat the
     base `.card--photo { padding: var(--space-4) }` rule declared later in
     the file. Without this, the photo frame inherits 16px of inset padding
     and the photo no longer hits the viewport edge. */
  .card--photo.card--photo-bleed {
    /* Pull out to viewport edges using the proven 100vw + negative-margin
       trick. The element is a block in normal flow (not a flex/grid item),
       so width: 100vw is honored without flex wrapping issues. */
    width: 100vw;
    margin-left: calc(50% - 50vw);
    margin-right: calc(50% - 50vw);
    margin-bottom: var(--space-5);
    /* True edge-to-edge: drop chrome that would interrupt the band. */
    border-radius: 0;
    border-left: 0;
    border-right: 0;
    padding: 0;
    max-width: none;
  }
  .card--photo.card--photo-bleed .photo-frame {
    border-radius: 0;
    border-left: 0;
    border-right: 0;
    margin: 0;
  }
  /* Hide the kicker on the edge-to-edge band \u2014 the photo itself sets the
     visual hierarchy; the small "Peaceful Photo" label looked awkward
     floating above a viewport-wide image. The caption inside the frame is
     still shown. */
  .card--photo.card--photo-bleed .card-head { display: none; }
}

/* ---------- Card base ---------- */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  position: relative;
  overflow: hidden;
  box-shadow: var(--shadow-1);
  transition: transform var(--transition-interactive),
              box-shadow var(--transition-interactive);
  animation: card-enter 700ms var(--ease-out) both;
}

@keyframes card-enter {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

.card:nth-child(1)  { animation-delay: 80ms; }
.card:nth-child(2)  { animation-delay: 140ms; }
.card:nth-child(3)  { animation-delay: 200ms; }
.card:nth-child(4)  { animation-delay: 260ms; }
.card:nth-child(5)  { animation-delay: 320ms; }
.card:nth-child(6)  { animation-delay: 380ms; }
.card:nth-child(7)  { animation-delay: 440ms; }
.card:nth-child(8)  { animation-delay: 500ms; }
.card:nth-child(9)  { animation-delay: 560ms; }
.card:nth-child(10) { animation-delay: 620ms; }

.card-head {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.card-kicker {
  font-size: var(--text-xs);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-faint);
  font-weight: 500;
}
.card h2 {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--text-xl);
  line-height: 1.2;
  letter-spacing: -0.01em;
  margin: 0;
  color: var(--ink);
}
.card-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-3); margin-top: auto;
}

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex; align-items: center; gap: var(--space-2);
  padding: 10px 18px;
  border-radius: var(--radius-pill);
  font-size: var(--text-sm);
  font-weight: 500;
  letter-spacing: 0.01em;
  transition: background var(--transition-interactive),
              color var(--transition-interactive),
              transform var(--transition-interactive),
              box-shadow var(--transition-interactive);
}
.btn--ghost {
  background: transparent;
  color: var(--ink);
  border: 1px solid var(--border-strong);
}
.btn--ghost:hover {
  background: var(--sage-soft);
  border-color: var(--sage);
  color: var(--sage);
}
.btn--ghost:active { transform: translateY(1px); }

/* ---------- Breath card ---------- */
.card--breath {
  background:
    radial-gradient(ellipse at 50% 100%, rgba(143, 192, 169, 0.18) 0%, transparent 60%),
    var(--surface);
  /* Smoothly transition into/out of fullscreen focus mode. The fixed-position
     class swap is instant but the visual properties cross-fade. */
  transition: background 320ms var(--ease-out);
}

/* ---------- Breath: Focus mode (fullscreen) ----------
   Activated by `body.is-breath-focus`. The breath card becomes a fixed,
   viewport-filling overlay; the rest of the page is hidden behind it. The
   orb is centered and enlarged for distraction-free breathing. */

/* Exit (X) button \u2014 hidden by default; only visible while in focus mode.
   `display: none` until `body.is-breath-focus` flips it to `display: grid`. */
.breath-focus-exit {
  position: absolute;
  top: 18px;
  right: 18px;
  width: 44px;
  height: 44px;
  display: none;
  place-items: center;
  border-radius: 50%;
  border: 1px solid rgba(255, 248, 230, 0.12);
  background: rgba(20, 20, 18, 0.55);
  color: var(--ink-muted);
  cursor: pointer;
  z-index: 2;
  transition: color 200ms var(--ease-out), border-color 200ms var(--ease-out),
              background 200ms var(--ease-out), transform 200ms var(--ease-out);
}
.breath-focus-exit:hover {
  color: var(--ink);
  border-color: rgba(255, 248, 230, 0.22);
  background: rgba(30, 30, 28, 0.75);
}
.breath-focus-exit:focus-visible {
  outline: 2px solid var(--sage);
  outline-offset: 2px;
}

body.is-breath-focus {
  /* Lock background scroll while focused. */
  overflow: hidden;
}
body.is-breath-focus .card--breath {
  position: fixed;
  inset: 0;
  z-index: 100;
  /* Override the base `.card { max-width: 440px }` / `flex: 1 1 320px` rules
     that constrain card width inside the grid. With position: fixed we want
     to fill the whole viewport. */
  width: 100vw;
  max-width: none;
  height: 100vh;
  height: 100dvh; /* Use dvh on mobile so the viewport ignores URL bar. */
  flex: none;
  /* Full-viewport solid backdrop so nothing bleeds through. The soft sage
     glow from the bottom still reads as ambient lighting. */
  background:
    radial-gradient(ellipse at 50% 100%, rgba(143, 192, 169, 0.22) 0%, transparent 65%),
    radial-gradient(ellipse at 50% 50%, rgba(20, 22, 20, 0.96), rgba(10, 10, 9, 0.99)),
    var(--bg);
  border-radius: 0;
  border: 0;
  padding: clamp(24px, 5vh, 56px) clamp(20px, 5vw, 80px);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  /* Smooth fade-in for the overlay. */
  animation: breathFocusFadeIn 320ms var(--ease-out);
}
@keyframes breathFocusFadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* In focus mode the orb gets dramatically larger \u2014 the orb-size variable is
   bumped, and the wider scale range makes inhale/exhale feel more embodied. */
body.is-breath-focus .breath-orb {
  --orb-size: min(60vmin, 460px);
  --orb-min: 0.62;
  --orb-max: 1.18;
}
/* The instruction label inside the orb ("Breathe in", "Hold", "Breathe out")
   needs to be readable from across the room \u2014 scale it up substantially in
   focus mode. The countdown number below stays slightly smaller so the
   instruction reads as the primary cue. */
body.is-breath-focus .breath-label {
  font-size: clamp(22px, 3.4vmin, 36px);
  font-weight: 600;
  letter-spacing: 0.02em;
}
body.is-breath-focus .breath-count {
  font-size: clamp(28px, 5vmin, 56px);
}
body.is-breath-focus .breath-stage {
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  max-width: 900px;
}
/* Center the title + subtitle above the orb with more breathing room. */
body.is-breath-focus .card--breath .card-head {
  text-align: center;
  max-width: 720px;
  margin: 0 auto;
}
body.is-breath-focus .card--breath .card-kicker {
  font-size: 11.5px;
  letter-spacing: 0.18em;
  opacity: 0.7;
}
body.is-breath-focus .card--breath #breath-title {
  font-size: clamp(28px, 3.6vw, 40px);
  margin-top: 8px;
}
body.is-breath-focus .breath-sub {
  font-size: var(--text-sm);
  max-width: 520px;
  margin: 10px auto 0;
}
body.is-breath-focus .breath-patterns {
  justify-content: center;
  margin-top: var(--space-3);
}
body.is-breath-focus .card-foot--breath {
  max-width: 520px;
  margin: 0 auto;
  gap: 12px;
}
/* Reveal the exit button only in focus mode. */
body.is-breath-focus .breath-focus-exit { display: grid; }

/* Session progress bar \u2014 spans the bottom of the modal in focus mode.
   Hidden outside focus mode. The fill width is driven by JS via a CSS
   custom property `--breath-progress` (0\u20131). */
.breath-progress { display: none; }
body.is-breath-focus .breath-progress {
  display: block;
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 10px;
  background: rgba(255, 248, 230, 0.08);
  overflow: hidden;
  z-index: 1;
}
.breath-progress-fill {
  height: 100%;
  width: calc(var(--breath-progress, 0) * 100%);
  background: linear-gradient(
    90deg,
    rgba(143, 192, 169, 0.85),
    rgba(182, 217, 196, 1) 50%,
    rgba(143, 192, 169, 0.85)
  );
  box-shadow: 0 0 12px rgba(143, 192, 169, 0.5);
  /* Smooth, linear fill that matches the 1s tick cadence. */
  transition: width 1000ms linear;
}
/* When the session is reset (Stop pressed mid-session), snap the fill back
   to 0 instantly rather than animating back. */
.breath-progress-fill.is-resetting {
  transition: none;
}

/* Hide everything outside the breath card while focused. Sibling cards in
   the grid, the photo band, hero, footer, etc. \u2014 all hidden so only the
   breath overlay is visible. */
body.is-breath-focus .site-header,
body.is-breath-focus #main > *:not(:has(.card--breath)),
body.is-breath-focus .grid > .card:not(.card--breath),
body.is-breath-focus .card--photo-bleed,
body.is-breath-focus .site-footer {
  visibility: hidden;
}

.breath-stage {
  flex: 1;
  display: grid; place-items: center;
  min-height: 240px;
  padding: var(--space-4) 0;
}
/* Slightly more breathing room around the orb on wider viewports so the
   bloom/scale animation reads clearly. */
@media (min-width: 720px) {
  .breath-stage { min-height: 300px; padding: var(--space-5) 0; }
}

/* Center the Start/Stop button below the orb. The session-length micro text
   sits centered just under it. */
.card-foot--breath {
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  text-align: center;
}
.card-foot--breath .micro {
  opacity: 0.8;
}
/* Hide the Stop button until the exercise has started. We key off the
   button's own aria-pressed state which JS already toggles. The button is
   visible by default (when aria-pressed="false" — it reads “Start”). When
   aria-pressed="true" it reads “Stop”. */
/* No-op — the Start state is always visible; Stop only appears after start.
   Implemented via JS by setting .hidden until activated. */
.breath-sub {
  margin: 4px 0 0 0;
  font-size: var(--text-xs);
  color: var(--ink-muted);
  line-height: 1.45;
}
.breath-patterns {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: var(--space-3);
}
.breath-pattern {
  appearance: none;
  -webkit-appearance: none;
  border: 1px solid rgba(143, 192, 169, 0.28);
  background: rgba(143, 192, 169, 0.06);
  color: var(--ink-muted);
  font: inherit;
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.04em;
  padding: 5px 11px;
  border-radius: 999px;
  cursor: pointer;
  transition: background 200ms var(--ease-out), color 200ms var(--ease-out), border-color 200ms var(--ease-out);
}
.breath-pattern:hover {
  color: var(--ink);
  border-color: rgba(143, 192, 169, 0.55);
}
.breath-pattern[aria-checked="true"] {
  background: rgba(143, 192, 169, 0.22);
  color: var(--sage);
  border-color: rgba(143, 192, 169, 0.7);
}
.breath-pattern:focus-visible {
  outline: 2px solid var(--sage);
  outline-offset: 2px;
}

.breath-orb {
  --orb-min: 0.78;
  --orb-max: 1.32;
  --orb-scale: var(--orb-min);
  --orb-duration: 4000ms;
  --orb-ease: cubic-bezier(0.4, 0, 0.2, 1);
  --orb-size: 180px;
  width: var(--orb-size); height: var(--orb-size);
  border-radius: 50%;
  background:
    radial-gradient(circle at 35% 30%, rgba(255,255,255,0.4), transparent 55%),
    radial-gradient(circle at 50% 50%, #B6D9C4, #5C8A75 78%);
  box-shadow:
    0 0 80px rgba(143, 192, 169, 0.4),
    0 30px 60px -20px rgba(0, 0, 0, 0.5),
    inset 0 -10px 30px rgba(0,0,0,0.15);
  display: grid; place-items: center;
  color: #0E0E0C;
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: 0.04em;
  transform: scale(var(--orb-scale));
  transition: transform var(--orb-duration) var(--orb-ease), box-shadow var(--orb-duration) var(--orb-ease);
  position: relative;
  cursor: pointer;
}
.breath-orb.is-active {
  box-shadow:
    0 0 calc(80px + (var(--orb-scale) - var(--orb-min)) * 180px) rgba(143, 192, 169, 0.55),
    0 30px 60px -20px rgba(0, 0, 0, 0.55),
    inset 0 -10px 30px rgba(0,0,0,0.15);
}
.breath-orb::before,
.breath-orb::after {
  content: '';
  position: absolute;
  inset: -2px;
  border-radius: 50%;
  border: 1px solid rgba(182, 217, 196, 0.55);
  opacity: 0;
  pointer-events: none;
  transform: scale(1);
  transition: transform var(--orb-duration) var(--orb-ease), opacity var(--orb-duration) var(--orb-ease);
}
.breath-orb.is-inhaling::before {
  opacity: 0.55;
  transform: scale(1.18);
  transition: transform var(--orb-duration) var(--orb-ease), opacity var(--orb-duration) var(--orb-ease);
}
.breath-orb.is-inhaling::after {
  opacity: 0;
  transform: scale(1.35);
}
.breath-orb.is-exhaling::before {
  opacity: 0;
  transform: scale(1);
}
.breath-orb.is-exhaling::after {
  opacity: 0.4;
  transform: scale(1.25);
  transition: transform var(--orb-duration) var(--orb-ease), opacity var(--orb-duration) var(--orb-ease);
}
/* Orb content stack: phase label on top, countdown number below. Both share
   the same translucent text shadow so they read against the green gradient. */
.breath-orb-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  pointer-events: none;
}
.breath-label {
  text-align: center;
  padding: 0 var(--space-3);
  text-shadow: 0 1px 2px rgba(255, 255, 255, 0.25);
  transition: opacity 300ms var(--ease-out);
  font-size: var(--text-sm);
  font-weight: 600;
}
.breath-count {
  font-family: var(--font-display);
  font-variant-numeric: tabular-nums;
  font-size: 32px;
  line-height: 1;
  font-weight: 500;
  color: #0E0E0C;
  text-shadow: 0 1px 2px rgba(255, 255, 255, 0.25);
  opacity: 0;
  /* Slow, soft fade so each tick feels like a gentle breath rather than a
     hard cut. Active state eases in; the per-tick `is-swapping` class dips
     opacity briefly while the number changes. */
  transition: opacity 520ms var(--ease-out), transform 520ms var(--ease-out);
  transform: translateY(0);
  will-change: opacity, transform;
}
.breath-orb.is-active .breath-count { opacity: 0.9; }
.breath-orb.is-active .breath-count.is-swapping {
  opacity: 0.18;
  transform: translateY(2px);
}
@media (min-width: 720px) {
  .breath-count { font-size: 40px; }
}
/* Counter-scale the inner content so the phase label + countdown don't grow
   along with the orb — they stay readable while the orb itself breathes. */
.breath-orb-inner {
  transform: scale(calc(1 / var(--orb-scale)));
  transition: transform var(--orb-duration) var(--orb-ease);
}
/* Larger orb on wider viewports so the scale animation is clearly visible
   even when the breath card occupies its full bento span. */
@media (min-width: 720px) {
  .breath-orb { --orb-size: 220px; }
}
@media (min-width: 1080px) {
  .breath-orb { --orb-size: 260px; }
}

/* Breath orb expand/contract kept under prefers-reduced-motion — the orb IS the
   breath exercise. Removing its motion defeats the feature. The motion is slow
   (4-8s per phase), predictable, and user-initiated. */


/* ---------- Permission Slip ---------- */
.card--permission {
  background:
    repeating-linear-gradient(
      to bottom,
      transparent 0,
      transparent 34px,
      rgba(165, 184, 208, 0.18) 34px,
      rgba(165, 184, 208, 0.18) 35px
    ),
    var(--surface);
  position: relative;
}
.card--permission::before {
  content: '';
  position: absolute;
  left: var(--space-5); top: 0; bottom: 0;
  width: 1px;
  background: var(--rose);
  opacity: 0.7;
}
.permission-text {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--text-xl);
  line-height: 1.35;
  color: var(--ink);
  margin: var(--space-3) 0 var(--space-4) var(--space-5);
  padding-left: var(--space-2);
}

/* ---------- Photo card ---------- */
.photo-frame {
  position: relative;
  margin: 0;
  border-radius: var(--radius);
  overflow: hidden;
  aspect-ratio: 4 / 3;
  background:
    radial-gradient(ellipse at 30% 25%, rgba(143, 192, 169, 0.18), transparent 55%),
    linear-gradient(155deg, #1F2A26 0%, #1B232E 50%, #2A2218 100%);
  border: 1px solid var(--border);
}
.photo-frame::after {
  /* gentle pulse while loading */
  content: '';
  position: absolute; inset: 0;
  background: linear-gradient(110deg,
    transparent 30%,
    rgba(255, 248, 230, 0.06) 50%,
    transparent 70%);
  background-size: 200% 100%;
  animation: shimmer 2.2s var(--ease-io) infinite;
  pointer-events: none;
}
.photo-frame:has(img.is-loaded)::after { display: none; }
@keyframes shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -100% 0; }
}
.photo-frame img {
  width: 100%; height: 100%; object-fit: cover;
  opacity: 0;
  transition: opacity 600ms var(--ease-out);
}
.photo-frame img.is-loaded { opacity: 1; }
.photo-cap {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: var(--space-5) var(--space-4) var(--space-3);
  font-size: var(--text-xs);
  color: rgba(245, 241, 230, 0.92);
  background: linear-gradient(to top, rgba(0,0,0,0.7), transparent);
  letter-spacing: 0.04em;
}
.card--photo { padding: var(--space-4); }
.card--photo .photo-frame { aspect-ratio: 16 / 11; }
/* When the photo is a full-width band on desktop, use a wide panoramic ratio
   so the landscape image dominates without becoming a tall wall of pixels. */
@media (min-width: 1080px) {
  .card--photo .photo-frame { aspect-ratio: 24 / 9; }
}
.photo-frame--small { aspect-ratio: 1 / 1; }

/* ---------- Sound card (modern calm) ---------- */
.card--sound {
  position: relative;
  overflow: hidden;
  background:
    radial-gradient(120% 80% at 100% 0%, rgba(143, 192, 169, 0.10) 0%, transparent 60%),
    radial-gradient(100% 80% at 0% 100%, rgba(165, 184, 208, 0.10) 0%, transparent 60%),
    linear-gradient(180deg, var(--surface) 0%, var(--surface) 100%);
}
/* Soft aurora that slowly drifts behind the player while playing. */
.card--sound::before {
  content: "";
  position: absolute; inset: -30%;
  background:
    radial-gradient(40% 40% at 30% 35%, rgba(143, 192, 169, 0.16) 0%, transparent 70%),
    radial-gradient(40% 40% at 70% 65%, rgba(165, 184, 208, 0.16) 0%, transparent 70%);
  filter: blur(20px);
  opacity: 0;
  transition: opacity 800ms var(--ease-out);
  pointer-events: none;
  z-index: 0;
}
.card--sound.is-playing::before {
  opacity: 1;
  animation: sound-aurora 18s var(--ease-io) infinite alternate;
}
@keyframes sound-aurora {
  0%   { transform: translate3d(-2%, -1%, 0) rotate(0deg); }
  100% { transform: translate3d(2%, 1%, 0) rotate(6deg); }
}
.card--sound > * { position: relative; z-index: 1; }

.sound-stage {
  display: flex; align-items: center; gap: var(--space-4);
}
.sound-play {
  position: relative;
  width: 64px; height: 64px;
  border-radius: 50%;
  /* Match the dropdown + volume pills: dark ink overlay, sage edge, soft
     inner highlight. The play glyph reads bright sage against the dark fill. */
  background:
    linear-gradient(180deg, rgba(15, 24, 30, 0.55) 0%, rgba(15, 24, 30, 0.42) 100%),
    var(--bg-tint);
  border: 1px solid rgba(143, 192, 169, 0.45);
  color: #c9e3d6;
  display: grid; place-items: center;
  transition: transform var(--transition-interactive),
              box-shadow var(--transition-interactive),
              background var(--transition-interactive),
              border-color var(--transition-interactive),
              color var(--transition-interactive);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.08) inset,
    0 10px 22px -10px rgba(0, 0, 0, 0.55);
  flex-shrink: 0;
  cursor: pointer;
}
/* Soft conic ring that fades in only while playing. */
.sound-play-ring {
  position: absolute; inset: -4px;
  border-radius: 50%;
  background: conic-gradient(
    from 210deg,
    rgba(143, 192, 169, 0.55),
    rgba(165, 184, 208, 0.45),
    rgba(143, 192, 169, 0.55)
  );
  -webkit-mask: radial-gradient(circle, transparent 60%, #000 62%);
          mask: radial-gradient(circle, transparent 60%, #000 62%);
  opacity: 0;
  transition: opacity 400ms var(--ease-out);
  animation: sound-ring-spin 12s linear infinite;
  pointer-events: none;
}
.sound-play-pulse {
  position: absolute; inset: 0;
  border-radius: 50%;
  border: 1px solid rgba(143, 192, 169, 0.5);
  opacity: 0;
  pointer-events: none;
}
.sound-play[aria-pressed="true"] .sound-play-ring { opacity: 0.9; }
.sound-play[aria-pressed="true"] .sound-play-pulse {
  animation: sound-play-pulse 3.2s var(--ease-io) infinite;
}
@keyframes sound-ring-spin { to { transform: rotate(360deg); } }
@keyframes sound-play-pulse {
  0%   { transform: scale(1);    opacity: 0.55; }
  100% { transform: scale(1.55); opacity: 0; }
}
.sound-play:hover {
  transform: translateY(-1px);
  background:
    linear-gradient(180deg, rgba(15, 24, 30, 0.62) 0%, rgba(20, 38, 30, 0.50) 100%),
    var(--sage-soft);
  border-color: rgba(143, 192, 169, 0.7);
  color: #e6f3ec;
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.10) inset,
    0 14px 28px -10px rgba(0, 0, 0, 0.6);
}
.sound-play:active { transform: translateY(0); }
.sound-play:focus-visible {
  outline: none;
  box-shadow:
    0 0 0 3px rgba(143, 192, 169, 0.30),
    0 10px 22px -10px rgba(0, 0, 0, 0.55);
}
.sound-play .icon-play,
.sound-play .icon-pause { width: 24px; height: 24px; transition: opacity 200ms var(--ease-out); }
.sound-play .icon-play  { margin-left: 2px; }
.sound-play .icon-pause { display: none; }
.sound-play[aria-pressed="true"] .icon-play  { display: none; }
.sound-play[aria-pressed="true"] .icon-pause { display: block; }

.sound-meta { min-width: 0; }
.sound-name {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  margin: 0 0 2px;
  letter-spacing: -0.005em;
}
.sound-desc {
  margin: 0;
  color: var(--ink-soft);
  font-size: var(--text-sm);
  line-height: 1.45;
}

/* Imagine… cue — dark, high-contrast panel with a soft sage edge. */
.sound-imagine {
  margin: var(--space-3) 0 0;
  padding: var(--space-3) var(--space-4);
  border-radius: 14px;
  background:
    linear-gradient(180deg, rgba(15, 24, 30, 0.42) 0%, rgba(15, 24, 30, 0.30) 100%),
    var(--bg-tint);
  border: 1px solid rgba(143, 192, 169, 0.28);
  display: flex; flex-direction: column; gap: 4px;
  position: relative;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.06) inset;
  transition: background var(--transition-interactive), border-color var(--transition-interactive);
}
.sound-imagine-eyebrow {
  font-family: var(--font-display);
  font-size: var(--text-xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #c9e3d6;
  font-weight: 600;
}
.sound-imagine-text {
  font-family: var(--font-body);
  font-size: var(--text-sm);
  line-height: 1.55;
  color: rgba(245, 248, 246, 0.92);
  font-style: italic;
}
.card--sound.is-playing .sound-imagine {
  background:
    linear-gradient(180deg, rgba(15, 24, 30, 0.52) 0%, rgba(20, 38, 30, 0.42) 100%),
    var(--sage-soft);
  border-color: rgba(143, 192, 169, 0.55);
}

/* Sound chooser dropdown removed — the day's sound is now picked by the
   server and not user-selectable per session. */

/* Waveform — each bar runs on its own randomized clock via inline CSS vars
   (--bar-dur, --bar-delay, --bar-peak) set in app.js. */
/* Siri-inspired audio wave: two layered sine paths, JS-animated. */
.sound-wave {
  display: block;
  width: 100%;
  height: 44px;
  margin-top: var(--space-3);
  overflow: visible;
  opacity: 0.45;
  transition: opacity 320ms var(--ease-out);
}
.card--sound.is-playing .sound-wave { opacity: 1; }
.sound-wave-path {
  transition: stroke-width 240ms var(--ease-out);
  will-change: d;
}
.sound-wave-path--back { opacity: 0.55; }

/* ---------- News card ---------- */
.card--news {
  background: linear-gradient(180deg, rgba(232, 185, 122, 0.10) 0%, var(--surface) 70%);
}
.news-headline {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--ink);
  margin: 0;
}
.news-blurb {
  font-size: var(--text-sm);
  color: var(--ink-soft);
  margin: 0;
}

/* ---------- Win card ---------- */
.win-text {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  line-height: 1.3;
  color: var(--ink);
  margin: 0;
}
.win-check {
  display: inline-flex; align-items: center; gap: var(--space-3);
  cursor: pointer; user-select: none;
  font-size: var(--text-sm); color: var(--ink-soft);
  margin-top: auto;
}
.win-check input { position: absolute; opacity: 0; pointer-events: none; }
.win-check-box {
  width: 22px; height: 22px; border-radius: 50%;
  border: 1.5px solid var(--border-strong);
  display: grid; place-items: center;
  transition: all var(--transition-interactive);
}
.win-check-box::after {
  content: ''; width: 8px; height: 14px;
  border-right: 2px solid #0E0E0C;
  border-bottom: 2px solid #0E0E0C;
  transform: rotate(45deg) translate(-1px, -2px) scale(0);
  transition: transform 200ms var(--ease-out);
  margin-top: -2px;
}
.win-check input:checked + .win-check-box {
  background: var(--sage);
  border-color: var(--sage);
}
.win-check input:checked + .win-check-box::after {
  transform: rotate(45deg) translate(-1px, -2px) scale(1);
}
.win-check input:checked ~ .win-check-label { color: var(--sage); }

/* ---------- Teaser ---------- */
.teaser-q {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  line-height: 1.35;
  color: var(--ink);
  margin: 0;
}
.teaser-reveal {
  margin-top: auto;
  font-size: var(--text-sm);
}
.teaser-reveal summary {
  cursor: pointer;
  color: var(--sage);
  list-style: none;
  padding: 8px 0;
  font-weight: 500;
  letter-spacing: 0.01em;
  display: inline-flex; align-items: center; gap: 6px;
  transition: color var(--transition-interactive);
}
.teaser-reveal summary::-webkit-details-marker { display: none; }
.teaser-reveal summary::before {
  content: '+';
  display: inline-grid; place-items: center;
  width: 18px; height: 18px;
  border-radius: 50%;
  background: var(--sage-soft);
  color: var(--sage);
  font-size: 14px; line-height: 1;
  transition: transform var(--transition-interactive);
}
.teaser-reveal[open] summary::before { content: '−'; }
.teaser-a {
  margin: var(--space-2) 0 0;
  padding: var(--space-3);
  background: var(--bg-tint);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  color: var(--ink);
}

/* ---------- Closing ---------- */
/* The footer is the calm coda after the day's cards. The closing line is
   the only "voice" element; everything else is metadata, collapsed into a
   single quiet inline row. The admin gear floats in the bottom-right corner
   so it's discoverable but doesn't clutter the centered axis. */
.closing {
  position: relative;
  max-width: var(--container);
  margin: 0 auto;
  padding: var(--space-8) var(--gutter) var(--space-8);
  text-align: center;
}
.closing-line {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  line-height: 1.4;
  color: var(--ink-soft);
  margin: 0 auto var(--space-4);
  max-width: 560px;
}

/* Inline meta row: "Refreshed daily at sunrise \u00b7 Updated 4:53 PM \u00b7 History".
   Flex with wrap so it gracefully stacks on narrow viewports. */
.footer-meta {
  display: inline-flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  column-gap: 8px;
  row-gap: 4px;
  margin: 0;
  opacity: 0.65;
}
.footer-meta > * { display: inline-flex; align-items: center; }
.footer-meta-sep {
  opacity: 0.5;
  user-select: none;
}
/* When [hidden] is set on a flex child it must stay hidden even though the
   parent overrides display. */
.footer-meta [hidden] { display: none; }

/* The "Updated" stamp inherits the meta row's quiet styling; no extra rules
   needed. The legacy `.footer-updated` class is kept for the JS hook. */
.footer-updated {
  letter-spacing: 0.05em;
}

/* ---------- News card (clickable) ---------- */
.news-body {
  appearance: none;
  -webkit-appearance: none;
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  color: inherit;
  font: inherit;
  text-align: left;
  width: 100%;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  border-radius: var(--radius-md);
  transition: opacity var(--transition-interactive),
              transform var(--transition-interactive);
}
.news-body:hover { opacity: 0.92; }
.news-body:focus-visible {
  outline: 2px solid var(--sage);
  outline-offset: 4px;
}
.news-body:disabled { cursor: default; opacity: 1; }
.news-more {
  font-family: var(--font-mono, var(--font-display));
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--sage);
  margin-top: auto;
  padding-top: var(--space-2);
}

/* ---------- News modal ---------- */
.news-modal {
  border: 0;
  padding: 0;
  background: transparent;
  color: var(--ink-warm);
  max-width: min(720px, 92vw);
  width: 92vw;
  max-height: 88vh;
  border-radius: var(--radius-lg);
}
.news-modal::backdrop {
  background: rgba(8, 8, 7, 0.78);
  backdrop-filter: blur(6px);
}
.news-modal-inner {
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  padding: var(--space-6) var(--space-6) var(--space-5);
  max-height: 88vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.news-modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.news-modal-close {
  appearance: none;
  background: none;
  border: 0;
  color: var(--ink-soft);
  font-size: 1.75rem;
  line-height: 1;
  cursor: pointer;
  padding: 0 var(--space-2);
  transition: color var(--transition-interactive);
}
.news-modal-close:hover { color: var(--ink-warm); }
.news-modal-title {
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  line-height: 1.15;
  margin: 0;
  color: var(--ink-warm);
}
.news-modal-meta {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-soft);
  margin: 0;
}
.news-modal-img {
  width: 100%;
  height: auto;
  max-height: 320px;
  object-fit: cover;
  border-radius: var(--radius-md);
  margin: var(--space-2) 0;
}
.news-modal-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  font-size: var(--text-base);
  line-height: 1.65;
  color: var(--ink-warm);
}
.news-modal-body p { margin: 0; }
.news-modal-foot {
  border-top: 1px solid var(--border-soft);
  padding-top: var(--space-3);
  margin-top: var(--space-2);
}
.news-modal-source {
  font-family: var(--font-display);
  color: var(--sage);
  text-decoration: none;
  font-size: var(--text-sm);
  transition: color var(--transition-interactive);
}
.news-modal-source:hover { color: var(--ink-warm); }

/* ---------- Footer link (History) ---------- */
/* Looks identical to the surrounding .micro spans \u2014 the only visual cue
   that it's interactive is the hover state. Keeping it inline-matched lets
   the meta row read as one quiet sentence with separators. */
.footer-link {
  appearance: none;
  background: none;
  border: 0;
  font: inherit;
  /* Inherit color/size/letter-spacing from the parent .micro.footer-meta so
     the button blends with the surrounding text. */
  color: inherit;
  cursor: pointer;
  padding: 0;
  border-radius: 4px;
  text-decoration: none;
  transition: color 160ms ease, opacity 160ms ease;
}
.footer-link:hover,
.footer-link:focus-visible {
  color: var(--ink-soft);
  outline: none;
  text-decoration: underline;
  text-decoration-color: rgba(255,255,255,0.25);
  text-underline-offset: 3px;
}
.footer-link:focus-visible {
  outline: 2px solid var(--sage);
  outline-offset: 3px;
}

/* ---------- History modal ---------- */
.history-modal {
  border: 0;
  padding: 0;
  background: transparent;
  color: var(--ink-warm);
  max-width: min(720px, 92vw);
  width: 92vw;
  max-height: 88vh;
  border-radius: var(--radius-lg);
}
.history-modal::backdrop {
  background: rgba(8, 8, 7, 0.78);
  backdrop-filter: blur(6px);
}
.history-modal-inner {
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  padding: var(--space-6) var(--space-6) var(--space-5);
  max-height: 88vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.history-modal-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
}
.history-modal-head .card-kicker {
  display: block;
  margin-bottom: 4px;
}
.history-modal-title {
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  line-height: 1.15;
  margin: 0;
  color: var(--ink-warm);
}
.history-modal-sub {
  margin: 0 0 var(--space-2);
  font-size: var(--text-sm);
  color: var(--ink-soft);
}
.history-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0;
  padding: 0;
}
/* `display: flex` would otherwise override the [hidden] attribute, so we
   need to explicitly hide the list when switching to the detail view. */
.history-list[hidden],
.history-detail[hidden] { display: none; }
.history-row {
  appearance: none;
  background: none;
  border: 1px solid transparent;
  border-radius: 8px;
  padding: 10px 12px;
  text-align: left;
  cursor: pointer;
  color: inherit;
  font: inherit;
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 4px 12px;
  align-items: baseline;
  transition: background 140ms ease, border-color 140ms ease;
}
.history-row:hover,
.history-row:focus-visible {
  background: rgba(255,255,255,0.03);
  border-color: var(--border-soft);
  outline: none;
}
.history-row.is-current {
  background: rgba(143,192,169,0.07);
  border-color: rgba(143,192,169,0.25);
}
.history-when {
  font-family: var(--font-display);
  font-size: var(--text-base);
  line-height: 1.3;
  color: var(--ink-warm);
}
.history-meta {
  font-size: 10.5px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.history-source {
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--sage);
  border: 1px solid rgba(143,192,169,0.28);
  background: rgba(143,192,169,0.08);
  padding: 2px 7px;
  border-radius: 999px;
}
.history-empty {
  font-size: var(--text-sm);
  color: var(--ink-soft);
  padding: var(--space-3);
  text-align: center;
}
.history-detail {
  border-top: 1px solid var(--border-soft);
  padding-top: var(--space-3);
  margin-top: var(--space-2);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.history-detail-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-3);
}
.history-detail-when {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  margin: 0;
  color: var(--ink-warm);
}
.history-detail-back {
  appearance: none;
  background: none;
  border: 0;
  color: var(--sage);
  font: inherit;
  font-size: var(--text-sm);
  cursor: pointer;
  padding: 4px 8px;
  border-radius: 6px;
}
.history-detail-back:hover { background: rgba(255,255,255,0.04); }
.history-detail-photo {
  width: 100%;
  max-height: 260px;
  object-fit: cover;
  border-radius: var(--radius-md);
}
.history-detail-section {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.history-detail-kicker {
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.history-detail-body {
  font-size: var(--text-sm);
  line-height: 1.55;
  color: var(--ink-warm);
  margin: 0;
}
.history-detail-attr {
  font-size: var(--text-xs);
  color: var(--ink-soft);
  margin: 0;
}

/* ---------- Admin gear (footer corner) ---------- */
/* Anchored to the bottom-right corner of the footer (which is
   position: relative). Easy to find, impossible to mistake for a primary
   action. On mobile we drop it back into the flow as a centered inline
   element so it doesn't crowd the wrapped meta row. */
.admin-gear {
  position: absolute;
  right: var(--gutter);
  bottom: var(--space-5);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  color: var(--ink-faint);
  opacity: 0.35;
  transition: opacity var(--transition-interactive),
              color var(--transition-interactive),
              transform var(--transition-interactive),
              background var(--transition-interactive);
}
.admin-gear:hover {
  opacity: 1;
  color: var(--sage);
  background: rgba(143, 192, 169, 0.08);
  transform: rotate(45deg);
}
.admin-gear:focus-visible {
  opacity: 1;
  outline: 2px solid var(--sage);
  outline-offset: 4px;
}

/* On narrow viewports, return the gear to the centered axis below the meta
   row so it doesn't visually fight the wrapped "Refreshed... History" line. */
@media (max-width: 640px) {
  .admin-gear {
    position: static;
    margin: var(--space-3) auto 0;
  }
}

/* ---------- Reduced motion safeguard ---------- */
/* Note: we intentionally do NOT apply a blanket
   `* { animation-duration: 0.01ms }` reduced-motion override here. The hero,
   breath orb, and scroll-hint each have their own targeted
   `(prefers-reduced-motion: reduce)` rules above that tone down or remove
   the heavier motion. The remaining animations on this site (waveform bars
   while a sound is actively playing, gentle opacity fades) are quiet,
   intentional, and only run in response to a direct user action, so we
   leave them on for users with reduce-motion enabled. This also means
   desktop browsers with OS-level "Reduce Motion" still see the page feel
   alive rather than completely static. */
