/* ═══════════════════════════════════════════════════════════════════════
   KETT — STYLE
   Tokens read off Figma (Flect-Visuals '26 · Kett · Landing 843:3933,
   1728 grid). Same fluid method as Pebb/Totwo: every size is
   clamp(min, design-px / 17.28 vw, design-px), so any desktop width under
   1728 keeps the exact composition.
   ═══════════════════════════════════════════════════════════════════════ */

:root {
  /* ── color (exact Figma fills) ── */
  --ink:       #2e2733;   /* body copy, headings          */
  --btn-bg:    #372e3d;   /* pills                        */
  --accent:    #6f6091;   /* editorial serif accents      */
  --paper:     #f5f1ea;   /* page                         */
  --mist:      #e7e1f0;   /* section + card tint          */
  --lavender:  #cfc5e6;   /* hero glow, card tail, field  */
  --mut:       #85808c;   /* placeholder, copyright       */
  --hairline:  rgba(46, 39, 51, 0.25);

  /* ── type ── */
  --font-sans:  'PP Neue Montreal', 'Helvetica Neue', Arial, sans-serif;
  --font-serif: 'PP Editorial New', Georgia, serif;

  --fs-display: clamp(46px, 8.102vw, 140px);  /* hero h1        140 */
  --fs-h2:      clamp(34px, 4.167vw, 72px);   /* section head    72 */
  --fs-h3:      clamp(24px, 2.778vw, 48px);   /* physio list     48 */
  --fs-h4:      clamp(24px, 2.431vw, 42px);   /* step + manifesto 42 */
  --fs-h5:      clamp(20px, 1.736vw, 30px);   /* row title       30 */
  --fs-lead:    clamp(18px, 1.505vw, 26px);   /* hero sub        26 */
  --fs-body:    clamp(16px, 1.157vw, 20px);   /* body            20 */
  --fs-num:     clamp(22px, 1.852vw, 32px);   /* ( 01 )          32 */
  --fs-btn:     clamp(16px, 1.157vw, 20px);   /* form button     20 */
  --fs-btn-sm:  clamp(15px, 0.984vw, 17px);   /* header button   17 */
  --fs-caption: clamp(13px, 0.868vw, 15px);   /* footer          15 */

  /* ── space ── */
  --pad-x:     clamp(20px, 6.944vw, 120px);   /* 120 */
  --sec-py:    clamp(72px, 8.681vw, 150px);   /* 150 */
  --gap-head:  clamp(40px, 4.63vw, 80px);     /*  80 */
  --gap-block: clamp(32px, 3.704vw, 64px);    /*  64 */
  --gap-copy:  clamp(20px, 1.852vw, 32px);    /*  32 */
  --header-p:  clamp(12px, 0.926vw, 16px);    /*  16 */
  --radius-card: clamp(28px, 5.324vw, 92px);  /*  92 */

  /* ── controls (pills) ── */
  --ctl-h:        clamp(54px, 3.704vw, 64px);    /*  64 */
  --ctl-px:       clamp(24px, 2.083vw, 36px);    /*  36 */
  --input-px:     clamp(18px, 1.62vw, 28px);     /*  28 */
  --input-w:      clamp(190px, 20.833vw, 360px); /* 360 */
  --header-btn-h: clamp(38px, 2.604vw, 45px);    /*  45 */

  /* ── motion ── */
  --ease:      cubic-bezier(0.22, 1, 0.36, 1);
  --ease-luxe: cubic-bezier(0.16, 1, 0.22, 1);

  --muted-opacity: 0.7;
}

/* ═══════════ FONTS ═══════════ */
@font-face {
  font-family: 'PP Neue Montreal';
  src: url('../assets/fonts/PPNeueMontreal-Regular.woff') format('woff');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'PP Neue Montreal';
  src: url('../assets/fonts/PPNeueMontreal-Medium.woff') format('woff');
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'PP Editorial New';
  src: url('../assets/fonts/PPEditorialNew-Regular.woff') format('woff');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'PP Editorial New';
  src: url('../assets/fonts/PPEditorialNew-Ultralight.woff') format('woff');
  font-weight: 200; font-style: normal; font-display: swap;
}

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

html {
  -webkit-text-size-adjust: 100%;
  overscroll-behavior-y: none;
  background: var(--paper);
  scrollbar-width: none;
}
html::-webkit-scrollbar { width: 0; height: 0; display: none; }

body {
  font-family: var(--font-sans);
  font-weight: 400;
  background: var(--paper);
  color: var(--ink);
  line-height: 1.4;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: clip;
}

a { color: inherit; text-decoration: none; }
img, svg { display: block; max-width: 100%; }
img { height: auto; }
button { font-family: inherit; cursor: pointer; background: none; border: none; color: inherit; }
ul, ol { list-style: none; }
figure { margin: 0; }
::selection { background: var(--ink); color: var(--paper); }

:focus-visible { outline: 2px solid currentColor; outline-offset: 3px; border-radius: 4px; }
.btn:focus-visible { outline-color: var(--ink); }

