/* ==========================================================================
   BACK TO BEAST — marketing site
   The same design system as the app: design/tokens.ts is the source of truth,
   and these values are lifted from it rather than re-picked by eye.

   Rules carried over from CLAUDE.md, because a marketing site that contradicts
   the product it is selling is worse than no site:
     · each accent means exactly one thing, everywhere
     · colour is never the only carrier of meaning
     · 4pt grid, springs not linear easings, ~280ms standard
   ========================================================================== */

:root {
  /* Type — SF Pro on Apple hardware, which is most of this audience. */
  --font: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text", Inter,
    "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

  /* Surfaces — the app's dark theme, unmodified. */
  --bg: #08090b;
  --bg-2: #0b0d11;
  --surface: #12151b;
  --surface-2: #171b22;
  --stroke: rgba(255, 255, 255, 0.09);
  --stroke-2: rgba(255, 255, 255, 0.16);

  --text: #f4f6f8;
  --text-2: rgba(244, 246, 248, 0.72);
  --text-3: rgba(244, 246, 248, 0.48);

  /* Accents. One meaning each — see the table in CLAUDE.md. */
  --charge: #00e39b; /* recovery, progress, cleared, go */
  --load: #ff7a2f;   /* training, effort, modified */
  --flag: #ff4d5e;   /* safety, stop */
  --caution: #ffc24d;/* modify, watch */
  --cool: #4db8ff;   /* data, information */
  --mind: #b388ff;   /* education */

  --charge-soft: rgba(0, 227, 155, 0.12);

  /* 4pt grid */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px; --s5: 20px;
  --s6: 24px; --s7: 32px; --s8: 40px; --s9: 48px; --s10: 64px;
  --s12: 96px; --s14: 128px;

  --r-sm: 10px; --r-md: 14px; --r-lg: 20px; --r-xl: 28px; --r-full: 999px;

  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --dur: 280ms;
  --dur-fast: 160ms;

  --measure: 62ch;
  --page: 1180px;
  --gutter: clamp(20px, 5vw, 48px);
}

/* --------------------------------------------------------------- reset --- */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 17px;
  line-height: 1.6;
  letter-spacing: -0.011em;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* Kept as a final guard, but it must never be load-bearing — it hides evidence
     of an overflow rather than preventing one, which is how the headline clipping
     above went unnoticed. */
  overflow-x: hidden;
}

img, video { display: block; max-width: 100%; }
a { color: inherit; }

/* Focus that is actually visible on a dark page. Keyboard users are not an
   afterthought and the accent already means "go". */
:focus-visible {
  outline: 2px solid var(--charge);
  outline-offset: 3px;
  border-radius: 4px;
}

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--charge);
  color: #04150f;
  padding: var(--s3) var(--s5);
  border-radius: 0 0 var(--r-sm) 0;
  font-weight: 650;
  z-index: 100;
}
.skip:focus { left: 0; }

/* ---------------------------------------------------------- typography --- */

h1, h2, h3 { margin: 0; font-weight: 680; letter-spacing: -0.03em; line-height: 1.05; }

/*
   The clamp MINIMUMS are deliberately low and the middle term is viewport-based.
   These were originally set so the headline just fitted in SF Pro at 390px — which
   worked on an iPhone and overflowed everywhere else, because SF Pro is narrower
   than the fallbacks and the type was effectively sized to one font's metrics.
   Anything that does not have SF Pro (Android, Windows, headless) got a clipped
   headline. Sizing from the viewport instead means it cannot overflow whatever
   font resolves, and `break-word` is the belt to that braces.
*/
.display {
  font-size: clamp(2.125rem, 8.6vw, 5.25rem);
  letter-spacing: -0.042em;
  line-height: 0.98;
  overflow-wrap: break-word;
}
.h2 {
  font-size: clamp(1.625rem, 5.4vw, 3.25rem);
  letter-spacing: -0.035em;
  line-height: 1.04;
  overflow-wrap: break-word;
}
.h3 { font-size: clamp(1.125rem, 2vw, 1.5rem); letter-spacing: -0.024em; line-height: 1.2; }

p { margin: 0; }

.lede {
  font-size: clamp(1.125rem, 1.9vw, 1.4rem);
  line-height: 1.5;
  color: var(--text-2);
  max-width: 34ch;
  letter-spacing: -0.018em;
}
.body { color: var(--text-2); max-width: var(--measure); }
.small { font-size: 0.9375rem; line-height: 1.55; color: var(--text-3); }

.eyebrow {
  font-size: 0.75rem;
  font-weight: 650;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-3);
  margin: 0 0 var(--s4);
}
.eyebrow--charge { color: var(--charge); }

/* --------------------------------------------------------------- layout --- */

