/**
 * All Here — QR landing page.
 *
 * Design is a port of the app's Start screen (AllHereApp/app/(tabs)/index.tsx,
 * src/components/CircleButton.tsx, src/components/AttentionSwitch.tsx). Tokens
 * below are copied from AllHereApp/src/theme/index.ts so the two surfaces stay
 * in lockstep; the play button's animation durations and amplitudes are copied
 * from CircleButton so the ring breathes at exactly the same rhythm.
 *
 * Layout: on phones, three full-height screens — brand and links, then the practice
 * and the app, then the booking options — each joined to the next by a scroll cue.
 * From 980px it's a two-column grid with everything on one screen.
 */

:root {
  /* --- theme tokens (AllHereApp/src/theme/index.ts) ------------------- */
  --bg: #00102E;
  --bg-deep: #000823;
  --text: #E8EAF0;
  --text-muted: rgba(232, 234, 240, 0.72);
  --text-dim: rgba(232, 234, 240, 0.48);
  --border: rgba(255, 255, 255, 0.12);
  --border-strong: rgba(255, 255, 255, 0.25);
  /* Hairline separator used by every list surface in the app. */
  --separator: rgba(255, 255, 255, 0.09);
  /* START_BLUE — the Start tab's fixed accent. The big play button keeps this
     colour regardless of journey progression (app/(tabs)/index.tsx). */
  --accent: #3D6BBA;

  --sp-xs: 4px;
  --sp-sm: 8px;
  --sp-md: 16px;
  --sp-lg: 24px;
  --sp-xl: 32px;
  --sp-xxl: 48px;

  --radius-pill: 999px;
  --radius-lg: 18px;

  /* Soft dark drop-shadow applied to every text style in the app, so white
     text stays legible when a bright patch of the shader sits behind it. */
  --text-shadow: 0 1px 3px rgba(0, 0, 0, 0.55);

  /* easeInOutSine — the curve behind the app's breath / glow / sway loops. */
  --ease-sine: cubic-bezier(0.37, 0, 0.63, 1);

  /* Play button diameter. The app derives it from viewport height
     (clamp 120–160 on phones, up to 240 on tablets); same shape here. */
  --play-size: clamp(120px, 18vh, 160px);

  /* Reading column cap — the app stops content stretching past a comfortable
     width. Widened at each breakpoint below. */
  --column-max: 460px;
}

*,
*::before,
*::after { box-sizing: border-box; }

/* --- orphan control ---------------------------------------------------
   Two layers, because neither is sufficient alone:

   1. The last two words of every fixed string of three words or more are
      already glued with a non-breaking space in index.html — the same rule as
      the app's src/utils/noOrphan.ts (which skips two-word strings, since
      gluing those makes one unbreakable token that forces character wrapping
      in a narrow container). That guarantee holds in every browser.
   2. These `text-wrap` hints let the browser do better than the glue where it
      can: `balance` evens out short headings, `pretty` avoids short last lines
      in prose. Both are the Lounge site's house convention, and both degrade
      to normal wrapping where unsupported.                                   */

.title,
.section-title,
.link__title,
.sheet__title { text-wrap: balance; }

.link__sub,
.now,
.booking__venues,
.sheet__subtitle,
.sheet__body p { text-wrap: pretty; }

/* No overflow / contain / filter on <html> or <body>: any of them would make
   the element a containing block for position:fixed descendants, which detaches
   the atmosphere canvas from the viewport and scrolls it off the page. */
html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  min-height: 100%;
  /* Painted background is the shader's fallback: if WebGL is unavailable or
     blocked, this gradient stays visible instead of a black page. Matches
     AtmosphereBackground's FALLBACK_COLORS.lake. */
  background: radial-gradient(circle at 50% 45%, #0A2A33 0%, #041A22 60%, #02121A 100%);
  color: var(--text);
  font-family: 'Montserrat', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  font-weight: 400;
  text-shadow: var(--text-shadow);
  overscroll-behavior-y: none;
}

/* --- animated atmosphere ---------------------------------------------- */

#atmosphere {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  z-index: 0;
  pointer-events: none;
}

/* Content sits above the canvas. */
.page {
  position: relative;
  z-index: 1;
  max-width: var(--column-max);
  margin: 0 auto;
  padding: var(--sp-lg) var(--sp-lg) var(--sp-xxl);
  display: flex;
  flex-direction: column;
  align-items: center;
  min-height: 100svh;
}

/* Wrappers that exist only for one layout each, dissolved in the other. `.screen`
   groups a phone screen; `.practice-block` groups the desktop left-hand column.
   `display: contents` is what lets one set of markup carry both. */