/* the Figma Landing frame clips its children; this is that frame */
.page-end { position: relative; overflow: clip; }

/* ═══════════ TYPE ═══════════ */
.h1 {
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: var(--fs-display);
  line-height: 0.9;
  letter-spacing: -0.03em;
}
.h2 {
  font-weight: 500;
  font-size: var(--fs-h2);
  line-height: 1;
  letter-spacing: -0.03em;
}
.h3 {
  font-weight: 500;
  font-size: var(--fs-h3);
  line-height: 1.06;
  letter-spacing: -0.03em;
}
.h4 {
  font-weight: 500;
  font-size: var(--fs-h4);
  line-height: 1;
  letter-spacing: -0.03em;
}
.body {
  font-weight: 400;
  font-size: var(--fs-body);
  line-height: 1.3;
  letter-spacing: -0.01em;
}
/* the editorial cut carries every accent, at the same size as its host */
.serif {
  font-family: var(--font-serif);
  font-weight: 400;
  font-style: normal;
  color: var(--accent);
}
.muted { opacity: var(--muted-opacity); --reveal-opacity: var(--muted-opacity); }

/* widow control on the long measures */
.body, .hero-sub, .step-desc, .manifesto-sub { text-wrap: pretty; }

/* ═══════════ BUTTON + LETTER ROLL ═══════════ */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: var(--ctl-h);
  padding: 0 var(--ctl-px);
  border-radius: 100px;
  background: var(--btn-bg);
  color: var(--paper);
  font-size: var(--fs-btn);
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1;
  white-space: nowrap;
  flex: 0 0 auto;
  -webkit-tap-highlight-color: transparent;
  transition: background-color 0.3s var(--ease), opacity 0.3s var(--ease);
}
.btn:disabled { opacity: 0.55; cursor: default; }
.btn--sm {
  height: var(--header-btn-h);
  padding: 0 clamp(18px, 1.505vw, 26px);
  font-size: var(--fs-btn-sm);
  letter-spacing: -0.01em;
}

/* The header CTA is redundant while the hero's own form is on screen, and
   again once the closing form arrives — it only earns its place in between.
   .cta-active already carries both conditions. Visibility is delayed off so
   the fade can finish before the button leaves the tab order. */
.header-cta {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translate3d(12px, 0, 0);
  transition:
    opacity 0.5s var(--ease-luxe),
    transform 0.6s var(--ease-luxe),
    background-color 0.3s var(--ease),
    visibility 0s linear 0.5s;
}
body.cta-active .header-cta {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: none;
  transition:
    opacity 0.5s var(--ease-luxe),
    transform 0.6s var(--ease-luxe),
    background-color 0.3s var(--ease);
}
.btn-label { display: inline-flex; line-height: 1; }
.roll-space { display: inline-block; }
.roll-char {
  position: relative;
  display: inline-block;
  overflow: hidden;
  line-height: 1;
  /* room for descenders inside the clip without changing layout height */
  padding-bottom: 0.14em;
  margin-bottom: -0.14em;
}
.roll-char > span {
  display: block;
  transition: transform 0.55s var(--ease-luxe);
  transition-delay: var(--cd, 0ms);
}
.roll-char .r-bot { position: absolute; inset: 0; transform: translateY(110%); }
.btn:hover .roll-char .r-top { transform: translateY(-110%); }
.btn:hover .roll-char .r-bot { transform: translateY(0); }

/* spinner while a submit is in flight */
.btn.is-loading .btn-label { visibility: hidden; }
.btn.is-loading::after {
  content: '';
  position: absolute;
  width: 18px; height: 18px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: btn-spin 0.7s linear infinite;
}
.btn.is-loading { position: relative; }
@keyframes btn-spin { to { transform: rotate(360deg); } }

/* ═══════════ FORM ═══════════ */
/* no row gap: the Turnstile host renders 0-tall until it actually has a
   challenge to show, and a flex gap would still reserve space around it */
