/* ═══════════════════════════════════════════════════════════════════
   ELITE SPEED · "Track Black"  (locked direction, Step 0 · 2026-07-25)
   Brand True: the customer's real style guide, verified per §12e.
   Motion contract: brand-kit/interactions.md  (native scroll, no Lenis)
   ═══════════════════════════════════════════════════════════════════ */

/* ── Self-hosted faces (§6b) ─────────────────────────────────────── */
@font-face{font-family:'Oswald';src:url('assets/fonts/oswald-var.woff2') format('woff2');
  font-weight:200 700;font-style:normal;font-display:swap}
@font-face{font-family:'Montserrat';src:url('assets/fonts/montserrat-var.woff2') format('woff2');
  font-weight:100 900;font-style:normal;font-display:swap}
@font-face{font-family:'Space Mono';src:url('assets/fonts/space-mono-400.woff2') format('woff2');
  font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:'Space Mono';src:url('assets/fonts/space-mono-700.woff2') format('woff2');
  font-weight:700;font-style:normal;font-display:swap}
/* metric-matched fallbacks so the swap causes no shift */
@font-face{font-family:'Oswald Fallback';src:local('Arial Narrow'),local('Arial');
  size-adjust:88%;ascent-override:112%;descent-override:28%;line-gap-override:0%}
@font-face{font-family:'Montserrat Fallback';src:local('Helvetica Neue'),local('Arial');
  size-adjust:104%;ascent-override:92%;descent-override:24%;line-gap-override:0%}

:root{
  /* Colour — locked_direction tokens, verified brand signal */
  --bg:#000000; --surface:#141414; --surface-2:#1C1C1C;
  --ink:#FFFFFF; --muted:#A8A8A8;
  --accent:#FFF201; --accent-2:#DDD207; --accent-on:#000000;
  --line:rgba(255,255,255,.14); --line-2:rgba(255,255,255,.26);

  /* Type */
  --display:'Oswald','Oswald Fallback',Impact,sans-serif;
  --body:'Montserrat','Montserrat Fallback',system-ui,sans-serif;
  --mono:'Space Mono',ui-monospace,SFMono-Regular,monospace;

  /* Motion — interactions.md §1 */
  --ease-settle:cubic-bezier(.77,0,.175,1);
  --ease-out:cubic-bezier(.22,.61,.36,1);
  --ease-hover:cubic-bezier(.4,0,.2,1);
  --dur-micro:.15s; --dur-hover:.25s; --dur-settle:.6s; --dur-reveal:.8s; --dur-hero:1.2s;

  /* Space — density: standard (4/8/16/24/40/64) */
  --s1:8px; --s2:16px; --s3:28px; --s4:48px; --s5:clamp(64px,9vw,104px);
  --nav-h:76px; --pad:clamp(20px,5vw,56px); --wrap:1280px;
  --r:0px; --r-cta:0px;

  /* Measure (Wave 2 · 2.8). Montserrat's `0` advance is .66em while its average
     lowercase advance is ~.52em, so a `ch` cap over-reads: 62ch measures 75 real
     characters at the body size. Verified in-browser, not assumed. */
  --measure:62ch;

  /* ── The micro floor (Wave 8) ─────────────────────────────────────
     Before this wave the Space Mono label layer ran at SEVEN sizes inside a
     2px band: .62 / .64 / .66 / .68 / .70 / .72 / .74rem (9.92px → 11.84px).
     Nobody can see a 0.32px step, so that was noise pretending to be a scale,
     and every one of those sizes sat under the 14px legibility floor — on a
     site whose reader is a parent in their forties reading a phone at the side
     of a turf field. The whole layer now sets at ONE size, 14px, and its
     hierarchy is carried by weight, colour and tracking instead. */
  --fs-micro:.875rem;

  /* ── Depth (Wave 3) ───────────────────────────────────────────────
     design.md signature move #2 asks for an 8px card lift with the shadow
     travelling `0 2px 8px …/.10` → `0 8px 24px …/.20`, and states the shadow
     "uses the brand accent at low opacity". The literal `rgba(0,51,136,…)` in
     that line is the taxonomy's blue, not this build's locked accent — and a
     black shadow is invisible on a #000 ground, so it would render the whole
     move a no-op. Geometry and the alpha ladder are kept verbatim; only the
     hue is resolved to the locked accent (#FFF201). interactions.md §9b names
     the same pair. */
  --lift:-8px;
  --shadow-rest:0 2px 8px rgba(255,242,1,.10);
  --shadow-lift:0 8px 24px rgba(255,242,1,.20);
  --shadow-cta:0 2px 10px rgba(255,242,1,.12);
  --shadow-cta-hi:0 10px 28px rgba(255,242,1,.26);
  /* Hover wash: the one surface tint used across every large hit area. */
  --wash:rgba(255,242,1,.05);
  --wash-2:rgba(255,242,1,.16);
}

*,*::before,*::after{box-sizing:border-box}
html{
  scroll-behavior:smooth;
  overflow-x:clip;              /* NOT hidden — sticky (P-009) breaks under hidden */
  -webkit-text-size-adjust:100%;
  scroll-padding-top:calc(var(--nav-h) + 16px);
}
body{
  margin:0;background:var(--bg);color:var(--ink);
  font-family:var(--body);font-weight:400;
  font-size:clamp(1rem,.965rem + .18vw,1.075rem);line-height:1.62;
  overflow-x:clip;font-synthesis:none;-webkit-font-smoothing:antialiased;
}
img,video,svg{max-width:100%;display:block}
a{color:inherit;text-decoration:none}
/* §7c #5 — kill the 300ms tap delay on every interactive surface, not just .btn */
a,button,summary,label,input,select,textarea{touch-action:manipulation}

/* ── Focus (Wave 3 · 3.4) ────────────────────────────────────────────
   One ring, every focusable element, and it has to read on FOUR different
   grounds: #000, the #141414/#1C1C1C surfaces, the hero photograph, and the
   yellow face of a primary button. A bare accent outline disappears on the
   last of those, so the 3px offset gap is filled with near-black by a
   box-shadow — the ring is then always yellow-on-black no matter what is
   underneath it. `border-radius` is 0, not the 2px it carried before: the
   locked direction is 0px radius everywhere, and that includes the ring. */
:focus-visible{
  outline:3px solid var(--accent);outline-offset:3px;border-radius:0;
  box-shadow:0 0 0 3px rgba(0,0,0,.92);
}

h1,h2,h3,h4{
  font-family:var(--display);font-weight:700;text-transform:uppercase;
  line-height:1.02;letter-spacing:0;margin:0;text-wrap:balance;
}
h1{font-size:clamp(3rem,10.5vw,8.5rem)}
h2{font-size:clamp(2rem,4.6vw,3.6rem)}
h3{font-size:clamp(1.2rem,2.2vw,1.6rem)}
p{margin:0;text-wrap:pretty}
.lede{color:var(--muted);font-size:clamp(1.02rem,.98rem + .3vw,1.2rem)}
/* Accent word inside a headline. Oswald ships no italic axis and font-synthesis is off,
   so emphasis is expressed in the locked direction's own vocabulary: the brand yellow. */
h1 .hl,h2 .hl,h3 .hl,.hl{color:var(--accent);font-style:normal}

/* ── P-005 orphan prevention (Wave 2 · 2.9, BUILD-STANDARDS §7) ────
   `text-wrap: balance` is a BLOCK-level property: it is ignored on an inline
   box. The FAQ questions are `<h3 style="display:inline">` inside `<summary>`,
   so the h1-h4 rule above never reached them and every long question orphaned
   its last word on mobile. Balance is therefore declared on the real block
   container of every short-text run, not just on the heading tags. */
.faq summary,.lede,.hero__sub,.kick,blockquote,.btn,
.rowlink__t,.card b,.mech__list b,.coach b,.related b,.step h3,
.price__per,.progs__hd p,.faq__hd p,.closer p{text-wrap:balance}
/* Long-form running prose prefers `pretty` — `balance` over-distributes a long
   paragraph and wrecks the inter-line rhythm (P-005). */
.prose p,.prose li,.legal p,.legal li,.faq .a p,.svc p{text-wrap:pretty}
/* …but a lede is a headline-adjacent block, so it balances even inside prose.
   Declared last and at higher specificity so `.prose p` cannot outrank it. */
.prose .lede,.svc .lede,.subhero .lede{text-wrap:balance}

.wrap{max-width:var(--wrap);margin-inline:auto;padding-inline:var(--pad)}
.kick{font-family:var(--mono);font-size:var(--fs-micro);font-weight:700;letter-spacing:.15em;
  text-transform:uppercase;color:var(--accent);margin:0}
.kick--m{color:var(--muted)}
.sec{padding-block:var(--s5)}
.sec--alt{background:var(--surface)}

/* ── Skip link ───────────────────────────────────────────────────────
   `position:fixed`, and inset 12px from the corner rather than flush to it, so
   the 3px ring + 3px offset are never clipped by the viewport edge — the skip
   link is the FIRST thing a keyboard user lands on, and an invisible focus
   state there means the whole page reads as unfocusable. */
.skip{position:fixed;left:-9999px;top:12px;z-index:200;background:var(--accent);
  color:var(--accent-on);padding:12px 20px;font-family:var(--mono);font-weight:700;
  transition:background var(--dur-micro) var(--ease-hover)}
.skip:focus{left:12px}
@media (hover:hover){.skip:hover{background:var(--accent-2)}}

/* ── Buttons ─────────────────────────────────────────────────────────
   Checklist 3.6 asks for a GRADIENT fill. DIRECTION OVERRIDE: the locked
   direction is Brand True on the customer's own flat #FFF201, and a gradient
   would be a colour the brand does not own. The tactility the item is really
   after is delivered by motion instead — the deepening accent arrives as a
   left-origin `scaleX` WIPE, which is the same gesture the nav underline and
   the programme rows already use, so the whole site hovers with one hand.
   `isolation:isolate` + `z-index:-1` keeps the wipe behind the label without
   needing a wrapper element around bare text nodes. */
.btn{
  position:relative;isolation:isolate;
  display:inline-flex;align-items:center;justify-content:center;gap:10px;
  font-family:var(--mono);font-size:var(--fs-micro);font-weight:700;letter-spacing:.11em;
  text-transform:uppercase;padding:16px 30px;min-height:52px;
  border-radius:var(--r-cta);background:var(--accent);color:var(--accent-on);
  border:2px solid var(--accent);touch-action:manipulation;cursor:pointer;
  box-shadow:var(--shadow-cta);
  transition:background var(--dur-hover) var(--ease-hover),
             border-color var(--dur-hover) var(--ease-hover),
             box-shadow var(--dur-hover) var(--ease-hover),
             translate var(--dur-hover) var(--ease-hover),
             scale var(--dur-hover) var(--ease-hover);
}
.btn::before{content:'';position:absolute;inset:0;z-index:-1;background:var(--accent-2);
  transform:scaleX(0);transform-origin:left;
  transition:transform var(--dur-hover) var(--ease-hover)}
.btn--ghost{background:transparent;color:var(--ink);border-color:var(--line-2);box-shadow:none}
.btn--ghost::before{background:rgba(255,255,255,.12)}
.btn--onlight{background:var(--accent);color:var(--accent-on);border-color:var(--accent)}
@media (hover:hover){
  /* interactions.md §9c: scale up one step + the accent deepens. The pill the
     spec names does not exist here (0px radius, operator declined it), so the
     BEHAVIOUR transfers to the square block and the shadow sharpens with it. */
  .btn:hover{border-color:var(--accent-2);translate:0 -2px;scale:1.03;
    box-shadow:var(--shadow-cta-hi)}
  .btn:hover::before{transform:scaleX(1)}
  .btn--ghost:hover{border-color:var(--ink);box-shadow:none}
}
/* (the press state for .btn lives with the rest of them, near the bottom) */
.btn__row{display:flex;flex-wrap:wrap;gap:12px}