.screen { display: contents; }
.practice-block { display: contents; }

/* Scroll cue from one screen to the next. Hidden by default; the phone media query
   turns it on. */
.scroll-cue { display: none; }

/* --- header ----------------------------------------------------------- */

.brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
}

.logo {
  /* The official All Here logo PNG, as shipped in the app. Never redrawn. */
  width: min(200px, 56vw);
  height: auto;
  margin-top: var(--sp-md);
}

.title {
  /* type.display tier at the app's Start-screen size (22/28, ls 1). */
  font-weight: 900;
  font-size: 22px;
  line-height: 28px;
  letter-spacing: 1px;
  text-transform: uppercase;
  text-align: center;
  margin: var(--sp-lg) 0 0;
  padding: 0 var(--sp-sm);
}

/* --- section headings -------------------------------------------------
   One tier for all four ("Get to know us", "Try a meditation", "Download the Silent
   Mind App", "Booking options"): the app's h3, sentence case. Deliberately NOT the
   overline tier — an 11px uppercase eyebrow read as smaller than the readout it was
   meant to govern, which inverted the hierarchy. Sizes descend strictly: 22 page
   title, 16 section heading, 14 the attention switch, 12 the readout (26 / 18 / 14 /
   12 from 640px). */
.section-title {
  font-weight: 600;
  font-size: 16px;
  line-height: 21px;
  letter-spacing: 0.2px;
  color: var(--text);
  text-align: center;
  margin: 0 0 var(--sp-md);
}

/* --- hero (the practice) ---------------------------------------------- */

.hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-md);
  width: 100%;
}

/* The flex gap already spaces the heading from the switch below it; the tier's own
   bottom margin added to it, so the heading sat 32px from its own content while the
   content items sat 16px apart — it read as floating above the group rather than
   belonging to it. */
.hero .section-title { margin-bottom: 0; }

/* Readout of what the button will play — one line at the caption tier. It is
   state, not a heading: it repeats the pressed pill, and during a rounds session
   it reports "Round 2 of 3" / "Break". */
.now {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0;
  color: var(--text-muted);
  font-size: 12px;
  letter-spacing: 0.3px;
  text-align: center;
}

.now svg { display: block; flex: none; }

.now__dot { color: var(--text-dim); }

/* --- play button (port of src/components/CircleButton.tsx) -------------
   Geometry is expressed in a 0 0 100 100 viewBox so every radius scales with
   --play-size exactly as the app's size-relative maths does:
     innerR = size * 0.46   -> r 46
     stroke = size * 0.018  -> 1.8
     ring   = stroke * 1.4  -> 2.52
     ripple = stroke * 1.2  -> 2.16
     glow r = innerR - stroke * 0.75 -> 44.65
   --------------------------------------------------------------------- */

.play {
  position: relative;
  width: var(--play-size);
  height: var(--play-size);
  padding: 0;
  border: 0;
  background: none;
  color: var(--text);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  flex: none;
}

.play:active { opacity: 0.8; }

.play:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 6px;
  border-radius: 50%;
}

.play__layer {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  pointer-events: none;
}

.play__layer svg {
  width: 100%;
  height: 100%;
  display: block;
}

/* Inward radial-gradient halo, accent-tinted, transparent at the centre so the
   ring outlines it crisply.
   Its brightness keeps the app's own 2700 ms period — deliberately out of step
   with the ring, so the button reads as living rather than metronomic — but its
   SCALE is locked to the ring's breath. The app scales the two by different
   amounts (5% vs 2%) on different periods, and since the halo's bright rim
   (r 44.65) sits a tenth of a unit inside the ring's inner edge (r 44.74), any
   drift between them opens a visible dark gap and closes it again. Same period,
   same amount, no gap; the independent fade keeps the layering. */
.play__glow {
  animation: glow-fade 2700ms var(--ease-sine) infinite alternate,
             breath-scale 3600ms var(--ease-sine) infinite alternate;
}

/* Ghost copy of the ring drifting outward — the "tap me" emanation. Runs only
   in the idle state (see .is-playing below). Detaching from the ring is the
   whole point here, so it keeps its own scale. */
.play__ripple { animation: ripple 4800ms cubic-bezier(0.5, 1, 0.89, 1) infinite; }

/* The ring: scales ±5% and fades 0.55 -> 1 with the breath. Fade and scale are
   separate animations so the halo above can borrow the scale alone. */
.play__ring {
  animation: breath-fade 3600ms var(--ease-sine) infinite alternate,
             breath-scale 3600ms var(--ease-sine) infinite alternate;
}

/* Elapsed-progress arc — audio position while playing, break progress while
   between rounds. */