.access-form { display: flex; flex-direction: column; }
.form-row { display: flex; align-items: center; gap: 10px; }
.form-note { margin-top: clamp(10px, 0.926vw, 16px); }
.input {
  height: var(--ctl-h);
  width: var(--input-w);
  padding: 0 var(--input-px);
  border: 1px solid var(--lavender);
  border-radius: 100px;
  background: #ffffff;
  font-family: inherit;
  font-size: var(--fs-btn);
  font-weight: 500;
  letter-spacing: -0.02em;
  color: var(--ink);
  transition: box-shadow 0.25s var(--ease), border-color 0.25s var(--ease);
}
.input::placeholder { color: var(--mut); }
.input:focus { outline: none; box-shadow: inset 0 0 0 1px var(--accent); border-color: var(--accent); }
.input.is-error { border-color: #c34434; box-shadow: inset 0 0 0 1px #c34434; }
.form-note {
  font-size: var(--fs-caption);
  letter-spacing: -0.01em;
  line-height: 1.3;
  color: var(--mut);
}
.form-note.is-error { color: #c34434; }
/* the comp has no note under the field — the slot only exists so a submit
   error has somewhere to land */
.form-note:empty { display: none; }
.cf-turnstile:empty { display: none; }

/* ═══════════ HEADER ═══════════ */
.site-header {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--header-p) var(--header-p) var(--header-p) clamp(16px, 1.852vw, 32px);
  pointer-events: none;
}
.site-header > * { pointer-events: auto; }
.logo { display: block; }
.logo-mark {
  display: block;
  width: clamp(84px, 6.308vw, 109px);   /* 109 */
  aspect-ratio: 109 / 28;
  background: var(--ink);
  -webkit-mask: url('../assets/logo.svg') no-repeat center / contain;
  mask: url('../assets/logo.svg') no-repeat center / contain;
}

/* ═══════════ HERO ═══════════ */
.hero {
  position: relative;
  /* 1042 tall on the 1728 grid, capped so a short window never crops it */
  min-height: min(60.301vw, 100svh);
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: min(13.889vw, 20svh) var(--pad-x) var(--sec-py);
  /* The render is taller than the section and is meant to hang over the
     block below, shadow and all. Clipping is off, and the raised stacking
     level puts the whole hero over the photo that follows — without it the
     photo's own positioned layer would paint on top. */
  overflow: visible;
  z-index: 2;
  /* Figma radial: centred on the top edge, and so wide horizontally that it
     reads as a vertical wash — sampled off the comp to confirm. */
  background:
    radial-gradient(392% 100% at 50% 0%,
      var(--lavender) 0%,
      var(--mist) 54.8%,
      var(--paper) 100%);
}
.hero-copy {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: clamp(24px, 2.315vw, 40px);
  max-width: 100%;
}
.hero-title {
  /* 692 of the 1728 grid — the measure that gives the comp's two lines */
  max-width: min(100%, 40.046vw);
}
.hero-sub {
  font-size: var(--fs-lead);
  font-weight: 500;
  line-height: 1.34;
  letter-spacing: -0.02em;
  opacity: var(--muted-opacity);
  --reveal-opacity: var(--muted-opacity);
  max-width: min(100%, 41.667vw);
}
.hero-mockup {
  position: absolute;
  z-index: 1;
  /* 1199 wide at x 616 of 1728 — right-anchored so a short window shrinks it
     toward the edge instead of marching it inward. The svh cap keeps the
     render inside the section on window ratios wider than the comp's. */
  /* 1199/1042 = 115.07% of the hero's own height, so at the comp's ratio the
     svh arm lands on exactly the same number the vw arm does */
  --mock-w: min(69.387vw, 115.07svh);
  width: var(--mock-w);
  right: -5.035vw;                        /* 1815 - 1728 = 87 */
  top: calc(var(--mock-w) * -0.00334);    /* -4 */
}
.hero-mockup picture { display: block; }
.hero-mockup img { width: 100%; height: auto; }
/* the foot-of-section fade is a phone-only device */
.hero-fade { display: none; }

/* hero entrance: the mockup glides up as the lines rise */
.hero-mockup img {
  opacity: 0;
  transform: translate3d(0, 4.4%, 0) scale(1.03);
  animation: hero-mock-in 1.5s var(--ease-luxe) 0.12s both;
}
@keyframes hero-mock-in {
  to { opacity: 1; transform: none; }
}

/* ═══════════ MANIFESTO ═══════════ */
.manifesto {
  position: relative;
  /* 960 of 1728 */
  height: 55.556vw;
  overflow: clip;
  background: var(--paper);
}
.manifesto-bg { position: absolute; inset: 0; display: block; }
.manifesto-bg img { width: 100%; height: 100%; object-fit: cover; }

/* Full-size layer cut down to the right half rather than a half-width box:
   that way the blurred copy keeps the same horizontal mapping as the sharp
   shot and the two register across the seam, with no percentage bookkeeping.

   A CSS blur fades out at the image's own edges, where Figma's
   background-blur clamps instead. The layer is therefore grown past the top
   and bottom of the section and clipped back to it, so that fade lands
   outside the visible area and no soft band shows along either edge. */
.manifesto-blur {
  position: absolute;
  inset: -96px 0;
  clip-path: inset(96px 0 96px 50%);
}
.manifesto-blur img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Figma's 64 background-blur radius is about twice the CSS deviation */
  filter: blur(32px);
}
.manifesto-card {
  position: absolute;
  top: 0; right: 0; bottom: 0;
  width: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(28px, 3.241vw, 56px) var(--pad-x) clamp(28px, 3.241vw, 56px) clamp(24px, 3.241vw, 56px);
  /* the comp's scrim: clear where the plate meets the photo, solid paper by
     the right edge, so the copy always sits on flat colour */
  background: linear-gradient(90deg,
    rgba(245, 241, 234, 0.41) 0%,
    rgba(245, 241, 234, 1) 100%);
}
.manifesto-copy {
  /* 438 of the 1728 grid */
  width: min(100%, 25.347vw);
  display: flex;
  flex-direction: column;
  gap: clamp(18px, 1.852vw, 32px);
}
.manifesto-title { line-height: 1.3; }
.manifesto-forus {
  font-family: var(--font-serif);
  font-size: var(--fs-h2);
  line-height: 1.3;
  letter-spacing: -0.03em;
}
.manifesto-forus .serif { font-size: inherit; }
.manifesto-sub {
  line-height: 1.5;
  --reveal-opacity: var(--muted-opacity);
  margin-top: clamp(6px, 0.926vw, 16px);
}

