/* ============================================================
   GAMECHANGER — Base, layout, components
   ============================================================ */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  font-family: var(--font);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  letter-spacing: var(--tr-body);
  color: var(--text-on-dark);
  background: var(--bg-dark);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

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

a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
ul, ol { list-style: none; padding: 0; }

::selection { background: var(--accent); color: var(--accent-contrast); }

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

.skip {
  position: absolute; left: -9999px; top: 0;
  background: var(--accent); color: var(--accent-contrast);
  padding: var(--s-3) var(--s-5); z-index: 999; font-weight: 600;
}
.skip:focus { left: var(--s-4); top: var(--s-4); }

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

/* ============================================================
   LAYOUT
   ============================================================ */

.shell {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.shell--wide { max-width: 100rem; }
.shell--flush { max-width: none; padding-inline: var(--gutter); }

.section { padding-block: var(--section-y); position: relative; }

/* Where two sections of the SAME tone meet, no colour change marks the join,
   so their facing paddings stack into one large empty band instead of
   reading as two sections — a full 2x --section-y of nothing. Pull the join
   in. Reported on Speed & Agility between "In the session" and the parent
   quote (31 Aug 2026, client); the same pattern was latent on six other
   pages, so this is fixed at the root rather than per-page. Sections of
   DIFFERENT tones keep the full gap — there the colour change does the work. */
.section--dark   + .section--dark,
.section--light  + .section--light,
.section--alt    + .section--alt,
.section--raised + .section--raised { padding-top: calc(var(--section-y) * .4); }
.section--dark  { background: var(--bg-dark);   color: var(--text-on-dark); }
.section--raised{ background: var(--bg-dark-raised); color: var(--text-on-dark); }
.section--light { background: var(--bg-light);  color: var(--text-on-light); }
.section--alt   { background: var(--bg-light-alt); color: var(--text-on-light); }
.section--tight { padding-block: clamp(3.5rem, 7vw, 6rem); }

/* ============================================================
   TYPE
   ============================================================ */

.eyebrow {
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--tr-eyebrow);
  text-transform: uppercase;
  color: var(--accent);
  display: flex;
  align-items: center;
  gap: var(--s-3);
}
.eyebrow::before {
  content: "";
  width: clamp(1.5rem, 3vw, 2.75rem);
  height: 1px;
  background: currentColor;
  flex: none;
  opacity: .75;
}
.eyebrow--plain::before { display: none; }

/* THE SIGNATURE (31 Aug 2026) — the eyebrow rule draws itself in.
   Every section on the site opens with this hairline before its label
   ("01 — FOOTBALL"), so animating it gives the whole site one repeating
   gesture that is already part of the design language rather than a new
   decoration bolted on. transform-only, no JS, no layout cost: ::before is
   `flex: none` with a fixed width, so scaleX cannot move the label.
   The 120ms offset lets the words land first, then the rule draws under them.
   Scoped to .eyebrow[data-reveal] deliberately — one eyebrow on contact.html
   has no data-reveal, so it never receives .is-in and an unscoped rule would
   leave its hairline collapsed forever. */
.js-motion .eyebrow[data-reveal]::before {
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 420ms var(--ease-out);
  transition-delay: calc(var(--d, 0ms) + 120ms);
}
.js-motion .eyebrow[data-reveal].is-in::before { transform: scaleX(1); }
@media (prefers-reduced-motion: reduce) {
  .js-motion .eyebrow[data-reveal]::before { transform: none; transition: none; }
}
.section--light .eyebrow, .section--alt .eyebrow { color: var(--accent-on-light); }

/* balance/pretty keep the rag under control: without them headings orphan
   single words ("...actions / that / decide the game") once a column gets
   narrow. Line splitting measures AFTER this resolves, so the masks match. */
/* .display is excluded: at hero size, balance splits
   "Maximise your / potential." into "Maximise / your potential.",
   which reads weaker. Its 15ch max-width already wraps it well. */
.h1, .h2, .h3, .row__title, .quote__text { text-wrap: balance; }
.lede, .body, .card__text, .fitem__d { text-wrap: pretty; }

.display {
  font-family: var(--font-display);
  font-size: var(--fs-display);
  font-weight: 700;
  line-height: var(--lh-display);
  letter-spacing: var(--tr-display);
}
.h1 { font-family: var(--font-display); font-size: var(--fs-h1); font-weight: 700; line-height: var(--lh-heading); letter-spacing: var(--tr-heading); }
.h2 { font-family: var(--font-display); font-size: var(--fs-h2); font-weight: 700; line-height: var(--lh-heading); letter-spacing: var(--tr-heading); }
.h3 { font-family: var(--font-display); font-size: var(--fs-h3); font-weight: 700; line-height: 1.18; letter-spacing: var(--tr-heading); }
.h4 { font-family: var(--font-display); font-size: var(--fs-h4); font-weight: 700; line-height: 1.3; letter-spacing: -.015em; }

.lede {
  font-size: clamp(1.05rem, 1.5vw, 1.3rem);
  line-height: 1.55;
  color: var(--text-on-dark-muted);
  max-width: var(--maxw-text);
}
.section--light .lede, .section--alt .lede { color: var(--text-on-light-muted); }

.body { color: var(--text-on-dark-muted); max-width: var(--maxw-text); }
.section--light .body, .section--alt .body { color: var(--text-on-light-muted); }

.mono {
  font-size: var(--fs-mono);
  letter-spacing: .14em;
  text-transform: uppercase;
  font-weight: 500;
  color: var(--text-on-dark-faint);
  font-variant-numeric: tabular-nums;
}
.section--light .mono, .section--alt .mono { color: var(--text-on-light-faint); }

.gold { color: var(--accent); }
.section--light .gold, .section--alt .gold { color: var(--accent-on-light); }

/* Section header block */
.head { display: grid; gap: var(--s-5); margin-bottom: clamp(2.5rem, 5vw, 4.5rem); }
.head__top { display: flex; justify-content: space-between; align-items: baseline; gap: var(--s-5); flex-wrap: wrap; }
.head__title { max-width: 22ch; }
.head--split {
  grid-template-columns: 1fr;
}
@media (min-width: 62rem) {
  .head--split { grid-template-columns: minmax(0,1.15fr) minmax(0,.85fr); align-items: end; gap: var(--s-7); }
}

/* ============================================================
   BUTTONS
   ============================================================ */

.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  padding: .95rem 1.6rem;
  font-size: .9rem;
  font-weight: 600;
  letter-spacing: -.01em;
  border-radius: 999px;
  position: relative;
  isolation: isolate;
  transition:
    transform var(--dur-ui) var(--ease-out),
    background-color var(--dur-ui) var(--ease-out),
    color var(--dur-ui) var(--ease-out),
    border-color var(--dur-ui) var(--ease-out);
}
.btn__arrow {
  transition: transform var(--dur-ui-slow) var(--ease-out);
  flex: none;
}

.btn--gold { background: var(--accent); color: var(--accent-contrast); }
.btn--ghost {
  border: 1px solid var(--line-on-dark-strong);
  color: var(--text-on-dark);
}
.section--light .btn--ghost, .section--alt .btn--ghost {
  border-color: var(--line-on-light); color: var(--text-on-light);
}

@media (hover: hover) and (pointer: fine) {
  .btn:hover { transform: translate3d(0, -2px, 0); }
  .btn--gold:hover { background: var(--gold-400); }
  .btn--ghost:hover { border-color: var(--accent); color: var(--accent); }
  .btn:hover .btn__arrow { transform: translate3d(4px, 0, 0); }
}
.btn:active { transform: translate3d(0, 0, 0); }

/* Text link with an animated rule. Draws with a scaleX'd ::after rather than
   an animated background-size (31 Aug 2026 audit): background-size repaints
   the element every frame, transform composites. Same visual, and it now uses
   the identical mechanism as .nav__link::after instead of a second technique
   for the same effect. min-height keeps the tap target at 44px on touch. */
