/* BMS Theme — Base Variables */
:root {
  /* Opt out of mobile-browser forced dark mode (Samsung Internet,
     Chrome-Android). The site is already a dark design; without this the
     browser re-darkens the ember orange into a muddy maroon. Paired with
     the color-scheme meta tag in Layout.astro. */
  color-scheme: dark;

  --bms-ink:        #15110D;
  --bms-ember:      #D97A2E;
  --bms-paper:      #EDE3D2;
  --bms-brick:      #8B4A2F;
  --bms-ash:        #4A4035;
  --bms-smoke:      #2A231C;
  --bms-birch:      #C8B89A;
  --bms-ice:        #A8C5D6;
  --bms-sauna:      #F5E6C8;
  --bms-live-green: #4CAF78;

  --type-display: clamp(4rem, 10vw, 9rem);
  --type-h1:      clamp(3rem, 6vw, 5.5rem);
  --type-h2:      clamp(2rem, 4vw, 3.5rem);
  --type-h3:      clamp(1.5rem, 3vw, 2.2rem);
  --type-body:    1.125rem;
  --type-small:   0.875rem;
  --type-micro:   0.75rem;

  --space-xs:  0.5rem;
  --space-sm:  1rem;
  --space-md:  2rem;
  --space-lg:  4rem;
  --space-xl:  8rem;

  /* Maximum content width for centred sections (Offerings, etc).
     Without this, sections that reference var(--site-width) span the full
     viewport on iMac — making columns disproportionately wide and the
     proportions between text/photo columns break down. */
  --site-width: 1400px;

  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 16px;

  --transition-fast: 200ms ease;
  --transition-mid:  400ms ease;
  --transition-slow: 800ms cubic-bezier(0.16, 1, 0.3, 1);
}

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

html {
  background: var(--bms-ink);
  color: var(--bms-paper);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  /* Suppress the iOS / Android pull-to-refresh rubber-band on the
     document. Without this, pulling down on a touch device causes the
     viewport to bounce while position:fixed elements (site-header,
     nav-toggle) stay glued to the actual viewport — the visual result
     is the bar appearing to "detach" from the logo and hamburger as
     the rest of the page rubber-bands. `contain` keeps inner scrolling
     intact (modals etc.) while killing the outer bounce. */
  overscroll-behavior-y: contain;
  /* No `scroll-behavior: smooth` here — it animates scroll restoration on
     browser back, producing the "slide down from the top" effect. Anchor
     link smooth-scroll is handled per-click in main.js where wanted, and
     back navigation now lands instantly at the saved position. */
}

body { overscroll-behavior-y: contain; }

::selection {
  background: var(--bms-ember);
  color: var(--bms-ink);
}

img { max-width: 100%; height: auto; display: block; }

body { overflow-x: hidden; }

/* Typography */
h1, h2, h3, h4, .staatliches {
  font-family: 'Staatliches', sans-serif;
  font-weight: 400;
  letter-spacing: 0.02em;
  line-height: 1;
  color: var(--bms-paper);
}

h1 { font-size: var(--type-h1); }
h2 { font-size: var(--type-h2); }
h3 { font-size: var(--type-h3); }

.display { font-size: var(--type-display); }

.editorial {
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-style: italic;
  line-height: 1.4;
}

p { font-size: var(--type-body); color: var(--bms-birch); }

/* Links */
a { color: var(--bms-ember); text-decoration: none; transition: color var(--transition-fast), opacity var(--transition-fast); }
a:hover { opacity: 1; }

.site-nav a:hover {
  color: #ffffff;
  text-shadow: 0 0 20px rgba(255, 255, 255, 0.4);
}

/* Utility */
.ember { color: var(--bms-ember); }
.paper { color: var(--bms-paper); }
.birch { color: var(--bms-birch); }
.ice   { color: var(--bms-ice); }

/* ===== STICKY HEADER ===== */
.site-header {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 100;
  /* Fluid side padding across the whole desktop range — keeps the
     transition continuous instead of snapping at any media boundary. */
  padding: 1rem clamp(0.85rem, calc(-2.1rem + 6.14vw), 2.5rem);
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: rgba(21, 17, 13, 0.7);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid rgba(237, 227, 210, 0.08);
  transition: background var(--transition-mid);
}

.site-header {
  transition:
    background var(--transition-mid),
    transform 400ms cubic-bezier(0.16, 1, 0.3, 1),
    box-shadow var(--transition-mid);
}

.site-header.scrolled {
  background: rgba(21, 17, 13, 0.95);
  box-shadow: 0 1px 0 0 rgba(237, 227, 210, 0.06);
}

.site-header.hidden {
  transform: translateY(-110%);
}

/* Hamburger toggle is a separate fixed sibling of the header — when
   the header slides off-screen on scroll-down, the toggle would
   otherwise stay glued to the top corner. Mirror the header's
   transform via a general sibling selector so they slide out together.
   `.nav-toggle.open` (menu is open) overrides the hide so the user can
   always tap the X to close, even if a body-scroll fires the header's
   hidden state while the overlay is up. */
.site-header.hidden ~ .nav-toggle:not(.open) {
  transform: translateY(-160%);
  pointer-events: none;
}
.nav-toggle {
  transition: transform 400ms cubic-bezier(0.16, 1, 0.3, 1);
}

.site-logo { display: inline-flex; align-items: center; transition: transform var(--transition-fast); }
.site-logo:hover { transform: scale(1.04); opacity: 1; }
.site-logo img { height: 80px; width: auto; display: block; }
.footer-brand .site-logo img { height: 96px; }

/* Left-cluster wrapper — back chip + logo behave as one flex item so the
   header's space-between distribution always keeps them anchored to the
   left. Without this, the logo drifts to the centre on inner pages. */
.site-header-left {
  display: flex;
  align-items: center;
  /* Same gap the back chip used for its right margin, kept here so the
     space between chip and logo stays correct even if margins on the
     children change later. */
}

/* ── Back chip (inner pages, sits left of the logo in the header) ────── */
.site-back {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-right: 1rem;
  padding: 0.5rem 0.9rem 0.5rem 0.7rem;
  background: rgba(237, 227, 210, 0.06);
  border: 1px solid rgba(237, 227, 210, 0.18);
  border-radius: 999px;
  color: var(--bms-paper);
  font-family: 'Staatliches', sans-serif;
  font-size: 0.78rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  text-decoration: none;
  transition:
    border-color 220ms ease,
    background 220ms ease,
    color 220ms ease;
}

.site-back svg {
  flex-shrink: 0;
  transition: transform 220ms cubic-bezier(0.4, 0, 0.2, 1);
}

.site-back:hover,
.site-back:focus-visible {
  border-color: var(--bms-ember);
  color: var(--bms-ember);
  background: rgba(217, 122, 46, 0.08);
}

.site-back:hover svg {
  transform: translateX(-3px);
}

@media (max-width: 768px) {
  /* On mobile the header is tight — shrink the chip and drop the label */
  .site-back {
    margin-right: 0.5rem;
    padding: 0.4rem 0.6rem;
    font-size: 0.7rem;
    letter-spacing: 0.15em;
  }
  .site-back span { display: none; }
}

.site-nav {
  display: flex;
  /* Fluid spacing so items don't overlap the logo/live-status at
     mid-range widths. Scales smoothly between 0.4rem (tightest, at
     the just-above-mobile breakpoint) and 2rem (full desktop). */
  /* True linear interpolation across the full desktop range (770px → 1920px):
     ~0.45rem at the mobile breakpoint, ~2rem at full desktop. No pegging
     at the max until ~1920px, so the gap shrinks continuously as the
     window narrows rather than holding flat then snapping. */
  gap: clamp(0.45rem, calc(-2.33rem + 5.77vw), 2rem);
  align-items: center;
  flex-wrap: nowrap;
  /* Anchor the nav at the optical centre of the page, independent of the
     logo/live-status widths. Without this, justify-content: space-between
     on the header puts the nav midway between those two elements, which
     visually drifts left because the live-status is wider than the logo. */
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}

.site-nav a {
  font-family: 'Staatliches', sans-serif;
  /* Fluid type so labels shrink instead of overflowing into the logo
     between 900px (mobile breakpoint) and ~1500px (full size).
     Slope tightened so all nav items fit at narrower widths
     without needing to hide any of them. */
  font-size: clamp(0.85rem, calc(0.22rem + 1.3vw), 1.2rem);
  letter-spacing: 0.04em;
  color: var(--bms-paper);
  white-space: nowrap;
  transition: color var(--transition-fast);
}

/* The nav is absolutely centred in the header, so as it widens it can
   reach the right-side live-status. Adding EVENTS (9 nav items) tipped
   the row into the status across the whole laptop range. Tighten the
   gap + type from the mobile breakpoint up to 1400px so the row always
   clears the status; only very wide screens (≥1401px), where there's
   ample room, use the roomy base spacing. */
@media (min-width: 769px) and (max-width: 1400px) {
  .site-nav { gap: clamp(0.4rem, calc(-1.2rem + 3.3vw), 1.5rem); }
  .site-nav a { font-size: clamp(0.78rem, calc(0.3rem + 0.9vw), 1.1rem); }
}

.site-nav a { transition: color var(--transition-fast), text-shadow var(--transition-fast); }

/* Nav-sized variant of .btn.btn-primary, inherits the gradient, inner
   highlight, hover paper-fill animation and tactile press from .btn-primary;
   this rule only tightens the size to fit the header. The scoped
   `.site-nav a.nav-book-btn` selector wins specificity over `.site-nav a`'s
   default birch text colour, so the BOOK NOW text reads as the proper dark
   ink against the ember background. */
/* Higher specificity (`.site-nav a.nav-book-btn`, 0,2,1) is required
   to beat the `.btn` base rule (0,1,0) which appears LATER in the
   stylesheet and would otherwise win the source-order tiebreak and
   restore the chunky `padding: 0.95rem 2.2rem; font-size: 1.2rem`
   that makes the button tower over the rest of the nav. */
.site-nav a.nav-book-btn {
  color: var(--bms-ink);
  padding: clamp(0.35rem, calc(-0.1rem + 0.93vw), 0.6rem) clamp(0.7rem, calc(-0.29rem + 2.05vw), 1.25rem);
  font-size: clamp(0.85rem, calc(0.31rem + 1.12vw), 1.15rem);
  letter-spacing: 0.04em;
  line-height: 1.1;
  min-height: 0;
}

/* Mid-range tightening: between the mobile breakpoint and full
   desktop the nav has more items than fit comfortably alongside
   the logo + live-status. The breakpoints below are deliberately
   wide so there's a generous safety buffer at the cross-over
   widths — no chance of nav items crowding the logo on the left
   or the Book Now button covering the open-status text on the
   right at any width in this range. */

/* Mid-range live-status: keep the text visible everywhere, but
   drop its fixed 13rem reservation so the container only takes
   the width it actually needs. The text shrinks with the rest of
   the nav at narrower widths via the font-size clamp below. */
@media (min-width: 901px) and (max-width: 1180px) {
  .header-live-status { min-width: 0 !important; }
}

/* Live indicator */
.live-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  /* Inherit the parent's text colour — green when open, ember when
     closed. The container (.header-live-status / .mobile-book-bar-status)
     swaps its colour via .is-open / .is-closed. */
  background: currentColor;
  border-radius: 50%;
  /* The pulse glow uses --pulse-rgb so it tints to match the dot. */
  --pulse-rgb: 76, 175, 120;
  animation: pulse-live 2s ease-in-out infinite;
  flex-shrink: 0;
}

.header-live-status.is-closed .live-dot,
.mobile-book-bar-status.is-closed .live-dot,
.header-live-status.is-loading .live-dot,
.mobile-book-bar-status.is-loading .live-dot {
  --pulse-rgb: 217, 122, 46;
}

/* Loading state: while JS calculates the open/closed status on first
   page load, show an amber-tinted "CHECKING…" placeholder instead of
   the misleading green "—". The dot stays visible and pulses amber. */
.header-live-status.is-loading,
.mobile-book-bar-status.is-loading {
  color: var(--bms-ember);
  opacity: 0.7;
}

@keyframes pulse-live {
  0%, 100% { box-shadow: 0 0 0 0 rgba(var(--pulse-rgb), 0.4); }
  50%       { box-shadow: 0 0 0 6px rgba(var(--pulse-rgb), 0); }
}

.header-live-status {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.4rem;
  font-family: 'Staatliches', sans-serif;
  /* Fluid type, follows the same slope as the nav links so the
     status label reads in proportion to them at any width. */
  font-size: clamp(0.78rem, calc(0.38rem + 0.82vw), 1rem);
  letter-spacing: 0.08em;
  color: var(--bms-live-green);
  white-space: nowrap;
  /* Reserve room for the widest realistic status string
     ("OPENS · TOMORROW 7AM") so the placeholder doesn't paint at a
     narrow width and then push the nav leftwards when JS swaps in
     the real text. Scaled so the reservation matches the actual
     rendered text width at the current viewport. */
  min-width: clamp(8rem, 13vw, 13rem);
}

.header-live-status.is-closed {
  color: var(--bms-ember);
}

/* Hamburger (mobile) */
.nav-toggle {
  display: none;
  background: none;
  border: none;
  cursor: pointer;
  padding: 0.5rem;
  z-index: 201;
  position: relative;
}

.nav-toggle span {
  display: block;
  width: 24px;
  height: 2px;
  background: var(--bms-paper);
  margin: 5px 0;
  border-radius: 2px;
  transform-origin: center;
  /* Smooth burger ⇄ X morph */
  transition:
    transform 360ms cubic-bezier(0.22, 1, 0.36, 1),
    opacity   180ms ease,
    background 200ms ease;
}

.nav-toggle.open span:nth-child(1) { transform: translateY(4px) rotate(45deg); }
.nav-toggle.open span:nth-child(2) { transform: translateY(-3px) rotate(-45deg); }

/* ===== MARQUEE ===== */
.marquee-wrap {
  position: relative;
  overflow: hidden;
  background: var(--bms-ember);
  padding: 0.6rem 0;
  cursor: grab;
  user-select: none;
}

.marquee-wrap.is-dragging {
  cursor: grabbing;
}

.marquee-wrap::before,
.marquee-wrap::after {
  content: '';
  position: absolute;
  top: 0; bottom: 0;
  width: 80px;
  z-index: 2;
  pointer-events: none;
}

.marquee-wrap::before { left: 0;  background: linear-gradient(to right, var(--bms-ember), transparent); }
.marquee-wrap::after  { right: 0; background: linear-gradient(to left,  var(--bms-ember), transparent); }

.marquee-track {
  display: flex;
  align-items: center;
  white-space: nowrap;
  will-change: transform;
}

.marquee-item {
  font-family: 'Staatliches', sans-serif;
  font-size: 1.1rem;
  /* Display caps fonts like Staatliches have generous top padding inside
     the line-box, which leaves the visible caps top-aligned in the bar.
     Tight line-height removes that padding so the caps sit on the
     optical centre line of the marquee. */
  line-height: 1;
  letter-spacing: 0.05em;
  color: var(--bms-ink);
  padding: 0 3rem;
}

.marquee-sep {
  color: var(--bms-ink);
  opacity: 0.4;
  padding: 0 1rem;
  line-height: 1;
  user-select: none;
}

@keyframes marquee-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

/* ===== HERO ===== */
.hero {
  position: relative;
  height: 100vh; /* fallback for browsers without svh (Safari < 15.4) */
  height: 100svh;
  min-height: 600px;
  display: grid;
  place-items: center;
  overflow: hidden;
  /* Push the grid's centering origin down by the fixed header height (~100px).
     Without this, place-items:center splits the full 100svh, leaving content
     visually too high because the top ~100px is covered by the fixed nav.
     padding-top = header height → center shifts to (100svh + header-h) / 2,
     which aligns with the optical centre of the visible viewport area. */
  padding-top: 5.5rem;
}

.hero-bg {
  position: absolute;
  inset: 0;
  overflow: hidden;
  background: var(--bms-ink);
}

.hero-bg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Bias the crop toward the top of the frame so the taller bather's head
     keeps headroom and isn't clipped where shorter/wider viewports crop the
     landscape top/bottom. */
  object-position: center 25%;
  opacity: 0;
  /* Initial scale matches the animation's `from` keyframe exactly, so when
     `.loaded` is added the animation picks up seamlessly with no snap. */
  transform: translate3d(0, 0, 0) scale(1.05);
  transition: opacity 1400ms ease;
  will-change: opacity, transform;
}

.hero-bg img.loaded {
  opacity: 1;
  /* Animation owns the transform — no separate transition fighting for the
     same property. Starts at 0s so it begins exactly when the fade-in does;
     no mid-flight handoff that would cause a visible jump. */
  animation: hero-drift 32s ease-in-out 0s infinite alternate;
}

@keyframes hero-drift {
  from { transform: translate3d(0, var(--parallax, 0px), 0) scale(1.05); }
  to   { transform: translate3d(0, calc(var(--parallax, 0px) - 1.5%), 0) scale(1.02); }
}

.hero-overlay {
  position: absolute;
  inset: 0;
  /* Layered gradients (read top to bottom in stacking order):
       1. Warm ember light-leak from the top-right corner — suggests
          firelight catching the room. Very subtle; the camera-flare
          quality, not a glaring orange wash.
       2. Corner vignette — darkens the edges so the eye lands on
          centre composition.
       3. Original top-and-bottom darken for legibility against the
          headline text. */
  background:
    /* Subtle ember light-leak in the top-right corner — kept purely
       for character, not for dimming. */
    radial-gradient(ellipse 70% 50% at 85% 10%, rgba(194, 98, 43, 0.06) 0%, rgba(194, 98, 43, 0) 70%),
    /* Lighter localized darken behind the hero text block — just
       enough contrast for the headline/sub copy to read without
       muting the surrounding image. */
    radial-gradient(ellipse 55% 38% at 50% 55%, rgba(0, 0, 0, 0.26) 0%, rgba(0, 0, 0, 0.12) 45%, rgba(0, 0, 0, 0) 75%);
}

.hero-content {
  position: relative;
  z-index: 2;
  text-align: center;
  padding: 0 1.5rem;
  max-width: 1200px;
}


.hero-headline {
  font-family: 'Staatliches', sans-serif;
  /* Gentle vw slope so mid-size laptops (Surface ~1504, 16:10 ~1280–1680)
     don't get swamped. 6vw gives ~90px at 1504px and ~77px at 1280px, and
     still reaches the 9rem cap on large external monitors (~2400px+). */
  font-size: clamp(4rem, 6vw, 9rem);
  line-height: 0.92;
  color: var(--bms-paper);
  margin-bottom: 1.5rem;
}

/* Mobile-only line break: used by the hero headline, sub-line, and
   location strip to wrap onto extra lines on narrow screens while
   keeping desktop typography on fewer lines. */
.hero-mobile-break { display: none; }

@media (max-width: 600px) {
  .hero-mobile-break { display: inline; }
  /* Drop the " · " separator on mobile since the line break does the
     work of separating BLUE MOUNTAINS SAUNA from LEURA, NSW. */
  
}

/* Surface laptops / smaller-resolution desktops: stack the location strip
   (BLUE MOUNTAINS SAUNA / LEURA, NSW) onto two lines so it breathes with
   the tamed hero type. Scoped to the location break only so the headline
   and sub-line keep their desktop wrapping. */


.hero-sub {
  /* Same hold-then-ramp curve as .hero-location — pegs at 1.6rem
     above ~1200px, but the floor is held at 1.25rem so the sub copy
     stays comfortably readable at narrow widths rather than dropping
     into fine-print territory. */
  font-size: clamp(1.25rem, calc(0.62rem + 1.3vw), 1.6rem);
  color: var(--bms-paper);
  margin-bottom: 2rem;
  line-height: 1.5;
  /* no opacity here — set per-line below so est. 2022 can be full white */
}

.hero-sub-playfair {
  font-family: 'Playfair Display', Georgia, serif;
  font-style: italic;
  font-weight: 400;
  display: block;
  /* Full paper + a soft shadow so the cursive line holds its own over the
     photo (was 85% with no shadow, which read as slightly washed out). */
  color: var(--bms-paper);
  text-shadow: 0 1px 12px rgba(13, 10, 7, 0.65);
}


.hero-ctas {
  display: flex;
  gap: 1rem;
  justify-content: center;
  flex-wrap: wrap;
}

/* Surface laptops / smaller-resolution desktops: tighten the hero CTAs so
   they sit in proportion with the tamed hero type (less padding either side,
   slightly smaller label) without touching the global .btn used elsewhere. */
@media (min-width: 769px) and (max-width: 1536px) {
  .hero-ctas .btn {
    font-size: 1.05rem;
    padding: 0.8rem 1.5rem;
  }
}

/* ── HERO OPTION C — centred + decluttered ────────────────────────────
   Keep the copy centred (the original placement) but tighten the column,
   demote the 2nd CTA to a quiet link, and deepen a soft pool behind the
   centred text. The pool also tames the weaker centre of the photo while
   the loved left edge stays bright. Revert: restore /tmp/bms-hero-backup. */
/* Keep the original 1200px column so the big "REAL SAUNA CULTURE." line
   stays on one row (760px forced an extra wrap). */
.hero-content { max-width: 1200px; }

.hero-overlay {
  background:
    /* top-right ember light-leak, character only */
    radial-gradient(ellipse 60% 45% at 86% 8%, rgba(194, 98, 43, 0.06) 0%, rgba(194, 98, 43, 0) 70%),
    /* centred readability pool — kept tight to the text so the photo keeps
       its punch at the edges. Strong core, quick falloff that clears the
       headline (~25–75% of the width) and lets the left figure + right side
       stay vivid. */
    radial-gradient(ellipse 44% 40% at 50% 50%, rgba(11, 8, 6, 0.64) 0%, rgba(11, 8, 6, 0.22) 36%, rgba(11, 8, 6, 0) 60%),
    /* whisper of a foot gradient just to seat the CTAs */
    linear-gradient(to top, rgba(11, 8, 6, 0.2) 0%, rgba(11, 8, 6, 0) 13%);
}

/* Quiet secondary hero action (the demoted "Not a spa" link). */
.hero-cta-link {
  display: inline-flex;
  align-items: center;
  font-family: 'Staatliches', sans-serif;
  letter-spacing: 0.05em;
  font-size: 1.05rem;
  color: var(--bms-paper);
  border-bottom: 1px solid rgba(237, 227, 210, 0.4);
  padding-bottom: 2px;
  text-shadow: 0 1px 10px rgba(13, 10, 7, 0.55);
  transition: color 0.2s ease, border-color 0.2s ease;
}
.hero-cta-link:hover,
.hero-cta-link:focus-visible {
  color: var(--bms-ember);
  border-color: var(--bms-ember);
  outline: none;
}

/* ── Form anti-abuse (Turnstile + honeypot) ──────────────────────────
   .cf-turnstile-slot hosts the lazily-rendered Turnstile widget (see
   main.js). With appearance:'interaction-only' the slot stays empty for
   legitimate visitors, so the spacing below only applies on the rare
   occasion a real interactive challenge is painted. .hp-field is the
   honeypot: visually removed but present for naive bots; endpoints reject
   when it's filled. */
/* Collapsed by default: interaction-only keeps the widget invisible, so the
   slot must not reserve height (no empty gap in the form). When a real
   challenge IS painted, the Cloudflare iframe brings its own ~65px height
   and the :has() rule adds breathing room above/below it. */
.cf-turnstile-slot { min-height: 0; }
.cf-turnstile-slot:has(iframe) {
  margin: 0.6rem 0;
  /* Centre the fixed-width (~300px) widget so it doesn't sit flush-left and
     overrun the form's right edge. */
  display: flex;
  justify-content: center;
}
.hp-field {
  position: absolute !important;
  left: -9999px !important;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* Keyboard skip link — first focusable element on every page. Hidden
   off-canvas until focused, then drops in over the fixed header. */
.skip-link {
  position: fixed;
  top: 0.75rem;
  left: 0.75rem;
  z-index: 1000;
  transform: translateY(-300%);
  padding: 0.7rem 1.2rem;
  background: var(--bms-ember);
  color: var(--bms-ink);
  font-family: 'Staatliches', sans-serif;
  letter-spacing: 0.08em;
  border-radius: 6px;
  transition: transform 0.2s ease;
}
.skip-link:focus-visible {
  transform: translateY(0);
  outline: 2px solid var(--bms-paper);
  outline-offset: 2px;
}

/* ===== BUTTONS ===== */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: 'Staatliches', sans-serif;
  font-size: 1.2rem;
  letter-spacing: 0.06em;
  padding: 0.95rem 2.2rem;
  border-radius: var(--radius-sm);
  cursor: pointer;
  border: none;
  text-decoration: none;
  line-height: 1;
  overflow: hidden;
  isolation: isolate;
  transition:
    transform 220ms cubic-bezier(0.22, 1, 0.36, 1),
    box-shadow 280ms cubic-bezier(0.22, 1, 0.36, 1),
    color 300ms ease,
    border-color 280ms ease,
    background 280ms ease;
}

/* Tactile press: small down-shift + inset shadow on the buttons that
   want to feel like physical surfaces. Defined per-variant below so
   each can have its own pressed shadow. */
.btn:active { transform: translateY(1px) scale(0.99); }

/* ── Primary: glowing coal ─────────────────────────────────────────────
   Subtle 180° gradient (4% lighter top → ember bottom) gives the button
   a sense of being lit from above. A 1px inner highlight at the top edge
   reinforces the bevel without crossing into skeuomorphism. The resting
   shadow is barely-there — it's depth, not drama. */
.btn-primary {
  background: linear-gradient(180deg, #d4823a 0%, var(--bms-ember) 100%);
  color: var(--bms-ink);
  box-shadow:
    inset 0 1px 0 rgba(255, 230, 195, 0.32),
    0 1px 2px rgba(0, 0, 0, 0.25);
}

.btn-primary::before {
  content: '';
  position: absolute;
  inset: 0;
  /* Bright paper wave that fills the button on hover — keeps the
     ember-to-white flip energetic and reads as a clear state change. */
  background: var(--bms-paper);
  transform: translate(var(--btn-x, 0%), var(--btn-y, 101%));
  transition: transform 480ms cubic-bezier(0.16, 1, 0.3, 1);
  z-index: -1;
}

.btn-primary.is-hovered::before { transform: translate(0%, 0%); }
.btn-primary.no-transition::before { transition: none; }

.btn-primary:hover {
  color: var(--bms-ink);
  box-shadow:
    inset 0 1px 0 rgba(255, 230, 195, 0.38),
    0 6px 22px -6px rgba(217, 122, 46, 0.55),
    0 2px 4px rgba(0, 0, 0, 0.2);
}

.btn-primary:active {
  box-shadow:
    inset 0 1px 3px rgba(0, 0, 0, 0.28),
    0 0 0 transparent;
}

/* ── Ghost: refined edge ───────────────────────────────────────────────
   Slight tinted background + inner highlight gives presence at rest, so
   it reads as a real surface rather than a borrowed outline. */
.btn-ghost {
  background: rgba(237, 227, 210, 0.025);
  color: var(--bms-paper);
  border: 1px solid rgba(237, 227, 210, 0.28);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.04),
    0 1px 2px rgba(0, 0, 0, 0.18);
}

.btn-ghost::before {
  content: '';
  position: absolute;
  inset: 0;
  /* Bright paper fill — matches the .btn-primary hover so both
     buttons read the same energetic ember-to-white flip on cursor-in. */
  background: var(--bms-paper);
  transform: translate(var(--btn-x, 0%), var(--btn-y, 101%));
  transition: transform 480ms cubic-bezier(0.16, 1, 0.3, 1);
  z-index: -1;
}

.btn-ghost.is-hovered::before { transform: translate(0%, 0%); }
.btn-ghost.no-transition::before { transition: none; }

.btn-ghost:hover {
  border-color: rgba(237, 227, 210, 0.6);
  /* Flip text to ink so it reads against the new paper fill. */
  color: var(--bms-ink);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.06),
    0 4px 14px -4px rgba(0, 0, 0, 0.35);
}

.btn-ghost:active {
  box-shadow:
    inset 0 1px 3px rgba(0, 0, 0, 0.3);
}

/* ===== ON TODAY — bulletin board ===== */
/* ===== THE HEAT IS ON (featured image panel) ===== */


/* ── OFFERINGS SECTION ──────────────────────────────────────────────────── */
.offerings-section {
  background: var(--bms-ink);
  padding: var(--space-xl) 0;
}

.offerings-inner {
  max-width: var(--site-width);
  margin: 0 auto;
  padding: 0 var(--space-md);
  display: grid;
  grid-template-columns: 5fr 6fr;
  gap: var(--space-lg);
  align-items: stretch;
}

.offerings-photos {
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
  min-height: 0;
  width: 100%;
  /* stretches to match the copy column height via parent's align-items: stretch */
}

.offerings-photos > .offerings-grid {
  flex: 1;
  height: auto;
  min-height: 0;
  /* no max-height — each block takes half of whatever the copy column dictates */
}

.offerings-text {
  padding-right: var(--space-md);
}

.offerings-text .section-label {
  margin-bottom: 0.2rem;
}

.offerings-headline {
  font-family: 'Staatliches', sans-serif;
  font-size: clamp(4.5rem, 9vw, 8rem);
  color: var(--bms-paper);
  line-height: 0.9;
  letter-spacing: 0.02em;
  margin: 0 0 var(--space-md);
}

.offerings-list {
  list-style: none;
  padding: 0;
  /* No bottom margin — the .offerings-note footnote sits directly
     under the columns. Spacing to the next block (info links) is
     controlled by the note's own bottom margin. */
  margin: 0;
  display: flex;
  flex-direction: column;
}

.offerings-list li {
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-size: 1.15rem;
  color: rgba(237, 227, 210, 0.9);
  padding: 0.85rem 0;
  border-bottom: 1px solid rgba(237, 227, 210, 0.08);
  /* Two-column grid: a fixed-width label rail (left) plus a fluid
     description column (right). All labels align to the same x even
     though they vary in length, so the descriptions also line up on
     a single vertical rail. Much cleaner editorial feel than the
     previous baseline-flex which left every row starting at a
     different x. */
  display: grid;
  grid-template-columns: 11rem 1fr;
  column-gap: 1.25rem;
  align-items: baseline;
  line-height: 1.45;
}

.offerings-list li:first-child {
  border-top: 1px solid rgba(237, 227, 210, 0.08);
}