/* ═══════════ SECTION SHELL ═══════════ */
.section {
  padding: var(--sec-py) var(--pad-x);
  display: flex;
  flex-direction: column;
}

/* ═══════════ WHAT'S INSIDE THE APP ═══════════ */
.inside {
  gap: var(--gap-head);
  /* paper at the top, settling into the tint at the bottom */
  background: linear-gradient(180deg, var(--paper) 0%, var(--mist) 100%);
}
.steps {
  display: flex;
  /* flat 16, not scaled: the columns are already narrow, and letting the gap
     shrink with the viewport is the first thing that makes the row feel
     cramped */
  gap: 16px;
  /* every column takes the height of the tallest one, which is what lets the
     three renders start on a single line — see .step-details below */
  align-items: stretch;
}
.step {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: clamp(20px, 2.315vw, 40px);
}
.step-num {
  font-family: var(--font-serif);
  font-weight: 200;
  font-size: var(--fs-num);
  line-height: 1;
  color: var(--accent);
}
.step-body {
  display: flex;
  flex-direction: column;
  gap: clamp(14px, 1.157vw, 20px);
  /* fill the column so the growth below has room to act on */
  flex: 1 1 auto;
  min-height: 0;
}
/* The copy block absorbs whatever height the tallest column has spare, which
   pins every render to the bottom of its column. Since the columns are equal
   width the renders are equal height too, so their tops land on one line —
   driven by the longest caption rather than a reserved line count. */
.step-details {
  display: flex;
  flex-direction: column;
  gap: clamp(12px, 1.157vw, 20px);
  padding-right: clamp(12px, 1.389vw, 24px);
  flex: 1 1 auto;
}
.step-media { flex: 0 0 auto; }
/* the columns arrive one micro-beat after the last, same cadence as the
   gallery band */
.steps .step:nth-child(2) { --step-reveal-delay: 110ms; }
.steps .step:nth-child(3) { --step-reveal-delay: 220ms; }
.step-desc {
  --reveal-opacity: var(--muted-opacity);
}
.step-media {
  /* 485 × 600 with radius 92; the render is 844 tall and rides out the
     bottom, so the frame crops it exactly as the comp does. The image is
     taken out of flow on purpose — in flow it is taller than the box and
     would win over aspect-ratio, stretching the frame to the full render. */
  position: relative;
  aspect-ratio: 485 / 600;
  border-radius: var(--radius-card);
  background: linear-gradient(180deg, var(--mist) 0%, var(--paper) 100%);
  overflow: clip;
}
.step-media img {
  position: absolute;
  top: clamp(12px, 1.389vw, 24px);
  left: 50%;
  width: 84.54%;      /* 410 of 485 */
  height: auto;
  max-width: none;
  transform: translateX(-50%);
}
/* The render rises into its frame rather than blooming out of an over-scale:
   it starts a fifth of the way down the card, and since the frame clips, the
   phone reads as coming up from under the bottom edge. The rise rides on top
   of the centring translate. Shorter than the shared 1.5s bloom — a travel
   this long reads as drift rather than a move. */
.step-media.img-reveal img {
  transform: translate(-50%, 11%);
  transition:
    opacity 0.8s var(--ease-luxe) var(--step-reveal-delay, 0ms),
    transform 1.15s var(--ease-luxe) var(--step-reveal-delay, 0ms);
}
.step-media.img-reveal.visible img { transform: translate(-50%, 0); }

/* ═══════════ DEVELOPED WITH CLINICAL PSYCHOLOGISTS ═══════════ */
.physio { gap: var(--gap-block); }
.physio-head {
  line-height: 1.15;
  max-width: min(100%, 69.734vw);   /* 1205 */
}
.physio-card {
  display: flex;
  align-items: center;
  border-radius: var(--radius-card);
  background: linear-gradient(180deg, var(--mist) 0%, var(--lavender) 100%);
  overflow: clip;
}
.physio-media {
  flex: 0 0 50%;
  display: flex;
  justify-content: center;
  padding: clamp(20px, 2.315vw, 40px) 0;
}
.physio-media img {
  width: 55.11%;      /* 410 of 744 */
  height: auto;
  flex: 0 0 auto;
  max-width: none;
}
.physio-list {
  flex: 0 0 50%;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--gap-copy);
  padding: clamp(40px, 5.787vw, 100px) clamp(24px, 9.259vw, 160px) clamp(40px, 5.787vw, 100px) 0;
}
.physio-list li + li { border-top: 1px solid var(--hairline); padding-top: var(--gap-copy); }

