/* ==========================================================================
   BULLETPROOF PERFORMANCE — stylesheet

   Dark-first design. The palette is built from the logo's single saturated
   colour, #80C0E0, which is why the mark sits so naturally on these grounds.

   THEMING
   Every component is written against neutral tokens (--heading, --text,
   --surface, --line, --accent). :root holds the dark values; .band-light
   re-declares the same names, so a section becomes a light band just by
   carrying that class — no component needs a light-mode variant.

   SPACING
     Desktop  (above 1024px)  : 100px section rhythm
     Tablet   (1024 and below): 75px
     Mobile   (767 and below) : 50px
   Sections use --section-y, cards use --card-pad, section head → content
   uses --block-gap. Nothing is spaced by hand.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. DESIGN TOKENS
   -------------------------------------------------------------------------- */
:root {
  /* Brand scale, anchored on the logo blue */
  --brand-100: #d9edf8;
  --brand-200: #aed8ee;
  --brand-300: #80c0e0;   /* sampled from the artwork, whose dominant blue
                             measures #7cb8dc, so this is a shade off */
  --brand-350: #75afd1;   /* the pale blue the client gave first. This is the
                             family the logo artwork actually contains: every
                             blue pixel in logo.png is within 30 of it, and none
                             is anywhere near the deep --brand-600. */
  --brand-400: #4fa3d0;
  --brand-500: #2f8fc4;
  --brand-600: #196fa3;   /* the brand blue, as the client states it.
                             White clears 5.46:1 */
  --brand-700: #145c86;
  --brand-800: #0f4666;
  --brand-900: #0b3550;   /* deepest step; light-band CTA hover only */

  /* Dark ground */
  --page-bg: #070b10;
  --page-bg-2: #0a1017;
  --surface: #0e151e;
  --surface-2: #131c27;
  --line: rgba(255, 255, 255, .09);
  --line-strong: rgba(128, 192, 224, .22);

  --heading: #ffffff;
  --text: rgba(255, 255, 255, .72);
  --muted: rgba(255, 255, 255, .55);
  --accent: var(--brand-300);
  --accent-ink: var(--brand-300);   /* icon glyphs on tinted chips */
  --accent-chip: rgba(128, 192, 224, .12);

  /* Primary CTA, dark ground. Flat --brand-600, which is the client's brand
     blue #196fa3, with the logo blue carried by the chevron pip beside the
     label. Flat rather than a gradient because a gradient means most of the
     pill is never the brand colour, and the pale logo blue cannot be the field
     itself: white is only 2.13:1 on it, so the label would have to go near
     black, which reads as a disabled control rather than the main action. */
  --cta-fill:       var(--brand-600);
  --cta-fill-hover: var(--brand-700);     /* deepens, white goes to 7.21:1 */
  --cta-ink:        #ffffff;              /* 5.46:1 on the fill */
  --cta-pip:        var(--brand-350);     /* the logo blue, the client's hex */
  --cta-pip-ink:    #06141f;              /* arrow, 7.55:1 inside the pip */
  --cta-ring:       var(--brand-350);     /* the logo blue reads clearly 3px off
                                             a brand-600 button */
  --cta-edge:       transparent;
  --cta-edge-hover: transparent;
  --cta-shadow:     0 14px 30px -16px rgba(25, 111, 163, .9);

  /* Type */
  --font-display: "Archivo", "Helvetica Neue", Arial, sans-serif;
  --font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
               Helvetica, Arial, sans-serif;

  /* Spacing */
  --space-xs: 8px;
  --space-sm: 12px;
  --space-md: 16px;
  --space-lg: 24px;
  --space-xl: 32px;
  --space-2xl: 48px;

  --section-y: 100px;
  --block-gap: 64px;
  --grid-gap: 28px;
  --card-pad: 32px;

  --shell: 1400px;
  --gutter: 40px;

  /* One radius family */
  --radius-xs: 6px;
  --radius-sm: 10px;
  --radius: 14px;
  --radius-lg: 20px;

  --shadow-md: 0 18px 40px -22px rgba(0, 0, 0, .8);
  --shadow-lg: 0 40px 80px -36px rgba(0, 0, 0, .9);
  --glow: 0 0 0 1px rgba(128, 192, 224, .18);

  --header-h: 92px;
  /* What the header actually measures once it is stuck: the inner shrinks to
     74px and the border adds one. 92px is its at rest height over the hero,
     which no in page jump can ever land against, because arriving anywhere
     below the fold leaves the header stuck. */
  --header-stuck: 75px;
  /* One control height for every button. Nothing else sets a button height:
     min-height plus align-items centre does it, so a variant changing its
     padding can no longer change how tall it is. */
  --btn-h: 52px;
  --ease: cubic-bezier(.22, .61, .36, 1);
}

/* Light band — same token names, light values. */
.band-light {
  --page-bg: #f3f7fa;
  --page-bg-2: #ffffff;
  --surface: #ffffff;
  --surface-2: #eaf1f7;
  --line: #dfe8f0;
  --line-strong: rgba(23, 102, 154, .25);

  --heading: #080d13;
  --text: #46535f;
  --muted: #66737f;
  --accent: #17669a;               /* 5.9:1 on white */
  --accent-ink: #17669a;
  --accent-chip: rgba(23, 102, 154, .09);

  /* Primary CTA, light ground. The fill, label and pip are the same on both
     grounds now, so only the two things that are measured against the band need
     re-declaring: the deep fill already sits at 4.85:1 against #f3f7fa, so it
     needs no edge, and the focus ring goes to --brand-600 to match the one
     .band-light :focus-visible sets for everything else here.
     .band-light--blue needs nothing of its own. */
  --cta-fill-hover: var(--brand-700);
  --cta-ring:       var(--brand-600);
  --cta-shadow:     0 14px 30px -16px rgba(25, 111, 163, .55);

  background: var(--page-bg);
  color: var(--text);
}

/* A bluer light band. Carries more colour than .band-light so two light
   sections in a row still read as separate bands. --muted is darkened
   because the default #66737f only reaches 4.3:1 on this ground. */
.band-light--blue {
  --page-bg: #eaf2f9;
  --surface: #ffffff;
  --surface-2: #dceaf5;
  --line: #d3e3f0;
  --line-strong: rgba(23, 102, 154, .28);
  --muted: #5d6a76;
  --accent-chip: rgba(23, 102, 154, .1);
}

/* --------------------------------------------------------------------------
   2. RESET + BASE
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
  /* `clip`, not `hidden`: stops sideways scroll without creating a scroll
     container, so position:sticky keeps working. */
  overflow-x: clip;
}

body {
  margin: 0;
  background: var(--page-bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* MUST be `clip`, never `hidden`. `overflow-x: hidden` turns <body> into a
     scroll container, and every `position: sticky` descendant then sticks to
     a box that never scrolls — silently breaking the pinned roadmap and the
     sticky header. `clip` blocks sideways scrolling without that side effect. */
  overflow-x: clip;
}

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

/* Author `display` declarations outrank the UA sheet's `[hidden]` rule, so
   anything given display:block below (the video facade, for one) would ignore
   the hidden attribute entirely. This puts it back. */
[hidden] { display: none !important; }

h1, h2, h3, h4 {
  font-family: var(--font-display);
  color: var(--heading);
  line-height: 1.08;
  letter-spacing: -.025em;
  margin: 0 0 var(--space-md);
  font-weight: 800;
  text-wrap: balance;
}
h1 { font-size: clamp(2.6rem, 5.4vw, 4.25rem); line-height: 1.02; }
h2 { font-size: clamp(1.85rem, 3.6vw, 2.9rem); }
h3 { font-size: clamp(1.15rem, 1.7vw, 1.4rem); font-weight: 700; line-height: 1.25; }

/* Highlighted word inside a heading, as in the reference */
em { font-style: normal; color: var(--accent); }

p { margin: 0 0 var(--space-md); }
p:last-child { margin-bottom: 0; }

a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 3px; }

ul, ol { margin: 0; padding: 0; list-style: none; }
strong { color: var(--heading); font-weight: 600; }

::selection { background: var(--brand-300); color: #06101a; }

:focus-visible {
  outline: 3px solid var(--brand-300);
  outline-offset: 3px;
  border-radius: var(--radius-xs);
}
.band-light :focus-visible { outline-color: var(--brand-600); }

.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

.skip-link {
  position: absolute;
  left: 50%;
  top: 0;
  transform: translate(-50%, -110%);
  z-index: 200;
  background: var(--brand-600);
  color: #fff;
  padding: var(--space-sm) var(--space-lg);
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  font-weight: 600;
  text-decoration: none;
  transition: transform .2s var(--ease);
}
.skip-link:focus { transform: translate(-50%, 0); }

/* --------------------------------------------------------------------------
   3. LAYOUT PRIMITIVES
   -------------------------------------------------------------------------- */
.shell {
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section { padding-block: var(--section-y); position: relative; }
/* The stuck height, and no gap on top of it. Measured against the at rest
   token this was landing 29px below the header on desktop and 11px below on a
   phone, and wherever a light band sits above a dark one, as the results band
   does above the community section, that gap showed as a bright strip under
   the header and read as a rendering fault. Every section carries 50px to
   100px of its own top padding, so the breathing room was never coming from
   here anyway. */
section[id] { scroll-margin-top: var(--header-stuck); }

/* The angled brand streak that used to sit here is gone. At 103deg its bands
   were only 13 degrees off vertical, and the ramp was narrow (transparent 42,
   colour 50, transparent 58), so it had defined shoulders. Against a large flat
   dark ground that reads as a seam down the page rather than as lighting:
   measured on the academy section it was rgb(15,24,32) against rgb(10,16,23)
   either side, a step of 7.5 in luminance with nothing around it to explain the
   edge. It also only ever matched one section, since every other .section on
   the page carries .band-light. Section lighting is done by the radial washes
   on .academy::after instead, which have no edges to catch. */

.eyebrow {
  font-family: var(--font-body);
  font-size: .76rem;
  font-weight: 600;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 var(--space-md);
  line-height: 1.4;
}
/* Keeps a sentence from breaking across lines, so the wrap lands at the full
   stop and the longer half sits on the bottom line. Only bites when there is
   not room for the whole eyebrow on one line, so wide screens are unaffected. */
.eyebrow__keep { white-space: nowrap; }

.section-head {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr);
  gap: var(--space-xl) var(--space-2xl);
  align-items: end;
  margin-bottom: var(--block-gap);
}
.section-head h2 { margin-bottom: 0; }
.section-head__note { color: var(--muted); font-size: 1.02rem; margin: 0; max-width: 62ch; }

/* Vertical brand label on a band edge */
.edge-label {
  position: absolute;
  left: 14px;
  top: 50%;
  transform: translateY(-50%) rotate(180deg);
  writing-mode: vertical-rl;
  font-family: var(--font-display);
  font-size: clamp(2rem, 4vw, 3.4rem);
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--heading);
  opacity: .05;
  pointer-events: none;
  user-select: none;
}

/* --------------------------------------------------------------------------
   4. BUTTONS, LINKS, TICKS
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  /* min-height, not height: a label that wraps is still allowed to grow rather
     than spill out of the pill. Vertical padding is zero because the height is
     the token's job now; the lopsided inline padding stays, since the round pip
     sits closer to the edge than the text does. */
  min-height: var(--btn-h);
  padding: 0 18px 0 26px;
  border: 1px solid transparent;
  border-radius: 999px;
  font: 600 .95rem/1 var(--font-body);
  letter-spacing: .01em;
  text-decoration: none;
  cursor: pointer;
  transition: background-color .22s var(--ease), border-color .22s var(--ease),
              transform .22s var(--ease), box-shadow .22s var(--ease), color .22s var(--ease);
}
/* Circular chevron, as in the reference's buttons */
.btn__chev {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .18);
  flex: 0 0 auto;
  transition: transform .22s var(--ease), background-color .22s var(--ease);
}
.btn__chev svg { width: 15px; height: 15px; }
.btn:hover .btn__chev { transform: translateX(3px); }

.btn--primary {
  background: var(--cta-fill);
  border-color: var(--cta-edge);
  color: var(--cta-ink);
  box-shadow: var(--cta-shadow);
}
/* Hover always moves the button further from its band, never towards it: the
   pale variant brightens, the deep variant deepens. On both grounds the label
   gains contrast rather than losing it. */
.btn--primary:hover {
  background: var(--cta-fill-hover);
  border-color: var(--cta-edge-hover);
  color: var(--cta-ink);
  transform: translateY(-2px);
}
/* The base pip is a translucent white disc built for a mid blue fill. On the
   pale variant it is a 1.18:1 smudge, and the arrow inside it would be dark on
   pale on pale. Solid disc on both grounds instead. */