/* ── Header (P-003 transparent → solid) ──────────────────────────── */
.hdr{
  position:fixed;inset:0 0 auto 0;z-index:60;height:var(--nav-h);
  display:flex;align-items:center;justify-content:space-between;gap:24px;
  padding-inline:var(--pad);background:transparent;
  padding-top:env(safe-area-inset-top);
  transition:background var(--dur-hover) var(--ease-hover),
             box-shadow var(--dur-hover) var(--ease-hover);
}
.hdr.is-solid,.hdr.is-open{background:var(--bg);box-shadow:0 1px 0 var(--line)}
/* Wave 8 · 8.4: the mark is 34px tall, so the anchor around it measured
   35.81×34 — under the 44px floor on every page of the site, on the one
   control that gets you home. The floor is set on the anchor; the header is
   76px tall and the logo is vertically centred, so nothing moves. */
.hdr__logo{display:flex;align-items:center;justify-content:center;opacity:1;
  min-width:44px;min-height:44px;touch-action:manipulation;
  transition:opacity var(--dur-hover) var(--ease-hover)}
.hdr__logo img{height:34px;width:auto;
  filter:drop-shadow(0 1px 8px rgba(0,0,0,.6));transition:filter var(--dur-hover)}
.hdr.is-solid .hdr__logo img{filter:none}
@media (hover:hover){.hdr__logo:hover{opacity:.78}}
.nav{display:flex;align-items:center;gap:26px}
.nav a{position:relative;display:inline-flex;align-items:center;min-height:44px;
  font-family:var(--mono);font-size:var(--fs-micro);font-weight:700;
  letter-spacing:.14em;text-transform:uppercase;padding-block:8px;
  text-shadow:0 1px 8px rgba(0,0,0,.6);
  transition:color var(--dur-hover) var(--ease-hover)}
.hdr.is-solid .nav a{text-shadow:none}
.nav a::after{content:'';position:absolute;inset:auto 0 2px 0;height:2px;background:var(--accent);
  transform:scaleX(0);transform-origin:left;
  transition:transform var(--dur-hover) var(--ease-hover)}
@media (hover:hover){.nav a:hover{color:var(--accent)}.nav a:hover::after{transform:scaleX(1)}}
/* Keyboard parity: a hover-only affordance is invisible to the tab key. Every
   sweep in this build fires on :focus-visible too, OUTSIDE the hover query. */
.nav a:focus-visible::after{transform:scaleX(1)}
.hdr__cta{display:flex;align-items:center;gap:12px}
.hdr .btn{padding:12px 22px;min-height:44px;font-size:var(--fs-micro)}

.burger{display:none;width:48px;height:48px;background:none;border:0;cursor:pointer;
  align-items:center;justify-content:center;padding:0}
.burger span{display:block;width:24px;height:2px;background:var(--ink);position:relative;
  transition:background var(--dur-micro)}
.burger span::before,.burger span::after{content:'';position:absolute;left:0;width:24px;height:2px;
  background:var(--ink);transition:transform var(--dur-hover) var(--ease-hover)}
.burger span::before{top:-7px}.burger span::after{top:7px}
@media (hover:hover){
  .burger:hover span,.burger:hover span::before,.burger:hover span::after{background:var(--accent)}
}
.hdr.is-open .burger span{background:transparent}
.hdr.is-open .burger span::before{transform:translateY(7px) rotate(45deg)}
.hdr.is-open .burger span::after{transform:translateY(-7px) rotate(-45deg)}

/* Drawer: top-level fixed scroller (§7a) — never nested in the fixed header */
.drawer{
  position:fixed;left:0;right:0;bottom:0;top:var(--drawer-top,var(--nav-h));
  z-index:55;background:var(--bg);overflow-y:auto;-webkit-overflow-scrolling:touch;
  touch-action:pan-y;overscroll-behavior:contain;
  padding:var(--s3) var(--pad) calc(var(--s4) + env(safe-area-inset-bottom));
  transform:translateY(-12px);opacity:0;visibility:hidden;
  transition:opacity var(--dur-hover) var(--ease-hover),
             transform var(--dur-hover) var(--ease-hover),visibility 0s .25s;
}
.hdr.is-open ~ .drawer{opacity:1;visibility:visible;transform:none;transition-delay:0s}
.drawer a{display:block;font-family:var(--display);font-size:1.9rem;text-transform:uppercase;
  padding:14px 0;border-bottom:1px solid var(--line);
  transition:color var(--dur-hover) var(--ease-hover),
             border-color var(--dur-hover) var(--ease-hover),
             transform var(--dur-hover) var(--ease-hover)}
@media (hover:hover){
  .drawer a:not(.btn):hover{color:var(--accent);border-bottom-color:var(--accent);
    transform:translateX(6px)}
}
/* The drawer is the mobile surface: touch has no hover, so the tab ring is the
   only state a keyboard user gets in here. It is checked in the §15 captures. */
.drawer a:not(.btn):focus-visible{color:var(--accent);border-bottom-color:var(--accent)}
.drawer .btn{margin-top:var(--s3);width:100%}
@media (max-width:960px){
  .nav{display:none}
  .burger{display:flex}
  .hdr__cta .btn{display:none}
}

/* ── HERO (P-011 + P-001 + P-010 + P-002) ────────────────────────── */
.hero{position:relative;min-height:100vh;min-height:100svh;display:flex;align-items:flex-end;
  background:var(--bg);overflow:clip;isolation:isolate}
.hero__media{position:absolute;inset:-4% 0;z-index:0;will-change:transform}
.hero__media img,.hero__media video{width:100%;height:100%;object-fit:cover}
.hero__media video{position:absolute;inset:0;opacity:0;
  transition:opacity .9s var(--ease-settle)}
.hero__media video.is-live{opacity:1}
.hero__scrim{position:absolute;inset:0;z-index:1;background:
  linear-gradient(180deg,rgba(0,0,0,.74) 0%,rgba(0,0,0,.16) 30%,rgba(0,0,0,.34) 58%,rgba(0,0,0,.92) 100%)}
/* §7 / P-002 — DESKTOP + TABLET, and this is a defect Wave 3 found, not a
   refinement. Wave 1 measured the hero's worst-pixel contrast on MOBILE only.
   Re-measuring at 1440 (same method, Range line-boxes over a screenshot of the
   real composite) showed the ≥721px hero failing badly: with the customer's
   video running, the white H1 read 1.84:1 and the yellow H1 1.57:1 against
   floors of 3:1, and the sub-copy 2.88:1 against 4.5:1. Cause: the vertical
   scrim only reaches .19 alpha where the H1 sits, the desktop frame is far
   brighter than the mobile poster, and a VIDEO ground moves — the bright pixel
   arrives on a frame nobody screenshotted.
   Fix is a left-anchored scrim under the copy column, which is P-002's own
   "very busy photo" remedy and also the classic cinematic composition: the
   type side is grounded, the right two thirds of the customer's video stay
   clean. The vertical scrim's midsection is nudged with it. */
@media (min-width:721px){
  .hero__scrim{background:
    linear-gradient(90deg,rgba(0,0,0,.60) 0%,rgba(0,0,0,.54) 42%,
                          rgba(0,0,0,.42) 64%,rgba(0,0,0,0) 88%),
    linear-gradient(180deg,rgba(0,0,0,.78) 0%,rgba(0,0,0,.22) 30%,
                          rgba(0,0,0,.40) 58%,rgba(0,0,0,.92) 100%)}
}
/* §7 / P-002 — the mobile poster is a brighter, busier frame than the desktop one and the
   copy block starts higher up the image, so the scrim carries more weight under the text.
   Measured target: ≥0.5 black over the lightest pixel any body copy crosses, which holds
   white body text above 4.5:1 and the yellow H1 above 3:1 on every pixel. */
@media (max-width:720px){
  .hero__scrim{background:linear-gradient(180deg,
    rgba(0,0,0,.76) 0%,rgba(0,0,0,.58) 12%,rgba(0,0,0,.58) 30%,
    rgba(0,0,0,.62) 58%,rgba(0,0,0,.94) 100%)}
}
.hero__in{position:relative;z-index:2;width:100%;
  padding:calc(var(--nav-h) + var(--s4)) var(--pad) clamp(32px,5vh,56px)}
/* 3.3 — radial halo behind the headline. Two jobs: it pools light under the H1
   so the display type sits IN the frame instead of on top of it, and it is the
   one place the brand yellow appears as atmosphere rather than as an object.
   Alpha is deliberately low and the centre is placed high-left, over the WHITE
   headline line — a yellow bloom sitting under the yellow line would lift its
   background luminance and eat contrast. Isolated at 1440 on the poster: the
   halo costs the white H1 0.69 of a contrast point (6.88 → 6.19) and the yellow
   H1 0.02 (1.83 → 1.81). Both numbers were already failing for an unrelated
   reason; see the scrim note above for the fix and the final measurements. */
.hero__in::before{
  content:'';position:absolute;z-index:-1;pointer-events:none;
  left:0;bottom:8%;width:min(105%,1180px);height:min(78%,660px);
  background:radial-gradient(46% 52% at 24% 34%,
    rgba(255,242,1,.085) 0%,rgba(255,242,1,.035) 45%,rgba(255,242,1,0) 72%);
}
@media (max-width:720px){
  .hero__in::before{width:118%;height:62%;bottom:18%;
    background:radial-gradient(52% 46% at 30% 30%,
      rgba(255,242,1,.07) 0%,rgba(255,242,1,.028) 46%,rgba(255,242,1,0) 74%)}
}
.hero .kick{color:var(--accent);text-shadow:0 1px 10px rgba(0,0,0,.8)}
.hero h1{margin:.18em 0 0;text-shadow:0 2px 24px rgba(0,0,0,.55)}
.hero h1 span,.hero h1 em{display:block;font-style:normal}
.hero h1 .l1{font-weight:200}
.hero h1 .l2{font-weight:700;color:var(--accent);margin-left:clamp(0px,7vw,152px)}
.hero__sub{max-width:46ch;margin-top:var(--s3);color:#fff;
  font-size:clamp(1.02rem,.97rem + .35vw,1.2rem);text-shadow:0 1px 12px rgba(0,0,0,.75)}