.offerings-list li strong {
  font-family: 'Staatliches', sans-serif;
  font-size: 0.95rem;
  letter-spacing: 0.14em;
  color: var(--bms-ember);
  font-weight: 400;
  /* Label sits in the first grid column, no need for flex-shrink. */
}


.offerings-note {
  /* Match the body text of .offerings-list li so the footnote reads
     as the same voice as the list rows, just italicised. */
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-style: italic;
  font-size: 1.15rem;
  line-height: 1.45;
  color: rgba(237, 227, 210, 0.9);
  margin: 0.6rem 0 var(--space-lg);
  border-bottom: none;
}

/* Match the etiquette/info card header treatment (.offerings-info-label):
   Staatliches caps instead of the default italic serif footnote. */
.offerings-note--label {
  font-family: 'Staatliches', sans-serif;
  font-style: normal;
  font-size: 1.1rem;
  letter-spacing: 0.1em;
  color: var(--bms-paper);
}

/* Hide the mobile-only headline portrait on desktop — the offerings
   photo grid to the right of the text handles all imagery here. */
.offerings-headline-photo { display: none; }

/* Mobile rhythm for the facility list: stack the Staatliches label on
   its own line above the serif value so neither column gets squeezed.
   On narrower widths the side-by-side flex was forcing long values
   ("filtered water, tea and salt for your hydration…") to wrap under
   the value column with a deep hanging indent — the stacked layout
   reads cleanly and keeps the rows on equal vertical rhythm. */
@media (max-width: 600px) {
  /* Show the headline-row portrait on mobile — narrow vertical image
     fills the empty space to the right of the stacked SIT/SWEAT/CHILL/
     REPEAT headline. Aspect ratio + flex sizing keeps the photo as a
     tall slim band rather than a square so it reads as an intentional
     graphic-design choice (not a thumbnail). Subtle hairline border
     and rounded corners tie it to the rest of the section's cards. */
  .offerings-headline-row {
    display: flex;
    /* Same gap value as .offerings-actions below, so the photo + CTA
       columns share a continuous vertical seam. */
    gap: 0.65rem;
    align-items: stretch;
    margin-bottom: 1.5rem;
  }
  .offerings-headline-row .offerings-headline {
    flex: 1 1 auto;
    margin-bottom: 0;
  }
  .offerings-headline-photo {
    display: block;
    /* Match the PASSES button width directly underneath — both columns
       are exactly half of (section width − 0.65rem gap), so the
       photo's right edge stacks with the PASSES right edge for a
       balanced two-column rhythm. */
    flex: 0 0 calc(50% - 0.325rem);
    max-width: none;
    align-self: stretch;
    overflow: hidden;
    border-radius: var(--radius-md);
    border: 1px solid rgba(237, 227, 210, 0.08);
  }
  .offerings-headline-photo img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    /* Bias the crop up toward the subject's face. */
    object-position: center 28%;
    display: block;
  }

  /* Balance the CTAs under the headline+photo row: extend the buttons
     edge-to-edge of the section so they line up with the right edge
     of the portrait above and read as a stable base for the
     headline+photo composition. */
  .offerings-actions {
    width: 100%;
    gap: 0.65rem;
    flex-wrap: nowrap;
  }
  .offerings-actions .btn {
    flex: 1 1 0;
    min-width: 0;
    padding: 0.95rem 1rem;
    justify-content: center;
  }

  /* Top-to-bottom fade-in cascade for the section blocks. Specificity
     bumped via the parent .offerings-text so this rule beats the
     global mobile `.fade-up { transition-delay: 0ms }` reset that
     lives later in the file. Each block has its own --card-delay set
     inline on the element. */
  /* CSS animation (not transition) for the cascade. Animations play
     from their declared keyframes regardless of paint-cycle timing,
     so even if the IO adds `.visible` in the same frame as the
     elements are inserted, the animation still runs from 0%. */
  .offerings-text .offerings-block.fade-up {
    opacity: 0;
    /* Hint the compositor so the cascade renders on its own GPU layer.
       Removes janky frame-jumps on lower-power phones and keeps the
       transform interpolation pixel-smooth. */
    will-change: opacity, transform;
  }
  .offerings-text .offerings-block.fade-up.visible {
    /* Longer 1.1s duration + ease-out-expo curve gives the blocks a
       slow settle into place rather than a snappy pop-up. The curve
       starts fast and decelerates very softly toward the resting
       position, which reads as "premium". */
    animation: offerings-rise 1.1s cubic-bezier(0.22, 0.61, 0.36, 1) both;
    animation-delay: var(--card-delay, 0ms);
  }
  /* Drop the will-change hint once the animation has settled — leaving
     it on permanently is a memory cost the GPU shouldn't carry. */
  .offerings-text .offerings-block.fade-up.visible {
    will-change: auto;
  }
  /* Keyframes moved outside this @media block — see bottom of file.
     Defining @keyframes inside an @media scopes them to that query,
     so the animation referenced from other media queries (e.g.
     @media max-width: 768px) couldn't resolve and cards stayed at
     opacity: 0. */

  /* Subtle staggered entry for each photo in the grid below the text.
     Each .offerings-photo-card has its own --photo-delay set inline
     by the component (i * 90ms, second block continues the cascade).
     Effect: gentle fade + tiny scale-up. No translate, no rotation —
     just enough motion to feel intentional without being jarring. */
  .offerings-photos .offerings-photo-card {
    opacity: 0;
    transform: scale(0.97);
    will-change: opacity, transform;
  }
  .offerings-photos .offerings-photo-card.visible {
    animation: offerings-photo-in 1s cubic-bezier(0.22, 0.61, 0.36, 1) both;
    animation-delay: var(--photo-delay, 0ms);
  }
  .offerings-photos .offerings-photo-card.visible {
    will-change: auto;
  }
  /* Keyframes moved to global scope at end of file (see comment). */

  /* Tighten the gap below the facility list so the walk-ins note
     doesn't float in a sea of dead space above the ETIQUETTE / INFO
     cards. The base rule uses var(--space-lg) (~3.5rem) which is
     unnecessarily large at narrow widths. */
  .offerings-list { margin-bottom: 0.75rem; }
  .offerings-info-links { margin-top: 0.75rem; }

  .offerings-list li {
    flex-direction: column;
    gap: 0.2rem;
    padding: 0.85rem 0;
    align-items: stretch;
    font-size: 1.1rem;
    line-height: 1.45;
  }
  .offerings-list li strong {
    font-size: 0.85rem;
    letter-spacing: 0.18em;
    color: var(--bms-ember);
  }
  .offerings-note { padding-top: 1rem !important; }
}

.offerings-info-links {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.75rem;
  margin-top: 1.5rem;
}

.offerings-info-card {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  padding: 1rem 1.25rem;
  background: var(--bms-ink);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: var(--radius-md);
  text-decoration: none;
  transition: border-color 250ms ease, transform 250ms ease;
}

.offerings-info-card:hover {
  border-color: rgba(255, 255, 255, 0.18);
  transform: translateY(-2px);
}

.offerings-info-label {
  font-family: 'Staatliches', sans-serif;
  font-size: 1.1rem;
  letter-spacing: 0.1em;
  color: var(--bms-paper);
}

.offerings-info-sub {
  font-size: 0.8rem;
  /* 0.62 alpha clears WCAG AA (4.5:1) on the smoke card bg; 0.5 sat at 4.43. */
  color: rgba(237, 227, 210, 0.62);
}

/* Mobile: tighter padding on the info cards so the arrow stays beside
   the label instead of being pushed onto a second line. */
@media (max-width: 600px) {
  .offerings-info-card { padding: 0.85rem 0.9rem; }
  .offerings-info-sub { font-size: 0.75rem; }
}

.offerings-actions {
  display: flex;
  gap: var(--space-sm);
  flex-wrap: wrap;
  margin-bottom: var(--space-lg);
}

/* 3-image grid: tall left, 2 stacked right */
.offerings-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  gap: 0.625rem;
  height: 580px;
}

.offerings-img {
  overflow: hidden;
  border-radius: var(--radius-sm);
}

.offerings-img--tall {
  grid-column: 1;
  grid-row: 1 / 3;
}

/* Tall-right variant: two small on left, tall on right */
.offerings-grid--tall-right .offerings-img:nth-child(1) {
  grid-column: 1;
  grid-row: 1;
}

.offerings-grid--tall-right .offerings-img:nth-child(2) {
  grid-column: 1;
  grid-row: 2;
}

.offerings-grid--tall-right .offerings-img--tall {
  grid-column: 2;
  grid-row: 1 / 3;
}

.offerings-img img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Mild brightness/saturation lift so the imagery pops against the
     section's ink background. Subtle — not a stylized punch, just
     enough to keep the photos from feeling muted next to the
     brighter Savu / Purpose / Invitation panels elsewhere on the
     page. */
  filter: brightness(1.08) saturate(1.06);
  transition:
    transform 700ms cubic-bezier(0.25, 0.46, 0.45, 0.94),
    filter 300ms ease;
  display: block;
}

.offerings-img:hover img {
  transform: scale(1.04);
}

/* Mobile: match the offerings top padding to the "As featured in"
   strip's 3rem bottom padding so the divider line between them has
   equal breathing room on both sides (rather than stacking the larger
   section-xl on top). */
@media (max-width: 600px) {
  /* Top matches the "As featured in" strip's 3rem; bottom trimmed from
     the large section-xl to the same 3rem so the gap down to Path to
     Now is consistent rather than oversized. */
  .offerings-section { padding-top: 3rem; padding-bottom: 3rem; }
  /* Drop the second tall image on mobile to save vertical scroll — its
     two companion thumbnails carry the second block on small screens. */
  .offerings-img--hide-mobile { display: none; }
}

@media (max-width: 960px) {
  .offerings-inner {
    grid-template-columns: 1fr;
    gap: 3rem;
  }
  .offerings-text {
    padding-right: 0;
  }
  .offerings-photos {
    gap: 0.625rem;
  }
  /* Restructure each photo block so the tall image gets full width on top
     and the two smaller images sit side-by-side below — preserves aspect
     ratios so nothing looks cropped on small screens. */
  .offerings-photos > .offerings-grid {
    height: auto;
    flex: none;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: auto auto;
  }
  .offerings-img--tall {
    grid-column: 1 / -1;
    grid-row: 1;
    aspect-ratio: 4 / 5;
  }
  .offerings-grid:not(.offerings-grid--tall-right) .offerings-img:not(.offerings-img--tall) {
    grid-row: 2;
    aspect-ratio: 4 / 3;
  }
  .offerings-grid--tall-right .offerings-img--tall {
    grid-column: 1 / -1;
    grid-row: 1;
  }
  .offerings-grid--tall-right .offerings-img:nth-child(1),
  .offerings-grid--tall-right .offerings-img:nth-child(2) {
    grid-column: auto;
    grid-row: 2;
    aspect-ratio: 4 / 3;
  }
}

@media (max-width: 480px) {
  .offerings-photos > .offerings-grid {
    gap: 0.375rem;
  }
  .offerings-headline {
    font-size: clamp(3.5rem, 14vw, 5rem);
  }
}


/* Header strip */


/* Body */


/* Widget cell */


/* Footer strip */


/* ===== EVENTS CAROUSEL ===== */
.events-section {
  padding-bottom: var(--space-lg);
}

/* Mobile: .section-alt's var(--space-xl) (128px) top padding leaves a
   big dead band of ink between the pricing fine-print and the "MARK THE
   CALENDAR" header. Tighten it so the events section follows on at a
   normal mobile rhythm. */
@media (max-width: 600px) {
  .section-alt.events-section {
    padding-top: 2.5rem;
    padding-bottom: 2.5rem;
  }
}

.events-header {
  padding: 0 var(--space-lg);
  margin-bottom: var(--space-md);
}

.events-carousel-wrap {
  position: relative;
}

.events-carousel {
  display: flex;
  gap: 1.25rem;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  scrollbar-width: none;
  padding: 0.5rem var(--space-lg) var(--space-md);
  cursor: grab;
  -webkit-overflow-scrolling: touch;
}

.events-carousel::-webkit-scrollbar { display: none; }
.events-carousel.is-dragging { cursor: grabbing; scroll-behavior: auto; }

.event-card {
  scroll-snap-align: start;
  flex: 0 0 min(360px, 82vw);
  /* Per-card accent: a single RGB triplet (--ev-accent) drives the corner
     glow, border, tag pill and CTA so each card reads distinct while sharing
     the same dark smoke base. Defaults to ember; overridden by :nth-child
     below to cycle a warm-led palette with one cool (ice) accent. */
  --ev-accent: 217, 122, 46;
  /* Warmer than flat ink — smoke base with a soft accent diagonal at
     the top-right corner so each card has a glow of life rather than
     reading as a plain black box. */
  background:
    radial-gradient(ellipse 70% 80% at 110% -10%, rgba(var(--ev-accent), 0.18), transparent 55%),
    var(--bms-smoke);
  border: 1px solid rgba(var(--ev-accent), 0.18);
  border-radius: var(--radius-md);
  padding: 2rem;
  display: flex;
  flex-direction: column;
  gap: 1rem;
  transition: border-color 300ms ease, transform 300ms ease, box-shadow 300ms ease;
  /* Whole card is now an <a> — strip default link styling so existing
     typography inside the card still reads as designed. */
  text-decoration: none;
  color: inherit;
}

/* Accent cycle. Featured (1st child) keeps ember; the rest alternate a
   warm-led palette with ice as the lone cool accent for contrast. */
#events-carousel .event-card:nth-child(2) { --ev-accent: 138, 180, 205; } /* ice */
#events-carousel .event-card:nth-child(3) { --ev-accent: 198, 184, 154; } /* birch/gold */
#events-carousel .event-card:nth-child(4) { --ev-accent: 100, 175, 130; } /* sage */
#events-carousel .event-card:nth-child(5) { --ev-accent: 214, 122,  92; } /* brick/rose (lightened for WCAG AA on the card bg) */
#events-carousel .event-card:nth-child(6) { --ev-accent: 138, 180, 205; } /* ice */
#events-carousel .event-card:nth-child(7) { --ev-accent: 198, 184, 154; } /* birch/gold */
/* Per-card image framing so subjects stay in view at the cropped card size. */
#events-carousel .event-card:nth-child(4) .event-card-image img { object-position: center 45%; } /* Social Saturday: keep deck group + platform pair */
#events-carousel .event-card:nth-child(6) .event-card-image img { object-position: center 38%; } /* Sweat-Work: keep both bathers' faces */
#events-carousel .event-card:nth-child(7) .event-card-image img { object-position: center 32%; } /* Orphans Christmas: centre on foreground bathers + warm bench group */

.event-card:hover {
  border-color: rgba(var(--ev-accent), 0.4);
  transform: translateY(-3px);
  box-shadow: 0 14px 30px rgba(0, 0, 0, 0.28), 0 0 24px rgba(var(--ev-accent), 0.12);
}

.event-card--featured {
  flex: 0 0 min(500px, 90vw);
  /* Featured: a fuller ember wash from the bottom-left so it visually
     sits a notch above the regular cards in the row. */
  background:
    radial-gradient(ellipse 70% 80% at 110% -10%, rgba(var(--ev-accent), 0.22), transparent 55%),
    linear-gradient(135deg, var(--bms-smoke) 55%, rgba(var(--ev-accent), 0.18));
  border-color: rgba(var(--ev-accent), 0.35);
}

.event-card--featured:hover {
  border-color: rgba(var(--ev-accent), 0.6);
}

.event-card-image {
  margin: -2rem -2rem 1.25rem;
  height: 165px;
  overflow: hidden;
  border-radius: var(--radius-md) var(--radius-md) 0 0;
}

/* Featured image kept the same height as the rest so the header band,
   title and description all start at the same Y across every card. The
   featured card still reads distinct via its greater width + ember wash. */
.event-card--featured .event-card-image {
  height: 165px;
}

.event-card-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 600ms cubic-bezier(0.16, 1, 0.3, 1);
}

.event-card:hover .event-card-image img {
  transform: scale(1.04);
}

.event-card-top {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  /* Reserve a consistent header band so the big-day cards and the
     small date-label cards start the headline at the same height. */
  min-height: 3.2rem;
}

.event-tag {
  font-family: 'Staatliches', sans-serif;
  font-size: 0.8rem;
  letter-spacing: 0.18em;
  color: rgb(var(--ev-accent));
  background: rgba(var(--ev-accent), 0.12);
  padding: 0.2rem 0.6rem;
  border-radius: 2px;
}

.event-date {
  text-align: right;
  line-height: 1;
}

.event-date-day {
  display: block;
  font-family: 'Staatliches', sans-serif;
  font-size: clamp(2.2rem, 4vw, 3.2rem);
  color: var(--bms-paper);
  line-height: 0.9;
}

.event-date-month {
  display: block;
  font-family: 'Staatliches', sans-serif;
  font-size: 0.85rem;
  letter-spacing: 0.2em;
  color: var(--bms-birch);
  margin-top: 0.2rem;
}

/* Recurring / teaser date phrase (e.g. "Last Sunday of every month").
   Rendered small + right-aligned so a multi-word label wraps cleanly
   in the corner instead of blowing out at the big-day font size. */
.event-date--label {
  max-width: 8.5rem;
}
.event-date-label {
  display: block;
  font-family: 'Staatliches', sans-serif;
  font-size: 0.92rem;
  letter-spacing: 0.12em;
  line-height: 1.15;
  color: var(--bms-birch);
}

.event-name {
  font-family: 'Staatliches', sans-serif;
  /* Reduced a touch so the longest titles (BIRTHDAY SUIT, SAUNAMEISTER)
     each fit on a single line and never wrap into a third line that would
     grow the band and push that card's description out of line. */
  font-size: clamp(1.6rem, 3vw, 2.2rem);
  color: var(--bms-paper);
  line-height: 0.98;
  margin: 0;
  /* Hard two-line band, top-aligned, so every regular card's title occupies
     the same height and its description starts at the same Y. A stray third
     line is clipped rather than allowed to shove the row down. */
  height: 2.05em;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
}

/* Featured title only a touch larger than the rest, and crucially the same
   line-height/min-height band, so its description starts level with the
   other cards. The featured emphasis comes from card width + ember wash. */
.event-card--featured .event-name {
  font-size: clamp(2rem, 3.6vw, 2.8rem);
}

.event-desc {
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-style: italic;
  font-size: 1.05rem;
  color: var(--bms-birch);
  line-height: 1.55;
  flex: 1;
  /* Clamp to a uniform 3-line block so copy of differing lengths reads as
     a consistent card and the cards stay compact. Full text remains in the
     DOM for screen readers. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
}

.event-cta {
  font-family: 'Staatliches', sans-serif;
  font-size: 1rem;
  letter-spacing: 0.1em;
  color: rgb(var(--ev-accent));
  text-decoration: none;
  transition: color 260ms ease, transform 260ms cubic-bezier(0.16, 1, 0.3, 1);
  margin-top: auto;
  display: inline-block;
  align-self: flex-start;
  transform-origin: left center;
}

.event-cta:hover {
  color: var(--bms-paper);
  transform: scale(1.04);
  opacity: 1;
}

/* ── Split-action card (Learn more + Express interest) ─────────────────────
   The card body click and the "Learn more" link go to the page; a separate
   button opens the modal. A stretched, decorative link covers the card for
   mouse users without trapping the modal button (which sits above it). */
.event-card--split { position: relative; }
.event-card-stretch {
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: inherit;
  /* purely a click target — no visible styling */
}
.event-card-actions {
  position: relative;
  z-index: 2;
  margin-top: auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1.5rem;
}
/* Normalise both actions to the same box so the <a> and <button> line up:
   reset the base .event-cta's flex-start/auto-margin, and pin an identical
   line-height (a <button> doesn't inherit line-height like the <a> does, which
   is what threw them to different heights). */
.event-card-actions .event-cta {
  margin-top: 0;
  align-self: center;
  font-size: 1rem;
  line-height: 1.2;
}
/* On narrow viewports the two actions can't fit on one line, so they stack.
   Force a clean single column with a deliberate row gap rather than letting
   them read as an accidental wrap. */
@media (max-width: 600px) {
  .event-card-actions { flex-direction: column; align-items: flex-start; row-gap: 0.85rem; }
  .event-card-actions .event-cta { align-self: flex-start; text-align: left; }
}
/* The modal trigger is a <button> — strip native chrome so it matches the
   .event-cta link styling exactly. */
button.event-cta {
  background: none;
  border: 0;
  padding: 0;
  font-family: 'Staatliches', sans-serif;
  cursor: pointer;
}
/* "Learn more" is the secondary action — muted until hovered, so the accent
   "Express interest" trigger reads as the primary call. */
.event-cta--secondary { color: var(--bms-birch); }
.event-cta--secondary:hover { color: var(--bms-paper); }

/* Nav: arrows + dots */
.events-carousel-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.25rem;
  padding: 0 var(--space-lg);
  margin-top: var(--space-sm);
}

.events-nav-btn {
  background: none;
  border: 1px solid rgba(237, 227, 210, 0.2);
  color: var(--bms-birch);
  /* 44px minimum tap target (WCAG 2.5.8) — was 40px. */
  width: 44px;
  height: 44px;
  border-radius: 50%;
  cursor: pointer;
  font-size: 1rem;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: border-color 200ms ease, color 200ms ease;
}

.events-nav-btn:hover {
  border-color: var(--bms-ember);
  color: var(--bms-ember);
}

.events-dots {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}

.events-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  border: none;
  background: rgba(237, 227, 210, 0.25);
  cursor: pointer;
  padding: 0;
  transition: background 300ms ease, transform 300ms ease;
}

.events-dot.is-active {
  background: var(--bms-ember);
  transform: scale(1.4);
}

@media (max-width: 768px) {
  .events-header { padding: 0 var(--space-md); }
  .events-carousel { padding: 0.5rem var(--space-md) var(--space-md); }
}

/* ===== PATH TO NOW (timeline) ===== */
.path-intro {
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-style: italic;
  font-size: 1.2rem;
  line-height: 1.6;
  color: var(--bms-paper);
  opacity: 0.85;
  max-width: 640px;
  margin-top: 1rem;
}

/* ── Section background alternation ────────────────────────────────────────
   offerings (ink) → sessions (smoke) → pricing (ink) → events (smoke)
   → path-to-now (ink) → [savu: image] → the-why (smoke) → film (ink)
   → guestbook (smoke) → asa-band (ink)
   ─────────────────────────────────────────────────────────────────────── */

/* sessions — smoke */
.section-alt.sessions-section  { background: var(--bms-smoke); }

/* pricing — overridden below with a layered Declan-photo background. */
/* .pricing-section background defined in the homepage pricing block. */

/* events — smoke */
.section-alt.events-section    { background: var(--bms-ink); }

/* Anchor scroll offset — section IDs targeted from nav/hero CTAs should
   land below the fixed header with breathing room so the eyebrow + h2
   are visible, not clipped. */
#path-to-now,
#sessions,
#pricing,
#purpose,
#events,
#guestbook {
  scroll-margin-top: 96px;
}

/* path-to-now — image background with fire-glow overlay */
.section-alt.path-to-now-section {
  background: var(--bms-ink);
  position: relative;
  overflow: hidden;
  isolation: isolate;
  /* Fill the viewport so the firelit background gets a cinematic full-bleed
     moment. svh handles mobile browser chrome more gracefully than vh. */
  min-height: 100vh; /* fallback for browsers without svh (Safari < 15.4) */
  min-height: 100svh;
  display: flex;
  align-items: center;
}


/* ── Desktop scroll-pin wrapper ──────────────────────────────────────
   Wraps the path-to-now section so it can position:sticky inside a
   tall outer container. The viewport "pins" to the firelit section
   while the user scrolls through the wrap's extra height; their
   scroll input drives --path-progress, which scales the line + lights
   the dots + fades each milestone's text. Once they've scrolled past
   the wrap, the section unpins and normal scrolling resumes. Disabled
   on mobile (the wrap collapses to its content height) so phones keep
   their existing scroll-into-view animation. */
@media (min-width: 769px) {
  .path-to-now-pin-wrap {
    position: relative;
    /* Total wrap height = 1 viewport (the visible pinned section) +
       extra travel for animation playback + a dwell tail at the end.
       JS reaches progress = 1 partway through (see --pin-active-frac
       below); the remaining travel is "read time" where the final
       milestone sits fully revealed before the section unpins. */
    height: 220vh;
    /* Fraction of the pinnable travel used by the animation. The
       remainder (1 - frac) is the dwell tail at the end. JS reads
       this var so the cadence can be tuned here without touching JS. */
    --pin-active-frac: 0.7;
  }
  .path-to-now-pin-wrap > .section-alt.path-to-now-section {
    position: sticky;
    top: 0;
    height: 100vh;
    min-height: 0;
  }

  /* Scroll-driven mode (desktop). When JS adds .is-scroll-progress,
     replace the keyframe-based line draw + dot ignites with values
     read from the --path-progress custom property. Thresholds per
     step (--t) are roughly the dot's horizontal position along the
     timeline, with a small lead so each dot pops as the line arrives. */
  .path-timeline.is-scroll-progress.fade-up,
  .path-timeline.is-scroll-progress {
    opacity: 1 !important;
    transform: none !important;
  }
  .path-timeline.is-scroll-progress::before {
    /* Line starts pre-drawn to the first dot (~8% of the line range)
       so the section doesn't pin with an empty viewport. From there
       it scales with progress. */
    transform: scaleX(max(0.085, var(--path-progress, 0))) !important;
    transform-origin: left center !important;
    transition: none !important;
    animation: none !important;
  }
  .path-timeline.is-scroll-progress .path-step.fade-up,
  .path-timeline.is-scroll-progress .path-step {
    opacity: clamp(0, calc((var(--path-progress, 0) - var(--t, 0)) * 16), 1) !important;
    transform: translateY(calc((1 - clamp(0, calc((var(--path-progress, 0) - var(--t, 0)) * 16), 1)) * 14px)) !important;
    transition: none !important;
  }
  .path-timeline.is-scroll-progress .path-step:not(.path-step--future)::before {
    animation: none !important;
    transition: none !important;
    opacity: clamp(0, calc((var(--path-progress, 0) - var(--t, 0)) * 30), 1) !important;
    transform: scale(clamp(0, calc((var(--path-progress, 0) - var(--t, 0)) * 30), 1)) !important;
  }
  /* Future dot: opacity tied to scroll, but keep the continuous
     spin + pulse animations (the desktop's time-staged crescendo
     can't sync with arbitrary scroll velocity). */
  .path-timeline.is-scroll-progress .path-step--future::before {
    transition: none !important;
    opacity: clamp(0, calc((var(--path-progress, 0) - var(--t, 0)) * 30), 1) !important;
    animation:
      dot-future-spin 10s linear infinite,
      path-future-pulse 2.6s ease-in-out infinite !important;
  }
  /* Per-step thresholds, matched to each dot's horizontal column
     position (6 cols, line spans 8%-92% of timeline width). Slight
     negative offset so the dot lights as the line arrives, not after. */
  /* Step 1 starts already fully revealed so the pinned viewport
     doesn't open empty — multiplier is 16/30 so a -0.07 threshold
     gives opacity > 1 (clamped) and scale = 1 at progress 0. */
  .path-timeline.is-scroll-progress .path-step:nth-child(1) { --t: -0.07; }
  .path-timeline.is-scroll-progress .path-step:nth-child(2) { --t: 0.20; }
  .path-timeline.is-scroll-progress .path-step:nth-child(3) { --t: 0.38; }
  .path-timeline.is-scroll-progress .path-step:nth-child(4) { --t: 0.56; }
  .path-timeline.is-scroll-progress .path-step:nth-child(5) { --t: 0.74; }
  .path-timeline.is-scroll-progress .path-step:nth-child(6) { --t: 0.90; }
}

.path-to-now-bg {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  z-index: 0;
  filter: brightness(0.55) saturate(1.15) contrast(1.05) sepia(0.12);
  animation: path-fire-flicker 6s ease-in-out infinite;
  transform: scale(1.04);
  transform-origin: center;
}

.path-to-now-overlay {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    radial-gradient(ellipse at 50% 0%, rgba(0, 0, 0, 0.55) 0%, transparent 55%),
    linear-gradient(180deg, rgba(13, 13, 13, 0.72) 0%, rgba(13, 13, 13, 0.55) 50%, rgba(13, 13, 13, 0.78) 100%);
  mix-blend-mode: normal;
}

.section-alt.path-to-now-section > .section-inner {
  position: relative;
  z-index: 2;
  width: 100%;
}

@keyframes path-fire-flicker {
  0%, 100% { filter: brightness(0.55) saturate(1.15) contrast(1.05) sepia(0.12); }
  35%      { filter: brightness(0.6) saturate(1.2) contrast(1.07) sepia(0.14); }
  62%      { filter: brightness(0.52) saturate(1.12) contrast(1.04) sepia(0.1); }
}

@media (prefers-reduced-motion: reduce) {
  .path-to-now-bg { animation: none; }
  /* Skip the line-draw / dot-ignite entrance — show everything in final state */
  .path-timeline::before { transform: scaleX(1); transition: none; }
  .path-step::before { opacity: 1; transform: scale(1); animation: none !important; }
  .path-step--future::before { animation: none !important; }
}

/* the-why — ink */
.section.the-why                { background: var(--bms-ink); }

/* guestbook — muted tan (birch) with a warm radial wash on top
   that adds a sense of warm light catching the panel from above,
   plus a subtle vignette at the edges so the section reads as a
   deliberate amber-toned panel rather than a flat tan slab. */
.section-alt.guestbook-section {
  /* Atmospheric photo (BMS2-1798) sits behind a dark ink wash so the
     translucent glass cards on top read cleanly. Section settles
     darker top + bottom so the panel reads as a deliberate frame. */
  background:
    linear-gradient(180deg, rgba(21, 17, 13, 0.45) 0%, rgba(21, 17, 13, 0.3) 50%, rgba(21, 17, 13, 0.5) 100%),
    var(--guestbook-bg, none) center 30% / cover no-repeat fixed,
    var(--bms-ink);
  color: var(--bms-paper);
}
.section-alt.guestbook-section .section-label {
  color: var(--bms-ember);
  font-weight: 500;
}
.section-alt.guestbook-section .section-header h2 {
  color: var(--bms-paper);
  text-shadow:
    0 1px 2px rgba(0, 0, 0, 0.55),
    0 0 18px rgba(0, 0, 0, 0.35);
}

.path-timeline {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 0;
  position: relative;
  padding: var(--space-md) 0;
}

/* Override the fade-up parent treatment — we use .visible only to trigger the
   line draw + dot ignition; we don't want the whole list to fade-up. */