.wrap {
  width: 100%;
  max-width: var(--page);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

section { padding-block: clamp(72px, 11vw, 152px); }
.section-tight { padding-block: clamp(56px, 8vw, 104px); }

.rule { height: 1px; background: var(--stroke); border: 0; margin: 0; }

/* ----------------------------------------------------------------- nav --- */

.nav {
  position: fixed;
  inset: 0 0 auto;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s5);
  padding: var(--s3) var(--gutter);
  min-height: 118px;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
  /* Transparent over the hero, then a real surface once you have scrolled past
     it — a permanently frosted bar over a video is just a smudge. */
  background: transparent;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
  border-bottom: 1px solid transparent;
}
.nav[data-stuck="true"] {
  background: rgba(8, 9, 11, 0.82);
  backdrop-filter: saturate(150%) blur(18px);
  -webkit-backdrop-filter: saturate(150%) blur(18px);
  border-bottom-color: var(--stroke);
}
/* `flex: none` and `max-width: none` are both load-bearing, and their absence was
   a silent bug worth naming: the global `img { max-width: 100% }` capped the
   lockup's WIDTH inside this flex row, and for a replaced element a width cap
   back-computes the HEIGHT from the aspect ratio. Every height declared below was
   being quietly overridden — the logo measured 68px no matter what was asked for,
   including an inline style. It looked like a cascade problem and was a sizing one. */
.nav__brand {
  display: flex;
  align-items: center;
  gap: var(--s3);
  text-decoration: none;
  min-height: 44px;
  flex: none;
}
.nav__brand img { max-width: none; }
/* BIG, AND THE SAME SIZE THE WHOLE WAY DOWN.

   The lockup is a mark, a wordmark AND a strapline stacked, so it needs real
   height before the strapline resolves at all: 26px was a smudge, 54 was legible,
   88 reads as branding.

   An earlier version shrank this on scroll to reclaim viewport. It is gone: the
   shrink never applied — the height was being derived from a width constraint
   rather than from the declared value — and a nicety that does not work is worth
   less than a plain rule that does. `width: auto` with a fixed height is what
   makes the declared number the real number. */
.nav__brand img {
  height: 88px;
  width: auto;
}
@media (max-width: 960px) { .nav__brand img { height: 64px; } }
@media (max-width: 520px) { .nav__brand img { height: 52px; } }
.nav__links { display: flex; align-items: center; gap: var(--s7); }
.nav__links a {
  text-decoration: none;
  font-size: 0.9375rem;
  color: var(--text-2);
  transition: color var(--dur-fast) var(--ease);
  /* The label stays 15px; the touch target becomes 44. CLAUDE.md's definition of
     done asks for 44pt minimum and a text link is where that quietly gets missed. */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
.nav__links a:hover { color: var(--text); }
@media (max-width: 860px) { .nav__links { display: none; } }

/* -------------------------------------------------------------- buttons --- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  min-height: 48px; /* 44pt minimum touch target, plus a little */
  padding: 0 var(--s7);
  border-radius: var(--r-full);
  border: 1px solid transparent;
  font: inherit;
  font-size: 1rem;
  font-weight: 620;
  letter-spacing: -0.012em;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: transform var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease),
    border-color var(--dur-fast) var(--ease), opacity var(--dur-fast) var(--ease);
}
.btn:active { transform: scale(0.975); }

.btn--primary { background: var(--charge); color: #04150f; }
.btn--primary:hover { background: #2af0b0; }

.btn--ghost { background: transparent; color: var(--text); border-color: var(--stroke-2); }
.btn--ghost:hover { border-color: var(--text-3); background: rgba(255, 255, 255, 0.04); }

.btn[disabled] { opacity: 0.5; cursor: not-allowed; }

/* ----------------------------------------------------------------- hero --- */

.hero {
  position: relative;
  min-height: 100svh;
  display: grid;
  align-items: end;
  overflow: hidden;
  padding-block: clamp(150px, 20vh, 220px) clamp(48px, 8vh, 88px);
}
.hero__media { position: absolute; inset: 0; z-index: 0; }
.hero__media video,
.hero__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Barely anything. The photograph is already a dark gym with deep shadows —
     the heavy grade this once carried was tuned for bright video and crushed the
     picture to flat black. Pull a little saturation, leave the exposure alone. */
  filter: saturate(0.82) contrast(1.04);
}
/* Two scrims, not one. A flat overlay kills the image; a gradient weighted to the
   bottom — where the type sits — plus a soft vignette keeps the picture alive at
   the top, where there is nothing to read. */
.hero__scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  background:
    linear-gradient(180deg, rgba(8, 9, 11, 0.55) 0%, rgba(8, 9, 11, 0.12) 30%,
      rgba(8, 9, 11, 0.62) 68%, rgba(8, 9, 11, 0.94) 92%, var(--bg) 100%),
    radial-gradient(130% 90% at 60% 35%, transparent 34%, rgba(8, 9, 11, 0.42) 100%);
}
.hero__content { position: relative; z-index: 2; }
.hero__title { max-width: 15ch; }
.hero__title em { font-style: normal; color: var(--charge); }
.hero__lede { margin-top: var(--s6); max-width: 46ch; }
.hero__cta { display: flex; flex-wrap: wrap; gap: var(--s3); margin-top: var(--s8); }
/* On a narrow phone two pills side by side is 380px of button in a 350px column.
   Let them go full width rather than letting one hang off the edge. */
@media (max-width: 420px) {
  .hero__cta .btn { flex: 1 1 100%; }
  .nav .btn { padding: 0 var(--s4); }
}
.hero__note { margin-top: var(--s5); font-size: 0.875rem; color: var(--text-3); }

/* ---------------------------------------------------------------- thesis --- */