.btn--primary .btn__chev {
  background: var(--cta-pip);
  color: var(--cta-pip-ink);
}
/* Sits after .band-light :focus-visible, which also weighs 0,2,0, so source
   order is what decides the ring colour. */
.btn--primary:focus-visible { outline-color: var(--cta-ring); }

.btn--glass {
  background: rgba(255, 255, 255, .07);
  border-color: rgba(255, 255, 255, .22);
  color: #fff;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.btn--glass:hover {
  background: rgba(255, 255, 255, .14);
  border-color: rgba(255, 255, 255, .4);
  color: #fff;
  transform: translateY(-2px);
}
.btn--glass .btn__chev { background: rgba(255, 255, 255, .16); }

/* Light-band secondary. .btn--glass is a translucent white built for dark
   grounds and would all but disappear on a light band, so this is its
   counterpart: a solid surface with a token border. */
.btn--outline {
  background: var(--surface);
  border-color: var(--line-strong);
  color: var(--heading);
}
.btn--outline:hover {
  border-color: var(--accent);
  color: var(--accent);
  transform: translateY(-2px);
  box-shadow: 0 14px 30px -20px rgba(12, 32, 51, .6);
}
/* The base chev is a translucent white pip, invisible on a white button */
.btn--outline .btn__chev { background: var(--accent-chip); color: var(--accent-ink); }

.btn--lg { padding: 0 20px 0 30px; font-size: 1rem; }
.btn--sm { padding: 0 13px 0 20px; font-size: .88rem; }
.btn--sm .btn__chev { width: 22px; height: 22px; }
.btn--sm .btn__chev svg { width: 13px; height: 13px; }
/* Leading icon instead of a trailing chevron. The base .btn padding is
   deliberately lopsided (26px text side / 18px pip side) so the round pip sits
   closer to the edge than the text does — with the pip on the left that has to
   mirror, or the button looks off-centre. .btn__chev's hover nudge would also
   push a leading icon INTO the text, so this is its own element. */
.btn--lead { padding: 0 26px 0 18px; }
.btn--lead.btn--lg { padding: 0 30px 0 20px; }
.btn--lead.btn--sm { padding: 0 20px 0 13px; }
/* Solid fill, not the 10% chip tint. The sprite glyphs are 1.6-stroke
   outlines, and at 15px in #17669a on a near-white pip the handset all but
   vanished beside the primary button solid white arrow. White on brand-600
   is 5.23:1 and reads immediately. */
.btn__lead {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--brand-600);
  color: #fff;
  transition: background-color .22s var(--ease), color .22s var(--ease);
}
.btn__lead svg { width: 16px; height: 16px; }
.btn--lead.btn--sm .btn__lead { width: 22px; height: 22px; }
.btn--lead.btn--sm .btn__lead svg { width: 13px; height: 13px; }
.btn--outline:hover .btn__lead { background: var(--brand-700); color: #fff; }

/* The call buttons only: no disc, and the handset grows into the space it used
   to occupy. Scoped by href rather than by class so the WhatsApp buttons, which
   share .btn--lead, keep their own filled mark.
   The glyph takes --accent rather than a literal, because these buttons appear
   on both grounds: --brand-300 on the dark hero and roadmap, #17669a on the
   light results and coach bands. */
.btn--lead[href^="tel:"] .btn__lead {
  width: 24px;
  height: 24px;
  border-radius: 0;
  background: none;
  color: var(--accent);
}
.btn--lead[href^="tel:"] .btn__lead svg { width: 24px; height: 24px; }
/* Without this the outline hover rule above puts the disc straight back */
.btn--outline[href^="tel:"]:hover .btn__lead { background: none; color: var(--accent); }
/* .btn__lead fills with --brand-600, which is exactly what a primary button is
   filled with, so on that button the pip would be invisible. Give it the same
   pair the chevron pip uses instead. */
.btn--primary .btn__lead { background: var(--cta-pip); color: var(--cta-pip-ink); }

/* WhatsApp, in WhatsApp's own green. Must sit after --primary and --glass:
   all three weigh 0,1,0, so source order is what decides.
   The label is near black rather than white because white on #25D366 is
   1.98:1. Near black is 9.39:1, and the pip inverts to carry the glyph in the
   green so the mark still reads as WhatsApp's. */
.btn--wa {
  background: #25d366;
  border-color: transparent;
  color: #06141f;
  box-shadow: 0 14px 30px -16px rgba(37, 211, 102, .55);
}
.btn--wa:hover {
  background: #1fbd5c;                    /* label still 7.53:1 */
  border-color: transparent;
  color: #06141f;
  transform: translateY(-2px);
}
.btn--wa .btn__lead { background: #06141f; color: #25d366; }
/* The default ring is --brand-300, a pale blue, which sits oddly 3px off a
   green button. White reads on both grounds. */
.btn--wa:focus-visible { outline-color: #ffffff; }

/* Shared closing row for a section CTA. --space-xl, not --block-gap: measured
   across the page, every other CTA sits 32px under whatever precedes it (hero,
   approach, technique, FAQ), and --block-gap put these at 64px on desktop —
   far enough adrift that the button stopped reading as part of its section. */
.section-cta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  margin-top: var(--space-xl);
}
/* Only this section's closing row. The same component closes the approach,
   technique and FAQ sections, where the copy above it is left aligned and a
   centred button would sit out of step with it. */
.results .section-cta { justify-content: center; }

.btn--block { width: 100%; }
.btn[disabled] { opacity: .65; cursor: progress; transform: none; }

.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-weight: 600;
  font-size: .9rem;
  text-decoration: none;
  color: var(--accent);
}
.link-arrow svg { width: 17px; height: 17px; flex: 0 0 auto; transition: transform .22s var(--ease); }
.link-arrow:hover { text-decoration: underline; }
.link-arrow:hover svg { transform: translateX(4px); }

/* Round check bullets, matching the reference's list style */
.ticks { display: grid; gap: 14px; margin: 0 0 var(--space-xl); }
.ticks li {
  display: grid;
  grid-template-columns: 24px 1fr;
  gap: 12px;
  align-items: start;
  font-size: .97rem;
  line-height: 1.55;
  color: var(--text);
}
.ticks li > span:first-child {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  margin-top: 2px;
  border-radius: 50%;
  background: var(--brand-600);
  color: #fff;
}
.ticks svg { width: 14px; height: 14px; }

/* --------------------------------------------------------------------------
   5. HEADER
   -------------------------------------------------------------------------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: border-color .3s var(--ease);
}
/* The stuck backing lives on a pseudo-element, NOT on .site-header itself.
   A non-none `backdrop-filter` makes an element a containing block for its
   fixed-position descendants, and the mobile nav panel is exactly that — put
   the blur on the header and, the moment it stuck, the panel resolved `top: 0`
   against the header box instead of the viewport and rendered off-screen at
   minus-the-scroll-distance. Unclickable, while the scrim (a sibling of the
   header) still dimmed the page. The pseudo carries the same blur without
   changing what the header is a containing block for.
   `inset: 0` covers the padding box, so the header keeps its own 1px border
   and its height is unchanged. */
.site-header::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: transparent;
  transition: background-color .3s var(--ease), backdrop-filter .3s var(--ease);
}
.site-header.is-stuck { border-bottom-color: var(--line); }
.site-header.is-stuck::before {
  background: rgba(7, 11, 16, .88);
  backdrop-filter: saturate(150%) blur(14px);
  -webkit-backdrop-filter: saturate(150%) blur(14px);
}

.site-header__inner {
  height: var(--header-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-lg);
  transition: height .3s var(--ease);
}
.site-header.is-stuck .site-header__inner { height: 74px; }

/* The logo is white + chrome, so on these dark grounds it needs no plate */
.brand { display: inline-flex; flex: 0 0 auto; }
.brand img { width: 152px; height: auto; transition: width .3s var(--ease); }
.site-header.is-stuck .brand img { width: 126px; }

.nav { display: flex; align-items: center; gap: clamp(18px, 2.4vw, 34px); }
/* Panel-only: above 1024px .nav is the horizontal header bar and has no room
   for it. Turned back on inside the mobile block. */
.nav__contact { display: none; }
.nav__list { display: flex; align-items: center; gap: clamp(14px, 1.9vw, 28px); }

.nav__link {
  position: relative;
  display: inline-block;
  padding: 6px 0;
  font-size: .92rem;
  font-weight: 500;
  color: rgba(255, 255, 255, .8);
  text-decoration: none;
  transition: color .2s var(--ease);
}
.nav__link::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -1px;
  width: 100%;
  height: 2px;
  background: var(--brand-300);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .28s var(--ease);
}
.nav__link:hover { color: #fff; }
.nav__link:hover::after,
.nav__link.is-active::after { transform: scaleX(1); }
.nav__link.is-active { color: #fff; }

.burger {
  display: none;
  width: 46px;
  height: 46px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, .22);
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, .06);
  cursor: pointer;
  place-items: center;
  align-items: center;
  gap: 6px;
  align-content: center;
}
.burger__bar {
  display: block;
  width: 20px;
  height: 2px;
  background: #fff;
  transition: transform .28s var(--ease), opacity .2s var(--ease);
}
.burger[aria-expanded="true"] .burger__bar:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.burger[aria-expanded="true"] .burger__bar:nth-child(2) { opacity: 0; }
.burger[aria-expanded="true"] .burger__bar:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

.nav-scrim {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: rgba(3, 6, 10, .6);
  backdrop-filter: blur(3px);
  animation: fade .25s var(--ease);
}
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }

/* --------------------------------------------------------------------------
   6. HERO
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  isolation: isolate;
  min-height: min(96vh, 940px);
  display: flex;
  align-items: flex-end;
  padding-top: calc(var(--header-h) + 40px);
  padding-bottom: var(--section-y);
  margin-top: calc(var(--header-h) * -1);   /* header sits over the photograph */
  overflow: hidden;
}
.hero__bg { position: absolute; inset: 0; z-index: -2; }
/* Stacked in the same box so they can cross-fade. The first is opaque from the
   start, so the hero is never blank and looks identical without JS. */
.hero__slide {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 62% 28%;
  opacity: 0;
  transition: opacity 1.2s var(--ease);
}
.hero__slide.is-active { opacity: 1; }
/* Was an inline style on the element, where a media query could never reach it */
.hero__slide--3 { object-position: 58% 40%; }
/* This one is cropped wider than the hero box, so unlike its siblings it is the
   sides that get trimmed rather than the top and bottom. Centred: the punch
   lands mid frame and both boxers should stay in it. */
.hero__slide--2 { object-position: 50% 50%; }
.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    linear-gradient(100deg, var(--page-bg) 8%, rgba(7, 11, 16, .82) 42%, rgba(7, 11, 16, .35) 70%),
    linear-gradient(0deg, var(--page-bg) 2%, rgba(7, 11, 16, 0) 46%);
}

.hero__inner { position: relative; width: 100%; }
.hero__copy { max-width: 40rem; }
.hero h1 { margin-bottom: var(--space-lg); }
.hero__lede {
  font-size: clamp(1rem, 1.25vw, 1.12rem);
  color: rgba(255, 255, 255, .78);
  max-width: 34rem;
  margin-bottom: var(--space-xl);
}
.hero__actions { display: flex; flex-wrap: wrap; gap: var(--space-sm); }

.hero__strip {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-lg);
  margin-top: var(--space-2xl);
  padding-top: var(--space-xl);
  border-top: 1px solid rgba(255, 255, 255, .14);
}
.hero__strip li { display: grid; grid-template-columns: 44px 1fr; gap: var(--space-sm); align-items: center; }
.hero__strip-icon {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--accent-chip);
  border: 1px solid var(--line-strong);
  color: var(--brand-300);
}
.hero__strip-icon svg { width: 21px; height: 21px; }
.hero__strip strong { display: block; font-size: .95rem; color: #fff; line-height: 1.3; }
.hero__strip span span,
.hero__strip li > span:last-child { font-size: .82rem; color: rgba(255, 255, 255, .55); line-height: 1.4; }

/* --------------------------------------------------------------------------
   7. ABOUT — tilted image pair
   -------------------------------------------------------------------------- */
/* `clip`, not `hidden`, for the same reason <body> uses it above: `overflow:
   hidden` turns this section into a scroll container, and the sticky photo
   column below would then stick to a box that never scrolls, which looks like
   sticky silently doing nothing. `clip` still crops the Bulletproof edge label
   the same way. */
.about { overflow: clip; }
.about__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-2xl) clamp(40px, 5vw, 76px);
  align-items: center;
}