/* ═══════════ GALLERY ═══════════ */
.gallery { background: var(--paper); }
.gallery-row { display: flex; }
.g-cell {
  flex: 1 1 0;
  min-width: 0;
  aspect-ratio: 576 / 880;
  overflow: clip;
}
.g-cell img { width: 100%; height: 100%; object-fit: cover; }

/* The shots bloom out of their own over-scale on the shared .img-reveal
   transition, one micro-beat apart so the band reads left to right instead
   of landing as a single slab. */
.g-cell:nth-child(2) { --step-reveal-delay: 110ms; }
.g-cell:nth-child(3) { --step-reveal-delay: 220ms; }

/* ═══════════ THE METHOD ═══════════ */
.science {
  gap: var(--gap-head);
  background: linear-gradient(180deg, var(--mist) 0%, var(--paper) 100%);
}
.science-head {
  max-width: min(100%, 55.556vw);   /* 960 */
}
.science-grid { display: flex; }
.sc-col {
  flex: 1 1 0;
  min-width: 0;
  padding: 0 clamp(16px, 1.852vw, 32px);
  border-left: 1px solid var(--hairline);
}
.sc-col:last-child { border-right: 1px solid var(--hairline); }
.sc-col:nth-child(2) { --step-reveal-delay: 110ms; }
.sc-col:nth-child(3) { --step-reveal-delay: 220ms; }
.sc-col .body {
  line-height: 1.5;
  /* the muted level is handed to the reveal, not set here: a resting opacity
     on a two-class selector would outrank .fade-in's own 0 and the paragraph
     would never have anything to fade from */
  --reveal-opacity: var(--muted-opacity);
}

/* ═══════════ CLOSING CTA ═══════════ */
/* The gutter lives on the card rather than the section, because the footer
   now sits inside this section and carries its own. */
.closing {
  position: relative;
  background: var(--paper);
}
.closing-card {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: clamp(26px, 2.778vw, 48px);
  /* the cap is on the measure, so the gutter has to be added back — with
     border-box the padding would otherwise eat into it and push the headline
     onto a third line */
  max-width: calc(40vw + var(--pad-x) * 2);
  padding: var(--sec-py) var(--pad-x);
}
/* phone-only device, same as the hero's */
.closing-fade { display: none; }
.closing-mockup {
  position: absolute;
  z-index: 1;
  /* 1277 wide at x 473 of 1728, riding 91 above the section top */
  --cta-w: min(73.9vw, 128svh);
  width: var(--cta-w);
  left: 27.373vw;
  top: calc(var(--cta-w) * -0.07126);   /* -91 */
  pointer-events: none;
}
.closing-mockup img { width: 100%; height: auto; }
/* the closing mockup glides in the same way the hero one does */
.closing-mockup img {
  opacity: 0;
  transform: translate3d(0, 5%, 0) scale(1.02);
  transition: opacity 1s var(--ease-luxe), transform 1.5s var(--ease-luxe);
  will-change: transform;
}
.closing-mockup.visible img { opacity: 1; transform: none; will-change: auto; }

/* ═══════════ FOOTER ═══════════ */
.site-footer {
  position: relative;
  z-index: 2;   /* the mockup passes behind the footer, as in the comp */
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: clamp(28px, 2.315vw, 40px) var(--pad-x) clamp(32px, 2.778vw, 48px);
}
.footer-links {
  display: flex;
  align-items: center;
  gap: clamp(24px, 3.241vw, 56px);
}
.footer-logo .logo-mark { width: clamp(72px, 5.093vw, 88px); }
.footer-nav { display: flex; gap: clamp(24px, 3.241vw, 56px); }
.foot-link {
  position: relative;
  font-size: var(--fs-caption);
  font-weight: 500;
  letter-spacing: -0.01em;
  transition: color 0.3s var(--ease);
}
.foot-link::after {
  content: '';
  position: absolute;
  left: 0; bottom: -3px;
  width: 100%; height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.45s var(--ease);
}
.foot-link:hover { color: var(--accent); }
.foot-link:hover::after { transform: scaleX(1); }
.copyright {
  font-size: var(--fs-caption);
  letter-spacing: -0.01em;
  color: var(--mut);
}

/* ═══════════ MOBILE CTA ═══════════ */
.mobile-cta {
  position: fixed;
  z-index: 90;
  left: 50%;
  bottom: max(16px, env(safe-area-inset-bottom));
  transform: translate3d(-50%, 140%, 0);
  opacity: 0;
  pointer-events: none;
  transition: transform 0.5s var(--ease), opacity 0.4s var(--ease);
  display: none;
}
body.cta-active .mobile-cta { transform: translate3d(-50%, 0, 0); opacity: 1; pointer-events: auto; }
html.keyboard-open .mobile-cta { display: none !important; }