.path-timeline.fade-up {
  opacity: 1;
  transform: none;
  transition: none;
}

.path-timeline::before {
  content: '';
  position: absolute;
  top: calc(var(--space-md) + 1.6rem);
  left: 8%;
  right: 8%;
  height: 1px;
  background: linear-gradient(
    to right,
    transparent,
    rgba(217, 122, 46, 0.5) 10%,
    rgba(217, 122, 46, 0.5) 90%,
    transparent
  );
  z-index: 0;
  /* Start collapsed at left; expand on visible. Accelerating ease-in — slow
     start, faster finish. Dot ignite delays are inverse-mapped to this curve
     so each dot lights exactly when the line crosses its column. */
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 2400ms cubic-bezier(0.5, 0, 0.75, 0.5);
}

.path-timeline.visible::before {
  transform: scaleX(1);
}

.path-step {
  position: relative;
  padding: 0 1.25rem;
  text-align: left;
  z-index: 1;
}

/* Sync text fade with dot ignite. Each step's --card-delay (set inline in
   PathToNow.astro) matches its dot's ignite-start time. We shorten the
   fade to 600ms with a small lift so the text crystallises around the
   same beat as the dot blooming to peak (dot peak = delay + 280ms;
   text complete = delay + 600ms — text settles just as the dot dims to
   its resting glow). Sharper than the generic .fade-up timing because
   we want a punch in time with the spark, not a slow drift. */
.path-step.fade-up {
  transform: translateY(14px);
  transition:
    opacity 600ms cubic-bezier(0.2, 0.7, 0.2, 1),
    transform 600ms cubic-bezier(0.2, 0.7, 0.2, 1);
  transition-delay: var(--card-delay, 0ms);
}

/* Solid dots — hidden initially, ignite sequentially as line draws */
.path-step::before {
  content: '';
  display: block;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--bms-ember);
  margin: 0 auto 1rem;
  box-shadow: 0 0 0 4px rgba(13, 13, 13, 0.7), 0 0 14px 2px rgba(217, 122, 46, 0.55);
  opacity: 0;
  transform: scale(0.4);
  transition: transform var(--transition-fast), box-shadow var(--transition-fast);
}

/* Sequential ignite — staggered so each dot pops as the drawing line reaches it */
/* Dot ignite delays = time at which the line (with the ease-in curve above)
   reaches each dot's column. Computed by inverting cubic-bezier(0.5, 0, 0.75, 0.5)
   at y = 0.004, 0.202, 0.401, 0.599, 0.798. Gaps shrink as the line accelerates. */
.path-timeline.visible .path-step:nth-child(1)::before { animation: dot-ignite 700ms cubic-bezier(0.4, 0, 0.2, 1) 175ms  forwards; }
.path-timeline.visible .path-step:nth-child(2)::before { animation: dot-ignite 700ms cubic-bezier(0.4, 0, 0.2, 1) 1175ms forwards; }
.path-timeline.visible .path-step:nth-child(3)::before { animation: dot-ignite 700ms cubic-bezier(0.4, 0, 0.2, 1) 1590ms forwards; }
.path-timeline.visible .path-step:nth-child(4)::before { animation: dot-ignite 700ms cubic-bezier(0.4, 0, 0.2, 1) 1895ms forwards; }
.path-timeline.visible .path-step:nth-child(5)::before { animation: dot-ignite 700ms cubic-bezier(0.4, 0, 0.2, 1) 2155ms forwards; }

/* ── 4-column timeline modifier ────────────────────────────────────
   Used by /aufguss/sauna-master-training. The base .path-timeline
   uses 6 columns; with only 4 items the line + dot spacing read as
   unbalanced. This modifier re-grids the timeline to 4 even
   columns, repositions the connecting line so it spans the actual
   dot positions, and re-times the dot ignites so they distribute
   evenly across the line-draw duration. Inline text is centered
   per-step. */


/* Re-spaced ignite delays for 4 dots over the same 2400ms line draw.
   Dot positions in 4 cols: 12.5%, 37.5%, 62.5%, 87.5% → invert the
   line easing curve cubic-bezier(0.5, 0, 0.75, 0.5) at those y
   values for the matching x delays. Rounded to clean numbers. */


/* Mobile — stack vertically (inherits .path-timeline mobile base) so
   no extra rule needed beyond resetting the centred alignment. */


/* ── 3-column timeline modifier ────────────────────────────────────
   Used by /sessions/felt-hat-workshop (3 stages). Dot positions in
   3 cols: 16.67% / 50% / 83.33%. Line spans the first to last dot,
   delays redistributed so the three dots ignite evenly across the
   2400ms line draw. */
.path-timeline.path-timeline--3col {
  grid-template-columns: repeat(3, 1fr);
  max-width: 1000px;
  margin: 0 auto;
}
.path-timeline.path-timeline--3col::before {
  /* Line spans wider than the dot-centre math (16.67% / 83.33%) so
     it visibly extends through all three dots even when scroll
     progress is just under 1. */
  left: 8%;
  right: 8%;
}
.path-timeline.path-timeline--3col .path-step {
  text-align: center;
  padding: 0 0.75rem;
}
.path-timeline.path-timeline--3col .path-step::before {
  margin-left: auto;
  margin-right: auto;
}
.path-timeline.path-timeline--3col .path-desc {
  margin-left: auto;
  margin-right: auto;
}
.path-timeline.path-timeline--3col.visible .path-step:nth-child(1)::before { animation: dot-ignite 700ms cubic-bezier(0.4, 0, 0.2, 1) 350ms  forwards; }
.path-timeline.path-timeline--3col.visible .path-step:nth-child(2)::before { animation: dot-ignite 700ms cubic-bezier(0.4, 0, 0.2, 1) 1450ms forwards; }
.path-timeline.path-timeline--3col.visible .path-step:nth-child(3)::before { animation: dot-ignite 700ms cubic-bezier(0.4, 0, 0.2, 1) 2200ms forwards; }
/* If the last step is marked as future (e.g. the felt workshop's
   "Shape & finish" milestone, which shares its visual treatment
   with the Path to Now "What's next" dot), play the dashed-hollow
   fade-in + pulse instead of the regular solid ignite. Comes after
   the nth-child(3) rule above so it wins at equal specificity. */
.path-timeline.path-timeline--3col.visible .path-step--future::before {
  animation:
    dot-fadein 600ms ease 1700ms forwards,
    path-future-pulse 2.6s ease-in-out 2300ms infinite;
}
/* Scroll-driven thresholds, three steps spread across the active
   travel so each one has its own dwell before the next ignites.
   Used when the felt-how section pins on desktop. */
@media (min-width: 769px) {
  .path-timeline.path-timeline--3col.is-scroll-progress .path-step:nth-child(1) { --t: 0.05; }
  .path-timeline.path-timeline--3col.is-scroll-progress .path-step:nth-child(2) { --t: 0.42; }
  .path-timeline.path-timeline--3col.is-scroll-progress .path-step:nth-child(3) { --t: 0.78; }
}
@media (max-width: 768px) {
  .path-timeline.path-timeline--3col .path-step { text-align: left; }
}

@keyframes dot-ignite {
  0% {
    opacity: 0;
    transform: scale(0.4);
    box-shadow: 0 0 0 4px rgba(13, 13, 13, 0.7), 0 0 0 0 rgba(217, 122, 46, 0);
  }
  40% {
    opacity: 1;
    transform: scale(1.4);
    box-shadow: 0 0 0 4px rgba(13, 13, 13, 0.7), 0 0 30px 8px rgba(217, 122, 46, 0.95);
  }
  100% {
    opacity: 1;
    transform: scale(1);
    box-shadow: 0 0 0 4px rgba(13, 13, 13, 0.7), 0 0 14px 2px rgba(217, 122, 46, 0.55);
  }
}

/* Hover — dot grows + intensifies; overrides the post-ignite resting state.
   Gated to hover-capable devices: on touch, a tap fired the grow state
   briefly then snapped back, reading as jitter. */
@media (hover: hover) {
  .path-step:hover::before {
    transform: scale(1.25);
    box-shadow: 0 0 0 6px rgba(13, 13, 13, 0.7), 0 0 26px 4px rgba(217, 122, 46, 0.85);
  }
}

/* Future step — dashed hollow dot with a slow pulse */
.path-step--future::before {
  background: transparent;
  border: 2px dashed var(--bms-ember);
  box-sizing: border-box;
  width: 16px;
  height: 16px;
  box-shadow: 0 0 0 4px rgba(13, 13, 13, 0.7);
  animation: path-future-pulse 2.6s ease-in-out infinite;
}

/* Future dot — fade in last, then resume the pulse */
/* Future dot — fades in as the line approaches; then on line-arrival receives
   the crescendo: ramps up to a fast spin at the apex (35%), then takes the
   remaining 65% to gently decelerate. End state holds a slightly larger
   resting size (scale 1.2) so the dot feels more "alive" going forward.
   Per-keyframe timing functions are tuned so rotation velocity is continuous
   through the apex and matches the slow-spin's speed at the handoff. */
.path-timeline.visible .path-step--future::before {
  animation:
    dot-fadein 600ms ease 2395ms forwards,
    dot-future-crescendo 2000ms 2395ms forwards,
    dot-future-spin 10s linear 4395ms infinite;
}

@keyframes dot-future-crescendo {
  0% {
    transform: scale(1) rotate(0deg);
    box-shadow: 0 0 0 4px rgba(13, 13, 13, 0.7), 0 0 12px 0 rgba(217, 122, 46, 0.4);
    /* Ramp-up over 700ms. Ends at slope 2.69 → apex velocity 1.385°/ms
       (peak of the arc). Less peaky than before so the decel doesn't have
       to work as hard to settle. */
    animation-timing-function: cubic-bezier(0.4, 0, 0.75, 0.328);
  }
  35% {
    transform: scale(1.35) rotate(360deg);
    box-shadow: 0 0 0 4px rgba(13, 13, 13, 0.7), 0 0 32px 8px rgba(217, 122, 46, 0.95);
    /* Decel over 1300ms. Starts at slope 5 (matches apex velocity exactly),
       ends at slope 0.133 → 0.037°/ms (matches 10s slow-spin's 0.036°/ms).
       Curve distributes ~69% of progress in the first half (was 81% before),
       so the back half doesn't visually crawl. */
    animation-timing-function: cubic-bezier(0.1, 0.5, 0.4, 0.92);
  }
  100% {
    transform: scale(1.2) rotate(720deg);
    box-shadow: 0 0 0 4px rgba(13, 13, 13, 0.7), 0 0 16px 3px rgba(217, 122, 46, 0.6);
  }
}

@keyframes dot-future-spin {
  0%   { transform: scale(1.2) rotate(0deg); }   /* visually = rotate(720deg), no jump */
  100% { transform: scale(1.2) rotate(360deg); }
}

@keyframes dot-fadein {
  0%   { opacity: 0; transform: scale(0.4); }
  100% { opacity: 1; transform: scale(1); }
}

@keyframes path-future-pulse {
  0%, 100% { box-shadow: 0 0 0 4px rgba(13, 13, 13, 0.7), 0 0 10px 0 rgba(217, 122, 46, 0.3); }
  50%      { box-shadow: 0 0 0 4px rgba(13, 13, 13, 0.7), 0 0 18px 2px rgba(217, 122, 46, 0.65); }
}

.path-year {
  display: block;
  font-family: 'Staatliches', sans-serif;
  font-size: 0.85rem;
  letter-spacing: 0.2em;
  color: var(--bms-ember);
  text-align: center;
  margin-bottom: 0.5rem;
  transition: transform 280ms cubic-bezier(0.4, 0, 0.2, 1);
}

.path-title {
  position: relative;
  font-family: 'Staatliches', sans-serif;
  font-size: 1.25rem;
  color: var(--bms-paper);
  text-align: center;
  margin-bottom: 0.5rem;
  line-height: 1.1;
  transition: transform 280ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* Thin amber underline that slides in on hover */
.path-title::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: -5px;
  width: 28px;
  height: 1px;
  background: var(--bms-ember);
  transform: translateX(-50%) scaleX(0);
  transform-origin: center;
  transition: transform 320ms cubic-bezier(0.4, 0, 0.2, 1);
}

.path-desc {
  font-size: var(--type-small);
  color: var(--bms-birch);
  text-align: center;
  line-height: 1.5;
  max-width: 220px;
  margin: 0 auto;
  transition: transform 280ms cubic-bezier(0.4, 0, 0.2, 1);
}

.path-desc em {
  color: var(--bms-ember);
  font-style: italic;
  transition: text-shadow 320ms ease;
}

/* Hover — lift the text block, widen year letterspacing, slide in title underline,
   ignite the amber em phrase */
.path-step:hover .path-year     { transform: translateY(-3px); }
.path-step:hover .path-title    { transform: translateY(-3px); }
.path-step:hover .path-desc     { transform: translateY(-3px); }
.path-step:hover .path-title::after { transform: translateX(-50%) scaleX(1); }
.path-step:hover .path-desc em  { text-shadow: 0 0 14px rgba(217, 122, 46, 0.65); }

/* Pricing amount suffix (e.g. /wk) */
.pricing-amount-suffix {
  font-family: 'Staatliches', sans-serif;
  font-size: 1.1rem;
  letter-spacing: 0.02em;
  color: var(--bms-birch);
  margin-left: 0.15em;
  vertical-align: middle;
}

/* ===== LIVE TICKER ===== */


/* ── FILM SECTION ───────────────────────────────────────────────────────── */


.film-trigger {
  display: block;
  width: 100%;
  position: relative;
  aspect-ratio: 16 / 7;
  overflow: hidden;
  cursor: pointer;
  border: none;
  padding: 0;
  background: none;
}


@media (max-width: 640px) {
  .film-trigger { aspect-ratio: 16 / 9; }
  
  
}

/* Film modal */
.film-modal {
  position: fixed;
  inset: 0;
  z-index: 9000;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity 350ms ease;
}

.film-modal.is-open {
  opacity: 1;
  pointer-events: all;
}

.film-modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(21, 17, 13, 0.96);
  cursor: pointer;
}


.film-modal-close {
  position: absolute;
  top: -2.75rem;
  right: 0;
  font-size: 1.1rem;
  letter-spacing: 0.1em;
  color: var(--bms-birch);
  background: none;
  border: none;
  cursor: pointer;
  padding: 0.5rem;
  transition: color 200ms ease;
  font-family: 'Staatliches', sans-serif;
}

.film-modal-close:hover {
  color: var(--bms-paper);
}


/* ===== SECTIONS ===== */
.section {
  padding: var(--space-xl) var(--space-md);
  max-width: 1400px;
  margin: 0 auto;
}

.section-alt {
  background: var(--bms-smoke);
  padding: var(--space-xl) var(--space-md);
  position: relative;
}

/* Ember-tinted seam between sections. Sits at the top of every
   .section-alt and fades out at the edges — a soft 1px line of warmth
   that punctuates the transition without shouting. */
.section-alt::before {
  content: '';
  position: absolute;
  top: 0;
  left: 10%;
  right: 10%;
  height: 1px;
  background: linear-gradient(
    90deg,
    rgba(194, 98, 43, 0) 0%,
    rgba(194, 98, 43, 0.32) 50%,
    rgba(194, 98, 43, 0) 100%
  );
  pointer-events: none;
}
/* Path to Now already has its own custom background treatment — skip
   the divider so we don't double up. */
.section-alt.path-to-now-section::before { display: none; }

.section-alt .section-inner {
  max-width: 1400px;
  margin: 0 auto;
}

.section-label {
  font-family: 'Staatliches', sans-serif;
  font-size: var(--type-small);
  letter-spacing: 0.2em;
  color: var(--bms-ember);
  text-transform: uppercase;
  margin-bottom: 1rem;
}

.section-header {
  margin-bottom: var(--space-md);
}

/* ===== SESSIONS GRID ===== */
.sessions-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;
  background: rgba(237, 227, 210, 0.08);
}

.session-card {
  position: relative;
  aspect-ratio: 3/4;
  overflow: hidden;
  cursor: pointer;
  display: block;
  text-decoration: none;
  user-select: none;
  /* Warm placeholder gradient — reads as "image is warming up" during the
     ~100ms between grid-appear and image-fade-in. Beats a flat dark grey
     that looks like a broken tile. */
  background:
    linear-gradient(135deg, #2a1f17 0%, #1a130d 100%);
  isolation: isolate;
  /* Depth: ember-warm glow on hover + subtle lift. Feels like heat radiating
     from the card rather than a generic drop shadow. */
  transition:
    transform 0.4s cubic-bezier(0.16, 1, 0.3, 1),
    box-shadow 0.4s ease;
}
.session-card:hover {
  transform: translateY(-3px);
  box-shadow:
    0 0 36px rgba(194, 98, 43, 0.20),
    0 18px 40px rgba(0, 0, 0, 0.35);
}

.session-card-media {
  position: absolute;
  inset: 0;
  overflow: hidden;
}

.session-card-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  opacity: 0;
  transform: scale(1.04);
  transition:
    opacity 400ms ease-out,
    transform 900ms cubic-bezier(0.16, 1, 0.3, 1);
  will-change: opacity, transform;
}

/* Reveal the photo on ARRIVAL (.session-card.visible, added by the scroll
   observer), not on image load. Images preload ~3 viewports ahead, so a
   load-gated reveal finishes long before the user scrolls here — leaving a
   static grid. Gating on arrival develops each photo in as you reach it.
   The container itself stays solid (see the min-width:769px block below),
   so only the photo fades — no muddy container+image double-fade. */
.session-card.visible .session-card-media img {
  opacity: 1;
  transform: scale(1);
}

.session-card:hover .session-card-media img {
  transform: scale(1.08);
  transition: transform 1400ms cubic-bezier(0.16, 1, 0.3, 1);
}

/* ── Desktop session-card reveal ──────────────────────────────────────
   The card keeps `fade-up` only as the scroll-observer hook (so it gets
   `.visible`, which drives the image reveal above). We neutralise the
   container's OWN opacity/translate here so nothing fades but the photo —
   fading the container AND the image together read as a muddy double-fade.
   `:not(:hover)` leaves the hover lift untouched, and we restore the
   snappy hover transition (fade-up otherwise stretches it to 0.9s). */
@media (min-width: 769px) {
  .sessions-grid > .session-card.fade-up {
    opacity: 1;
    transition:
      transform 0.4s cubic-bezier(0.16, 1, 0.3, 1),
      box-shadow 0.4s ease;
  }
  .sessions-grid > .session-card.fade-up:not(:hover) {
    transform: none;
  }
}

.session-card-overlay {
  position: absolute;
  inset: 0;
  /* Light overlay at rest so the imagery reads bright. Gradient is
     tight to the bottom (where the title + meta sit) and fades to
     fully transparent up high. On hover the overlay deepens — see
     the :hover rule below — to give the title block extra contrast
     when the user is engaging with the card. */
  background: linear-gradient(
    to top,
    rgba(21, 17, 13, 0.92) 0%,
    rgba(21, 17, 13, 0.78) 20%,
    rgba(21, 17, 13, 0.45) 42%,
    rgba(21, 17, 13, 0) 70%
  );
  opacity: 0.7;
  transition: opacity var(--transition-mid);
  pointer-events: none;
  z-index: 1;
}

/* Hover: ramp the overlay to full strength so the title reads crisp
   while the rest of the card pulls the eye in. */
.session-card:hover .session-card-overlay {
  opacity: 1;
}

.session-card::after {
  content: '';
  position: absolute;
  inset: 0;
  border: 1px solid transparent;
  transition: border-color var(--transition-mid);
  pointer-events: none;
  z-index: 2;
}

.session-card:hover::after {
  border-color: rgba(217, 122, 46, 0.4);
}

.session-card-content {
  z-index: 3;
}

/* Facility list inside bulletin board */


/* Session card — flip variant */
.session-card-inner {
  position: relative;
  width: 100%;
  height: 100%;
  perspective: 1200px;
  z-index: 1;
}

.session-card-face {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}


/* Warm-grade the Social Saturday image to match the rest of the gallery */


/* Disable flip on touch — show back on tap instead */
@media (hover: none) {
  
  
  

  /* Disable image hover scale effects on touch devices. A tap on a
     touch screen is briefly treated as a hover, which would otherwise
     fire the scale transform — the image grows briefly and snaps back,
     reading as jittery/random sizing. !important so these override
     the base hover rules cleanly. */
  .offerings-img:hover img,
  .manifesto-image:hover img,
  .why-media:hover img,
  .photo-grid-item:hover img,
  .savu-gallery-cell:hover img,
  .our-space-gallery-item:hover img,
  .savu-teaser:hover .savu-teaser-bg,
  .press-logos img:hover,
  .site-logo:hover {
    transform: none !important;
  }

  /* Pricing grid + cells: touch-as-hover briefly fires the lift +
     glow + background swap + underline-draw, reading as jittery
     random visual changes. Each hover state below is reset to its
     BASE values so a tap is clean. */
  .pricing-card:hover {
    transform: none !important;
    box-shadow: none !important;
    /* Pin to the translucent base on touch so cards keep the
       Declan-photo ground showing through (no hover background swap). */
    background: rgba(42, 35, 28, 0.7) !important;
  }
  .pricing-card.is-featured:hover {
    /* Featured cards have a layered gradient + smoke background;
       restore that on tap rather than the plain hover ash. */
    background: linear-gradient(180deg, rgba(217, 122, 46, 0.08), rgba(217, 122, 46, 0) 60%), var(--bms-smoke) !important;
  }
  .pricing-card:hover::after {
    /* Ember underline scaleX(0→1) draw — keep collapsed on touch. */
    transform: scaleX(0) !important;
  }
  
  

  /* Private Bookings ("Your people, your vibe") and Fine Print
     triggers also fire ember border + lift on hover. Same touch-as-
     hover problem — kill on touch so the tap is clean. Pin colors
     to the new glass base values (translucent + ember border). */
  .pricing-private-cta:hover {
    border-color: rgba(237, 227, 210, 0.22) !important;
    transform: none !important;
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.05),
      0 18px 40px -16px rgba(0, 0, 0, 0.45),
      0 0 32px -8px rgba(217, 122, 46, 0.14) !important;
  }
  .pricing-fineprint-trigger:hover {
    background: rgba(42, 35, 28, 0.7) !important;
    border-color: rgba(237, 227, 210, 0.18) !important;
    transform: none !important;
  }
  .pricing-fineprint-trigger:hover .pricing-fineprint-arrow {
    transform: none !important;
  }

  /* Show the tap hint on every interactive pricing card on touch.
     Sits below the price block — ember-coloured Staatliches small-caps
     with a → arrow so users know the card opens a modal on tap. */
  .pricing-card[data-pricing] .pricing-card-tap {
    display: block;
    opacity: 1;
    transform: none;
  }

  /* Event cards ("Mark the calendar" section): same touch-as-hover
     problem. Tapping briefly fires a lift + border colour change +
     inner image scale. Pin to base state on touch. */
  .event-card:hover {
    transform: none !important;
    border-color: rgba(217, 122, 46, 0.18) !important;
    box-shadow: none !important;
  }
  .event-card--featured:hover {
    border-color: rgba(217, 122, 46, 0.35) !important;
  }
  .event-card:hover .event-card-image img {
    transform: none !important;
  }
  /* The "BOOK TICKETS →" CTA scales + flips colour on hover; on touch
     that fires on tap and reads as a jitter. Pin it to its resting
     ember state. */
  .event-cta:hover {
    color: var(--bms-ember) !important;
    transform: none !important;
  }
  /* Carousel nav arrows are display:none on mobile, but neutralise their
     hover too in case they're ever shown on a touch device. */
  .events-nav-btn:hover {
    transform: none !important;
    background: none !important;
    color: inherit !important;
  }

  /* Session cards: same touch-as-hover problem. Tapping a card briefly
     fires the image scale, overlay darken, CTA reveal, hover panel
     reveal, orange border, and (on flip cards) the inner 3D rotation.
     Each hover rule below is reset to its BASE state so a tap reads
     as a clean state change with no pre-animation jitter and no
     surprise full-card overlays. */
  .session-card:hover {
    transform: none !important;
    box-shadow: none !important;
  }
  .session-card:hover .session-card-media img {
    transform: none !important;
  }
  /* On touch devices there's no hover-darken — the user can't move
     a cursor over the card to bring the overlay up. So lock the
     overlay at full strength always so the title + meta read clearly
     against the image. */
  .session-card-overlay {
    opacity: 1 !important;
  }
  .session-card:hover .session-card-overlay {
    opacity: 1 !important;
  }
  .session-card:hover::after {
    /* Kill the orange ember border that appears on hover. */
    border-color: transparent !important;
  }
  .session-card:hover .session-card-cta {
    /* CTA is always visible on mobile (base mobile rule below this
       file sets .session-card-cta { opacity: 1; transform: none }).
     Pin it at that visible state on tap so the touch-as-hover state
       doesn't briefly flip it back to translateY(0) animation. */
    opacity: 1 !important;
    transform: none !important;
  }
  
  .session-card:hover .session-card-play-icon {
    transform: none !important;
  }
  
}

/* Extra link pills (Aufguss) */


.session-card-content {
  position: absolute;
  inset: 0;
  padding: 1.5rem;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}

.session-card-num {
  display: none;
}

.session-card-title {
  font-family: 'Staatliches', sans-serif;
  font-size: 1.8rem;
  line-height: 1;
  color: var(--bms-paper);
  margin-bottom: 0.5rem;
}

.session-card-meta {
  font-size: var(--type-small);
  color: var(--bms-birch);
  line-height: 1.45;
  min-height: 2.55rem; /* 2-line baseline keeps titles aligned (max meta is now 2 lines) */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.session-card-cta {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  /* Keep "More Info →" on a single line — the arrow was wrapping to
     a second line when the card got narrow. */
  white-space: nowrap;
  font-family: 'Staatliches', sans-serif;
  font-size: 1rem;
  color: var(--bms-ember);
  margin-top: 0.75rem;
  opacity: 0;
  transform: translateY(4px);
  transition: all var(--transition-fast);
}

.session-card:hover .session-card-cta {
  opacity: 1;
  transform: translateY(0);
}


/* ── Session card: play icon (visual indicator only, whole card is clickable) */
.session-card-play-icon {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 8;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  border: 2px solid rgba(237, 227, 210, 0.65);
  background: rgba(21, 17, 13, 0.4);
  color: var(--bms-paper);
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  transition: background 0.25s, border-color 0.25s, transform 0.25s;
  backdrop-filter: blur(4px);
}
.session-card-play-icon svg {
  margin-left: 3px;
}
.session-card:hover .session-card-play-icon {
  background: rgba(217, 122, 46, 0.7);
  border-color: var(--bms-ember);
  transform: translate(-50%, -50%) scale(1.1);
}

/* ── Session modal ──────────────────────────────────────────────────────── */
.session-modal {
  position: fixed;
  inset: 0;
  z-index: 9000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  /* No opacity transition on the modal itself — the overlay and box each
     have their own choreographed entrances. This avoids the "blur lags
     the modal" effect that happens when the parent opacity is ramping
     up while child elements try to transition independently. */
}
.session-modal[hidden] { display: none; }
/* Hash-restored modals (refresh, back-button, deep links) skip every
   transition — they're already in their final state when the page paints. */
.session-modal.is-restored .session-modal-overlay,
.session-modal.is-restored .session-modal-box {
  transition: none !important;
  animation: none !important;
}
.session-modal.is-restored .session-modal-overlay {
  background: rgba(21, 17, 13, 0.82);
  backdrop-filter: blur(10px) saturate(120%);
  -webkit-backdrop-filter: blur(10px) saturate(120%);
}
.session-modal.is-restored .session-modal-box {
  opacity: 1;
  transform: none;
}

.session-modal-overlay {
  position: absolute;
  inset: 0;
  cursor: pointer;
  /* Two-speed entrance: the dim hits fast (180ms) so the underlying page
     text is obscured almost immediately — no awkward "I can still read
     the page behind the modal" moment. The blur takes its time (340ms)
     because it's atmospheric, not functional. Final dim is slightly
     deeper too (0.86) for stronger separation. */
  background: rgba(21, 17, 13, 0);
  backdrop-filter: blur(0px) saturate(100%);
  -webkit-backdrop-filter: blur(0px) saturate(100%);
  transition:
    background 0.18s ease-out,
    backdrop-filter 0.34s cubic-bezier(0.22, 1, 0.36, 1),
    -webkit-backdrop-filter 0.34s cubic-bezier(0.22, 1, 0.36, 1);
}
.session-modal.is-open .session-modal-overlay {
  background: rgba(21, 17, 13, 0.86);
  backdrop-filter: blur(12px) saturate(125%);
  -webkit-backdrop-filter: blur(12px) saturate(125%);
}

.session-modal-box {
  position: relative;
  z-index: 1;
  background: var(--bms-smoke);
  width: min(94vw, 800px);
  max-height: 90vh;
  overflow: hidden; /* clips border-radius; scrolling is on box-inner */
  border-radius: 4px;
  display: flex;
  flex-direction: column;
  /* Layered shadow: tight dark base + soft far-cast for depth, plus a
     very subtle ember rim-light around the modal so it feels lit by the
     sauna behind it rather than floating on a void. */
  box-shadow:
    0 0 0 1px rgba(194, 98, 43, 0.08),
    0 32px 80px rgba(0, 0, 0, 0.7),
    0 8px 24px rgba(0, 0, 0, 0.5);
  /* Entrance: a soft iOS-style materialise. Slight scale + a brief focus-
     pull blur that resolves as it settles, rather than a hard pop-in.
     The eye has time to take in the contents because the box arrives
     softened, then sharpens — the same way your eyes refocus on something
     coming towards you. Total duration ~340ms feels snappy but never harsh. */
  opacity: 0;
  transform: scale(0.96);
  filter: blur(6px);
  transition:
    opacity 0.32s cubic-bezier(0.22, 1, 0.36, 1) 0.05s,
    transform 0.38s cubic-bezier(0.22, 1, 0.36, 1) 0.05s,
    filter 0.28s ease-out 0.05s;
  transform-origin: center 55%;
}
.session-modal.is-open .session-modal-box {
  opacity: 1;
  transform: none;
  filter: blur(0);
}

/* Close button floats over the hero image */
.session-modal-close {
  position: absolute;
  top: 1rem;
  right: 1rem;
  z-index: 10;
  /* 44px minimum tap target (WCAG 2.5.8) — was 36px. */
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid rgba(237, 227, 210, 0.35);
  background: rgba(10, 10, 10, 0.6);
  color: var(--bms-paper);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 0.2s, border-color 0.2s;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.session-modal-close:hover {
  background: rgba(10, 10, 10, 0.9);
  border-color: rgba(237, 227, 210, 0.6);
}

/* Inner scroll container */
/* ── Scroll affordance (gradient fade + chevron) ────────────────────────
   Shown when the modal content overflows and the user hasn't scrolled to
   the bottom. main.js toggles .has-scroll-more on the box. The gradient
   uses the modal's own bg colour so it reads as content fading out, and
   the chevron carries a gentle bob so the eye notices it without it
   feeling like a banner. */
.session-modal-box::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 64px;
  background: linear-gradient(
    to bottom,
    rgba(40, 33, 26, 0) 0%,
    var(--bms-smoke) 75%
  );
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.3s ease;
  z-index: 2;
}
.session-modal-scroll-cue {
  position: absolute;
  left: 50%;
  bottom: 0.9rem;
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--bms-ember);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.3s ease;
  z-index: 3;
  /* The transform on the keyframe handles both centring and bob — keeping
     translateX(-50%) inside the animation so it doesn't fight with it. */
  animation: scroll-cue-bob 1.8s ease-in-out infinite;
}
.session-modal-box.has-scroll-more::after,
.session-modal-box.has-scroll-more .session-modal-scroll-cue {
  opacity: 1;
}
@keyframes scroll-cue-bob {
  0%, 100% { transform: translate(-50%, 0); }
  50%      { transform: translate(-50%, 4px); }
}
@media (prefers-reduced-motion: reduce) {
  .session-modal-scroll-cue { animation: none; transform: translateX(-50%); }
}