.tlink {
  position: relative;
  display: inline-flex; align-items: center; gap: var(--s-2);
  font-weight: 600; font-size: .9rem;
  padding-bottom: 3px;
  min-height: 44px;
  transition: color var(--dur-ui) var(--ease-out);
}
.tlink::after {
  content: ""; position: absolute; left: 0; bottom: 0; height: 1px; width: 100%;
  background: currentColor; transform: scaleX(0); transform-origin: left;
  transition: transform var(--dur-ui-slow) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .tlink:hover::after { transform: scaleX(1); }
}
.tlink:focus-visible::after { transform: scaleX(1); }

/* ============================================================
   NAV
   ============================================================ */

.nav {
  position: fixed; inset: 0 0 auto 0;
  z-index: var(--z-nav);
  padding-block: var(--s-5);
  transition:
    padding-block var(--dur-ui-slow) var(--ease-out),
    transform var(--dur-nav) var(--ease-out-soft);
}
.nav.is-stuck { padding-block: var(--s-4); }

/* Hide on scroll-down past the fold, reveal the instant the user
   scrolls back up — see syncNav() in motion.js. Never fires with
   prefers-reduced-motion, and focus inside the bar always wins: a
   keyboard user tabbing into a link must never have it yanked
   off-screen mid-navigation. Source order matters here — this rule
   has to follow .nav.is-hidden so the tie in specificity resolves
   in its favour. */
.nav.is-hidden { transform: translateY(-120%); }
.nav:has(:focus-visible) { transform: none; }

.nav__row { display: flex; align-items: center; justify-content: space-between; gap: var(--s-5); }
/* Three things share a phone row now — wordmark, CTA, burger — so the
   gaps come in. Desktop keeps the roomier rhythm. */
@media (max-width: 61.99rem) {
  .nav__row { gap: var(--s-4); }
  .nav__right { gap: var(--s-3); }
}

/* ---------- Pill -----------------------------------------------
   Logo + links share one capsule that is transparent and full-width
   at rest, then consolidates into a floating dark/blurred pill once
   .is-stuck — wordmark folds down to the icon alone, everything
   tightens and centres itself as a unit. The CTA lives in .nav__right
   instead, outside the capsule entirely, so "Book a Session" never
   gets pulled into the same treatment as the rest of the nav. */
.nav__pill {
  display: flex; align-items: center; gap: var(--s-6);
  border-radius: 999px;
  padding-block: .4rem; padding-inline: .4rem;
  transition:
    background-color var(--dur-ui-slow) var(--ease-out),
    gap var(--dur-ui-slow) var(--ease-out),
    padding var(--dur-ui-slow) var(--ease-out);
}
.nav.is-stuck .nav__pill {
  background: rgba(7,7,10,.82);
  -webkit-backdrop-filter: saturate(140%) blur(14px);
  backdrop-filter: saturate(140%) blur(14px);
  gap: var(--s-5);
  padding-block: .55rem; padding-inline: .5rem 1.25rem;
}
.nav__right { display: flex; align-items: center; gap: var(--s-4); }

.brand { display: flex; flex-direction: column; justify-content: center; line-height: 1; gap: 4px; min-height: 44px; }
.brand__name { font-weight: 700; font-size: .95rem; letter-spacing: .02em; }
.brand__sub  { font-size: .625rem; letter-spacing: .22em; text-transform: uppercase; color: var(--accent); font-weight: 600; }

/* Header-only lockup: wordmark only (icon mark dropped 29 Aug 2026 —
   read as a stray ball icon, not a brand mark). Footer's plain .brand
   (name/sub) is untouched by this — only ever applied inside .nav__pill. */
.nav__pill .brand { flex-direction: row; align-items: center; gap: .6rem; }

/* Collapses when the header sticks. Only opacity/transform are transitioned
   (31 Aug 2026 audit): max-width was animated, which is a layout property —
   it reflowed the whole nav row every frame of the stick. The width still
   collapses, just instantly, underneath a fade the eye actually reads. */
.brand__text {
  display: flex; flex-direction: column; gap: 4px; overflow: hidden; white-space: nowrap;
  max-width: 12rem; opacity: 1;
  transition: opacity var(--dur-ui) var(--ease-out), transform var(--dur-ui) var(--ease-out);
}
/* Desktop only. Below 62rem the pill holds nothing but the wordmark —
   the nav links live in the burger menu — so collapsing it on stick left
   an empty capsule and no branding in the bar at all (1 Sep 2026). The
   sub-line goes instead, which buys the row the width the CTA needs. */
@media (min-width: 62rem) {
  .nav.is-stuck .brand__text {
    max-width: 0; opacity: 0;
    transform: translate3d(-6px, 0, 0);
  }
}
@media (max-width: 61.99rem) {
  .nav__pill .brand__sub { display: none; }
}

.nav__links { display: none; align-items: center; gap: clamp(1.25rem, 2.4vw, 2.25rem); }
@media (min-width: 62rem) { .nav__links { display: flex; } }
.nav__link {
  font-size: .8125rem; font-weight: 500; letter-spacing: .02em;
  color: var(--text-on-dark-muted);
  transition: color var(--dur-ui) var(--ease-out);
  position: relative; padding-block: 4px;
}
.nav__link::after {
  content: ""; position: absolute; left: 0; bottom: 0; height: 1px; width: 100%;
  background: var(--accent); transform: scaleX(0); transform-origin: left;
  transition: transform var(--dur-ui-slow) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .nav__link:hover { color: var(--text-on-dark); }
  .nav__link:hover::after { transform: scaleX(1); }
}
.nav__link.is-active { color: var(--text-on-dark); }
.nav__link.is-active::after { transform: scaleX(1); }

.nav__cta { padding: .7rem 1.1rem; font-size: .8125rem; min-height: 44px; }
.nav__cta__label { display: inline; }
.nav__cta__rest { display: none; }
@media (min-width: 62rem) {
  .nav__cta { padding: .7rem 1.25rem; min-height: 0; }
  .nav__cta__rest { display: inline; }
}
/* Below 375px. The full row measures 359px against a 375px viewport — 16px
   of slack, fine — but at 360px that falls to 1px, so everything under the
   commonest phone width goes compact: the arrow is decoration, the button is
   not, and the wordmark steps down rather than clipping against
   .brand__text's overflow:hidden. */
@media (max-width: 23.375rem) {
  .nav__cta { padding-inline: .8rem; }
  .nav__cta .btn__arrow { display: none; }
  .nav__pill .brand__name { font-size: .85rem; }
}

@media (prefers-reduced-motion: reduce) {
  .nav { transition: padding-block var(--dur-ui-slow) var(--ease-out); }
  .nav.is-hidden { transform: none; }
}

/* ---------- Training dropdown (desktop) ----------
   Two separate hit targets by design: the word "Training" is a real
   <a> so it always works (keyboard, no-js, right-click-open-in-tab);
   the caret is a distinct <button> that only toggles the panel. This
   is the standard mega-menu split — see e.g. any "Products ▾" nav —
   and it's what lets a single component satisfy both "clicking the
   label goes to the hub" and "the dropdown opens on demand". */
.nav__drop { position: relative; display: flex; align-items: center; gap: .3rem; }
.nav__caret {
  display: grid; place-items: center;
  width: 1.1rem; height: 1.1rem;
  color: var(--text-on-dark-muted);
  transition: color var(--dur-ui) var(--ease-out), transform var(--dur-ui-slow) var(--ease-out);
}
.nav__caret svg { transition: transform var(--dur-ui-slow) var(--ease-out); }
.nav__drop.is-open .nav__caret svg { transform: rotate(180deg); }
.nav__drop:hover .nav__caret, .nav__drop.is-open .nav__caret { color: var(--text-on-dark); }