/* ═══════════ REVEALS ═══════════ */
/* blocks rise */
.reveal-up {
  opacity: 0;
  transform: translate3d(0, 28px, 0);
  transition:
    opacity 0.8s var(--ease-luxe) var(--step-reveal-delay, 0ms),
    transform 1s var(--ease-luxe) var(--step-reveal-delay, 0ms);
  will-change: transform;
}
.reveal-up.visible { opacity: var(--reveal-opacity, 1); transform: none; }
.reveal-up.revealed { will-change: auto; }

/* Body copy fades in as one block. The line reveal is kept for the headings,
   where the staircase reads as intent; on a paragraph of small type it just
   makes the text arrive in pieces. */
.fade-in {
  opacity: 0;
  transition: opacity 0.9s var(--ease-luxe) var(--step-reveal-delay, 0ms);
}
.fade-in.visible { opacity: var(--reveal-opacity, 1); }

/* line reveal — the splitter rebuilds each visual line as its own clip */
.word-fade .line-measure { display: inline; white-space: nowrap; }
/* The clip has to make room for ink that leaves the line box on BOTH sides.
   The editorial cut rises 64.5px at 78px against a 70.2px line box, so its
   ascenders overflow the top by ~6px — measured. Padding opens the mask, the
   matching negative margin keeps the layout box where it was. */
.word-fade .line-clip {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  padding-top: 0.14em;
  margin-top: -0.14em;
  padding-bottom: 0.14em;
  margin-bottom: -0.14em;
}
.word-fade .line-inner {
  display: block;
  /* One clip is one line, by construction. The line already fits by
     definition, so forbidding a second, independent re-wrap costs nothing
     and closes the whole class of "two lines inside one clip" bug. */
  white-space: nowrap;
  transform: translateY(112%);
  /* --ld staggers the lines within one block, --step-reveal-delay staggers
     the blocks against each other; a heading inside a step or a column needs
     both or it would land ahead of its own card */
  transition: transform 0.9s var(--ease-luxe)
    calc(var(--step-reveal-delay, 0ms) + var(--ld, 0ms));
  will-change: transform;
}
.word-fade { opacity: 0; transition: opacity 0.01s linear; }
.word-fade.is-split, .word-fade.visible { opacity: var(--reveal-opacity, 1); }
.word-fade.visible .line-inner { transform: none; }
.word-fade.revealed .line-inner { transition: none; will-change: auto; }

/* photo bloom */
.img-reveal { overflow: clip; }
.img-reveal img {
  opacity: 0;
  transform: scale(1.1);
  transition:
    opacity 0.9s var(--ease-luxe) var(--step-reveal-delay, 0ms),
    transform 1.5s var(--ease-luxe) var(--step-reveal-delay, 0ms);
  will-change: transform;
}
.img-reveal.visible img { opacity: 1; transform: none; will-change: auto; }

/* the hero title rises on load, not on scroll — it is above the fold */
.hero-title { opacity: 0; }
.hero-title.is-split { opacity: 1; }
.hero-title .line-clip {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  /* same reason as .word-fade .line-clip: the serif's ascenders leave the
     line box at this line-height and the top of the mask was shaving them */
  padding-top: 0.14em;
  margin-top: -0.14em;
  padding-bottom: 0.14em;
  margin-bottom: -0.14em;
}
.hero-title .line-inner {
  display: block;
  white-space: nowrap;   /* same reason as .word-fade .line-inner */
  animation: hero-line-rise 1s var(--ease-luxe) var(--ld, 0ms) both;
}
.hero-title.settled .line-inner { animation: none; }
@keyframes hero-line-rise {
  from { transform: translateY(112%); }
  to   { transform: none; }
}

/* the sub and form follow the title in */
.hero-sub, .hero .access-form {
  animation: hero-fade-up 1s var(--ease-luxe) both;
}
.hero-sub { animation-delay: 0.34s; }
.hero .access-form { animation-delay: 0.44s; }
@keyframes hero-fade-up {
  from { opacity: 0; transform: translate3d(0, 20px, 0); }
  to   { opacity: var(--reveal-opacity, 1); transform: none; }
}

/* ═══════════ RAIL (mobile scrollers) ═══════════ */
.rail { position: relative; }

/* ═══════════ RESPONSIVE ═══════════ */
@media (max-width: 1200px) {
  .hero-title { max-width: min(100%, 52vw); }
  .closing-card { max-width: calc(52vw + var(--pad-x) * 2); }
  .physio-list { padding-right: clamp(24px, 5vw, 96px); }
}