.session-modal-box-inner {
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  max-height: 90vh;
  /* Custom scrollbar — Chrome / Edge / Safari */
  scrollbar-width: thin;
  scrollbar-color: var(--bms-ember) rgba(237, 227, 210, 0.08);
}
.session-modal-box-inner::-webkit-scrollbar {
  width: 5px;
}
.session-modal-box-inner::-webkit-scrollbar-track {
  background: rgba(237, 227, 210, 0.05);
}
.session-modal-box-inner::-webkit-scrollbar-thumb {
  background: var(--bms-ember);
  border-radius: 3px;
}
.session-modal-box-inner::-webkit-scrollbar-thumb:hover {
  background: #d4743a;
}

/* Media header — image (flush to top edge, full width) */
.session-modal-media--img {
  width: 100%;
  height: 300px;
  overflow: hidden;
  flex-shrink: 0;
}
.session-modal-media--img img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Media header — video */
.session-modal-media--video {
  width: 100%;
  background: #000;
  flex-shrink: 0;
}
.session-modal-media--video video {
  width: 100%;
  max-height: 52vh;
  display: block;
}

/* Text body */
.session-modal-text {
  padding: 2rem 2rem 2.5rem;
}
.session-modal-num {
  display: none;
}
.session-modal-title {
  font-family: 'Staatliches', sans-serif;
  font-size: clamp(1.8rem, 4vw, 2.6rem);
  letter-spacing: 0.05em;
  color: var(--bms-paper);
  line-height: 1;
  padding-bottom: 1rem;
  margin-bottom: 1.25rem;
  border-bottom: 1px solid var(--bms-ember);
}
/* Featured date row for event modals (e.g. Orphans Christmas) — sits just
   under the title's ember divider so the when/where reads at a glance. */
.event-modal-date {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  margin: -0.4rem 0 1.1rem;
}
.event-modal-date-big {
  font-family: 'Staatliches', sans-serif;
  font-size: clamp(1.7rem, 4.5vw, 2.3rem);
  line-height: 1;
  letter-spacing: 0.04em;
  color: var(--bms-ember);
}
.event-modal-date-sub {
  font-size: 0.78rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--bms-birch);
}

.session-modal-body {
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-size: 1.1rem;
  line-height: 1.65;
  color: var(--bms-birch);
}
.session-modal-body p { margin-bottom: 1rem; }

.modal-list {
  list-style: none;
  padding: 0;
  margin: 0.75rem 0 1rem;
}
.modal-list li {
  padding: 0.45rem 0;
  border-bottom: 1px solid rgba(237, 227, 210, 0.1);
  font-size: 1rem;
  color: var(--bms-birch);
}
.modal-list li:last-child { border-bottom: none; }
.modal-list strong {
  color: var(--bms-paper);
  margin-right: 0.5em;
  font-family: 'Staatliches', sans-serif;
  font-size: 1rem;
  letter-spacing: 0.06em;
}


.modal-important {
  border-left: 3px solid var(--bms-ember);
  padding: 0.6rem 0.9rem;
  margin: 1rem 0;
  background: rgba(194, 98, 43, 0.08);
  font-size: 0.95rem;
  line-height: 1.55;
  color: var(--bms-birch);
}

/* Coloured section boxes (A few points / Etiquette) */
.modal-section {
  background: rgba(237, 227, 210, 0.04);
  border: 1px solid rgba(237, 227, 210, 0.09);
  border-radius: 3px;
  padding: 1.1rem 1.25rem 0.5rem;
  margin: 1.25rem 0;
}
.modal-section-head {
  font-family: 'Staatliches', sans-serif;
  font-size: 1rem;
  letter-spacing: 0.12em;
  color: var(--bms-ember);
  text-transform: uppercase;
  margin: 0 0 0.75rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid rgba(194, 98, 43, 0.25);
}

/* Eyebrow labels inside list items */
.modal-list-eyebrow {
  display: block;
  font-family: 'Staatliches', sans-serif;
  font-size: 0.78rem;
  letter-spacing: 0.1em;
  color: var(--bms-ember);
  margin-bottom: 0.1rem;
  font-style: normal;
  opacity: 0.85;
}
/* Remove the bottom border on the last item inside a section box */
.modal-section .modal-list li:last-child {
  border-bottom: none;
  padding-bottom: 0.25rem;
}


.session-modal-actions {
  margin-top: 1.75rem;
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.session-modal-actions--stack {
  flex-direction: column;
  gap: 0.6rem;
}

.session-modal-actions--stack .btn {
  width: 100%;
  text-align: center;
  justify-content: center;
}

/* Loading state for the Sweat-Work submit button: inline spinner + label. */
.btn.is-loading {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.55em;
  cursor: progress;
  opacity: 0.85;
}
.btn-spinner {
  width: 1em;
  height: 1em;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-top-color: transparent;
  animation: btn-spin 0.6s linear infinite;
  flex: 0 0 auto;
}
@keyframes btn-spin {
  to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
  .btn-spinner { animation-duration: 1.5s; }
}

/* ── Enquiry form (Sweat-Work) inside the shared session modal ─────────── */
.bms-form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  margin-top: 0.5rem;
}
.bms-form-row {
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
}
.bms-form-row > .bms-field {
  flex: 1 1 200px;
}
.bms-field {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}
.bms-field label,
.bms-field legend {
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bms-birch);
  font-weight: 600;
}
.bms-field .req {
  color: var(--bms-ember);
  margin-left: 0.15em;
}
.bms-field input[type="text"],
.bms-field input[type="email"],
.bms-field input[type="tel"] {
  width: 100%;
  padding: 0.7rem 0.85rem;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: var(--radius-sm);
  color: var(--bms-paper);
  font-size: 1rem;
  font-family: inherit;
  transition: border-color 0.2s ease, background 0.2s ease;
}
.bms-field input:focus {
  outline: none;
  border-color: var(--bms-ember);
  background: rgba(255, 255, 255, 0.07);
}
.bms-field--check {
  border: 0;
  padding: 0;
  margin: 0;
  min-width: 0;
}
.bms-field--check legend {
  margin-bottom: 0.5rem;
  padding: 0;
}
.bms-check {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  text-transform: none;
  letter-spacing: normal;
  font-weight: 400;
  color: var(--bms-paper);
  font-size: 0.95rem;
  cursor: pointer;
  padding: 0.25rem 0;
}
.bms-check input {
  width: 1.1rem;
  height: 1.1rem;
  accent-color: var(--bms-ember);
  cursor: pointer;
}
.bms-form-status {
  font-size: 0.9rem;
  margin: 0;
  padding: 0.6rem 0.75rem;
  border-radius: var(--radius-sm);
}
.bms-form-status[data-kind="error"] {
  color: #ffd2c2;
  background: rgba(217, 122, 46, 0.16);
}
.bms-form-status[data-kind="success"] {
  color: #cfe6d3;
  background: rgba(100, 175, 130, 0.16);
}

/* Confirmation panel shown in place of the form after a successful submit. */
.bms-form-success {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0.85rem;
  padding: 1.5rem 0.5rem 0.5rem;
}
.bms-form-success-icon {
  width: 3rem;
  height: 3rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: rgba(100, 175, 130, 0.18);
  color: #8fd6a0;
  font-size: 1.5rem;
  line-height: 1;
}
.bms-form-success h3 {
  font-family: 'Staatliches', sans-serif;
  font-size: clamp(1.4rem, 4vw, 1.9rem);
  letter-spacing: 0.03em;
  color: var(--bms-paper);
  margin: 0;
}
.bms-form-success p {
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-size: 1.1rem;
  line-height: 1.6;
  color: var(--bms-birch);
  margin: 0;
  max-width: 34ch;
}
.bms-form-success .btn {
  margin-top: 0.5rem;
}

/* "Gift this pass →" entry inside a pass modal. Designed to read as a
   real secondary CTA rather than a footnote: full-width pill with an
   ember-tinted outline + soft glow so it's clearly tappable, while
   staying visually subordinate to the solid primary BUY buttons above
   it. Includes a small "OR" divider so the relationship to the BUY
   stack is obvious. Replaces the standalone gift sub-row that doesn't
   render on mobile. */
.pricing-modal-gift-link {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  width: 100%;
  margin: 1rem 0 0;
  padding: 0.85rem 1rem;
  background: rgba(194, 98, 43, 0.08);
  border: 1px solid rgba(194, 98, 43, 0.55);
  border-radius: var(--radius-md, 6px);
  font-family: 'Staatliches', sans-serif;
  font-size: 1rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--bms-ember);
  cursor: pointer;
  position: relative;
  transition: background 200ms ease, border-color 200ms ease, color 200ms ease, transform 160ms ease;
}
.pricing-modal-gift-link::before {
  content: "OR";
  position: absolute;
  top: -0.55rem;
  left: 50%;
  transform: translateX(-50%);
  padding: 0 0.55rem;
  background: var(--bms-ink);
  font-size: 0.65rem;
  letter-spacing: 0.22em;
  color: var(--bms-birch);
  opacity: 0.65;
}
.pricing-modal-gift-link:hover,
.pricing-modal-gift-link:focus-visible {
  background: rgba(194, 98, 43, 0.16);
  border-color: var(--bms-ember);
  color: var(--bms-paper);
  outline: none;
}
.pricing-modal-gift-link:active {
  transform: translateY(1px);
}

/* ===== MANIFESTO ===== */
.manifesto {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-lg);
  align-items: center;
}


.manifesto-negation {
  border-left: 2px solid var(--bms-ember);
  padding-left: 1.75rem;
  margin: 2.25rem 0;
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-style: italic;
  /* Scales from 1.4rem on laptops up to 1.7rem on iMac. Cap held at 1.7rem
     (not higher) so "No treatments. No spa menus. No fluffy extras." lands
     on a single line in the iMac text column (~593px wide at 1.2fr 1fr).
     Still reads clearly above body copy (1.15rem) without competing with
     the headline. */
  font-size: clamp(1.4rem, 1.6vw, 1.7rem);
  line-height: 1.4;
  color: var(--bms-paper);
}

.manifesto-image {
  position: relative;
  aspect-ratio: 4/5;
  overflow: hidden;
  border-radius: var(--radius-md);
  background: var(--bms-ash);
}

.manifesto-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  transition: transform 1200ms cubic-bezier(0.16, 1, 0.3, 1);
}

.manifesto-image:hover img { transform: scale(1.04); }

/* ===== THE WHY — landscape image, text below ===== */
.the-why {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-md);
  border-top: 1px solid rgba(217, 122, 46, 0.22);
}

.why-media {
  position: relative;
  aspect-ratio: 16/9;
  overflow: hidden;
  border-radius: var(--radius-md);
  background: var(--bms-ash);
}

.why-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 50%;
  transition: transform 1400ms cubic-bezier(0.16, 1, 0.3, 1);
}

/* Mobile crop — lift the image to reveal more of the rocks and the
   ladle pour. !important because the component sets an inline
   object-position style for the desktop crop. */
@media (max-width: 768px) {
  .the-why:not(.first-visit) .why-media img {
    /* Horizontal centre + slightly lifted vertical crop so the ladle
       pour + rocks sit on the optical centre of the 4/5 portrait. */
    object-position: 50% 60% !important;
  }

  /* ALL mobile customisations for the why-section are scoped to the
     On Purpose panel only — :not(.first-visit). The Invitation panel
     is left fully untouched by mobile overrides so it renders with
     its original pre-formatting layout (boxed 16/9 photo, original
     headline/body sizing). */
  .section.the-why:not(.first-visit) {
    gap: 1.75rem;
    padding: var(--space-lg) 0;
    /* Close the gap to the path-to-now section above. */
    padding-top: 0;
  }
  .section.the-why:not(.first-visit) .why-text {
    gap: 1.2rem;
    padding-left: var(--space-md);
    padding-right: var(--space-md);
  }
  .section.the-why:not(.first-visit) .why-headline {
    font-size: clamp(1.85rem, 7vw, 2.5rem);
    line-height: 1.1;
    margin-bottom: 0.25rem;
  }
  .section.the-why:not(.first-visit) .why-text p {
    font-size: 1.1rem;
    line-height: 1.65;
    max-width: none;
  }
  .section.the-why:not(.first-visit) .manifesto-negation {
    font-size: 1.15rem;
    line-height: 1.45;
    margin: 0.5rem 0;
    padding-left: 1rem;
  }
  .section.the-why:not(.first-visit) .why-text .why-tag {
    font-size: 1.2rem;
    line-height: 1.5;
    margin-top: 1rem;
  }
  .section.the-why:not(.first-visit) .why-media {
    aspect-ratio: 4 / 5;
    border-radius: 0;
  }
  .section.the-why:not(.first-visit) .why-media-caption {
    left: var(--space-md);
    bottom: 1.5rem;
  }

  /* Trim the path-to-now bottom padding so the On Purpose image butts
     against it without a tall gap. */
  .path-to-now-pin-wrap > .section-alt.path-to-now-section {
    padding-bottom: var(--space-md);
  }

  /* On Purpose + Invitation share the same scroll-tied cascade as the
     Sit Sweat blocks: staggered smooth fade-up triggered late so the
     animation plays as the user reaches the section. Reuses the
     offerings-rise keyframes for visual parity. Specificity
     (.section.the-why .fade-up) wins over the global mobile
     .fade-up { transition-delay: 0ms } reset. */
  .section.the-why .fade-up {
    opacity: 0;
    will-change: opacity, transform;
  }
  .section.the-why .fade-up.visible {
    animation: offerings-rise 1.1s cubic-bezier(0.22, 0.61, 0.36, 1) both;
    animation-delay: var(--card-delay, 0ms);
    will-change: auto;
  }

  /* Session cards + pricing cards: same scroll-tied cascade so the
     individual cards rise into view in sequence as the user reaches
     the section. Auto-stagger JS adds fade-up + --card-delay to each
     direct child of .sessions-grid and .pricing-grid, and the late
     observer (-10% rootMargin) wires up the trigger.

     The desktop grids use a 1px cream gap as dividers (parent bg
     shows through), which looked like "broken empty boxes" during
     the per-card fade-up on mobile because the cream divider colour
     filled the card area while cards were invisible mid-animation.
     On mobile we drop the cream-divider treatment: transparent grid
     bg + a small dark gap so cards fade up against the section bg,
     not a cream lattice. */
  .sessions-grid,
  .pricing-grid {
    background: transparent !important;
    gap: 0.5rem !important;
    border: none !important;
  }
  /* Session cards: keep the card CONTAINER solid on mobile — never fade
     it via opacity. Each card has a dark placeholder gradient behind its
     image, so a card-level opacity fade shows that dark gradient first
     and the photo a beat later — the "black flash". The card stays put;
     the reveal lives entirely on the IMAGE (below). */
  .sessions-grid.fade-up,
  .sessions-grid > .session-card,
  .sessions-grid > .fade-up {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
    animation: none !important;
  }

  /* Image reveal gated on ARRIVAL, not on load. The image preload runs
     ~3 viewports ahead, so a load-gated fade (the default .loaded rule)
     finishes long before the user scrolls here — they arrive to static
     cards. Instead hold the photo hidden until the card gets .visible
     (added by the scroll observer as the card enters view), then develop
     it in with a soft scale. Because the photo is already downloaded,
     this is a clean image reveal — and since the card itself never
     changes opacity, the dark placeholder never flashes on its own. */
  .session-card-media img,
  .session-card-media.loaded img,
  .session-card-media img.loaded {
    opacity: 0;
    transform: scale(1.06);
    transition:
      opacity 0.7s ease,
      transform 1.1s cubic-bezier(0.16, 1, 0.3, 1);
  }
  .session-card.visible .session-card-media img {
    opacity: 1 !important;
    transform: scale(1) !important;
  }

  /* Pricing cards rise in with TRANSFORM ONLY — opacity stays pinned at 1.
     These cards carry no image and sit directly on the section background,
     so an opacity fade (the old offerings-rise) blinked the card out the
     instant it was hidden and faded it back in — the "flash". With opacity
     locked at 1 there is nothing to flash: the card is always painted, it
     just slides the last few pixels into place as it scrolls into view.
     Each card triggers .visible individually as it crosses the viewport,
     so we skip the --card-delay stagger and keep the slide short and clean. */
  .pricing-grid > .fade-up {
    opacity: 1 !important;
    transform: translateY(16px);
    transition: transform 0.55s cubic-bezier(0.22, 0.61, 0.36, 1);
    transition-delay: 0ms;
    animation: none !important;
    will-change: transform;
  }
  .pricing-grid > .fade-up.visible {
    transform: translateY(0);
    will-change: auto;
  }
  /* Session cards: container stays solid (handled above); the reveal lives
     on the image. Keep the per-card slide off the container entirely. */
  .sessions-grid > .fade-up.visible {
    animation: offerings-rise 0.55s cubic-bezier(0.22, 0.61, 0.36, 1) both;
    animation-delay: 0ms;
    will-change: auto;
  }

  /* ── Invitation panel (first-visit) ─────────────────────────────
     Same text formatting + margins as the On Purpose panel above —
     the section's horizontal padding drops to 0 so the inner blocks
     control their own padding-left/right, headline/body/tag sizes
     match the mobile On Purpose rule. The IMAGE keeps its boxed
     4/3 + max-height treatment (not full-bleed like On Purpose). */
  .section.the-why.first-visit {
    padding: var(--space-lg) 0;
    gap: 1.75rem;
  }
  .section.the-why.first-visit .why-text {
    gap: 1.2rem;
    padding-left: var(--space-md);
    padding-right: var(--space-md);
  }
  .section.the-why.first-visit .why-headline {
    font-size: clamp(1.85rem, 7vw, 2.5rem);
    line-height: 1.1;
    margin-bottom: 0.25rem;
  }
  .section.the-why.first-visit .why-text p {
    font-size: 1.1rem;
    line-height: 1.65;
    max-width: none;
  }
  .section.the-why.first-visit .why-text .why-tag {
    font-size: 1.2rem;
    line-height: 1.5;
    margin-top: 1rem;
  }
  /* Image stays boxed (not full-bleed). Aspect 4/3 with a 280px
     height cap; justify-self centres the smaller-than-grid-track box
     on the column's optical midline. */
  .section.the-why.first-visit .why-media {
    aspect-ratio: 4 / 3;
    max-height: 280px;
    width: auto;
    justify-self: center;
    /* Keep horizontal padding equal to .why-text so the image left/
       right edges align with the body copy column above. */
    margin-left: var(--space-md);
    margin-right: var(--space-md);
  }
  /* CTAs styled like the pricing-card "MORE INFO →" hint: small
     ember Staatliches small-caps, no button chrome. Reads as quiet
     wayfinding links rather than overstating filled buttons. */
  .section.the-why.first-visit .first-visit-actions--mobile {
    gap: 1.25rem;
    margin-top: 0.5rem;
    padding-left: var(--space-md);
    padding-right: var(--space-md);
  }
  .section.the-why.first-visit .first-visit-actions--mobile .btn {
    background: none !important;
    border: none !important;
    padding: 0 !important;
    box-shadow: none !important;
    font-family: 'Staatliches', sans-serif !important;
    font-style: normal;
    font-weight: 400;
    font-size: 0.78rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--bms-ember) !important;
    text-decoration: none;
    line-height: 1.3;
    white-space: nowrap;
  }
  /* Trailing arrow on the link so it reads as wayfinding. Pseudo
     keeps the HTML clean — no separate desktop/mobile spans. */
  .section.the-why.first-visit .first-visit-actions--mobile .btn::after {
    content: "\00a0→";
  }
  /* Kill the .btn::before paper-fill pseudo so the link reads as
     pure text, not a hidden button waiting to fill. */
  .section.the-why.first-visit .first-visit-actions--mobile .btn::before {
    display: none;
  }
}
/* END @media (max-width: 768px) — all rules below are global. */

.why-media:hover img { transform: scale(1.03); }

.why-media::after {
  content: '';
  position: absolute;
  inset: 0;
  /* Bottom fade kept only for the Purpose section caption ("Heat ·
     Steam · Ventilation") legibility — barely any darkening, and
     only on the bottom ~12% of the image. Invitation image (no
     caption) reads at full brightness through this gradient since
     the visible darkening sits below the bottom edge of the photo. */
  background: linear-gradient(to bottom, transparent 88%, rgba(21,17,13,0.28) 100%);
  pointer-events: none;
}

.why-media-caption {
  position: absolute;
  left: 1.5rem;
  bottom: 1.25rem;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  /* Soft ink chip behind the caption so the small ember eyebrow holds
     WCAG AA over any photo, instead of sitting raw on the image. */
  padding: 0.55rem 0.8rem;
  background: rgba(13, 10, 7, 0.55);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  border-radius: var(--radius-sm, 6px);
}

.why-media-caption-eyebrow {
  font-family: 'Staatliches', sans-serif;
  font-size: 0.75rem;
  letter-spacing: 0.2em;
  color: var(--bms-ember);
  text-transform: uppercase;
}

.why-media-caption-text {
  font-family: 'Staatliches', sans-serif;
  font-size: 1.4rem;
  letter-spacing: 0.05em;
  color: var(--bms-paper);
}

.why-text {
  max-width: 760px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.why-headline {
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-style: italic;
  font-weight: 400;
  font-size: clamp(2rem, 4vw, 3rem);
  color: var(--bms-paper);
  margin-bottom: 0.5rem;
  line-height: 1.15;
}

.why-text p {
  font-size: 1.15rem;
  line-height: 1.7;
  color: var(--bms-birch);
  max-width: 60ch;
}

.why-text p em {
  color: var(--bms-paper);
  font-style: italic;
}

.why-text p strong {
  color: var(--bms-paper);
  font-weight: 600;
}

/* Note: .why-text .why-tag (not just .why-tag) so this beats the
   .why-text p font-size rule above on specificity — otherwise the
   closing statement silently renders at body-text size instead of
   matching the pull quote. */
.why-text .why-tag {
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-style: italic;
  /* Mirrors .manifesto-negation: orange left border, paper-coloured italic,
     same clamp scale. The pull quote and closing statement now bookend
     Body 2 with parallel visual weight — two declarative moments framing
     the problem-statement paragraph. */
  border-left: 2px solid var(--bms-ember);
  padding-left: 1.75rem;
  font-size: clamp(1.4rem, 1.6vw, 1.7rem);
  line-height: 1.4;
  color: var(--bms-paper);
  margin-top: 2.25rem;
}

@media (min-width: 900px) {
  /* Slight shift from 1.4fr toward text-column dominance so the pull quote
     can land on a single line on iMac without dropping its scale, while the
     portrait image stays a substantial feature (~880px tall instead of
     ~950px on iMac). */
  .the-why { grid-template-columns: 1.2fr 1fr; align-items: center; }
  .why-text { margin: 0; }
  .why-media { aspect-ratio: 4/5; }
}

/* ── Why we're different — image-backed differentiator cards ───────────── */
/* Flows on from Sit Sweat Chill Repeat (same ink background): collapse the
   top seam so the two sections read as one continuous block. */
.section.why-different {
  background: var(--bms-ink);
  padding-top: 0;
  max-width: var(--site-width);
}
/* Two-column mirror of Sit Sweat Chill Repeat: cards LEFT (larger column),
   bold headline RIGHT. Stacks on mobile with the header reading first. */
.why-diff-inner {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-lg);
}
/* Mobile: headline reads first (mirrors Sit Sweat's text-then-photos stack). */
.why-diff-head {
  order: -1;
  text-align: left;
}
.why-diff-headline {
  font-family: 'Staatliches', sans-serif;
  font-size: clamp(3rem, 8vw, 6rem);
  line-height: 0.9;
  letter-spacing: 0.02em;
  color: var(--bms-paper);
  margin: 0;
}
.why-diff-lead {
  color: var(--bms-sauna);
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-size: clamp(1.25rem, 2.4vw, 1.8rem);
  font-style: italic;
  line-height: 1.3;
  max-width: 32ch;
  margin: 1rem 0 0;
}
/* Mobile: centre the headline + lead so the section reads as a tidy,
   well-framed intro above the card grid. */
@media (max-width: 600px) {
  /* Match the Sit Sweat Chill Repeat section's side margins (--space-md),
     overriding the narrower 1rem .section padding the <=480px rule applies. */
  .section.why-different {
    padding-left: var(--space-md);
    padding-right: var(--space-md);
  }
  .why-diff-head {
    text-align: center;
  }
  .why-diff-lead {
    /* Cancel the desktop left-bias so the italic lead centres cleanly. */
    max-width: 36ch;
    margin-left: auto;
    margin-right: auto;
  }
}
.why-diff-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.875rem;
}
.why-diff-tile {
  position: relative;
  display: flex;
  border-radius: var(--radius-md);
  overflow: hidden;
  aspect-ratio: 3 / 4;
  background: var(--bms-smoke);
  box-shadow: 0 10px 30px -18px rgba(0, 0, 0, 0.8);
}
.why-diff-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Slight lift so the photos don't get lost under the overlay. */
  filter: brightness(1.12) saturate(1.06);
  transition: transform 1100ms cubic-bezier(0.16, 1, 0.3, 1);
}
/* Dark gradient so the title + blurb stay legible over any photo.
   Kept strong at the base for text, but lighter through the upper
   two-thirds so more of the image reads. */
.why-diff-tile::after {
  content: "";
  position: absolute;
  inset: 0;
  /* Deeper, near-black foot so the title + blurb read clearly; fades to
     clear at the top so the image stays bright up there. */
  background: linear-gradient(
    to top,
    rgba(13, 10, 7, 0.97) 0%,
    rgba(15, 12, 9, 0.88) 22%,
    rgba(18, 14, 10, 0.55) 45%,
    rgba(21, 17, 13, 0.18) 72%,
    rgba(21, 17, 13, 0) 100%
  );
  transition: opacity 360ms ease;
}
/* Mobile: knock the imagery back and deepen the overlay so the title +
   blurb stay clearly readable on the smaller, denser cards. Placed after
   the base rules above so it wins on source order. */