.thesis { background: var(--bg-2); }
.thesis__quote {
  font-size: clamp(1.375rem, 4.6vw, 2.75rem);
  overflow-wrap: break-word;
  font-weight: 620;
  letter-spacing: -0.032em;
  line-height: 1.18;
  max-width: 22ch;
}
.thesis__quote span { color: var(--text-3); }

/* ---------------------------------------------------------------- split --- */

.split {
  display: grid;
  gap: clamp(32px, 6vw, 80px);
  align-items: center;
  grid-template-columns: 1fr;
}
@media (min-width: 900px) {
  .split { grid-template-columns: 1fr 1fr; }
  .split--flip .split__media { order: -1; }
}
.split__media { position: relative; border-radius: var(--r-xl); overflow: hidden; }
.split__media video {
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  display: block;
  background: #000;
}
.split__media img {
  width: 100%;
  /* `height: auto` is load-bearing. The width/height ATTRIBUTES on the <img> are
     there so the browser reserves the right box before the file arrives (no layout
     shift) — but a presentational height attribute also wins over `aspect-ratio`,
     which only applies when height is auto. Without this line every split image
     renders 900px tall regardless of the ratio asked for. */
  height: auto;
  aspect-ratio: 16 / 10;
  object-fit: cover;
}
.split__media::after {
  content: "";
  position: absolute;
  inset: 0;
  box-shadow: inset 0 0 0 1px var(--stroke);
  border-radius: inherit;
  pointer-events: none;
}
.split__body > * + * { margin-top: var(--s5); }

/* Rungs — the ladder, drawn rather than described. */
.rungs { list-style: none; margin: var(--s7) 0 0; padding: 0; display: grid; gap: var(--s3); }
.rung {
  display: flex;
  align-items: center;
  gap: var(--s4);
  padding: var(--s4) var(--s5);
  background: var(--surface);
  border: 1px solid var(--stroke);
  border-radius: var(--r-md);
  font-size: 0.9375rem;
}
.rung__dot {
  flex: none;
  width: 10px; height: 10px;
  border-radius: 50%;
  background: var(--stroke-2);
}
.rung[data-state="cleared"] .rung__dot { background: var(--charge); }
.rung[data-state="here"] { border-color: var(--charge); background: var(--charge-soft); }
.rung[data-state="here"] .rung__dot { background: var(--charge); box-shadow: 0 0 0 4px var(--charge-soft); }
.rung__label { flex: 1; }
/* Colour is never alone — every rung carries a word too. */
.rung__state { font-size: 0.75rem; color: var(--text-3); letter-spacing: 0.06em; text-transform: uppercase; }
.rung[data-state="here"] .rung__state { color: var(--charge); }

/* ----------------------------------------------------------------- grid --- */

.cards {
  display: grid;
  gap: var(--s4);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
  margin-top: var(--s9);
}
.card {
  padding: var(--s7);
  background: var(--surface);
  border: 1px solid var(--stroke);
  border-radius: var(--r-lg);
  position: relative;
  overflow: hidden;
}
/* The rail is the app's own card treatment, and it means the same thing here. */
.card::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 3px;
  background: var(--rail, transparent);
}
.card h3 { margin-bottom: var(--s3); }
.card p { color: var(--text-2); font-size: 0.9375rem; }
.card__icon {
  width: 40px; height: 40px;
  display: grid; place-items: center;
  border-radius: var(--r-sm);
  background: rgba(255, 255, 255, 0.05);
  margin-bottom: var(--s5);
}
.card__icon svg { width: 20px; height: 20px; stroke: var(--rail, var(--text-2)); fill: none;
  stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }

/* --------------------------------------------------------------- safety --- */

.safety { background: var(--bg-2); }
.safety__panel {
  margin-top: var(--s8);
  padding: var(--s7);
  border-radius: var(--r-lg);
  border: 1px solid rgba(255, 77, 94, 0.32);
  background: rgba(255, 77, 94, 0.07);
  display: flex;
  gap: var(--s5);
  align-items: flex-start;
}
.safety__panel svg { flex: none; width: 22px; height: 22px; stroke: var(--flag); fill: none; stroke-width: 1.9;
  stroke-linecap: round; stroke-linejoin: round; }

/* --------------------------------------------------------------- pricing --- */

.price {
  display: grid;
  gap: var(--s4);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
  margin-top: var(--s9);
  align-items: start;
}
.plan {
  padding: var(--s8) var(--s7);
  border-radius: var(--r-lg);
  border: 1px solid var(--stroke);
  background: var(--surface);
}
.plan--featured { border-color: var(--charge); background: linear-gradient(180deg, var(--charge-soft), var(--surface) 45%); }
.plan__price { font-size: clamp(2rem, 4vw, 3rem); font-weight: 680; letter-spacing: -0.04em; line-height: 1; }
.plan__period { font-size: 1rem; font-weight: 500; color: var(--text-3); letter-spacing: -0.01em; }
.plan ul { list-style: none; margin: var(--s6) 0 0; padding: 0; display: grid; gap: var(--s3); }
.plan li { display: flex; gap: var(--s3); font-size: 0.9375rem; color: var(--text-2); }
.plan li svg { flex: none; width: 17px; height: 17px; margin-top: 3px; stroke: var(--charge);
  fill: none; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
.plan__badge {
  display: inline-block;
  font-size: 0.6875rem; font-weight: 680; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--charge); background: var(--charge-soft);
  padding: 5px var(--s3); border-radius: var(--r-full); margin-bottom: var(--s4);
}