.play__arc { opacity: 0; transition: opacity 400ms ease; }
.play.is-playing .play__arc { opacity: 1; }

/* Neutral track behind the arc. Only needed during a break, where the ring is
   hidden; while playing, the ring itself reads as the track. */
.play__arc-track { opacity: 0; }

.play__arc-fill {
  /* 2 * PI * 46 = 289.03 — set as the dash array so stroke-dashoffset maps
     linearly to elapsed fraction (driven from links.js). */
  stroke-dasharray: 289.03;
  stroke-dashoffset: 289.03;
  transition: stroke-dashoffset 250ms linear;
}

/* Glyph rides the breath (scale) and the slow sway (rotate) — two animations,
   so two nested elements. */
.play__sway {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  pointer-events: none;
  animation: sway 9000ms var(--ease-sine) infinite alternate;
}

.play__glyph {
  display: grid;
  place-items: center;
  animation: glyph-breath 3600ms var(--ease-sine) infinite alternate;
}

/* Play triangle — the small left margin compensates its optical asymmetry so it
   reads as centred, same trick as the app. */
.play__triangle {
  font-size: calc(var(--play-size) * 0.21);
  line-height: 1;
  margin-left: calc(var(--play-size) * 0.03);
  opacity: 0.95;
}

.play__pause {
  display: none;
  gap: calc(var(--play-size) * 0.07);
}

.play__pause span {
  display: block;
  width: calc(var(--play-size) * 0.05);
  height: calc(var(--play-size) * 0.25);
  border-radius: 2px;
  background: var(--text);
  opacity: 0.9;
}