.hero .btn--ghost{color:#fff;border-color:rgba(255,255,255,.6);backdrop-filter:blur(2px)}

/* Action block: CTA pair + proof card. Stacked on phones; on desktop the proof card sits
   beside the buttons so the split-time rail still clears a 900px fold. */
.hero__act{display:flex;flex-direction:column;align-items:flex-start;
  gap:var(--s3);margin-top:var(--s3)}
@media (min-width:900px){
  .hero__act{flex-direction:row;align-items:flex-end;gap:24px;flex-wrap:wrap}
}

/* Hero proof card — glass panel carrying one real Google review (Wave 1 · 1.5) */
.hero__proof,.glass-card{
  margin:0;max-width:min(100%,540px);display:grid;gap:9px;
  padding:16px 20px;background:rgba(0,0,0,.42);
  border:1px solid rgba(255,255,255,.20);border-left:3px solid var(--accent);
  -webkit-backdrop-filter:blur(14px) saturate(1.15);backdrop-filter:blur(14px) saturate(1.15);
  box-shadow:0 18px 44px rgba(0,0,0,.45);
}
.hero__proof .stars{color:var(--accent);font-size:var(--fs-micro);letter-spacing:.2em;line-height:1}
.hero__proof blockquote{margin:0;font-family:var(--body);font-weight:600;
  font-size:clamp(1rem,.96rem + .22vw,1.09rem);line-height:1.42;color:#fff;text-wrap:balance}
.hero__proof figcaption{font-family:var(--mono);font-size:var(--fs-micro);letter-spacing:.10em;
  text-transform:uppercase;color:#D6D6D6}
@media (max-width:720px){
  .hero__act{gap:var(--s2)}
  .hero__proof{padding:13px 16px;gap:7px}
  .hero__proof blockquote{font-size:1rem;line-height:1.4}
}

/* Split-time trust rail — the site's stat row (Wave 2 · 2.3).
   `.stat-row` is the polish-checklist's canonical token; `.rail` is the name the
   build and interactions.md §9d already use. Both ride the same rule, the way
   Wave 1 paired `.callbar` with `.sticky-cta`. */
.rail,.stat-row{display:grid;grid-template-columns:repeat(4,1fr);gap:var(--s2);
  margin-top:clamp(28px,4.5vh,48px);padding-top:var(--s2);border-top:1px solid var(--line-2)}
/* Accent bar above each stat: a short 3px rule, not a full-width band, so the
   rail keeps one accent and one ornament (restraint criterion). */
.rail>div,.stat-row>div{position:relative;padding-top:14px}
.rail>div::before,.stat-row>div::before{content:'';position:absolute;left:0;top:0;
  width:26px;height:3px;background:var(--accent);box-shadow:0 1px 6px rgba(0,0,0,.75)}
.rail b,.stat-row b{display:block;font-family:var(--display);font-weight:700;
  font-size:clamp(1.5rem,3vw,2.1rem);line-height:1;
  color:var(--accent);text-shadow:0 1px 10px rgba(0,0,0,.7)}
.rail span,.stat-row span{display:block;margin-top:6px;
  font-family:var(--mono);font-size:var(--fs-micro);letter-spacing:.10em;text-transform:uppercase;
  color:#fff;text-shadow:0 1px 8px rgba(0,0,0,.8);text-wrap:balance}
@media (max-width:760px){.rail,.stat-row{grid-template-columns:repeat(2,1fr);row-gap:var(--s3)}}
@media (max-width:720px){
  .kick__x{display:none}                       /* keep the eyebrow to one line */
  .hero h1 .l2{margin-left:0}
  .hero__in{padding-block:calc(var(--nav-h) + var(--s3)) clamp(28px,4vh,44px)}
}
/* §7b-5 — stacked CTA groups share ONE width (the widest child) */
@media (max-width:560px){
  .hero .btn__row,.closer .btn__row{display:grid;grid-template-columns:minmax(0,max-content);gap:12px}
  .closer .btn__row{justify-content:center}
}

/* ── Mechanism ───────────────────────────────────────────────────── */
.mech__h{max-width:19ch;margin-top:var(--s2)}
.mech__h .hl{color:var(--accent)}
.mech__grid{display:grid;grid-template-columns:1.05fr .95fr;gap:var(--s5);
  align-items:start;margin-top:var(--s4)}
@media (max-width:900px){.mech__grid{grid-template-columns:1fr;gap:var(--s4)}}
.mech__list{list-style:none;margin:var(--s3) 0 0;padding:0}
.mech__list li{display:grid;grid-template-columns:auto 1fr;gap:var(--s2);
  padding:var(--s3) 0;border-top:1px solid var(--line)}
.mech__list li:last-child{border-bottom:1px solid var(--line)}
.mech__list i{font-style:normal;font-family:var(--mono);font-size:var(--fs-micro);font-weight:700;
  letter-spacing:.14em;color:var(--accent)}
.mech__list b{display:block;font-family:var(--display);font-size:1.28rem;text-transform:uppercase;
  margin-bottom:6px}
.mech__list p{color:var(--muted);font-size:1rem}
/* Wave 4 · 4.6 — framed-image treatment. Every photograph on the site that sits
   INSIDE the page flow (rather than bleeding behind type) gets the same frame:
   a 1px inset hairline ring so the photo has an edge against the black ground,
   and a soft outer accent glow so it reads as a lit object rather than a hole
   punched in the page. `box-shadow` carries both layers so nothing changes the
   element's box and no radius is introduced (0px is locked). */
.framed,.mech__fig img,.coach img,.prose figure img{
  position:relative;display:block;border-radius:var(--r);
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.14),
             inset 0 0 0 2px rgba(0,0,0,.55),
             0 18px 44px -28px rgba(255,242,1,.30),
             0 2px 10px rgba(0,0,0,.55)}
.mech__fig{position:relative}
/* `--focus` is the art-direction hook (Wave 4): a subject that is not dead
   centre would otherwise be cropped out by `object-fit:cover` at one breakpoint
   or the other. Per-image `style="--focus:24% 34%"` keeps the subject in frame
   at 1440, 820 and 430 without a second file. Defaults to centre. */
/* Wave 6 · the aspect-ratio bug Wave 5 handed forward, fixed at the root.
   `width`/`height` CONTENT attributes (required for CLS) map to presentational
   hints. This rule set `width:100%` but never `height`, so the height hint won
   and `aspect-ratio` was silently ignored on every in-flow figure since the file
   was written. Measured before the fix: the same component rendered 505x422,
   505x794, 505x1066, 505x1200, 505x1898 and 445x1600 across the site, i.e. the
   raw pixel height of whatever file was dropped in. `/speed-training/ofallon`
   and `/speed-training/collinsville` were rendering a 505x1898 photograph.
   `height:auto` lets `aspect-ratio` apply; space is still reserved before load
   (width is known, ratio is declared) so the CLS work is untouched.
   Ratio is portrait in the two-column grid, where the figure sits beside a
   ~600-1000px text column, and 4/3 once the grid stacks. */
.mech__fig img{width:100%;height:auto;aspect-ratio:4/3;object-fit:cover;
  object-position:var(--focus,50% 50%);border-radius:var(--r)}
@media (min-width:901px){.mech__fig img{aspect-ratio:5/7}}
.mech__cap{margin-top:12px;font-family:var(--mono);font-size:var(--fs-micro);letter-spacing:.13em;
  text-transform:uppercase;color:var(--muted)}

/* ── Programs: hairline arrow rows (interactions.md §9a) ─────────── */
.progs__hd{text-align:center;max-width:820px;margin:0 auto var(--s4)}
.progs__hd .lede,.progs__hd>p:not(.kick),.faq__hd .lede,.faq__hd>p:not(.kick){
  max-width:var(--measure);margin-inline:auto}
.progs__hd h2{margin-top:12px}
.progs__hd p{margin-top:var(--s2)}
/* Wave 3 · interactions.md §9a to the letter. The row previously animated
   `padding-left` by 14px, which reflows the whole flex row on every hover and
   is four times the 2px the contract asks for. The shift now rides a transform
   on the LABEL (compositor only, no layout), and a low-alpha accent wash gives
   a 1200px-wide hit area somewhere for the eye to land. */
.rowlink{position:relative;display:flex;align-items:baseline;gap:var(--s3);
  padding:20px 4px;border-top:1px solid var(--line);background-color:transparent;
  transition:background-color var(--dur-hover) var(--ease-hover)}
.rowlink:last-of-type{border-bottom:1px solid var(--line)}
.rowlink::after{content:'';position:absolute;inset:auto 0 -1px 0;height:2px;background:var(--accent);
  transform:scaleX(0);transform-origin:left;
  transition:transform var(--dur-hover) var(--ease-hover)}
.rowlink__n{font-family:var(--mono);font-size:var(--fs-micro);font-weight:700;letter-spacing:.14em;
  color:var(--muted);min-width:3.2em;flex:0 0 auto;
  transition:color var(--dur-hover) var(--ease-hover)}
.rowlink__t{font-family:var(--display);font-weight:700;text-transform:uppercase;
  font-size:clamp(1.1rem,2.3vw,1.72rem);
  transition:color var(--dur-hover) var(--ease-hover),
             transform var(--dur-hover) var(--ease-hover)}
.rowlink__d{margin-left:auto;font-family:var(--mono);font-size:var(--fs-micro);letter-spacing:.11em;
  text-transform:uppercase;color:var(--muted);text-align:right;flex:0 0 auto}
.rowlink__a{font-family:var(--display);font-size:1.2rem;color:var(--accent);flex:0 0 auto;
  transition:transform var(--dur-hover) var(--ease-hover)}
@media (hover:hover){
  .rowlink:hover{background-color:var(--wash)}
  .rowlink:hover::after{transform:scaleX(1)}
  .rowlink:hover .rowlink__a{transform:translate(4px,-4px)}
  .rowlink:hover .rowlink__t{color:var(--accent);transform:translateX(2px)}
  .rowlink:hover .rowlink__n{color:var(--accent)}
}
.rowlink:focus-visible::after{transform:scaleX(1)}
.rowlink:focus-visible .rowlink__a{transform:translate(4px,-4px)}
.rowlink:focus-visible .rowlink__t{color:var(--accent);transform:translateX(2px)}
.rowlink:focus-visible .rowlink__n{color:var(--accent)}
@media (max-width:680px){.rowlink__d{display:none}.rowlink{gap:var(--s2)}}

/* ── Value cards ─────────────────────────────────────────────────── */
.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s2);margin-top:var(--s4)}
@media (max-width:880px){.cards{grid-template-columns:1fr}}
.card{position:relative;background:var(--surface-2);border:1px solid var(--line);
  border-radius:var(--r);padding:var(--s3);box-shadow:var(--shadow-rest);
  transition:translate var(--dur-hover) var(--ease-hover),
             border-color var(--dur-hover) var(--ease-hover),
             box-shadow var(--dur-hover) var(--ease-hover)}
.sec--alt .card{background:var(--bg)}
/* 3.2 asks for a gradient top-bar reveal. There is no gradient in this palette
   (one flat accent, by the lock), so the bar is a solid accent hairline that
   sweeps in from the left — the same scaleX gesture as the nav, the programme
   rows and the CTA. One site, one hover grammar. */
.card::before{content:'';position:absolute;inset:-1px -1px auto -1px;height:3px;
  background:var(--accent);transform:scaleX(0);transform-origin:left;
  transition:transform var(--dur-hover) var(--ease-hover)}
.card h3{margin:12px 0 10px;transition:color var(--dur-hover) var(--ease-hover)}
.card p{color:var(--muted);font-size:1rem}
@media (hover:hover){
  /* `translate`, not `transform` — see the reveal block's cascade note. */
  .card:hover{translate:0 var(--lift);border-color:var(--accent);
    box-shadow:var(--shadow-lift)}
  .card:hover::before{transform:scaleX(1)}
  .card:hover h3{color:var(--accent)}
}

/* ── Pinned-layer depth (P-009) ──────────────────────────────────── */
.layers{position:relative;z-index:1}
.layers-bg{position:sticky;top:0;height:100vh;height:100svh;overflow:hidden}
.layers-bg img{width:100%;height:100%;object-fit:cover;will-change:transform}
.layers-bg::after{content:'';position:absolute;inset:0;
  background:linear-gradient(180deg,rgba(0,0,0,.7),rgba(0,0,0,.45) 45%,rgba(0,0,0,.85))}
.layers-flow{margin-top:-100vh;margin-top:-100svh;position:relative;z-index:2}
.layer-panel{min-height:118vh;min-height:118svh;display:flex;align-items:center;
  padding-block:var(--s5);padding-inline:var(--pad)}
.layer-panel__in{max-width:640px;margin-inline:auto;text-align:center}
.layer-panel h2{margin-top:var(--s2)}
.layer-panel p{margin-top:var(--s3);color:#fff;text-shadow:0 1px 14px rgba(0,0,0,.85)}
@media (max-width:700px){.layer-panel{min-height:104vh;min-height:104svh}}

/* ── Pull-quote (P-004) ──────────────────────────────────────────── */
.quote{text-align:center}
.quote blockquote{max-width:17ch;margin:var(--s2) auto 0;font-family:var(--display);
  font-weight:500;text-transform:none;font-size:clamp(1.7rem,4.6vw,3.4rem);line-height:1.08}
.quote .attr{margin-top:var(--s3);font-family:var(--mono);font-size:var(--fs-micro);letter-spacing:.15em;
  text-transform:uppercase;color:var(--muted)}
.quote .stars{color:var(--accent);letter-spacing:.1em}
.quote .seeall{display:inline-flex;align-items:center;min-height:44px;margin-top:var(--s3);font-family:var(--mono);font-size:var(--fs-micro);
  font-weight:700;letter-spacing:.13em;text-transform:uppercase;color:var(--accent);
  border-bottom:2px solid var(--accent);padding-bottom:4px;
  transition:color var(--dur-hover) var(--ease-hover),
             border-color var(--dur-hover) var(--ease-hover),
             box-shadow var(--dur-hover) var(--ease-hover)}
@media (hover:hover){
  .quote .seeall:hover{color:var(--accent-2);border-bottom-color:var(--accent-2);
    box-shadow:0 6px 18px -12px rgba(255,242,1,.9)}
}

/* ── Coaches ─────────────────────────────────────────────────────── */
.coaches{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s2);margin-top:var(--s4)}
@media (max-width:900px){.coaches{grid-template-columns:repeat(2,1fr)}}
@media (max-width:540px){.coaches{grid-template-columns:1fr}}
/* 3.1's "inner image zoom" lands here — the coaches are the only photographs in
   a hoverable frame. `overflow:clip` on the figure keeps the scaled frame inside
   its own edges (clip, not hidden, per the P-009 sitewide rule). */
.coach{overflow:clip}
/* Wave 6 · same root cause as `.mech__fig img`: the height content attribute was
   winning, so `aspect-ratio:16/10` never applied and every headshot rendered at a
   flat 422px tall (0.897 at 1440, 0.855 at 820, 0.917 at 430 — a different ratio
   per breakpoint). 9/10 is what the grid was actually rendering at desktop and
   mobile, so this makes the declared ratio the real one without moving the
   headshots. A 16/10 headshot would have been a letterbox across all six faces. */
.coach img{width:100%;height:auto;aspect-ratio:9/10;object-fit:cover;object-position:center 22%;
  border-radius:var(--r);filter:grayscale(1) contrast(1.06);
  transition:filter var(--dur-hover) var(--ease-hover),
             transform var(--dur-hover) var(--ease-hover)}
@media (hover:hover){.coach:hover img{filter:none;transform:scale(1.045)}}
.coach b{display:block;font-family:var(--display);font-size:1.15rem;text-transform:uppercase;
  margin-top:14px}
.coach span{font-family:var(--mono);font-size:var(--fs-micro);letter-spacing:.14em;text-transform:uppercase;
  color:var(--muted)}

/* ── FAQ (§7b centered column) ───────────────────────────────────── */
.faq__hd{text-align:center;max-width:780px;margin:0 auto var(--s4)}
.faq__list{max-width:780px;margin-inline:auto}
.faq details{border-top:1px solid var(--line)}
.faq details:last-of-type{border-bottom:1px solid var(--line)}
.faq summary{list-style:none;cursor:pointer;padding:22px 44px 22px 0;position:relative;
  font-family:var(--display);font-weight:500;text-transform:uppercase;
  font-size:clamp(1rem,1.7vw,1.22rem);line-height:1.2}
.faq summary::-webkit-details-marker{display:none}
/* +/− toggle (Wave 2 · 2.4). CSS-drawn cross, not a glyph and not the default
   <details> triangle: two accent bars painted as background-images, the vertical
   one collapsing to zero height on open so + morphs into −.
   DIRECTION OVERRIDE: the checklist asks for a CIRCULAR toggle; the locked
   direction is 0px radius on every surface, so the toggle is a SQUARE accent
   well. Softening it here would be the one round shape on the site. */
.faq summary::after{
  content:'';position:absolute;right:0;top:50%;width:30px;height:30px;
  transform:translateY(-50%) scale(1);border:1px solid var(--line-2);
  background-color:transparent;box-shadow:0 0 0 0 rgba(255,242,1,0);
  background-image:linear-gradient(var(--accent),var(--accent)),
                   linear-gradient(var(--accent),var(--accent));
  background-size:12px 2px,2px 12px;background-position:center;background-repeat:no-repeat;
  transition:background-size var(--dur-hover) var(--ease-hover),
             border-color var(--dur-hover) var(--ease-hover),
             background-color var(--dur-hover) var(--ease-hover),
             box-shadow var(--dur-hover) var(--ease-hover),
             transform var(--dur-hover) var(--ease-hover);
}
.faq details[open] summary::after{background-size:12px 2px,2px 0;border-color:var(--accent)}
/* Wave 2 handed the well over with a placeholder hover (border + a 9% wash) and
   asked Wave 3 to finish it. Three genuinely distinct states, and none of them
   softens the square:
     rest  — hairline border, accent cross, no fill
     hover — accent border, 16% accent fill, a 12px accent bloom, scale 1.06
     focus — accent border + a 10% fill, NO bloom and NO scale, so the keyboard
             state never masquerades as the pointer state
   The well is not filled SOLID on hover: the bars are painted as background
   images, `background-image` does not interpolate, and flipping them to black
   would snap mid-transition. A wash plus a bloom animates cleanly and keeps the
   toggle legible at every frame. */
@media (hover:hover){
  .faq summary:hover{color:var(--accent)}
  .faq summary:hover::after{border-color:var(--accent);background-color:var(--wash-2);
    box-shadow:0 0 12px 0 rgba(255,242,1,.35);transform:translateY(-50%) scale(1.06)}
}
.faq summary:focus-visible{color:var(--accent)}
.faq summary:focus-visible::after{border-color:var(--accent);background-color:rgba(255,242,1,.10)}
@media (prefers-reduced-motion:reduce){.faq summary::after{transition:none}}
.faq summary{transition:color var(--dur-hover) var(--ease-hover)}
.faq .a{padding:0 0 24px;color:var(--muted);max-width:var(--measure)}

/* ── Closer ──────────────────────────────────────────────────────── */
.closer{text-align:center}
.closer h2{margin-top:var(--s2)}
.closer .lede{margin:var(--s3) auto 0;max-width:58ch}
.closer .btn__row{justify-content:center;margin-top:var(--s4)}
@media (max-width:560px){.closer .btn__row{display:grid;grid-template-columns:minmax(0,max-content);
  gap:12px;justify-content:center}}
.closer__meta{display:flex;flex-wrap:wrap;gap:10px 40px;justify-content:center;
  margin-top:var(--s4);padding-top:var(--s3);border-top:1px solid var(--line)}
.closer__meta span{font-family:var(--mono);font-size:var(--fs-micro);letter-spacing:.14em;
  text-transform:uppercase;color:var(--muted)}
.teamdoor{margin-top:var(--s4);padding:var(--s3);border:1px solid var(--line);
  display:flex;flex-wrap:wrap;gap:var(--s2) var(--s3);align-items:center;justify-content:center;
  transition:border-color var(--dur-hover) var(--ease-hover),
             background-color var(--dur-hover) var(--ease-hover)}
.teamdoor p{font-family:var(--mono);font-size:var(--fs-micro);letter-spacing:.1em;text-transform:uppercase;
  color:var(--muted)}
.teamdoor a{display:inline-flex;align-items:center;min-height:44px;font-family:var(--mono);font-size:var(--fs-micro);font-weight:700;letter-spacing:.12em;
  text-transform:uppercase;color:var(--accent);border-bottom:2px solid var(--accent);padding-bottom:3px;
  transition:transform var(--dur-hover) var(--ease-hover),
             color var(--dur-hover) var(--ease-hover)}
@media (hover:hover){
  .teamdoor:hover{border-color:var(--line-2);background-color:var(--wash)}
  .teamdoor a:hover{color:var(--accent-2);transform:translateX(3px)}
}
.teamdoor:focus-within{border-color:var(--line-2);background-color:var(--wash)}

/* ── Footer ──────────────────────────────────────────────────────── */
.foot{background:var(--surface);padding-block:var(--s4);border-top:1px solid var(--line)}
/* Wave 2 handed over a composition problem: a 1.4fr brand column holding only a
   mark and a two-line blurb, leaving roughly 300px of dead space at 1440 beside
   two six-row nav columns. Fixed by giving the column real content (the NAP
   block that was previously buried as two list items) and by adding a fourth
   column for the six city pages, which had no footer presence at all — so the
   fix also buys every page six more internal links. */
.foot__grid{display:grid;grid-template-columns:1.5fr 1fr 1fr 1fr;gap:var(--s4) var(--s3)}
@media (max-width:1080px){.foot__grid{grid-template-columns:1.2fr 1fr 1fr}}
@media (max-width:820px){.foot__grid{grid-template-columns:1fr 1fr;gap:var(--s3)}}
/* On a phone, four stacked single-file columns ran the footer to 1412px — a
   screen and a half of nothing but links. The brand block takes the full width,
   the two short nav columns pair up, and the six city links set as two columns
   in a full-width row. Same content, ~600px shorter, nothing orphaned. */
@media (max-width:520px){
  .foot__grid{grid-template-columns:1fr 1fr;gap:var(--s3) var(--s2)}
  .foot__grid>:first-child,.foot__grid>:last-child{grid-column:1 / -1}
  .foot__grid>:last-child ul{columns:2;column-gap:var(--s2)}
}
.foot img.mark{height:38px;width:auto}
.foot__blurb{margin-top:var(--s2);color:var(--muted);font-size:1rem;max-width:34ch}
.foot__nap{margin:var(--s3) 0 0;display:grid;gap:12px}
.foot__nap div{display:grid;gap:2px}
.foot__nap dt{font-family:var(--mono);font-size:var(--fs-micro);font-weight:700;letter-spacing:.16em;
  text-transform:uppercase;color:var(--accent)}
.foot__nap dd{margin:0;color:var(--muted);font-size:.92rem;line-height:1.5}
/* §7 — the NAP links are the highest-intent taps in the footer, so they clear
   44px. Done with `min-height` on an inline-flex the box centres its text and
   the one-line rows (Phone, Email) pick up ~11px of leading that the two-line
   rows (Address, Hours) do not, so the four label/value pairs sat on four
   different rhythms. Padding plus an equal negative margin buys the same 44px
   hit area while leaving every value exactly where the type wants it. */
.foot__nap dd a{display:inline-block;vertical-align:top;
  padding-block:11px;margin-block:-11px;font-size:.92rem}
/* Wave 6 · §3 — these were `<h4>` sitting after the last content `<h2>`, which
   made every one of the 31 pages skip a heading level (H2 -> H4). They are now
   `<h3>` and the type treatment is unchanged. */
.foot h3{font-family:var(--mono);font-size:var(--fs-micro);font-weight:700;letter-spacing:.16em;
  text-transform:uppercase;color:var(--accent);margin-bottom:var(--s2)}
.foot ul{list-style:none;margin:0;padding:0}
.foot li{margin-bottom:2px}
/* §7 — every footer link clears a 44px tap target */
.foot li a{display:inline-flex;align-items:center;min-height:44px;min-width:44px;padding-block:4px}
.foot a{font-size:.92rem;color:var(--muted);transition:color var(--dur-micro)}
.foot__bot a{display:inline-flex;align-items:center;min-height:44px;padding-inline:2px}
@media (hover:hover){.foot a:hover{color:var(--accent)}}
.foot__bot{margin-top:var(--s4);padding-top:var(--s3);border-top:1px solid var(--line);
  display:flex;flex-wrap:wrap;gap:12px 24px;justify-content:space-between;align-items:center}
.foot__bot p{font-size:var(--fs-micro);color:var(--muted)}
/* Wave 6 · §4d — the visible "Last updated" that has to match the page's own
   schema `dateModified`. Mono so it reads as a stamp, not as prose. */
.foot__upd{font-family:var(--mono);font-size:var(--fs-micro);letter-spacing:.1em;text-transform:uppercase}
/* §12 — the agency name pops. Yellow on the dark footer passes AA comfortably. */
.foot__bot .lc{color:var(--accent);font-weight:700}
@media (hover:hover){.foot__bot .lc:hover{color:var(--accent-2)}}
@media (max-width:720px){
  /* §7a — footer's own dark clearance so the sticky dock never covers the credit.
     Wave 6 raised this from 92px: the §4d "Last updated" stamp added a third line
     to `.foot__bot`, which cut the measured gap between the LocalCraft credit and
     the top of the dock from ~46px to 14px at 430px. 124px restores it. */
  .foot{padding-bottom:calc(124px + env(safe-area-inset-bottom))}
}

/* ── Mobile sticky CTA bar (§7a: hidden on load, reveals on scroll) ──
   Carries both class names: .callbar is the §7a pattern name every LocalCraft build
   uses; .sticky-cta is the polish-wave checklist's canonical token (Wave 1 · 1.7). */
.callbar,.sticky-cta{
  position:fixed;left:0;right:0;bottom:0;z-index:58;display:none;gap:10px;
  background:var(--surface);border-top:1px solid var(--line);
  padding:12px 16px;padding-bottom:max(14px,env(safe-area-inset-bottom));
  box-shadow:0 48px 0 0 var(--surface);              /* safe-area colour bleed */
  transform:translateY(calc(100% + 48px + env(safe-area-inset-bottom,0px)));
  /* was a raw .3s, copied from the §7a reference snippet. This is a hover-speed
     UI slide, so it rides the contract's own token like everything else. */
  transition:transform var(--dur-hover) var(--ease-hover);
}
.callbar.is-visible{transform:translateY(0)}
.callbar .btn{flex:1;padding:14px 12px;min-height:50px}
@media (max-width:720px){.callbar{display:flex}}
.hdr.is-open ~ .callbar{transform:translateY(120%)}

/* ── Press states: the touch surface's answer to hover ───────────────
   Every `:hover` in this stylesheet is correctly gated behind
   `@media (hover: hover)` per §7c #4 — which means the phone, where most of
   this traffic lands and which §15 calls the privileged surface, was getting
   NONE of this wave's interaction layer. A thumb does not hover.
   `:active` fires on touch-down, clears on release, needs no media gate, and is
   what makes a tap feel answered. Everything here runs on `--dur-micro`, the
   contract's press token, so the response is immediate rather than eased.
   The default iOS tap flash is suppressed only where a real press state
   replaces it — never bare. */
a,button,summary{-webkit-tap-highlight-color:transparent}
.btn:active{translate:0 0;scale:.99;transition-duration:var(--dur-micro)}
.rowlink:active{background-color:var(--wash-2);transition-duration:var(--dur-micro)}
.rowlink:active::after{transform:scaleX(1)}
.rowlink:active .rowlink__t{color:var(--accent);transform:translateX(2px)}
.rowlink:active .rowlink__n{color:var(--accent)}
.related a:active{border-color:var(--accent);transition-duration:var(--dur-micro)}
.related a:active::before{transform:scaleX(1)}
.related a:active b{color:var(--accent)}
.drawer a:not(.btn):active{color:var(--accent);border-bottom-color:var(--accent)}
.faq summary:active{color:var(--accent)}
.faq summary:active::after{border-color:var(--accent);background-color:var(--wash-2)}
.foot a:active,.crumb a:active{color:var(--accent)}
/* These two already sit AT the accent when idle, so an accent press state would
   be a no-op — they press to the deeper accent instead, matching their hover. */
.quote .seeall:active{color:var(--accent-2);border-bottom-color:var(--accent-2)}
.teamdoor a:active{color:var(--accent-2);border-bottom-color:var(--accent-2);transform:translateX(3px)}
.prose a:not(.btn):active{border-bottom-color:var(--accent);color:var(--accent-2)}
.hdr__logo:active{opacity:.78}
.burger:active span,.burger:active span::before,.burger:active span::after{background:var(--accent)}

/* ── Motion: underline draw (interactions.md §9e / design.md move #3) ──
   One hand-drawn accent underline per page, stroke drawn left to right over
   --dur-reveal on --ease-settle when it enters view. It sits under the closing
   CTA headline, which is the one moment on every page where the reader is being
   asked for something. The path is irregular on purpose — design.md asks for a
   hand-drawn mark, not a rule.
   No-JS and reduced-motion both resolve to the FINISHED stroke: the dash offset
   only exists inside `html.js-motion`, which JS adds and reduced-motion never
   gets, so the fallback is a drawn line, never a missing one. */
.uline{display:block;width:min(100%,360px);height:14px;margin:14px 0 0;overflow:visible}
.progs__hd .uline,.faq__hd .uline,.closer .uline{margin-inline:auto}
.uline path{fill:none;stroke:var(--accent);stroke-width:3.5;stroke-linecap:round;
  stroke-linejoin:round}
/* 380 > the path's ~356 user units, so offset 0 is a fully solid stroke and
   offset 380 is nothing at all — no dash gap can appear mid-draw. */
html.js-motion .uline path{stroke-dasharray:380;stroke-dashoffset:380;
  transition:stroke-dashoffset var(--dur-reveal) var(--ease-settle)}
html.js-motion .uline.is-drawn path{stroke-dashoffset:0}

/* ── Motion: reveal (P-008) ──────────────────────────────────────────
   THE CASCADE BUG WAVE 3 FOUND, and it is the reason this block carries six
   more transition channels than P-008 needs.

   `html.js-motion .reveal.is-visible{transform:none}` is (0,3,1). A component's
   own hover rule — `.card:hover{transform:translateY(-8px)}` — is (0,2,0). The
   reveal rule wins, so the card lift SILENTLY DID NOTHING. Measured in a real
   browser: hovering `.card` and `.price` produced `transform: none → none`.
   50 of 51 cards and 24 of 24 pricing cards carry `.reveal`, so the "cards lift
   on hover" move has been inert on essentially every card since Wave 1, and no
   source-reading audit would have caught it because the CSS is right there.

   The same shorthand also wiped each component's `transition` declaration, so
   the border and shadow changes that DID apply were snapping with no easing.

   Two-part fix, both structural rather than a specificity arms race:
   1. Lifts move to the independent `translate` property. It composes with
      `transform` instead of competing for the same declaration, so the reveal
      can own `transform` forever and a component can still lift.
   2. The hover channels are declared here, at hover speed, because this rule
      outranks every component transition on any element that is also a reveal
      target. */
html.js-motion .reveal{opacity:0;transform:translateY(32px);
  transition:opacity var(--dur-reveal) var(--ease-settle) var(--rd,0s),
             transform var(--dur-reveal) var(--ease-settle) var(--rd,0s),
             translate var(--dur-hover) var(--ease-hover),
             scale var(--dur-hover) var(--ease-hover),
             border-color var(--dur-hover) var(--ease-hover),
             box-shadow var(--dur-hover) var(--ease-hover),
             background-color var(--dur-hover) var(--ease-hover),
             color var(--dur-hover) var(--ease-hover)}
html.js-motion .reveal.is-visible{opacity:1;transform:none}

/* ── Motion: hero entrance (P-001) ───────────────────────────────── */
/* interactions.md §3 stakes the whole sequence on ONE hard criterion: "total runtime
   under 1.8s … so it reads as intentional, not as loading." Its own delay table
   (…1.60s) plus the 0.9s resolve ran to 2.50s, which broke that criterion and left the
   CTA unpainted for 1.35s on the surface where 70% of this traffic lands. The seven
   beats and their order are preserved; the cadence is compressed so the last one
   finishes at 1.78s. */
@keyframes es-resolve{
  from{opacity:0;filter:blur(10px);transform:translateY(14px) scale(.985)}
  to{opacity:1;filter:blur(0);transform:none}
}
.hr{opacity:0;animation:es-resolve .72s var(--ease-out) both}
.hr[data-d="1"]{animation-delay:.06s}.hr[data-d="2"]{animation-delay:.22s}
.hr[data-d="3"]{animation-delay:.40s}.hr[data-d="4"]{animation-delay:.60s}
.hr[data-d="5"]{animation-delay:.78s}.hr[data-d="6"]{animation-delay:.92s}
.hr[data-d="7"]{animation-delay:1.06s}
/* Frozen-frame guard (interactions.md §10): once the sequence is spent, JS stamps
   html.hero-in and the hero is pinned to its end state. A stuck blur is a build failure. */
html.hero-in .hr{opacity:1;animation:none;filter:none;transform:none}
/* Per-beat pin (Step 3.7). JS adds .hr-done on each element's own animationend.
   Visually a no-op — the element is already sitting on the keyframe's `to` state —
   but it releases the element from its animation the instant that beat is spent
   instead of at the 1900ms global stamp. Chrome will not count text as a contentful
   paint while its element still carries an entrance animation that began at
   opacity 0, so this is what lets the hero's largest text register as LCP on its
   own schedule. Measured: mobile LCP 2.7s -> 2.2s, nothing about the motion changed. */
.hr.hr-done{opacity:1;animation:none;filter:none;transform:none}

@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  .hr{opacity:1;animation:none;filter:none;transform:none}
  .hero__media{inset:0}
  .layers-bg img{transform:none!important}
  *,*::before,*::after{animation-duration:.01ms!important;animation-iteration-count:1!important;
    transition-duration:.01ms!important}
}