.nav__panel {
  position: absolute; top: calc(100% + 1rem); left: 50%;
  transform: translate3d(-50%, -8px, 0);
  width: 15.5rem;
  background: var(--ink-850);
  border: 1px solid var(--line-on-dark);
  border-radius: .7rem;
  padding: .5rem;
  box-shadow: 0 18px 40px rgba(0,0,0,.45);
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity var(--dur-ui-slow) var(--ease-out), transform var(--dur-ui-slow) var(--ease-out), visibility var(--dur-ui-slow);
}
.nav__drop.is-open .nav__panel {
  opacity: 1; visibility: visible; pointer-events: auto;
  transform: translate3d(-50%, 0, 0);
}
.nav__panel__link {
  display: block; padding: .65rem .85rem; border-radius: .45rem;
  font-size: .8125rem; font-weight: 500; color: var(--text-on-dark-muted);
  transition: background-color var(--dur-ui) var(--ease-out), color var(--dur-ui) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .nav__panel__link:hover { background: rgba(255,255,255,.06); color: var(--text-on-dark); }
}
.nav__panel__link:focus-visible { background: rgba(255,255,255,.06); color: var(--text-on-dark); outline-offset: -2px; }
.nav__panel__link.is-active { color: var(--accent); }

@media (prefers-reduced-motion: reduce) {
  .nav__panel { transition: opacity 120ms linear; transform: translate3d(-50%, 0, 0) !important; }
  .nav__caret svg { transition: none; }
}

/* burger */
.burger {
  display: grid; place-items: center; width: 44px; height: 44px;
  margin-right: -10px;
}
@media (min-width: 62rem) { .burger { display: none; } }
.burger__box { display: grid; gap: 5px; width: 22px; }
.burger__bar {
  height: 1.5px; width: 100%; background: currentColor; border-radius: 2px;
  transition: transform var(--dur-ui-slow) var(--ease-out), opacity var(--dur-ui) var(--ease-out);
}
.burger[aria-expanded="true"] .burger__bar:nth-child(1) { transform: translate3d(0,6.5px,0) rotate(45deg); }
.burger[aria-expanded="true"] .burger__bar:nth-child(2) { opacity: 0; }
.burger[aria-expanded="true"] .burger__bar:nth-child(3) { transform: translate3d(0,-6.5px,0) rotate(-45deg); }

/* mobile menu */
.menu {
  position: fixed; inset: 0; z-index: var(--z-menu);
  background: var(--ink-900);
  display: grid; grid-template-rows: auto 1fr auto;
  padding: var(--s-5) var(--gutter) var(--s-7);
  opacity: 0; visibility: hidden;
  /* visibility is stepped, not transitioned (31 Aug 2026 audit): transitioning
     it left a window where the panel was open but still `hidden`, and a
     `visibility: hidden` element cannot take focus — so moving focus into the
     menu on open silently failed. Delaying the step only on CLOSE keeps the
     fade-out intact while making the panel focusable the instant it opens. */
  transition: opacity var(--dur-ui-slow) var(--ease-out),
              visibility 0s linear var(--dur-ui-slow);
}
.menu.is-open {
  opacity: 1; visibility: visible;
  transition: opacity var(--dur-ui-slow) var(--ease-out), visibility 0s linear 0s;
}
.menu__list { display: grid; align-content: center; gap: var(--s-4); }
.menu__link {
  font-size: clamp(2rem, 9vw, 3rem); font-weight: 700;
  letter-spacing: var(--tr-heading); line-height: 1.05;
  /* 44px floor — the type is large but line-height 1.05 made the hit box 42px */
  display: inline-flex; align-items: center; min-height: 44px;
  opacity: 0; transform: translate3d(0, 18px, 0);
  transition: opacity var(--dur-reveal) var(--ease-out), transform var(--dur-reveal) var(--ease-out);
}
.menu.is-open .menu__link { opacity: 1; transform: none; }
.menu__foot { display: grid; gap: var(--s-3); }

/* ---------- Training accordion (mobile menu) ----------
   No hover on touch, so the desktop hover-panel pattern doesn't apply —
   this is a plain expand/collapse toggle inline in the fullscreen menu. */
.menu__drop { display: grid; gap: 0; }
.menu__drop-row { display: flex; align-items: center; gap: var(--s-3); }
.menu__drop-row .menu__link { flex: 1 1 auto; }
.menu__drop-toggle {
  flex: none;
  width: 2.75rem; height: 2.75rem;
  display: grid; place-items: center;
  opacity: 0; transform: translate3d(0, 18px, 0);
  transition: opacity var(--dur-reveal) var(--ease-out), transform var(--dur-reveal) var(--ease-out);
}
.menu.is-open .menu__drop-toggle { opacity: 1; transform: none; }
.menu__drop-toggle svg { transition: transform var(--dur-ui-slow) var(--ease-out); }
.menu__drop.is-open .menu__drop-toggle svg { transform: rotate(180deg); }

.menu__submenu {
  display: grid;
  grid-template-rows: 0fr;
  overflow: hidden;
  transition: grid-template-rows var(--dur-ui-slow) var(--ease-out);
}
.menu__drop.is-open .menu__submenu { grid-template-rows: 1fr; }
.menu__submenu > div { min-height: 0; display: grid; gap: .1rem; padding-block: .35rem .9rem; }
.menu__sublink {
  font-size: 1.05rem; font-weight: 500; color: var(--text-on-dark-muted);
  display: flex; align-items: center; min-height: 44px;
  padding-block: .4rem;
}
.menu__sublink.is-active { color: var(--accent); }

/* ============================================================
   HERO
   ============================================================ */

.hero {
  position: relative;
  min-height: 100svh;
  display: grid;
  align-items: end;
  overflow: hidden;
  background: var(--ink-900);
}
/* Interior pages still get a hero close to the home page's own size —
   short heroes were cropping the copy block right over the photo,
   leaving neither legible. Capped below 100svh so the page below still
   starts working without a second full-screen title card. */
.hero--page { min-height: min(88svh, 54rem); }
.hero--page .hero__title { max-width: 20ch; }

/* Football's hero is a portrait action shot (1365x2048) where the player's
   head sits at y=731 and the ball at y=1493 — a 762px subject. A hero whose
   height comes only from viewport HEIGHT gives a 751px window at 1440 wide,
   so the pose never fits: bias the crop up and the ball disappears, bias it
   down and the head goes. That is the "football is cut off" report
   (31 Aug 2026, client) — a repositioned crop alone cannot solve it.
   Deriving the floor from viewport WIDTH keeps the window growing in step
   with the image, so the visible slice stays constant (~819px of the
   original) at every width and the whole pose stays in frame.

   The trade, recorded because it is deliberate: 60vw means the hero runs to
   ~107% of viewport height on 16:9 screens (1366x768, 1920x1080), so it
   overflows the fold by about 7%. On those short-wide laptops the window
   needed to hold head-and-ball is simply taller than the screen — measured,
   not guessed: at 1366x768 the pose needs 795px and the viewport is 768.
   Fitting the fold and showing the whole subject are mutually exclusive
   here, and the brief makes the subject the requirement. 16:10 screens
   (1440x900, 1728x1117) land at 92-96% and are unaffected; phones and
   tablets are height-bound and show the frame whole. Capped at 100rem so
   ultrawide monitors do not get an absurd hero — past ~2660px it clips
   again. Verified across 10 viewports incl. parallax drift: worst-case
   clearance +10px head / +12px ball. */
.hero--figure { min-height: min(max(88svh, 60vw), 100rem); }

/* DUO HERO (31 Aug 2026, client) — two photos side by side, each shown at its
   FULL height with nothing cropped. Both frames are 1365x2048, so at half the
   viewport each, the pair sets the hero's height to 75vw and the images render
   at their natural aspect. No cropping happens on desktop because the box is
   exactly the shape of the picture.
   Below 62rem that would leave a ~281px-tall hero with a headline over it, so
   small screens keep a normal hero box and the pair covers it instead. */