/* Between-rounds countdown — type.display at the app's break-timer size. */
.play__break {
  display: none;
  font-weight: 900;
  font-size: calc(var(--play-size) * 0.175);
  letter-spacing: 1px;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.play.is-playing .play__triangle { display: none; }
.play.is-playing .play__pause { display: flex; }

/* Playing state — the app mutes the breath to 45 % intensity, stops the sway
   entirely (the audio itself is the motion) and drops the ripple.
   The name lists stay positional against the `animation` shorthands above, so
   each layer keeps its own duration while swapping to the muted keyframes — and
   the halo keeps following the ring's scale. */
.play.is-playing .play__ring { animation-name: breath-fade-muted, breath-scale-muted; }
.play.is-playing .play__glow { animation-name: glow-fade, breath-scale-muted; }
.play.is-playing .play__glyph { animation-name: glyph-breath-muted; }
.play.is-playing .play__sway { animation: none; }
.play.is-playing .play__ripple { opacity: 0; animation: none; }

/* Break state. Must come after the .is-playing block (equal specificity, so
   source order decides): a break is also "playing", but the app's break mode
   replaces the ring with the progress arc and turns the halo off. */
.play.is-break .play__ring { display: none; }
.play.is-break .play__glow { opacity: 0; animation: none; }
.play.is-break .play__arc-track { opacity: 1; }
.play.is-break .play__pause { display: none; }
.play.is-break .play__break { display: block; }

/* Breath, split into scale and fade so the ring and the halo can share the
   geometry while keeping separate brightness rhythms. */
@keyframes breath-scale {
  from { transform: scale(1); }
  to   { transform: scale(1.05); }
}
@keyframes breath-fade {
  from { opacity: 0.55; }
  to   { opacity: 1; }
}
/* 45 % of the amplitude above: scale +2.25 %, opacity floor 0.7975. */
@keyframes breath-scale-muted {
  from { transform: scale(1); }
  to   { transform: scale(1.0225); }
}
@keyframes breath-fade-muted {
  from { opacity: 0.7975; }
  to   { opacity: 1; }
}
@keyframes glow-fade {
  from { opacity: 0.55; }
  to   { opacity: 1; }
}
@keyframes glyph-breath {
  from { transform: scale(1); }
  to   { transform: scale(1.03); }
}
@keyframes glyph-breath-muted {
  from { transform: scale(1); }
  to   { transform: scale(1.0135); }
}
@keyframes sway {
  from { transform: rotate(-1.4deg); }
  to   { transform: rotate(1.4deg); }
}
/* Expand 0 -> 1 over 2800 ms (ring grows + fades), then a 2 s hold so the
   emanations read as deliberate exhales rather than a continuous chain.
   2800 / 4800 = 58.33 %. */
@keyframes ripple {
  0%      { transform: scale(1);   opacity: 0.40; }
  58.33%  { transform: scale(1.4); opacity: 0; }
  100%    { transform: scale(1.4); opacity: 0; }
}
/* Scroll cue — a slow drift down and back, on a period in the same family as the
   button's breath and halo so the page keeps one rhythm. */
@keyframes cue-bob {
  from { transform: translateY(0);   opacity: 0.55; }
  to   { transform: translateY(6px); opacity: 1; }
}

/* --- attention switch (port of src/components/AttentionSwitch.tsx) -----
   Minimal by design: no track, no fill, no halo — just the two words, each with
   a small glyph, and a thin accent rule that glides under the active one. The
   WORD is the state carrier; the glyph reinforces it. */

.attention {
  display: inline-block;
  position: relative;
  padding-bottom: 7px;
}

/* Spacing is asymmetric on purpose: a word sits tight against its own (i), and
   the two pairs are pushed well apart, so it's obvious which (i) belongs to
   which word. */
.attention__row {
  display: flex;
  align-items: center;
}

.attention__opt {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 2px 4px;
  margin-right: 4px;
  border: 0;
  background: none;
  font-family: inherit;
  font-size: 14px;
  letter-spacing: 0.4px;
  color: var(--text-dim);
  text-shadow: var(--text-shadow);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color 260ms ease;
}

.attention__opt[aria-pressed='true'] { color: var(--text); }

.attention__opt svg { display: block; }

.attention__info {
  display: grid;
  place-items: center;
  padding: 2px;
  border: 0;
  background: none;
  color: var(--text-dim);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.attention__info:hover { color: var(--text-muted); }
.attention__info:active { opacity: 0.5; }

/* The wide separation between the two pairs. */
.attention__info--gap { margin-right: 42px; }

/* The only state decoration. Width and offset are measured in JS from the
   active word, so the rule matches its exact extent and glides between them. */
.attention__rule {
  position: absolute;
  left: 0;
  bottom: 0;
  height: 2px;
  width: 0;
  border-radius: 1px;
  background: var(--accent);
  opacity: 0;
  transition: transform 260ms cubic-bezier(0.33, 1, 0.68, 1),
              width 260ms cubic-bezier(0.33, 1, 0.68, 1);
}

/* --- pills ------------------------------------------------------------ */

.pills {
  display: flex;
  justify-content: center;
  gap: var(--sp-sm);
}

/* `display: flex` above would otherwise beat the UA's [hidden] rule, leaving
   both attention groups on screen at once. */
.pills[hidden] { display: none; }

/* Crossfade on the attention switch — matches the app's FadeIn(200) when it
   remounts the pills. */
.pills--enter { animation: pills-in 200ms ease; }

@keyframes pills-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.pill {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 92px;
  padding: 8px 12px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  background: rgba(157, 138, 232, 0.04);
  color: var(--text);
  font-family: inherit;
  text-shadow: var(--text-shadow);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  /* Soft accent glow in the same direction as the button's halo, scaled down so
     the pills feel related without competing with the CTA. */
  box-shadow: 0 0 12px rgba(61, 107, 186, 0.35);
  transition: background 200ms ease, border-color 200ms ease, box-shadow 200ms ease;
}

.pill:active { opacity: 0.7; }

/* Selected state. The app's pills each launch their own track, so it has no
   selected state; here they pick what the single play button will play, so the
   current choice has to be legible. */
.pill[aria-pressed='true'] {
  border-color: var(--accent);
  background: rgba(61, 107, 186, 0.18);
  box-shadow: 0 0 18px rgba(61, 107, 186, 0.55);
}

.pill__duration {
  font-weight: 600;
  font-size: 13px;
  line-height: 16px;
  letter-spacing: 0.4px;
  white-space: nowrap;
}

.pill__sub {
  font-weight: 600;
  font-size: 9px;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.72);
  margin-top: 2px;
  white-space: nowrap;
}

/* --- attention info sheets -------------------------------------------- */

.sheet {
  width: min(420px, calc(100vw - 2 * var(--sp-lg)));
  max-height: min(80svh, 640px);
  padding: var(--sp-lg);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: var(--radius-lg);
  background: var(--bg);
  color: var(--text);
  font-family: inherit;
  text-shadow: var(--text-shadow);
  overflow-y: auto;
}

.sheet::backdrop { background: rgba(0, 0, 0, 0.72); }

.sheet__title {
  font-weight: 600;
  font-size: 16px;
  margin: 0;
}

/* Sits directly under the title in the secondary tone: it names the family of
   practice rather than repeating the heading. */
.sheet__subtitle {
  font-size: 13px;
  color: var(--text-dim);
  margin: 2px 0 var(--sp-md);
}

.sheet__body { color: var(--text-muted); }

.sheet__body p {
  font-size: 14px;
  line-height: 21px;
  margin: 0 0 var(--sp-md);
}

.sheet__body p:last-child { margin-bottom: 0; }

.sheet__close {
  display: block;
  margin: var(--sp-md) 0 0 auto;
  padding: var(--sp-sm);
  border: 0;
  background: none;
  color: var(--accent);
  font-family: inherit;
  font-weight: 900;
  font-size: 13px;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  cursor: pointer;
}

/* --- links ------------------------------------------------------------ */

.links {
  position: relative;
  width: 100%;
  margin-top: var(--sp-xl);
}

/* Scrim behind the link list. The shader is dark but not uniformly so, and 15px
   labels over a moving surface get tiring to read; the app solves the same
   problem by stacking two flat dark overlays over its video background
   (VideoBackground.tsx). Scoped to the list and fading in from the top so the
   atmosphere stays unclouded above it. */
.links::before {
  content: '';
  position: absolute;
  /* Bleeds into the page's own side padding, so on a phone (where the column
     spans the viewport) it reaches the screen edges — without a 100vw overhang
     that would need an overflow rule on an ancestor. */
  inset: 0 calc(var(--sp-lg) * -1) calc(var(--sp-lg) * -1);
  z-index: -1;
  /* Fades in at the top AND out at the bottom. It used to stop hard at its lower
     edge, which was invisible while the list was the last thing on the page —
     but the practice section now sits below it, and the edge showed as a seam. */
  background: linear-gradient(
    to bottom,
    rgba(0, 8, 35, 0) 0,
    rgba(0, 8, 35, 0.58) 72px,
    rgba(0, 8, 35, 0.58) calc(100% - 64px),
    rgba(0, 8, 35, 0) 100%
  );
  /* Feather the left and right edges so the scrim never reads as a dark
     rectangle pasted over the water. */
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 12%, #000 88%, transparent 100%);
          mask-image: linear-gradient(to right, transparent 0, #000 12%, #000 88%, transparent 100%);
  pointer-events: none;
}

/* Link rows. The app's list surfaces are borderless with a hairline separator
   (rgba(255,255,255,0.09)) and an optional 4 px accent rail on the left for
   per-row identity. */
.link-list {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--separator);
}

.link-list li { border-bottom: 1px solid var(--separator); }

.link {
  display: flex;
  align-items: center;
  gap: var(--sp-md);
  padding: var(--sp-md) var(--sp-sm);
  color: var(--text);
  text-decoration: none;
  transition: background 200ms ease;
}

.link:hover,
.link:focus-visible { background: rgba(255, 255, 255, 0.05); }

.link:focus-visible { outline: 1px solid var(--accent); outline-offset: -1px; }

/* Accent rail — marks the featured row (the world map). */
.link--featured { border-left: 4px solid var(--accent); padding-left: 12px; }

.link__icon {
  flex: none;
  width: 22px;
  display: grid;
  place-items: center;
  color: var(--text-dim);
}

.link__icon svg { display: block; }

.link__text { flex: 1 1 auto; min-width: 0; }

.link__title {
  display: block;
  font-weight: 600;
  font-size: 15px;
  line-height: 20px;
}

.link__sub {
  display: block;
  font-size: 12px;
  line-height: 17px;
  letter-spacing: 0.3px;
  color: var(--text-muted);
  margin-top: 1px;
}

/* Preview of the destination — currently just the world map, whose row earns it
   because the map is the draw. Sized in CSS px at half the asset's pixels so it
   stays crisp on a 2× screen. */
.link__thumb {
  display: block;
  flex: none;
  width: 84px;
  height: auto;
  border-radius: 6px;
  border: 1px solid var(--separator);
}

.link__chevron { flex: none; color: var(--text-dim); font-size: 18px; line-height: 1; }

/* --- socials ----------------------------------------------------------- */

.socials {
  list-style: none;
  display: flex;
  justify-content: center;
  gap: var(--sp-md);
  margin: var(--sp-lg) 0 0;
  padding: 0;
}

/* Same circular icon-button shell as the app's account button — hairline border
   over a barely-there fill. */
.social {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.28);
  background: rgba(255, 255, 255, 0.06);
  color: var(--text-muted);
  transition: color 200ms ease, background 200ms ease;
  -webkit-tap-highlight-color: transparent;
}