/* ═══════════════════════════════════════════════════════════════════
   SHARED INTERIOR-PAGE SYSTEM
   Inherited from the homepage patterns captured at Step 1.
   Page builders use THESE classes; nobody edits this file per-page.
   ═══════════════════════════════════════════════════════════════════ */

/* ── Sub-hero (every interior page) ──────────────────────────────── */
.subhero{position:relative;padding:calc(var(--nav-h) + var(--s5)) 0 var(--s5);
  background:var(--bg);overflow:clip;isolation:isolate}
.subhero--media{min-height:min(72vh,640px);min-height:min(72svh,640px);display:flex;align-items:flex-end}
.subhero__media{position:absolute;inset:-4% 0;z-index:0}
/* Wave 4 — the subhero box is ~2.1:1 at 1440 and ~0.67:1 at 430, so a single
   photograph is cropped in opposite directions at the two breakpoints. A
   subject that is not centred survives one and is lost in the other. `--focus`
   sets `object-position` per image so the art direction holds on both.
   Measured per page, not guessed; see POLISH-LOG Wave 4. */
.subhero__media img{width:100%;height:100%;object-fit:cover;
  object-position:var(--focus,50% 50%)}
/* §7 / P-002 — the same defect the homepage hero had, on all 23 media
   subheroes. Measured before this change (worst pixel across 10 sample pages,
   1440 and 430): H1 accent word 2.66:1 against a 3:1 floor, breadcrumb 1.58:1
   against 4.5:1, lede 3.33:1 against 4.5:1. The old scrim ran a .42 alpha
   through the middle of the frame, and these photographs are bright turf.
   Same remedy as the hero: a left-anchored scrim under the copy plus a heavier
   mid-frame, so the type side is grounded and the right of the photo stays. */