.about__media { position: relative; padding: 10px 0 56px; }
/* The copy column runs several hundred pixels longer than the photographs now
   that the before and after clip sits in it, so the photographs hold their
   place while the text scrolls past rather than sliding away near the top of
   the section.
   align-self: start is not optional: the grid is align-items: center, which
   sizes this column to its content and centres it in the row, leaving sticky
   nothing to travel within. top is measured from the stuck header rather than
   from 0, or the photographs would slide underneath it.
   Only while the grid is actually two columns: below 1025px it collapses to
   one, and a sticky element in a single column stack would sit over the text
   that follows it. */
@media (min-width: 1025px) {
  .about__media {
    position: sticky;
    top: calc(var(--header-stuck) + var(--space-lg));
    align-self: start;
  }
}
.about__card { margin: 0; border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow-md); }
.about__card img { width: 100%; }
.about__card--main {
  width: 84%;
  transform: rotate(-2.5deg);
}
.about__card--main img { aspect-ratio: 1 / 1; object-fit: cover; }
.about__card--sub {
  position: absolute;
  right: 0;
  bottom: 0;
  width: 46%;
  transform: rotate(3deg);
  border: 5px solid var(--surface);
  z-index: 2;
}
.about__card--sub img { aspect-ratio: 1 / 1; object-fit: cover; }

.about__copy h2 { margin-bottom: var(--space-md); }
/* Capped: at the 1350px shell this column runs ~99 characters a line without it */
/* :not(.eyebrow) matters: the label is a <p> too, and this rule was giving it
   a 32px body paragraph margin, so About sat at double the gap under its
   eyebrow that every other section uses. */
.about__copy > p:not(.eyebrow) { margin-bottom: var(--space-xl); max-width: 62ch; }
.about__copy .ticks { max-width: 62ch; }
/* The before and after clip, under the Ways to train button. It keeps the
   facade's 880px cap but not its auto side margins: measured at 1024, where
   this column is 960 wide, the cap left the clip centred and so sitting 40px
   in from the left edge while the heading, the text, the ticks and the button
   above it all start at the column edge. Left aligned it lines up with them,
   and the column simply runs wider than the clip the same way it already runs
   wider than the 62ch paragraphs. --space-xl above so it clears the button
   rather than crowding the call to action. */
.about__copy .video { margin: var(--space-xl) 0 0; }

/* --------------------------------------------------------------------------
   8. TRAINING CARDS
   -------------------------------------------------------------------------- */
.train-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--grid-gap);
}
.tcard {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  overflow: hidden;
  /* Two-layer shadow: a tight contact edge plus a wide soft cast, which is
     what stops a light card reading as a flat rectangle */
  box-shadow: 0 1px 2px rgba(12, 32, 51, .05), 0 14px 30px -22px rgba(12, 32, 51, .35);
  transition: transform .35s var(--ease), border-color .35s var(--ease), box-shadow .35s var(--ease);
}
.tcard:hover {
  transform: translateY(-6px);
  border-color: var(--line-strong);
  box-shadow: 0 2px 4px rgba(12, 32, 51, .06), 0 28px 50px -26px rgba(12, 32, 51, .45);
}
.tcard__media { position: relative; margin: 0; overflow: hidden; }
/* Grades the foot of the photo so the badge has something to sit against
   whatever the image happens to be doing there */
.tcard__media::after {
  content: "";
  position: absolute;
  inset: auto 0 0;
  height: 42%;
  background: linear-gradient(transparent, rgba(8, 22, 36, .45));
  pointer-events: none;
}
.tcard__media img {
  width: 100%;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  transition: transform .7s var(--ease);
}
.tcard:hover .tcard__media img { transform: scale(1.05); }

.tcard__body {
  position: relative;
  padding: var(--card-pad);
  display: flex;
  flex-direction: column;
  flex: 1;
}
/* Circular blue badge straddling the image edge, as in the reference */
.tcard__icon {
  position: absolute;
  top: calc(var(--card-pad) * -1);
  left: var(--card-pad);
  display: grid;
  place-items: center;
  width: 54px;
  height: 54px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--brand-500), var(--brand-700));
  color: #fff;
  /* Ring in the card colour so the badge reads as lifted off the photo */
  box-shadow: 0 0 0 5px var(--surface), 0 12px 24px -12px rgba(12, 32, 51, .6);
  transition: transform .35s var(--ease);
}
.tcard:hover .tcard__icon { transform: scale(1.06); }
.tcard__icon svg { width: 24px; height: 24px; }
.tcard__body h3 { margin: var(--space-lg) 0 var(--space-xs); }
.tcard__body p { color: var(--muted); font-size: .95rem; margin-bottom: var(--space-lg); }

/* Footer row: label left, circular arrow right, divided from the copy by a
   hairline. Reads as a deliberate action rather than a stray text link. */
.tcard__link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  margin-top: auto;
  padding-top: var(--space-lg);
  border-top: 1px solid var(--line);
  font: 600 .93rem/1.3 var(--font-body);
  color: var(--accent);
  text-decoration: none;
}
.tcard__go {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 1px solid var(--line-strong);
  color: var(--accent);
  transition: background-color .28s var(--ease), border-color .28s var(--ease),
              color .28s var(--ease), transform .28s var(--ease);
}
.tcard__go svg { width: 16px; height: 16px; }
.tcard:hover .tcard__go,
.tcard__link:focus-visible .tcard__go {
  background: var(--brand-600);
  border-color: var(--brand-600);
  color: #fff;
  transform: translateX(3px);
}


/* --------------------------------------------------------------------------
   9. TECHNIQUE ROADMAP
   -------------------------------------------------------------------------- */
.roadmap {
  position: relative;
  background: var(--page-bg-2);
  border-block: 1px solid var(--line);
}
.roadmap::before {
  content: "";
  position: absolute;
  inset: -40px;
  background: url("images/IMG_3191.jpeg") center / cover no-repeat;
  opacity: .09;
  /* Heavily blurred: this is a tonal texture, not a photograph to read */
  filter: grayscale(1) contrast(1.1) blur(14px);
}
.roadmap__sticky { position: relative; z-index: 1; padding-block: var(--section-y); }
.roadmap__head h2 { max-width: 18ch; }
.roadmap__intro { color: var(--muted); margin-bottom: 0; max-width: 46rem; }
/* A single word stranded on the last line reads as a mistake, and it lands on
   a different paragraph at every width: the lede and stops 1 and 2 at 430, the
   stop descriptions at 768 and 820, stop 6 at 1024 and again at 320. Rewriting
   copy to chase it only moves it to the next breakpoint, so the browser is
   asked to avoid the short last line instead. The headings already do the same
   thing one rule up with text-wrap: balance, and a browser without it simply
   wraps the way it did before. */
.roadmap__intro,
.stop p,
.roadmap__foot p,
.results__note {
  /* Two declarations on purpose, not a mistake. Firefox does not support
     text-wrap: pretty, measured: CSS.supports reports false and the computed
     value falls back to "wrap", which put 11 single word last lines back across
     the breakpoints. It does support balance, so it keeps that one and drops
     the next as unparseable. Chrome understands both and the later wins, so it
     still gets pretty, which is the better algorithm for a paragraph. */
  text-wrap: balance;
  text-wrap: pretty;
}

.roadmap__viewport {
  overflow-x: auto;
  overflow-y: hidden;
  margin-block: var(--block-gap);
  scroll-snap-type: x proximity;
  scroll-padding-inline-start: max(var(--gutter), calc((100% - var(--shell)) / 2 + var(--gutter)));
  scrollbar-width: thin;
  scrollbar-color: rgba(128, 192, 224, .5) transparent;
  -webkit-overflow-scrolling: touch;
  /* Soft right edge, so a partly visible stop reads as "more along here".
     A fixed width, not 7%: as a percentage the fade grew with the window, to
     241px on a 3440 monitor, which is wider than a whole stop. 64px also sits
     inside the track's 72px minimum trailing pad, so the last stop always
     comes to rest clear of the fade rather than washed out by it. */
  -webkit-mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 64px), transparent 100%);
  mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 64px), transparent 100%);
}
.roadmap__viewport::-webkit-scrollbar { height: 6px; }
.roadmap__viewport::-webkit-scrollbar-track { background: rgba(255, 255, 255, .07); }
.roadmap__viewport::-webkit-scrollbar-thumb { background: rgba(128, 192, 224, .5); border-radius: 999px; }

.roadmap__track {
  position: relative;
  display: flex;
  gap: var(--grid-gap);
  /* Leading pad starts the first stop on the shell's left edge. The trailing
     pad had the same value, and that was the bug: the track is shifted by
     scrollWidth minus clientWidth, so every pixel of trailing padding becomes
     empty space on screen once the run finishes, and that value grows with the
     window. Measured at the end of the travel it left 244px of nothing at
     1440, 484px at 1920 and 804px at 2560, which is a quarter of the rail.
     A fixed trailing pad keeps the last stop off the edge without the void. */
  padding-inline: max(var(--gutter), calc((100% - var(--shell)) / 2 + var(--gutter)))
                  clamp(72px, 8vw, 120px);
  width: max-content;
  /* Clearance above the scrollbar. Without it the rail track is drawn hard
     against the pills, because the content box ends exactly where the
     scrollbar starts. */
  padding-bottom: var(--space-lg);
  will-change: transform;
}
/* Pinned mode has overflow: hidden and therefore no scrollbar, so the full
   scrollbar clearance would be dead height. Zero is wrong too. Above 1024 the
   pill is pushed to the very bottom of the stop by margin-top: auto, which left
   its rounded bottom border sitting on the clip line with 0.22px to spare at
   1440 and 0.01px at 1280. A 1px border cannot be painted inside a fifth of a
   pixel, so the bottom of every pill was sliced flat. This is clip tolerance,
   not decoration. */
.roadmap.is-pinned .roadmap__track { padding-bottom: var(--space-sm); }

.stop {
  position: relative;
  /* Column layout so the pill can be pushed to a shared baseline. The stops
     are already equal height, but the pills followed their paragraph, so a
     2-line stop sat 25px above a 3-line one. */
  display: flex;
  flex-direction: column;
  flex: 0 0 clamp(240px, 25vw, 300px);
  padding-top: 68px;
  scroll-snap-align: start;
}
/* align-self keeps the pill hugging its text rather than stretching the full
   column width; stretch stays on the headings and paragraphs so they wrap
   against the column, not their own content. */
.stop .pill { margin-top: auto; align-self: flex-start; }
.stop::before {                    /* node */
  content: "";
  position: absolute;
  z-index: 1;
  top: 28px;
  left: 0;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: var(--brand-300);
  box-shadow: 0 0 0 5px rgba(128, 192, 224, .18);
}
/* Each stop draws the segment to the next node, so the rail begins at the
   first node and ends at the last — no line floating in the leading gutter */
.stop::after {
  content: "";
  position: absolute;
  top: 34px;
  left: 6px;
  width: calc(100% + var(--grid-gap));
  height: 1px;
  background: rgba(128, 192, 224, .38);
}
.stop:last-child::after { display: none; }
.stop__num {
  position: absolute;
  top: 0;
  left: 0;
  font-family: var(--font-display);
  font-size: .8rem;
  font-weight: 700;
  letter-spacing: .16em;
  color: var(--brand-300);
}
.stop h3 { margin-bottom: var(--space-xs); }
.stop p { color: var(--muted); font-size: .93rem; margin-bottom: var(--space-md); }

/* The shape has to be visible, not just the word inside it. The label was
   already strong at 7.82:1, but the fill sat at 1.22:1 against the band and the
   edge at 1.54:1, so the pill read as floating text rather than a tag. WCAG
   puts the threshold for a component boundary at 3:1, which is what the edge
   now clears. */
.pill {
  display: inline-block;
  padding: 7px 16px;
  border: 1px solid rgba(128, 192, 224, .75);
  border-radius: 999px;
  background: rgba(128, 192, 224, .22);
  color: #eaf4fb;
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
}

.roadmap__hint {
  display: none;
  align-items: center;
  gap: 8px;
  margin-block: 0 var(--block-gap);
  color: var(--brand-300);
  font-size: .78rem;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
}
.roadmap__hint svg { width: 16px; height: 16px; }

/* .shell owns the position; the measure is capped on the inner paragraph so
   the block's own `margin-inline: auto` cannot recentre it */
.roadmap__foot { margin-block: 0; }
.roadmap__foot p { color: var(--muted); font-size: 1.02rem; max-width: 46rem; margin: 0; }
.roadmap__foot p::before {
  content: "";
  display: block;
  width: 44px;
  height: 3px;
  margin-bottom: var(--space-md);
  background: var(--brand-300);
}