@media (max-width: 600px) {
  /* Single column on phones: two columns squeezed each blurb so narrow it
     filled the whole card and buried the photo. Full width gives the text
     room and keeps the image visible above it. Cards go landscape-ish so
     four stacked tiles don't run too tall. */
  .why-diff-grid {
    grid-template-columns: 1fr;
    gap: 1rem;
  }
  .why-diff-tile { aspect-ratio: 4 / 3; }
  .why-diff-img {
    filter: brightness(0.95) saturate(1.04);
  }
  .why-diff-tile::after {
    background: linear-gradient(
      to top,
      rgba(21, 17, 13, 0.95) 0%,
      rgba(21, 17, 13, 0.7) 30%,
      rgba(21, 17, 13, 0.3) 58%,
      rgba(21, 17, 13, 0.1) 80%,
      rgba(21, 17, 13, 0) 100%
    );
  }
  .why-diff-tile-body { padding: 1.1rem 1.15rem; }
  .why-diff-blurb { font-size: 0.92rem; }
}
.why-diff-tile-body {
  position: relative;
  z-index: 1;
  margin-top: auto;
  padding: 0.9rem 0.95rem;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
/* Ember accent rule above the title. */
.why-diff-title {
  margin: 0;
  position: relative;
  padding-bottom: 0.6rem;
  font-family: 'Staatliches', sans-serif;
  font-size: clamp(1.2rem, 1.6vw, 1.45rem);
  line-height: 1.02;
  letter-spacing: 0.03em;
  color: #fff;
}
/* Ember rule sits UNDER the title, separating it from the blurb. */
.why-diff-title::after {
  content: "";
  position: absolute;
  bottom: 0;
  left: 0;
  width: 34px;
  height: 3px;
  background: var(--bms-ember);
  transition: width 420ms cubic-bezier(0.16, 1, 0.3, 1);
}
.why-diff-blurb {
  margin: 0;
  color: rgba(237, 227, 210, 0.92);
  font-size: 0.8rem;
  line-height: 1.4;
}
/* Reserve a consistent blurb height so the ember-ruled titles line up
   across each row regardless of how many lines each blurb wraps to. */
@media (min-width: 600px) {
  .why-diff-blurb { min-height: 8.4em; }
}
/* Narrow 3-column laptop widths: the columns are tight, so the longest
   blurbs wrap to more lines. Reserve more height so the ember-ruled titles
   stay aligned across the row, deepen the gradient for legibility over the
   taller text block, and let the cards grow a touch taller to suit. */
@media (min-width: 900px) and (max-width: 1320px) {
  .why-diff-blurb { min-height: 11.5em; }
  .why-diff-tile { aspect-ratio: 3 / 4.35; }
  .why-diff-tile::after {
    background: linear-gradient(
      to top,
      rgba(13, 10, 7, 0.98) 0%,
      rgba(14, 11, 8, 0.92) 32%,
      rgba(18, 14, 10, 0.62) 56%,
      rgba(21, 17, 13, 0.22) 78%,
      rgba(21, 17, 13, 0) 100%
    );
  }
}
@media (hover: hover) {
  .why-diff-tile:hover .why-diff-img { transform: scale(1.06); }
  .why-diff-tile:hover .why-diff-title::after { width: 64px; }
}
/* Desktop: cards on the left (larger column), headline on the right. */
@media (min-width: 900px) {
  .why-diff-inner {
    /* Give the cards more width by eating into the headline column's
       negative space (was 5fr 2fr) and tightening the gap. */
    grid-template-columns: 6.6fr 2fr;
    align-items: center;
    gap: var(--space-lg);
  }
  .why-diff-grid {
    order: 1;
    grid-template-columns: repeat(3, 1fr);
  }
  .why-diff-head {
    order: 2;
    text-align: right;
    position: sticky;
    top: var(--space-lg);
  }
  .why-diff-lead {
    margin-left: auto;
  }
}

/* ── First Visit — flipped mirror of The Why ──────────────────────────
   Reuses every The Why style (background, colours, typography, image
   sizing) and only reverses the column order so the image sits on the
   right with text on the left. Same proportions, mirrored. */
@media (min-width: 900px) {
  /* Image column at ~58% of the row (1fr text : 1.4fr image) — a
     balanced mid-point between the previous slim image (1.2fr) and
     the wide image (1.6fr). Reads dominant without crowding the
     copy. */
  .the-why.first-visit { grid-template-columns: 1fr 1.4fr; }
}
@media (min-width: 900px) and (max-height: 1100px) {
  /* On shorter laptop screens flatten the ratio slightly so the
     image still gets the larger share but the copy column stays
     comfortable. */
  .the-why.first-visit { grid-template-columns: 1fr 1.15fr; }
}
.first-visit-actions {
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-top: 1.75rem;
}

/* Two action rows exist in the markup: one inside .why-text for desktop,
   one as a sibling after .why-media for mobile. CSS swaps which is
   visible so the CTAs sit beside the copy on desktop, but under the
   image on phones. */
.first-visit-actions--mobile { display: none; }
@media (max-width: 768px) {
  .first-visit-actions--desktop { display: none; }
  .first-visit-actions--mobile {
    display: flex;
    justify-content: center;
    margin-top: 1.5rem;
  }
}

/* "On Purpose" section: on laptop-class viewports (<1100px tall) the photo
   needs to frame the text — same height top-to-bottom — and the whole section
   needs to shrink slightly to fit in shorter viewports. iMac and tall external
   displays keep the original spec. */
@media (min-width: 900px) and (max-height: 1100px) {
  .section.the-why {
    padding: var(--space-lg) var(--space-md);
  }

  /* Image column stretches to match text-column height: the photo now frames
     the entire text block instead of floating at 4:5 and leaving empty space
     below it. The image inside still uses object-fit: cover so the framing
     stays composed regardless of how the aspect ratio changes. */
  .the-why {
    grid-template-columns: 1fr 1.6fr;
    align-items: stretch;
  }

  .why-media {
    aspect-ratio: auto;
    height: 100%;
  }

  /* Slightly more compact typography so the whole section lands in one
     viewport on Surface (1200×800) and MacBook Air (1280×800) without
     scrolling past the closing statement. */
  .why-headline {
    font-size: clamp(1.75rem, 3vw, 2.4rem);
    margin-bottom: 0.25rem;
  }

  .why-text p {
    font-size: 1.05rem;
    line-height: 1.6;
  }

  /* .why-text .why-tag (not bare .why-tag) so this beats the
     .why-text p override at the same media query level. */
  .manifesto-negation,
  .why-text .why-tag {
    font-size: clamp(1.2rem, 1.4vw, 1.45rem);
    padding-left: 1.25rem;
    line-height: 1.45;
  }

  .manifesto-negation {
    margin: 1.5rem 0;
  }

  .why-text .why-tag {
    margin-top: 1.75rem;
  }
}

/* ===== PRICING (homepage) ===== */
/* Pricing section: Declan Blackall photo (declan-0002.jpg) piped in
   via the --pricing-bg CSS variable (set on the <section> element
   in Pricing.astro). Heavy ink-on-image gradient + ember corner
   tint knocks the photo back so the price cards on top stay legible
   while the warmth + texture of the scene reads as atmospheric
   ground. Ink fallback if the image fails to load. */
/* Mobile-only photo layer (see the max-width:600px block). Hidden by
   default so it never renders on desktop, where the section's own
   background paints the photo. */
.pricing-bg-layer { display: none; }

/* Mobile-only forced line break (used in the pricing header). Collapses
   to nothing on desktop so the headline wraps naturally there. */
.br-mobile { display: none; }

.pricing-section {
  /* Very light overlay — the Declan photo behind pricing reads as
     bright atmospheric ground without feeling heavy. The translucent
     glass pricing cards do most of the legibility work on top, so
     the section overlay can stay subtle (just enough to soften the
     top/bottom edges so the photo doesn't end abruptly). */
  background:
    linear-gradient(180deg, rgba(21, 17, 13, 0.3) 0%, rgba(21, 17, 13, 0.08) 45%, rgba(21, 17, 13, 0.35) 100%),
    radial-gradient(ellipse 50% 40% at 90% 10%, rgba(217, 122, 46, 0.05), transparent 65%),
    var(--pricing-bg, none) center/cover no-repeat fixed,
    var(--bms-ink);
  padding-top: var(--space-lg);
  padding-bottom: var(--space-lg);
}

.pricing-section .section-header {
  max-width: 720px;
  margin-bottom: var(--space-sm);
}
/* Soft text shadow behind the "PRICING / THERE'S SOMETHING FOR
   EVERYONE." header so it stays legible against the bright Declan-
   photo background underneath. Layered: a tight dark halo for letter
   edge contrast, plus a wider soft glow that reads as a subtle
   shadow rather than a hard outline. */
.pricing-section .section-header .section-label,
.pricing-section .section-header h2 {
  text-shadow:
    0 1px 2px rgba(0, 0, 0, 0.55),
    0 0 18px rgba(0, 0, 0, 0.4);
}

.pricing-intro-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 2rem;
  margin-bottom: var(--space-md);
}

.pricing-intro {
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-size: 1.4rem;
  /* Slightly heavier weight on the off-peak / peak info so it reads
     with more authority against the bright Declan-photo ground. */
  font-weight: 500;
  line-height: 1.6;
  color: var(--bms-paper);
  margin: 0;
  max-width: 620px;
  flex: 1 1 auto;
}

.pricing-intro-label {
  color: var(--bms-ember);
  font-style: normal;
  font-weight: 600;
}

.pricing-section-sublabel {
  font-family: 'Staatliches', sans-serif;
  /* Bigger + brighter so PASSES / MEMBERSHIPS / PRIVATE BOOKINGS /
     THE FINE PRINT read as proper sub-headings rather than tiny
     ember eyebrow labels. Letter-spacing trimmed a touch to keep
     the wider text from feeling stretched. */
  font-size: 1.25rem;
  letter-spacing: 0.18em;
  color: var(--bms-paper);
  margin: var(--space-md) 0 0;
  padding-bottom: 0.7rem;
  border-bottom: 1px solid rgba(237, 227, 210, 0.18);
}


.pricing-special-aside {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.4rem;
  flex: 0 0 calc(33.333% - 1rem); /* 2 of 6 equal columns */
}

.pricing-special-aside-label {
  /* Matches the .pricing-section-sublabel rhythm (PASSES,
     MEMBERSHIPS) so SPECIAL OFFERS reads as a sibling sub-heading
     rather than a tiny aside eyebrow. */
  font-family: 'Staatliches', sans-serif;
  font-size: 1.25rem;
  letter-spacing: 0.18em;
  color: var(--bms-paper);
  text-transform: uppercase;
  white-space: nowrap;
}


.pricing-card--static {
  cursor: default;
  pointer-events: none;
}

.pricing-card--static::after {
  display: none;
}

.pricing-grid--specials {
  border-radius: var(--radius-sm);
  width: 100%;
}


.pricing-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  /* No gap between cards. With the cards now translucent (glass), a
     1px cream divider lattice read as an awkward solid line through
     the glass — removing the gap lets each card share the same
     continuous glass surface, with cell delineation coming from each
     card's own translucent fill + top-edge highlight rather than a
     hard divider line. */
  gap: 0;
  background: transparent;
  border: 1px solid rgba(237, 227, 210, 0.22);
  border-radius: var(--radius-md);
  overflow: hidden;
  margin-top: 0.75rem;
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.05) inset,
    0 18px 40px -16px rgba(0, 0, 0, 0.45),
    0 0 32px -8px rgba(217, 122, 46, 0.16);
}
/* Subtle ember-tinted vertical dividers between adjacent cards via
   a left-border on every card except the first in its row. Reads as
   a soft inset etch rather than a stark cream line — sits well
   against the translucent glass surface. */
.pricing-grid > .pricing-card + .pricing-card {
  border-left: 1px solid rgba(237, 227, 210, 0.08);
}

.pricing-grid--passes {
  grid-template-columns: repeat(6, 1fr);
}

.pricing-grid--specials {
  grid-template-columns: repeat(3, 1fr);
}

/* Specials tiles: names wrap to different line counts ("KIDS UNDER 18"
   fits one line, the other two wrap to two), which knocked the $ amounts
   out of horizontal alignment. Reserve a fixed two-line block for every
   name so the price row lines up across all three tiles. */
.pricing-grid--specials .pricing-card-name {
  line-height: 1.25;
  min-height: 2.5em;
}

.pricing-grid--members {
  grid-template-columns: repeat(6, 1fr);
}


.pricing-card {
  position: relative;
  /* Translucent smoke so the section's atmospheric Declan-photo
     background reads through each card. Backdrop blur softens the
     photo directly behind the price text, keeping the type legible
     while the warm ground stays present. The thin inset top-edge
     highlight (1px cream at 5% opacity) reads as a faint glass
     reflection — pairs with the grid's outer drop shadow to make
     the whole pricing block feel like an elevated glass panel. */
  background: rgba(42, 35, 28, 0.7);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
  padding: 1.5rem 1rem 1.25rem;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  text-align: left;
  transition:
    background var(--transition-fast),
    transform 0.35s cubic-bezier(0.16, 1, 0.3, 1),
    box-shadow 0.35s ease;
  overflow: hidden;
}
.pricing-card:hover {
  transform: translateY(-2px);
  box-shadow:
    0 0 28px rgba(194, 98, 43, 0.16),
    0 14px 30px rgba(0, 0, 0, 0.3);
  z-index: 1;
}

.pricing-card::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 2px;
  background: var(--bms-ember);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 500ms cubic-bezier(0.16, 1, 0.3, 1);
}

.pricing-card { cursor: pointer; }
.pricing-card:hover { background: rgba(56, 47, 38, 0.8); }
.pricing-card:hover::after { transform: scaleX(1); }
.pricing-card:focus-visible { outline: 2px solid var(--bms-ember); outline-offset: -2px; }
.pricing-card:focus-visible::after { transform: scaleX(1); }

.pricing-card.is-featured {
  background: linear-gradient(180deg, rgba(217, 122, 46, 0.08), rgba(217, 122, 46, 0) 60%), var(--bms-smoke);
}

/* Featured top-edge: a 2px ember bar that fades out at both ends and
   carries a soft glow above the card — reads as the top of an ember,
   not a generic underline. */
.pricing-card.is-featured::before {
  content: '';
  position: absolute;
  left: 0; right: 0; top: 0;
  height: 2px;
  background: linear-gradient(
    90deg,
    rgba(194, 98, 43, 0) 0%,
    var(--bms-ember) 25%,
    var(--bms-ember) 75%,
    rgba(194, 98, 43, 0) 100%
  );
  box-shadow: 0 0 14px rgba(194, 98, 43, 0.55);
}

.pricing-card-flag {
  position: absolute;
  top: 0.6rem;
  right: 0.75rem;
  font-family: 'Staatliches', sans-serif;
  font-size: 0.65rem;
  letter-spacing: 0.15em;
  color: var(--bms-ember);
  background: rgba(217, 122, 46, 0.12);
  padding: 0.2rem 0.55rem;
  border-radius: 999px;
}

.pricing-card-name {
  font-family: 'Staatliches', sans-serif;
  font-size: 1rem;
  letter-spacing: 0.06em;
  color: var(--bms-birch);
  margin-bottom: 0.6rem;
  text-transform: uppercase;
}

.pricing-card-amount {
  font-family: 'Staatliches', sans-serif;
  font-size: clamp(1.6rem, 2vw, 2.2rem);
  color: var(--bms-paper);
  line-height: 1;
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: 0.05em;
  white-space: nowrap;
}

.pricing-card-currency {
  font-size: 0.95rem;
  color: var(--bms-ember);
  margin-right: 0.05em;
  align-self: flex-start;
  margin-top: 0.3rem;
}

.pricing-card-amount .pricing-amount-suffix {
  font-size: 1rem;
  color: var(--bms-birch);
  margin-left: 0.15em;
  letter-spacing: 0.02em;
}

.pricing-card-per {
  font-size: var(--type-small);
  color: var(--bms-birch);
  margin-top: 0.5rem;
  letter-spacing: 0.02em;
}

/* Tap hint shown ONLY on mobile, telling users they can tap the card
   to open the purchase modal. Hidden on desktop where the hover lift
   + ember underline already signals interactivity. Hidden entirely
   on .pricing-card--static (non-interactive Kids Under 18 card).
   `margin-top: auto` pushes the hint to the bottom of the flex column
   so it aligns to a consistent baseline across cards regardless of
   how much content sits above it. Padding-top gap on top of that
   keeps a minimum breathing space when a card is content-light. */
.pricing-card-tap {
  /* Always laid out at the bottom of each card (display: block) but
     hidden visually at rest (opacity 0 + translateY 4px). On desktop
     hover, opacity goes to 1 and it slides into place in sync with
     the ember underline. On touch devices (handled in the hover:none
     block) it's always visible — see further down. */
  display: block;
  margin-top: auto;
  padding-top: 0.8rem;
  font-family: 'Staatliches', sans-serif;
  font-size: 0.62rem;
  letter-spacing: 0.16em;
  color: var(--bms-ember);
  text-transform: uppercase;
  white-space: nowrap;
  opacity: 0;
  transform: translateY(4px);
  transition:
    opacity 280ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 280ms cubic-bezier(0.22, 1, 0.36, 1);
  pointer-events: none;
}
.pricing-card:hover .pricing-card-tap,
.pricing-card:focus-visible .pricing-card-tap {
  opacity: 1;
  transform: translateY(0);
}
.pricing-card--static .pricing-card-tap { display: none !important; }


.pricing-card-prices {
  display: flex;
  gap: 0;
  align-items: flex-end;
  margin-top: 0.5rem;
}

.pricing-card-price-col {
  display: flex;
  flex-direction: column;
}

.pricing-card-price-col + .pricing-card-price-col {
  border-left: 1px solid rgba(237, 227, 210, 0.15);
  margin-left: 0.9rem;
  padding-left: 0.9rem;
}

.pricing-card-offlabel {
  font-family: 'Staatliches', sans-serif;
  font-size: 0.7rem; /* floored from 0.6rem: sub-12px rendered text */
  letter-spacing: 0.18em;
  color: var(--bms-ember);
  text-transform: uppercase;
  margin-top: 0.5rem;
  margin-bottom: 0.1rem;
}

.pricing-card-peak-label {
  font-family: 'Staatliches', sans-serif;
  font-size: 0.7rem; /* floored from 0.6rem: sub-12px rendered text */
  letter-spacing: 0.18em;
  color: var(--bms-birch);
  text-transform: uppercase;
  margin-bottom: 0.1rem;
}

.pricing-card-peak-amount {
  font-family: 'Staatliches', sans-serif;
  font-size: clamp(1.6rem, 2vw, 2.2rem);
  color: var(--bms-birch);
  line-height: 1;
  display: flex;
  align-items: baseline;
  gap: 0.05em;
  white-space: nowrap;
}

/* Per-session breakdown under each price column on dual-price packs
   (Double Pack, 5-Pack) — mirrors the single-line ".. / session" sub
   that the off-peak-only packs (10/20/40) show via .pricing-card-per. */
.pricing-card-percol {
  font-size: 0.75rem; /* floored from 0.7rem: sub-12px rendered text */
  color: var(--bms-birch);
  margin-top: 0.35rem;
  letter-spacing: 0.02em;
  white-space: nowrap;
}

.pricing-card--member { padding-bottom: 1.75rem; }


/* Sharing rules block */
/* ── "Gift this →" sub-row beneath the passes grid ────────────────────
   Mirrors the 6-column passes grid so each pill lands directly under
   its matching pass card. Tight spacing above (0.5rem) so it reads as
   attached to the passes grid; the normal section gap below brings in
   the next sublabel. */


/* ── Private group hire — single CTA card ─────────────────────────────
   Replaces the previous 3-card grid. Links to /group-bookings where
   the full tier breakdown lives. Different visual treatment so it
   reads as a route to a different page, not another pass tier. */
/* Private bookings + fine print share one row so they take up a single
   band of vertical space instead of stacking. Each column keeps its own
   sublabel above its card; the grid stretches both cards to equal height
   so the row reads as a balanced pair. Collapses to a stack on mobile. */
.pricing-footer-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-lg);
  align-items: stretch;
}
.pricing-footer-col {
  display: flex;
  flex-direction: column;
}
/* Let the card in each column fill the remaining column height so the two
   cards line up at the same bottom edge. */
.pricing-footer-col > .pricing-private-cta,
.pricing-footer-col > .pricing-fineprint-trigger {
  flex: 1;
}
@media (max-width: 768px) {
  .pricing-footer-row {
    grid-template-columns: 1fr;
    gap: var(--space-md);
  }
}

.pricing-private-cta {
  margin-top: 0.75rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
  padding: 1.5rem 1.75rem;
  /* Same glass treatment as the pricing-grid: translucent smoke +
     backdrop blur so the Declan photo behind reads through, with
     an inset top highlight + outer drop shadow lifting it off the
     atmospheric ground. */
  background: rgba(42, 35, 28, 0.7);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: 1px solid rgba(237, 227, 210, 0.22);
  border-radius: var(--radius-md);
  text-decoration: none;
  color: var(--bms-paper);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.05),
    0 18px 40px -16px rgba(0, 0, 0, 0.45),
    0 0 32px -8px rgba(217, 122, 46, 0.14);
  transition:
    border-color 220ms ease,
    transform 320ms cubic-bezier(0.22, 1, 0.36, 1),
    box-shadow 320ms ease;
}
.pricing-private-cta:hover {
  border-color: rgba(194, 98, 43, 0.45);
  transform: translateY(-2px);
  box-shadow:
    0 0 28px rgba(194, 98, 43, 0.12),
    0 14px 30px rgba(0, 0, 0, 0.3);
}

.pricing-private-cta-title {
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-style: italic;
  font-size: clamp(1.4rem, 2.4vw, 1.9rem);
  line-height: 1.1;
  color: var(--bms-paper);
  margin: 0 0 0.3rem;
}
.pricing-private-cta-sub {
  font-size: 0.95rem;
  color: var(--bms-birch);
  margin: 0;
  line-height: 1.5;
}
.pricing-private-cta-meta {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.35rem;
  flex-shrink: 0;
}
.pricing-private-cta-price {
  font-family: 'Staatliches', sans-serif;
  font-size: 1.4rem;
  letter-spacing: 0.06em;
  color: var(--bms-paper);
}
.pricing-private-cta-link {
  font-family: 'Staatliches', sans-serif;
  font-size: 0.8rem;
  letter-spacing: 0.16em;
  color: var(--bms-ember);
  text-transform: uppercase;
}
@media (max-width: 768px) {
  .pricing-private-cta {
    flex-direction: column;
    align-items: flex-start;
    gap: 1rem;
  }
  .pricing-private-cta-meta { align-items: flex-start; }
}

/* Fine-print trigger — visually a banner card, semantically a button.
   Opens the shared session modal (same as every other pricing card). */
.pricing-fineprint-trigger {
  margin-top: 0.75rem;
  width: 100%;
  display: flex;
  /* Override .pricing-card's column stack so label + arrow sit on one
     row. Same-specificity selector, later in the file, so this wins. */
  flex-direction: row;
  align-items: center;
  justify-content: flex-start;
  gap: 0.6rem;
  padding: 1.1rem 1.5rem;
  /* Same glass treatment so this trigger sits as a translucent panel
     on the same atmospheric ground as the rest of the pricing block. */
  background: rgba(42, 35, 28, 0.7);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: 1px solid rgba(237, 227, 210, 0.18);
  border-left: 3px solid var(--bms-ember);
  border-radius: var(--radius-md);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.05),
    0 18px 40px -16px rgba(0, 0, 0, 0.45);
  color: var(--bms-paper);
  font-family: 'Staatliches', sans-serif;
  font-size: 0.95rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-align: left;
  cursor: pointer;
  transition:
    background 220ms ease,
    border-color 220ms ease,
    transform 320ms cubic-bezier(0.22, 1, 0.36, 1);
}
.pricing-fineprint-trigger:hover {
  background: var(--bms-ash);
  border-color: rgba(194, 98, 43, 0.45);
  transform: translateY(-1px);
}
.pricing-fineprint-label {
  font-weight: normal;
}
.pricing-fineprint-arrow {
  color: var(--bms-ember);
  font-size: 1.1rem;
  transition: transform 320ms cubic-bezier(0.22, 1, 0.36, 1);
}
.pricing-fineprint-trigger:hover .pricing-fineprint-arrow {
  transform: translateX(3px);
}

/* List used inside the modal */
.pricing-sharing-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.6rem;
}
.pricing-sharing-list li {
  font-size: 0.95rem;
  line-height: 1.6;
  color: var(--bms-birch);
  padding-left: 1.1rem;
  position: relative;
}
.pricing-sharing-list li::before {
  content: '·';
  position: absolute;
  left: 0;
  color: var(--bms-ember);
  font-weight: 700;
  font-size: 1.4rem;
  line-height: 1;
}
.pricing-sharing-list li strong {
  color: var(--bms-paper);
  font-weight: 400;
  font-family: 'Staatliches', sans-serif;
  letter-spacing: 0.05em;
}


/* Meta strip — kids, 2-for-1, group */


/* ===== Legacy pricing strip (still used by inner pages if needed) ===== */
/* ===== PRICING STRIP ===== */
.pricing-strip {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  border: 1px solid rgba(237, 227, 210, 0.12);
  border-radius: var(--radius-md);
  overflow: hidden;
}


/* ===== FIRSTS LEDGER ===== */
.firsts-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;
  background: rgba(237, 227, 210, 0.08);
}


/* ===== SAVU TEASER ===== */
.savu-teaser {
  position: relative;
  min-height: 500px;
  display: grid;
  place-items: center;
  overflow: hidden;
}

/* Full-viewport on desktop so the Savu teaser reads as its own immersive
   panel. Mobile keeps the 500px floor above so it doesn't over-stretch on
   tall phone screens. */
@media (min-width: 769px) {
  .savu-teaser {
    min-height: 100vh;
  }
}

.savu-teaser-bg {
  position: absolute;
  inset: 0;
  background-color: var(--bms-smoke);
  overflow: hidden;
}
/* The photo is a real <img> now (object-fit: cover) so it always fills
   the section on any aspect ratio and can never tile/double the way the
   old CSS background did on tall mobile viewports. object-position keeps
   the box-shape feature + lower scene in frame, matching the previous
   50% 65% crop bias. */
.savu-teaser-bg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 65%;
  display: block;
}

/* Hover scale removed — the image was zooming on cursor-over which
   added visual noise without payoff. Static at all times. */

.savu-teaser-overlay {
  position: absolute;
  inset: 0;
  /* Very light overlay — just enough darkening at the top + bottom
     edges to keep the eyebrow + CTAs readable. Middle 60% is nearly
     transparent so the Savu interior shines through brightly. */
  background:
    linear-gradient(180deg, rgba(21, 17, 13, 0.35) 0%, rgba(21, 17, 13, 0.06) 40%, rgba(21, 17, 13, 0.06) 60%, rgba(21, 17, 13, 0.35) 100%),
    radial-gradient(ellipse 65% 45% at 50% 50%, rgba(0, 0, 0, 0.1), transparent 75%);
}

.savu-teaser-content {
  position: relative;
  z-index: 2;
  text-align: center;
  padding: var(--space-xl) var(--space-md);
}

.savu-teaser-eyebrow {
  font-family: 'Staatliches', sans-serif;
  font-size: 1.35rem;
  letter-spacing: 0.22em;
  /* Black rather than ember so it stays legible over the bright/high-contrast
     black-and-white smoke behind it. */
  color: var(--bms-ink);
  text-transform: uppercase;
  margin-bottom: 1rem;
}

.savu-teaser-actions {
  display: inline-flex;
  gap: 1rem;
  flex-wrap: wrap;
  justify-content: center;
  margin-top: var(--space-sm);
}

.savu-teaser-sub {
  max-width: 600px;
  margin-left: auto;
  margin-right: auto;
}

.savu-teaser-headline {
  font-family: 'Staatliches', sans-serif;
  font-size: var(--type-h1);
  color: var(--bms-paper);
  margin-bottom: 3rem;
}

.savu-teaser-sub {
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-style: italic;
  font-size: clamp(1.2rem, 2.5vw, 1.6rem);
  color: var(--bms-birch);
  margin-bottom: var(--space-md);
}

/* Mobile legibility: on phones the copy sits over the brightest part of
   the smoke and the thin italic sub was getting lost. Drop a soft dark
   scrim behind just the text block (image stays bright at the edges),
   brighten the sub to paper, and add a subtle shadow so every line
   reads cleanly. */
@media (max-width: 600px) {
  .savu-teaser-content::before {
    content: "";
    position: absolute;
    inset: -1.5rem -0.5rem;
    z-index: -1;
    background: radial-gradient(
      ellipse 92% 72% at 50% 42%,
      rgba(21, 17, 13, 0.66) 0%,
      rgba(21, 17, 13, 0.5) 45%,
      transparent 80%
    );
    pointer-events: none;
  }
  .savu-teaser-eyebrow {
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
  }
  .savu-teaser-headline {
    text-shadow: 0 2px 8px rgba(0, 0, 0, 0.55);
  }
  .savu-teaser-sub {
    color: var(--bms-paper);
    opacity: 0.96;
    text-shadow: 0 1px 5px rgba(0, 0, 0, 0.75);
  }
}

/* ===== TESTIMONIAL ===== */


/* ===== GUESTBOOK / REVIEWS ===== */


/* ── Guestbook marquee ──────────────────────────────────────────────
   Continuous left-scrolling track of review cards. Same logic as
   the brand marquee at the top of the page — duplicated content +
   translateX loop. Wrap clips overflow, track holds the cards. */
.guestbook-marquee-wrap {
  position: relative;
  overflow: hidden;
  cursor: grab;
  user-select: none;
  /* Allow native vertical pan; horizontal drag handled by JS. */
  touch-action: pan-y;
  padding: 1.25rem 0 1.75rem;
  /* Break out of the .section-inner 1400px constraint so the
     marquee spans the full viewport width — the row of review
     cards can scroll edge-to-edge instead of being capped to the
     centred content column. */
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
}
.guestbook-marquee-wrap.is-dragging { cursor: grabbing; }
.guestbook-marquee {
  display: flex;
  gap: var(--space-md);
  /* Track sits as a horizontal row that JS translates leftward via
     transform every frame. width auto so the track is as wide as
     the cards demand. */
  width: max-content;
  will-change: transform;
  padding: 0 var(--space-md);
}

.guestbook-card {
  /* Translucent glass card — lets the dark photo ground bleed through
     while the backdrop blur keeps the quote legible. Mirrors the
     pricing card glass treatment. */
  flex: 0 0 min(360px, 82vw);
  background: rgba(42, 35, 28, 0.55);
  border: 1px solid rgba(237, 227, 210, 0.16);
  padding: 1.85rem 1.6rem 1.5rem;
  border-radius: 18px;
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.08) inset,
    0 14px 32px -16px rgba(0, 0, 0, 0.55);
  transition:
    border-color 280ms ease,
    transform 220ms cubic-bezier(0.22, 1, 0.36, 1),
    box-shadow 280ms ease,
    background 280ms ease;
}

.guestbook-card:hover {
  border-color: rgba(217, 122, 46, 0.55);
  background: rgba(42, 35, 28, 0.7);
  transform: translateY(-3px);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.1) inset,
    0 18px 38px -16px rgba(0, 0, 0, 0.65),
    0 0 28px -10px rgba(217, 122, 46, 0.3);
}

/* "Grab" feedback — when the user grabs the marquee row, every card
   compresses slightly and tilts a hair, like picking up a stack. */
.guestbook-marquee-wrap.is-dragging .guestbook-card {
  transform: scale(0.97) rotate(-0.4deg);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.08) inset,
    0 6px 16px -8px rgba(0, 0, 0, 0.6);
}
.guestbook-marquee-wrap.is-dragging .guestbook-card:hover {
  transform: scale(0.97) rotate(-0.4deg);
}

.guestbook-stars {
  display: flex;
  gap: 2px;
  color: var(--bms-ember);
}

.guestbook-quote {
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-size: 1.08rem;
  /* Paper at near-full opacity sits cleanly on the translucent dark
     glass card over the photo ground. */
  font-weight: 500;
  color: var(--bms-paper);
  line-height: 1.55;
  margin: 0;
  flex: 1;
}

.guestbook-attr {
  font-family: 'Staatliches', sans-serif;
  font-size: 0.78rem;
  letter-spacing: 0.14em;
  color: var(--bms-ember);
  margin-top: auto;
}

.guestbook-source {
  text-align: center;
  margin-top: var(--space-md);
}

.guestbook-source a {
  font-family: 'Staatliches', sans-serif;
  font-size: 0.85rem;
  letter-spacing: 0.16em;
  color: var(--bms-ember);
  transition: color 200ms ease;
}

.guestbook-source a:hover { color: var(--bms-paper); }

/* ===== FOOTER ===== */
/* ===== ASA / INDUSTRY BAND ===== */
.asa-band {
  /* Transparent fill so the parent wrapper's atmospheric photo
     ground shows through. No top border — it was rendering as a
     hairline divider between the Thank You Wall and the ASA band,
     even though both panels share the same continuous bg, which
     made the two read as separate slabs instead of one panel. */
  background: transparent;
  border-top: none;
  border-bottom: none;
  padding: var(--space-lg) var(--space-md);
}

/* Homepage wrapper that hosts the Thank You Wall + ASA Band together.
   Single Declan-photo background with a deeper ink wash so the names
   on top read as the dominant visual layer rather than competing
   with a bright photo. Both sections inside render transparent. */