.subhero__scrim{position:absolute;inset:0;z-index:1;background:
  linear-gradient(90deg,rgba(0,0,0,.42) 0%,rgba(0,0,0,.36) 46%,
                        rgba(0,0,0,.20) 68%,rgba(0,0,0,0) 88%),
  linear-gradient(180deg,rgba(0,0,0,.86) 0%,rgba(0,0,0,.48) 34%,rgba(0,0,0,.62) 62%,
                        rgba(0,0,0,.92) 100%)}
.subhero__in{position:relative;z-index:2;width:100%}
/* Wave 2 · 2.9 — 17ch forced a 3-word position-page H1 onto two lines and left
   the last word alone. 20ch lets `Wide receiver training` set on one line at
   desktop and gives balance a second option on the city-page H1s. */
.subhero h1{font-size:clamp(2.4rem,6.4vw,5rem);margin-top:14px;max-width:20ch}
/* At 430px the 2.4rem floor left `Edwardsville athletes` 6px wider than the
   measure once the pair was bound, so the final `s` clipped. 2.3rem clears it
   with 11px to spare and holds the h1 > h2 step (36.8px vs 32px). */
@media (max-width:480px){.subhero h1{font-size:2.3rem}}
.subhero .lede{margin-top:var(--s3);max-width:56ch}
.subhero--media .lede{color:#EDEDED;text-shadow:0 1px 12px rgba(0,0,0,.8)}
.subhero--media .kick{text-shadow:0 1px 10px rgba(0,0,0,.8)}
/* The breadcrumb is 10.5px type in --muted (#A8A8A8). Over a photograph that
   colour cannot reach 4.5:1 at any survivable scrim weight — measured 1.58:1 on
   /membership at 430. Over MEDIA subheroes only, it lightens; on the plain dark
   subheroes it stays muted, where it already clears AA against #000. */
.subhero--media .crumb,.subhero--media .crumb a{color:#EFEFEF;
  text-shadow:0 1px 10px rgba(0,0,0,.85)}
.subhero .btn__row{margin-top:var(--s3)}
@media (max-width:560px){.subhero .btn__row{display:grid;grid-template-columns:minmax(0,max-content);gap:12px}}

/* ── Utility pages: lead with the action (operator standard) ──────────
   `/free-trial` and `/contact` are not marketing pages, they are the doing
   surface. Wave 5 put `body.pg--utility` on both and left the hook unused;
   Wave 6 handed it forward. The rule is: no decorative hero, the action rides
   high, the form is first on mobile, the secondary CTA is a slim chip.

   Measured on /free-trial at 430px before this block: the type-only hero stood
   541px tall and pushed the first field to y=1009, a full screen of scrolling
   before the visitor could type anything. */
.pg--utility .subhero{padding:calc(var(--nav-h) + var(--s3)) 0 var(--s3)}
.pg--utility .subhero h1{font-size:clamp(2.1rem,4.6vw,3.4rem);max-width:24ch}
.pg--utility .subhero .lede{margin-top:var(--s2);max-width:60ch}
.pg--utility .subhero .btn__row{margin-top:var(--s2)}
@media (max-width:720px){
  .pg--utility .subhero{padding:calc(var(--nav-h) + 20px) 0 20px}
  .pg--utility .subhero h1{font-size:2.05rem}
  /* the action section that follows the hero loses its own top padding, so the
     hero and the form read as one block rather than two stacked screens */
  .pg--utility .subhero + .sec{padding-top:var(--s3)}
}
/* Wave 8 · 8.8 — the dead right half, resolved rather than handed on again.
   Wave 7 measured `/free-trial` and `/contact` opening on a type-only hero whose
   right ~600px at 1440 was empty black, and named it a standoff between §8.8
   (no dead space wider than 200px) and the utility-page rule (no decorative
   hero). Both hold at once if the space carries the ACTION instead of a
   photograph: headline column left, the call chip and the three published trust
   facts right. It is the same move Wave 1 made on the home hero (`.hero__act`),
   so the site keeps one grammar, and it adds no image, no colour and no
   component. Measured after: the empty right rail on both pages goes from
   ~600px to 0, and the hero gets shorter, which pulls the form higher still. */
@media (min-width:1000px){
  .pg--utility .subhero__in{
    display:grid;
    grid-template-columns:minmax(0,1fr) minmax(300px,.6fr);
    column-gap:var(--s4);
    align-items:start;
  }
  .pg--utility .subhero .crumb{grid-column:1;grid-row:1}
  .pg--utility .subhero h1{grid-column:1;grid-row:2}
  .pg--utility .subhero .lede{grid-column:1;grid-row:3;margin-bottom:0}
  .pg--utility .subhero .btn__row{grid-column:2;grid-row:2;align-self:end;margin-top:0}
  .pg--utility .subhero .proof{grid-column:2;grid-row:3;max-width:none;margin-top:var(--s2)}
}
/* Slim chip: the secondary affordance on a utility page is a link with a box
   around it, not a second 52px slab competing with the primary action. */
.btn--chip{min-height:44px;padding:11px 16px;font-size:var(--fs-micro);letter-spacing:.11em;
  box-shadow:none}
@media (hover:hover){.btn--chip:hover{scale:1.015;translate:0 -1px;box-shadow:none}}

/* ── Proof strip (§7.4: a trust signal beside every CTA) ──────────────
   Wave 7 measured 70 of 194 in-body CTAs with no trust signal anywhere in
   their section, including the FIRST CTA above the fold on twenty interior
   pages. The home hero had the `.rail`; nothing else on the site had anything.
   One line, three published facts, no new colour and no new font. */
.proof{display:flex;flex-wrap:wrap;align-items:center;gap:6px 16px;margin-top:var(--s2);
  font-family:var(--mono);font-size:var(--fs-micro);letter-spacing:.08em;text-transform:uppercase;
  color:var(--muted);max-width:52ch}
.proof span{display:inline-flex;align-items:center;gap:8px;white-space:nowrap}
.proof span::before{content:'';width:5px;height:5px;background:var(--accent);flex:none}
.proof b{color:var(--ink);font-weight:700}
/* Over a photograph the muted grey cannot hold 4.5:1 at any survivable scrim
   weight, the same finding as the breadcrumb in §7/P-002 above. */
.subhero--media .proof{color:#E8E8E8;text-shadow:0 1px 10px rgba(0,0,0,.85)}
.closer .proof{justify-content:center;margin-inline:auto}
@media (max-width:560px){
  .proof{gap:6px 14px;font-size:var(--fs-micro)}
  .closer .proof{justify-content:flex-start}
}

/* ── Breadcrumb ──────────────────────────────────────────────────── */
.crumb{font-family:var(--mono);font-size:var(--fs-micro);letter-spacing:.13em;text-transform:uppercase;
  color:var(--muted)}
/* Wave 8 · 8.4. A breadcrumb link measured 31×16px — a real navigation control
   at a third of the 44px floor. `padding-block` with an equal negative margin
   buys the hit area without moving a single glyph or changing the row's height
   (the same trick Wave 3 used on the footer NAP rows). */
.crumb a{color:var(--muted);display:inline-block;min-width:44px;padding-block:11px;margin-block:-11px;
  touch-action:manipulation}
.crumb span{color:var(--accent)}
@media (hover:hover){.crumb a:hover{color:var(--accent)}}

/* ── Two-column prose + aside (service pages) ────────────────────── */
.svc{display:grid;grid-template-columns:1.15fr .85fr;gap:var(--s5);align-items:start}
@media (max-width:940px){.svc{grid-template-columns:1fr;gap:var(--s4)}}
.prose{max-width:var(--measure)}
.prose>*+*{margin-top:var(--s3)}
.prose h2{margin-top:var(--s4)}
.prose h3{margin-top:var(--s3)}
.prose p{color:var(--muted)}
.prose strong{color:var(--ink);font-weight:700}
.prose ul{list-style:none;padding:0;margin-top:var(--s2)}
.prose ul li{position:relative;padding-left:26px;color:var(--muted);margin-top:12px}
.prose ul li::before{content:'';position:absolute;left:0;top:.62em;width:12px;height:2px;
  background:var(--accent)}
.prose a:not(.btn){color:var(--accent);border-bottom:1px solid rgba(255,242,1,.4)}
@media (hover:hover){.prose a:not(.btn):hover{border-bottom-color:var(--accent)}}

/* ── Aside card (sticky booking / facts panel) ───────────────────── */
.aside{position:sticky;top:calc(var(--nav-h) + 20px);background:var(--surface);
  border:1px solid var(--line);padding:var(--s3);box-shadow:var(--shadow-rest);
  transition:border-color var(--dur-hover) var(--ease-hover),
             box-shadow var(--dur-hover) var(--ease-hover)}
.aside:focus-within{border-color:var(--accent);box-shadow:var(--shadow-lift)}
.aside h3{font-size:1.15rem;margin-bottom:var(--s2)}
.aside dl{margin:0;display:grid;grid-template-columns:auto 1fr;gap:10px var(--s2);
  font-family:var(--mono);font-size:var(--fs-micro);letter-spacing:.06em}
.aside dt{color:var(--muted);text-transform:uppercase}
.aside dd{margin:0;color:var(--ink)}
/* Wave 8 · 8.4. The aside's phone, email, map and reviews links are the
   conversion path on 27 pages and every one of them measured 17px tall.
   Same padding-block / negative-margin pair as the breadcrumb: 44px of hit
   area, zero movement in the grid. */
.aside dd a{display:inline-block;padding-block:13px;margin-block:-13px;
  touch-action:manipulation}
.aside .btn{width:100%;margin-top:var(--s3)}
@media (max-width:940px){.aside{position:static}}

/* ── Answer capsule (AEO, §3) ────────────────────────────────────── */
.capsule{border-left:3px solid var(--accent);padding-left:var(--s3);
  font-size:clamp(1.05rem,1rem + .3vw,1.18rem);color:var(--ink)}
/* §4c — the sourced fact, sitting under the answer capsule it supports. Reads as
   a footnote, not as a second lede: one step down in size, muted, and the only
   underlined links on the site so an outbound citation is visually distinct
   from every internal link. */
.src{margin-top:var(--s2);font-size:.9375rem;color:var(--muted);max-width:var(--measure)}
.src a{color:var(--ink);text-decoration:underline;text-underline-offset:3px;
  text-decoration-color:var(--accent);text-decoration-thickness:1px}
@media (hover:hover){.src a:hover{color:var(--accent)}}

/* ── Pricing ─────────────────────────────────────────────────────── */
.prices{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s2);margin-top:var(--s4)}
@media (max-width:940px){.prices{grid-template-columns:1fr}}
.price{background:var(--surface-2);border:1px solid var(--line);padding:var(--s3);
  display:flex;flex-direction:column;box-shadow:var(--shadow-rest);
  transition:translate var(--dur-hover) var(--ease-hover),
             border-color var(--dur-hover) var(--ease-hover),
             box-shadow var(--dur-hover) var(--ease-hover)}
.price--feature{border-color:var(--accent)}
/* The pricing card is not itself a link, but it OWNS a CTA. `:focus-within`
   lifts the whole card when the tab key reaches that button, so a keyboard user
   can see which plan they are about to buy — the state a mouse gets for free. */
@media (hover:hover){
  .price:hover{translate:0 -6px;border-color:var(--accent);box-shadow:var(--shadow-lift)}
}
.price:focus-within{translate:0 -6px;border-color:var(--accent);box-shadow:var(--shadow-lift)}
.price__tag{font-family:var(--display);font-weight:700;font-size:2.5rem;color:var(--accent);
  line-height:1;margin:12px 0 4px}
.price__per{font-family:var(--mono);font-size:var(--fs-micro);letter-spacing:.14em;text-transform:uppercase;
  color:var(--muted)}
.price h3{margin-bottom:6px}
.price p{color:var(--muted);font-size:1rem;margin-top:var(--s2)}
/* Cascade fix (Wave 2 · hierarchy): `.price p` (0,1,1) sat after and outranked
   `.price__tag` (0,1,0), so the PRICE — the single most important fact on a
   pricing card — was rendering muted grey at body size instead of 40px accent,
   and the mono cadence label was rendering at body size too. */
.price p.price__tag{color:var(--accent);font-size:2.5rem;margin-top:var(--s2)}
.price p.price__per{color:var(--muted);font-size:var(--fs-micro);margin-top:8px}
.price .btn{margin-top:auto;margin-top:var(--s3);width:100%}
.price__note{margin-top:var(--s3);font-family:var(--mono);font-size:var(--fs-micro);letter-spacing:.1em;
  text-transform:uppercase;color:var(--muted);text-align:center}

/* ── Roster / staff grid (7v7 age brackets, coach lists) ─────────── */
.roster{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s2);margin-top:var(--s3)}
@media (max-width:880px){.roster{grid-template-columns:1fr}}
.roster__col{background:var(--surface-2);border:1px solid var(--line);padding:var(--s3)}
.roster__col h3{color:var(--accent);margin-bottom:var(--s2)}
.roster__col ul{list-style:none;margin:0;padding:0}
.roster__col li{padding:10px 0;border-top:1px solid var(--line);font-size:1rem}
.roster__col li:first-child{border-top:0}
.roster__col li span{display:block;font-family:var(--mono);font-size:var(--fs-micro);letter-spacing:.13em;
  text-transform:uppercase;color:var(--muted);margin-top:3px}