/* ------------------------------------------------------------- waitlist --- */

.signup { background: var(--bg-2); }
.signup__inner { max-width: 660px; margin-inline: auto; text-align: center; }
.form {
  display: flex; gap: var(--s3); margin-top: var(--s8); flex-wrap: wrap;
  justify-content: center;
}
.form input {
  flex: 1 1 280px;
  min-height: 48px;
  padding: 0 var(--s5);
  font: inherit;
  font-size: 1rem;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--stroke-2);
  border-radius: var(--r-full);
}
.form input::placeholder { color: var(--text-3); }
.form input:focus-visible { border-color: var(--charge); }
.form__note { margin-top: var(--s5); min-height: 1.5em; font-size: 0.9375rem; }
.form__note[data-tone="ok"] { color: var(--charge); }
.form__note[data-tone="error"] { color: var(--caution); }

/* --------------------------------------------------------------- footer --- */

.footer { padding-block: var(--s10) var(--s8); border-top: 1px solid var(--stroke); }
.footer__grid {
  display: grid; gap: var(--s8);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
}
.footer h4 { margin: 0 0 var(--s4); font-size: 0.75rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--text-3); font-weight: 650; }
.footer ul { list-style: none; margin: 0; padding: 0; display: grid; }
.footer a {
  color: var(--text-2);
  text-decoration: none;
  font-size: 0.9375rem;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
.footer a:hover { color: var(--text); }
.footer__legal {
  margin-top: var(--s10);
  padding-top: var(--s6);
  border-top: 1px solid var(--stroke);
  display: flex; flex-wrap: wrap; gap: var(--s4); justify-content: space-between;
}

/* The general-wellness footnote. It is required on every surface carrying
   clinical content and it is not decoration — same string as the app. */
.disclaimer {
  font-size: 0.8125rem;
  line-height: 1.55;
  color: var(--text-3);
  max-width: 70ch;
}

/* --------------------------------------------------------------- motion --- */

/* Reveal on scroll. Deliberately small — 12px and an opacity fade. Anything
   bigger reads as a page that has not finished loading.

   NOTE THE `.js` GATE. Content is visible by default and only hidden once a
   script has confirmed it is running and able to reveal it again. Hiding it in
   plain CSS would mean that a failed script, a blocked bundle or an old browser
   leaves a page of invisible text — which is the single worst way to lose a
   visitor, because it looks like nothing is wrong. */
.js [data-reveal] {
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 600ms var(--ease), transform 600ms var(--ease);
}
.js [data-reveal].is-in { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .js [data-reveal] { opacity: 1; transform: none; transition: none; }
}

/* ---------------------------------------------------------------- prose --- */
/* privacy.html and support.html */

.prose { max-width: 72ch; }
.prose h2 { font-size: clamp(1.4rem, 2.6vw, 1.9rem); margin-top: var(--s10); margin-bottom: var(--s4); }
.prose h3 { margin-top: var(--s8); margin-bottom: var(--s3); }
.prose p, .prose li { color: var(--text-2); }
.prose p + p { margin-top: var(--s4); }
.prose ul { padding-left: var(--s5); display: grid; gap: var(--s2); margin: var(--s4) 0 0; }
.prose strong { color: var(--text); font-weight: 620; }
.prose a { color: var(--charge); text-decoration-color: rgba(0, 227, 155, 0.4); text-underline-offset: 3px; }
.prose table { width: 100%; border-collapse: collapse; margin-top: var(--s5); font-size: 0.9375rem; }
.prose th, .prose td { text-align: left; padding: var(--s3) var(--s4); border-bottom: 1px solid var(--stroke); vertical-align: top; }
.prose th { color: var(--text); font-weight: 620; }
.prose td { color: var(--text-2); }
.updated { font-size: 0.875rem; color: var(--text-3); margin-top: var(--s4); }

/* ------------------------------------------------------------------ film --- */
/* The supplied brand film is a motion-graphics board with its own typography.
   Behind an H1 the two fight and the headline loses, so it gets its own room:
   letterboxed, centred, controls visible, and nothing layered on top of it. */

.film { background: var(--bg-2); }
.film__frame {
  margin: var(--s9) auto 0;
  max-width: 760px;
  border-radius: var(--r-xl);
  overflow: hidden;
  border: 1px solid var(--stroke);
  background: #000;
  box-shadow: 0 40px 120px -40px rgba(0, 0, 0, 0.9);
}
.film__frame video { width: 100%; height: auto; display: block; }
/* The landscape cut is 3:2, so it can take the full frame width — the 620px cap
   existed only because the square board would otherwise run 760px tall and shove
   the sections either side of it apart. */
.film__frame { max-width: 860px; }

/* The hero still, now that it is an image rather than video. */
.hero__media img { width: 100%; height: 100%; object-fit: cover; }

/* ==========================================================================
   ANIMATION
   Everything below is an ENHANCEMENT. Content is legible, laid out and usable
   with none of it, and all of it collapses to nothing under reduced-motion —
   which is not a courtesy here: vestibular disorders are common in exactly the
   older audience the later-years section speaks to.

   Only `transform` and `opacity` are animated. Anything touching layout would
   drop frames on the mid-range phone this audience actually carries.
   ========================================================================== */

/* --- staggered reveals -------------------------------------------------- */
/* Children of a [data-stagger] arrive one after another rather than as a slab.
   The delay is capped: past ~6 items a stagger stops reading as choreography
   and starts reading as lag. */
.js [data-stagger] > * { transition-delay: calc(var(--i, 0) * 70ms); }

/* --- hero ---------------------------------------------------------------- */
/* A slow push-in on the still. 8 seconds and 4% — far enough to feel alive,
   slow enough that you never catch it moving. */
@keyframes heroDrift {
  from { transform: scale(1.04) translate3d(0, 0, 0); }
  to   { transform: scale(1.10) translate3d(0, -1.2%, 0); }
}
.js .hero__media img { animation: heroDrift 18s var(--ease) forwards; will-change: transform; }
/* Footage moves on its own. A push-in on top of a moving shot is two motions
   fighting, and it reads as a wobble rather than as craft. */
.js .hero__media video { animation: none; transform: none; }

/* The headline sets itself line by line. */
@keyframes riseIn {
  from { opacity: 0; transform: translate3d(0, 26px, 0); }
  to   { opacity: 1; transform: none; }
}
.js .hero__title,
.js .hero__lede,
.js .hero__cta,
.js .hero__note { animation: riseIn 900ms var(--ease) backwards; }
.js .hero__title { animation-delay: 80ms; }
.js .hero__lede  { animation-delay: 220ms; }
.js .hero__cta   { animation-delay: 340ms; }
.js .hero__note  { animation-delay: 440ms; }

/* The accent word gets its own beat — the green is the one thing on the page
   that means "go", so it earns the emphasis. */
@keyframes accentIn {
  from { opacity: 0; transform: translate3d(0, 8px, 0) scale(0.97); }
  to   { opacity: 1; transform: none; }
}
.js .hero__title em { display: inline-block; animation: accentIn 700ms var(--ease) 520ms backwards; }

/* --- images -------------------------------------------------------------- */
/* A photograph settling rather than popping. */
.js .split__media img,
.js .split__media video { transform: scale(1.06); transition: transform 1100ms var(--ease); }
.js .split__media[data-reveal].is-in img,
.js .split__media[data-reveal].is-in video { transform: scale(1); }

/* --- rungs --------------------------------------------------------------- */
/* The ladder climbs. Each rung arrives from the left in order, which is the
   one place on this page where the animation is saying something the static
   layout cannot: these are steps, and they happen in sequence. */
@keyframes rungIn {
  from { opacity: 0; transform: translate3d(-14px, 0, 0); }
  to   { opacity: 1; transform: none; }
}
.js .rungs .rung { opacity: 0; }
.js [data-reveal].is-in .rungs .rung {
  animation: rungIn 620ms var(--ease) forwards;
}
.js [data-reveal].is-in .rungs .rung:nth-child(1) { animation-delay: 120ms; }
.js [data-reveal].is-in .rungs .rung:nth-child(2) { animation-delay: 240ms; }
.js [data-reveal].is-in .rungs .rung:nth-child(3) { animation-delay: 360ms; }
.js [data-reveal].is-in .rungs .rung:nth-child(4) { animation-delay: 480ms; }

/* The current rung pulses once when it lands, then stops. A permanent pulse is
   an animation nobody asked for that never goes away. */
@keyframes rungPulse {
  0%   { box-shadow: 0 0 0 0 rgba(0, 227, 155, 0.45); }
  100% { box-shadow: 0 0 0 16px rgba(0, 227, 155, 0); }
}
.js [data-reveal].is-in .rung[data-state="here"] .rung__dot {
  animation: rungPulse 1400ms var(--ease) 700ms 2;
}

/* --- cards --------------------------------------------------------------- */
.card { transition: transform var(--dur) var(--ease), border-color var(--dur) var(--ease); }
@media (hover: hover) {
  .card:hover { transform: translateY(-4px); border-color: var(--stroke-2); }
}
/* The rail draws down as the card arrives. */
.js .card::before { transform: scaleY(0); transform-origin: top; transition: transform 700ms var(--ease) 180ms; }
.js .card[data-reveal].is-in::before { transform: scaleY(1); }

/* --- plans --------------------------------------------------------------- */
@media (hover: hover) {
  .plan { transition: transform var(--dur) var(--ease); }
  .plan:hover { transform: translateY(-4px); }
}

/* --- buttons ------------------------------------------------------------- */
/* A sheen that crosses once on hover. Cheap, and it makes the primary action
   feel like a physical object rather than a rectangle. */
.btn--primary { position: relative; overflow: hidden; }
.btn--primary::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: translateX(-120%);
  background: linear-gradient(105deg, transparent 30%, rgba(255, 255, 255, 0.42) 50%, transparent 70%);
  transition: transform 700ms var(--ease);
  pointer-events: none;
}
@media (hover: hover) { .btn--primary:hover::after { transform: translateX(120%); } }