/* Pinned mode: switched on by JS only where there is room for it */
.roadmap.is-pinned { height: var(--pin-height, 300vh); }
.roadmap.is-pinned .roadmap__sticky {
  position: sticky;
  /* Below the header, not behind it. The site header is sticky at the top and
     never hides, so a panel stuck to 0 centres its content inside a box whose
     top 92px is covered: at 1280x720 that hid the eyebrow completely and at
     1366x768 it clipped it. Sticking below the header and taking the height
     that is left keeps the whole panel in the clear, and because the pin gate
     measures this same box rather than guessing from innerHeight, the shorter
     box also makes the section fall back to a scroller wherever the panel no
     longer fits. */
  top: var(--header-h);
  /* svh, not vh: on a phone vh includes the retracted URL bar, so the panel
     would resize mid scroll as the bar hides and the pin would visibly jump.
     svh is the stable small viewport, and the vh line is the fallback for
     browsers without it. */
  height: calc(100vh - var(--header-h));
  height: calc(100svh - var(--header-h));
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding-block: var(--space-xl);
}
.roadmap.is-pinned .roadmap__viewport {
  overflow: hidden;
  scroll-snap-type: none;
  /* The rail must never be the thing that gives way. As a flex item in a fixed
     height column it defaults to flex-shrink: 1, and because it is also
     overflow: hidden while pinned, shrinking silently cut the bottom off every
     stop: the description and the pill. */
  flex: 0 0 auto;
}

/* On a short window the chrome gives way instead of the rail. This buys back
   112px, which is the difference between the panel fitting and not. */
@media (max-height: 860px) {
  .roadmap.is-pinned .roadmap__sticky { padding-block: var(--space-md); }
  .roadmap.is-pinned .roadmap__viewport { margin-block: var(--space-lg); }
}
/* Pinned, the scroll drives the track, so there is nothing to swipe */
.roadmap.is-pinned .roadmap__hint { display: none; }

/* --------------------------------------------------------------------------
   10. STATS BAND
   -------------------------------------------------------------------------- */
/* The one dark band among the light ones, at the client's request: the figures
   stand out more against it. It carries no band class at all, so it inherits
   the :root dark palette, and because every colour here reads from tokens
   rather than literal whites the section flipped without any rule changing.
   It sits directly under the roadmap, which is the lifted --page-bg-2 dark
   with hairline edges, so the two still read as separate bands. */
.stats-band { padding-block: var(--section-y); }
.stats-band__copy { max-width: 44rem; margin-bottom: var(--block-gap); }
/* :not(.eyebrow) for the same reason as .about__copy: the label is a <p>, and
   zeroing every paragraph margin here collapsed the gap under it to nothing. */
.stats-band__copy p:not(.eyebrow) { color: var(--text); margin-bottom: 0; }
/* Credentials are evidence, not an offer, so this band gets a quiet way onward
   rather than a pill asking for commitment on the strength of a CV. */
.stats-band__more { margin: var(--space-lg) 0 0; }

.stats-row {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: var(--space-lg);
  margin: 0;
  padding-top: var(--space-xl);
  border-top: 1px solid var(--line);
}
.stat {
  display: grid;
  grid-template-columns: 52px minmax(0, 1fr);
  grid-template-rows: auto auto;
  gap: 0 var(--space-md);
  align-content: start;
}
.stat__icon {
  grid-row: 1 / 3;
  align-self: start;
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: var(--accent-chip);
  border: 1px solid var(--line-strong);
  color: var(--accent-ink);
}
.stat__icon svg { width: 24px; height: 24px; }
.stat dd {
  margin: 0;
  font-family: var(--font-display);
  /* Counting up changes the digits every frame; proportional figures would
     make the number jitter sideways as 1s and 7s swap in and out. */
  font-variant-numeric: tabular-nums;
  font-size: clamp(1.6rem, 2.6vw, 2.2rem);
  font-weight: 800;
  letter-spacing: -.03em;
  line-height: 1.1;
  color: var(--heading);
}
.stat dt { font-size: .82rem; color: var(--muted); line-height: 1.35; }

/* --------------------------------------------------------------------------
   11. RESULTS
   -------------------------------------------------------------------------- */
/* The case card is a heavy visual block, so it sits closer to its heading and
   closing line than the page default. --block-gap drives both the section-head
   margin and the closing note, so one declaration moves both. */
.results { --block-gap: 40px; }

/* A dark island inside the light band. Re-declaring the tokens flips every
   child to the dark treatment, so no component needs a special case. */
.case {
  --heading: #ffffff;
  --text: rgba(255, 255, 255, .74);
  --muted: rgba(255, 255, 255, .56);
  --line: rgba(255, 255, 255, .1);
  --line-strong: rgba(128, 192, 224, .24);
  --accent: var(--brand-300);
  --surface: #131c27;

  position: relative;
  overflow: hidden;
  padding: var(--card-pad);
  border-radius: var(--radius-lg);
  color: var(--text);
  background:
    radial-gradient(72% 62% at 88% 0%, rgba(26, 114, 168, .3), transparent 62%),
    #0c131b;
}
.case__label {
  font-size: .74rem;
  font-weight: 600;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: var(--space-lg);
}

.case__top {
  display: grid;
  grid-template-columns: minmax(0, .325fr) minmax(0, .675fr);
  gap: clamp(24px, 2.8vw, 44px);
  /* Centred, not start-aligned. The collage is a fixed 4:5 ratio so its height
     tracks its column width, while the prose re-wraps taller as the column
     narrows — below 1280 they diverge by up to 95px. Centring splits that
     evenly instead of dumping it all under the image. */
  align-items: center;
}
.case__shot {
  margin: 0;
  border: 1px solid rgba(255, 255, 255, .14);
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow-md);
}
.case__shot img { width: 100%; }

.case__name { font-size: clamp(1.6rem, 2.6vw, 2.1rem); margin-bottom: var(--space-md); }
.case__story blockquote { margin: 0; }
.case__by { margin-top: var(--space-md); font-size: .85rem; color: var(--muted); }

/* Progression, built from the four dated figures in the photographs. A line
   of nodes rather than a bar chart: it says the same thing in a third of the
   height, and echoes the technique roadmap's node motif. */
.case__data {
  margin-top: var(--space-lg);
  padding-top: var(--space-lg);
  border-top: 1px solid var(--line);
}
.case__data-label {
  margin: 0 0 var(--space-md);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--muted);
}

.prog {
  /* Node row centre = time row (20) + gap (6) + half the node (6) */
  --node-y: 32px;
  --prog-gap: var(--space-xs);
  position: relative;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--prog-gap);
}
.prog::before {                    /* connector, first node centre to last */
  content: "";
  position: absolute;
  /* Half a column in from each end — a flat 12.5% ignores the three gaps
     and leaves the line overshooting the outer nodes */
  left: calc((100% - 3 * var(--prog-gap)) / 8);
  right: calc((100% - 3 * var(--prog-gap)) / 8);
  top: var(--node-y);
  height: 1px;
  background: linear-gradient(90deg, rgba(128, 192, 224, .22), var(--brand-300));
}
.prog__step {
  display: grid;
  grid-template-rows: 20px 12px auto;
  gap: 6px;
  justify-items: center;
  text-align: center;
}
.prog__time {
  font-family: var(--font-display);
  font-size: 1.15rem;
  font-weight: 800;
  letter-spacing: -.02em;
  line-height: 20px;
  color: var(--heading);
}
.prog__node {
  position: relative;
  z-index: 1;
  align-self: center;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: #0c131b;
  box-shadow: inset 0 0 0 2px rgba(128, 192, 224, .55);
}
.prog__step.is-final .prog__time { color: var(--accent); }
.prog__step.is-final .prog__node {
  background: var(--brand-300);
  box-shadow: 0 0 0 4px rgba(128, 192, 224, .18);
}
/* The radar blip the client asked for. Rings on pseudo elements rather than
   more box-shadow layers, because two of them can then run the same keyframes
   half a cycle apart, and it is one ring leaving as the next arrives that
   reads as a radar sweep rather than a single slow throb. Measured, the
   shadow ring it replaces was visible for only 800ms of every 2400 and peaked
   at 44% opacity, which is why it read as nothing much.

   There is only 12px of clear space above and below the node before the time
   and the date, so the ring is brightest while it is still small and has
   faded to nothing by the time it reaches them. That is the same shape a real
   blip has, so the constraint costs nothing. */
.prog__step.is-final .prog__node::before,
.prog__step.is-final .prog__node::after {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: 50%;
  border: 1.5px solid var(--brand-300);
  opacity: 0;
  pointer-events: none;
  animation: prog-blip 2.6s cubic-bezier(.16, .62, .3, 1) infinite;
}
/* half a cycle behind, so the beat is even */
.prog__step.is-final .prog__node::after { animation-delay: 1.3s; }

@keyframes prog-blip {
  0%   { transform: scale(.9); opacity: 0; }
  12%  { transform: scale(1);  opacity: .85; }
  70%  { transform: scale(3.1); opacity: 0; }
  100% { transform: scale(3.1); opacity: 0; }
}
.prog__date { font-size: .74rem; color: var(--muted); line-height: 1.3; }

/* The two figures under the progression, each one inside a pill and the pair
   centred. The whole figure goes in the pill rather than just the number: it is
   the more literal reading of the request, and it gives the pair an outline of
   their own between the timeline above and the video bar below.
   The old rule centred dd but not dt, so the number sat centred over a left
   aligned label and the two never lined up. */
.case__figures {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: var(--space-sm);
  margin: var(--space-lg) 0 0;
  padding: 0;
}
.case__figures > div {
  display: flex;
  align-items: center;
  /* flex items default to min-width auto, which is min-content; without this a
     long label would push the pill wider than the card instead of wrapping */
  min-width: 0;
  max-width: 100%;
  padding: 9px 18px;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  /* A literal tint rather than var(--accent-chip). .case is a dark island
     inside .band-light, which redeclares that token to rgba(23,102,154,.09)
     for white grounds, and .case never overrides it, so through the token the
     fill is invisible here and only the border would show. */
  background: rgba(128, 192, 224, .1);
}
.case__figures dd {
  order: 1;
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.15rem, 1rem + .8vw, 1.35rem);
  font-weight: 800;
  letter-spacing: -.02em;
  line-height: 1.15;
  color: var(--accent);
  white-space: nowrap;
}
.case__figures dt {
  order: 2;
  min-width: 0;
  margin: 0 0 0 10px;
  font-family: var(--font-body);
  font-size: .72rem;
  line-height: 1.35;
  color: var(--muted);
  /* Deliberately left to wrap. .case is overflow: hidden, so a label pinned to
     one line would be silently cut off rather than pushed out if the body face
     fell back wider or the visitor raised their default text size. */
}
/* Room to breathe, and a hairline between the figure and what it measures */
@media (min-width: 480px) {
  .case__figures { gap: var(--space-md); }
  .case__figures > div { padding: 10px 20px; }
  .case__figures dt {
    display: flex;
    align-items: center;
    align-self: stretch;
    margin-left: 12px;
    padding-left: 12px;
    border-left: 1px solid var(--line);
  }
}

/* The pair of session clips. Two up while there is room for both to stay
   watchable, one column below that rather than two postage stamps. */
.sessions { margin: var(--block-gap) 0 0; }
.sessions__title {
  margin: 0 0 var(--space-md);
  font-size: clamp(1.15rem, 1.8vw, 1.4rem);
  text-align: center;
}
.sessions__grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--grid-gap);
}
/* The facade caps itself at 880px for a single player in a full width column.
   Here two sit side by side, so each one takes the width its own cell gives it. */
.session .video { max-width: 100%; }

/* General closing line — sits below the case study so it is clearly about
   other clients rather than reading as part of Rachel's story */
/* auto, not 0, on the horizontal margin: the measure is capped at 70ch, so
   centring the text inside a block that is still pinned to the left edge would
   leave it visibly off centre on a wide screen. The block has to be centred too. */
.results__note {
  /* --space-lg, not --block-gap. Measured, the section gap put 40px above this
     line on desktop against 32px below it to the buttons, so the closing remark
     sat nearer the buttons it introduces than the clips it comments on. 24px
     the other way round reads correctly: tight to the content above, a clear
     step before the call to action. */
  margin: var(--space-lg) auto 0;
  color: var(--muted);
  max-width: 70ch;
  text-align: center;
}

/* --------------------------------------------------------------------------
   12. ACADEMY
   -------------------------------------------------------------------------- */
.academy { background: var(--page-bg-2); overflow: hidden; }
.academy::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(64% 42% at 76% 10%, rgba(25, 111, 163, .32) 0%, rgba(25, 111, 163, 0) 72%),
    radial-gradient(72% 34% at 18% 88%, rgba(25, 111, 163, .16) 0%, rgba(25, 111, 163, 0) 74%);
  pointer-events: none;
}
.academy .shell { position: relative; z-index: 1; }