.social:hover,
.social:focus-visible {
  color: var(--text);
  background: rgba(255, 255, 255, 0.12);
}

.social svg { display: block; }

/* --- app download ----------------------------------------------------- */

.app {
  width: 100%;
  margin-top: var(--sp-xl);
}

/* The two official store badges, side by side. There used to be a shot of the app
   in a hand beside them; it was the tallest thing in the block and made the ask
   busier than "here are the two buttons". */
.stores {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: var(--sp-sm);
}

.stores a {
  display: block;
  transition: opacity 200ms ease;
}

.stores a:hover { opacity: 0.78; }

.stores img {
  display: block;
  height: 46px;
  width: auto;
}

/* --- booking ----------------------------------------------------------
   Sessions bookable near the visitor. This panel stops at a link into
   allherelounge.com/booking/: reserving needs a signed-in account and credits,
   which already live there. See assets/booking.js for how a venue is resolved
   and why the panel changes position rather than changing markup. */

.booking {
  width: 100%;
  margin-top: var(--sp-xl);
}

.booking__note {
  margin: 0 0 var(--sp-md);
  text-align: center;
  font-size: 13px;
  line-height: 19px;
  color: var(--text-muted);
}

/* Every venue, once, under the heading. Takes the place of naming them on each row,
   where the same names repeated four times over. */