.hero--duo .hero__media {
  display: grid;
  grid-template-columns: 1fr 1fr;
}
@media (min-width: 62rem) {
  .hero--duo { min-height: 0; }
  .hero--duo .hero__media { position: relative; inset: auto; }
  .hero--duo .hero__media img {
    width: 100%; height: auto;
    object-fit: fill;          /* box already matches the picture's aspect */
  }
  .hero--duo .hero__body { position: absolute; left: 0; right: 0; bottom: 0; }
}

/* media box is deliberately taller than the hero so the parallax
   translate never drags an edge into view */
.hero__media {
  position: absolute; inset: -9% 0;
  z-index: 0;
  overflow: hidden;
}
.hero__media img, .hero__media video {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: 56% 60%;
}
.hero__scrim {
  position: absolute; inset: 0; z-index: 1;
  background:
    linear-gradient(to top, rgba(7,7,10,.94) 0%, rgba(7,7,10,.58) 34%, rgba(7,7,10,.16) 62%, rgba(7,7,10,.5) 100%);
}
/* interior heroes previously carried an extra rgba(7,7,10,.3) flat
   layer on top of the gradient below ("give them an extra floor") —
   removed 30 Aug 2026: stacked with the gradient it made the photo
   unreadable on darker source frames (the gym, the dusk shots).
   Interior heroes now use the exact same scrim as the home hero. */
.hero__scrim--side {
  background: linear-gradient(100deg, rgba(7,7,10,.72) 0%, rgba(7,7,10,.18) 52%, rgba(7,7,10,0) 78%);
}
.hero__body {
  position: relative; z-index: 2;
  padding-block: clamp(3rem, 9vh, 7rem);
  display: grid; gap: var(--s-6);
}
.hero__title { max-width: 15ch; }
.hero__actions { display: flex; flex-wrap: wrap; gap: var(--s-3); align-items: center; }
.hero__lede { max-width: 34rem; }

/* corner data marks — quiet "designed" detail, like the reference sites */
/* The layer itself is now always on (31 Aug 2026, client) so the brand badge
   can show on a phone — it was the reason the badge had no size on mobile, not
   any rule on the badge. The two corner TEXT marks stay desktop-only: coords
   and an "Est. 2023" stamp are detail a 375px screen does not have room for. */
.hero__marks {
  position: absolute; inset: 0; z-index: 2; pointer-events: none;
  padding: calc(var(--s-9) + 1rem) var(--gutter) var(--s-6);
}
.hero__mark { position: absolute; display: none; }
@media (min-width: 62rem) { .hero__mark { display: block; } }
.hero__mark--tr { top: calc(var(--s-9) + 1.5rem); right: var(--gutter); text-align: right; }
.hero__mark--br { bottom: var(--s-6); right: var(--gutter); text-align: right; }

/* Large brand badge, top-left dead space above the eyebrow — the hero
   is bottom-anchored (.hero{align-items:end}) so that whole top band
   sits empty on anything taller than a phone. Rides the .hero__scrim--side
   darkening on the left edge, so it stays legible over the photo without
   its own shadow. Desktop-only alongside its .hero__marks siblings —
   mobile's hero is short enough that headline/lede/CTAs need the room. */
.hero__badge { position: absolute; top: calc(var(--s-9) + 1rem); left: var(--gutter); width: clamp(7rem, 10vw, 10.5rem); height: auto; }
/* Keep the badge on screen instead of hiding it (1 Sep 2026, client: "always
   in the box on desktop"). The old rule hid it under 850px of window height,
   which is most laptops once tab/toolbar chrome is subtracted — the brand was
   missing from the desktop hero nearly all the time.

   The collision it was avoiding is real, though. The badge sizes off the
   viewport's WIDTH (10vw) while the band it lives in is set by its HEIGHT:
   the hero is bottom-anchored, so the eyebrow below creeps up as the window
   shortens. Wide-and-short is the bad case — 1728x900 left only 5px between
   them. So cap the width by the height actually available rather than hiding.

   Second term = (band - 16px breathing room) / the logo's 1.149 aspect ratio,
   where band = 100svh - top(112px) - C. C is the bottom-anchored copy block,
   which grows with the fluid headline — measured 507px @1024w, 544 @1173,
   581 @1440, 590 @1728, so C ~= 406px + 11.8vw. That fit is ~5px light around
   1440 (C rises fast then flattens), so the constant carries 32px of slack
   rather than 16: 112px + 32px + 406px = 550px. Verified clearance is then
   27-52px across 1024-1728. The cap only bites on short windows; at 1173x841
   it computes 133px and the 10vw width wins unchanged.

   No floor: on a genuinely tiny window the term reaches zero and the badge
   bows out on its own, which is the honest outcome when nothing fits. */
@media (min-width: 62rem) {
  .hero__badge { width: min(clamp(7rem, 10vw, 10.5rem), calc((100svh - 11.8vw - 550px) / 1.149)); }
}
/* Landscape phones only — a 390px-tall hero has no top band at all. Portrait
   keeps the badge (31 Aug 2026): an 812px handset is under any laptop
   threshold, and a height-only rule used to wipe the brand off mobile too. */
@media (max-height: 620px) and (orientation: landscape) { .hero__badge { display: none; } }