/* Asymmetric head: title block left, the pitch and the offer right */
.academy__top {
  display: grid;
  grid-template-columns: minmax(0, .55fr) minmax(0, .45fr);
  gap: clamp(32px, 4vw, 64px);
  align-items: center;
}

.proof {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  padding: 8px 20px 8px 8px;
  margin-bottom: var(--space-lg);
  /* Component level so one pill works on either ground. The Academy instance
     sits on dark, this one on the light blue training band. */
  background: var(--proof-bg, rgba(255, 255, 255, .05));
  border: 1px solid var(--line);
  border-radius: 999px;
  text-align: left;
  /* inline-flex children default to min-width auto, so without this a long line
     pushes the pill past its column instead of wrapping */
  max-width: 100%;
  min-width: 0;
}
.band-light .proof { --proof-bg: #ffffff; }
.proof > img {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  object-fit: cover;
  object-position: 50% 30%;
  flex: 0 0 auto;
}
.proof__text { display: grid; gap: 1px; font-size: .84rem; color: var(--muted); line-height: 1.4; }

/* Single line count pill, as used under the training heading. The Academy
   instance keeps the two line form above. */
/* Dark on the light band, as in the client's reference. It also lets the ring
   around each face be the capsule's own colour, so the row reads as overlapping
   cut outs rather than as five bordered circles. */
/* Both selectors: the bare class alone is 0,1,0 and would lose to
   .band-light .proof at 0,2,0, so the token would never apply on the light
   band this pill actually sits on. */
.proof--head,
.band-light .proof--head {
  --proof-bg: #0e151e;
  margin: var(--space-lg) 0 0;
  padding: 5px 20px 5px 5px;
  gap: var(--space-sm);
  border-color: rgba(255, 255, 255, .14);
}
.proof__faces { display: flex; flex: 0 0 auto; align-items: center; }
.proof__faces img {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  object-fit: cover;
  /* box-shadow rather than border: the ring then costs no layout, so the 34px
     box stays 34px and the overlap below stays predictable */
  box-shadow: 0 0 0 2px var(--proof-bg);
}
/* Each face tucks under the one before it, which is what makes a row of five
   read as a crowd rather than as a list */
.proof__faces img + img { margin-left: -11px; }
.proof__line {
  min-width: 0;
  font-size: .86rem;
  line-height: 1.45;
  /* literal, not --muted: the capsule is dark while the band around it is light,
     so the band's tokens would put dark grey text on a dark ground */
  color: rgba(255, 255, 255, .78);
}
.proof__line strong { color: #ffffff; font-weight: 700; }
.proof__text strong {
  color: #fff;
  font-size: .7rem;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
}

.academy__title { font-size: clamp(1.9rem, 3.5vw, 3.05rem); margin-bottom: 0; }
.academy__lede { font-size: 1.02rem; margin-bottom: 20px; }

/* Offer block: promise, action and reassurance as one unit */
.academy__offer {
  padding: var(--card-pad);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: linear-gradient(160deg, rgba(128, 192, 224, .09), rgba(128, 192, 224, .02));
}
.academy__sub {
  color: var(--brand-300);
  font-family: var(--font-display);
  font-size: clamp(1.05rem, 1.7vw, 1.28rem);
  font-weight: 700;
  letter-spacing: -.01em;
  line-height: 1.3;
  margin-bottom: var(--space-lg);
}
.academy__offer .btn { width: 100%; }

/* What is inside — a full-width tile grid, so the seven inclusions carry the
   lower half of the section rather than sitting in a narrow list panel */
.academy__inside { margin-top: var(--block-gap); }
.academy__inside h3 {
  font-family: var(--font-body);
  font-size: .74rem;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--brand-300);
  margin-bottom: var(--space-lg);
}
.inside-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--grid-gap);
}
.inside-grid li {
  display: grid;
  /* Rows are auto, so without this the leftover height in a stretched grid row
     gets shared between the rows and opens a gap between the icon and the text
     rather than collecting harmlessly at the bottom of the tile. */
  align-content: start;
  gap: 0;
  padding: var(--card-pad);
  background: rgba(255, 255, 255, .04);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  font-size: .97rem;
  line-height: 1.4;
  color: var(--heading);
  transition: border-color .3s var(--ease), transform .3s var(--ease), background-color .3s var(--ease);
}
.inside-grid li:hover {
  border-color: var(--line-strong);
  background: rgba(255, 255, 255, .06);
  transform: translateY(-3px);
}
.inside__icon {
  display: grid;
  place-items: center;
  margin-bottom: var(--space-md);
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--accent-chip);
  border: 1px solid var(--line-strong);
  color: var(--brand-300);
}
.inside__icon svg { width: 21px; height: 21px; }
.inside__name {
  margin: 0;
  font-family: var(--font-body);
  font-size: .97rem;
  font-weight: 600;
  line-height: 1.35;
  color: var(--heading);
}
.inside__text {
  margin: 8px 0 0;
  font-size: .88rem;
  line-height: 1.55;
  /* --text, not --muted: 9.52:1 on the flattened tile fill against 6.05:1, and
     this is real body copy rather than a caption */
  color: var(--text);
}

/* --------------------------------------------------------------------------
   13. VIDEO FACADE — nothing loads until the visitor clicks play
   -------------------------------------------------------------------------- */
.video {
  position: relative;
  display: block;
  width: 100%;
  max-width: 880px;
  margin-inline: auto;
  padding: 0;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  /* Literal ink, not a themed token: a player should stay dark on the light
     bands too, or the white play label washes out against it */
  background: #0e151e;
  overflow: hidden;
  cursor: pointer;
  aspect-ratio: 16 / 9;
  transition: transform .3s var(--ease), box-shadow .3s var(--ease);
}
.video:hover { transform: translateY(-3px); box-shadow: var(--shadow-lg); }
.video__poster {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: brightness(.6) saturate(.9);
  transition: transform .7s var(--ease);
}
.video:hover .video__poster { transform: scale(1.03); }
.video__plate {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: var(--space-md);
  background: linear-gradient(135deg, rgba(26, 114, 168, .45), transparent 62%), #0e151e;
}
.video__play {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: grid;
  place-items: center;
  width: 76px;
  height: 76px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--brand-500), var(--brand-700));
  color: #fff;
  box-shadow: 0 0 0 10px rgba(128, 192, 224, .16);
  transition: transform .3s var(--ease);
}
.video__plate .video__play { position: static; transform: none; }
.video:hover .video__play { transform: translate(-50%, -50%) scale(1.06); }
.video:hover .video__plate .video__play { transform: scale(1.06); }
.video__play svg { width: 26px; height: 26px; margin-left: 3px; }
.video__label {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: var(--space-xl) var(--space-lg) var(--space-md);
  background: linear-gradient(transparent, rgba(4, 8, 12, .88));
  color: #fff;
  font-size: .82rem;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.video__plate .video__label { position: static; padding: 0; background: none; color: rgba(255, 255, 255, .72); }
/* Playing state: the box takes its shape from the clip itself rather than
   holding the poster's ratio, so a landscape or portrait source fills it edge
   to edge and no letterbox bars are painted. height:auto lets the video's own
   intrinsic ratio drive the height; contain stays as a safety net for the
   instant before metadata arrives, and the ground is the player's own ink so
   any residual sliver blends with the frame instead of reading as a black slab. */
.video video { display: block; width: 100%; height: auto; object-fit: contain; background: transparent; }
/* The frame shrink-wraps the clip: no ratio is computed for it, so there is
   no border-box rounding gap to leave a sliver at the bottom. max-height caps
   a tall portrait source; because the width is auto the browser keeps the
   ratio while shrinking, so the cap costs framing on no axis and adds no bar. */
.video.is-playing {
  aspect-ratio: auto;
  width: fit-content;
}
.video.is-playing video { width: auto; max-width: 100%; max-height: 78vh; }
/* Portrait, not square: a 1:1 frame sat ~70px short of the copy at both top
   and bottom, so the photo looked like it was floating in the column. */
.video--tall { max-width: 100%; aspect-ratio: 4 / 5; }
/* Source not yet uploaded: presented as a placeholder, not a dead button */
.video.is-unavailable { cursor: default; }
.video.is-unavailable:hover { transform: none; box-shadow: none; }
.video.is-unavailable .video__label { color: rgba(255, 255, 255, .5); }
.video.is-playing { cursor: default; }
.video.is-playing:hover { transform: none; box-shadow: none; }

/* Shown beside the facade if the source is missing or cannot be decoded */
.video__error {
  margin: var(--space-sm) 0 0;
  padding: var(--space-sm) var(--space-md);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text);
  font-size: .88rem;
  line-height: 1.5;
}

/* --------------------------------------------------------------------------
   14. PROCESS
   -------------------------------------------------------------------------- */
/* Heading spans the full width with the CTA on the same baseline. */
.process__head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--space-xl) var(--space-2xl);
  align-items: end;
  margin-bottom: var(--block-gap);
}
.process__head h2 { margin-bottom: 0; max-width: 20ch; }

/* No cards. An editorial list — oversized numerals, hairline rules and open
   space — reads as considered where four boxes read as a template. */
.steps {
  display: grid;
  border-top: 1px solid var(--line-strong);
}

.step {
  position: relative;
  display: grid;
  grid-template-columns: clamp(76px, 9vw, 140px) minmax(0, 1fr);
  gap: clamp(16px, 3vw, 48px);
  align-items: start;
  padding: clamp(20px, 1.8vw, 26px) 0;
  border-bottom: 1px solid var(--line-strong);
  transition: background-color .4s var(--ease);
}
/* Accent draws down the left edge, and the row tints, as the pointer moves
   over it — enough life without pretending the row is a link. */
.step::before {
  content: "";
  position: absolute;
  left: 0;
  top: -1px;
  bottom: -1px;
  width: 3px;
  background: linear-gradient(180deg, var(--brand-500), var(--brand-700));
  transform: scaleY(0);
  transform-origin: top;
  transition: transform .45s var(--ease);
}
.step:hover::before { transform: scaleY(1); }
.step:hover { background: rgba(255, 255, 255, .55); }

.step__num {
  font-family: var(--font-display);
  font-size: clamp(1.9rem, 2.8vw, 2.5rem);
  font-weight: 800;
  letter-spacing: -.045em;
  line-height: 1;
  color: var(--accent);
  opacity: .26;
  padding-left: clamp(0px, 1.6vw, 24px);
  transition: opacity .4s var(--ease);
}
.step:hover .step__num { opacity: 1; }

.step__text { display: grid; gap: 6px; }
.step h3 { margin: 0; font-size: clamp(1.15rem, 1.8vw, 1.4rem); }
.step p { margin: 0; color: var(--muted); font-size: .97rem; max-width: 68ch; }

/* --------------------------------------------------------------------------
   15. COACH
   -------------------------------------------------------------------------- */
.coach__grid {
  display: grid;
  grid-template-columns: minmax(0, .95fr) minmax(0, 1.05fr);
  gap: var(--space-2xl) clamp(40px, 5vw, 72px);
  align-items: center;
}
.coach__copy > p { max-width: 62ch; }
.coach__facts { display: grid; gap: var(--space-md); margin-top: var(--space-xl); }
.coach__facts li { display: grid; grid-template-columns: 46px 1fr; gap: var(--space-sm); align-items: center; }
.coach__fact-icon {
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  background: var(--accent-chip);
  color: var(--accent-ink);
}
.coach__fact-icon svg { width: 21px; height: 21px; }
.coach__facts strong { display: block; font-size: .95rem; line-height: 1.3; }
.coach__facts li > span:last-child { font-size: .84rem; color: var(--muted); line-height: 1.4; }

/* --------------------------------------------------------------------------
   16. FAQ
   -------------------------------------------------------------------------- */
/* Two independent column stacks. A 2-column grid shares row heights between
   columns, so opening a tile on the right stretched the row and left dead
   space beside it on the left — the thing that read as "awkward". Stacks
/* Intro column beside the questions. The intro is the shorter column, so it
   sits at the top of its track rather than stretching to the stack's height. */
.faq__grid {
  display: grid;
  grid-template-columns: minmax(0, .82fr) minmax(0, 1.18fr);
  gap: clamp(36px, 4.5vw, 84px);
  align-items: start;
}

.faq__intro h2 { margin-bottom: var(--space-lg); }
.faq__lede {
  max-width: 42ch;
  margin: 0;
  color: var(--muted);
  font-size: 1.02rem;
  line-height: 1.7;
}

/* A short rule leads the eyebrow in, the way the reference sets it */
.eyebrow--rule { display: flex; align-items: center; gap: var(--space-sm); }
.eyebrow--rule::before {
  content: "";
  flex: 0 0 auto;
  width: 26px;
  height: 2px;
  border-radius: 2px;
  background: currentColor;
}