.booking__venues {
  margin: calc(var(--sp-md) * -1) 0 var(--sp-md);
  text-align: center;
  font-size: 12px;
  letter-spacing: 0.3px;
  color: var(--text-muted);
}

/* Same borderless-with-hairlines grammar as the link list. */
.booking__options {
  list-style: none;
  margin: 0 0 var(--sp-md);
  padding: 0;
  border-top: 1px solid var(--separator);
}

.booking__options li { border-bottom: 1px solid var(--separator); }

.booking__option {
  display: flex;
  align-items: center;
  gap: var(--sp-md);
  padding: var(--sp-sm);
  color: var(--text);
  text-decoration: none;
  transition: background 200ms ease;
}

.booking__option:hover,
.booking__option:focus-visible { background: rgba(255, 255, 255, 0.05); }

.booking__option:focus-visible { outline: 1px solid var(--accent); outline-offset: -1px; }

.booking__what { flex: 1 1 auto; min-width: 0; }

.booking__session {
  display: block;
  font-weight: 600;
  font-size: 14px;
  line-height: 19px;
}

/* Where it runs, how long, what it costs — filled in from the catalogue, and empty
   until it arrives (or if it never does). */
.booking__meta {
  display: block;
  font-size: 11px;
  line-height: 16px;
  letter-spacing: 0.3px;
  color: var(--text-muted);
  margin-top: 1px;
}

.booking__meta:empty { display: none; }

.booking__chevron { flex: none; color: var(--text-dim); font-size: 18px; line-height: 1; }

/* Primary action, in the pills' grammar so it reads as part of the same family
   rather than as a web button dropped onto the page. */
.booking__cta {
  display: block;
  width: fit-content;
  margin: 0 auto;
  padding: 10px 20px;
  border: 1px solid var(--accent);
  border-radius: var(--radius-pill);
  background: rgba(61, 107, 186, 0.18);
  box-shadow: 0 0 18px rgba(61, 107, 186, 0.45);
  color: var(--text);
  font-weight: 600;
  font-size: 13px;
  letter-spacing: 0.4px;
  text-decoration: none;
  transition: background 200ms ease, box-shadow 200ms ease;
}

.booking__cta:hover { background: rgba(61, 107, 186, 0.3); }

/* Quieter than the CTA: it asks for a permission, so it shouldn't look like the
   thing you came to press. */