/* --- later years --------------------------------------------------------- */
.later { background: var(--bg-2); }

/* --- the whole lot, off ------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .js .hero__media img,
  .js .hero__title, .js .hero__lede, .js .hero__cta, .js .hero__note,
  .js .hero__title em,
  .js [data-reveal].is-in .rungs .rung,
  .js [data-reveal].is-in .rung[data-state="here"] .rung__dot { animation: none; }
  .js .rungs .rung { opacity: 1; }
  .js .split__media img,
  .js .split__media video { transform: none; transition: none; }
  .js .card::before { transform: scaleY(1); transition: none; }
  .btn--primary::after { display: none; }
  .card:hover, .plan:hover { transform: none; }
}

/* ==========================================================================
   THE APP CAROUSEL
   It is an app, so the page should show the app. This sits directly under the
   hero, wrapped around the thesis line, because "every other rehab app tells
   you to rest" is a claim and the screens are the evidence for it.
   ========================================================================== */

.showcase { background: var(--bg-2); overflow: hidden; }

.showcase__head { max-width: 24ch; }
.showcase__quote span { color: var(--text-3); }

/* The track scrolls horizontally, snaps, and is a real scroller — so it works
   with a trackpad, a touch drag, the arrow keys and a screen reader, none of
   which a transform-based "carousel" gives you for free. */