.thank-you-wall-wrap {
  position: relative;
  /* Single smooth linear ink wash from top to bottom — no
     mid-section peak/trough that was causing a visible darkening
     band between the Thank You Wall and the ASA band. The wash
     darkens slightly toward the bottom so the section settles
     naturally into the footer below, but the slope is monotonic
     (no inflection point) so the eye reads it as continuous. */
  background: var(--bms-ink);
}

.asa-band-inner {
  max-width: 720px;
  margin: 0 auto;
  text-align: center;
}

.asa-band-inner .section-label {
  color: var(--bms-ember);
  margin-bottom: 0.75rem;
}

.asa-band-title {
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-style: italic;
  font-size: clamp(1.6rem, 2.6vw, 2.2rem);
  color: var(--bms-paper);
  line-height: 1.2;
  margin: 0 0 1.25rem;
}

/* ASA logo — sits between the headline and the body copy, links
   to saunaaustralia.org.au. Tap target gets a subtle opacity lift
   on hover. The image inside is constrained by max-width since
   the source is huge (3232 × 705). */
.asa-band-logo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto 1.25rem;
  padding: 0.25rem 0.5rem;
  transition: opacity 220ms ease, transform 220ms ease;
  opacity: 0.85;
}
.asa-band-logo:hover {
  opacity: 1;
  transform: translateY(-1px);
}
.asa-band-logo img {
  height: auto;
  width: auto;
  max-width: min(220px, 50vw);
  max-height: 44px;
  display: block;
}

.asa-band-body {
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-size: 1.05rem;
  line-height: 1.7;
  color: var(--bms-paper);
  opacity: 0.92;
  margin: 0 0 1.25rem;
}

.asa-band-body strong { color: var(--bms-paper); font-weight: 400; }

.asa-band-link {
  font-family: 'Staatliches', sans-serif;
  font-size: 0.9rem;
  letter-spacing: 0.16em;
  color: var(--bms-ember);
  text-decoration: none;
  display: inline-block;
  transition: color 200ms ease;
}

.asa-band-link:hover { color: var(--bms-paper); }

.site-footer {
  /* Ink ground with a soft warmth that fades into pure black at the
     bottom — the footer reads as a deliberate base slab anchoring the
     page, rather than a flat panel. The hairline ember accent at the
     very top separates it from the ASA band above. */
  background:
    radial-gradient(ellipse 100% 60% at 50% 0%, rgba(217, 122, 46, 0.07), transparent 70%),
    linear-gradient(180deg, var(--bms-ink) 0%, #0a0807 100%);
  border-top: 1px solid rgba(217, 122, 46, 0.18);
  padding: calc(var(--space-lg) + 0.5rem) var(--space-md) var(--space-md);
  position: relative;
}
.site-footer::before {
  /* A second, ultra-subtle horizontal rule just below the ember accent,
     so the top edge reads as a hand-set printed rule rather than a
     single hard border. */
  content: '';
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  top: 6px;
  width: min(1100px, 88%);
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(237, 227, 210, 0.12), transparent);
  pointer-events: none;
}

/* ===== FOOTER MAILING LIST ===== */
.footer-signup {
  max-width: 1400px;
  margin: 0 auto var(--space-lg);
  padding: 0 0 var(--space-md);
  border-bottom: 1px solid rgba(237, 227, 210, 0.08);
  display: grid;
}

.footer-signup-inner {
  display: grid;
  grid-template-columns: 1fr 1.4fr;
  gap: var(--space-lg);
  align-items: start;
}

.footer-signup-intro .section-label {
  color: var(--bms-ember);
  margin-bottom: 0.5rem;
}

.footer-signup-headline {
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-style: italic;
  font-size: clamp(1.6rem, 2.5vw, 2.2rem);
  color: var(--bms-paper);
  line-height: 1.15;
  margin: 0 0 0.75rem;
}

/* Small superscript trademark mark. */
.tm-mark {
  font-size: 0.45em;
  vertical-align: super;
  font-style: normal;
}

.footer-signup-blurb {
  font-size: var(--type-small);
  color: var(--bms-birch);
  max-width: 380px;
  line-height: 1.6;
}

.footer-signup-form {
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}

.footer-signup-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.9rem;
}

/* Underline-style inputs, on-brand */
.footer-signup-form input {
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-size: 1rem;
  color: var(--bms-paper);
  background: transparent;
  border: none;
  border-bottom: 1px solid rgba(237, 227, 210, 0.2);
  border-radius: 0;
  padding: 0.55rem 0;
  width: 100%;
  transition: border-color 200ms ease;
}

.footer-signup-form input::placeholder {
  color: rgba(237, 227, 210, 0.42);
  font-style: italic;
}

.footer-signup-form input:focus {
  outline: none;
  border-bottom-color: var(--bms-ember);
  background: transparent;
}

.footer-signup-submit {
  align-self: flex-start;
  margin-top: 0.5rem;
  padding: 0.75rem 1.5rem;
}

.footer-signup-status {
  font-family: 'Staatliches', sans-serif;
  font-size: 0.85rem;
  letter-spacing: 0.12em;
  color: var(--bms-birch);
  min-height: 1.2em;
  margin: 0;
}

.footer-signup-status.is-success { color: var(--bms-ember); }
.footer-signup-status.is-error   { color: #d97a7a; }

.footer-signup-form.is-submitting .footer-signup-submit {
  opacity: 0.5;
  pointer-events: none;
}

.footer-signup-form.is-done .footer-signup-row,
.footer-signup-form.is-done .footer-signup-submit {
  display: none;
}

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

@media (max-width: 760px) {
  .footer-signup-inner { grid-template-columns: 1fr; gap: var(--space-md); }
  .footer-signup-row { grid-template-columns: 1fr; gap: 0.75rem; }
  /* Full-width SIGN UP under the stacked full-width inputs — the
     natural-width left-aligned button read as unbalanced on phones. */
  .footer-signup-submit {
    align-self: stretch;
    width: 100%;
    display: flex;
    justify-content: center;
  }
}

.footer-grid {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr 1.1fr;
  gap: var(--space-lg);
  max-width: 1400px;
  margin: 0 auto;
  padding-top: var(--space-md);
}

.footer-brand .site-logo img { height: 96px; width: auto; }

.footer-brand p {
  font-size: var(--type-small);
  color: var(--bms-birch);
  margin-top: 1rem;
  max-width: 280px;
  line-height: 1.7;
}

.footer-col-title {
  font-family: 'Staatliches', sans-serif;
  font-size: 0.85rem;
  letter-spacing: 0.18em;
  color: var(--bms-ember);
  text-transform: uppercase;
  margin-bottom: 0.9rem;
  /* Soft hairline rule under the column title for editorial structure. */
  padding-bottom: 0.55rem;
  border-bottom: 1px solid rgba(237, 227, 210, 0.07);
}

.footer-col ul { list-style: none; }

.footer-col ul li { margin-bottom: 0.5rem; }

/* Mobile-only footer links (Press / Join the team in the Info column):
   hidden by default, shown ≤768px where the FIND US / FOLLOW band is
   removed (see the footer rules in the mobile @media block). */
.footer-li--mobile { display: none; }

.footer-col ul li a {
  font-size: var(--type-small);
  color: rgba(237, 227, 210, 0.78);
  transition: color var(--transition-fast), padding-left var(--transition-fast);
  display: inline-block;
}

.footer-col ul li a:hover {
  color: var(--bms-paper);
  opacity: 1;
  padding-left: 4px;
}


.footer-contact-icons {
  display: flex;
  gap: 0.5rem;
  margin-top: 1.25rem;
}

.footer-icon-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: 1px solid rgba(237, 227, 210, 0.15);
  color: var(--bms-birch);
  transition: border-color 220ms ease, color 220ms ease, background 220ms ease;
  flex-shrink: 0;
}

.footer-icon-btn:hover {
  border-color: var(--bms-ember);
  color: var(--bms-paper);
  background: rgba(217, 122, 46, 0.12);
}

.footer-quick-link {
  color: var(--bms-ember) !important;
  font-size: var(--type-micro) !important;
  opacity: 0.8;
  padding-left: 0.5rem;
}

.footer-quick-link:hover {
  opacity: 1;
  color: var(--bms-paper) !important;
}

.footer-bottom {
  max-width: 1400px;
  margin: var(--space-lg) auto 0;
  padding-top: var(--space-md);
  border-top: 1px solid rgba(237, 227, 210, 0.08);
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: var(--type-micro);
  letter-spacing: 0.04em;
  color: rgba(237, 227, 210, 0.45);
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
}

.footer-bottom-links a {
  color: rgba(237, 227, 210, 0.65);
  transition: color 180ms ease;
  margin-right: 0.75rem;
}
.footer-bottom-links a:hover { color: var(--bms-ember); }

/* Inline BACK TO TOP control above the copyright block. Mobile-only: on
   phones the floating .back-to-top circle hovered over the footer links,
   so the affordance lives in the page flow instead. Desktop keeps the
   floating pill (homepage) and hides this. */
.footer-top-link { display: none; }

@media (max-width: 768px) {
  .footer-top-link {
    display: flex;
    width: 100%;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    margin-top: var(--space-md);
    padding: 1.1rem 0;
    background: none;
    border: none;
    border-top: 1px solid rgba(237, 227, 210, 0.08);
    color: var(--bms-birch);
    font-family: 'Staatliches', sans-serif;
    font-size: 0.9rem;
    letter-spacing: 0.16em;
    cursor: pointer;
  }
  .footer-top-link:active,
  .footer-top-link:focus-visible {
    color: var(--bms-ember);
    outline: none;
  }
  /* The control supplies its own divider; tighten the copyright block's
     top spacing so the two rules don't stack into a double line. */
  .footer-bottom {
    margin-top: 0;
  }
}


/* ===== STICKY BOTTOM BAR (mobile + desktop) ===== */
.mobile-book-bar {
  display: flex;
  position: fixed;
  bottom: 0; left: 0; right: 0;
  z-index: 99;
  background: rgba(21, 17, 13, 0.95);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-top: 1px solid rgba(237, 227, 210, 0.12);
  /* iOS Safari/Chrome: ADD the safe-area inset to the base padding so
     the bar extends past the home indicator (and below the dynamic
     browser toolbar) rather than floating above it. Earlier max()
     approach didn't work in all browsers; explicit addition is more
     reliable. The base 0.55rem keeps the bar's content padding the
     same on browsers where the inset resolves to 0. */
  padding: 0.55rem 1rem;
  padding-bottom: calc(0.55rem + env(safe-area-inset-bottom, 0px));
  gap: 0.75rem;
  align-items: center;
  justify-content: space-between;
  /* Slide-in from below. JS adds .is-revealed when the user is scrolling
     down past the hero; removed when scrolling up or on the hero itself. */
  transform: translateY(110%);
  transition: transform 0.32s cubic-bezier(0.22, 1, 0.36, 1);
  pointer-events: none;
  will-change: transform;
}
.mobile-book-bar.is-revealed {
  transform: translateY(0);
  pointer-events: auto;
}

.mobile-book-bar-status {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-family: 'Staatliches', sans-serif;
  /* Holds 0.9rem down to ~488px, then ramps to 0.7rem at ~320px so the
     status string still fits next to the BOOK button on narrow phones. */
  font-size: clamp(0.7rem, calc(0.32rem + 1.9vw), 0.9rem);
  letter-spacing: 0.1em;
  color: var(--bms-live-green);
}

.mobile-book-bar-status.is-closed {
  color: var(--bms-ember);
}

/* Contact icon row — visible on desktop, hidden on mobile (icons live in burger menu) */
.mobile-book-bar-icons {
  display: none;
  align-items: center;
  gap: 0.4rem;
}

.mobile-book-bar-icons .mbb-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: 1px solid rgba(237, 227, 210, 0.18);
  color: var(--bms-paper);
  transition: border-color 200ms ease, color 200ms ease, background 200ms ease;
}

.mobile-book-bar-icons .mbb-icon:hover,
.mobile-book-bar-icons .mbb-icon:focus-visible {
  border-color: var(--bms-ember);
  color: var(--bms-ember);
  background: rgba(217, 122, 46, 0.1);
}

@media (min-width: 769px) {
  .mobile-book-bar-icons { display: inline-flex; }
}

/* Reserve space at the page bottom for the book bar ONLY while the bar
   is actually revealed — an unconditional body padding left a permanent
   dead scroll strip past the footer on every page where the bar never
   shows (inner pages have .page-hero, not .hero, so setupMobileBookBar
   bails and the bar stays hidden; users could "scroll into nothing").
   The bar's own padding-bottom grows by env(safe-area-inset-bottom) on
   iOS (so it extends around the home indicator), so the reserved gap
   grows by the same amount or the bar covers the footer's last lines.
   The JS pins the bar revealed within 160px of the document end, so
   this padding never collapses mid-bounce at the bottom (which would
   clamp-jump the scroll position). Browsers without :has() simply get
   no reservation: the revealed bar overlaps the footer's last lines
   until it auto-hides — acceptable degradation. */
body:has(.mobile-book-bar.is-revealed) {
  padding-bottom: calc(70px + env(safe-area-inset-bottom, 0px));
}

/* ===== INNER PAGE HERO ===== */
.page-hero {
  position: relative;
  height: 60vh;
  min-height: 400px;
  display: grid;
  place-items: center;
  overflow: hidden;
  padding-top: 80px;
}

.page-hero-bg {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  background-color: var(--bms-ash);
}

/* Parallax-enabled inner page hero. The image starts at 1.06x scale
   so the JS-driven translateY doesn't expose a blank edge as the
   image drifts up. will-change hint keeps the transform on its own
   GPU layer. */
[data-page-hero-parallax="true"] {
  overflow: hidden;
}
[data-page-hero-parallax="true"] .page-hero-bg img {
  transform: translate3d(0, 0, 0) scale(1.06);
  transform-origin: center;
  will-change: transform;
}

.page-hero-overlay {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to bottom,
    rgba(21,17,13,0.4) 0%,
    rgba(21,17,13,0.6) 100%
  );
}

.page-hero-content {
  position: relative;
  z-index: 2;
  text-align: center;
  padding: 0 1.5rem;
}

.page-hero-eyebrow {
  font-family: 'Staatliches', sans-serif;
  font-size: 1rem;
  letter-spacing: 0.25em;
  color: var(--bms-ember);
  margin-bottom: 1rem;
}

.page-hero-title {
  font-family: 'Staatliches', sans-serif;
  /* Own clamp (was var(--type-h1) capped at 5.5rem) with a lower ceiling so
     inner-page hero titles don't blow out on mid-size laptops; mobile floor
     unchanged at 3rem. */
  font-size: clamp(3rem, 5vw, 5rem);
  color: var(--bms-paper);
  margin-bottom: 1rem;
}

.page-hero-sub {
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-style: italic;
  font-size: clamp(1.2rem, 2.5vw, 1.6rem);
  color: var(--bms-paper);
  opacity: 0.85;
}

/* Optional supporting paragraph below the sub (slot content). */
.page-hero-note {
  max-width: 620px;
  margin: 1.25rem auto 0;
  font-size: clamp(1rem, 1.6vw, 1.15rem);
  line-height: 1.65;
  color: var(--bms-paper);
  opacity: 0.92;
  text-shadow: 0 1px 14px rgba(21, 17, 13, 0.65);
}

/* ===== SPECS TABLE ===== */
.specs-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--type-small);
}

.specs-table tr {
  border-bottom: 1px solid rgba(237, 227, 210, 0.08);
}

.specs-table td {
  padding: 0.75rem 0;
  color: var(--bms-birch);
}

.specs-table td:first-child {
  font-family: 'Staatliches', sans-serif;
  letter-spacing: 0.05em;
  color: var(--bms-ember);
  width: 40%;
}

/* ===== FAQ ACCORDION ===== */
.faq-item {
  border-bottom: 1px solid rgba(237, 227, 210, 0.1);
}

.faq-question {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1.25rem 0;
  cursor: pointer;
  font-family: 'Staatliches', sans-serif;
  font-size: 1.3rem;
  letter-spacing: 0.02em;
  color: var(--bms-paper);
  list-style: none;
  gap: 1rem;
}

.faq-question::-webkit-details-marker { display: none; }

.faq-chevron {
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  color: var(--bms-ember);
  transition: transform var(--transition-fast);
}

details[open] .faq-chevron { transform: rotate(180deg); }

.faq-answer {
  padding: 0 0 1.25rem;
  font-size: var(--type-body);
  color: var(--bms-birch);
  line-height: 1.7;
}

/* Grouped FAQ — a small label + heading introduces each cluster of
   questions (Visiting / Sessions / Passes). */
.faq-group {
  margin-top: var(--space-lg);
}
.faq-group:first-of-type {
  margin-top: var(--space-md);
}
.faq-group-label {
  font-family: 'Staatliches', sans-serif;
  letter-spacing: 0.16em;
  font-size: 0.8rem;
  color: var(--bms-ember);
  margin: 0 0 0.25rem;
}
.faq-group-heading {
  margin: 0 0 0.5rem;
}

/* ===== PHOTO GRID ===== */


/* ===== PUNCHPASS PAGE ===== */
.punchpass-page {
  padding-top: 80px;
  min-height: 100vh;
}

.punchpass-page-header {
  padding-bottom: var(--space-md);
}

/* Single-row title bar: title text on the left, help + fullscreen button
   on the right. Bottom-aligned so the eyebrow + h1 stack reads with the
   help line at the same baseline. Used on /schedule and /passes to keep
   the iframe as high on the page as possible. */
.punchpass-page-bar {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1.5rem 2rem;
  flex-wrap: wrap;
  max-width: 1400px;
  margin: 0 auto;
  padding: var(--space-md) var(--space-md) 1rem;
}
.punchpass-page-bar-title { flex: 0 1 auto; }
.punchpass-page-bar-title h1 {
  font-size: clamp(2rem, 4vw, 3rem);
  margin: 0.2rem 0 0;
  line-height: 1;
}
.punchpass-page-bar-tools {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  flex: 1 1 auto;
  justify-content: flex-end;
  flex-wrap: wrap;
}

.punchpass-page-sub {
  margin-top: 0.5rem;
  max-width: 560px;
}

.punchpass-page-grid {
  /* Single column — the today/tomorrow aside has been removed so the
     main embed gets the full content width. */
  display: block;
  padding: 0 var(--space-md) var(--space-xl);
  max-width: 1400px;
  margin: 0 auto;
}

/* The Punchpass iframe renders on a white canvas. We match that on the
   wrap so the loading state and the loaded state share the same paper —
   no dark-to-white snap when the embed paints in. The iframe itself
   fades up via opacity once `onload` fires (see schedule/passes pages),
   covering the few frames of layout settle. */
/* Quiet helper line — now lives inside the toolbar on the left, with
   the fullscreen button to its right. Smaller scale so they balance. */
.punchpass-help {
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-style: italic;
  font-size: 0.95rem;
  line-height: 1.5;
  color: var(--bms-birch);
  max-width: 640px;
  margin: 0;
}

.punchpass-embed-wrap {
  position: relative;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: #fff;
  /* Layered shadow: tight dark base + soft far-cast for depth, plus a
     subtle ember rim so the embed feels framed by the BMS palette
     rather than pasted onto the page. */
  border: 1px solid rgba(237, 227, 210, 0.14);
  box-shadow:
    0 0 0 1px rgba(194, 98, 43, 0.08),
    0 18px 50px rgba(0, 0, 0, 0.35),
    0 1px 0 rgba(0, 0, 0, 0.04) inset;
  transition:
    box-shadow 320ms cubic-bezier(0.22, 1, 0.36, 1),
    border-radius 320ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* Toolbar above the iframe — holds the help line on the left and the
   fullscreen toggle on the right, in a single compact row. */
.punchpass-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  margin-bottom: 0.75rem;
  flex-wrap: wrap;
}

/* Pill-shaped light button — reads against the dark page background. */
.punchpass-fullscreen-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  background: transparent;
  border: 1px solid rgba(237, 227, 210, 0.28);
  color: var(--bms-paper);
  padding: 0.45rem 0.95rem;
  border-radius: 999px;
  font-family: 'Staatliches', sans-serif;
  font-size: 0.78rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  cursor: pointer;
  transition:
    background 220ms ease,
    border-color 220ms ease,
    color 220ms ease;
}
.punchpass-fullscreen-btn:hover {
  background: rgba(237, 227, 210, 0.06);
  border-color: rgba(237, 227, 210, 0.6);
}
.punchpass-fullscreen-icon { display: block; }
.punchpass-fullscreen-icon--close { display: none; }
.punchpass-fullscreen-label--close { display: none; }
/* Swap icon + label when the matching wrap is in fullscreen mode.
   The fullscreen button now sits in .punchpass-page-bar (sibling of the
   grid), so we :has() at the .punchpass-page section level. */
.punchpass-page:has(.punchpass-embed-wrap.is-fullscreen) .punchpass-fullscreen-icon--open { display: none; }
.punchpass-page:has(.punchpass-embed-wrap.is-fullscreen) .punchpass-fullscreen-icon--close { display: block; }
.punchpass-page:has(.punchpass-embed-wrap.is-fullscreen) .punchpass-fullscreen-label--open { display: none; }
.punchpass-page:has(.punchpass-embed-wrap.is-fullscreen) .punchpass-fullscreen-label--close { display: inline; }

/* When fullscreen, lift the fullscreen button to the corner of the
   viewport so it remains accessible above the fullscreen embed. */
body.punchpass-fullscreen-open .punchpass-fullscreen-btn {
  position: fixed;
  top: 1.25rem;
  right: 1.25rem;
  z-index: 9100;
  background: rgba(21, 17, 13, 0.88);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}

/* Fullscreen state — the embed wrap lifts out of the page flow and fills
   the viewport. The resize-target iframe takes the remaining height
   after the nav-buttons iframe is accounted for. */
.punchpass-embed-wrap.is-fullscreen {
  position: fixed;
  inset: 1rem;
  z-index: 9050;
  border-radius: var(--radius-md);
  box-shadow:
    0 0 0 1px rgba(194, 98, 43, 0.15),
    0 40px 120px rgba(0, 0, 0, 0.7);
  display: flex;
  flex-direction: column;
}
.punchpass-embed-wrap.is-fullscreen iframe[data-punchpass-resize] {
  flex: 1 1 auto;
  height: 100% !important;
  min-height: 0;
}
/* Page scroll lock + soft scrim behind the fullscreen embed. */
body.punchpass-fullscreen-open {
  overflow: hidden;
}
body.punchpass-fullscreen-open::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 9040;
  background: rgba(21, 17, 13, 0.86);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}

.punchpass-embed-wrap iframe {
  opacity: 0;
  transition: opacity 320ms ease-out;
}
.punchpass-embed-wrap iframe.is-loaded {
  opacity: 1;
}

/* Loading state — calm centred mark on the same white paper as Punchpass.
   A small amber dot pulses on/off; below it, a quiet "LOADING" label in
   our display face. No moving gradient, no edge-to-edge shimmer — just a
   single point of life on a page that's already the right colour. */
.punchpass-loading {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.9rem;
  background: #fff;
  color: #6b6357;
  z-index: 1;
  pointer-events: none;
  transition: opacity 240ms ease-out;
}
.punchpass-loading.is-hidden {
  opacity: 0;
}

.punchpass-loading::before {
  content: "";
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--bms-ember, #c9762a);
  box-shadow: 0 0 0 0 rgba(201, 118, 42, 0.35);
  animation: punchpass-pulse 1.4s ease-in-out infinite;
}

.punchpass-loading::after {
  content: "FETCHING THE SCHEDULE";
  font-family: 'Staatliches', sans-serif;
  font-size: 0.78rem;
  letter-spacing: 0.28em;
  color: rgba(21, 17, 13, 0.55);
}
/* Passes page reuses the same loading mark — swap the label via the
   parent page's class so each context gets the right copy. */
.passes-page .punchpass-loading::after,
.punchpass-loading[data-context="passes"]::after {
  content: "LOADING PASSES";
}

@keyframes punchpass-pulse {
  0%, 100% {
    transform: scale(0.85);
    box-shadow: 0 0 0 0 rgba(201, 118, 42, 0.35);
  }
  50% {
    transform: scale(1);
    box-shadow: 0 0 0 8px rgba(201, 118, 42, 0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .punchpass-loading::before { animation: none; }
  .punchpass-embed-wrap iframe { transition: none; }
}

/* Airtable embeds — same calm loading treatment as Punchpass.
   Airtable's "tealLight" background is a pale teal; we match it on the
   wrap so the handoff is a soft cross-dissolve, not a colour snap. */
.airtable-embed-wrap {
  position: relative;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: #e9f4f2;
  border: 1px solid rgba(237, 227, 210, 0.14);
  max-width: 760px;
  margin: 0 auto;
}

.airtable-embed-wrap iframe {
  opacity: 0;
  transition: opacity 320ms ease-out;
}
.airtable-embed-wrap iframe.is-loaded {
  opacity: 1;
}

.airtable-loading {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.9rem;
  background: #e9f4f2;
  color: #5e6e6c;
  z-index: 1;
  pointer-events: none;
  transition: opacity 240ms ease-out;
}
.airtable-loading.is-hidden { opacity: 0; }

.airtable-loading::before {
  content: "";
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--bms-ember, #c9762a);
  animation: punchpass-pulse 1.4s ease-in-out infinite;
}

.airtable-loading::after {
  content: "LOADING";
  font-family: 'Staatliches', sans-serif;
  font-size: 0.85rem;
  letter-spacing: 0.28em;
}

@media (prefers-reduced-motion: reduce) {
  .airtable-loading::before { animation: none; }
  .airtable-embed-wrap iframe { transition: none; }
}

.punchpass-aside {
  position: sticky;
  top: 96px;
  background: var(--bms-smoke);
  border: 1px solid rgba(237, 227, 210, 0.1);
  border-radius: var(--radius-md);
  padding: 1.5rem 1.25rem;
}

.punchpass-aside .section-label {
  margin-bottom: 0.75rem;
}

.punchpass-today-widget {
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: #fff;
  margin-bottom: 1rem;
}

.punchpass-aside-note {
  font-size: var(--type-small);
  color: var(--bms-birch);
  line-height: 1.5;
  margin: 0;
}

@media (max-width: 900px) {
  .punchpass-page-grid {
    grid-template-columns: 1fr;
  }
  .punchpass-aside {
    position: static;
    order: -1; /* show today widget above the main embed on mobile */
  }
}

/* ===== SAVU PAGE ===== */
.page-hero-actions {
  display: inline-flex;
  gap: 1rem;
  flex-wrap: wrap;
  justify-content: center;
  margin-top: var(--space-md);
}

.savu-prose {
  max-width: 720px;
  margin: 0 auto;
}

.savu-prose h2 {
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-style: italic;
  font-weight: 400;
  font-size: clamp(2rem, 4vw, 2.8rem);
  margin: 0.5rem 0 1.25rem;
  color: var(--bms-paper);
}

.savu-prose p {
  font-size: 1.15rem;
  line-height: 1.7;
  color: var(--bms-birch);
  margin-bottom: 1rem;
}

.savu-prose p strong { color: var(--bms-paper); }


.savu-pull {
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-style: italic;
  font-size: 1.4rem;
  line-height: 1.5;
  color: var(--bms-paper);
  border-left: 3px solid var(--bms-ember);
  padding: 0.5rem 0 0.5rem 1.5rem;
  margin: 1.5rem 0;
}

.savu-pull cite {
  display: block;
  margin-top: 1rem;
  font-style: normal;
  font-size: 0.85rem;
  letter-spacing: 0.1em;
  color: var(--bms-ember);
  font-family: 'Staatliches', sans-serif;
}


/* ===== BIRTHDAY SUIT PAGE ===== */
.bs-intro {
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  gap: var(--space-lg);
  align-items: start;
}

.bs-intro-text p { margin-bottom: 1rem; max-width: 60ch; }

.bs-key-facts {
  background: var(--bms-smoke);
  border: 1px solid rgba(237, 227, 210, 0.1);
  border-left: 3px solid var(--bms-ember);
  border-radius: var(--radius-md);
  padding: 1.5rem 1.25rem;
  position: sticky;
  top: 96px;
}

.bs-key-label {
  font-family: 'Staatliches', sans-serif;
  font-size: 0.8rem;
  letter-spacing: 0.2em;
  color: var(--bms-ember);
  margin-bottom: 0.75rem;
}

.bs-key-facts ul { list-style: none; padding: 0; margin: 0; }

.bs-key-facts li {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.55rem 0;
  border-bottom: 1px solid rgba(237, 227, 210, 0.07);
  font-size: var(--type-small);
}

.bs-key-facts li span { color: var(--bms-birch); letter-spacing: 0.05em; }
.bs-key-facts li strong {
  color: var(--bms-paper);
  font-weight: 400;
  font-family: 'Staatliches', sans-serif;
  font-size: 0.9rem;
  letter-spacing: 0.05em;
  text-align: right;
}

.bs-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-md);
}

.bs-step {
  background: var(--bms-ink);
  border: 1px solid rgba(237, 227, 210, 0.08);
  border-top: 2px solid var(--bms-ember);
  border-radius: var(--radius-md);
  padding: 1.75rem 1.5rem;
  position: relative;
}

.bs-step-num {
  font-family: 'Staatliches', sans-serif;
  font-size: 0.85rem;
  letter-spacing: 0.25em;
  color: var(--bms-ember);
  display: block;
  margin-bottom: 1rem;
}

.bs-step h3 {
  font-family: 'Staatliches', sans-serif;
  font-size: 1.4rem;
  color: var(--bms-paper);
  margin-bottom: 0.6rem;
}

.bs-step p {
  font-size: var(--type-small);
  color: var(--bms-birch);
  line-height: 1.6;
}


@media (max-width: 900px) {
  .bs-intro,
  .bs-expect { grid-template-columns: 1fr; }
  .bs-key-facts { position: static; }
  .bs-steps { grid-template-columns: 1fr; }
}

/* ===== PRESS PAGE ===== */
.press-intro {
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  gap: var(--space-lg);
  align-items: start;
}

.press-intro-text p {
  margin-bottom: 1rem;
}

.press-intro-card {
  background: var(--bms-smoke);
  border: 1px solid rgba(237, 227, 210, 0.1);
  border-left: 3px solid var(--bms-ember);
  border-radius: var(--radius-md);
  padding: 1.75rem 1.5rem;
  position: sticky;
  top: 96px;
}

.press-card-label {
  font-family: 'Staatliches', sans-serif;
  font-size: 0.8rem;
  letter-spacing: 0.2em;
  color: var(--bms-ember);
  margin-bottom: 0.5rem;
}