.booking__locate {
  display: block;
  margin: var(--sp-md) auto 0;
  padding: var(--sp-sm);
  border: 0;
  background: none;
  color: var(--text-muted);
  font-family: inherit;
  font-size: 12px;
  letter-spacing: 0.3px;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.booking__locate:disabled { color: var(--text-dim); cursor: default; text-decoration: none; }

/* --- phones: two screens ----------------------------------------------
   Screen one is the brand and the links — what should be reached first. Screen
   two is the practice and the app. A centred stack of title, ring and pills was
   precisely the app's Start screen, which made the page read as a copy of the
   app rather than as its doorway; here the ring sits left with the three lengths
   beside it instead. */

@media (max-width: 639px) {
  html {
    /* `proximity`, not `mandatory`: the second screen can grow past a short
       phone's viewport, and mandatory snapping there fights the user trying to
       reach its lower half. */
    scroll-snap-type: y proximity;
    scroll-behavior: smooth;
  }

  :root {
    /* A little smaller than the centred layout's ring, to leave room for the
       column of lengths beside it — but not so small that it stops being the
       thing your eye lands on first. */
    --play-size: clamp(132px, 17vh, 150px);
  }

  /* The screens carry their own spacing, so the page's padding can't push the
     first one past the fold and hide the cue. */
  .page { padding-top: 0; padding-bottom: 0; }

  .screen {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    /* Content centres as one group. Letting a child grow into the leftover
       height instead pinned the wordmark to the top of the window and opened a
       300px hole below it. */
    justify-content: center;
    width: 100%;
    min-height: 100svh;
    scroll-snap-align: start;
    /* Room for the cue, which is pinned to the bottom of the first screen. */
    padding-bottom: var(--sp-xxl);
  }

  .scroll-cue {
    /* Pinned rather than laid out in the flow, so it takes no part in centring
       the group above. Centred with a negative margin, not translateX — the bob
       animation owns `transform`. */
    position: absolute;
    bottom: var(--sp-sm);
    left: 50%;
    margin-left: -80px;
    width: 160px;
    display: grid;
    justify-items: center;
    gap: 2px;
    color: var(--text-dim);
    text-decoration: none;
    -webkit-tap-highlight-color: transparent;
  }

  .scroll-cue__label {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 1.4px;
    text-transform: uppercase;
    white-space: nowrap;
  }

  /* Only the chevron moves. Bobbing the label with it made the whole cue drift,
     which reads as the page settling rather than as an invitation to scroll. */
  .scroll-cue svg {
    display: block;
    animation: cue-bob 2200ms var(--ease-sine) infinite alternate;
  }

  /* Only the last screen has no cue under it, so only it can give that room back. */
  #book { padding-bottom: var(--sp-lg); }

  .links { margin-top: var(--sp-lg); }

  /* Clear air between the practice and the download ask, so they read as two
     separate offers rather than one continuous block. A full step and a half:
     at 48px the two still read as one run of centred text. */
  .app { margin-top: calc(var(--sp-xxl) + var(--sp-lg)); }

  .booking { margin-top: var(--sp-lg); }

  .hero {
    display: grid;
    /* Both columns sized to their content and the pair centred, rather than a
       `1fr` column that stretches the lengths to whatever is left over. The rows
       that span both columns are all narrower than the two columns combined, so
       none of them inflates the grid. */
    grid-template-columns: auto auto;
    justify-content: center;
    grid-template-areas:
      'label  label'
      'switch switch'
      'now    now'
      'play   pills';
    column-gap: var(--sp-md);
    /* Full-step gutters. At 8px the four stacked rows read as one dense block. */
    row-gap: var(--sp-md);
    align-items: center;
  }

  .hero .section-title { grid-area: label; margin: 0; }
  .attention { grid-area: switch; justify-self: center; }
  .play { grid-area: play; }

  /* Spans both columns rather than sitting in the narrow one beside the ring:
     "Three minutes meditation" needs about 210px on one line, which no column
     narrow enough to leave room for the ring can give it. Below that width the
     non-breaking-space glue starts pushing the first word onto its own line. */
  .now { grid-area: now; }

  .pills {
    grid-area: pills;
    flex-direction: column;
    align-self: start;
    /* One fixed width for all three rather than stretch-to-column: stretching
       made them read as a heavy slab, and letting each size to its own content
       leaves a ragged edge because the sub-labels differ in length. */
    width: 128px;
  }

  /* The title needs air under the wordmark; at 24px the two ran together. */
  .title { margin-top: var(--sp-xl); }
}

/* --- wider viewports --------------------------------------------------
   Tablets and small laptops get a roomier single column; from 980px the page
   splits into two, and everything fits one screen without scrolling. */

@media (min-width: 640px) {
  :root { --column-max: 580px; }
  .title { font-size: 26px; line-height: 32px; letter-spacing: 1.2px; }
  .section-title { font-size: 18px; line-height: 24px; }
  .link__thumb { width: 104px; }
  .hero { padding-top: var(--sp-lg); }
}

@media (min-width: 980px) {
  :root {
    --column-max: 1120px;
    /* 20vh rather than 22: with the links panel now pairing with the short brand
       row, the two columns together needed 25px more than a 1280x800 laptop has.
       This and the page padding below buy that back without compressing the row
       rhythm, and 160px is still comfortably the biggest thing on screen. */
    --play-size: clamp(150px, 20vh, 190px);
  }

  .page {
    display: grid;
    /* Hero a touch wider than the panels so the button stays the focal point. */
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
    /* Rows are all `auto`, never `1fr`: a fractional row would swallow the free
       space and leave `align-content` nothing to distribute, which pins the
       brand to the top of the window instead of centring the stack. */
    grid-template-rows: auto auto auto;
    /* Left: the brand, then the practice and the app download as one block. Right:
       the links and the booking options. Pairing the tall links panel
       with the short brand row (rather than with the practice) is what keeps the
       whole thing inside a 1280x800 laptop — the rows are shared between columns,
       so a short item next to a tall one wastes exactly that difference. */
    /* Both columns end on the same row by construction rather than by luck: the
       practice block spans rows 2-3 and the booking panel sits in row 3, so their
       bottom edges are the same grid line. */
    grid-template-areas:
      'brand    links'
      'practice links'
      'practice booking';
    column-gap: clamp(40px, 5vw, 88px);
    /* `min-height` (not height) above means the grid grows when the content is
       taller than the viewport, so centring here can't push anything out of
       reach on a short window. */
    align-content: center;
    padding: var(--sp-lg);
  }

  /* Anchored to the bottom of its row, so the gap below it is a value we set
     rather than whatever the grid has left over — which is what makes the
     symmetry below possible. */
  .brand { grid-area: brand; align-self: end; }

  /* The merge: one grid item holding the practice and the app download, so the two
     read as a single offer — try a meditation, and here's where it lives. */
  .practice-block {
    grid-area: practice;
    /* Stretched and laid out as a column, so its bottom edge IS the row's bottom
       edge whichever side of the grid turns out taller. With `start` the alignment
       only held while the left column was the taller one; the moment the right grew
       past it the booking frame slid 12px below the download buttons. */
    align-self: stretch;
    display: flex;
    flex-direction: column;
    /* Equal to the minimum gap from the pills down to the hairline below, so the
       practice group sits optically centred between the page title and the download
       ask rather than crowded against one of them. */
    margin-top: var(--sp-xxl);
  }

  /* Absorbs any surplus height, so the download block stays pinned to the bottom of
     the span while keeping its own margin as a floor. `margin-top: auto` on .app
     alone would have collapsed that floor to nothing whenever the left column was
     the taller one. */
  .hero { margin-bottom: auto; }


  /* Inside the block it's no longer a panel of its own — it shares the hero's bare
     treatment over the atmosphere, which is what makes the two read as one block.
     "One block" still needs two groups inside it, and since both headings keep the
     same tier, the separation has to do that work on its own: a double step of air
     plus a hairline, the same divider the app puts between list rows. Shrinking the
     download heading instead made it read as a stray label. */
  .app {
    margin-top: var(--sp-xxl);
    padding-top: var(--sp-xxl);
    border-top: 1px solid var(--separator);
  }



  .links { grid-area: links; align-self: start; margin-top: 0; }

  /* Stretched, not centred: the row is sized by the left column's content, so
     filling it is what brings the panel's bottom border level with the download
     buttons opposite. */
  .booking {
    grid-area: booking;
    align-self: stretch;
    margin-top: var(--sp-md);
  }
  /* Five activities are taller than the column has room for once everything else
     is placed, so the list scrolls inside the panel rather than pushing the page
     past the fold. Capped at three rows: enough to read as a list and to make the
     fourth one visibly cut off, which is what says "there is more". The permanent
     bottom fade is safe here because the list is always longer than the cap. */
  .booking__options {
    max-height: 153px;
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-width: thin;
    scrollbar-color: rgba(255, 255, 255, 0.22) transparent;
    /* The fade sits on the bottom edge only. Reaching further up dimmed a row that
       was fully present, which read as a rendering fault rather than as "there is
       more below". */
    -webkit-mask-image: linear-gradient(to bottom, #000 88%, transparent 100%);
            mask-image: linear-gradient(to bottom, #000 88%, transparent 100%);
  }

  .booking__options::-webkit-scrollbar { width: 6px; }
  .booking__options::-webkit-scrollbar-track { background: transparent; }
  .booking__options::-webkit-scrollbar-thumb {
    background: rgba(255, 255, 255, 0.22);
    border-radius: 3px;
  }

  /* Floating glass panels replace the phone layout's scrim — the same grammar as
     the leaderboard's standings panel. */
  .links,
  .booking {
    padding: var(--sp-lg);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    background: rgba(0, 8, 35, 0.42);
  }

  .links::before { display: none; }

  .logo { margin-top: 0; }
}

/* --- reduced motion ---------------------------------------------------
   Honour the OS setting: hold the ring, halo and glyph still, and drop the
   ripple. The shader keeps running — it's the page's background image and its
   motion is slow and non-vestibular — but the button stops pulsing. */
@media (prefers-reduced-motion: reduce) {
  .play__glow,
  .play__ring,
  .play__glyph,
  .play__sway { animation: none; }
  .play__glow { opacity: 0.8; }
  .play__ring { opacity: 1; }
  .play__ripple { display: none; }
  .play__arc-fill { transition: none; }
  .pills--enter { animation: none; }
  .attention__rule { transition: none; }
  /* The cue stays visible, just still — and the jump to the second screen
     becomes instant rather than a long animated scroll. */
  .scroll-cue svg { animation: none; opacity: 0.85; }
  html { scroll-behavior: auto; }
}