.showcase__viewport {
  /* `position: relative` is what makes this element the offsetParent of the
     slides. Without it their `offsetLeft` is measured from the page, not from
     the scroller, and every scrollTo target lands at or below zero — the
     buttons appear to do nothing at all. */
  position: relative;
  margin-top: var(--s9);
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  /* Fade the edges so the strip reads as continuing rather than being cut. */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}
.showcase__viewport::-webkit-scrollbar { display: none; }

.showcase__track {
  display: flex;
  gap: var(--s5);
  padding-inline: var(--gutter);
  width: max-content;
}

.phone {
  scroll-snap-align: center;
  flex: none;
  width: 232px;
  position: relative;
}
@media (max-width: 520px) { .phone { width: 196px; } }

/* A device frame, drawn rather than imaged — one less asset, and it stays sharp
   at any density. */
.phone__frame {
  border-radius: 34px;
  padding: 7px;
  background: linear-gradient(160deg, #2a2f38, #14171d 40%, #0c0e12);
  box-shadow: 0 24px 60px -20px rgba(0, 0, 0, 0.85), 0 0 0 1px rgba(255, 255, 255, 0.06);
}
.phone__screen {
  border-radius: 27px;
  overflow: hidden;
  background: #000;
  display: block;
  aspect-ratio: 1179 / 2556;
}
.phone__screen img { width: 100%; height: 100%; object-fit: cover; display: block; }

.phone__label {
  margin-top: var(--s4);
  font-size: 0.875rem;
  line-height: 1.4;
  color: var(--text-3);
  text-align: center;
  min-height: 2.8em;
  padding-inline: var(--s2);
}
.phone[data-active="true"] .phone__label { color: var(--text-2); }

/* Controls. Colour is never the only carrier — the buttons are labelled for
   assistive tech and the dots expose their index. */
.showcase__controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s4);
  margin-top: var(--s7);
}
.showcase__btn {
  width: 46px; height: 46px;
  display: grid; place-items: center;
  border-radius: var(--r-full);
  border: 1px solid var(--stroke-2);
  background: transparent;
  color: var(--text);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.showcase__btn:hover { border-color: var(--text-3); background: rgba(255,255,255,.05); }
.showcase__btn svg { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }

.showcase__dots { display: flex; gap: var(--s2); }
.showcase__dot {
  width: 7px; height: 7px; padding: 0;
  border: 0; border-radius: 50%;
  background: var(--stroke-2);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.showcase__dot[aria-current="true"] { background: var(--charge); transform: scale(1.5); }

@media (prefers-reduced-motion: reduce) {
  .showcase__viewport { scroll-behavior: auto; }
}

/* ----------------------------------------------------------- highlights --- */
/* A composed sequence, not a grid. The rhythm is deliberate: a feature with the
   most room, a quiet reversed row, a pair that only reads together, and so on —
   so the eye is moved rather than marched. Vertical space between blocks is
   larger than anything else on the page; that space IS the design.

   `height: auto` on every image is load-bearing. A declared height under a
   capped max-width beats aspect-ratio and renders each frame the wrong size.
   See reference_css_layout_gotchas. */

.flow { padding-block: clamp(5rem, 11vw, 10rem); }

.flow__intro {
  text-align: center;
  max-width: 46ch;
  margin-inline: auto;
  margin-bottom: clamp(4rem, 9vw, 8rem);
}
/* The heading wants a tighter measure than the paragraph under it — two full
   lines rather than three short ones with a stranded last word. */
.flow__intro .h2 { max-width: 17ch; margin-inline: auto; text-wrap: balance; }
.flow__intro .lede { margin-inline: auto; max-width: 46ch; }

/* --- shared media treatment ------------------------------------------- */

.flow__media { position: relative; }

.flow__media img {
  display: block;
  width: 100%;
  height: auto;
  max-width: 100%;
  border-radius: 22px;
  border: 1px solid var(--stroke-2);
  background: #000;
  box-shadow: 0 40px 90px -30px rgba(0, 0, 0, 0.85);
}

/* A soft charge-coloured bloom behind the two feature screens only. Sitting
   behind the frame rather than on it, so nothing is tinted. */
.flow__media--glow::before {
  content: "";
  position: absolute;
  inset: -14% -22% -8%;
  background: radial-gradient(60% 45% at 50% 38%, rgba(0, 227, 155, 0.20), transparent 70%);
  filter: blur(28px);
  z-index: -1;
  pointer-events: none;
}

/* --- blocks ------------------------------------------------------------ */

.flow__feature,
.flow__row,
.flow__duo {
  display: grid;
  align-items: center;
  gap: clamp(2.5rem, 6vw, 6rem);
  margin-bottom: clamp(5rem, 12vw, 11rem);
}
.flow > .wrap > *:last-child { margin-bottom: 0; }

.flow__feature { grid-template-columns: minmax(0, 0.85fr) minmax(0, 1fr); }
.flow__row     { grid-template-columns: minmax(0, 0.62fr) minmax(0, 1fr); }
.flow__duo     { grid-template-columns: minmax(0, 1fr) minmax(0, 0.95fr); }

/* Reversed variants swap visual order without moving the source order, so the
   copy still precedes its screens for a screen reader.

   The COLUMN TEMPLATE has to be mirrored too. `order` moves an element to the
   other side but the track sizes stay bound to position, so a reversed row
   otherwise drops its phone into the wide column and renders it oversized —
   which is exactly what happened before this rule existed. */
.flow__feature--reverse > .flow__media,
.flow__duo--reverse > .flow__duo__copy { order: 2; }
.flow__row--reverse > .flow__media { order: 2; }

.flow__feature--reverse { grid-template-columns: minmax(0, 1fr) minmax(0, 0.85fr); }
.flow__row--reverse     { grid-template-columns: minmax(0, 1fr) minmax(0, 0.62fr); }
.flow__duo--reverse     { grid-template-columns: minmax(0, 0.95fr) minmax(0, 1fr); }

/* --- the paired-screen block ------------------------------------------- */

.flow__duo__pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(1rem, 2.5vw, 2rem);
  align-items: start;
}
/* The second phone drops slightly — two frames at identical height read as a
   contact sheet, offset they read as a sequence. */
.flow__media--drop { margin-top: clamp(1.5rem, 5vw, 4.5rem); }

/* --- copy -------------------------------------------------------------- */

.flow__id {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.75rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--charge);
  margin: 0 0 var(--s6);
}