.press-card-email {
  display: block;
  font-family: 'Staatliches', sans-serif;
  font-size: 1.2rem;
  letter-spacing: 0.02em;
  color: var(--bms-paper);
  word-break: break-all;
  margin-bottom: 1.25rem;
  transition: color var(--transition-fast);
}

.press-card-email:hover { color: var(--bms-ember); opacity: 1; }

.press-card-list {
  list-style: none;
  margin: 0 0 1.25rem;
  padding: 0;
  border-top: 1px solid rgba(237, 227, 210, 0.1);
}

.press-card-list li {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 0.6rem 0;
  border-bottom: 1px solid rgba(237, 227, 210, 0.07);
  font-size: var(--type-small);
}

.press-card-list li span {
  color: var(--bms-birch);
  letter-spacing: 0.05em;
}

.press-card-list li strong {
  color: var(--bms-paper);
  font-weight: 400;
  font-family: 'Staatliches', sans-serif;
  font-size: 0.95rem;
  letter-spacing: 0.05em;
}

.press-card-btn {
  width: 100%;
  text-align: center;
  font-size: 1rem !important;
  padding: 0.7rem 1rem !important;
}

/* As-seen-in logo strip */


/* Article cards grid */
.press-grid {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: 1px;
  background: rgba(237, 227, 210, 0.08);
  border: 1px solid rgba(237, 227, 210, 0.08);
  border-radius: var(--radius-md);
  overflow: hidden;
  margin-top: var(--space-md);
}

.press-card {
  background: var(--bms-ink);
  position: relative;
  transition: background var(--transition-fast);
}

.press-card:hover { background: var(--bms-smoke); }


.press-card-top {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 1rem;
  margin-bottom: 1rem;
}

.press-card-outlet {
  font-family: 'Staatliches', sans-serif;
  font-size: 0.95rem;
  letter-spacing: 0.15em;
  color: var(--bms-ember);
  text-transform: uppercase;
  line-height: 1.2;
}

.press-card-type {
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  color: var(--bms-birch);
  text-transform: uppercase;
  border: 1px solid rgba(237, 227, 210, 0.18);
  padding: 0.2rem 0.55rem;
  border-radius: 999px;
  flex-shrink: 0;
  white-space: nowrap;
}

.press-card-headline {
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-style: italic;
  font-weight: 400;
  font-size: 1.35rem;
  line-height: 1.35;
  color: var(--bms-paper);
  margin-bottom: 1.25rem;
}

.press-card-foot {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  border-top: 1px solid rgba(237, 227, 210, 0.08);
  padding-top: 1rem;
  margin-top: auto;
}

.press-card-date {
  font-size: var(--type-small);
  color: var(--bms-birch);
  letter-spacing: 0.05em;
}


/* Media kit split */


@media (max-width: 900px) {
  .press-intro,
  .press-kit { grid-template-columns: 1fr; }
  .press-intro-card { position: static; }
}

/* Visit page — hours table tweaks */


.hours-note {
  display: inline-block;
  margin-left: 0.5rem;
  font-style: italic;
  font-size: 0.85em;
  color: var(--bms-ember);
}

@media (max-width: 768px) {
  .hours-note { display: block; margin: 0.25rem 0 0; }
}

/* ===== TEAM GRID ===== */


/* ===== ETIQUETTE RULES ===== */
.etiquette-list {
  list-style: none;
}

.etiquette-item {
  display: grid;
  grid-template-columns: 1.5rem 1fr;
  gap: var(--space-md);
  padding: var(--space-md) 0;
  border-bottom: 1px solid rgba(237, 227, 210, 0.08);
  align-items: start;
}

/* Marker: a small ember ring aligned to the rule heading. These items are
   pointers, not sequential steps, so no numbering. */
.etiquette-num {
  width: 0.7rem;
  height: 0.7rem;
  margin-top: 0.5rem;
  border-radius: 50%;
  border: 2px solid var(--bms-ember);
  box-shadow: 0 0 0 4px rgba(217, 122, 46, 0.12);
}

.etiquette-rule {
  font-family: 'Staatliches', sans-serif;
  font-size: 1.5rem;
  color: var(--bms-paper);
  margin-bottom: 0.5rem;
}

.etiquette-desc {
  font-size: var(--type-body);
  color: var(--bms-birch);
  line-height: 1.7;
}

/* ===== SIT / SWEAT / CHILL / REPEAT CYCLE ===== */
/* ===== INTERACTIVE RADIAL CYCLE ===== */
.cycle-radial {
  margin-top: var(--space-lg, 3rem);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(2.25rem, 6vw, 3.5rem);
}

/* Desktop: ring on the left, detail panel fills the negative space on the
   right. Hovering / tapping a node swaps the panel in place. */
@media (min-width: 900px) {
  .cycle-radial {
    flex-direction: row;
    align-items: center;
    justify-content: center;
    gap: clamp(2.5rem, 5vw, 5rem);
    max-width: 1120px;
    margin-inline: auto;
  }
}

/* Square ring holding four photo-nodes around a dashed circular track. */
.cycle-ring {
  position: relative;
  width: min(560px, 90vw);
  aspect-ratio: 1;
  flex: none;
}

.cycle-ring-track {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.cycle-ring-circle {
  fill: none;
  stroke: rgba(217, 122, 46, 0.5);
  stroke-width: 0.5;
  stroke-dasharray: 1.6 2.6;
  stroke-linecap: round;
}

@media (prefers-reduced-motion: no-preference) {
  /* Spin the circle, not the <svg> element. Rotating the square svg box
     grows its bounding box (~40% at 45deg) and intermittently triggers
     horizontal page scroll on narrow viewports. */
  .cycle-ring-circle {
    animation: cycle-spin 70s linear infinite;
    transform-box: fill-box;
    transform-origin: 50% 50%;
  }
}

@keyframes cycle-spin { to { transform: rotate(360deg); } }

.cycle-ring-center {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  display: grid;
  justify-items: center;
  gap: 0.3rem;
  text-align: center;
  pointer-events: none;
}

.cycle-ring-mark {
  font-size: clamp(1.9rem, 5vw, 2.7rem);
  line-height: 1;
  color: var(--bms-ember);
}

.cycle-ring-word {
  font-family: 'Staatliches', sans-serif;
  letter-spacing: 0.16em;
  font-size: clamp(0.8rem, 2vw, 1rem);
  line-height: 1.1;
  color: var(--bms-paper);
  opacity: 0.85;
}

/* Photo-nodes at 12 / 3 / 6 / 9 o'clock; centres sit on the track radius
   (37% of the box) so everything stays inside the square. The label hangs
   below each image, out of flow, so node centres stay symmetric. */
.cycle-node {
  position: absolute;
  width: clamp(110px, 29%, 172px);
  aspect-ratio: 1;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.cycle-node--t { top: 17%; left: 50%; transform: translate(-50%, -50%); }
.cycle-node--r { top: 50%; left: 83%; transform: translate(-50%, -50%); }
.cycle-node--b { top: 83%; left: 50%; transform: translate(-50%, -50%); }
.cycle-node--l { top: 50%; left: 17%; transform: translate(-50%, -50%); }

.cycle-node-img {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  overflow: hidden;
  border: 3px solid rgba(237, 227, 210, 0.18);
  box-shadow: 0 6px 20px rgba(21, 17, 13, 0.5);
  transition: border-color 0.25s ease, transform 0.25s ease, box-shadow 0.25s ease;
}

.cycle-node-img img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.cycle-node-label {
  position: absolute;
  top: calc(100% + 0.45rem);
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  font-family: 'Staatliches', sans-serif;
  letter-spacing: 0.14em;
  font-size: clamp(0.95rem, 2.2vw, 1.2rem);
  color: var(--bms-paper);
  transition: color 0.25s ease;
}

.cycle-node:hover .cycle-node-img,
.cycle-node:focus-visible .cycle-node-img {
  border-color: var(--bms-ember);
  transform: scale(1.05);
}

.cycle-node[aria-pressed="true"] .cycle-node-img {
  border-color: var(--bms-ember);
  box-shadow: 0 0 0 4px rgba(217, 122, 46, 0.28), 0 8px 24px rgba(21, 17, 13, 0.55);
}

.cycle-node[aria-pressed="true"] .cycle-node-label { color: var(--bms-ember); }

/* Detail panel: below the ring on mobile, beside it on desktop. */
.cycle-panels {
  position: relative;
  width: 100%;
  max-width: 620px;
}

@media (min-width: 900px) {
  .cycle-panels {
    flex: 1 1 0;
    max-width: 440px;
    /* Reserve vertical room so swapping steps with different point counts
       doesn't shift the ring's vertical centring. */
    min-height: 360px;
    display: flex;
    flex-direction: column;
    justify-content: center;
  }
}

.cycle-panel { text-align: center; }
.cycle-panel[hidden] { display: none; }

@media (min-width: 900px) {
  .cycle-panel { text-align: left; }
}

/* Mobile: the panel reads as a bubble pinned just under the ring, with a
   caret pointing up to the selected node and its own internal scroll. The
   four circles stay in view above it, so switching steps needs no scroll
   back up. */
@media (max-width: 899px) {
  .cycle-radial { gap: 1.4rem; }

  .cycle-panels {
    width: min(560px, 92vw);
    max-width: none;
    margin-top: 0.25rem;
    padding: 1.3rem 1.25rem 1.45rem;
    background: rgba(21, 17, 13, 0.82);
    border: 1px solid rgba(217, 122, 46, 0.3);
    border-radius: var(--radius-md, 14px);
    box-shadow: 0 14px 34px rgba(21, 17, 13, 0.45);
  }

  /* Caret connecting the bubble to the ring above. */
  .cycle-panels::before {
    content: "";
    position: absolute;
    top: -8px;
    left: 50%;
    width: 15px;
    height: 15px;
    transform: translateX(-50%) rotate(45deg);
    background: rgba(21, 17, 13, 0.82);
    border-left: 1px solid rgba(217, 122, 46, 0.3);
    border-top: 1px solid rgba(217, 122, 46, 0.3);
  }
}

@media (prefers-reduced-motion: no-preference) {
  .cycle-panel:not([hidden]) { animation: cycle-fade 0.35s ease both; }
}

@keyframes cycle-fade {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: none; }
}

.cycle-panel .point-list {
  display: inline-flex;
  text-align: left;
  margin-top: var(--space-sm);
}

@media (min-width: 900px) {
  .cycle-panel .point-list { display: flex; }

  /* Desktop hierarchy: ember step heading with a short accent rule, a
     brighter/larger essence lead-in, and point markers that sit on a faint
     ember halo so the list doesn't read as one flat block of birch text. */
  .cycle-panel .cycle-step {
    font-size: clamp(2.4rem, 3.4vw, 3.25rem);
    color: var(--bms-ember);
    display: inline-block;
    position: relative;
    padding-bottom: 0.55rem;
    margin: 0 0 0.9rem;
  }

  .cycle-panel .cycle-step::after {
    content: "";
    position: absolute;
    left: 0;
    bottom: 0;
    width: 2.4rem;
    height: 3px;
    border-radius: 2px;
    background: var(--bms-ember);
  }

  .cycle-panel .cycle-essence {
    font-size: 1.5rem;
    line-height: 1.45;
    color: var(--bms-paper);
    max-width: 36ch;
    margin-bottom: 1.4rem;
  }

  .cycle-panel .point-list { gap: 0.95rem; }

  .cycle-panel .point-list li { padding-left: 1.85rem; }

  .cycle-panel .point-list li::before {
    top: 0.5em;
    width: 0.55rem;
    height: 0.55rem;
    box-shadow: 0 0 0 4px rgba(217, 122, 46, 0.16);
  }
}

.cycle-step {
  font-family: 'Staatliches', sans-serif;
  font-size: clamp(2rem, 4vw, 2.75rem);
  line-height: 1;
  color: var(--bms-paper);
  margin: 0.35rem 0 0.6rem;
}

.cycle-essence {
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-style: italic;
  font-size: 1.2rem;
  line-height: 1.5;
  color: var(--bms-birch);
  margin-bottom: var(--space-sm);
}

/* ===== POINT LISTS (etiquette + do-nots) ===== */
.point-list {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
}

.point-list li {
  position: relative;
  padding-left: 1.6rem;
  font-size: var(--type-body);
  color: var(--bms-birch);
  line-height: 1.65;
}

.point-list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.62em;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--bms-ember);
}

.point-list--dont li::before {
  content: "\00d7";
  top: 0;
  left: 0;
  width: auto;
  height: auto;
  border-radius: 0;
  background: none;
  font-family: 'Staatliches', sans-serif;
  font-size: 1.1rem;
  line-height: 1.5;
  color: var(--bms-ember);
}

.point-list strong {
  color: var(--bms-paper);
  font-weight: 400;
}


/* ===== SCROLL ANIMATIONS ===== */
.fade-up {
  opacity: 0;
  transform: translateY(28px);
  transition:
    opacity 0.9s cubic-bezier(0.16, 1, 0.3, 1),
    transform 0.9s cubic-bezier(0.16, 1, 0.3, 1);
  transition-delay: var(--card-delay, 0ms);
}

.fade-up.visible {
  opacity: 1;
  transform: translateY(0);
}

/* Mobile: lighter, faster, no stagger — keeps things feeling responsive on touch
   and avoids the "missing card" gap when scrolling fast through clustered grids. */
@media (max-width: 768px) {
  .fade-up {
    transform: translateY(12px);
    transition:
      opacity 0.45s ease,
      transform 0.45s cubic-bezier(0.16, 1, 0.3, 1);
    transition-delay: 0ms;
  }
}

/* Initial hero entrance — fires on page load (no scroll observer needed) */
.fade-in-up {
  opacity: 0;
  transform: translateY(18px);
  animation: fade-in-up 1.1s cubic-bezier(0.16, 1, 0.3, 1) forwards;
  animation-delay: var(--delay, 0ms);
}

@keyframes fade-in-up {
  to { opacity: 1; transform: translateY(0); }
}

/* Hero scroll cue */
.hero-scroll-cue {
  position: absolute;
  bottom: 2rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 3;
  width: 28px;
  height: 44px;
  border: 1.5px solid rgba(237, 227, 210, 0.45);
  border-radius: 14px;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  padding-top: 8px;
  opacity: 0;
  animation: scroll-cue-fadein 800ms ease forwards 700ms;
  transition: border-color var(--transition-fast), opacity 2800ms ease;
}

@keyframes scroll-cue-fadein {
  to { opacity: 1; }
}

.hero-scroll-cue:hover { border-color: var(--bms-paper); }
.hero-scroll-cue.is-hidden { opacity: 0; pointer-events: none; }

.hero-scroll-cue span {
  display: block;
  width: 2px;
  height: 8px;
  background: var(--bms-paper);
  border-radius: 1px;
  animation: scroll-cue-bounce 2s ease-in-out infinite;
}

@keyframes scroll-cue-bounce {
  0%   { transform: translateY(0);    opacity: 0; }
  30%  { opacity: 1; }
  100% { transform: translateY(14px); opacity: 0; }
}

/* Surface laptops / smaller-resolution desktops (≈1536px and below, above
   the mobile breakpoint): shrink the scroll cue so it stays in proportion
   with the tamed hero type on these shorter screens. Applies to every hero
   that uses .hero-scroll-cue (homepage + inner PageHero pages). */
@media (min-width: 769px) and (max-width: 1536px) {
  .hero-scroll-cue {
    width: 22px;
    height: 35px;
    padding-top: 6px;
    border-radius: 11px;
  }
  .hero-scroll-cue span {
    height: 6px;
  }
}

/* Mobile flips the cue direction: the gesture to scroll down is a
   swipe UP with your finger, so the dot bounces upward instead. */
@media (max-width: 768px) {
  .hero-scroll-cue {
    align-items: flex-end;
    padding-top: 0;
    padding-bottom: 8px;
  }
  .hero-scroll-cue span {
    animation: scroll-cue-bounce-up 2s ease-in-out infinite;
  }
  @keyframes scroll-cue-bounce-up {
    0%   { transform: translateY(0);     opacity: 0; }
    30%  { opacity: 1; }
    100% { transform: translateY(-14px); opacity: 0; }
  }
}

/* Hide mobile-only contact icons by default; mobile @media overrides */
.nav-contact-icons { display: none; }

/* ===== MOBILE ===== */
/* ── Laptop / mid-range screens (769 – 1700px) ──────────────────────────────
   Targets MacBook Air 13" (1280px CSS), Microsoft Surface 2400×1600 at both
   200% DPR → 1200px and 150% DPR → 1600px, and similar mid-range displays.

   Header padding: increased from 2rem to 3rem sides so the logo and nav have
   a comfortable margin from the viewport edge. At 1200px, 2rem (32px) reads as
   no breathing room; 3rem (48px) restores the intended visual weight.

   Nav gap: tightened slightly (2rem → 1.5rem) so the 6 links + BOOK button
   don't need to fight for space as the header padding grows.

   Hero height: 85svh gives a noticeably wider aspect ratio than 100svh:
       MacBook Air 1280×770 → 655px tall → 1280:655 ≈ 1.95:1
       Surface     1200×800 → 680px tall → 1200:680 ≈ 1.76:1
       Surface     1600×997 → 847px tall → 1600:847 ≈ 1.89:1
   The portrait hero image still fills the full container width via
   object-fit:cover — the shorter section shows a more panoramic horizontal
   slice of the scene.

   The headline font already scales down via the 7.5vw slope in the base rule
   (6rem at 1280px, 7.5rem at 1600px, 9rem cap from 1920px up).
   ─────────────────────────────────────────────────────────────────────────── */
@media (max-width: 768px) {

  /* Mobile nav: full-screen fold-out menu ─────────────────────────────────── */
  .site-nav {
    display: flex;
    flex-direction: column;
    position: fixed;
    inset: 0;
    /* Reset the desktop-only centring transform — otherwise the fixed
       full-screen menu is translated 50% up and 50% to the left of the
       viewport and renders off-screen. */
    transform: none;
    top: 0;
    left: 0;
    background: var(--bms-ink);
    z-index: 200;
    /* Full link list + BOOK + contact icons in a confident full-screen
       menu. Top-anchored (NOT centred) so expanding the SESSIONS / AUFGUSS
       dropdowns only pushes the items below them downward — the rest of
       the nav stays put rather than the whole column re-centring upward.
       Taller content (a long list or an open dropdown) scrolls gracefully
       via overflow-y. Top padding gives the list breathing room below the
       header that the old vertical centring used to provide. */
    padding: 4.5rem 1.75rem 2rem;
    align-items: center;
    justify-content: flex-start;
    gap: 0;
    overflow-y: auto;

    /* Closed state (this rule is also active during the CLOSE transition).
       Mirror of entry, played in reverse: icons → items (reverse stagger) → bg. */
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition:
      opacity 220ms ease 700ms,                                  /* bg fades LAST, ~700ms in */
      visibility 0s linear 920ms;
  }

  .site-nav.open {
    /* Open state (active during the OPEN transition).
       Background appears almost instantly to mask the page behind. */
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transition:
      opacity 180ms ease,
      visibility 0s linear 0s;
  }

  .site-nav a {
    font-family: 'Staatliches', sans-serif;
    font-size: clamp(1.85rem, 6.8vw, 2.5rem);
    letter-spacing: 0.04em;
    line-height: 1;
    padding: 0.85rem 0;
    width: 100%;
    text-align: center;
    color: var(--bms-paper);
    border-bottom: 1px solid rgba(237, 227, 210, 0.08);

    /* CLOSE: items fade out only (no slide). Reverse stagger. */
    opacity: 0;
    transition: opacity 280ms ease;
  }

  /* Reverse stagger for closing — exit cascade reads bottom-up:
       1. contact icons fade out FIRST at 0ms (handled below on
          .nav-contact-icons), with a ~200ms transition
       2. BOOK NOW only starts fading once the icons are mostly gone
          (240ms delay — clear visual gap so the icons disappear
          BEFORE anything else moves)
       3. then the 8 nav links unwind in reverse order, SESSIONS last */
  .site-nav a:nth-child(1) { transition-delay: 620ms; }   /* SESSIONS — fades last */
  .site-nav a:nth-child(2) { transition-delay: 580ms; }
  .site-nav a:nth-child(3) { transition-delay: 540ms; }
  .site-nav a:nth-child(4) { transition-delay: 500ms; }
  .site-nav a:nth-child(5) { transition-delay: 460ms; }
  .site-nav a:nth-child(6) { transition-delay: 420ms; }   /* EVENTS */
  .site-nav a:nth-child(7) { transition-delay: 380ms; }
  .site-nav a:nth-child(8) { transition-delay: 320ms; }
  .site-nav a:nth-child(9) { transition-delay: 240ms; }   /* BOOK — starts after icons are gone */

  .site-nav.open a {
    /* OPEN: forward stagger */
    opacity: 1;
    transition: opacity 320ms ease;
  }

  /* Forward stagger for opening — overrides reverse stagger above.
     BOOK (nth-child(9)) explicitly delayed past the final nav item so
     it lands after the rest of the menu has resolved, rather than
     popping in at 0ms with no transition-delay rule. */
  .site-nav.open a:nth-child(1) { transition-delay: 220ms; }
  .site-nav.open a:nth-child(2) { transition-delay: 260ms; }
  .site-nav.open a:nth-child(3) { transition-delay: 300ms; }
  .site-nav.open a:nth-child(4) { transition-delay: 340ms; }
  .site-nav.open a:nth-child(5) { transition-delay: 380ms; }
  .site-nav.open a:nth-child(6) { transition-delay: 420ms; }   /* EVENTS */
  .site-nav.open a:nth-child(7) { transition-delay: 460ms; }
  .site-nav.open a:nth-child(8) { transition-delay: 500ms; }
  .site-nav.open a:nth-child(9) { transition-delay: 560ms; }   /* BOOK — appears last */

  /* Mobile menu BOOK button — full width, larger tap target. Selector
     bumped to `.site-nav a.nav-book-btn` (0,2,1) so it beats the
     desktop base rule of the same specificity that lives EARLIER in
     the file; without this, the desktop's tight clamp padding +
     small font-size would win the source-order tiebreak inside the
     mobile menu too, leaving the BOOK button as a thin slab. Visual
     fill comes from .btn.btn-primary on the same element. */
  .site-nav a.nav-book-btn {
    margin-top: 1.25rem;
    padding: 1rem 0;
    width: 100%;
    border-bottom: none;
    font-size: 1.2rem;
    letter-spacing: 0.08em;
    line-height: 1;
    min-height: 0;
  }

  /* Contact icons row inside mobile menu — quick access to call, email, directions, IG */
  .site-nav .nav-contact-icons {
    display: flex;
    justify-content: center;
    gap: 0.6rem;
    margin-top: 1.5rem;
    padding: 1.1rem 0 0;
    border-top: 1px solid rgba(237, 227, 210, 0.08);
    width: 100%;
    flex-wrap: wrap;

    /* CLOSE: icons fade out FIRST (they came in last on entry).
       Faster transition (180ms) + 0 delay so they're fully gone before
       BOOK starts fading at 240ms — clear visual sequence. */
    opacity: 0;
    transition: opacity 180ms ease;
  }

  .site-nav.open .nav-contact-icons {
    /* OPEN: fade in last, after the link cascade */
    opacity: 1;
    transition: opacity 360ms ease 550ms;
  }

  .site-nav .nav-icon-btn {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    flex: 1 1 0;
    min-width: 64px;
    max-width: 90px;
    padding: 0.75rem 0.5rem;
    border: 1px solid rgba(237, 227, 210, 0.15);
    border-radius: var(--radius-sm);
    color: var(--bms-paper);
    font-family: 'Staatliches', sans-serif;
    font-size: 0.7rem;
    letter-spacing: 0.12em;
    text-align: center;
    text-decoration: none;
    transition: border-color 200ms ease, color 200ms ease, background 200ms ease;
  }

  .site-nav .nav-icon-btn svg {
    flex-shrink: 0;
    opacity: 0.85;
  }

  .site-nav .nav-icon-btn:hover,
  .site-nav .nav-icon-btn:focus-visible {
    border-color: var(--bms-ember);
    color: var(--bms-ember);
    background: rgba(217, 122, 46, 0.08);
  }

  /* Tighter header on mobile — half the padding, smaller logo, less
     wasted height above the content. */
  .site-header {
    padding: 0.55rem 1.25rem;
    /* Drop the hairline cream border-bottom on mobile — it was
       reading as a thin grey strip under the logo with the dead space
       between the header and marquee accentuating it. */
    border-bottom: none;
  }
  .site-logo img { height: 60px; }

  /* Burger toggle vertically centred against the header. Header is
     ~78px tall on mobile (60px logo + 0.55rem padding × 2 = 78px); the
     44px toggle centres at (78 − 44) / 2 = 17px from the viewport top. */
  .nav-toggle {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    position: fixed;
    top: 17px;
    right: 1rem;
    z-index: 250;
    padding: 0.5rem;
    width: 44px;
    height: 44px;
  }
  .nav-toggle span { margin: 3px 0; }
  .header-live-status { display: none; }

  /* Single-column session grid across all mobile widths — each card
     spans the full content width of the section. !important so this
     wins against any cached or inherited 2-col override. */
  .sessions-grid { grid-template-columns: 1fr !important; }
  /* Tighter rhythm above the first card on mobile — desktop section
     padding (8rem) leaves a stadium-sized gap between the "Our Sessions"
     header and the grid below. Section-header margin-bottom trimmed
     so the cards sit closer to the heading. */
  .section-alt.sessions-section {
    padding-top: var(--space-md);
    padding-bottom: var(--space-md);
  }
  .section-alt.sessions-section .section-header {
    margin-bottom: 1rem;
  }
  .firsts-grid   { grid-template-columns: repeat(2, 1fr); }
  .pricing-strip { grid-template-columns: repeat(2, 1fr); }
  .pricing-grid,
  .pricing-grid--passes,
  .pricing-grid--members,
  .pricing-grid--gifts { grid-template-columns: repeat(2, 1fr); }
  
  
  
  .path-timeline {
    grid-template-columns: 1fr;
    padding-left: 1.5rem;
  }
  .path-step { padding: 1rem 0 1.25rem 1.5rem; }
  .path-step::before {
    position: absolute;
    left: -1.5rem;
    top: 1.2rem;
    margin: 0;
  }

  /* ── Scroll-driven mode ──────────────────────────────────────────────
     When JS adds .is-scroll-progress, override the desktop's stagger
     animations and tie each dot + each step's text fade to the timeline's
     --path-progress var (0-1). All overrides use !important to defeat
     the desktop's higher-specificity ".visible .path-step:nth-child(N)::before"
     rules. Each step has a threshold (--t) so it ignites at a specific
     progress value; sharp scale 30 over the ignite window. */
  .path-timeline.is-scroll-progress {
    position: relative;
  }
  .path-timeline.is-scroll-progress::before {
    top: 0;
    bottom: 0;
    left: 0.5rem;
    right: auto;
    width: 1px;
    height: auto;
    background: linear-gradient(to bottom, transparent, rgba(217, 122, 46, 0.5) 8%, rgba(217, 122, 46, 0.5) 92%, transparent) !important;
    transform: scaleY(var(--path-progress, 0)) !important;
    transform-origin: top center !important;
    transition: none !important;
    animation: none !important;
  }
  .path-timeline.is-scroll-progress .path-step {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  .path-timeline.is-scroll-progress .path-step > * {
    opacity: clamp(0, calc((var(--path-progress, 0) - var(--t, 0)) * 20), 1) !important;
    transform: translateY(calc((1 - clamp(0, calc((var(--path-progress, 0) - var(--t, 0)) * 20), 1)) * 6px)) !important;
    transition: none !important;
  }
  /* Past steps: opacity + scale tied to scroll, ignite animation killed. */
  .path-timeline.is-scroll-progress .path-step:not(.path-step--future)::before {
    animation: none !important;
    transition: none !important;
    opacity: clamp(0, calc((var(--path-progress, 0) - var(--t, 0)) * 30), 1) !important;
    transform: scale(clamp(0, calc((var(--path-progress, 0) - var(--t, 0)) * 30), 1)) !important;
  }
  /* Future dot: opacity tied to scroll (fades in as the line reaches
     it), transform left alone so the continuous spin animation can
     drive rotation. The desktop's time-delayed crescendo wouldn't
     sync with scroll input, so we use the plain ongoing spin +
     box-shadow pulse here. */
  .path-timeline.is-scroll-progress .path-step--future::before {
    transition: none !important;
    opacity: clamp(0, calc((var(--path-progress, 0) - var(--t, 0)) * 30), 1) !important;
    animation:
      dot-future-spin 10s linear infinite,
      path-future-pulse 2.6s ease-in-out infinite !important;
  }
  /* Per-step thresholds: 6 steps, spaced from ~5% to ~85% so the user has
     visible scroll distance between each ignite + the line completes near
     the bottom. */
  .path-timeline.is-scroll-progress .path-step:nth-child(1) { --t: 0.05; }
  .path-timeline.is-scroll-progress .path-step:nth-child(2) { --t: 0.20; }
  .path-timeline.is-scroll-progress .path-step:nth-child(3) { --t: 0.36; }
  .path-timeline.is-scroll-progress .path-step:nth-child(4) { --t: 0.52; }
  .path-timeline.is-scroll-progress .path-step:nth-child(5) { --t: 0.68; }
  .path-timeline.is-scroll-progress .path-step:nth-child(6) { --t: 0.84; }
  .path-year, .path-title, .path-desc { text-align: left; }
  .path-desc { max-width: none; margin: 0; }
  .manifesto     { grid-template-columns: 1fr; }
  .footer-grid   { grid-template-columns: 1fr 1fr; }
  /* Drop the FIND US / FOLLOW band on mobile: the brand block above
     already carries the address plus icon buttons for Instagram,
     Facebook, YouTube, email and phone, so the band was pure
     duplication (and its inner grid never lined up with the SESSIONS /
     INFO columns). Its two unique links (Press, Join the team) surface
     in the Info column via .footer-li--mobile. */
  .footer-col--contact { display: none; }
  .footer-li--mobile { display: list-item; }
  
  

  .session-card { aspect-ratio: 1; }
  .session-card-cta { opacity: 1; transform: none; }

  /* Events: stack cards vertically on mobile instead of horizontal carousel.
     Avoids the iOS "swipe ambiguity" problem and lets normal page scroll work. */
  .events-carousel {
    flex-direction: column;
    gap: 1rem;
    overflow: visible;
    scroll-snap-type: none;
    scroll-behavior: auto;
    /* Zero horizontal padding so the event cards span the same width
       as the session cards above — both grids now sit inside the
       section's own padding only, no extra inset on the carousel. */
    padding: 0 0 var(--space-md);
    cursor: default;
    -webkit-overflow-scrolling: auto;
    touch-action: auto;
  }
  .event-card,
  .event-card--featured {
    flex: 1 1 auto;
    width: 100%;
    min-width: 0;
    max-width: none;
    scroll-snap-align: none;
  }
  .events-carousel-nav { display: none; }

  .mobile-book-bar { display: flex; }

  /* Marquee → top of the page on mobile only, sitting directly below
     the fixed header. Reorders via flex without touching the DOM. The
     hero loses its header-offset padding (the marquee handles the
     visual gap now) and shortens by the height of header + marquee
     so the hero copy stays optically centred in the visible viewport. */
  #main {
    display: flex;
    flex-direction: column;
  }
  /* Fully opaque ink header on mobile — kills the amber haze at the
     top edge of the viewport that was coming from the desktop header
     treatment (translucent rgba(21,17,13,0.7) + backdrop-filter blur)
     picking up the ember-toned hero image behind it. */
  .site-header {
    background: var(--bms-ink);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  .site-header.scrolled {
    background: var(--bms-ink);
  }
  .marquee-wrap {
    order: -1;
    /* Clear the fixed header — 60px logo + 0.55rem padding × 2 (the
       mobile header padding, NOT the desktop 1rem) = 78px. Border-bottom
       is now removed on mobile so no extra px to account for. */
    margin-top: 78px;
  }
  .hero {
    padding-top: 0;
    height: calc(100vh - 115px); /* fallback for browsers without svh */
    height: calc(100svh - 115px); /* 78px header offset + ~37px marquee */
    min-height: 500px;
  }

  /* Re-anchor the hero-overlay gradients for the BMS-1028 mobile image.
     On the mobile shot the firepit sits centre-bottom (~38% / 78%) with
     smoke billowing up through the centre, so:
       — the warm ember light-leak moves from top-right to bottom-left
         where the coals actually glow
       — the dark vignette for legibility stays centred but lifts up
         slightly so it darkens behind the headline/sub copy without
         drowning the smoke plume that defines the composition. */
  .hero-overlay {
    /* Dark smoke plume anchored on the LEFT side of the canvas (the
       firepit side, opposite the seated subject on the right). Tighter
       ellipse (45% wide × 55% tall) and centred at 15% horizontally so
       the dark falloff stays well within the left half of the image.
       Ember glow on the coals removed at the user's request. */
    background:
      radial-gradient(ellipse 45% 55% at 15% 50%, rgba(0, 0, 0, 0.58) 0%, rgba(0, 0, 0, 0.3) 50%, rgba(0, 0, 0, 0) 80%);
  }

  /* More vertical breathing room between hero copy blocks on mobile.
     Desktop already has plenty of room; phones felt cramped. */
  .hero-headline {
    font-size: clamp(2rem, 9vw, 4rem);
    line-height: 1.02;
    margin-bottom: 1.5rem;
  }
  
  .hero-sub {
    font-size: clamp(1.35rem, 4.2vw, 1.8rem);
    line-height: 1.45;
    margin-top: 1.75rem;
    margin-bottom: 2.75rem;
  }
  .hero-ctas {
    gap: 0.65rem;
  }
  .hero-ctas .btn {
    font-size: 1.1rem;
    padding: 0.95rem 1.5rem;
    letter-spacing: 0.08em;
    /* Match the box dimensions of the two CTAs on mobile. Both share
       the same padding + font-size so heights tally; min-width is set
       to the natural width of BOOK A SESSION (the longer label) so
       OUR STORY ↓ grows to match it without either button getting
       wider than the longer label's own content needs. */
    min-width: 10.5rem;
    justify-content: center;
  }
  .marquee-item { font-size: 1rem; }
  /* Drop the edge fade gradients entirely on mobile — they were
     dimming the running text inside a narrow viewport. */
  .marquee-wrap::before,
  .marquee-wrap::after { display: none; }
  /* Lift the marquee above any blurred / faded sibling overlays so
     the ember bar reads at full brightness across phones. The
     !important on opacity overrides the inline style the scroll-fade
     JS in main.js sets — that fade logic assumes the desktop layout
     where the marquee sits below the hero, but on mobile it's pinned
     to the top via order:-1 and the fadeOut maths starts immediately
     (rect.bottom ≈ 105px / 140px fade range ≈ 75% opacity at scroll 0).
     Skipping the fade keeps the marquee fully opaque on phones. */
  .marquee-wrap {
    position: relative;
    z-index: 50;
    isolation: isolate;
    opacity: 1 !important;
  }

  .manifesto-image { display: none; }

  .etiquette-item { grid-template-columns: 1.25rem 1fr; gap: 1rem; }
}

@media (max-width: 480px) {
  .hero-headline {
    font-size: clamp(1.85rem, 11vw, 3rem);
    line-height: 1.02;
    margin-bottom: 1.25rem;
  }
  
  .hero-sub {
    font-size: 1.4rem;
    line-height: 1.45;
    margin-top: 1.4rem;
    margin-bottom: 2rem;
  }
  /* Single column on small phones — cards span the full mobile width
     so the imagery + title can breathe at phone size. */
  .sessions-grid { grid-template-columns: 1fr !important; }
  .firsts-grid   { grid-template-columns: 1fr 1fr; }
  .pricing-strip { grid-template-columns: 1fr 1fr; }
  /* Keep pricing as 2 columns even on small phones — 1 column stacks too tall */
  .pricing-grid,
  .pricing-grid--passes,
  .pricing-grid--members,
  .pricing-grid--gifts { grid-template-columns: repeat(2, 1fr); }
  /* ── FOOTER (mobile) ───────────────────────────────────────────────
     Two-column layout for the link blocks with the brand block centred
     across the top. The FIND US / FOLLOW contact column is hidden on
     mobile (see the 768px block) — the brand block already carries the
     address + contact/social icon row. */
  .footer-grid {
    grid-template-columns: 1fr 1fr;
    gap: 1.75rem 1.25rem;
    text-align: center;
  }
  .footer-brand {
    grid-column: 1 / -1;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
  }
  .footer-brand p {
    margin-left: auto;
    margin-right: auto;
  }
  .footer-contact-icons {
    justify-content: center;
  }
  /* Link columns: plain left alignment on mobile. (A converging-centre
     layout — left column right-aligned, right column left-aligned — was
     tried here; any link that wrapped to two lines got a ragged edge
     that read as broken rather than deliberate.) */
  .footer-col {
    text-align: left;
  }
  .footer-col-title {
    margin-bottom: 0.75rem;
  }
  .footer-bottom {
    flex-direction: column;
    text-align: center;
    justify-content: center;
    gap: 0.35rem;
  }
  /* Trim the footer's bottom breathing room on mobile — the global
     `body { padding-bottom: 70px }` already reserves space for the
     fixed book bar below it, so the 64px var(--space-lg) padding on
     the footer was creating an empty dark gap between the
     copyright line and the bar. */
  .site-footer {
    padding-bottom: 1.25rem;
  }
  
  
  .section { padding: var(--space-lg) 1rem; }
}

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

/* ===== FOCUS-VISIBLE (keyboard nav polish) ===== */
:focus { outline: none; }

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

a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[role="button"]:focus-visible {
  outline: 2px solid var(--bms-ember);
  outline-offset: 3px;
}

.btn:focus-visible {
  outline-offset: 4px;
}

/* ===== FORM STYLING (inputs, textareas, selects) ===== */
input[type="text"],
input[type="email"],
input[type="tel"],
input[type="url"],
input[type="search"],
input[type="number"],
input[type="date"],
input[type="time"],
textarea,
select {
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-size: 1.05rem;
  color: var(--bms-paper);
  background: rgba(237, 227, 210, 0.04);
  border: 1px solid rgba(237, 227, 210, 0.15);
  border-radius: var(--radius-sm);
  padding: 0.85rem 1rem;
  width: 100%;
  transition: border-color 200ms ease, background 200ms ease;
}

input::placeholder,
textarea::placeholder {
  color: rgba(237, 227, 210, 0.35);
  font-style: italic;
}

input:focus,
textarea:focus,
select:focus {
  border-color: var(--bms-ember);
  background: rgba(237, 227, 210, 0.08);
  outline: none;
}

textarea { min-height: 140px; resize: vertical; }

label {
  font-family: 'Staatliches', sans-serif;
  font-size: 0.8rem;
  letter-spacing: 0.16em;
  color: var(--bms-birch);
  display: block;
  margin-bottom: 0.4rem;
}

/* ===== 404 PAGE ===== */


/* ===== SCROLL PROGRESS BAR ===== */
.scroll-progress {
  position: fixed;
  top: 0;
  left: 0;
  height: 2px;
  width: 0;
  background: var(--bms-ember);
  box-shadow: 0 0 8px rgba(217, 122, 46, 0.5);
  z-index: 10000;
  transition: width 80ms linear;
  pointer-events: none;
}

/* Bigger + brighter on mobile so it actually reads */
@media (max-width: 768px) {
  .scroll-progress {
    height: 3px;
    box-shadow: 0 0 10px rgba(217, 122, 46, 0.7);
  }
}

/* ===== MOBILE INTERACTION ANIMATIONS ===== */
@media (max-width: 768px) {
  /* Card tap feedback — subtle scale-down on press so taps feel tactile */
  .session-card:active,
  .event-card:active,
  .pricing-card:active,
  .guestbook-card:active,
  .path-step:active {
    transform: scale(0.98);
    transition: transform 120ms ease;
  }

  /* Buttons feel responsive on tap */
  .btn-primary:active,
  .btn-ghost:active {
    transform: scale(0.97);
    transition: transform 100ms ease;
  }

  /* Image placeholder while loading — subtle slow pulse rather than blank gap.
     Hides as soon as the image's parent gets a .loaded marker. */
  .session-card-media:not(.loaded),
  .offerings-img:not(.loaded) {
    background: linear-gradient(
      120deg,
      rgba(237, 227, 210, 0.04) 0%,
      rgba(237, 227, 210, 0.08) 50%,
      rgba(237, 227, 210, 0.04) 100%
    );
    background-size: 200% 100%;
    animation: bms-skeleton 1.6s ease-in-out infinite;
  }

  @keyframes bms-skeleton {
    0%   { background-position: 200% 0; }
    100% { background-position: -200% 0; }
  }
}

/* ── OUR SPACE / FACILITIES PAGE ─────────────────────────────────────────── */

/* Each facility area is an alternating two-column feature: copy on one
   side, a self-contained three-image composition on the other (one tall
   hero beside two stacked supports). The media block is a fixed 2×2 grid
   with the first item spanning both rows, so it always reads as a clean,
   balanced unit — no orphaned tiles at any breakpoint. */
.facility-inner {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-md);
  align-items: center;
}