/* --- The questions ------------------------------------------------------ */
/* Closed items are a real tinted tile, not a ghost. The old
   rgba(255,255,255,.66) sat at 1.084:1 against the band, so six closed
   questions read as soft blank blocks and the shadow on the open one did all
   the work. --surface-2 is declared in .band-light--blue for exactly this
   role and the section had never used it. Hover previews the open surface. */
/* 16px, not 12: closed items are a real tinted fill now rather than a ghost, so
   the gap is the only thing keeping six filled blocks from reading as one slab,
   and the open card's shadow already reaches ~7px below its edge. */
.accordion { display: grid; gap: var(--space-md); }

.accordion__item {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--surface-2);
  box-shadow: inset 0 0 0 1px var(--line-strong);
  /* Short on purpose: --surface-2 -> --surface passes exactly through the band
     colour (#eaf2f9) at t=0.40, so the tile momentarily matches the page. The
     ring and shadow carry the slower part. */
  transition: background-color .16s var(--ease), box-shadow .32s var(--ease);
}
/* Deepens, never lightens. Hover used to set --surface, which is the OPEN fill:
   a mouse pointer sits on the tile at the moment of the click, so the tile was
   already white and opening it produced no visible change at all. Layering
   --accent-chip over the tint moves it the other way, away from both the band
   and the open state. */
.accordion__item:hover { background-image: linear-gradient(var(--accent-chip), var(--accent-chip)); }
.accordion__item.is-open {
  background-color: var(--surface);
  background-image: none;
  /* The house light-card cast, as .about__card and .tcard carry it, rather
     than the heavier 0 22px 44px -30px/.5 that lifted the open card off the
     band and detached it from its siblings. */
  box-shadow: inset 0 0 0 1px var(--line-strong),
              0 1px 2px rgba(12, 32, 51, .05),
              0 14px 30px -22px rgba(12, 32, 51, .35);
}

.accordion__item h3 { margin: 0; font-size: inherit; }

.accordion__btn {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 30px;
  align-items: center;
  gap: var(--space-md);
  width: 100%;
  padding: clamp(17px, 1.6vw, 21px) clamp(18px, 1.7vw, 24px);
  border: 0;
  background: none;
  cursor: pointer;
  text-align: left;
  font: 700 clamp(.97rem, 1.18vw, 1.05rem)/1.45 var(--font-display);
  letter-spacing: -.01em;
  color: var(--heading);
  transition: color .25s var(--ease);
}
.accordion__btn:hover { color: var(--accent); }
/* The button is flush to the card on all four sides, and the card clips its
   overflow — so the global ring, drawn 3px OUTSIDE the box, was cropped away
   entirely on a closed item and reduced to a stray blue rule under the question
   on an open one. Drawing it inside keeps it whole and unclippable. */
.accordion__btn:focus-visible { outline-offset: -3px; border-radius: var(--radius); }

/* Plus that loses its upright stroke when the panel opens. Open, the chip
   fills brand-600: left on the 10% tint it sat on a white card and read as
   greyed-out at the very moment it is the active control. */
.accordion__mark {
  position: relative;
  justify-self: end;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--accent-chip);
  box-shadow: inset 0 0 0 1px var(--line-strong);
  color: var(--accent-ink);
  transition: background-color .3s var(--ease), box-shadow .3s var(--ease),
              color .3s var(--ease);
}
.accordion__mark::before,
.accordion__mark::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  border-radius: 2px;
  background: currentColor;
}
.accordion__mark::before { width: 11px; height: 2px; transform: translate(-50%, -50%); }
.accordion__mark::after {
  width: 2px;
  height: 11px;
  transform: translate(-50%, -50%);
  transition: transform .32s var(--ease), opacity .2s var(--ease);
}
.accordion__btn[aria-expanded="true"] .accordion__mark {
  background: var(--brand-600);
  box-shadow: inset 0 0 0 1px var(--brand-600);
  color: #fff;
}
.accordion__btn[aria-expanded="true"] .accordion__mark::after {
  transform: translate(-50%, -50%) scaleY(0);
  opacity: 0;
}

/* `visibility` keeps a collapsed panel out of the accessibility tree */
/* Collapsed only when JS is present to expand it again. The .js flag is set by
   an inline script in <head> before first paint, so this cannot flash open —
   and without JS all six answers are simply there, rather than being sealed
   shut by CSS with nothing able to open them. */
.accordion__panel {
  display: grid;
  grid-template-rows: 1fr;
  transition: grid-template-rows .34s var(--ease), visibility .34s var(--ease);
}
.js .accordion__panel { grid-template-rows: 0fr; visibility: hidden; }
.js .accordion__panel.is-open { grid-template-rows: 1fr; visibility: visible; }
.accordion__content { overflow: hidden; }
/* No second surface. The answer sits on the card's own white, divided from its
   question by a hairline inset to the text — a box inside a box was the thing
   that read wrong. The rule lives on the <p>, never on .accordion__panel or
   .accordion__content: a border on either of those is inside the animated
   grid-rows box and would show through at 0fr. */
/* The divider is a clipped CHILD, not a border. A border on .accordion__panel
   or .accordion__content sits outside the animated grid-rows box and paints a
   1px sliver across all six closed rows; a child is clipped to nothing at 0fr.
   It spans the full text inset while the prose stops at the measure — the rule
   spans, the prose does not. */
.accordion__content::before {
  content: "";
  display: block;
  height: 1px;
  margin-inline: clamp(18px, 1.7vw, 24px);
  background: var(--line);
}
.accordion__content p {
  margin: clamp(15px, 1.4vw, 18px) clamp(18px, 1.7vw, 24px) clamp(17px, 1.6vw, 21px);
  max-width: 68ch;
  /* Body ink, not the washed-out token. --muted only existed to survive the
     deleted tint, and `visibility` flips at the END of the .34s collapse, so
     the answer is read against the closed tile for that whole close — where
     --muted manages 4.52:1 against 7.89:1 for --text. */
  color: var(--text);
  font-size: .97rem;
  line-height: 1.7;
}


/* --------------------------------------------------------------------------
   17. CONTACT
   -------------------------------------------------------------------------- */
.contact {
  position: relative;
  isolation: isolate;
  padding-block: var(--section-y);
  overflow: hidden;
}
.contact::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    radial-gradient(60% 70% at 88% 18%, rgba(26, 114, 168, .28) 0%, transparent 62%),
    var(--page-bg-2);
}
/* .contact__intro is a column flex container, so without align-self the button
   stretches the full width of the column. The details list above it owns its
   own bottom spacing, so the margin is only the gap that separates a block of
   information from an action. */
.contact__wa {
  align-self: start;
  margin-top: var(--space-lg);
}

.contact__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: var(--space-2xl) clamp(36px, 5vw, 72px);
  align-items: stretch;
}
.contact__intro { display: flex; flex-direction: column; }
.contact__intro > p { color: var(--muted); max-width: 44ch; }

/* No bottom margin: .contact__intro is a flex column, so margins there never
   collapse. The 32px this used to carry simply added to the WhatsApp button's
   own 24px and opened a 56px hole under the list, out of step with the 16/16/48
   rhythm above it. Whatever follows owns the gap now. */
.contact__details { display: grid; gap: var(--space-lg); margin: 12px 0 0; }
.contact__details li { display: grid; grid-template-columns: 46px 1fr; gap: var(--space-md); align-items: start; }
.contact__details strong {
  display: block;
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 3px;
}
.contact__details a {
  display: inline-block;
  padding-block: 3px;
  color: var(--heading);
  text-decoration: none;
  font-weight: 500;
  overflow-wrap: break-word;
}
.contact__details a:hover { color: var(--accent); text-decoration: underline; }
.contact__details li > span:last-child { font-size: .97rem; color: var(--heading); line-height: 1.55; }
.contact__icon {
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  background: var(--accent-chip);
  border: 1px solid var(--line-strong);
  color: var(--brand-300);
}
.contact__icon svg { width: 21px; height: 21px; }

/* A hairline closes the column off so the bottom-anchored block reads as a
   deliberate footer rather than content that simply ran out */
.contact__intro .socials {
  margin-top: auto;
  padding-top: var(--space-lg);
  border-top: 1px solid var(--line);
}
.socials__label {
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: var(--space-sm);
}
.socials ul, .socials__list { display: flex; gap: var(--space-xs); }
.socials a, .socials__list a {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border: 1px solid var(--line-strong);
  border-radius: 50%;
  color: #fff;
  transition: background-color .22s var(--ease), border-color .22s var(--ease),
              transform .22s var(--ease);
}
.socials a svg, .socials__list a svg { width: 20px; height: 20px; }
.socials a:hover, .socials__list a:hover {
  background: var(--brand-600);
  border-color: var(--brand-600);
  transform: translateY(-2px);
}

/* Form */
.form-panel {
  /* Sits at its natural height rather than stretching to the taller intro
     column, which would leave a visibly empty panel below the button */
  align-self: start;
  padding: var(--card-pad);
  background: rgba(14, 21, 30, .82);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}
/* Honeypot. Moved off-screen rather than display:none, which some bots skip;
   it is also aria-hidden and out of the tab order so nobody real meets it. */
/* The animated figure is hidden from assistive tech and the true value is
   carried by a sibling, so a screen reader never reads a partial count. */
.u-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.form__trap {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.form__row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-md); margin-bottom: var(--space-md); }
/* The row owns the spacing. Left to their own margins, `p:last-child` would
   zero the second field's margin, making it 16px taller than its neighbour
   and pushing that column's input out of line. */
.form__row .field { margin-bottom: 0; }
/* `start` stops a stretched field from inflating its label and control rows */
.field { display: grid; align-content: start; gap: 6px; margin: 0 0 var(--space-md); }
.field label { font-size: .8rem; font-weight: 600; color: var(--heading); }
.field label span[aria-hidden] { color: var(--brand-300); }
.field__opt { font-weight: 400; color: var(--muted); }