/* ── Steps / numbered process ────────────────────────────────────── */
.steps{counter-reset:s;display:grid;gap:var(--s2);margin-top:var(--s4)}
.step{display:grid;grid-template-columns:auto minmax(0,1fr);gap:6px var(--s3);align-items:start;
  padding:var(--s3) 0;border-top:1px solid var(--line)}
.step:last-child{border-bottom:1px solid var(--line)}
/* The three grid items are ::before, h3, p. Left to auto-placement the paragraph
   landed in the NUMBER column and squeezed every step heading into an ~88px
   ribbon that set one word per line and ran off the right edge (caught in the
   Wave 2 orphan scan, visible on all 20 pages that use `.steps`). Placement is
   explicit now: number in column 1, heading and body stacked in column 2. */
.step::before{counter-increment:s;content:counter(s,decimal-leading-zero);
  grid-column:1;grid-row:1 / span 2;padding-top:6px;
  font-family:var(--mono);font-weight:700;font-size:var(--fs-micro);letter-spacing:.14em;color:var(--accent)}
/* Two markup variants shipped from page-build: some `.step`s wrap heading+body
   in a div, some do not. `display:contents` flattens the wrapper so both feed
   the same grid. */
.step>div{display:contents}
.step h3{grid-column:2;grid-row:1;margin-bottom:2px}
.step p{grid-column:2;grid-row:2;color:var(--muted);font-size:1rem;max-width:var(--measure)}
/* Full-width step lists read as an editorial three-column rule at desktop:
   number · step name · body. Stacked, a measure-capped paragraph left a dead
   right half of the section. Scoped to `.wrap > .steps` on purpose — the same
   component also runs inside the 612px `.prose` column on service pages, where
   three columns would crush the body to ~250px. */