.facility-heading {
  margin: 0 0 0.75rem;
}

.facility-intro {
  color: var(--bms-birch);
  max-width: 48ch;
  margin: 0 0 var(--space-sm);
}

.facility-media {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  gap: 0.6rem;
  aspect-ratio: 5 / 4;
}

.facility-media-item {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-md);
  background: var(--bms-smoke);
}

.facility-media-item:first-child {
  grid-row: 1 / span 2;
}

.facility-media-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform var(--transition-slow);
}

.facility-media-item:hover img {
  transform: scale(1.04);
}

@media (min-width: 900px) {
  .facility-inner {
    grid-template-columns: 0.85fr 1.15fr;
    gap: var(--space-xl);
  }
  .facility--flip .facility-text { order: 2; }
  .facility--flip .facility-media { order: 1; }
}

@media (max-width: 600px) {
  .facility-media {
    aspect-ratio: auto;
    grid-template-rows: auto;
  }
  .facility-media-item:first-child {
    grid-column: 1 / -1;
    grid-row: auto;
    aspect-ratio: 16 / 10;
  }
  .facility-media-item:not(:first-child) {
    aspect-ratio: 1 / 1;
  }
}

/* ── Astro view transitions ──────────────────────────────────────────────── */
/* Two flavours need disabling:                                              */
/*   1. Native View Transitions (Chrome, Edge, Safari 18+)                   */
/*   2. Astro's JS-based fallback (older Safari, Firefox)                    */
/* Both default to a cross-fade that flashes between page snapshots — and    */
/* particularly causes a visible glitch when restoring a modal via back nav. */
/*
   Safari 18+ respects view-transition-name: none more reliably than the
   animation:none override. Excluding root from the transition entirely
   means the browser doesn't snapshot it, so there's nothing to cross-fade. */
:root {
  view-transition-name: none;
}
::view-transition-old(root),
::view-transition-new(root),
::view-transition-group(root),
::view-transition-image-pair(root) {
  animation: none !important;
  mix-blend-mode: normal;
}

/* Safari/Firefox fallback path: Astro applies these named keyframes to      */
/* the document during transitions. Override them to no-op so the swap is    */
/* effectively instant (matching the native-transition path).                */
@keyframes astroFadeInOut { 0%, 100% { opacity: 1; } }
@keyframes astroFadeIn    { 0%, 100% { opacity: 1; } }
@keyframes astroFadeOut   { 0%, 100% { opacity: 1; } }
@keyframes astroSlideFromRight { 0%, 100% { transform: none; opacity: 1; } }
@keyframes astroSlideToLeft    { 0%, 100% { transform: none; opacity: 1; } }
@keyframes astroSlideFromLeft  { 0%, 100% { transform: none; opacity: 1; } }
@keyframes astroSlideToRight   { 0%, 100% { transform: none; opacity: 1; } }


/* ── Global image lightbox ─────────────────────────────────────────────────
   Click any non-interactive image and it opens here at full resolution.
   Shares the choreography of the session modal: dim+blur arrives quickly,
   the image materialises with a soft scale + focus-pull blur.            */
.image-lightbox {
  position: fixed;
  inset: 0;
  z-index: 9100;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Extra top space so the absolutely-positioned close button (which
     floats above the box) is always visible. */
  padding: 4rem 2rem 2rem;
}
.image-lightbox[hidden] { display: none; }

.image-lightbox-overlay {
  position: absolute;
  inset: 0;
  cursor: pointer;
  background: rgba(21, 17, 13, 0);
  backdrop-filter: blur(0px) saturate(100%);
  -webkit-backdrop-filter: blur(0px) saturate(100%);
  transition:
    background 0.18s ease-out,
    backdrop-filter 0.34s cubic-bezier(0.22, 1, 0.36, 1),
    -webkit-backdrop-filter 0.34s cubic-bezier(0.22, 1, 0.36, 1);
}
.image-lightbox.is-open .image-lightbox-overlay {
  background: rgba(21, 17, 13, 0.92);
  backdrop-filter: blur(14px) saturate(125%);
  -webkit-backdrop-filter: blur(14px) saturate(125%);
}

.image-lightbox-box {
  position: relative;
  z-index: 1;
  /* inline-flex so the box sizes to its content (image + credit) instead
     of stretching to its max — keeps the close button right next to the
     actual image. */
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 0.85rem;
  max-width: 100%;
  max-height: 100%;
  opacity: 0;
  transform: scale(0.96);
  filter: blur(6px);
  transition:
    opacity 0.32s cubic-bezier(0.22, 1, 0.36, 1) 0.05s,
    transform 0.38s cubic-bezier(0.22, 1, 0.36, 1) 0.05s,
    filter 0.28s ease-out 0.05s;
}
.image-lightbox.is-open .image-lightbox-box {
  opacity: 1;
  transform: scale(1);
  filter: blur(0);
}

.image-lightbox-img {
  display: block;
  /* Constrain to the available viewport space after subtracting the
     lightbox padding (top 4rem + bottom 2rem) and the credit line
     reserved area (~2.5rem incl. gap). svh handles mobile UI bars. */
  max-width: min(96vw, 1400px);
  max-height: calc(100vh - 8.5rem); /* fallback for browsers without svh */
  max-height: calc(100svh - 8.5rem);
  object-fit: contain;
  border-radius: 4px;
  box-shadow:
    0 0 0 1px rgba(194, 98, 43, 0.06),
    0 32px 80px rgba(0, 0, 0, 0.7);
}

.image-lightbox-credit {
  flex-shrink: 0;
  margin: 0;
  font-family: 'Staatliches', sans-serif;
  font-size: 0.78rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--bms-birch);
  opacity: 0.7;
  text-align: center;
}

.image-lightbox-close {
  position: absolute;
  top: -2.6rem;
  right: 0;
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(237, 227, 210, 0.08);
  border: 1px solid rgba(237, 227, 210, 0.25);
  border-radius: 50%;
  color: var(--bms-paper);
  cursor: pointer;
  transition: background 220ms ease, border-color 220ms ease, color 220ms ease;
}
.image-lightbox-close:hover {
  background: rgba(237, 227, 210, 0.18);
  border-color: rgba(237, 227, 210, 0.55);
}

/* Cursor hint on images we'll lightbox. The JS adds this attribute.
   Custom SVG cursor: ember magnifier with a dark outer ring for
   contrast against any image background. Falls back to native zoom-in
   if the data URL fails to load in older browsers. */
img[data-lightbox-clickable="true"] {
  cursor:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'><circle cx='8' cy='8' r='5.5' fill='none' stroke='%2315110D' stroke-width='1.6'/><circle cx='8' cy='8' r='5.5' fill='none' stroke='%23EDE3D2' stroke-width='0.9'/><line x1='12' y1='12' x2='17.5' y2='17.5' stroke='%2315110D' stroke-width='1.8' stroke-linecap='round'/><line x1='12' y1='12' x2='17.5' y2='17.5' stroke='%23EDE3D2' stroke-width='1' stroke-linecap='round'/></svg>") 8 8,
    zoom-in;
  transition: filter 280ms ease, transform 480ms cubic-bezier(0.22, 1, 0.36, 1);
}

img[data-lightbox-clickable="true"]:hover {
  /* Subtle ember warmth on hover — reinforces interactivity. */
  filter: brightness(1.06) saturate(1.08);
}

/* ===== PRICING SECTION — MOBILE POLISH ====================================
   Consolidated mobile-only refinements for the pricing block. Existing
   2-column grid collapse rules are kept earlier in the file; this block
   tightens typography, paddings, and the intro/aside stacking so the
   whole section reads cleanly on phones. */
@media (max-width: 768px) {
  /* Stack the intro row: off-peak/peak explainer above the Special
     Offers aside, with breathing room between them. */
  .pricing-intro-row {
    flex-direction: column;
    gap: 1.5rem;
  }
  .pricing-intro {
    font-size: 1.05rem;
    line-height: 1.55;
  }
  .pricing-special-aside {
    flex: 1 1 auto;
    width: 100%;
  }

  /* Tighter card padding at 2-col width so the name/amount don't crowd
     the edges, and smaller display type so big numbers fit on one line. */
  .pricing-card {
    padding: 1.1rem 0.85rem 0.95rem;
  }
  .pricing-card-name {
    font-size: 0.9rem;
    letter-spacing: 0.08em;
  }
  .pricing-card-amount {
    font-size: clamp(1.6rem, 7vw, 2.2rem);
  }
  .pricing-card-per {
    font-size: 0.78rem;
  }
  /* The off-peak/peak split inside Single/Double/5-Pack/10-Pack cards
     stays as a side-by-side at this width, but tightens its inner gap. */
  .pricing-card-prices { gap: 0.5rem; }
  .pricing-card-offlabel,
  .pricing-card-peak-label {
    font-size: 0.6rem;
    letter-spacing: 0.14em;
  }
  .pricing-card-peak-amount { font-size: clamp(1.3rem, 5.5vw, 1.7rem); }

  /* On mobile the standalone gift sub-row doesn't reliably fire taps on
     iOS Safari (the pills sit inside a tight column under the pass card
     and were being swallowed by the card's overlay tap target). Hide
     the row entirely on mobile — the equivalent "Gift this pass →"
     link is rendered inside each pass modal so the action is still
     one tap away. */
  

  /* Private bookings CTA: tighter padding + smaller display type. */
  .pricing-private-cta {
    padding: 1.25rem 1.25rem;
  }
  .pricing-private-cta-title {
    font-size: clamp(1.25rem, 5.5vw, 1.6rem);
  }
  .pricing-private-cta-sub { font-size: 0.9rem; }
  .pricing-private-cta-price { font-size: 1.2rem; }

  /* Fine print trigger: tighten on mobile so the label + arrow stay
     comfortably on one line at narrow widths. */
  .pricing-fineprint-trigger {
    padding: 0.95rem 1rem;
    font-size: 0.85rem;
    letter-spacing: 0.12em;
  }

  /* Section-level: pull the whole pricing block in a bit so cards
     have full available width to breathe. */
  .pricing-section {
    padding-left: 1rem;
    padding-right: 1rem;
    position: relative;
    isolation: isolate;
    background: var(--bms-ink);
  }
  /* Photo layer spans the full (tall) section, but the inner is pinned
     to the viewport via position:sticky, so `cover` only ever sizes the
     image to ONE screenful (~812px) instead of the whole ~2100px
     section. That keeps upscaling low so the photo stays detailed and
     doesn't blow out, and gives a subtle parallax (image holds still
     while the cards scroll over it). Sticky is reliable on iOS, unlike
     position:fixed behind a negative z-index.
     NOTE: do NOT put overflow:hidden on this layer — that makes it the
     scroll container and pins the sticky child to the section top
     instead of the viewport. The sticky child is constrained to this
     layer's box, so it can't bleed past the section anyway. */
  .pricing-bg-layer {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
  }
  .pricing-bg-sticky {
    position: sticky;
    top: 0;
    height: 100vh;
    height: 100vh; /* fallback for browsers without svh */
    height: 100svh;
    /* Gradient (edge softening) over the photo, both tone-mapped by the
       filter: brightness < 1 keeps highlights in check; contrast +
       saturation bring the detail through. */
    background:
      linear-gradient(180deg, rgba(21, 17, 13, 0.42) 0%, rgba(21, 17, 13, 0.26) 45%, rgba(21, 17, 13, 0.52) 100%),
      var(--pricing-bg-mobile, var(--pricing-bg, none)) center / cover no-repeat;
    filter: brightness(0.62) contrast(1.12) saturate(1.05);
  }
  /* Keep the actual pricing content above the photo layer. */
  .pricing-section > .section-inner {
    position: relative;
    z-index: 1;
  }
  

  /* Mobile pricing header — was rendering small/timid against the big
     photo ground. Scale it up so "THERE'S SOMETHING FOR EVERYONE" lands
     as a real section statement, tighten the leading so the two lines
     stack as a confident block, and give the eyebrow more presence. */
  .pricing-section .section-header {
    margin-bottom: 1.25rem;
  }
  .pricing-section .section-header .section-label {
    font-size: 0.95rem;
    letter-spacing: 0.24em;
    margin-bottom: 0.5rem;
  }
  .pricing-section .section-header h2 {
    font-size: clamp(2.5rem, 11vw, 3.4rem);
    line-height: 0.95;
    letter-spacing: 0.005em;
    margin: 0;
  }
  /* Force the headline break after "SOMETHING" and open up the leading a
     touch so the two lines breathe — gives the header more vertical
     presence and lets more of the photo (feet on the bench) show. */
  .pricing-section .section-header h2 .br-mobile {
    display: block;
  }
  .pricing-section .section-header h2 {
    line-height: 1.04;
  }
}


/* ══════════════════════════════════════════════════════════════════════
   TRIAL INTERACTIVITY FEATURES (trial — easy revert: delete this block
   plus the three setupX() calls in main.js setupPage).

   1. Ember cursor    — replaces native cursor with an ember dot
   2. Card tilt       — subtle 3D tilt on cards under pointer
   3. Section dock    — right-rail dot nav on the homepage
   ══════════════════════════════════════════════════════════════════════ */
@media (hover: hover) and (pointer: fine) {

  /* ── 1. Paper-tinted native cursors ──────────────────────────────────
     Custom SVG arrow + pointer-hand replace the OS defaults so the
     cursor carries a subtle paper accent with an ink outline. Native
     motion is kept (no JS-tracked dot) — only the colour changes.
     Magnifier on lightbox-clickable images is declared elsewhere with
     the same palette. */
  /* Default arrow — paper-tinted version of the OS pointer with a
     thin ink outline for contrast against light backgrounds. */
  body {
    cursor:
      url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='22' height='22' viewBox='0 0 22 22'><path d='M2 1 L2 17 L6.5 13 L9 19 L11.5 18 L9 12 L15 12 Z' fill='%23EDE3D2' stroke='%2315110D' stroke-width='1' stroke-linejoin='round'/></svg>") 2 1,
      default;
  }
  /* Pointer hand — compact pointing index finger, paper fill + hair
     ink outline. !important needed because element-level
     `cursor: pointer` declarations beat the plain `a, button` selector. */
  a, a[href], button, [role='button'], [tabindex='0'], label, summary,
  .btn, .session-card, .session-card *, .pricing-card[role='button'],
  .pricing-card[data-pricing], .event-card, .events-dot, .events-nav-btn,
  .footer-icon-btn, input[type='submit'], input[type='button'] {
    cursor:
      url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='20' viewBox='0 0 16 20'><path d='M5.5 1.5 c0-.8 .6-1.4 1.4-1.4 s1.4 .6 1.4 1.4 v7.2 h.6 v-2.1 c0-.7 .6-1.3 1.3-1.3 s1.3 .6 1.3 1.3 v2.1 h.6 v-1.2 c0-.7 .6-1.3 1.3-1.3 s1.3 .6 1.3 1.3 v6.2 c0 3-1.6 4.7-4.4 4.7 h-1.9 c-1.4 0-2.2-.4-2.8-1.4 l-3.4-5 c-.45-.65-.3-1.4 .25-1.85 c.55-.45 1.35-.4 1.85 .15 l.7 .8 z' fill='%23EDE3D2' stroke='%2315110D' stroke-width='0.7' stroke-linejoin='round'/></svg>") 6 1,
      pointer !important;
  }

  /* ── 2. Card tilt ────────────────────────────────────────────────── */
  .tilt-card {
    transition: transform 320ms cubic-bezier(0.2, 0.7, 0.2, 1);
    transform-style: preserve-3d;
  }
  .tilt-card.is-tilting {
    transition: transform 80ms linear;
    transform:
      perspective(900px)
      rotateX(var(--tilt-x, 0deg))
      rotateY(var(--tilt-y, 0deg))
      translateZ(0);
  }

  /* ── 3. Section dock ─────────────────────────────────────────────── */
  .section-dock {
    position: fixed;
    /* Hug the right edge with a slim footprint so the dots sit in the
       gutter and don't overlap section content on narrower laptops. */
    right: 0.35rem;
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
    z-index: 80;
    padding: 0.45rem 0.2rem;
    opacity: 0;
    transition: opacity 320ms ease;
    pointer-events: none;
  }
  /* On laptop widths (no roomy desktop gutter) tuck it in further still. */
  @media (max-width: 1280px) {
    .section-dock { right: 0.15rem; gap: 0.7rem; }
  }
  /* On large displays (iMac etc.) pull it in from the very edge so the dots +
     tooltips clear the (often overlay) scrollbar that sits in the far-right
     gutter when scrolling. */
  @media (min-width: 1600px) {
    .section-dock { right: 1.25rem; }
  }
  .section-dock.is-revealed {
    opacity: 1;
    pointer-events: auto;
  }
  .section-dock-dot {
    width: 9px; height: 9px;
    padding: 0;
    border-radius: 50%;
    border: 1px solid rgba(237, 227, 210, 0.4);
    background: transparent;
    cursor: pointer;
    position: relative;
    transition:
      background 200ms ease,
      border-color 200ms ease,
      transform 220ms cubic-bezier(0.2, 0.7, 0.2, 1),
      box-shadow 220ms ease;
  }
  .section-dock-dot:hover {
    border-color: var(--bms-ember);
    background: rgba(217, 122, 46, 0.45);
    transform: scale(1.35);
  }
  .section-dock-dot.is-active {
    background: var(--bms-ember);
    border-color: var(--bms-ember);
    box-shadow: 0 0 12px 2px rgba(217, 122, 46, 0.55);
    transform: scale(1.2);
  }

  /* Light-section state: only the inactive dot borders flip to ink
     so they don't wash out against the cream — the active + hover
     dots keep their ember tone since ember (#D97A2E) still reads
     fine against the birch/sauna palette. */
  .section-dock.is-on-light .section-dock-dot {
    border-color: rgba(21, 17, 13, 0.55);
  }
  .section-dock-tip {
    position: absolute;
    right: calc(100% + 0.7rem);
    top: 50%;
    transform: translateY(-50%);
    font-family: 'Staatliches', sans-serif;
    font-size: 0.7rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--bms-paper);
    background: rgba(13, 13, 13, 0.88);
    padding: 0.32rem 0.65rem;
    border-radius: 4px;
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transition: opacity 180ms ease, transform 220ms cubic-bezier(0.2, 0.7, 0.2, 1);
    transform-origin: right center;
  }
  .section-dock-dot:hover .section-dock-tip,
  .section-dock-dot:focus-visible .section-dock-tip {
    opacity: 1;
  }

  @media (prefers-reduced-motion: reduce) {
    .tilt-card,
    .tilt-card.is-tilting,
    .section-dock-dot {
      transition: none;
    }
    .tilt-card.is-tilting {
      transform: none;
    }
  }
}


/* Shared scroll-cascade keyframe used by mobile cascades across
   Sit Sweat / On Purpose / Invitation / Sessions / Pricing. Defined
   in the global scope so any @media block can reference it. */
@keyframes offerings-rise {
  from { opacity: 0; transform: translateY(22px); }
  60%  { opacity: 1; }
  to   { opacity: 1; transform: translateY(0); }
}

/* Photo-grid scale-in keyframe used by .offerings-photo-card. */
@keyframes offerings-photo-in {
  from { opacity: 0; transform: scale(0.97); }
  to   { opacity: 1; transform: scale(1); }
}

/* ── TABLET TIER (601-768px) ─────────────────────────────────────────
   iPads in portrait and large phones in landscape fall between the
   mobile (≤600) and desktop (≥769) breakpoints. Without this tier the
   sessions grid collapsed to a single 700px-wide column: eight
   screen-filling cards in a row. Two columns read far better. The
   1fr !important in the ≤768 block still wins below 601px. */
@media (min-width: 601px) and (max-width: 768px) {
  .sessions-grid { grid-template-columns: repeat(2, 1fr) !important; }
}