.flow__h {
  font-size: clamp(1.75rem, 3.4vw, 2.6rem);
  line-height: 1.08;
  letter-spacing: -0.03em;
  font-weight: 720;
  margin: 0 0 var(--s6);
  color: var(--text);
}

.flow__copy p:not(.flow__id):not(.flow__note),
.flow__duo__copy p:not(.flow__id):not(.flow__note) {
  color: var(--text-2);
  max-width: 42ch;
  margin: 0;
}

.flow__note {
  margin: var(--s6) 0 0;
  padding-left: var(--s6);
  border-left: 2px solid var(--stroke-2);
  color: var(--text-3);
  font-size: 0.95rem;
  max-width: 44ch;
}

/* --- narrow ------------------------------------------------------------ */

@media (max-width: 900px) {
  .flow__feature,
  .flow__row,
  .flow__duo { grid-template-columns: minmax(0, 1fr); gap: clamp(2rem, 7vw, 3rem); }

  /* Copy first on a phone. A screenshot with no caption yet is just a picture. */
  .flow__feature--reverse > .flow__media,
  .flow__row--reverse > .flow__media,
  .flow__duo--reverse > .flow__duo__copy { order: 0; }
  .flow__feature > .flow__copy,
  .flow__row > .flow__copy,
  .flow__duo > .flow__duo__copy { order: -1; }

  .flow__media { max-width: 340px; margin-inline: auto; }
  .flow__duo__pair { max-width: none; }
  .flow__media--drop { margin-top: 0; }
  .flow__duo__pair .flow__media { max-width: none; }
}

/* ---------------------------------------------------------------- founder --- */
/* The founder section. Three photographs on a rotating stage, and the line the
   whole product is an argument for.

   A clip of her shipped here originally and was removed at his request; the
   quote now closes the section on its own. */

.founder {
  padding-block: var(--s14) var(--s12);
  background:
    radial-gradient(120% 70% at 50% 0%, rgba(0, 227, 155, 0.07), transparent 62%),
    var(--bg-2);
  border-top: 1px solid var(--stroke);
  overflow: clip; /* the back cards swing outside the measure */
}

.founder__head { text-align: center; max-width: 46ch; margin-inline: auto; }

.founder__name {
  font-size: clamp(2.4rem, 7vw, 4.5rem);
  line-height: 1.02;
  letter-spacing: -0.035em;
  margin-top: var(--s3);
  text-wrap: balance;
}