.field input,
.field select,
.field textarea {
  width: 100%;
  padding: 14px 16px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, .04);
  color: #fff;
  font: 400 .96rem/1.5 var(--font-body);
  transition: border-color .2s var(--ease), box-shadow .2s var(--ease), background-color .2s var(--ease);
}
.field select option { background: #0e151e; color: #fff; }
.field textarea { resize: vertical; min-height: 118px; }
/* Mouse affordance only — the global :focus-visible ring stays intact */
.field input:hover, .field select:hover, .field textarea:hover { border-color: var(--brand-400); }
.field input:focus, .field select:focus, .field textarea:focus {
  border-color: var(--brand-400);
  background: rgba(255, 255, 255, .07);
  box-shadow: 0 0 0 3px rgba(128, 192, 224, .18);
}
.field ::placeholder { color: rgba(255, 255, 255, .45); opacity: 1; }
.field.is-invalid input, .field.is-invalid textarea { border-color: #e2685c; }
.field__err { font-size: .8rem; color: #f0938a; }

.form__status { margin: var(--space-md) 0 0; font-size: .92rem; font-weight: 500; }
.form__status.is-ok { color: #6fd39b; }
.form__status.is-error { color: #f0938a; }

/* --------------------------------------------------------------------------
   18. FOOTER
   -------------------------------------------------------------------------- */
.site-footer { background: var(--page-bg-2); border-top: 1px solid var(--line); }
.site-footer__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-lg) var(--space-xl);
  padding-block: var(--section-y);
}
.site-footer__nav ul {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-sm) var(--space-lg);
}
.site-footer a { color: var(--muted); text-decoration: none; font-size: .93rem; }
.site-footer a:hover { color: var(--brand-300); }
.site-footer__top .brand img { width: 150px; }

.site-footer__base {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs) var(--space-lg);
  justify-content: center;
  text-align: center;
  padding-block: var(--space-lg);
  border-top: 1px solid var(--line);
  font-size: .84rem;
  /* .42 measured 4.1:1 on this ground — below AA for 13px text. .5 gives 5.3:1 */
  color: rgba(255, 255, 255, .5);
}
.site-footer__base p { margin: 0; }

/* Credit rides on the copyright line on wide screens and drops below it once
   the footer row starts wrapping */
.site-footer__credit { display: inline-block; }
.site-footer__credit::before { content: "·"; margin: 0 8px; opacity: .55; }
.site-footer__credit a {
  /* Never underlined, resting or hovered — the colour shift alone marks it */
  color: rgba(255, 255, 255, .72);
  text-decoration: none;
}
.site-footer__credit a:hover {
  color: var(--brand-300);
  text-decoration: none;
}

/* --------------------------------------------------------------------------
   19. REVEAL
   Only hidden when JS is present to bring it back.
   -------------------------------------------------------------------------- */
.js .reveal {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity .7s var(--ease), transform .7s var(--ease);
  transition-delay: calc(var(--d, 0) * 90ms);
}
.js .reveal.is-visible { opacity: 1; transform: none; }

/* --------------------------------------------------------------------------
   20. RESPONSIVE
   Breakpoints: 1200 · 1024 (tablet) · 767 (mobile) · 480 (small)
   -------------------------------------------------------------------------- */
@media (max-width: 1200px) {

  :root { --gutter: 32px; }
}

/* ---- Tablet ---- */
@media (max-width: 1024px) {
  :root {
    --section-y: 75px;
    --block-gap: 48px;
    --grid-gap: 22px;
    --card-pad: 28px;
    --header-h: 84px;
    /* here the stuck inner keeps --header-h, so only the border is added */
    --header-stuck: 85px;
  }

  /* The mark needs room to breathe — at 152px it filled the bar to within 3px */
  .brand img { width: 132px; }

  /* Mobile navigation panel */
  .burger { display: grid; }
  /* Solid backing: the transparent version disappeared against light parts of
     the hero photograph, where white bars on a white background vanish. */
  .burger {
    background: rgba(10, 16, 23, .62);
    border-color: rgba(255, 255, 255, .3);
    backdrop-filter: saturate(140%) blur(8px);
    -webkit-backdrop-filter: saturate(140%) blur(8px);
  }
  .nav {
    position: fixed;
    top: 0;
    right: 0;
    z-index: 95;
    width: min(360px, 86vw);
    height: 100vh;
    height: 100dvh;
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    gap: 0;
    padding: calc(var(--header-h) + 16px) var(--gutter) var(--space-2xl);
    background: #0a1017;
    border-left: 1px solid var(--line);
    box-shadow: var(--shadow-lg);
    overflow-y: auto;
    overscroll-behavior: contain;
    transform: translateX(102%);
    visibility: hidden;
    transition: transform .34s var(--ease), visibility .34s var(--ease);
  }
  .nav.is-open { transform: translateX(0); visibility: visible; }

  /* The burger doubles as the close control, so it has to paint ABOVE the
     panel. Left at `z-index: auto` it sat under .nav (z-index 95) and the
     only ways out of an open menu were the scrim and Escape. */
  .burger { position: relative; z-index: 96; }

  /* The scroll lock sets `overflow: hidden` on <body>, which turns body into
     a scroll container and un-sticks the header — so with the panel open at
     any scroll position the logo and the close control rode off-screen with
     it. Pin the header for the duration, and give body matching padding so
     taking the header out of flow does not shift the page underneath it.
     `position: fixed` on an ancestor does NOT establish a containing block
     for a fixed descendant, so the panel itself is unaffected. */
  body.nav-open { padding-top: calc(var(--header-h) + 1px); }
  body.nav-open .site-header { position: fixed; top: 0; left: 0; right: 0; }
  .nav__list { flex-direction: column; align-items: stretch; gap: 0; }
  .nav__list li { border-bottom: 1px solid var(--line); }
  .nav__link {
    display: block;
    padding: var(--space-md) 0;
    font-family: var(--font-display);
    font-size: 1.12rem;
    font-weight: 700;
    color: #fff;
  }
  .nav__link::after { display: none; }
  .nav__cta { margin-top: var(--space-lg); }

  /* Panel foot. `margin-top: auto` drops it to the bottom of the column when
     the panel is tall and collapses to nothing once the panel has to scroll.
     No rule above it — the gap and the letterspaced label already separate it
     from the CTA, and a second hairline so soon after the nav list's own
     dividers read as clutter. */
  .nav__contact {
    display: block;
    margin-top: auto;
    padding-top: var(--space-lg);
  }
  .nav__contact-label {
    margin: 0 0 var(--space-xs);
    font-size: .72rem;
    font-weight: 600;
    letter-spacing: .16em;
    text-transform: uppercase;
    color: var(--muted);
  }
  .nav__contact ul { display: grid; gap: 2px; }
  .nav__contact a {
    display: grid;
    grid-template-columns: 44px minmax(0, 1fr);
    align-items: center;
    gap: var(--space-sm);
    min-height: 44px;
    /* Pulled into the panel gutter so the tap area runs the full width while
       the text stays on the same left edge as the links above. */
    margin-inline: -8px;
    padding: 6px 8px;
    border-radius: var(--radius-sm);
    color: var(--heading);
    font: 500 .95rem/1.45 var(--font-body);
    text-decoration: none;
    /* The domain is a 28-character unbreakable run and the panel is only
       235px wide at a 320px viewport, so it has to be allowed to break. */
    overflow-wrap: anywhere;
    transition: background-color .22s var(--ease), color .22s var(--ease);
  }
  .nav__contact a:hover { background: rgba(255, 255, 255, .05); color: var(--brand-300); }
  .nav__contact-icon {
    display: grid;
    place-items: center;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    background: var(--accent-chip);
    border: 1px solid var(--line-strong);
    color: var(--brand-300);
    transition: background-color .22s var(--ease), border-color .22s var(--ease),
                color .22s var(--ease);
  }
  .nav__contact-icon svg { width: 19px; height: 19px; }
  .nav__contact a:hover .nav__contact-icon {
    background: var(--brand-600);
    border-color: var(--brand-600);
    color: #fff;
  }
  .site-header.is-stuck .site-header__inner { height: var(--header-h); }

  /* Layout collapses */
  .about__grid,
  .coach__grid,

  .contact__grid,
  .faq__grid,
  .case__top { grid-template-columns: minmax(0, 1fr); }
  /* The 32px bottom margin this used to carry is gone from the base rule now,
     so there is nothing to undo here. */
  /* Stacked, the lede no longer needs a narrow measure to sit beside the
     questions, and the facts row closes the intro rather than floating. */
  .faq__lede { max-width: 62ch; }
  .case__shot { max-width: 380px; margin-inline: auto; }
  .academy__top { grid-template-columns: minmax(0, 1fr); }
  .inside-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  .train-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Third card spans the row rather than sitting in a half column with an
     empty gap beside it. Laid out horizontally so a full-width card does not
     end up with a ~600px-tall image. */
  .train-grid .tcard:last-child { grid-column: 1 / -1; flex-direction: row; }
  .train-grid .tcard:last-child .tcard__media { flex: 0 0 42%; }
  .train-grid .tcard:last-child .tcard__media img { height: 100%; aspect-ratio: auto; min-height: 260px; }
  .train-grid .tcard:last-child .tcard__media::after { inset: 0 0 0 auto; width: 40%; height: auto;
    background: linear-gradient(90deg, transparent, rgba(8, 22, 36, .4)); }
  /* Extra left padding so the copy clears the badge, which otherwise fills the
     padding gutter exactly and leaves 1px at the circle widest point. */
  .train-grid .tcard:last-child .tcard__body { justify-content: center; padding-left: calc(var(--card-pad) + 16px); }
  /* Half the 54px badge each side of the image/body seam. The previous
     -(card-pad + 27) put a full icon-width left of the seam, so it sat
     entirely on the photograph. */
  .train-grid .tcard:last-child .tcard__icon { top: 50%; left: -27px; margin-top: -27px; }
  .train-grid .tcard:last-child .tcard__link { margin-top: var(--space-lg); }

  .stats-row { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-xl) var(--space-lg); }
  /* Five items over two columns leaves a stray fifth. Spanning it reads as a
     deliberate closing row instead of an accident. */
  .stats-row .stat:last-child { grid-column: 1 / -1; }

  .section-head { grid-template-columns: minmax(0, 1fr); align-items: start; }

  .about__media { max-width: 520px; margin-inline: auto; }
  /* Centred once the grid has stacked, exactly as .about__media above does.
     Without margin-inline the 560px cap left the clip hard against the left
     edge with up to 432px of empty column beside it between 601 and 1024px. */
  /* Stacked, the section runs the full shell width. The clip fills its column
     and everything shares the shell left edge; only the prose keeps a reading
     measure, the same "the block spans, the prose does not" relationship used
     in the FAQ answers. */
  .coach__media { width: 100%; }
  /* A 4:5 clip sits right beside the copy in the two-column layout (593x741),
     but once the grid stacks and it spans the shell it becomes 960x1200 at
     1024 — 133% of a 900px screen. max-height, not a fixed height: it only
     engages where the ratio-derived height is too big, so phones keep the
     portrait framing untouched. The poster is object-fit:cover, so the clamp
     crops rather than distorts. */
  .coach__media .video--tall:not(.is-playing) { max-height: min(62vh, 560px); }
  /* Stacked, the three facts run across the column rather than down it. As a
     single stack under a 62ch measure they left most of the width empty; the
     prose keeps its reading measure, the facts use the space. */
  .coach__facts { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-lg); }
  .edge-label { display: none; }

  .results { --block-gap: 32px; }
  .roadmap__viewport { margin-bottom: 12px; }
  /* The pill stops hugging a shared baseline here. That alignment is worth
     having when several stops are visible at once, but the stops narrow to
     240px on a phone so the copy wraps to two or three lines unevenly, and a
     two line stop was left with a 41px gap under it against 16px on a three
     line one. Only about one and a half stops are on screen at this width, so
     the alignment buys nothing and the gap costs a lot. */
  .stop .pill { margin-top: 0; }   /* the paragraph already carries 16px */
  .roadmap__hint { display: flex; }

  .hero { min-height: 0; padding-top: calc(var(--header-h) + 72px); }
}