@media (max-width: 900px) {
  :root {
    /* 78 on the 402 frame — the size that puts the headline on two lines
       across the column at every phone width */
    --fs-display: clamp(52px, 19.403vw, 84px);
    /* 38 at 402, shared by every section head. The ceiling is set by the
       longest one: past ~41 "Developed with clinical" stops fitting the
       column and strands "clinical" on a line of its own. */
    --fs-h2:      clamp(29px, 9.45vw, 44px);
    --fs-h3:      clamp(24px, 6.4vw, 38px);
    --fs-h4:      clamp(24px, 6.4vw, 38px);
    --fs-lead:    clamp(17px, 4.6vw, 24px);
    --fs-body:    clamp(15px, 4.1vw, 19px);
    --fs-num:     clamp(20px, 5.3vw, 26px);
    --pad-x:      16px;
    /* --sec-py and --gap-head are deliberately NOT tightened here: their root
       clamps already bottom out at 72 and 40, which is the section rhythm the
       phone layout is built around. Overriding them lower was what made the
       page read cramped next to the same blocks on Totwo. */
    --radius-card: 28px;

    /* the controls keep the root clamps rather than the frame's flat 64/20/28:
       full-width pills at that size read oversized on a phone */
  }

  /* ── hero ──
     Copy first, render under it, the whole thing left-aligned. Sizes come off
     the 402 mobile frame, so every vw figure below is design-px / 4.02. */
  .hero {
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
    height: auto;
    min-height: 0;
    padding: clamp(136px, 52.24vw, 212px) var(--pad-x) 0;   /* frame says 150 */
    /* the render sits in flow here and has no shadow to spill, so the section
       keeps its own edges — the overhang is a desktop device */
    overflow: clip;
    gap: clamp(28px, 9.95vw, 40px);                          /*  40 */
    background:
      radial-gradient(180% 62% at 50% 0%,
        var(--lavender) 0%,
        var(--mist) 54.8%,
        var(--paper) 100%);
  }
  .hero-copy {
    flex: 0 0 auto;
    align-items: stretch;
    text-align: left;
    width: 100%;
    /* the frame sets 16 between headline and sub, then 40 before the form —
       the container carries the 16 and the form makes up the rest */
    gap: clamp(12px, 3.98vw, 16px);                          /*  16 */
  }
  .hero .access-form { margin-top: clamp(16px, 5.97vw, 24px); }  /* 16+24=40 */
  .hero-title, .hero-sub { max-width: 100%; }

  /* the render runs the full width of the column and keeps its own ratio */
  .hero-mockup {
    position: relative;
    order: 0;
    left: auto;
    right: auto;
    top: auto;
    width: 100%;
    margin: 0;
    display: block;
  }
  .hero-mockup picture { display: block; }
  .hero-mockup img {
    width: 100%;
    height: auto;
    max-width: none;
    max-height: none;
    aspect-ratio: 756 / 942;
  }
  /* the comp's fade at the foot of the section, full bleed past the gutters */
  .hero-fade {
    display: block;
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 26.12vw;                                         /* 105 */
    background: linear-gradient(180deg,
      rgba(245, 241, 234, 0) 0%,
      var(--paper) 100%);
    pointer-events: none;
    z-index: 2;
  }

  /* the field and the pill stack, each running the full column */
  .access-form { width: 100%; }
  .form-row {
    flex-direction: column;
    align-items: stretch;
    width: 100%;
    max-width: none;
    margin-inline: 0;
    gap: 10px;
  }
  .input { width: 100%; flex: 0 0 auto; }
  .form-row .btn { width: 100%; }

  /* ── manifesto: photo on top, copy beneath ── */
  .manifesto { height: auto; overflow: visible; }
  .manifesto-bg { position: static; display: block; }
  .manifesto-bg img { width: 100%; height: auto; aspect-ratio: 1122 / 1402; }
  /* the halves unwind into a stack here, so there is no seam to blur */
  .manifesto-blur { display: none; }
  .manifesto-card {
    position: static;
    width: 100%;
    /* the left edge keeps the page gutter so the block lines up with every
       other section; the measure is shortened from the right instead, which
       is what stops this statement reading as a stretched paragraph */
    padding: clamp(32px, 9vw, 56px) clamp(40px, 13.93vw, 56px) var(--sec-py) var(--pad-x);
    background: var(--paper);
  }
  .manifesto-copy { width: 100%; gap: clamp(16px, 4.5vw, 28px); }

  /* ── inside: rail ── */
  .rail:not(.gallery-rail) {
    margin-inline: calc(var(--pad-x) * -1);
    padding-inline: var(--pad-x);
    /* the step media has a soft shadow-free crop, but the rail still needs
       breathing room so nothing is shaved by the scroller edge */
    padding-block: 6px;
    margin-block: -6px;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    scroll-padding-inline-start: var(--pad-x);
    scrollbar-width: none;
  }
  .rail::-webkit-scrollbar { display: none; }
  .steps { width: max-content; gap: 20px; }
  .step { width: 76vw; flex: 0 0 auto; scroll-snap-align: start; }
  .step-media { aspect-ratio: 485 / 560; }

  /* ── physio: phone under the copy ── */
  /* the desktop measure cap (1205 of 1728) was still in force here, so the
     heading was wrapping inside 70% of the column instead of across it */
  .physio-head { max-width: 100%; }
  .physio-card { flex-direction: column; align-items: stretch; }
  .physio-list {
    flex: 0 0 auto;
    order: -1;
    padding: clamp(28px, 8vw, 44px) clamp(20px, 6vw, 36px) clamp(8px, 3vw, 20px);
    gap: clamp(14px, 4vw, 24px);
  }
  .physio-list li + li { padding-top: clamp(14px, 4vw, 24px); }
  /* the render sits on the same gutter as the list above it, rather than
     running out to the card's own edges */
  .physio-media {
    flex: 0 0 auto;
    padding: clamp(16px, 5vw, 32px) clamp(20px, 6vw, 36px) clamp(24px, 7vw, 44px);
  }
  /* width follows the card, height follows the render's own ratio — no fixed
     percentage to fall out of proportion at any viewport */
  .physio-media img {
    width: 100%;
    height: auto;
    aspect-ratio: 615 / 1266;
    object-fit: contain;
  }

  /* ── gallery: swipe band ── */
  .gallery-rail {
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
  }
  .gallery-rail::-webkit-scrollbar { display: none; }
  .gallery-row { width: max-content; }
  .g-cell { width: 72vw; flex: 0 0 auto; scroll-snap-align: center; }

  /* ── science: stacked, hairlines become rules ── */
  .science-grid { flex-direction: column; gap: clamp(24px, 7vw, 40px); }
  .sc-col {
    padding: clamp(20px, 6vw, 32px) 0 0;
    border-left: 0;
    border-top: 1px solid var(--hairline);
  }
  .sc-col:last-child { border-right: 0; }
  .science-head { max-width: 100%; }

  /* ── closing ──
     One column, mirroring the hero: copy, form, footer, render, fade.
     Figures come off the 402 footer frame, so vw = design-px / 4.02. */
  .closing {
    display: flex;
    flex-direction: column;
    padding: clamp(64px, 21.9vw, 88px) var(--pad-x) 0;      /* frame says 40 */
    gap: clamp(40px, 14.925vw, 60px);                        /* 60 */
    /* Flat paper. The frame does carry the hero's radial wash, but a solid
       paper fill sits over it at full opacity, so the gradient never shows. */
    background: var(--paper);
  }
  .closing-card {
    max-width: 100%;
    align-items: stretch;
    text-align: left;
    padding: 0;
    gap: clamp(22px, 7.96vw, 32px);                          /* 32 */
  }
  .closing-card .access-form { align-items: stretch; }
  .closing-card .form-note { text-align: left; }
  /* Sized off the column rather than the shared head token. Its longest line,
     "Find out what gives your", measures 9.76x the font size, so dividing the
     available width by 10.05 lands it with about 3% to spare — the headline
     then tracks the screen and holds two lines at every phone width instead
     of clearing the column by 6px and hoping. */
  .closing-card .h2 {
    line-height: 0.95;
    font-size: clamp(28px, calc((100vw - var(--pad-x) * 2) / 10.05), 46px);
  }

  /* Centred: the mark takes its own line, then the legal links and the
     copyright share the next one. .footer-links goes display:contents so all
     three become siblings of the same row and can be laid out together. */
  .site-footer {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 14px 20px;
    padding: 0;
    text-align: center;
  }
  .footer-links { display: contents; }
  .footer-logo {
    flex: 0 0 100%;
    display: flex;
    justify-content: center;
  }
  /* the frame gives the mark its full size down here, same as the header's */
  .footer-logo .logo-mark { width: clamp(92px, 27.114vw, 109px); }
  .footer-nav { gap: 20px; }

  .closing-mockup {
    position: relative;
    width: 100%;
    left: auto;
    top: auto;
    margin: 0;
  }
  .closing-mockup picture { display: block; }
  .closing-mockup img {
    width: 100%;
    height: auto;
    aspect-ratio: 756 / 1064;
  }
  .closing-fade {
    display: block;
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 26.12vw;                                         /* 105 */
    background: linear-gradient(180deg,
      rgba(245, 241, 234, 0) 0%,
      var(--paper) 100%);
    pointer-events: none;
    z-index: 3;
  }

  .mobile-cta { display: inline-flex; }
  .header-cta { display: none; }

  /* Absolute, not fixed: with no plate behind it the bar would sit straight
     on the copy once the hero is gone, so on a phone it rides away with the
     page and the floating pill carries the CTA. */
  .site-header { position: absolute; }
}

@media (max-width: 420px) {
  .footer-nav { gap: 20px; }
}

/* ═══════════ REDUCED MOTION ═══════════ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .reveal-up, .word-fade, .hero-title { opacity: 1; transform: none; }
  .fade-in { opacity: var(--reveal-opacity, 1); }
  .word-fade .line-inner, .hero-title .line-inner { transform: none; animation: none; }
  .img-reveal img { opacity: 1; transform: none; }
  /* the step render carries its own centring translate, so `transform: none`
     above both loses on specificity and would decentre it if it won */
  .step-media.img-reveal img { transform: translate(-50%, 0); }
  .hero-mockup img, .closing-mockup img { opacity: 1; transform: none; animation: none; }
  .hero-sub, .hero .access-form { animation: none; opacity: 1; transform: none; }
}