.founder__role {
  margin-top: var(--s5);
  color: var(--text-3);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

.founder__grid { margin-top: clamp(var(--s10), 8vw, var(--s12)); }

/* --- the rotating stage --------------------------------------------------- */
/* Three cards on one carousel. The front card is the subject; the other two sit
   back and angled so the group reads as depth rather than as a row. Positions
   come from `data-slot`, which the script rotates — the CSS owns the geometry
   and the script owns only which card is where. */

.stage {
  position: relative;
  perspective: 1600px;
  height: clamp(430px, 46vw, 600px);
}

.stage__card {
  position: absolute;
  inset: 0;
  margin: auto;
  width: clamp(190px, 23vw, 300px);
  aspect-ratio: 3 / 4.4;
  border-radius: var(--r-xl);
  overflow: hidden;
  border: 1px solid var(--stroke-2);
  background: var(--surface);
  box-shadow: 0 34px 70px -24px rgba(0, 0, 0, 0.78);
  transform:
    translateX(var(--x)) translateZ(var(--z)) rotateY(var(--ry)) scale(var(--sc));
  opacity: var(--op);
  z-index: var(--zi);
  transition:
    transform 900ms var(--ease),
    opacity 900ms var(--ease),
    filter 900ms var(--ease);
  will-change: transform;
}

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

/* A scrim so the index and the stroke stay legible over any photograph. */
.stage__card::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(to top, rgba(6, 8, 10, 0.66), transparent 42%);
  pointer-events: none;
}

.stage__idx {
  position: absolute;
  left: var(--s5); bottom: var(--s4);
  z-index: 1;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  color: var(--text-2);
}

/* Absolute offsets, not percentages: the perspective already shrinks the two back
   cards toward the middle, so a percentage of their own (now smaller) width
   compounds that and the three collapse into a pile. */
.stage__card[data-slot="0"] { --x: 0;      --z: 0;      --ry: 0deg;   --sc: 1;    --op: 1;    --zi: 3; }
.stage__card[data-slot="1"] { --x: 330px;  --z: -180px; --ry: -24deg; --sc: 1;    --op: 0.5;  --zi: 2; }
.stage__card[data-slot="2"] { --x: -330px; --z: -180px; --ry: 24deg;  --sc: 1;    --op: 0.5;  --zi: 2; }

.stage__card[data-slot="1"],
.stage__card[data-slot="2"] { filter: saturate(0.65); }

.stage__dots {
  display: flex;
  justify-content: center;
  gap: var(--s3);
  margin-top: var(--s7);
}

.stage__dot {
  width: 40px; height: 40px;      /* 44pt-ish target; the ink inside is small */
  display: grid; place-items: center;
  background: none; border: 0; padding: 0;
  cursor: pointer;
  border-radius: var(--r-full);
}
.stage__dot::before {
  content: "";
  width: 28px; height: 3px;
  border-radius: var(--r-full);
  background: var(--stroke-2);
  transition: background var(--dur-fast) var(--ease), width var(--dur) var(--ease);
}
.stage__dot[aria-current="true"]::before { background: var(--charge); width: 40px; }
.stage__dot:focus-visible { outline: 2px solid var(--charge); outline-offset: 2px; }

/* --- the line ------------------------------------------------------------- */

/* Standing alone under the stage now, so it is centred at every width. */
.founder__quote {
  margin: clamp(var(--s10), 8vw, var(--s14)) auto 0;
  text-align: center;
}

.founder__quote p {
  font-size: clamp(1.45rem, 3.8vw, 2.45rem);
  line-height: 1.22;
  letter-spacing: -0.025em;
  font-weight: 560;
  text-wrap: balance;
  /* The measure belongs HERE, not on the blockquote: `ch` resolves against the
     element's own font-size, and the blockquote inherits 16px — so 26ch there is
     about 208px and the line breaks six times instead of four. */
  max-width: 27ch;
  margin-inline: auto;
}
.founder__quote em { font-style: normal; color: var(--charge); }

.founder__rule {
  width: 44px; height: 2px;
  background: var(--charge);
  margin: 0 auto var(--s7);
  border: 0;
}

.founder__attrib {
  margin-top: var(--s7);
  color: var(--text-3);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}


@media (max-width: 1000px) {
  .stage__card[data-slot="1"] { --x: 240px; }
  .stage__card[data-slot="2"] { --x: -240px; }
}

@media (max-width: 620px) {
  .stage { height: clamp(370px, 104vw, 470px); }
  .stage__card { width: clamp(165px, 48vw, 215px); }
  .stage__card[data-slot="1"] { --x: 150px; --z: -200px; --op: 0.4; }
  .stage__card[data-slot="2"] { --x: -150px; --z: -200px; --op: 0.4; }
}

/* --- reduced motion ------------------------------------------------------- */
/* No perspective, no rotation, no timer: the three photographs simply sit in a
   row. The section loses an effect and keeps every piece of its content.

   This block is LAST on purpose. It was originally written above the width
   queries, which then re-applied a fixed card width — so on a phone the three
   static cards came to 594px inside a 350px column and ran off the screen. Same
   specificity means the last one wins, and "no motion" has to win. */
@media (prefers-reduced-motion: reduce) {
  .stage {
    height: auto;
    perspective: none;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--s3);
  }
  .stage__card {
    position: static;
    width: auto;
    /* `margin: auto` is what centres the card while it is absolutely positioned.
       Left in place here it makes each grid item shrink to its content and sit
       in the middle of its column instead of filling it. */
    margin: 0;
    inset: auto;
    transform: none;
    opacity: 1;
    filter: none;
    transition: none;
  }
  .stage__dots { display: none; }
}