/* scroll cue */
.cue {
  position: absolute; left: 50%; bottom: var(--s-5); z-index: 3;
  transform: translateX(-50%);
  display: grid; justify-items: center; gap: var(--s-2);
}
.cue__rail {
  width: 1px; height: 44px; background: var(--line-on-dark-strong);
  position: relative; overflow: hidden;
}
.cue__rail::after {
  content: ""; position: absolute; inset: 0;
  background: var(--accent);
  transform: translate3d(0, -100%, 0);
  animation: cue-run 2.4s var(--ease-out) infinite;
}
@keyframes cue-run {
  0%   { transform: translate3d(0, -100%, 0); }
  55%  { transform: translate3d(0, 0, 0); }
  100% { transform: translate3d(0, 100%, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .cue__rail::after { animation: none; transform: none; opacity: .5; }
}

/* ============================================================
   AMBIENT MEDIA BANDS (full-bleed video/photo backgrounds)
   ============================================================ */

.band {
  position: relative;
  min-height: clamp(28rem, 78vh, 46rem);
  display: grid; align-items: center;
  overflow: hidden;
  isolation: isolate;
}
.band__media { position: absolute; inset: -9% 0; z-index: 0; overflow: hidden; }
.band__media img, .band__media video {
  width: 100%; height: 100%; object-fit: cover;
}
/* Two layers: a flat floor that guarantees contrast over bright frames
   (white kits, overcast sky), plus a gradient for depth. A single
   gradient tested fine over dark footage and failed over light. */
.band__scrim {
  position: absolute; inset: 0; z-index: 1;
  background:
    linear-gradient(to top, rgba(7,7,10,.86), rgba(7,7,10,.34) 52%, rgba(7,7,10,.66)),
    rgba(7,7,10,.42);
}
.band__body { position: relative; z-index: 2; display: grid; gap: var(--s-5); }
.band--center .band__body { justify-items: center; text-align: center; }
/* copy over photography needs more punch than copy on a flat panel */
.band .lede { color: rgba(255,255,255,.84); }
.band--center .band__title { max-width: 18ch; }

/* ============================================================
   SPLIT FEATURE — full-bleed video pane beside a quote
   (31 Aug 2026, client) "Inside the work" is no longer a full-bleed strip
   with centred copy over it. The clip now fills exactly the left half of the
   viewport, edge to edge, with the founder quote occupying the right half.
   No shell, no gutter, no gap: the two halves meet in the middle of the
   screen. The video is the whole upright frame the phone shot, covering a
   50vw x 100svh pane — at that pane shape a little of the top and bottom is
   outside the crop, which is the trade the client chose for a clean
   half-and-half split with no letterboxing.
   ============================================================ */
.splitfeat {
  display: grid;
  grid-template-columns: 1fr;      /* phones stack: video, then quote */
  position: relative;
  isolation: isolate;
}
@media (min-width: 62rem) {
  .splitfeat { grid-template-columns: 1fr 1fr; min-height: 100svh; }
}

.splitfeat__media {
  position: relative;
  overflow: hidden;
  background: var(--ink-900);
  aspect-ratio: 9 / 16;            /* phones: the clip's own shape, full width */
}
@media (min-width: 62rem) {
  .splitfeat__media { aspect-ratio: auto; min-height: 100svh; }
}
.splitfeat__media > video,
.splitfeat__media > img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
}
/* Copy sits over the video, so it needs its own floor for contrast — the
   footage runs from bright sky to dark gym within one clip. */
.splitfeat__scrim {
  position: absolute; inset: 0; z-index: 1;
  background:
    linear-gradient(to top, rgba(7,7,10,.82), rgba(7,7,10,.28) 55%, rgba(7,7,10,.55)),
    rgba(7,7,10,.34);
}
.splitfeat__over {
  position: absolute; z-index: 2;
  left: 0; right: 0; bottom: 0;
  display: grid; gap: var(--s-4);
  padding: var(--s-6) var(--gutter) var(--s-7);
  color: #fff;                 /* pure white over footage, not the body's off-white */
}
/* Caption laid over a media pane — big and white. Capped in measure so it
   breaks as a deliberate two- or three-line stack rather than running the
   full width of the pane. */
.splitfeat__cap { max-width: 12ch; }
@media (min-width: 62rem) {
  .splitfeat__over { padding: var(--s-8) clamp(2rem, 4vw, 3.5rem); }
}

/* Quote half — centred in its own column, never full-bleed text */
.splitfeat__quote {
  display: grid; align-content: center; justify-items: start;
  gap: var(--s-5);
  padding: var(--s-9) var(--gutter);
  background: var(--bg-dark-raised);
}
@media (min-width: 62rem) {
  .splitfeat__quote { padding: var(--s-9) clamp(2.5rem, 5vw, 5rem); }
}
.splitfeat__quote > * { max-width: 34ch; }

/* Content variant of the split — a full copy block (eyebrow, heading, body,
   list) rather than a centred quote. Used on Speed & Agility, where the media
   sits in the SECOND column: the text pane is first in the DOM, so desktop
   puts the video on the right and mobile stacks heading-first, which is the
   right reading order on a phone either way. The section grows past 100svh
   when the copy is long; the media pane is a grid item, so it stretches to
   whatever height the copy sets and the video keeps filling it edge to edge. */
.splitfeat__body {
  display: grid; align-content: center;
  gap: var(--s-5);
  padding: var(--s-9) var(--gutter);
  background: var(--bg-dark);
}
@media (min-width: 62rem) {
  .splitfeat__body { padding: var(--s-9) clamp(2.5rem, 5vw, 5rem); }
}
.splitfeat__body > .flist { margin-top: var(--s-2); }

/* When the heading sits OVER the media rather than in the copy pane, the
   stacked phone order has to lead with the media — otherwise the page opens
   on a headless paragraph and the heading turns up underneath it. Desktop is
   unaffected: the body is still first in the DOM, so it still takes column 1. */
@media (max-width: 61.99rem) {
  .splitfeat--titled .splitfeat__media { order: -1; }
}

/* sticky parallax figure */
.sticky-media {
  position: relative;
  height: 200vh;
}
.sticky-media__inner {
  position: sticky; top: 0; height: 100svh; overflow: hidden;
  display: grid; place-items: center;
}

/* ============================================================
   MEDIA FRAMES + REVEALS
   ============================================================ */

.frame { position: relative; overflow: hidden; background: var(--ink-800); }
.frame img, .frame video { width: 100%; height: 100%; object-fit: cover; }
.frame--3x4  { aspect-ratio: 3 / 4; }
.frame--4x5  { aspect-ratio: 4 / 5; }
.frame--1x1  { aspect-ratio: 1 / 1; }
.frame--16x9 { aspect-ratio: 16 / 9; }
.frame--9x16 { aspect-ratio: 9 / 16; }
.frame--21x9 { aspect-ratio: 21 / 9; }

.frame__tag {
  position: absolute; left: var(--s-4); bottom: var(--s-4); z-index: 2;
  color: #fff; text-shadow: 0 1px 12px rgba(0,0,0,.8);
}

/* Designed placeholder for a frame with no confirmed photo yet — used on
   the Founder section while the correct photo of Brodie is outstanding.
   A wrong face under his name is worse than an honest gap, so this
   stands in rather than either misattributed candidate. */
.frame--pending {
  display: grid; place-items: center; gap: var(--s-4);
  background:
    repeating-linear-gradient(135deg, transparent 0 18px, rgba(255,255,255,.035) 18px 19px),
    var(--ink-800);
  border: 1px solid var(--line-on-dark);
}
.frame--pending__mark { width: 2.75rem; height: auto; opacity: .5; }
.frame--pending__note { text-align: center; padding-inline: var(--s-5); }

/* ============================================================
   DISCIPLINE LIST — numbered rows, media on hover (desktop)
   ============================================================ */

.rows { border-top: 1px solid var(--line-on-dark); }
.section--light .rows, .section--alt .rows { border-top-color: var(--line-on-light); }

.row {
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--s-4);
  padding-block: clamp(1.35rem, 3vw, 2.1rem);
  border-bottom: 1px solid var(--line-on-dark);
  transition: color var(--dur-ui-slow) var(--ease-out);
}
.section--light .row, .section--alt .row { border-bottom-color: var(--line-on-light); }

.row__num { font-size: var(--fs-mono); letter-spacing: .14em; color: var(--text-on-dark-faint); font-variant-numeric: tabular-nums; width: 2.5rem; }
.section--light .row__num, .section--alt .row__num { color: var(--text-on-light-faint); }
.row__title { font-size: clamp(1.5rem, 3.4vw, 2.6rem); font-weight: 700; letter-spacing: var(--tr-heading); line-height: 1.05; }
.row__meta { font-size: var(--fs-mono); letter-spacing: .12em; text-transform: uppercase; color: var(--text-on-dark-faint); display: none; }
@media (min-width: 48rem) { .row__meta { display: block; } }
.section--light .row__meta, .section--alt .row__meta { color: var(--text-on-light-faint); }

/* Disciplines: the list drives one sticky media panel beside it.
   The previous cursor-following thumbnail sat on top of the rows it was
   meant to illustrate and covered the copy underneath. */
.disciplines { display: grid; gap: clamp(1.5rem, 4vw, 4rem); }
@media (min-width: 62rem) {
  .disciplines { grid-template-columns: minmax(0, 1.3fr) minmax(0, .7fr); align-items: start; }
}

.disc-media {
  display: none;
  position: sticky;
  top: clamp(6rem, 16vh, 11rem);
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background: var(--ink-800);
}
@media (min-width: 62rem) { .disc-media { display: block; } }

.disc-media__item {
  position: absolute; inset: 0;
  opacity: 0;
  transform: scale(1.05);
  transition: opacity 520ms var(--ease-out), transform 900ms var(--ease-out);
}
.disc-media__item.is-active { opacity: 1; transform: scale(1); }
.disc-media__item img { width: 100%; height: 100%; object-fit: cover; }
.disc-media__cap {
  position: absolute; left: var(--s-4); bottom: var(--s-4); z-index: 2;
  color: #fff; text-shadow: 0 1px 14px rgba(0,0,0,.75);
}

.row.is-active .row__title { color: var(--accent); }
.section--light .row.is-active .row__title { color: var(--accent-on-light); }

@media (prefers-reduced-motion: reduce) {
  .disc-media__item { transition: opacity 160ms linear; transform: none; }
  .disc-media__item.is-active { transform: none; }
}