@media (min-width:980px){
  .wrap>.steps>.step{grid-template-columns:auto minmax(0,22ch) minmax(0,1fr);
    gap:0 var(--s4);align-items:start}
  .wrap>.steps>.step h3{grid-column:2;grid-row:1;margin-bottom:0}
  .wrap>.steps>.step p{grid-column:3;grid-row:1}
  .wrap>.steps>.step::before{grid-row:1;padding-top:8px}
}

/* ── Forms (§9b/§9c compliant shells) ────────────────────────────── */
.form{display:grid;gap:var(--s2);max-width:560px}
/* §7.1 gradient top accent bar. A grid item rather than an absolute overlay, so
   it inherits the grid gap and cannot cover a field. */
.form::before{content:'';height:3px;
  background:linear-gradient(90deg,var(--accent) 0%,var(--accent-2) 62%,transparent 100%)}
.form .row2{display:grid;grid-template-columns:1fr 1fr;gap:var(--s2)}
@media (max-width:560px){.form .row2{grid-template-columns:1fr}}
/* §7.1 expectation-setter: three short lines telling the visitor exactly what
   happens after they send it. Sits above the first field, under the accent bar. */
.form__expect{list-style:none;display:grid;gap:6px;margin:0;padding:0}
.form__expect li{position:relative;padding-left:20px;font-size:.9375rem;color:var(--muted)}
.form__expect li::before{content:'';position:absolute;left:0;top:.62em;
  width:8px;height:2px;background:var(--accent)}
.form__status{font-family:var(--mono);font-size:var(--fs-micro);letter-spacing:.06em;
  color:var(--accent);min-height:0}
.form__status:empty{display:none}
.field label{display:block;font-family:var(--mono);font-size:var(--fs-micro);letter-spacing:.12em;
  text-transform:uppercase;color:var(--ink);margin-bottom:8px}
.field input,.field select,.field textarea{width:100%;background:var(--surface-2);
  border:1px solid var(--line);color:var(--ink);padding:14px 16px;font-family:var(--body);
  font-size:16px;border-radius:0;min-height:52px;
  transition:border-color var(--dur-hover) var(--ease-hover),
             background-color var(--dur-hover) var(--ease-hover)}
.field textarea{min-height:132px;resize:vertical}
@media (hover:hover){
  .field input:hover,.field select:hover,.field textarea:hover{border-color:var(--line-2)}
}
/* Fields use `:focus`, not `:focus-visible` — a pointer user clicking into a
   field needs the ring too. Same 3px accent weight as the sitewide ring, on a
   2px offset because 3px reads loose around a full-width input. */
.field input:focus,.field select:focus,.field textarea:focus{outline:3px solid var(--accent);
  outline-offset:2px;border-color:var(--accent);background-color:var(--bg)}
.field label{transition:color var(--dur-hover) var(--ease-hover)}
.field:focus-within label{color:var(--accent)}
.hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}
/* Wave 8 · §7c-6. The six honeypots are the only controls on the site that were
   not at the 16px iOS-zoom floor — they inherited 13.33px from the UA sheet. No
   human can focus a 1px field parked at -9999px, so this changes nothing a
   visitor sees and nothing a bot inspects (the trap is detected on position,
   display and name, never on type size). It exists so a §7c form sweep returns
   a clean zero instead of six explained exceptions. */
.hp input{font-size:16px}
.form .btn{justify-self:start}
@media (max-width:560px){.form .btn{width:100%}}
.form__note{font-size:.875rem;color:var(--muted)}

/* ── Map embed ───────────────────────────────────────────────────── */
.map{border:1px solid var(--line);aspect-ratio:16/9;width:100%;filter:grayscale(1) invert(.92) contrast(.85)}
@media (max-width:700px){.map{aspect-ratio:4/3}}

/* ── Review grid (§7b-4: three across, see-all always) ───────────── */
.reviews{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s2);margin-top:var(--s4)}
@media (max-width:940px){.reviews{grid-template-columns:1fr}}
.reviews:has(> .rev:only-child){grid-template-columns:minmax(0,620px);justify-content:center}
.reviews:has(> .rev:nth-child(2):last-child){grid-template-columns:repeat(2,minmax(0,430px));
  justify-content:center}
.rev{background:var(--surface-2);border:1px solid var(--line);padding:var(--s3);
  display:flex;flex-direction:column}
.rev .stars{color:var(--accent);letter-spacing:.12em;font-size:.9rem}
.rev p{margin-top:var(--s2);color:var(--ink);font-size:1rem}
.rev cite{margin-top:auto;padding-top:var(--s3);font-style:normal;font-family:var(--mono);
  font-size:var(--fs-micro);letter-spacing:.13em;text-transform:uppercase;color:var(--muted)}

/* ── Related / next-page rail ────────────────────────────────────── */
.related{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s2);margin-top:var(--s3)}
@media (max-width:880px){.related{grid-template-columns:1fr}}
.related a{position:relative;border:1px solid var(--line);padding:var(--s3);display:block;
  box-shadow:var(--shadow-rest);
  transition:border-color var(--dur-hover) var(--ease-hover),
             box-shadow var(--dur-hover) var(--ease-hover),
             translate var(--dur-hover) var(--ease-hover)}
.related a::before{content:'';position:absolute;inset:-1px -1px auto -1px;height:3px;
  background:var(--accent);transform:scaleX(0);transform-origin:left;
  transition:transform var(--dur-hover) var(--ease-hover)}
.related b{font-family:var(--display);font-size:1.1rem;text-transform:uppercase;display:block;
  transition:color var(--dur-hover) var(--ease-hover)}
.related span{font-family:var(--mono);font-size:var(--fs-micro);letter-spacing:.13em;text-transform:uppercase;
  color:var(--muted);display:block;margin-top:6px}
@media (hover:hover){
  .related a:hover{border-color:var(--accent);translate:0 -6px;
    box-shadow:var(--shadow-lift)}
  .related a:hover::before{transform:scaleX(1)}
  .related a:hover b{color:var(--accent)}
}
.related a:focus-visible::before{transform:scaleX(1)}
.related a:focus-visible b{color:var(--accent)}

/* ── Legal / long-form pages ─────────────────────────────────────── */
/* Left rail, not centred: the subhero above it (breadcrumb, H1, lede) is
   left-aligned, and a centred body column put the reading text on a different
   vertical axis from its own headline. Same measure, one axis. */
.legal{max-width:var(--measure);margin-inline:0}
/* Wave 2 · 2.6 — the two legal pages carried ZERO entrance reveals. Each H2 and
   its body now travel together as one block so the reveal reads as a section
   rising in, not as paragraphs popping one at a time. */
.legal__blk+.legal__blk{margin-top:var(--s4)}
.legal__blk>*+*{margin-top:var(--s3)}
/* (0,2,0) so it outranks the later `.legal h2` / `.prose h2` top margins and the
   block's own gap is the only space between sections. */
.legal__blk>:first-child{margin-top:0}
.legal h2{font-size:clamp(1.4rem,2.6vw,1.9rem);margin-top:var(--s4)}
.legal h3{font-size:1.1rem;margin-top:var(--s3)}
.legal p,.legal li{color:var(--muted);font-size:1rem}
.legal ul{padding-left:22px;margin-top:var(--s2)}
.legal li{margin-top:10px}
.legal a{color:var(--accent)}
.legal .updated{font-family:var(--mono);font-size:var(--fs-micro);letter-spacing:.13em;text-transform:uppercase;
  color:var(--muted)}

/* ── Last-updated stamp (§4d) ────────────────────────────────────── */
.updated{font-family:var(--mono);font-size:var(--fs-micro);letter-spacing:.13em;text-transform:uppercase;
  color:var(--muted)}

/* ═══════════════════════════════════════════════════════════════════
   WAVE 5 — PAGE REGISTERS (cross-page distinctiveness, 5.3)
   ───────────────────────────────────────────────────────────────────
   31 pages built from one spec read as one page with the nouns swapped.
   The fix is not decoration: this site genuinely speaks in five different
   voices, and Wave 2 verified them line by line.

     · the five football POSITION pages are written TO THE ATHLETE ("you")
     · /team-training is written TO A COACH
     · the six CITY pages answer one question: is the drive worth it
     · the two ADULT fitness pages sell a different buyer entirely (§12c,
       Wave 4's art-direction split)
     · everything else is coach-to-parent

   Each register gets its own hero rhythm — subhero height, H1 scale and
   measure, lede measure — sized to the copy that register actually carries.
   A 2-word athlete H1 ("Linebacker training") and an 8-word parent H1
   ("Speed & agility training for Edwardsville athletes") were setting in the
   same 20ch box at the same clamp, which is why every page looked the same.

   NOTHING here adds a colour, a font, a radius or an ornament. The locked
   direction is untouched: the only levers are scale, measure and height.
   ═══════════════════════════════════════════════════════════════════ */

/* ── The spec line ───────────────────────────────────────────────────
   A hairline mono rule carrying ONE real, already-published fact about the
   page it sits on. It is not a chip and not a badge: no fill, no border-box,
   no radius, just an accent rule and tracked mono, the same vocabulary as
   `.kick` and `.crumb`. Three registers use it and each puts it in a
   different place doing a different job, so it never becomes the new
   template:
     athlete → UNDER the H1 (the position's own drill spine, the athlete's
               headline, in the site's own published words)
     city    → UNDER the lede (the real route, because "is this drive worth
               it" is the only objection a city page has to beat)
     adult   → ABOVE the H1, replacing the section kick (declares the buyer
               in the first line, because the buyer is the whole question) */
.spec{
  font-family:var(--mono);font-weight:700;font-size:var(--fs-micro);letter-spacing:.16em;
  text-transform:uppercase;color:var(--ink);
  /* Wave 8: `center` put the accent bar halfway down a two-line spec, so on the
     two adult pages the rule floated between the lines instead of leading the
     first one. Anchored to the first line's optical centre instead. */
  display:flex;align-items:flex-start;gap:12px;
}
.spec::before{content:'';flex:0 0 26px;height:3px;background:var(--accent);margin-top:.68em}
.subhero--media .spec{text-shadow:0 1px 10px rgba(0,0,0,.85)}
@media (max-width:480px){.spec{font-size:var(--fs-micro);letter-spacing:.13em}}

/* ── ATHLETE — the five football position pages ──────────────────────
   Written to a 14-year-old. The H1s are two and three words and were
   setting SMALL inside a 20ch box built for an eight-word parent headline;
   at 1440 "Linebacker training" filled a third of its own line. It should
   hit like a locker-room poster. The hero is also shorter, because the
   athlete came for the drills, not for a photograph. */
.pg--athlete .subhero--media{min-height:min(64vh,560px);min-height:min(64svh,560px)}
.pg--athlete .subhero h1{font-size:clamp(2.9rem,9.2vw,6.6rem);max-width:12ch}
@media (max-width:480px){.pg--athlete .subhero h1{font-size:2.55rem}}
.pg--athlete .subhero .lede{max-width:46ch}
.pg--athlete .subhero .spec{margin-top:18px}
.pg--athlete .subhero .lede{margin-top:20px}

/* ── COACH — /team-training ──────────────────────────────────────────
   A head coach evaluating a supplier is not browsing. Shortest hero on the
   site, widest lede: he wants the spec paragraph, not the picture. */
.pg--coach .subhero--media{min-height:min(56vh,500px);min-height:min(56svh,500px)}
.pg--coach .subhero h1{font-size:clamp(2.3rem,5.6vw,4.3rem);max-width:17ch}
.pg--coach .subhero .lede{max-width:64ch}

/* ── CITY — the six metro-east pages ─────────────────────────────────
   One job: make a family in another town believe the drive is short. The
   town name is the headline, so it gets its own line (15ch), and the real
   route rides directly under the lede where the objection lives. */
.pg--city .subhero h1{max-width:15ch}
.pg--city .subhero .spec{margin-top:20px}

/* ── ADULT — /mens-fitness-training, /womens-fitness-training ────────
   Wave 4 split the art direction here (no youth photograph on an adult
   page). The type follows: a shorter hero over a room with nobody in it,
   and the audience declared in the first line above the headline. */