/* ---- Mobile ---- */
@media (max-width: 767px) {
  :root {
    --section-y: 50px;
    --block-gap: 36px;
    --grid-gap: 18px;
    --card-pad: 24px;
    --gutter: 20px;
  }

  h1 { font-size: clamp(2.15rem, 9vw, 2.9rem); }
  h2 { font-size: clamp(1.65rem, 6.6vw, 2.15rem); }

  /* ---- Phone hero: the picture is a band across the top ----
     These are wide ring shots and the hero box is 0.45, so a full height
     backdrop could only ever keep about 28% of the frame and the client could
     not tell what was happening. The photograph gets its own band instead,
     sized 1.63 wide by tall, which is the widest of the three phone files: at
     that shape every frame keeps 100% of its WIDTH, which is where the action
     is, and only the two taller files give up a little off the top and bottom.
     Below the band the picture blends into the page with a gradient rather
     than stopping at an edge, and the copy sits under it on the flat ground. */
  .hero__bg {
    inset: 0 0 auto;
    height: 61.3vw;
  }
  .hero__bg img { object-position: 50% 34%; }
  .hero__bg::after {
    content: "";
    position: absolute;
    /* Overhangs the foot of the band by 3px on purpose. 61.3vw resolves to a
       fractional height (239.063px at 390), and while a slide is cross fading
       Chrome gives the image its own compositing layer, whose bottom edge can
       rasterise a device row lower than this overlay does. Measured at 3x
       during a fade, that left one row of undarkened photograph showing at the
       foot of the picture: luminance 0.0156 against 0.0032 either side, which
       is the bright line that appears only while the image changes. The extra
       3px is page colour painted over page colour, so it costs nothing. */
    inset: 0 0 -3px;
    /* bottom to top: the page colour at the foot of the picture, gone by two
       thirds up, so there is no visible join */
    background:
      /* A scrim the depth of the header, so the logo has a ground to sit on
         whichever photograph is showing. Slide 2 puts a lit window directly
         behind the mark and it was close to unreadable without this. */
      linear-gradient(to bottom,
        rgba(7, 11, 16, .72) 0,
        rgba(7, 11, 16, .42) calc(var(--header-h) * .6),
        rgba(7, 11, 16, 0) calc(var(--header-h) + 26px)),
      /* Bottom to top, and deliberately near solid for the first sixth. These
         are ring shots, and the ring rope runs across the lower part of every
         frame as a pale near horizontal band: measured, it sits between 9% and
         15% up from the foot of the picture. A gentler fade left it showing
         through exactly where the photograph ends, which read as a white line
         drawn under the image. Opaque by 16% swallows it, and the fade is still
         gone by two thirds up so the action is untouched. */
      linear-gradient(to top,
        var(--page-bg) 0%,
        rgba(7, 11, 16, .97) 16%,
        rgba(7, 11, 16, .80) 27%,
        rgba(7, 11, 16, .34) 50%,
        rgba(7, 11, 16, 0) 72%);
  }
  /* Room for the band, then the copy. The hero is bottom aligned, so its
     padding-top is exactly the gap between the top of the frame and the first
     line of text. */
  .hero { padding-top: calc(61.3vw + var(--space-lg)); }
  /* The whole hero scrim was there to keep text legible over a photograph
     behind it. Nothing sits over the photograph now, so it would only be
     dimming the picture for no reason. */
  .hero::after { background: none; }
  .hero__strip { grid-template-columns: minmax(0, 1fr); gap: var(--space-md); }

  .train-grid { grid-template-columns: minmax(0, 1fr); }
  .process__head { grid-template-columns: minmax(0, 1fr); }
  /* Numeral moves above the text once the row is too narrow to sit beside it */
  .step { grid-template-columns: minmax(0, 1fr); gap: var(--space-xs); }
  .step__num { padding-left: 0; }
  /* Losing the left column also lost the hover tint its breathing room. The row
     carries no horizontal padding, so once the numeral stacked the tint ended
     up hugging the words exactly: measured, no clear space at all on the left
     at 734 wide and none on either side at 390, which is what made the
     highlight read as a box clipping the text rather than as a row lighting up.
     The row takes the gutter as padding and the list is pulled out by the same
     amount, so the tint, the hairline rules and the accent bar all still agree
     with each other, the copy does not move a pixel, and on a phone the
     highlight runs cleanly to both edges instead of stopping a few pixels
     short of them. */
  .steps { margin-inline: calc(var(--gutter) * -1); }
  .step { padding-inline: var(--gutter); }
  /* Single column — the third card is an ordinary vertical card again */
  .train-grid .tcard:last-child { flex-direction: column; }
  .train-grid .tcard:last-child .tcard__media { flex: 0 0 auto; }
  .train-grid .tcard:last-child .tcard__media img { height: auto; aspect-ratio: 16 / 10; min-height: 0; }
  .train-grid .tcard:last-child .tcard__media::after { inset: auto 0 0; width: auto; height: 42%;
    background: linear-gradient(transparent, rgba(8, 22, 36, .45)); }
  .train-grid .tcard:last-child .tcard__body { justify-content: flex-start; }
  .train-grid .tcard:last-child .tcard__icon { top: calc(var(--card-pad) * -1); left: var(--card-pad); margin-top: 0; }
  .train-grid .tcard:last-child .tcard__link { margin-top: auto; }
  .train-grid .tcard:last-child .tcard__body { justify-content: center; padding-left: 24px; }
  .stats-row { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-lg); }
  .stat { grid-template-columns: 44px minmax(0, 1fr); gap: 0 var(--space-sm); }
  .stat__icon { width: 44px; height: 44px; }
  .stat__icon svg { width: 21px; height: 21px; }
  .form__row { grid-template-columns: minmax(0, 1fr); }
  /* Three across needs ~708px of content to hold the labels on one line each;
     below that they break to three lines apiece, so they go back to a stack. */
  .coach__facts { grid-template-columns: minmax(0, 1fr); gap: var(--space-md); }
  /* Shorter bars and tighter gaps so four columns still fit on a phone */
  .prog { --prog-gap: 4px; }
  .prog__time { font-size: 1rem; }
  .prog__date { font-size: .66rem; }
  .case__data-head { flex-direction: column; align-items: flex-start; gap: var(--space-md); }


  /* Stacked, the clip and the copy read as one block, so they sit closer than
     the 48px the two column layout needs between separate columns. */
  .about__grid, .coach__grid { row-gap: var(--space-xl); }

  /* Stacked, a heading and the paragraph under it are one unit, so they take
     the same 16px every plain sibling heading already uses. The 32px these
     carried is the two column gutter, which only means something while the
     text sits BESIDE the heading rather than under it. */
  .section-head { row-gap: var(--space-md); }
  .academy__top { row-gap: var(--space-md); }
  .faq__intro h2 { margin-bottom: var(--space-md); }

  .results { --block-gap: 24px; }
  /* Side by side at this width leaves each clip too small to read anything in */
  .sessions__grid { grid-template-columns: minmax(0, 1fr); }
  .proof { width: 100%; padding-right: var(--space-md); }
  .academy__title { max-width: none; }
  .video__label { padding: var(--space-lg) var(--space-md) var(--space-sm); font-size: .76rem; }

  .site-footer__top { flex-direction: column; text-align: center; }
  .site-footer__nav ul { justify-content: center; }
  .site-footer__credit { display: block; margin-top: 4px; }
  .site-footer__credit::before { display: none; }

  /* ---- Technique roadmap: vertical on a phone ----
     The sideways rail asked for a horizontal swipe on the one screen where
     that is hardest to discover, showed two stops out of eight, and carried
     every fault reported on this section: the pill bottoms sliced by its own
     overflow clip, and a rail that on one device painted nothing at all inside
     its full height. Stacking the stops hands the job to the page's own
     vertical scroll, which needs no clip, no mask, no compositing layer and no
     sticky panel to fit. Everything below simply undoes the horizontal rail. */
  .roadmap__viewport {
    overflow: visible;
    /* The closing rule and paragraph follow straight after now that the swipe
       hint is gone, so the rail keeps a full gap below it as well as above. */
    margin-block: var(--block-gap);
    scroll-snap-type: none;
    /* The soft right edge said "more along here". Nothing runs off the right
       any more, and a mask still paints on an overflow: visible box. */
    -webkit-mask-image: none;
    mask-image: none;
  }
  .roadmap__track {
    flex-direction: column;
    /* max-content would size the column to the widest stop and push the page
       sideways; auto lets it be the width of the rail. */
    width: auto;
    gap: var(--space-xl);
    padding-bottom: 0;
    /* Symmetric again. The trailing pad the horizontal rail needs, to stop the
       last card resting against the edge at the end of its travel, has no job
       once the stops are a plain vertical list: it just pushed the right hand
       edge of every description in by 72px while the left stayed on the 20px
       gutter, which narrowed the measure and put single word last lines back. */
    padding-inline: max(var(--gutter), calc((100% - var(--shell)) / 2 + var(--gutter)));
    /* The track is no longer moved by anything, so it should not be promoted
       to its own layer either. */
    will-change: auto;
    transform: none;
  }
  .stop {
    /* flex-basis is the HEIGHT in a column, so the 240px to 300px track width
       would become a fixed stop height. */
    flex: 0 0 auto;
    /* Room down the left for the rail and its nodes, and for the number, which
       now sits above the heading inside the text column. */
    padding: 26px 0 0 34px;
    scroll-snap-align: none;
  }
  .stop__num { left: 34px; }
  /* The node lines up with the heading rather than with a rail running across
     the top, and the segment to the next node runs down instead of across. */
  .stop::before { top: 30px; }
  .stop::after {
    top: 36px;
    left: 6px;
    width: 1px;
    /* From this node's centre to the next one: the rest of this stop, then the
       gap between them, then the next node's own offset. The three cancel to
       the stop's own height plus one gap. */
    height: calc(100% + var(--space-xl));
  }
  /* Nothing to swipe any more */
  .roadmap__hint { display: none; }
}

/* Buttons come down a step once they are full width and thumb driven rather
   than pointer driven. 48px still clears the 44px minimum target. */
@media (max-width: 767px) {
  :root { --btn-h: 48px; }
}

/* ---- Small mobile ---- */
@media (max-width: 480px) {
  .stats-row { grid-template-columns: minmax(0, 1fr); }

  /* Full width here, so --lg no longer signals anything the width is not
     already saying. Only the type needs collapsing now: the heights are the
     token's job and already match. */
  .btn--lg { font-size: .95rem; }
  /* Full width and centred, so the lopsided 26/18 padding no longer balances
     anything — it only ate text room. Inside .academy__offer (--card-pad each
     side) that left 148px for "Join the community", which wrapped to a second
     line and made that one button 10px taller than every other. */
  /* .btn--lead.btn--lg is listed too: at 0,2,0 it outweighs the bare .btn
     selector and would otherwise keep its 30/20 insets while every other
     button here sits at 16/16. */
  .btn,
  .btn--lead.btn--lg { padding-inline: var(--space-md); }

  /* Faces beside the sentence, as the client asked, and the full capsule with
     them. Stacking the two rows had cost the pill its notification shape: a
     999px radius on a tall two row box resolves to a 48px corner, so it had to
     drop to 28px and stopped reading as a pill at all. Side by side the box is
     short enough that the capsule radius behaves, and the faces shrink a little
     to leave the sentence the width it needs.
     Both selectors again: the base rule is .proof--head, .band-light .proof--head
     at 0,2,0, so a bare .proof--head here loses and nothing would apply. */
  .proof--head,
  .band-light .proof--head {
    padding: 4px 14px 4px 4px;
    gap: 8px;
  }
  /* Smaller faces and a deeper overlap, which is what buys the sentence enough
     width to wrap in two lines rather than three. Measured at 390: the row of
     four plus the padding and gap was taking 123px of the 350 available and the
     text ran to three lines, making a 72px tall box whose 36px corner stopped
     reading as a notification. At 26px with a 12px tuck the row is 68px, the
     text gets 255px and the pill comes back to two lines and about 46px, where
     the capsule radius is half the height and the shape is right again.
     All four faces stay: the earlier horizontal version dropped one to make
     room, and the client asked for the images. */
  .proof__faces img { width: 26px; height: 26px; }
  .proof__faces img + img { margin-left: -12px; }
  .proof__line { font-size: .82rem; line-height: 1.4; }
  /* The nav CTA too. --sm exists for the desktop header bar; at this width that
     bar is only logo + burger, so the only --sm button on screen is the one in
     the slide-in panel, which should read as a page CTA. */
  .btn--sm { font-size: .95rem; }
  .btn--sm .btn__chev { width: 26px; height: 26px; }
  .btn--sm .btn__chev svg { width: 15px; height: 15px; }

  .btn { width: 100%; }
  .hero__actions .btn { flex: 1 1 100%; }
  .inside-grid { grid-template-columns: minmax(0, 1fr); }
  .brand img { width: 116px; }
  .video__play { width: 58px; height: 58px; }
  .video__play svg { width: 22px; height: 22px; }
  .contact__details li,
  .coach__facts li,
  .hero__strip li { grid-template-columns: 38px 1fr; gap: var(--space-sm); }
  .contact__icon, .coach__fact-icon, .hero__strip-icon { width: 38px; height: 38px; }
  .contact__icon svg, .coach__fact-icon svg, .hero__strip-icon svg { width: 18px; height: 18px; }

  /* The panel gutter drops to 20px here, leaving a 213px text column at a
     360px viewport — 5px short of the 218px the email domain needs, which
     cost a whole extra line. Tightening the chip and the gap (the same
     direction the contact icons above already scale on mobile) buys that back
     and lands the domain on one line. The row stays a full-width 56px tap
     target, so the smaller chip costs nothing ergonomically. */
  .nav__contact a { grid-template-columns: 40px minmax(0, 1fr); gap: 10px; font-size: .93rem; }
  .nav__contact-icon { width: 40px; height: 40px; }
  .nav__contact-icon svg { width: 18px; height: 18px; }
}

/* --------------------------------------------------------------------------
   21. REDUCED MOTION
   -------------------------------------------------------------------------- */
/* Forced colours drop box-shadow, and in this component every boundary is a
   shadow — the tile ring, the open card's cast and the mark's circle. Borders
   survive, so the edges are redrawn as borders. */
@media (forced-colors: active) {
  .accordion__item { border: 1px solid CanvasText; }
  .accordion__mark { border: 1px solid CanvasText; }
  .accordion__item.is-open { border-color: Highlight; }
  .accordion__content::before { background: CanvasText; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  .js .reveal { opacity: 1; transform: none; }
  /* The halo is the node's own box-shadow now rather than a keyframe, so it
     cannot be lost to an end state and the marker still reads as current. What
     has to go is the blip itself. The universal rule above already cuts every
     animation to a hundredth of a millisecond and the rings begin and end
     fully transparent, but switching them off here states the intent instead
     of relying on that. */
  .prog__step.is-final .prog__node::before,
  .prog__step.is-final .prog__node::after {
    animation: none;
    opacity: 0;
  }
  /* The rotation is switched off in JS; make sure the one slide left showing
     is fully opaque rather than caught mid-fade. */
  .hero__slide.is-active { opacity: 1; }
  .roadmap.is-pinned { height: auto; }
  .roadmap.is-pinned .roadmap__sticky { position: static; height: auto; }
  .roadmap__track { transform: none !important; }
}

/* --------------------------------------------------------------------------
   22. PRINT
   -------------------------------------------------------------------------- */
@media print {
  .site-header, .nav, .burger, .video, .nav-scrim, .hero__bg { display: none !important; }
  body, .section, .academy, .roadmap, .site-footer { background: #fff !important; color: #000 !important; }
  h1, h2, h3, em, strong { color: #000 !important; }
  /* The FAQ is collapsed by CSS, and .reveal keeps anything never scrolled into
     view at opacity 0 — printing an unopened page produced six questions, no
     answers, and often a blank block. */
  .js .reveal { opacity: 1 !important; transform: none !important; }
  .js .accordion__panel { grid-template-rows: 1fr !important; visibility: visible !important; }
  .accordion__item { box-shadow: none !important; border: 1px solid #999; }
}