/* ============================================================
   CARD GRID
   ============================================================ */

.grid { display: grid; gap: clamp(1rem, 2.2vw, 1.75rem); }
.grid--2 { grid-template-columns: 1fr; }
.grid--3 { grid-template-columns: 1fr; }
.grid--4 { grid-template-columns: repeat(2, 1fr); }
@media (min-width: 40rem) {
  .grid--2 { grid-template-columns: repeat(2, 1fr); }
  .grid--3 { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 62rem) {
  .grid--3 { grid-template-columns: repeat(3, 1fr); }
  .grid--4 { grid-template-columns: repeat(4, 1fr); }
}

.card { display: grid; gap: var(--s-4); align-content: start; }
.card__media { overflow: hidden; position: relative; background: var(--ink-800); }
.card__media img, .card__media video {
  width: 100%; height: 100%; object-fit: cover;
  transition: transform 900ms var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .card:hover .card__media img, .card:hover .card__media video { transform: scale(1.045); }
}
@media (prefers-reduced-motion: reduce) {
  .card__media img, .card__media video { transition: none; }
  .card:hover .card__media img, .card:hover .card__media video { transform: none; }
}
.card__body { display: grid; gap: var(--s-2); }
.card__text { font-size: .95rem; color: var(--text-on-dark-muted); }
.section--light .card__text, .section--alt .card__text { color: var(--text-on-light-muted); }

/* ============================================================
   REELS — native 9:16, framed
   ============================================================ */

.reels {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(13rem, 16rem);
  gap: clamp(.75rem, 1.6vw, 1.25rem);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding-bottom: var(--s-3);
  scrollbar-width: thin;
  scrollbar-color: var(--line-on-dark-strong) transparent;
}
.reels::-webkit-scrollbar { height: 4px; }
.reels::-webkit-scrollbar-track { background: var(--line-on-dark); }
.reels::-webkit-scrollbar-thumb { background: var(--accent); }

/* SWIPE AFFORDANCE (31 Aug 2026, client) — on a phone this rail scrolls
   sideways, and the only cue was a 4px scrollbar most people never register.
   Two signals instead: the last tile bleeds off the right edge so the row
   visibly continues, and an explicit "Swipe" hint that removes itself the
   first time the rail is actually scrolled. Desktop is a plain 6-up grid with
   nothing to swipe, so both only appear while the rail actually overflows. */
.reels-wrap { position: relative; }
/* fade the right edge so the row reads as continuing past the screen */
.reels-wrap.is-scrollable::after {
  content: ""; position: absolute; top: 0; right: 0; bottom: var(--s-6);
  width: clamp(2rem, 9vw, 4rem); pointer-events: none; z-index: 1;
  background: linear-gradient(to left, var(--bg-dark), transparent);
  transition: opacity var(--dur-ui) var(--ease-out);
}
.reels-wrap.is-swiped::after { opacity: 0; }
.reels-hint {
  display: none;
  align-items: center; gap: var(--s-2);
  margin-top: var(--s-3);
  color: var(--accent);
  transition: opacity var(--dur-ui-slow) var(--ease-out),
              transform var(--dur-ui-slow) var(--ease-out);
}
/* Shown by JS when the rail is genuinely scrollable (.is-scrollable), not by a
   breakpoint (31 Aug 2026, client): the hint was tied to max-width 61.99rem,
   so at any width where the rail still overflowed but the query had stopped
   matching, the cue vanished while the swipe was still needed. Measuring the
   element is the honest test — it also covers tablets and future layouts. */
.reels-wrap.is-scrollable .reels-hint { display: inline-flex; }
.reels-wrap.is-swiped .reels-hint { opacity: 0; transform: translate3d(-6px, 0, 0); }
/* the arrow nudges right, so the hint reads as a direction not a label */
.reels-hint__arrow { animation: swipe-nudge 1.8s var(--ease-out) infinite; }
@keyframes swipe-nudge {
  0%, 60%, 100% { transform: translate3d(0, 0, 0); }
  30%           { transform: translate3d(5px, 0, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .reels-hint__arrow { animation: none; }
}
@media (min-width: 62rem) {
  .reels {
    grid-auto-flow: row;
    grid-template-columns: repeat(6, 1fr);
    overflow: visible;
  }
}
.reel { scroll-snap-align: start; position: relative; }
.reel__frame { aspect-ratio: 9/16; overflow: hidden; background: var(--ink-800); position: relative; }
.reel__frame video, .reel__frame img { width: 100%; height: 100%; object-fit: cover; }
.reel__label { margin-top: var(--s-3); }

/* offset the reel row so it reads as a designed rhythm, not a plain grid.
   Scoped to .reels (31 Aug 2026): unscoped, this dropped every even .reel
   anywhere on the site by 2.25rem — which silently un-levels any paired
   video layout using the same figure, e.g. the two masterclass clips. */
@media (min-width: 62rem) {
  .reels .reel:nth-child(even) { transform: translate3d(0, 2.25rem, 0); }
}

/* ============================================================
   TESTIMONIALS
   ============================================================ */

.quote {
  display: grid; gap: var(--s-5);
  padding-block: var(--s-6);
}
.quote__mark { font-size: 3rem; line-height: 1; color: var(--accent); font-weight: 700; }
.quote__text {
  font-size: clamp(1.4rem, 3.1vw, 2.4rem);
  font-weight: 600; line-height: 1.22; letter-spacing: var(--tr-heading);
  max-width: 24ch;
}
.quote__who { display: grid; gap: 2px; }
.quote__name { font-weight: 600; font-size: 1rem; }
.quote__role { font-size: .8125rem; color: var(--text-on-dark-faint); }
.section--light .quote__role, .section--alt .quote__role { color: var(--text-on-light-faint); }

/* video testimonial card with play affordance */
.vcard { position: relative; display: grid; gap: var(--s-4); }
.vcard__frame {
  position: relative; aspect-ratio: 9/16; overflow: hidden;
  background: var(--ink-800); cursor: pointer;
}
.vcard__frame video, .vcard__frame img { width: 100%; height: 100%; object-fit: cover; }
.vcard__play {
  position: absolute; inset: 0; z-index: 2;
  display: grid; place-items: center;
  background: linear-gradient(to top, rgba(7,7,10,.7), rgba(7,7,10,0) 55%);
}
.vcard__disc {
  width: 60px; height: 60px; border-radius: 999px;
  background: rgba(255,255,255,.14);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  border: 1px solid rgba(255,255,255,.4);
  display: grid; place-items: center;
  transition: transform var(--dur-ui-slow) var(--ease-out), background-color var(--dur-ui) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .vcard__frame:hover .vcard__disc { transform: scale(1.08); background: var(--accent); }
  .vcard__frame:hover .vcard__disc svg { fill: var(--accent-contrast); }
}
.vcard__cap {
  position: absolute; left: var(--s-4); right: var(--s-4); bottom: var(--s-4); z-index: 3;
  color: #fff; display: grid; gap: 2px;
}

/* modal player */
.modal {
  position: fixed; inset: 0; z-index: var(--z-modal);
  display: grid; place-items: center;
  padding: var(--gutter);
  background: rgba(4,4,6,.92);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  opacity: 0; visibility: hidden;
  transition: opacity var(--dur-ui-slow) var(--ease-out), visibility var(--dur-ui-slow);
}
.modal.is-open { opacity: 1; visibility: visible; }
.modal__inner {
  position: relative;
  max-height: 88svh; max-width: min(28rem, 92vw);
  transform: scale(.96);
  transition: transform var(--dur-ui-slow) var(--ease-out);
}
.modal.is-open .modal__inner { transform: scale(1); }
.modal__inner video { max-height: 88svh; width: auto; background: #000; }
.modal__close {
  position: absolute; top: -3rem; right: 0;
  display: inline-flex; align-items: center; justify-content: flex-end; gap: var(--s-2);
  min-height: 44px; min-width: 44px;
  font-size: .8125rem; font-weight: 600; color: #fff;
}

/* ============================================================
   MARQUEE — accolades ticker
   ============================================================ */

.marquee {
  overflow: hidden;
  border-block: 1px solid var(--line-on-dark);
  padding-block: var(--s-5);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.section--light .marquee, .section--alt .marquee { border-color: var(--line-on-light); }
.marquee__track {
  display: flex; align-items: center; gap: clamp(2rem, 5vw, 4.5rem);
  width: max-content;
  animation: marquee 46s linear infinite;
  will-change: transform;
}
@keyframes marquee { from { transform: translate3d(0,0,0); } to { transform: translate3d(-50%,0,0); } }
@media (hover: hover) and (pointer: fine) {
  .marquee:hover .marquee__track { animation-play-state: paused; }
}
@media (prefers-reduced-motion: reduce) {
  .marquee__track { animation: none; }
  .marquee { -webkit-mask-image: none; mask-image: none; overflow-x: auto; }
}
.marquee__item {
  font-size: clamp(.8rem, 1.1vw, .95rem); font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--text-on-dark-muted); white-space: nowrap;
  display: flex; align-items: center; gap: var(--s-4);
}
.marquee__item::after { content: ""; width: 5px; height: 5px; border-radius: 999px; background: var(--accent); flex: none; }

/* ============================================================
   KEY FIGURES
   Six tiles on a hairline grid. Dim by default, one lit at a time —
   the lit tile follows the pointer, so the eye is led rather than
   shouted at by six competing numbers.
   ============================================================ */

.figures {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1px;
  background: var(--line-on-dark);
  border: 1px solid var(--line-on-dark);
}
@media (min-width: 34rem) { .figures { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 62rem) { .figures { grid-template-columns: repeat(3, 1fr); } }

.figure {
  background: var(--ink-900);
  padding: clamp(1.25rem, 2.6vw, 2.25rem);
  min-height: clamp(9.5rem, 17vw, 14rem);
  display: grid;
  grid-template-rows: auto 1fr auto;
  gap: var(--s-4);
  transition: background-color var(--dur-ui-slow) var(--ease-out);
}
.figure__idx {
  font-size: var(--fs-mono);
  letter-spacing: .14em;
  color: var(--text-on-dark-faint);
  font-variant-numeric: tabular-nums;
}
.figure__num {
  align-self: center;
  text-align: right;
  font-size: clamp(2.75rem, 6.4vw, 5.25rem);
  font-weight: 700;
  line-height: .92;
  letter-spacing: -.045em;
  font-variant-numeric: tabular-nums;
  color: rgba(255,255,255,.30);
  transition: color 420ms var(--ease-out);
}
.figure__label {
  text-align: right;
  font-size: .875rem;
  color: var(--text-on-dark-faint);
  transition: color 420ms var(--ease-out);
}
.figure.is-lit { background: var(--ink-850); }
.figure.is-lit .figure__num   { color: #fff; }
.figure.is-lit .figure__label { color: var(--text-on-dark-muted); }

/* ============================================================
   SPLIT (media + copy)
   ============================================================ */

.split { display: grid; gap: clamp(2rem, 5vw, 4.5rem); align-items: center; }
@media (min-width: 62rem) {
  .split { grid-template-columns: repeat(2, minmax(0,1fr)); }
  .split--wide-left  { grid-template-columns: 1.15fr .85fr; }
  .split--wide-right { grid-template-columns: .85fr 1.15fr; }
  .split--flip > :first-child { order: 2; }
}
.split__body { display: grid; gap: var(--s-5); align-content: center; }

/* VIDEO-BESIDE-TEXT (31 Aug 2026, client) — the football page pairs each of
   its four clips with one writing section, alternating side to side. The
   clips are upright phone footage shown at their full height, so the media
   column is deliberately the NARROW one: a 9:16 frame in a half-width column
   stands over 1300px tall and buries the writing next to it. At 24rem the
   whole frame shows at ~680px, which sits comfortably against a column of
   text. Below 62rem the split stacks and the video caps out so it never
   takes over a phone screen. */
.split--vid { align-items: center; }
.split--vid > .split__media { width: 100%; max-width: 24rem; justify-self: center; }
@media (min-width: 62rem) {
  .split--vid       { grid-template-columns: minmax(0, 24rem) minmax(0, 1fr); }
  .split--vid-right { grid-template-columns: minmax(0, 1fr) minmax(0, 24rem); }
  .split--vid-right > .split__media { order: 2; }
}

/* A stacked list of writing blocks beside a video — the four "Who it's for"
   cards and the four parent reviews, relaid as a column instead of a
   four-across grid. No copy is cut; the same text simply runs vertically. */
.stacklist { display: grid; gap: var(--s-5); }
.stacklist__item { display: grid; gap: var(--s-2); padding-block: var(--s-4); border-top: 1px solid var(--line-on-dark); }
.section--light .stacklist__item, .section--alt .stacklist__item { border-top-color: var(--line-on-light); }
.stacklist__item:first-child { padding-top: 0; border-top: 0; }

/* feature list */
.flist { display: grid; gap: var(--s-3); }
.fitem {
  display: grid; grid-template-columns: auto 1fr; gap: var(--s-4);
  align-items: start; padding-block: var(--s-3);
  border-top: 1px solid var(--line-on-dark);
}
.section--light .fitem, .section--alt .fitem { border-top-color: var(--line-on-light); }
.fitem__k { font-size: var(--fs-mono); letter-spacing: .14em; color: var(--accent); font-variant-numeric: tabular-nums; padding-top: .35em; }
.section--light .fitem__k, .section--alt .fitem__k { color: var(--accent-on-light); }
.fitem__v { display: grid; gap: 4px; }
.fitem__t { font-weight: 600; font-size: 1.02rem; letter-spacing: -.015em; }
.fitem__d { font-size: .9rem; color: var(--text-on-dark-muted); }

/* Policies reuse .flist, but its description size is a caption meant for
   one-line qualifiers under a credential — policy clauses are read in full,
   so they get body sizing and a measure. (31 Aug 2026, rules & policies.) */
.flist--policy { gap: var(--s-4); }
.flist--policy .fitem__t { font-size: 1.12rem; }
.flist--policy .fitem__d { font-size: .975rem; line-height: 1.6; max-width: 68ch; }
.section--light .fitem__d, .section--alt .fitem__d { color: var(--text-on-light-muted); }

/* price tile */
.price {
  display: grid; gap: var(--s-4); align-content: start;
  padding: clamp(1.5rem, 3vw, 2.25rem);
  border: 1px solid var(--line-on-dark);
  background: var(--ink-850);
  transition: border-color var(--dur-ui-slow) var(--ease-out), transform var(--dur-ui-slow) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .price:hover { border-color: var(--accent); transform: translate3d(0,-3px,0); }
}
.price__amt { font-size: clamp(1.6rem, 3vw, 2.1rem); font-weight: 700; letter-spacing: -.03em; color: var(--accent); }
.price__amt small { font-size: .8125rem; font-weight: 500; letter-spacing: 0; color: var(--text-on-dark-faint); display: block; margin-top: 4px; }

/* ============================================================
   FOOTER
   ============================================================ */

.foot { background: var(--ink-900); border-top: 1px solid var(--line-on-dark); }
.foot__top { display: grid; gap: clamp(2.5rem, 5vw, 4rem); padding-block: var(--section-y); }
@media (min-width: 62rem) { .foot__top { grid-template-columns: 1.4fr .8fr .8fr 1fr; } }
.foot__col { display: grid; gap: var(--s-4); align-content: start; }
.foot__h { font-size: var(--fs-eyebrow); letter-spacing: var(--tr-eyebrow); text-transform: uppercase; color: var(--text-on-dark-faint); font-weight: 600; }
/* gap tightened as the links grew their own padding (31 Aug 2026 audit) —
   the hit box goes 19px -> 44px while the column's overall height barely moves */
.foot__list { display: grid; gap: 0; }
.foot__list a {
  font-size: .9375rem; color: var(--text-on-dark-muted);
  display: inline-flex; align-items: center; min-height: 44px;
  transition: color var(--dur-ui) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) { .foot__list a:hover { color: var(--accent); } }
.foot__bar {
  display: flex; flex-wrap: wrap; gap: var(--s-4); justify-content: space-between;
  padding-block: var(--s-5); border-top: 1px solid var(--line-on-dark);
}

/* giant wordmark */
.wordmark {
  font-size: clamp(3rem, 15vw, 13rem); font-weight: 700;
  letter-spacing: -.05em; line-height: .82;
  color: transparent;
  -webkit-text-stroke: 1px var(--line-on-dark-strong);
  padding-block: clamp(1.5rem, 4vw, 3rem);
  user-select: none;
}

/* ============================================================
   FORM
   ============================================================ */

.form { display: grid; gap: var(--s-5); }
.field { display: grid; gap: var(--s-2); }
.field label { font-size: var(--fs-eyebrow); letter-spacing: var(--tr-eyebrow); text-transform: uppercase; font-weight: 600; color: var(--text-on-dark-faint); }
.field input, .field textarea, .field select {
  font: inherit; font-size: .95rem;
  background: transparent; color: var(--text-on-dark);
  border: 0; border-bottom: 1px solid var(--line-on-dark-strong);
  padding: var(--s-3) 0;
  border-radius: 0;
  transition: border-color var(--dur-ui) var(--ease-out);
}
.field textarea { resize: vertical; min-height: 6rem; }
.field select option { background: var(--ink-800); color: #fff; }
.field input:focus, .field textarea:focus, .field select:focus {
  outline: none; border-bottom-color: var(--accent);
}
.field input::placeholder, .field textarea::placeholder { color: var(--text-on-dark-faint); }
.form__row { display: grid; gap: var(--s-5); }

/* Honeypot wrapper. Positioned off-screen rather than display:none — some bots
   skip hidden fields, and one that fills every input is exactly what this is
   for. Not .sr-only: that leaves the field in the accessibility tree. */
.form__hp {
  position: absolute; left: -9999px; top: auto;
  width: 1px; height: 1px; overflow: hidden;
}

/* Submit outcome. #formnote is aria-live, so this is the visual half of a
   message screen readers already hear. */
#formnote[data-state="ok"]  { color: var(--accent); }
#formnote[data-state="err"] { color: var(--danger); }

/* :user-invalid, not :invalid — :invalid paints every required field red
   before the visitor has typed a thing. */
.field input:user-invalid,
.field textarea:user-invalid { border-color: var(--danger); }

.form button[disabled] { opacity: .6; cursor: default; }
@media (min-width: 40rem) { .form__row { grid-template-columns: 1fr 1fr; } }

/* ============================================================
   SCROLL REVEAL — the motion workhorse
   Opacity + transform only. Transitions (not keyframes) so a
   fast scroll can interrupt and retarget mid-flight.
   ============================================================ */

/* Reveals are opt-IN, not opt-out.
   `.js-motion` is set by motion.js the moment it runs. Without it every
   rule below is inert and the page renders fully visible — so a script
   that fails to load, or an environment where the reveal driver never
   runs, degrades to a plain readable page instead of black voids.

   No permanent `will-change`: these are one-shot, and promoting 40+
   elements for the life of the page costs memory and leaves stale
   composited layers behind. The transition promotes them while it runs. */
.js-motion [data-reveal] {
  opacity: 0;
  transform: translate3d(0, 14px, 0);
  transition:
    opacity var(--dur-reveal) var(--ease-out),
    transform var(--dur-reveal) var(--ease-out);
  transition-delay: var(--d, 0ms);
}
.js-motion [data-reveal].is-in { opacity: 1; transform: translate3d(0, 0, 0); }

.js-motion [data-reveal="fade"]  { transform: none; }
.js-motion [data-reveal="left"]  { transform: translate3d(-26px, 0, 0); }
.js-motion [data-reveal="right"] { transform: translate3d(26px, 0, 0); }
.js-motion [data-reveal="scale"] { transform: scale(.97); }
.js-motion [data-reveal="scale"].is-in { transform: scale(1); }

/* image clip reveal — bar wipes up, image settles from a slight zoom */
.js-motion [data-reveal="clip"] {
  opacity: 1;
  transform: none;
  clip-path: inset(0 0 100% 0);
  transition: clip-path var(--dur-media) var(--ease-out);
  transition-delay: var(--d, 0ms);
}
.js-motion [data-reveal="clip"].is-in { clip-path: inset(0 0 0% 0); }
.js-motion [data-reveal="clip"] > img,
.js-motion [data-reveal="clip"] > video {
  transform: scale(1.05);
  transition: transform var(--dur-media) var(--ease-out);
  transition-delay: var(--d, 0ms);
}
.js-motion [data-reveal="clip"].is-in > img,
.js-motion [data-reveal="clip"].is-in > video { transform: scale(1); }

/* line-by-line heading reveal */
.lines { display: grid; }
/* The mask that each line rises from behind. line-height on .h1/.h2 is a
   tight 1.02, so a plain `overflow: hidden` box clips the tails of g j p q y
   on the last line ("Strength & Conditionin[g]"). Pad the mask down and pull
   the same amount back with a negative margin, so descenders clear without
   changing the layout rhythm between lines. */
.js-motion .line { overflow: hidden; padding-bottom: 0.2em; margin-bottom: -0.2em; }
.js-motion .line > span {
  display: block;
  transform: translate3d(0, 105%, 0);
  transition: transform var(--dur-lines) var(--ease-out);
  transition-delay: var(--d, 0ms);
}
.js-motion .lines.is-in .line > span { transform: translate3d(0, 0, 0); }

@media (prefers-reduced-motion: reduce) {
  .js-motion [data-reveal] { transform: none !important; clip-path: none !important; }
  .js-motion [data-reveal] > img, .js-motion [data-reveal] > video { transform: none !important; }
  .js-motion .line > span { transform: none !important; }
  .js-motion .line { overflow: visible; }
}

/* Parallax layers are moved by JS (translate only). No `will-change` here
   (removed 31 Aug 2026 audit): it was permanent, so every hero and band video
   held its own GPU layer for the life of the page — real memory on a phone at
   1080x1920. The rAF loop writes `transform` every frame anyway, which promotes
   the element for exactly as long as it is actually moving. */

/* ============================================================
   MOBILE REFINEMENTS
   The home hero has to hold an eyebrow, a three-line headline, a lede
   and two buttons inside 100svh. On a 375px screen that is 624px of
   content in an 812px box — legible, but with no room left for the
   scroll cue, and the eyebrow's leading rule strands itself when the
   label wraps.
   ============================================================ */
@media (max-width: 40rem) {
  /* a wrapped eyebrow leaves the rule floating on its own line */
  .eyebrow::before { display: none; }

  .hero__body { gap: var(--s-5); padding-block: clamp(2rem, 6vh, 4rem); }
  .hero__actions { gap: var(--s-3); }
  .hero__actions .btn { flex: 1 1 100%; justify-content: center; }

  /* the cue would collide with the buttons at this height */
  .hero .cue { display: none; }
  .hero--page .hero__title { max-width: none; }

  /* full-width tap targets read better than centred pills here */
  .btn { padding: 1rem 1.4rem; }

  .quote__text { max-width: none; }
  .wordmark { -webkit-text-stroke-width: .5px; }
}

/* very short viewports (landscape phones) — let the hero breathe */
@media (max-height: 34rem) and (max-width: 60rem) {
  .hero { min-height: auto; padding-block: calc(var(--s-9) + 2rem) var(--s-7); }
  .hero .cue { display: none; }
}