.pg--adult .subhero--media{min-height:min(54vh,480px);min-height:min(54svh,480px)}
.pg--adult .subhero h1{font-size:clamp(2.3rem,5.8vw,4.4rem);max-width:16ch}
.pg--adult .subhero .lede{max-width:52ch}
.pg--adult .subhero .spec{margin-top:16px;margin-bottom:2px}

/* ── SPORT — the five single-sport pages ─────────────────────────────
   Parent register, same voice as each other legitimately. Their headlines
   are the longest on the site ("Basketball speed training in the
   metro-east") and 20ch pushed them to three lines on a phone while leaving
   the desktop line half empty. 18ch sets them as two balanced lines at every
   breakpoint. Their distinctiveness is carried structurally, not here —
   each of the five ships a component and a section order its siblings
   do not (Wave 5 · 5.4). */
.pg--sport .subhero h1{font-size:clamp(2.4rem,6vw,4.6rem);max-width:18ch}

/* ── Wave 5 · 5.4 — component variants for the per-page elements ─────
   Three pages needed a shape the kit did not have yet. Every one of them
   is a GRID CHANGE on an existing component, not a new component: same
   borders, same accent, same 0px radius, same type ramp. */

/* A full-width numbered rule (basketball's four movements, the linebacker's
   five drills). In a `.mech__grid` column the list sets in one column beside
   a photograph; standing alone in a 1280px `.wrap` a single column would run
   the body copy well past the measure, so it breaks to two above 900px. */
.mech__list--wide{margin-top:var(--s4)}
@media (min-width:900px){
  .mech__list--wide{display:grid;grid-template-columns:1fr 1fr;
    column-gap:var(--s5);align-items:start}
  .mech__list--wide li:last-child{border-bottom:0}
  .mech__list--wide li:nth-last-child(-n+2){border-bottom:1px solid var(--line)}
}
.mech__list--wide p{max-width:var(--measure)}

/* Two-up variants. `.prices` and `.cards` are both `repeat(3,1fr)`; softball
   prices two real things and the defensive-back page compares exactly two
   jobs, and stretching either to three would mean inventing a third. */
.prices--pair{grid-template-columns:repeat(2,1fr);max-width:900px}
.cards--pair{grid-template-columns:repeat(2,1fr)}
@media (max-width:880px){.cards--pair{grid-template-columns:1fr}}
@media (max-width:940px){.prices--pair{grid-template-columns:1fr}}

/* The spec rule inherits its section's alignment. `.progs__hd` is centred. */
.progs__hd .spec{justify-content:center;margin-top:var(--s3)}

/* The spec rule inherits `.progs__hd`'s 62ch prose cap, which in Space Mono at
   .68rem is ~403px and broke a 52-character line in two. */
.progs__hd .spec{max-width:none}

/* ── Credential rule (offensive-line only) ───────────────────────────
   Eric Bergman's four published credentials are the strongest single fact
   on this site and they were buried in a paragraph. As a tall list inside
   one column of `.mech__grid` they left roughly 1200px of empty column
   beside a 422px photograph, so they run as a four-up rule under the grid
   instead: hairline dividers, mono ordinal, display-face label, no body
   copy. Nothing else on the site is shaped like it. */
.creds{list-style:none;margin:var(--s4) 0 0;padding:0;display:grid;
  grid-template-columns:repeat(4,1fr);
  border-top:1px solid var(--line);border-bottom:1px solid var(--line)}
.creds li{padding:var(--s3) var(--s2) var(--s3) var(--s2);border-left:1px solid var(--line)}
.creds li:first-child{border-left:0;padding-left:0}
.creds i{font-style:normal;font-family:var(--mono);font-size:var(--fs-micro);font-weight:700;
  letter-spacing:.14em;color:var(--accent)}
.creds b{display:block;margin-top:10px;font-family:var(--display);font-size:1.08rem;
  text-transform:uppercase;line-height:1.12;text-wrap:balance}
@media (max-width:900px){
  .creds{grid-template-columns:1fr 1fr}
  .creds li:nth-child(odd){border-left:0;padding-left:0}
  .creds li:nth-child(-n+2){border-bottom:1px solid var(--line)}
}
@media (max-width:520px){
  .creds{grid-template-columns:1fr}
  .creds li{border-left:0;padding-left:0;border-bottom:1px solid var(--line)}
  .creds li:last-child{border-bottom:0}
}

/* ── Hyphen-break guard (Wave 5, P-005 verification) ─────────────────
   `&nbsp;` binds a SPACE. It does nothing about the soft break a browser is
   allowed to take inside a hyphenated compound, and Oswald at 3.6rem in a
   660px prose column broke `travel-ball` after the hyphen: `TRAVEL-` on one
   line, `BALL CALENDAR` on the next. Wrapping the compound plus its bound
   partner in `white-space:nowrap` is the only thing that prevents it. */
.nb{white-space:nowrap}

/* ── Utility pages lead with the ACTION (Wave 7) ─────────────────────
   `/free-trial` and `/contact` are utility pages. The operator standard is
   that they do not open on a decorative hero: the form rides high and is the
   first thing under the headline on a phone, with the explanation and the
   source note beneath it rather than in front of it. Wave 5 put
   `body.pg--utility` on both pages and left the hook unused; this is the hook
   finally doing its job, and it is the reason `/free-trial` no longer costs a
   full screen of scroll to reach its own first field.

   Applied at EVERY width, not just the phone. Measured before the change,
   `/free-trial` put its first field 1.20 screens down on a 1440 desktop and
   1.07 screens down on a 430 phone; `/contact` put it 2.59 screens down. A
   rule that only fired on mobile would have left the desktop failure in
   place, so there is one code path rather than two.

   `order` reorders the flex line but NOT the DOM, so the tab order, the
   heading outline and the pixel's FormData all stay exactly as authored.
   Margins move to `gap` because `.prose>*+*` follows DOM order, not visual
   order, and would otherwise put the collapsed margin in the wrong place. */
.pg--utility .svc .prose{display:flex;flex-direction:column;gap:var(--s3)}
.pg--utility .svc .prose>*{margin-top:0;order:4}
.pg--utility .svc .prose>.kick{order:1}
.pg--utility .svc .prose>h2{order:2}
.pg--utility .svc .prose>form.form{order:3}

/* ── Wave 8 · the micro layer's phone budget ─────────────────────────
   Raising the whole Space Mono layer from ~11.8px to the 14px floor grew
   every button label by roughly 18%, and one of them ran out of road: the
   `#semi-private` aside CTA on `/membership` (`Start with the free session`,
   27 characters) measured 328px of label inside a 329px button at 430px and
   set on two lines. Rather than shorten a CTA to fit a stylesheet, the phone
   breakpoint gives every button 8px of its horizontal padding back. Measured
   after: 658 button labels across 31 pages at 430px, 0 wrapped. */
@media (max-width:560px){
  .btn{padding-inline:22px}
}

/* ── Wave 8 · §8.8 — the legal pages' dead right rail ─────────────────
   `/privacy` and `/terms` were the site's worst desktop dead space: the prose
   column is capped at `--measure` and sits on the subhero's left axis (Wave 2
   fixed that alignment deliberately), which left ~600px of empty black down the
   right of a 1440 screen. The answer is not to widen the measure — 73 characters
   is the readable line — and not to centre the column, which would break the
   axis again. It is to put something useful in the rail.

   A 12-section Privacy Policy and a 15-section Terms had no way to jump to a
   clause. The index is the existing `.aside` component, sticky, no new tokens,
   no new colour. Desktop only: on a phone a 15-item index in front of the text
   is a wall, and the reader can simply scroll. */
.legal__toc{display:none}
@media (min-width:1000px){
  .legal__wrap{display:grid;grid-template-columns:minmax(0,1fr) 280px;
    column-gap:var(--s4);align-items:start}
  .legal__wrap>.legal{grid-column:1;grid-row:1}
  .legal__toc{display:block;grid-column:2;grid-row:1;
    position:sticky;top:calc(var(--nav-h) + 20px)}
  .legal__toc h3{font-family:var(--mono);font-size:var(--fs-micro);font-weight:700;
    letter-spacing:.13em;text-transform:uppercase;color:var(--accent);margin-bottom:var(--s2)}
  .legal__toc ol{list-style:none;margin:0;padding:0;counter-reset:toc}
  .legal__toc li{counter-increment:toc;border-top:1px solid var(--line)}
  .legal__toc li:last-child{border-bottom:1px solid var(--line)}
  .legal__toc a{display:flex;gap:12px;align-items:baseline;min-height:44px;
    padding-block:11px;font-size:.9375rem;line-height:1.35;color:var(--muted);
    touch-action:manipulation;
    transition:color var(--dur-hover) var(--ease-hover)}
  .legal__toc a::before{content:counter(toc,decimal-leading-zero);
    font-family:var(--mono);font-size:var(--fs-micro);color:var(--accent);flex:none}
  .legal .legal__blk h2{scroll-margin-top:calc(var(--nav-h) + 24px)}
}
@media (hover:hover){.legal__toc a:hover{color:var(--ink)}}

/* The legal subhero carries a breadcrumb, an H1 and one line. With the standard
   `--s5` rhythm on both sides of it the page opened on 208px of empty black
   before the first clause. Same treatment `.pg--utility` already gets: the
   document starts sooner. */
.pg--legal .subhero{padding-block:calc(var(--nav-h) + var(--s4)) var(--s4)}
.pg--legal .subhero + .sec{padding-top:var(--s4)}


/* ============ Blog (content/blog → /blog/) — LocalCraft blog kit ============
   Append this block to the site's styles.css. Uses the site's existing
   tokens (--accent, --accent-2, --accent-3, --ink, --ink-soft, --ink-deep,
   --surface, --line, --s2..--s6, --radius, --radius-lg, --font-heading) and
   shared classes (.container, .eyebrow, .lede, .card, .card-grid, .offer-link,
   .band-surface, .page-hero, .ty-hero). No new tokens required. */
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
.blog-measure { max-width: 760px; }
.blog-post-head { padding-top: var(--s6); }
.blog-post-head h1 { font-size: clamp(2.1rem, 4.4vw, 3.2rem); line-height: 1.08; margin: 0 0 var(--s2); }
.blog-kicker-link { color: inherit; text-decoration: none; }
.blog-meta { color: var(--ink-soft); font-size: 0.95rem; }
.blog-hero-figure { margin: var(--s4) 0 0; }
.blog-hero-figure img { width: 100%; height: auto; border-radius: var(--radius-lg); display: block; box-shadow: 0 10px 34px rgba(22,8,32,0.10); }
.blog-prose { font-size: 1.075rem; line-height: 1.72; color: var(--ink); margin-top: var(--s4); }
.blog-prose > * + * { margin-top: 1.1em; }
.blog-prose h2 { font-size: clamp(1.5rem, 2.6vw, 1.9rem); line-height: 1.2; margin-top: 2em; }
.blog-prose h3 { font-size: 1.25rem; margin-top: 1.6em; }
.blog-prose a { color: var(--accent); text-underline-offset: 2px; }
.blog-prose ul, .blog-prose ol { padding-left: 1.3em; }
.blog-prose li + li { margin-top: 0.4em; }
.blog-prose blockquote { border-left: 4px solid var(--accent-3); padding-left: var(--s3); color: var(--ink-soft); font-style: italic; }
.blog-prose img { max-width: 100%; height: auto; border-radius: var(--radius); }
.blog-prose strong { color: var(--ink-deep); }
.blog-faq { max-width: 820px; }
.blog-faq-list { margin-top: var(--s4); display: flex; flex-direction: column; gap: 12px; }
.blog-faq-item { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.blog-faq-item summary { cursor: pointer; list-style: none; padding: var(--s3); font-weight: 600; font-family: var(--font-heading); display: flex; justify-content: space-between; align-items: center; gap: var(--s3); }
.blog-faq-item summary::-webkit-details-marker { display: none; }
.blog-faq-item summary::after { content: "+"; color: var(--accent); font-size: 1.4rem; line-height: 1; }
.blog-faq-item[open] summary::after { content: "−"; }
.blog-faq-a { padding: 0 var(--s3) var(--s3); color: var(--ink-soft); }
.blog-faq-a p { margin: 0; }
.blog-card .num { color: var(--accent-2); text-transform: none; letter-spacing: 0; font-weight: 600; }
